viyv-browser-mcp 0.10.2 → 0.11.0

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 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 = resolve3.call(this, root, ref);
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 resolve3(root, ref) {
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 resolve3(baseURI, relativeURI, options) {
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: resolve3,
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(resolve3, reject) {
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
- resolve3(buf);
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 readFileSync3 } from "fs";
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
 
@@ -11840,59 +11840,156 @@ function shouldBroadcastClosing(reason) {
11840
11840
  }
11841
11841
  }
11842
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
+
11843
11857
  // src/native-host-updater.ts
11844
- import {
11845
- chmodSync,
11846
- copyFileSync,
11847
- existsSync,
11848
- mkdirSync,
11849
- readFileSync,
11850
- renameSync,
11851
- unlinkSync
11852
- } 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";
11853
11864
  import { homedir } from "os";
11854
11865
  import { resolve } from "path";
11855
- import { fileURLToPath } from "url";
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
11856
11945
  var PKG_VERSION = (() => {
11857
- if (true) return "0.10.2";
11946
+ if (true) return "0.11.0";
11858
11947
  try {
11859
11948
  const here = fileURLToPath(import.meta.url);
11860
- const pkgPath = resolve(here, "..", "..", "package.json");
11861
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
11949
+ const pkgPath = resolve2(here, "..", "..", "package.json");
11950
+ const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
11862
11951
  return pkg.version ?? "0.0.0-test";
11863
11952
  } catch {
11864
11953
  return "0.0.0-test";
11865
11954
  }
11866
11955
  })();
11867
- var NATIVE_HOST_DIR = resolve(homedir(), ".viyv-browser", "native-host");
11868
11956
  function getSourceBinaryPath() {
11869
11957
  return fileURLToPath(import.meta.url);
11870
11958
  }
11871
11959
  function getNativeHostBinaryPath() {
11872
- return resolve(NATIVE_HOST_DIR, "index.js");
11960
+ return getDeploymentBinaryPath();
11873
11961
  }
11874
11962
  function isNativeHostDeployed() {
11875
- return existsSync(getNativeHostBinaryPath());
11963
+ return existsSync2(getNativeHostBinaryPath());
11876
11964
  }
11877
11965
  function syncNativeHostBinary() {
11878
11966
  const source = getSourceBinaryPath();
11879
- if (!existsSync(source)) {
11967
+ if (!existsSync2(source)) {
11880
11968
  throw new Error(`Source binary not found: ${source}`);
11881
11969
  }
11882
- mkdirSync(NATIVE_HOST_DIR, { recursive: true });
11970
+ const dir = getNativeHostDir();
11971
+ mkdirSync2(dir, { recursive: true });
11883
11972
  const target = getNativeHostBinaryPath();
11884
- const tmp = resolve(NATIVE_HOST_DIR, `index.js.tmp.${process.pid}`);
11973
+ const tmp = resolve2(dir, `index.js.tmp.${process.pid}`);
11885
11974
  try {
11886
11975
  copyFileSync(source, tmp);
11887
11976
  chmodSync(tmp, 493);
11888
- renameSync(tmp, target);
11977
+ renameSync2(tmp, target);
11889
11978
  } catch (err) {
11890
11979
  try {
11891
- unlinkSync(tmp);
11980
+ unlinkSync2(tmp);
11892
11981
  } catch {
11893
11982
  }
11894
11983
  throw err;
11895
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;
11896
11993
  }
11897
11994
  function shouldUpdateBridge(bridgeVersion) {
11898
11995
  return compareSemver(PKG_VERSION, bridgeVersion) > 0;
@@ -11900,20 +11997,11 @@ function shouldUpdateBridge(bridgeVersion) {
11900
11997
  function getPackageVersion() {
11901
11998
  return PKG_VERSION;
11902
11999
  }
11903
- function compareSemver(a, b) {
11904
- const pa = a.split(".").map(Number);
11905
- const pb = b.split(".").map(Number);
11906
- for (let i = 0; i < 3; i++) {
11907
- const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
11908
- if (diff !== 0) return diff;
11909
- }
11910
- return 0;
11911
- }
11912
12000
 
11913
12001
  // src/native-host/bridge-control.ts
11914
12002
  import { randomBytes } from "crypto";
11915
12003
  import { createServer } from "net";
11916
- import { chmodSync as chmodSync2, mkdirSync as mkdirSync2, writeFileSync } from "fs";
12004
+ import { chmodSync as chmodSync2, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
11917
12005
  import { dirname } from "path";
11918
12006
  var LOG_PREFIX = "[viyv-browser:native-host:control]";
11919
12007
  function startBridgeControl(opts) {
@@ -11923,9 +12011,9 @@ function startBridgeControl(opts) {
11923
12011
  const { deps, onError } = opts;
11924
12012
  let cookie;
11925
12013
  try {
11926
- mkdirSync2(dirname(cookiePath), { recursive: true });
12014
+ mkdirSync3(dirname(cookiePath), { recursive: true });
11927
12015
  cookie = randomBytes(BRIDGE.CONTROL_COOKIE_BYTES);
11928
- writeFileSync(cookiePath, cookie.toString("hex"), { mode: 384 });
12016
+ writeFileSync2(cookiePath, cookie.toString("hex"), { mode: 384 });
11929
12017
  try {
11930
12018
  chmodSync2(cookiePath, 384);
11931
12019
  } catch {
@@ -12063,7 +12151,7 @@ function startBridgeControl(opts) {
12063
12151
  }
12064
12152
 
12065
12153
  // src/native-host/bridge-relay.ts
12066
- import { readFileSync as readFileSync2 } from "fs";
12154
+ import { readFileSync as readFileSync3 } from "fs";
12067
12155
  import { createConnection } from "net";
12068
12156
  import { join } from "path";
12069
12157
 
@@ -12165,7 +12253,7 @@ function readProfileIdFromEnv() {
12165
12253
  const dir = process.env.VIYV_USER_DATA_DIR;
12166
12254
  if (!dir) return null;
12167
12255
  try {
12168
- const raw = readFileSync2(join(dir, "viyv-profile.id"), "utf8").trim();
12256
+ const raw = readFileSync3(join(dir, "viyv-profile.id"), "utf8").trim();
12169
12257
  return raw || null;
12170
12258
  } catch {
12171
12259
  return null;
@@ -12841,7 +12929,7 @@ function startBridge(options) {
12841
12929
  const primaryUserDataDir = process.env.VIYV_USER_DATA_DIR;
12842
12930
  if (primaryUserDataDir) {
12843
12931
  try {
12844
- const raw = readFileSync3(join2(primaryUserDataDir, "viyv-profile.id"), "utf8").trim();
12932
+ const raw = readFileSync4(join2(primaryUserDataDir, "viyv-profile.id"), "utf8").trim();
12845
12933
  if (raw) {
12846
12934
  setProfileId(primaryChromeId, raw);
12847
12935
  process.stderr.write(
@@ -12874,7 +12962,7 @@ function startBridge(options) {
12874
12962
 
12875
12963
  // src/server.ts
12876
12964
  import { randomUUID as randomUUID5 } from "crypto";
12877
- import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync4, statSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
12965
+ import { existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync5, statSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "fs";
12878
12966
  import http from "http";
12879
12967
  import { dirname as dirname2 } from "path";
12880
12968
 
@@ -24952,7 +25040,7 @@ var Protocol = class {
24952
25040
  return;
24953
25041
  }
24954
25042
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
24955
- await new Promise((resolve3) => setTimeout(resolve3, pollInterval));
25043
+ await new Promise((resolve4) => setTimeout(resolve4, pollInterval));
24956
25044
  options?.signal?.throwIfAborted();
24957
25045
  }
24958
25046
  } catch (error2) {
@@ -24969,7 +25057,7 @@ var Protocol = class {
24969
25057
  */
24970
25058
  request(request, resultSchema, options) {
24971
25059
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
24972
- return new Promise((resolve3, reject) => {
25060
+ return new Promise((resolve4, reject) => {
24973
25061
  const earlyReject = (error2) => {
24974
25062
  reject(error2);
24975
25063
  };
@@ -25047,7 +25135,7 @@ var Protocol = class {
25047
25135
  if (!parseResult.success) {
25048
25136
  reject(parseResult.error);
25049
25137
  } else {
25050
- resolve3(parseResult.data);
25138
+ resolve4(parseResult.data);
25051
25139
  }
25052
25140
  } catch (error2) {
25053
25141
  reject(error2);
@@ -25308,12 +25396,12 @@ var Protocol = class {
25308
25396
  }
25309
25397
  } catch {
25310
25398
  }
25311
- return new Promise((resolve3, reject) => {
25399
+ return new Promise((resolve4, reject) => {
25312
25400
  if (signal.aborted) {
25313
25401
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
25314
25402
  return;
25315
25403
  }
25316
- const timeoutId = setTimeout(resolve3, interval);
25404
+ const timeoutId = setTimeout(resolve4, interval);
25317
25405
  signal.addEventListener("abort", () => {
25318
25406
  clearTimeout(timeoutId);
25319
25407
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -26272,7 +26360,7 @@ var McpServer = class {
26272
26360
  let task = createTaskResult.task;
26273
26361
  const pollInterval = task.pollInterval ?? 5e3;
26274
26362
  while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
26275
- await new Promise((resolve3) => setTimeout(resolve3, pollInterval));
26363
+ await new Promise((resolve4) => setTimeout(resolve4, pollInterval));
26276
26364
  const updatedTask = await extra.taskStore.getTask(taskId);
26277
26365
  if (!updatedTask) {
26278
26366
  throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
@@ -27060,12 +27148,12 @@ var StdioServerTransport = class {
27060
27148
  this.onclose?.();
27061
27149
  }
27062
27150
  send(message2) {
27063
- return new Promise((resolve3) => {
27151
+ return new Promise((resolve4) => {
27064
27152
  const json = serializeMessage(message2);
27065
27153
  if (this._stdout.write(json)) {
27066
- resolve3();
27154
+ resolve4();
27067
27155
  } else {
27068
- this._stdout.once("drain", resolve3);
27156
+ this._stdout.once("drain", resolve4);
27069
27157
  }
27070
27158
  });
27071
27159
  }
@@ -27479,7 +27567,7 @@ var responseViaResponseObject = async (res, outgoing, options = {}) => {
27479
27567
  });
27480
27568
  if (!chunk) {
27481
27569
  if (i === 1) {
27482
- await new Promise((resolve3) => setTimeout(resolve3));
27570
+ await new Promise((resolve4) => setTimeout(resolve4));
27483
27571
  maxReadCount = 3;
27484
27572
  continue;
27485
27573
  }
@@ -27961,9 +28049,9 @@ data:
27961
28049
  const initRequest = messages.find((m) => isInitializeRequest(m));
27962
28050
  const clientProtocolVersion = initRequest ? initRequest.params.protocolVersion : req.headers.get("mcp-protocol-version") ?? DEFAULT_NEGOTIATED_PROTOCOL_VERSION;
27963
28051
  if (this._enableJsonResponse) {
27964
- return new Promise((resolve3) => {
28052
+ return new Promise((resolve4) => {
27965
28053
  this._streamMapping.set(streamId, {
27966
- resolveJson: resolve3,
28054
+ resolveJson: resolve4,
27967
28055
  cleanup: () => {
27968
28056
  this._streamMapping.delete(streamId);
27969
28057
  }
@@ -28485,11 +28573,11 @@ var BridgeClient = class {
28485
28573
  * is exhausted or `timeout` if the handshake takes too long.
28486
28574
  */
28487
28575
  enqueueToolCall(line) {
28488
- return new Promise((resolve3, reject) => {
28576
+ return new Promise((resolve4, reject) => {
28489
28577
  if (this.state === "ready" && this.socket && !this.socket.destroyed) {
28490
28578
  try {
28491
28579
  this.socket.write(line.endsWith(LINE_TERMINATOR) ? line : line + LINE_TERMINATOR);
28492
- resolve3();
28580
+ resolve4();
28493
28581
  } catch (err) {
28494
28582
  reject({ reason: "write-failed", detail: err instanceof Error ? err.message : String(err) });
28495
28583
  }
@@ -28498,7 +28586,7 @@ var BridgeClient = class {
28498
28586
  const enqueued = this.queue.enqueue({
28499
28587
  payload: {
28500
28588
  line: line.endsWith(LINE_TERMINATOR) ? line : line + LINE_TERMINATOR,
28501
- resolve: resolve3,
28589
+ resolve: resolve4,
28502
28590
  reject
28503
28591
  },
28504
28592
  onFlush: (entry) => {
@@ -28677,6 +28765,20 @@ function toolCallRejectionToErrorCode(rej) {
28677
28765
  }
28678
28766
  }
28679
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
+
28680
28782
  // ../shared/dist/bearer-equals.js
28681
28783
  import { timingSafeEqual } from "crypto";
28682
28784
  function bearerEquals(header, expectedToken) {
@@ -33585,13 +33687,17 @@ var smBundleSchema = external_exports.object({
33585
33687
  fetches: external_exports.array(external_exports.object({ fetch_id: external_exports.string() }).passthrough()),
33586
33688
  scenario: external_exports.object({ scenario_id: external_exports.string() }).passthrough()
33587
33689
  }).passthrough();
33690
+ var smRuntimeParamsSchema = external_exports.record(external_exports.union([external_exports.string(), external_exports.array(external_exports.string())])).optional();
33588
33691
  var smRunInlineOptionsSchema = external_exports.object({
33589
33692
  tabId: external_exports.coerce.number().optional(),
33590
33693
  tabStrategy: external_exports.enum(["reuse", "create", "create-then-close"]).optional(),
33591
33694
  chromeProfile: external_exports.string().optional(),
33592
33695
  timeoutMs: external_exports.coerce.number().int().positive().optional(),
33593
33696
  abortSignal: external_exports.string().optional(),
33594
- dryRun: external_exports.boolean().optional()
33697
+ dryRun: external_exports.boolean().optional(),
33698
+ // F3: scenario-wide cartesian-product cap. Omitted = runtime default
33699
+ // (SAFETY.DEFAULT_MAX_TOTAL_ITERATIONS = 1000). Batch callers override.
33700
+ maxTotalIterations: external_exports.coerce.number().int().positive().optional()
33595
33701
  }).optional();
33596
33702
  var smRunInlineTool = {
33597
33703
  name: "sm_run_inline",
@@ -33602,8 +33708,12 @@ var smRunInlineTool = {
33602
33708
  related: SM_RUN_INLINE_RELATED,
33603
33709
  inputSchema: external_exports.object({
33604
33710
  bundle: smBundleSchema.describe("Self-contained SM definitions to execute"),
33605
- params: external_exports.record(external_exports.unknown()).optional().describe("Scenario parameter values"),
33606
- options: smRunInlineOptionsSchema.describe("Tab / timeout / abort / dry-run")
33711
+ params: smRuntimeParamsSchema.describe(
33712
+ "Scenario parameter values (string or string[]; numeric/boolean params pass as strings)"
33713
+ ),
33714
+ options: smRunInlineOptionsSchema.describe(
33715
+ "Tab / timeout / abort / dry-run / maxTotalIterations"
33716
+ )
33607
33717
  })
33608
33718
  };
33609
33719
  var smProbeInlineTool = {
@@ -33616,12 +33726,19 @@ var smProbeInlineTool = {
33616
33726
  inputSchema: external_exports.object({
33617
33727
  bundle: smBundleSchema.describe("Bundle whose target locators to probe"),
33618
33728
  targetIds: external_exports.array(external_exports.string()).optional().describe("Subset of target_ids to probe; default = all targets in the bundle"),
33729
+ // F3: probe also exercises scenario.params resolution so callers get a
33730
+ // `missing-params` / `invalid-param-value` signal before committing to
33731
+ // a full run. Probe itself runs no scenario steps.
33732
+ params: smRuntimeParamsSchema.describe(
33733
+ "Scenario parameter values for resolution-only validation (probe does not execute steps)"
33734
+ ),
33619
33735
  options: external_exports.object({
33620
33736
  tabId: external_exports.coerce.number().optional(),
33621
33737
  tabStrategy: external_exports.enum(["reuse", "create", "create-then-close"]).optional(),
33622
33738
  chromeProfile: external_exports.string().optional(),
33623
33739
  timeoutMs: external_exports.coerce.number().int().positive().optional(),
33624
- abortSignal: external_exports.string().optional()
33740
+ abortSignal: external_exports.string().optional(),
33741
+ maxTotalIterations: external_exports.coerce.number().int().positive().optional()
33625
33742
  }).optional()
33626
33743
  })
33627
33744
  };
@@ -34205,6 +34322,7 @@ function computeToolTimeout(tool, input) {
34205
34322
  var pendingRequests = /* @__PURE__ */ new Map();
34206
34323
  var bridgeClient = null;
34207
34324
  var bridgeUpdateInfo = null;
34325
+ var shutdownGate = createShutdownGateState();
34208
34326
  function getBridgeSocket() {
34209
34327
  return bridgeClient?.getSocket() ?? null;
34210
34328
  }
@@ -34325,7 +34443,7 @@ function coerceShape(shape) {
34325
34443
  return result;
34326
34444
  }
34327
34445
  function handleFileExport(filePath, result) {
34328
- mkdirSync3(dirname2(filePath), { recursive: true });
34446
+ mkdirSync4(dirname2(filePath), { recursive: true });
34329
34447
  let content;
34330
34448
  let metadata;
34331
34449
  if (typeof result.data === "string") {
@@ -34345,7 +34463,7 @@ function handleFileExport(filePath, result) {
34345
34463
  const pages = Array.isArray(result.pages) ? result.pages : [];
34346
34464
  metadata = { ok: true, file_path: filePath, page_count: pages.length };
34347
34465
  }
34348
- writeFileSync2(filePath, content, "utf-8");
34466
+ writeFileSync3(filePath, content, "utf-8");
34349
34467
  metadata.file_size = Buffer.byteLength(content, "utf-8");
34350
34468
  return metadata;
34351
34469
  }
@@ -34733,7 +34851,7 @@ async function handleStreamableHttpRequest(req, res, sessions2) {
34733
34851
  }
34734
34852
  var MAX_BODY_SIZE = 4 * 1024 * 1024;
34735
34853
  function parseJsonBody(req) {
34736
- return new Promise((resolve3, reject) => {
34854
+ return new Promise((resolve4, reject) => {
34737
34855
  const chunks = [];
34738
34856
  let size = 0;
34739
34857
  let destroyed = false;
@@ -34751,7 +34869,7 @@ function parseJsonBody(req) {
34751
34869
  req.on("end", () => {
34752
34870
  if (destroyed) return;
34753
34871
  try {
34754
- resolve3(JSON.parse(Buffer.concat(chunks).toString("utf-8")));
34872
+ resolve4(JSON.parse(Buffer.concat(chunks).toString("utf-8")));
34755
34873
  } catch (error2) {
34756
34874
  reject(new Error(`Invalid JSON: ${error2.message}`));
34757
34875
  }
@@ -34919,15 +35037,24 @@ function handleExtensionMessage(message2) {
34919
35037
  const bridgeVersion = typeof msg.bridgeVersion === "string" ? msg.bridgeVersion : null;
34920
35038
  if (bridgeVersion && shouldUpdateBridge(bridgeVersion)) {
34921
35039
  const serverVersion = getPackageVersion();
34922
- process.stderr.write(
34923
- `[viyv-browser:mcp] Bridge version mismatch (bridge=${bridgeVersion}, server=${serverVersion}), requesting restart
35040
+ const nowMs = Date.now();
35041
+ if (shouldSendShutdownRequest(shutdownGate, bridgeVersion, nowMs)) {
35042
+ process.stderr.write(
35043
+ `[viyv-browser:mcp] Bridge version mismatch (bridge=${bridgeVersion}, server=${serverVersion}), requesting restart
34924
35044
  `
34925
- );
34926
- bridgeUpdateInfo = { from: bridgeVersion, to: serverVersion };
34927
- const shutdownSock = getBridgeSocket();
34928
- if (shutdownSock) {
34929
- shutdownSock.write(
34930
- `${JSON.stringify({ type: "bridge_shutdown_request", timestamp: Date.now() })}
35045
+ );
35046
+ bridgeUpdateInfo = { from: bridgeVersion, to: serverVersion };
35047
+ const shutdownSock = getBridgeSocket();
35048
+ if (shutdownSock) {
35049
+ shutdownSock.write(
35050
+ `${JSON.stringify({ type: "bridge_shutdown_request", timestamp: nowMs })}
35051
+ `
35052
+ );
35053
+ shutdownGate = markSent(shutdownGate, bridgeVersion, nowMs);
35054
+ }
35055
+ } else {
35056
+ process.stderr.write(
35057
+ `[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)
34931
35058
  `
34932
35059
  );
34933
35060
  }
@@ -35070,7 +35197,7 @@ async function callExtensionTool(tool, input) {
35070
35197
  if (paths) {
35071
35198
  for (const p of paths) {
35072
35199
  try {
35073
- if (!existsSync2(p) || !statSync(p).isFile()) {
35200
+ if (!existsSync3(p) || !statSync(p).isFile()) {
35074
35201
  return {
35075
35202
  content: [
35076
35203
  {
@@ -35102,7 +35229,7 @@ async function callExtensionTool(tool, input) {
35102
35229
  let csvData;
35103
35230
  if (typeof input.file_path === "string") {
35104
35231
  const fp = input.file_path;
35105
- if (!existsSync2(fp) || !statSync(fp).isFile()) {
35232
+ if (!existsSync3(fp) || !statSync(fp).isFile()) {
35106
35233
  return {
35107
35234
  content: [
35108
35235
  {
@@ -35114,7 +35241,7 @@ async function callExtensionTool(tool, input) {
35114
35241
  ]
35115
35242
  };
35116
35243
  }
35117
- csvData = readFileSync4(fp, "utf-8");
35244
+ csvData = readFileSync5(fp, "utf-8");
35118
35245
  } else if (typeof input.data === "string") {
35119
35246
  csvData = input.data;
35120
35247
  }
@@ -35160,13 +35287,13 @@ async function callExtensionTool(tool, input) {
35160
35287
  touchSession(agentId);
35161
35288
  const client = bridgeClient;
35162
35289
  const toolTimeout = computeToolTimeout(tool, input);
35163
- return new Promise((resolve3) => {
35290
+ return new Promise((resolve4) => {
35164
35291
  const removeErrorListener = () => {
35165
35292
  };
35166
35293
  const timer = setTimeout(() => {
35167
35294
  pendingRequests.delete(requestId);
35168
35295
  removeErrorListener();
35169
- resolve3({
35296
+ resolve4({
35170
35297
  content: [
35171
35298
  {
35172
35299
  type: "text",
@@ -35193,12 +35320,12 @@ async function callExtensionTool(tool, input) {
35193
35320
  if (FILE_EXPORT_TOOLS.has(tool) && typeof filePath === "string" && ("data" in result || "pages" in result)) {
35194
35321
  try {
35195
35322
  const metadata = handleFileExport(filePath, result);
35196
- resolve3({
35323
+ resolve4({
35197
35324
  content: [{ type: "text", text: JSON.stringify(metadata) }]
35198
35325
  });
35199
35326
  } catch (e) {
35200
35327
  const msg = e instanceof Error ? e.message : String(e);
35201
- resolve3({
35328
+ resolve4({
35202
35329
  content: [
35203
35330
  {
35204
35331
  type: "text",
@@ -35217,9 +35344,9 @@ async function callExtensionTool(tool, input) {
35217
35344
  if (RESULT_EXPORT_TOOLS.has(tool) && typeof filePath === "string") {
35218
35345
  try {
35219
35346
  const content = JSON.stringify(result, null, 2);
35220
- mkdirSync3(dirname2(filePath), { recursive: true });
35221
- writeFileSync2(filePath, content, "utf-8");
35222
- resolve3({
35347
+ mkdirSync4(dirname2(filePath), { recursive: true });
35348
+ writeFileSync3(filePath, content, "utf-8");
35349
+ resolve4({
35223
35350
  content: [
35224
35351
  {
35225
35352
  type: "text",
@@ -35232,7 +35359,7 @@ async function callExtensionTool(tool, input) {
35232
35359
  });
35233
35360
  } catch (e) {
35234
35361
  const msg = e instanceof Error ? e.message : String(e);
35235
- resolve3({
35362
+ resolve4({
35236
35363
  content: [
35237
35364
  {
35238
35365
  type: "text",
@@ -35259,21 +35386,21 @@ async function callExtensionTool(tool, input) {
35259
35386
  }
35260
35387
  if (tempImportFile) {
35261
35388
  try {
35262
- unlinkSync2(tempImportFile);
35389
+ unlinkSync3(tempImportFile);
35263
35390
  } catch {
35264
35391
  }
35265
35392
  }
35266
- resolve3({ content: buildResponseContent(tool, finalResult) });
35393
+ resolve4({ content: buildResponseContent(tool, finalResult) });
35267
35394
  },
35268
35395
  reject: (error2) => {
35269
35396
  removeErrorListener();
35270
35397
  if (tempImportFile) {
35271
35398
  try {
35272
- unlinkSync2(tempImportFile);
35399
+ unlinkSync3(tempImportFile);
35273
35400
  } catch {
35274
35401
  }
35275
35402
  }
35276
- resolve3({
35403
+ resolve4({
35277
35404
  content: [
35278
35405
  {
35279
35406
  type: "text",
@@ -35314,7 +35441,7 @@ async function callExtensionTool(tool, input) {
35314
35441
  clearTimeout(pending.timer);
35315
35442
  pendingRequests.delete(requestId);
35316
35443
  const { code, message: message2 } = toolCallRejectionToErrorCode(rej);
35317
- resolve3({
35444
+ resolve4({
35318
35445
  content: [{ type: "text", text: JSON.stringify({ error: { code, message: message2 } }) }]
35319
35446
  });
35320
35447
  });
@@ -35338,12 +35465,12 @@ async function handleSwitchBrowser() {
35338
35465
  setExtensionConnected(false);
35339
35466
  }
35340
35467
  process.stderr.write("[viyv-browser:mcp] switch_browser: waiting for bridge reconnection...\n");
35341
- return new Promise((resolve3) => {
35468
+ return new Promise((resolve4) => {
35342
35469
  const checkInterval = setInterval(() => {
35343
35470
  if (bridgeClient?.isReady() && isExtensionConnected()) {
35344
35471
  clearInterval(checkInterval);
35345
35472
  clearTimeout(timer);
35346
- resolve3({
35473
+ resolve4({
35347
35474
  content: [
35348
35475
  {
35349
35476
  type: "text",
@@ -35358,7 +35485,7 @@ async function handleSwitchBrowser() {
35358
35485
  }, 500);
35359
35486
  const timer = setTimeout(() => {
35360
35487
  clearInterval(checkInterval);
35361
- resolve3({
35488
+ resolve4({
35362
35489
  content: [
35363
35490
  {
35364
35491
  type: "text",
@@ -35377,9 +35504,9 @@ async function handleSwitchBrowser() {
35377
35504
 
35378
35505
  // src/setup.ts
35379
35506
  import { execSync } from "child_process";
35380
- import { chmodSync as chmodSync3, existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
35507
+ import { chmodSync as chmodSync3, existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
35381
35508
  import { homedir as homedir2, platform } from "os";
35382
- import { dirname as dirname3, resolve as resolve2 } from "path";
35509
+ import { dirname as dirname3, resolve as resolve3 } from "path";
35383
35510
  function runSetup(options = {}) {
35384
35511
  const os = platform();
35385
35512
  console.log("Viyv Browser MCP - Native Messaging Host Setup");
@@ -35411,8 +35538,8 @@ function runSetup(options = {}) {
35411
35538
  console.log(`Wrapper: ${wrapperPath}`);
35412
35539
  for (const manifestPath of manifestPaths) {
35413
35540
  console.log(`Manifest path: ${manifestPath}`);
35414
- mkdirSync4(dirname3(manifestPath), { recursive: true });
35415
- writeFileSync3(manifestPath, JSON.stringify(manifest, null, 2));
35541
+ mkdirSync5(dirname3(manifestPath), { recursive: true });
35542
+ writeFileSync4(manifestPath, JSON.stringify(manifest, null, 2));
35416
35543
  chmodSync3(manifestPath, 420);
35417
35544
  }
35418
35545
  console.log("\nNative Messaging Host registered successfully!");
@@ -35426,17 +35553,17 @@ function runSetup(options = {}) {
35426
35553
  }
35427
35554
  function createNativeHostWrapper(os, binaryPath, dockerMode) {
35428
35555
  const manifestDir = dockerMode ? "/etc/chromium/native-messaging-hosts" : dirname3(getManifestPath(os));
35429
- mkdirSync4(manifestDir, { recursive: true });
35556
+ mkdirSync5(manifestDir, { recursive: true });
35430
35557
  const nodePath = getNodePath();
35431
35558
  if (os === "win32") {
35432
- const wrapperPath2 = resolve2(manifestDir, `${NATIVE_HOST_NAME}.bat`);
35433
- writeFileSync3(wrapperPath2, `@echo off\r
35559
+ const wrapperPath2 = resolve3(manifestDir, `${NATIVE_HOST_NAME}.bat`);
35560
+ writeFileSync4(wrapperPath2, `@echo off\r
35434
35561
  "${nodePath}" "${binaryPath}" --native-host\r
35435
35562
  `);
35436
35563
  return wrapperPath2;
35437
35564
  }
35438
- const wrapperPath = resolve2(manifestDir, `${NATIVE_HOST_NAME}.sh`);
35439
- writeFileSync3(wrapperPath, `#!/bin/bash
35565
+ const wrapperPath = resolve3(manifestDir, `${NATIVE_HOST_NAME}.sh`);
35566
+ writeFileSync4(wrapperPath, `#!/bin/bash
35440
35567
  exec "${nodePath}" "${binaryPath}" --native-host
35441
35568
  `);
35442
35569
  chmodSync3(wrapperPath, 493);
@@ -35451,23 +35578,23 @@ function getNodePath() {
35451
35578
  }
35452
35579
  function getDockerManifestPaths() {
35453
35580
  return [
35454
- resolve2("/etc/chromium/native-messaging-hosts", `${NATIVE_HOST_NAME}.json`),
35455
- resolve2(homedir2(), ".config/chromium/NativeMessagingHosts", `${NATIVE_HOST_NAME}.json`)
35581
+ resolve3("/etc/chromium/native-messaging-hosts", `${NATIVE_HOST_NAME}.json`),
35582
+ resolve3(homedir2(), ".config/chromium/NativeMessagingHosts", `${NATIVE_HOST_NAME}.json`)
35456
35583
  ];
35457
35584
  }
35458
35585
  function getManifestPath(os) {
35459
35586
  const home = homedir2();
35460
35587
  switch (os) {
35461
35588
  case "darwin":
35462
- return resolve2(
35589
+ return resolve3(
35463
35590
  home,
35464
35591
  "Library/Application Support/Google/Chrome/NativeMessagingHosts",
35465
35592
  `${NATIVE_HOST_NAME}.json`
35466
35593
  );
35467
35594
  case "linux":
35468
- return resolve2(home, ".config/google-chrome/NativeMessagingHosts", `${NATIVE_HOST_NAME}.json`);
35595
+ return resolve3(home, ".config/google-chrome/NativeMessagingHosts", `${NATIVE_HOST_NAME}.json`);
35469
35596
  case "win32":
35470
- return resolve2(
35597
+ return resolve3(
35471
35598
  home,
35472
35599
  "AppData/Local/Google/Chrome/User Data/NativeMessagingHosts",
35473
35600
  `${NATIVE_HOST_NAME}.json`
@@ -35487,9 +35614,9 @@ function setupClaudeDesktopConfig() {
35487
35614
  const configPath = getClaudeDesktopConfigPath();
35488
35615
  console.log(`Config path: ${configPath}`);
35489
35616
  let config2 = {};
35490
- if (existsSync3(configPath)) {
35617
+ if (existsSync4(configPath)) {
35491
35618
  try {
35492
- config2 = JSON.parse(readFileSync5(configPath, "utf-8"));
35619
+ config2 = JSON.parse(readFileSync6(configPath, "utf-8"));
35493
35620
  } catch {
35494
35621
  console.error(
35495
35622
  `ERROR: Failed to parse ${configPath}. Fix the JSON manually or delete the file.`
@@ -35509,8 +35636,8 @@ function setupClaudeDesktopConfig() {
35509
35636
  env: { PATH: envPath }
35510
35637
  };
35511
35638
  const configDir = dirname3(configPath);
35512
- mkdirSync4(configDir, { recursive: true });
35513
- writeFileSync3(configPath, JSON.stringify(config2, null, 2));
35639
+ mkdirSync5(configDir, { recursive: true });
35640
+ writeFileSync4(configPath, JSON.stringify(config2, null, 2));
35514
35641
  console.log(`npx path: ${npxPath}`);
35515
35642
  console.log("\nClaude Desktop config updated successfully!");
35516
35643
  console.log("Please restart Claude Desktop for changes to take effect.");
@@ -35527,13 +35654,13 @@ function getClaudeDesktopConfigPath() {
35527
35654
  const home = homedir2();
35528
35655
  switch (process.platform) {
35529
35656
  case "darwin":
35530
- return resolve2(home, "Library/Application Support/Claude/claude_desktop_config.json");
35657
+ return resolve3(home, "Library/Application Support/Claude/claude_desktop_config.json");
35531
35658
  case "linux":
35532
- return resolve2(home, ".config/Claude/claude_desktop_config.json");
35659
+ return resolve3(home, ".config/Claude/claude_desktop_config.json");
35533
35660
  case "win32":
35534
- return resolve2(home, "AppData/Roaming/Claude/claude_desktop_config.json");
35661
+ return resolve3(home, "AppData/Roaming/Claude/claude_desktop_config.json");
35535
35662
  default:
35536
- return resolve2(home, ".config/Claude/claude_desktop_config.json");
35663
+ return resolve3(home, ".config/Claude/claude_desktop_config.json");
35537
35664
  }
35538
35665
  }
35539
35666
 
@@ -35575,7 +35702,18 @@ if (args.includes("setup")) {
35575
35702
  const authBearer = authIdx >= 0 ? args[authIdx + 1] : process.env.VIYV_MCP_AUTH_BEARER;
35576
35703
  if (isNativeHostDeployed()) {
35577
35704
  try {
35578
- syncNativeHostBinary();
35705
+ const decision = syncNativeHostBinaryIfNeeded();
35706
+ if (decision.shouldSync) {
35707
+ process.stderr.write(
35708
+ `[viyv-browser:mcp] Native host binary synced (self=${decision.selfVersion}, was=${decision.deployedVersion ?? "unknown"}, reason=${decision.reason})
35709
+ `
35710
+ );
35711
+ } else {
35712
+ process.stderr.write(
35713
+ `[viyv-browser:mcp] Native host sync skipped \u2014 deployed v${decision.deployedVersion} is newer than self v${decision.selfVersion} (preventing downgrade by orphan MCP)
35714
+ `
35715
+ );
35716
+ }
35579
35717
  } catch (err) {
35580
35718
  process.stderr.write(
35581
35719
  `[viyv-browser:mcp] WARNING: Failed to update native host binary: ${err.message}