ghost-bridge 0.8.0 → 0.9.1

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/server.js CHANGED
@@ -27102,8 +27102,8 @@ var Protocol = class {
27102
27102
  *
27103
27103
  * The Protocol object assumes ownership of the Transport, replacing any callbacks that have already been set, and expects that it is the only user of the Transport instance going forward.
27104
27104
  */
27105
- async connect(transport2) {
27106
- this._transport = transport2;
27105
+ async connect(transport) {
27106
+ this._transport = transport;
27107
27107
  const _onclose = this.transport?.onclose;
27108
27108
  this._transport.onclose = () => {
27109
27109
  _onclose?.();
@@ -28523,6 +28523,7 @@ import net from "net";
28523
28523
  import fs2 from "fs";
28524
28524
  import os from "os";
28525
28525
  import path2 from "path";
28526
+ import { spawn } from "child_process";
28526
28527
  import { fileURLToPath as fileURLToPath2 } from "url";
28527
28528
 
28528
28529
  // lib/version.js
@@ -28540,15 +28541,18 @@ var BASE_PORT = Number(process.env.GHOST_BRIDGE_PORT || 33333);
28540
28541
  var DEFAULT_WS_TOKEN = "ghost-bridge-local";
28541
28542
  var WS_TOKEN = process.env.GHOST_BRIDGE_TOKEN || DEFAULT_WS_TOKEN;
28542
28543
  var RESPONSE_TIMEOUT = 8e3;
28543
- var PORT_INFO_FILE = path2.join(os.tmpdir(), "ghost-bridge-port.json");
28544
+ var PORT_INFO_FILE = process.env.GHOST_BRIDGE_PORT_INFO || path2.join(os.tmpdir(), "ghost-bridge-port.json");
28544
28545
  var SERVER_STARTED_AT = (/* @__PURE__ */ new Date()).toISOString();
28545
28546
  var SERVER_ENTRY_PATH = fileURLToPath2(import.meta.url);
28547
+ var IS_DAEMON = process.env.GHOST_BRIDGE_DAEMON === "1";
28546
28548
  var chromeConnection = null;
28547
28549
  var activeConnection = null;
28548
28550
  var actualPort = BASE_PORT;
28549
28551
  var isMainInstance = false;
28550
28552
  var pendingRequests = /* @__PURE__ */ new Map();
28551
28553
  var mcpClients = /* @__PURE__ */ new Set();
28554
+ var mcpClientSeq = 0;
28555
+ var LOCAL_CLIENT_ID = "local";
28552
28556
  function log(msg) {
28553
28557
  console.error(`[ghost-bridge] ${msg}`);
28554
28558
  }
@@ -28561,7 +28565,8 @@ function buildIdentityPayload() {
28561
28565
  port: actualPort,
28562
28566
  version: GHOST_BRIDGE_VERSION,
28563
28567
  serverPath: SERVER_ENTRY_PATH,
28564
- startedAt: SERVER_STARTED_AT
28568
+ startedAt: SERVER_STARTED_AT,
28569
+ capabilities: ["heartbeat"]
28565
28570
  };
28566
28571
  }
28567
28572
  function getServiceMismatch(probe) {
@@ -28695,17 +28700,24 @@ function isPortAvailable(port) {
28695
28700
  server2.listen(port);
28696
28701
  });
28697
28702
  }
28698
- async function startWebSocketServer() {
28699
- const available = await isPortAvailable(BASE_PORT);
28700
- if (!available) {
28701
- throw new Error(`\u56FA\u5B9A\u7AEF\u53E3 ${BASE_PORT} \u4E0D\u53EF\u7528\uFF0C\u8BF7\u91CA\u653E\u8BE5\u7AEF\u53E3\u6216\u901A\u8FC7 GHOST_BRIDGE_PORT \u6307\u5B9A\u5176\u4ED6\u7AEF\u53E3`);
28702
- }
28703
- actualPort = BASE_PORT;
28704
- const wss2 = new import_websocket_server.default({ port: BASE_PORT });
28705
- log(`\u{1F680} WebSocket \u670D\u52A1\u5DF2\u542F\u52A8\uFF0C\u7AEF\u53E3 ${BASE_PORT}${WS_TOKEN ? "\uFF08\u542F\u7528 token \u6821\u9A8C\uFF09" : ""}`);
28706
- return wss2;
28703
+ function startWebSocketServer(port) {
28704
+ return new Promise((resolve, reject) => {
28705
+ let settled = false;
28706
+ const wss2 = new import_websocket_server.default({ port }, () => {
28707
+ if (settled) return;
28708
+ settled = true;
28709
+ wss2.removeListener("error", onError);
28710
+ resolve(wss2);
28711
+ });
28712
+ const onError = (err) => {
28713
+ if (settled) return;
28714
+ settled = true;
28715
+ reject(err);
28716
+ };
28717
+ wss2.on("error", onError);
28718
+ });
28707
28719
  }
28708
- async function initWebSocketService() {
28720
+ async function acquirePortForDaemon() {
28709
28721
  const existing = getExistingService();
28710
28722
  if (existing) {
28711
28723
  log(`\u68C0\u6D4B\u5230\u73B0\u6709\u670D\u52A1 (PID: ${existing.pid}, \u7AEF\u53E3: ${existing.port})\uFF0C\u9A8C\u8BC1\u4E2D...`);
@@ -28715,17 +28727,15 @@ async function initWebSocketService() {
28715
28727
  const mismatch = getServiceMismatch(probe);
28716
28728
  if (!mismatch) {
28717
28729
  actualPort = existing.port;
28718
- isMainInstance = false;
28719
- log(`\u2705 \u590D\u7528\u73B0\u6709\u670D\u52A1\uFF0C\u7AEF\u53E3 ${actualPort}`);
28720
- return null;
28730
+ log(`\u2705 \u5DF2\u6709\u5E38\u9A7B\u670D\u52A1\u5728\u8FD0\u884C\uFF0C\u65E0\u9700\u91CD\u590D\u542F\u52A8`);
28731
+ return { alreadyRunning: true };
28721
28732
  }
28722
28733
  await stopExistingService(Number(probe.pid) || existing.pid, existing.port, mismatch);
28723
28734
  } else {
28724
- const oldPid = Number(probe.pid) || existing.pid;
28725
- await stopExistingService(oldPid, existing.port, "token \u4E0D\u4E00\u81F4");
28735
+ await stopExistingService(Number(probe.pid) || existing.pid, existing.port, "token \u4E0D\u4E00\u81F4");
28726
28736
  }
28727
28737
  } else {
28728
- log(`\u274C \u73B0\u6709\u670D\u52A1\u9A8C\u8BC1\u5931\u8D25\uFF0C\u542F\u52A8\u65B0\u670D\u52A1...`);
28738
+ log(`\u274C \u73B0\u6709\u670D\u52A1\u9A8C\u8BC1\u5931\u8D25\uFF0C\u51C6\u5907\u63A5\u7BA1...`);
28729
28739
  try {
28730
28740
  fs2.unlinkSync(PORT_INFO_FILE);
28731
28741
  } catch {
@@ -28739,9 +28749,8 @@ async function initWebSocketService() {
28739
28749
  const mismatch = getServiceMismatch(probe);
28740
28750
  if (!mismatch) {
28741
28751
  actualPort = BASE_PORT;
28742
- isMainInstance = false;
28743
- log(`\u2705 \u590D\u7528\u56FA\u5B9A\u7AEF\u53E3\u4E0A\u7684\u73B0\u6709\u670D\u52A1\uFF0C\u7AEF\u53E3 ${actualPort}`);
28744
- return null;
28752
+ log(`\u2705 \u56FA\u5B9A\u7AEF\u53E3\u4E0A\u5DF2\u6709\u5E38\u9A7B\u670D\u52A1\u5728\u8FD0\u884C\uFF0C\u65E0\u9700\u91CD\u590D\u542F\u52A8`);
28753
+ return { alreadyRunning: true };
28745
28754
  }
28746
28755
  await stopExistingService(Number(probe.pid), BASE_PORT, mismatch);
28747
28756
  } else {
@@ -28752,8 +28761,34 @@ async function initWebSocketService() {
28752
28761
  throw new Error(`\u56FA\u5B9A\u7AEF\u53E3 ${BASE_PORT} \u5DF2\u88AB\u5176\u4ED6\u8FDB\u7A0B\u5360\u7528\uFF0C\u8BF7\u91CA\u653E\u8BE5\u7AEF\u53E3\u6216\u901A\u8FC7 GHOST_BRIDGE_PORT \u6307\u5B9A\u5176\u4ED6\u7AEF\u53E3`);
28753
28762
  }
28754
28763
  }
28755
- const wss2 = await startWebSocketServer();
28764
+ let wss2;
28765
+ try {
28766
+ wss2 = await startWebSocketServer(BASE_PORT);
28767
+ } catch (err) {
28768
+ const probe = await probeExistingService(BASE_PORT);
28769
+ if (probe?.service === "ghost-bridge" && probe.token === WS_TOKEN && !getServiceMismatch(probe)) {
28770
+ actualPort = BASE_PORT;
28771
+ log(`\u2705 \u53E6\u4E00\u4E2A\u5E38\u9A7B\u670D\u52A1\u5DF2\u62A2\u5148\u542F\u52A8\uFF0C\u672C\u8FDB\u7A0B\u9000\u51FA`);
28772
+ return { alreadyRunning: true };
28773
+ }
28774
+ throw err;
28775
+ }
28776
+ log(`\u{1F680} WebSocket \u670D\u52A1\u5DF2\u542F\u52A8\uFF0C\u7AEF\u53E3 ${BASE_PORT}${WS_TOKEN ? "\uFF08\u542F\u7528 token \u6821\u9A8C\uFF09" : ""}`);
28777
+ actualPort = BASE_PORT;
28756
28778
  isMainInstance = true;
28779
+ return { wss: wss2 };
28780
+ }
28781
+ async function runDaemon() {
28782
+ let acquired;
28783
+ try {
28784
+ acquired = await acquirePortForDaemon();
28785
+ } catch (e) {
28786
+ log(`\u274C daemon \u542F\u52A8\u5931\u8D25\uFF1A${e.message}`);
28787
+ process.exit(1);
28788
+ }
28789
+ if (acquired.alreadyRunning) {
28790
+ process.exit(0);
28791
+ }
28757
28792
  fs2.writeFileSync(
28758
28793
  PORT_INFO_FILE,
28759
28794
  JSON.stringify({
@@ -28766,11 +28801,55 @@ async function initWebSocketService() {
28766
28801
  }, null, 2)
28767
28802
  );
28768
28803
  log(`\u{1F4DD} \u7AEF\u53E3\u4FE1\u606F\u5DF2\u5199\u5165: ${PORT_INFO_FILE}`);
28769
- return wss2;
28804
+ return acquired.wss;
28770
28805
  }
28771
- var wss = await initWebSocketService();
28806
+ async function ensureDaemonRunning() {
28807
+ const existing = getExistingService();
28808
+ if (existing) {
28809
+ const probe = await probeExistingService(existing.port);
28810
+ if (probe?.service === "ghost-bridge" && probe.token === WS_TOKEN && !getServiceMismatch(probe)) {
28811
+ actualPort = Number(probe.port) || existing.port;
28812
+ return true;
28813
+ }
28814
+ } else {
28815
+ const probe = await probeExistingService(BASE_PORT);
28816
+ if (probe?.service === "ghost-bridge" && probe.token === WS_TOKEN && !getServiceMismatch(probe)) {
28817
+ actualPort = BASE_PORT;
28818
+ return true;
28819
+ }
28820
+ }
28821
+ return spawnDaemonAndWait();
28822
+ }
28823
+ async function spawnDaemonAndWait(timeoutMs = 15e3) {
28824
+ log("\u{1F6E0} \u5E38\u9A7B\u670D\u52A1\u672A\u8FD0\u884C\uFF0C\u6B63\u5728\u542F\u52A8 daemon...");
28825
+ const child = spawn(process.execPath, [SERVER_ENTRY_PATH], {
28826
+ detached: true,
28827
+ stdio: "ignore",
28828
+ env: { ...process.env, GHOST_BRIDGE_DAEMON: "1" }
28829
+ });
28830
+ child.unref();
28831
+ const deadline = Date.now() + timeoutMs;
28832
+ while (Date.now() < deadline) {
28833
+ const probe = await probeExistingService(BASE_PORT);
28834
+ if (probe?.service === "ghost-bridge" && probe.token === WS_TOKEN && !getServiceMismatch(probe)) {
28835
+ actualPort = Number(probe.port) || BASE_PORT;
28836
+ log(`\u2705 daemon \u5DF2\u5C31\u7EEA (PID: ${probe.pid}, \u7AEF\u53E3: ${actualPort})`);
28837
+ return true;
28838
+ }
28839
+ await sleep(250);
28840
+ }
28841
+ log(`\u274C daemon \u672A\u80FD\u5728 ${timeoutMs}ms \u5185\u5C31\u7EEA\uFF0C\u7A0D\u540E\u5C06\u91CD\u8BD5`);
28842
+ return false;
28843
+ }
28844
+ var wss = IS_DAEMON ? await runDaemon() : null;
28845
+ var wsPingInterval = null;
28846
+ var WS_PING_INTERVAL_MS = 3e4;
28772
28847
  if (wss) {
28773
28848
  wss.on("connection", (ws, req) => {
28849
+ ws.isAlive = true;
28850
+ ws.on("pong", () => {
28851
+ ws.isAlive = true;
28852
+ });
28774
28853
  const url2 = new URL(req.url || "/", "http://localhost");
28775
28854
  const token = url2.searchParams.get("token") || "";
28776
28855
  const role = url2.searchParams.get("role") || "";
@@ -28786,7 +28865,9 @@ if (wss) {
28786
28865
  }
28787
28866
  log(`\u8FDE\u63A5\u9A8C\u8BC1\u901A\u8FC7 (token: ${token})`);
28788
28867
  if (role === "mcp-client") {
28789
- log("\u{1F4E1} MCP \u5BA2\u6237\u7AEF\u5DF2\u8FDE\u63A5");
28868
+ const clientId = `c${++mcpClientSeq}`;
28869
+ ws.ghostClientId = clientId;
28870
+ log(`\u{1F4E1} MCP \u5BA2\u6237\u7AEF\u5DF2\u8FDE\u63A5 (${clientId})`);
28790
28871
  mcpClients.add(ws);
28791
28872
  ws.send(JSON.stringify(buildIdentityPayload()));
28792
28873
  ws.on("message", (data) => {
@@ -28814,15 +28895,26 @@ if (wss) {
28814
28895
  return;
28815
28896
  }
28816
28897
  if (msg.id) {
28817
- pendingRequests.set(msg.id, { source: ws });
28898
+ const wireId = `${clientId}:${msg.id}`;
28899
+ pendingRequests.set(wireId, { source: ws, originalId: msg.id });
28900
+ chromeConnection.send(JSON.stringify({
28901
+ id: wireId,
28902
+ command: msg.command,
28903
+ params: msg.params,
28904
+ clientId,
28905
+ ...WS_TOKEN ? { token: WS_TOKEN } : {}
28906
+ }));
28907
+ } else {
28908
+ chromeConnection.send(data);
28818
28909
  }
28819
- chromeConnection.send(data);
28820
28910
  } catch {
28821
28911
  }
28822
28912
  });
28823
28913
  ws.on("close", () => {
28824
- log("\u{1F4E1} MCP \u5BA2\u6237\u7AEF\u5DF2\u65AD\u5F00");
28914
+ log(`\u{1F4E1} MCP \u5BA2\u6237\u7AEF\u5DF2\u65AD\u5F00 (${ws.ghostClientId})`);
28825
28915
  mcpClients.delete(ws);
28916
+ askChrome("_clientDisconnected", { clientId: ws.ghostClientId }, { timeoutMs: 3e3 }).catch(() => {
28917
+ });
28826
28918
  });
28827
28919
  } else {
28828
28920
  if (chromeConnection && chromeConnection !== ws && chromeConnection.readyState === import_websocket.default.OPEN) {
@@ -28840,11 +28932,17 @@ if (wss) {
28840
28932
  ws.on("message", (data) => {
28841
28933
  try {
28842
28934
  const msg = JSON.parse(data.toString());
28935
+ if (msg.type === "heartbeat") {
28936
+ ws.send(JSON.stringify({ type: "heartbeat_ack", ts: msg.ts || Date.now() }));
28937
+ return;
28938
+ }
28843
28939
  if (msg.id && pendingRequests.has(msg.id)) {
28844
28940
  const pending = pendingRequests.get(msg.id);
28845
- if (pending.source && pending.source.readyState === import_websocket.default.OPEN) {
28941
+ if (pending.source) {
28846
28942
  pendingRequests.delete(msg.id);
28847
- pending.source.send(data);
28943
+ if (pending.source.readyState === import_websocket.default.OPEN) {
28944
+ pending.source.send(JSON.stringify({ ...msg, id: pending.originalId }));
28945
+ }
28848
28946
  return;
28849
28947
  }
28850
28948
  }
@@ -28852,31 +28950,55 @@ if (wss) {
28852
28950
  }
28853
28951
  handleIncoming(data);
28854
28952
  });
28855
- ws.on("close", () => {
28856
- log("\u{1F310} Chrome \u8FDE\u63A5\u5DF2\u5173\u95ED");
28953
+ ws.on("close", (code, reason) => {
28954
+ const reasonText = reason ? reason.toString() : "";
28955
+ log(`\u{1F310} Chrome \u8FDE\u63A5\u5DF2\u5173\u95ED (code=${code}${reasonText ? ` reason="${reasonText}"` : ""})`);
28956
+ if (chromeConnection !== ws) {
28957
+ log("\u5FFD\u7565\u65E7 Chrome \u8FDE\u63A5\u7684\u5173\u95ED\u4E8B\u4EF6");
28958
+ return;
28959
+ }
28857
28960
  chromeConnection = null;
28858
- activeConnection = null;
28961
+ if (activeConnection === ws) {
28962
+ activeConnection = null;
28963
+ }
28859
28964
  failAllPending("Chrome \u8FDE\u63A5\u65AD\u5F00");
28860
28965
  });
28966
+ ws.on("error", (err) => {
28967
+ log(`\u{1F310} Chrome \u8FDE\u63A5\u9519\u8BEF: ${err.message}`);
28968
+ });
28861
28969
  }
28862
28970
  });
28971
+ wsPingInterval = setInterval(() => {
28972
+ for (const client of wss.clients) {
28973
+ if (client.isAlive === false) {
28974
+ log("\u{1F310} \u5FC3\u8DF3\u8D85\u65F6\uFF0C\u4E3B\u52A8\u65AD\u5F00\u6B7B\u8FDE\u63A5");
28975
+ client.terminate();
28976
+ continue;
28977
+ }
28978
+ client.isAlive = false;
28979
+ try {
28980
+ client.ping();
28981
+ } catch {
28982
+ }
28983
+ }
28984
+ }, WS_PING_INTERVAL_MS);
28863
28985
  } else {
28864
- log(`\u{1F4E1} \u4F5C\u4E3A\u5BA2\u6237\u7AEF\u8FDE\u63A5\u5230\u4E3B\u5B9E\u4F8B (\u7AEF\u53E3 ${actualPort})...`);
28986
+ const ready = await ensureDaemonRunning();
28987
+ if (!ready) log("\u26A0\uFE0F \u5E38\u9A7B\u670D\u52A1\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u5C06\u4FDD\u6301\u540E\u53F0\u91CD\u8BD5\u5E76\u81EA\u52A8\u62C9\u8D77");
28988
+ log(`\u{1F4E1} \u4F5C\u4E3A\u5BA2\u6237\u7AEF\u8FDE\u63A5\u5230\u5E38\u9A7B\u670D\u52A1 (\u7AEF\u53E3: ${actualPort})...`);
28865
28989
  connectToMainInstance();
28866
28990
  }
28867
- var MAX_RECONNECT_ATTEMPTS = 10;
28868
28991
  var RECONNECT_INTERVAL = 3e3;
28869
- var reconnectAttempts = 0;
28870
- var wasEverConnected = false;
28992
+ var DAEMON_CHECK_EVERY = 5;
28993
+ var consecutiveFailures = 0;
28871
28994
  function connectToMainInstance() {
28872
28995
  const url2 = new URL(`ws://localhost:${actualPort}`);
28873
28996
  url2.searchParams.set("token", WS_TOKEN);
28874
28997
  url2.searchParams.set("role", "mcp-client");
28875
28998
  const ws = new import_websocket.default(url2.toString());
28876
28999
  ws.on("open", () => {
28877
- log(`\u2705 \u5DF2\u8FDE\u63A5\u5230\u4E3B\u5B9E\u4F8B (\u7AEF\u53E3 ${actualPort})`);
28878
- reconnectAttempts = 0;
28879
- wasEverConnected = true;
29000
+ log(`\u2705 \u5DF2\u8FDE\u63A5\u5230\u5E38\u9A7B\u670D\u52A1 (\u7AEF\u53E3: ${actualPort})`);
29001
+ consecutiveFailures = 0;
28880
29002
  });
28881
29003
  ws.on("message", (data) => {
28882
29004
  try {
@@ -28891,30 +29013,25 @@ function connectToMainInstance() {
28891
29013
  }
28892
29014
  });
28893
29015
  ws.on("close", () => {
28894
- log("\u26A0\uFE0F \u4E0E\u4E3B\u5B9E\u4F8B\u7684\u8FDE\u63A5\u5DF2\u65AD\u5F00");
29016
+ log("\u26A0\uFE0F \u4E0E\u5E38\u9A7B\u670D\u52A1\u7684\u8FDE\u63A5\u5DF2\u65AD\u5F00");
28895
29017
  activeConnection = null;
28896
- failAllPending("\u4E0E\u4E3B\u5B9E\u4F8B\u7684\u8FDE\u63A5\u5DF2\u65AD\u5F00");
28897
- reconnectAttempts++;
28898
- if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
28899
- log(`\u274C \u91CD\u8FDE\u5931\u8D25\u6B21\u6570\u8FC7\u591A (${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})\uFF0C\u5BA2\u6237\u7AEF\u8FDB\u7A0B\u9000\u51FA`);
28900
- process.exit(0);
28901
- }
28902
- setTimeout(() => {
28903
- if (!activeConnection) {
28904
- log(`\u{1F504} \u5C1D\u8BD5\u91CD\u65B0\u8FDE\u63A5\u5230\u4E3B\u5B9E\u4F8B... (${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`);
28905
- connectToMainInstance();
28906
- }
29018
+ failAllPending("\u4E0E\u5E38\u9A7B\u670D\u52A1\u7684\u8FDE\u63A5\u5DF2\u65AD\u5F00");
29019
+ consecutiveFailures++;
29020
+ const attempt = consecutiveFailures;
29021
+ const needDaemonCheck = attempt % DAEMON_CHECK_EVERY === 0;
29022
+ setTimeout(async () => {
29023
+ if (activeConnection) return;
29024
+ if (needDaemonCheck) {
29025
+ log(`\u{1F504} \u8FDE\u7EED ${attempt} \u6B21\u672A\u8FDE\u4E0A\u5E38\u9A7B\u670D\u52A1\uFF0C\u68C0\u67E5\u5E76\u5C1D\u8BD5\u62C9\u8D77...`);
29026
+ await ensureDaemonRunning();
29027
+ }
29028
+ if (activeConnection) return;
29029
+ log(`\u{1F504} \u5C1D\u8BD5\u91CD\u65B0\u8FDE\u63A5\u5230\u5E38\u9A7B\u670D\u52A1 (\u7B2C ${attempt} \u6B21)...`);
29030
+ connectToMainInstance();
28907
29031
  }, RECONNECT_INTERVAL);
28908
29032
  });
28909
29033
  ws.on("error", (err) => {
28910
- log(`\u274C \u8FDE\u63A5\u4E3B\u5B9E\u4F8B\u5931\u8D25: ${err.message}`);
28911
- if (!wasEverConnected) {
28912
- reconnectAttempts++;
28913
- if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
28914
- log(`\u274C \u65E0\u6CD5\u8FDE\u63A5\u5230\u4E3B\u5B9E\u4F8B\uFF0C\u5BA2\u6237\u7AEF\u8FDB\u7A0B\u9000\u51FA`);
28915
- process.exit(0);
28916
- }
28917
- }
29034
+ log(`\u274C \u8FDE\u63A5\u5E38\u9A7B\u670D\u52A1\u5931\u8D25: ${err.message}`);
28918
29035
  });
28919
29036
  }
28920
29037
  function failAllPending(message) {
@@ -28949,7 +29066,7 @@ function handleIncoming(data) {
28949
29066
  else resolve(result);
28950
29067
  }
28951
29068
  async function askMainInstance(command, params = {}) {
28952
- if (!activeConnection) throw new Error("\u672A\u8FDE\u63A5\u5230\u4E3B\u5B9E\u4F8B");
29069
+ if (!activeConnection) throw new Error("\u672A\u8FDE\u63A5\u5230\u5E38\u9A7B\u670D\u52A1");
28953
29070
  const id = crypto.randomUUID();
28954
29071
  const payload = { id, command, params };
28955
29072
  return new Promise((resolve, reject) => {
@@ -28968,9 +29085,13 @@ async function askMainInstance(command, params = {}) {
28968
29085
  });
28969
29086
  }
28970
29087
  async function askChrome(command, params = {}, options = {}) {
28971
- if (!activeConnection) throw new Error("Chrome \u672A\u8FDE\u63A5\uFF0C\u8BF7\u786E\u8BA4\u6D4F\u89C8\u5668\u5F00\u542F\u4E14\u6269\u5C55\u5DF2\u542F\u7528");
29088
+ if (!activeConnection) {
29089
+ throw new Error(
29090
+ IS_DAEMON ? "Chrome \u672A\u8FDE\u63A5\uFF0C\u8BF7\u786E\u8BA4\u6D4F\u89C8\u5668\u5F00\u542F\u4E14\u6269\u5C55\u5DF2\u542F\u7528" : "ghost-bridge \u5E38\u9A7B\u670D\u52A1\u672A\u8FDE\u63A5\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\uFF08\u53EF\u7528 ghost-bridge status \u68C0\u67E5\uFF09"
29091
+ );
29092
+ }
28972
29093
  const id = crypto.randomUUID();
28973
- const payload = { id, command, params };
29094
+ const payload = { id, command, params, clientId: LOCAL_CLIENT_ID };
28974
29095
  if (WS_TOKEN) payload.token = WS_TOKEN;
28975
29096
  const timeoutMs = options.timeoutMs || RESPONSE_TIMEOUT;
28976
29097
  return new Promise((resolve, reject) => {
@@ -28991,6 +29112,10 @@ async function askChrome(command, params = {}, options = {}) {
28991
29112
  function jsonText(data) {
28992
29113
  return typeof data === "string" ? data : JSON.stringify(data);
28993
29114
  }
29115
+ var TARGET_ARG = {
29116
+ type: "string",
29117
+ description: "\u547D\u540D\u6D4F\u89C8\u5668\u76EE\u6807\uFF0C\u5982 cases/app\u3002\u5148\u7528 bind_tab \u7ED1\u5B9A\uFF1B\u4E0D\u6307\u5B9A\u5219\u4F7F\u7528\u9ED8\u8BA4 focused/pinned \u76EE\u6807\u3002\u7ED1\u5B9A\u4E0E pin \u5747\u6309\u4F1A\u8BDD\u9694\u79BB"
29118
+ };
28994
29119
  function buildSnippet(source, line, column, { beautifyEnabled = true, contextLines = 20 } = {}) {
28995
29120
  const result = {};
28996
29121
  if (!source) {
@@ -29038,6 +29163,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29038
29163
  inputSchema: {
29039
29164
  type: "object",
29040
29165
  properties: {
29166
+ target: TARGET_ARG,
29041
29167
  selector: {
29042
29168
  type: "string",
29043
29169
  description: "CSS \u9009\u62E9\u5668\uFF0C\u9650\u5B9A\u5206\u6790\u8303\u56F4\u3002\u4E0D\u6307\u5B9A\u5219\u5206\u6790\u6574\u4E2A\u9875\u9762"
@@ -29063,6 +29189,36 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29063
29189
  description: "\u5217\u51FA\u5F53\u524D Chrome \u6807\u7B7E\u9875\uFF0C\u5E76\u8FD4\u56DE Ghost Bridge \u5F53\u524D\u76EE\u6807\u6A21\u5F0F\u4E0E\u76EE\u6807\u6807\u7B7E\u9875\u3002",
29064
29190
  inputSchema: { type: "object", properties: {} }
29065
29191
  },
29192
+ {
29193
+ name: "bind_tab",
29194
+ description: "\u6309 tabId\u3001URL \u7247\u6BB5\u6216\u6807\u9898\u7247\u6BB5\u628A Chrome \u6807\u7B7E\u9875\u7ED1\u5B9A\u4E3A\u547D\u540D target\uFF0C\u4F8B\u5982 cases \u6216 app\u3002target \u547D\u540D\u7A7A\u95F4\u6309 MCP \u4F1A\u8BDD\u9694\u79BB\uFF1A\u591A\u4E2A Agent / \u540C\u4E00 Agent \u7684\u591A\u4E2A\u4F1A\u8BDD\u5404\u81EA\u7ED1\u5B9A\u7684 target\uFF08\u542B\u540C\u540D\uFF09\u4E92\u4E0D\u5E72\u6270\u3002",
29195
+ inputSchema: {
29196
+ type: "object",
29197
+ properties: {
29198
+ name: { type: "string", description: "target \u540D\u79F0\uFF0C\u5982 cases/app\u3002\u53EA\u80FD\u5305\u542B\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\u548C\u8FDE\u5B57\u7B26" },
29199
+ tabId: { type: "number", description: "Chrome \u6807\u7B7E\u9875 ID\uFF0C\u4F18\u5148\u4F7F\u7528" },
29200
+ urlContains: { type: "string", description: "URL \u7247\u6BB5\u3002\u672A\u63D0\u4F9B tabId \u65F6\u6309\u5B83\u5339\u914D" },
29201
+ titleContains: { type: "string", description: "\u6807\u9898\u7247\u6BB5\u3002\u53EF\u4E0E urlContains \u540C\u65F6\u4F7F\u7528" }
29202
+ },
29203
+ required: ["name"]
29204
+ }
29205
+ },
29206
+ {
29207
+ name: "unbind_tab",
29208
+ description: "\u89E3\u7ED1\u4E00\u4E2A\u547D\u540D target\u3002\u82E5\u8BE5 tab \u6CA1\u6709\u5176\u4ED6 target \u5F15\u7528\uFF0C\u4F1A\u91CA\u653E\u5BF9\u5E94 debugger session\u3002",
29209
+ inputSchema: {
29210
+ type: "object",
29211
+ properties: {
29212
+ name: { type: "string", description: "\u8981\u89E3\u7ED1\u7684 target \u540D\u79F0" }
29213
+ },
29214
+ required: ["name"]
29215
+ }
29216
+ },
29217
+ {
29218
+ name: "list_targets",
29219
+ description: "\u67E5\u770B\u5F53\u524D\u5DF2\u7ED1\u5B9A\u7684\u547D\u540D targets\uFF0C\u4EE5\u53CA\u9ED8\u8BA4 focused/pinned \u76EE\u6807\u72B6\u6001\u3002",
29220
+ inputSchema: { type: "object", properties: {} }
29221
+ },
29066
29222
  {
29067
29223
  name: "get_target_tab",
29068
29224
  description: "\u67E5\u770B Ghost Bridge \u5F53\u524D\u64CD\u4F5C\u76EE\u6807\u3002\u76EE\u6807\u6A21\u5F0F\u4E3A focused \u65F6\u8DDF\u968F\u5F53\u524D\u805A\u7126\u6807\u7B7E\u9875\uFF1Bpinned \u65F6\u9501\u5B9A\u6307\u5B9A\u6807\u7B7E\u9875\u3002",
@@ -29075,7 +29231,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29075
29231
  },
29076
29232
  {
29077
29233
  name: "pin_tab",
29078
- description: "\u6309 tabId\u3001URL \u7247\u6BB5\u6216\u6807\u9898\u7247\u6BB5\u9501\u5B9A Chrome \u6807\u7B7E\u9875\u3002",
29234
+ description: "\u6309 tabId\u3001URL \u7247\u6BB5\u6216\u6807\u9898\u7247\u6BB5\u9501\u5B9A Chrome \u6807\u7B7E\u9875\u3002pin \u72B6\u6001\u6309 MCP \u4F1A\u8BDD\u9694\u79BB\uFF1A\u591A\u4E2A Agent / \u591A\u4E2A\u4F1A\u8BDD\u5404\u81EA pin \u7684\u9875\u9762\u4E92\u4E0D\u8986\u76D6\u3002",
29079
29235
  inputSchema: {
29080
29236
  type: "object",
29081
29237
  properties: {
@@ -29096,6 +29252,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29096
29252
  inputSchema: {
29097
29253
  type: "object",
29098
29254
  properties: {
29255
+ target: TARGET_ARG,
29099
29256
  severity: {
29100
29257
  type: "string",
29101
29258
  enum: ["error", "warn", "info", "all"],
@@ -29114,6 +29271,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29114
29271
  inputSchema: {
29115
29272
  type: "object",
29116
29273
  properties: {
29274
+ target: TARGET_ARG,
29117
29275
  scriptUrlContains: { type: "string" },
29118
29276
  line: { type: "number" },
29119
29277
  column: { type: "number" },
@@ -29128,6 +29286,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29128
29286
  inputSchema: {
29129
29287
  type: "object",
29130
29288
  properties: {
29289
+ target: TARGET_ARG,
29131
29290
  durationMs: { type: "number", description: "\u9ED8\u8BA4 1500ms" }
29132
29291
  }
29133
29292
  }
@@ -29138,6 +29297,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29138
29297
  inputSchema: {
29139
29298
  type: "object",
29140
29299
  properties: {
29300
+ target: TARGET_ARG,
29141
29301
  query: { type: "string" },
29142
29302
  scriptUrlContains: { type: "string" },
29143
29303
  maxMatches: { type: "number" }
@@ -29148,14 +29308,14 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29148
29308
  {
29149
29309
  name: "symbolic_hints",
29150
29310
  description: "\u6536\u96C6\u9875\u9762\u7684\u8D44\u6E90\u3001\u5168\u5C40\u7B26\u53F7\u4E0E UA/URL \u7EBF\u7D22\uFF0C\u5E2E\u52A9\u63A8\u65AD\u7248\u672C\u4E0E\u6A21\u5757\u5F52\u5C5E",
29151
- inputSchema: { type: "object", properties: {} }
29311
+ inputSchema: { type: "object", properties: { target: TARGET_ARG } }
29152
29312
  },
29153
29313
  {
29154
29314
  name: "eval_script",
29155
29315
  description: "\u5728\u76EE\u6807\u6807\u7B7E\u9875\u6267\u884C\u53EA\u8BFB JS \u8868\u8FBE\u5F0F\uFF08\u8C28\u614E\u4F7F\u7528\uFF09",
29156
29316
  inputSchema: {
29157
29317
  type: "object",
29158
- properties: { code: { type: "string" } },
29318
+ properties: { target: TARGET_ARG, code: { type: "string" } },
29159
29319
  required: ["code"]
29160
29320
  }
29161
29321
  },
@@ -29165,6 +29325,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29165
29325
  inputSchema: {
29166
29326
  type: "object",
29167
29327
  properties: {
29328
+ target: TARGET_ARG,
29168
29329
  filter: { type: "string", description: "URL \u5173\u952E\u8BCD\u8FC7\u6EE4" },
29169
29330
  method: { type: "string", description: "\u8BF7\u6C42\u65B9\u6CD5\uFF1AGET/POST/PUT/DELETE \u7B49" },
29170
29331
  status: { type: "string", description: "\u72B6\u6001\uFF1Asuccess/error/failed/pending" },
@@ -29184,6 +29345,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29184
29345
  inputSchema: {
29185
29346
  type: "object",
29186
29347
  properties: {
29348
+ target: TARGET_ARG,
29187
29349
  requestId: { type: "string", description: "\u8BF7\u6C42 ID\uFF08\u4ECE list_network_requests \u83B7\u53D6\uFF09" },
29188
29350
  includeBody: { type: "boolean", description: "\u662F\u5426\u5305\u542B\u54CD\u5E94\u4F53\uFF0C\u9ED8\u8BA4 false" }
29189
29351
  },
@@ -29193,7 +29355,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29193
29355
  {
29194
29356
  name: "clear_network_requests",
29195
29357
  description: "\u6E05\u7A7A\u5DF2\u6355\u83B7\u7684\u7F51\u7EDC\u8BF7\u6C42\u8BB0\u5F55",
29196
- inputSchema: { type: "object", properties: {} }
29358
+ inputSchema: { type: "object", properties: { target: TARGET_ARG } }
29197
29359
  },
29198
29360
  {
29199
29361
  name: "perf_metrics",
@@ -29201,6 +29363,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29201
29363
  inputSchema: {
29202
29364
  type: "object",
29203
29365
  properties: {
29366
+ target: TARGET_ARG,
29204
29367
  includeTimings: {
29205
29368
  type: "boolean",
29206
29369
  description: "\u662F\u5426\u5305\u542B Navigation Timing \u548C Web Vitals\uFF0C\u9ED8\u8BA4 true"
@@ -29218,6 +29381,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29218
29381
  inputSchema: {
29219
29382
  type: "object",
29220
29383
  properties: {
29384
+ target: TARGET_ARG,
29221
29385
  format: {
29222
29386
  type: "string",
29223
29387
  enum: ["png", "jpeg"],
@@ -29250,6 +29414,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29250
29414
  inputSchema: {
29251
29415
  type: "object",
29252
29416
  properties: {
29417
+ target: TARGET_ARG,
29253
29418
  mode: {
29254
29419
  type: "string",
29255
29420
  enum: ["text", "html", "structured"],
@@ -29276,6 +29441,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29276
29441
  inputSchema: {
29277
29442
  type: "object",
29278
29443
  properties: {
29444
+ target: TARGET_ARG,
29279
29445
  selector: {
29280
29446
  type: "string",
29281
29447
  description: "CSS \u9009\u62E9\u5668\uFF0C\u9650\u5B9A\u626B\u63CF\u8303\u56F4\u3002\u4E0D\u6307\u5B9A\u5219\u626B\u63CF\u6574\u4E2A\u9875\u9762"
@@ -29297,6 +29463,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29297
29463
  inputSchema: {
29298
29464
  type: "object",
29299
29465
  properties: {
29466
+ target: TARGET_ARG,
29300
29467
  ref: {
29301
29468
  type: "string",
29302
29469
  description: "\u76EE\u6807\u5143\u7D20\u7684 ref \u6807\u8BC6\uFF0C\u5982 'e1'\u3001'e5'\uFF08\u4ECE get_interactive_snapshot \u83B7\u53D6\uFF09"
@@ -29337,8 +29504,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
29337
29504
  const args = request.params.arguments || {};
29338
29505
  try {
29339
29506
  if (name === "inspect_page") {
29340
- const { selector, includeInteractive = true, maxElements = 30 } = args;
29507
+ const { target, selector, includeInteractive = true, maxElements = 30 } = args;
29341
29508
  const snapshot = await askChrome("inspectPageSnapshot", {
29509
+ target,
29342
29510
  selector,
29343
29511
  includeInteractive,
29344
29512
  maxElements
@@ -29399,7 +29567,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
29399
29567
  text: jsonText({
29400
29568
  service: "ghost-bridge",
29401
29569
  version: GHOST_BRIDGE_VERSION,
29402
- role: isMainInstance ? "\u4E3B\u5B9E\u4F8B (WebSocket Server)" : "\u5BA2\u6237\u7AEF (\u8FDE\u63A5\u5230\u4E3B\u5B9E\u4F8B)",
29570
+ role: isMainInstance ? "daemon (\u5E38\u9A7B WebSocket \u670D\u52A1)" : "\u4F1A\u8BDD\u5BA2\u6237\u7AEF (\u8FDE\u63A5\u5E38\u9A7B\u670D\u52A1)",
29403
29571
  wsPort: actualPort,
29404
29572
  wsUrl: `ws://localhost:${actualPort}`,
29405
29573
  pid: process.pid,
@@ -29421,6 +29589,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
29421
29589
  const res = await askChrome("listTabs");
29422
29590
  return { content: [{ type: "text", text: jsonText(res) }] };
29423
29591
  }
29592
+ if (name === "bind_tab") {
29593
+ const { name: targetName, tabId, urlContains, titleContains } = args;
29594
+ const res = await askChrome("bindTab", { name: targetName, tabId, urlContains, titleContains }, { timeoutMs: 1e4 });
29595
+ return { content: [{ type: "text", text: jsonText(res) }] };
29596
+ }
29597
+ if (name === "unbind_tab") {
29598
+ const { name: targetName } = args;
29599
+ const res = await askChrome("unbindTab", { name: targetName }, { timeoutMs: 1e4 });
29600
+ return { content: [{ type: "text", text: jsonText(res) }] };
29601
+ }
29602
+ if (name === "list_targets") {
29603
+ const res = await askChrome("listTargets");
29604
+ return { content: [{ type: "text", text: jsonText(res) }] };
29605
+ }
29424
29606
  if (name === "get_target_tab") {
29425
29607
  const res = await askChrome("getTargetTab");
29426
29608
  return { content: [{ type: "text", text: jsonText(res) }] };
@@ -29439,12 +29621,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
29439
29621
  return { content: [{ type: "text", text: jsonText(res) }] };
29440
29622
  }
29441
29623
  if (name === "get_last_error") {
29442
- const { severity = "error", limit = 20 } = args;
29443
- const data = await askChrome("getLastError", { severity, limit });
29624
+ const { target, severity = "error", limit = 20 } = args;
29625
+ const data = await askChrome("getLastError", { target, severity, limit });
29444
29626
  return { content: [{ type: "text", text: jsonText(data) }] };
29445
29627
  }
29446
29628
  if (name === "get_script_source") {
29447
29629
  const {
29630
+ target,
29448
29631
  scriptUrlContains,
29449
29632
  line,
29450
29633
  column,
@@ -29453,6 +29636,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
29453
29636
  } = args;
29454
29637
  const res = await askChrome("getScriptSource", {
29455
29638
  scriptUrlContains,
29639
+ target,
29456
29640
  line,
29457
29641
  column
29458
29642
  });
@@ -29479,45 +29663,46 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
29479
29663
  };
29480
29664
  }
29481
29665
  if (name === "coverage_snapshot") {
29666
+ const { target } = args;
29482
29667
  const durationMs = args.durationMs || 1500;
29483
- const res = await askChrome("coverageSnapshot", { durationMs }, { timeoutMs: durationMs + 4e3 });
29668
+ const res = await askChrome("coverageSnapshot", { target, durationMs }, { timeoutMs: durationMs + 4e3 });
29484
29669
  return { content: [{ type: "text", text: jsonText(res) }] };
29485
29670
  }
29486
29671
  if (name === "find_by_string") {
29487
- const { query, scriptUrlContains, maxMatches = 5 } = args;
29488
- const res = await askChrome("findByString", { query, scriptUrlContains, maxMatches });
29672
+ const { target, query, scriptUrlContains, maxMatches = 5 } = args;
29673
+ const res = await askChrome("findByString", { target, query, scriptUrlContains, maxMatches });
29489
29674
  return { content: [{ type: "text", text: jsonText(res) }] };
29490
29675
  }
29491
29676
  if (name === "symbolic_hints") {
29492
- const res = await askChrome("symbolicHints");
29677
+ const res = await askChrome("symbolicHints", { target: args.target });
29493
29678
  return { content: [{ type: "text", text: jsonText(res) }] };
29494
29679
  }
29495
29680
  if (name === "eval_script") {
29496
- const res = await askChrome("eval", { code: args.code });
29681
+ const res = await askChrome("eval", { target: args.target, code: args.code });
29497
29682
  return { content: [{ type: "text", text: jsonText(res) }] };
29498
29683
  }
29499
29684
  if (name === "list_network_requests") {
29500
- const { filter, method, status, resourceType, limit, priorityMode = "debug" } = args;
29501
- const res = await askChrome("listNetworkRequests", { filter, method, status, resourceType, limit, priorityMode });
29685
+ const { target, filter, method, status, resourceType, limit, priorityMode = "debug" } = args;
29686
+ const res = await askChrome("listNetworkRequests", { target, filter, method, status, resourceType, limit, priorityMode });
29502
29687
  return { content: [{ type: "text", text: jsonText(res) }] };
29503
29688
  }
29504
29689
  if (name === "get_network_detail") {
29505
- const { requestId, includeBody } = args;
29506
- const res = await askChrome("getNetworkDetail", { requestId, includeBody });
29690
+ const { target, requestId, includeBody } = args;
29691
+ const res = await askChrome("getNetworkDetail", { target, requestId, includeBody });
29507
29692
  return { content: [{ type: "text", text: jsonText(res) }] };
29508
29693
  }
29509
29694
  if (name === "clear_network_requests") {
29510
- const res = await askChrome("clearNetworkRequests");
29695
+ const res = await askChrome("clearNetworkRequests", { target: args.target });
29511
29696
  return { content: [{ type: "text", text: jsonText(res) }] };
29512
29697
  }
29513
29698
  if (name === "perf_metrics") {
29514
- const { includeTimings, includeResources } = args;
29515
- const res = await askChrome("perfMetrics", { includeTimings, includeResources });
29699
+ const { target, includeTimings, includeResources } = args;
29700
+ const res = await askChrome("perfMetrics", { target, includeTimings, includeResources });
29516
29701
  return { content: [{ type: "text", text: jsonText(res) }] };
29517
29702
  }
29518
29703
  if (name === "capture_screenshot") {
29519
- const { format, quality, fullPage, clip } = args;
29520
- const res = await askChrome("captureScreenshot", { format, quality, fullPage, clip }, { timeoutMs: 15e3 });
29704
+ const { target, format, quality, fullPage, clip } = args;
29705
+ const res = await askChrome("captureScreenshot", { target, format, quality, fullPage, clip }, { timeoutMs: 15e3 });
29521
29706
  const contents = [];
29522
29707
  if (res.imageData) {
29523
29708
  contents.push({
@@ -29541,7 +29726,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
29541
29726
  return { content: contents };
29542
29727
  }
29543
29728
  if (name === "get_page_content") {
29544
- const { mode = "text", selector, maxLength = 5e4, includeMetadata = true } = args;
29729
+ const { target, mode = "text", selector, maxLength = 5e4, includeMetadata = true } = args;
29545
29730
  const validModes = ["text", "html", "structured"];
29546
29731
  if (mode && !validModes.includes(mode)) {
29547
29732
  return {
@@ -29551,17 +29736,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
29551
29736
  }]
29552
29737
  };
29553
29738
  }
29554
- const res = await askChrome("getPageContent", { mode, selector, maxLength, includeMetadata });
29739
+ const res = await askChrome("getPageContent", { target, mode, selector, maxLength, includeMetadata });
29555
29740
  return { content: [{ type: "text", text: jsonText(res) }] };
29556
29741
  }
29557
29742
  if (name === "get_interactive_snapshot") {
29558
- const { selector, includeText, maxElements } = args;
29559
- const res = await askChrome("getInteractiveSnapshot", { selector, includeText, maxElements });
29743
+ const { target, selector, includeText, maxElements } = args;
29744
+ const res = await askChrome("getInteractiveSnapshot", { target, selector, includeText, maxElements });
29560
29745
  return { content: [{ type: "text", text: jsonText(res) }] };
29561
29746
  }
29562
29747
  if (name === "dispatch_action") {
29563
- const { ref, action, value, key, deltaX, deltaY, waitMs } = args;
29564
- const res = await askChrome("dispatchAction", { ref, action, value, key, deltaX, deltaY, waitMs }, { timeoutMs: 1e4 });
29748
+ const { target, ref, action, value, key, deltaX, deltaY, waitMs } = args;
29749
+ const res = await askChrome("dispatchAction", { target, ref, action, value, key, deltaX, deltaY, waitMs }, { timeoutMs: 1e4 });
29565
29750
  return { content: [{ type: "text", text: jsonText(res) }] };
29566
29751
  }
29567
29752
  return { content: [{ type: "text", text: `\u672A\u77E5\u5DE5\u5177\uFF1A${name}` }] };
@@ -29569,38 +29754,42 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
29569
29754
  return { content: [{ type: "text", text: `Error: ${e.message}` }] };
29570
29755
  }
29571
29756
  });
29572
- var transport = new StdioServerTransport();
29573
- await server.connect(transport);
29574
- var roleText = isMainInstance ? "\u4E3B\u5B9E\u4F8B" : "\u5BA2\u6237\u7AEF";
29575
- log(`\u2705 MCP server \u5DF2\u542F\u52A8 | \u89D2\u8272: ${roleText} | \u7AEF\u53E3: ${actualPort} | PID: ${process.pid} | PPID: ${process.ppid}`);
29576
- log(`\u{1F4C4} \u7AEF\u53E3\u4FE1\u606F\u6587\u4EF6: ${PORT_INFO_FILE}`);
29577
- log(`\u{1F4A1} \u4F7F\u7528 get_server_info \u5DE5\u5177\u67E5\u770B\u8BE6\u7EC6\u72B6\u6001`);
29578
- var PARENT_CHECK_INTERVAL = 5e3;
29579
- var parentPid = process.ppid;
29580
- process.stdin.on("end", () => {
29581
- log("\u26A0\uFE0F stdin \u5DF2\u5173\u95ED\uFF0C\u7236\u8FDB\u7A0B\u53EF\u80FD\u5DF2\u9000\u51FA\uFF0C\u6B63\u5728\u9000\u51FA...");
29582
- cleanup();
29583
- process.exit(0);
29584
- });
29585
- process.stdin.on("close", () => {
29586
- log("\u26A0\uFE0F stdin \u5DF2\u5173\u95ED\uFF0C\u6B63\u5728\u9000\u51FA...");
29587
- cleanup();
29588
- process.exit(0);
29589
- });
29590
- var parentCheckTimer = setInterval(() => {
29591
- try {
29592
- process.kill(parentPid, 0);
29593
- } catch (e) {
29594
- log(`\u26A0\uFE0F \u7236\u8FDB\u7A0B (PID: ${parentPid}) \u5DF2\u4E0D\u5B58\u5728\uFF0C\u6B63\u5728\u9000\u51FA...`);
29595
- clearInterval(parentCheckTimer);
29757
+ if (!IS_DAEMON) {
29758
+ const transport = new StdioServerTransport();
29759
+ await server.connect(transport);
29760
+ log(`\u2705 MCP server \u5DF2\u542F\u52A8 | \u89D2\u8272: \u4F1A\u8BDD\u5BA2\u6237\u7AEF | \u5E38\u9A7B\u670D\u52A1\u7AEF\u53E3: ${actualPort} | PID: ${process.pid} | PPID: ${process.ppid}`);
29761
+ log(`\u{1F4A1} \u4F7F\u7528 get_server_info \u5DE5\u5177\u67E5\u770B\u8BE6\u7EC6\u72B6\u6001`);
29762
+ const PARENT_CHECK_INTERVAL = 5e3;
29763
+ const parentPid = process.ppid;
29764
+ process.stdin.on("end", () => {
29765
+ log("\u26A0\uFE0F stdin \u5DF2\u5173\u95ED\uFF0C\u7236\u8FDB\u7A0B\u53EF\u80FD\u5DF2\u9000\u51FA\uFF0C\u6B63\u5728\u9000\u51FA...");
29596
29766
  cleanup();
29597
29767
  process.exit(0);
29598
- }
29599
- }, PARENT_CHECK_INTERVAL);
29600
- parentCheckTimer.unref();
29768
+ });
29769
+ process.stdin.on("close", () => {
29770
+ log("\u26A0\uFE0F stdin \u5DF2\u5173\u95ED\uFF0C\u6B63\u5728\u9000\u51FA...");
29771
+ cleanup();
29772
+ process.exit(0);
29773
+ });
29774
+ const parentCheckTimer = setInterval(() => {
29775
+ try {
29776
+ process.kill(parentPid, 0);
29777
+ } catch (e) {
29778
+ log(`\u26A0\uFE0F \u7236\u8FDB\u7A0B (PID: ${parentPid}) \u5DF2\u4E0D\u5B58\u5728\uFF0C\u6B63\u5728\u9000\u51FA...`);
29779
+ clearInterval(parentCheckTimer);
29780
+ cleanup();
29781
+ process.exit(0);
29782
+ }
29783
+ }, PARENT_CHECK_INTERVAL);
29784
+ parentCheckTimer.unref();
29785
+ } else {
29786
+ log(`\u2705 ghost-bridge daemon \u5DF2\u542F\u52A8 | \u7AEF\u53E3: ${actualPort} | PID: ${process.pid}`);
29787
+ log(`\u{1F4C4} \u7AEF\u53E3\u4FE1\u606F\u6587\u4EF6: ${PORT_INFO_FILE}`);
29788
+ log(`\u{1F4A1} \u505C\u6B62\u670D\u52A1: ghost-bridge stop`);
29789
+ }
29601
29790
  function cleanup() {
29602
29791
  log("\u{1F9F9} \u6B63\u5728\u6E05\u7406...");
29603
- if (isMainInstance) {
29792
+ if (IS_DAEMON) {
29604
29793
  try {
29605
29794
  if (fs2.existsSync(PORT_INFO_FILE)) {
29606
29795
  const info = JSON.parse(fs2.readFileSync(PORT_INFO_FILE, "utf-8"));
@@ -29612,6 +29801,10 @@ function cleanup() {
29612
29801
  } catch (e) {
29613
29802
  log(`\u6E05\u7406\u7AEF\u53E3\u4FE1\u606F\u6587\u4EF6\u5931\u8D25: ${e.message}`);
29614
29803
  }
29804
+ if (wsPingInterval) {
29805
+ clearInterval(wsPingInterval);
29806
+ wsPingInterval = null;
29807
+ }
29615
29808
  if (wss) {
29616
29809
  wss.close(() => {
29617
29810
  log("\u{1F50C} WebSocket \u670D\u52A1\u5668\u5DF2\u5173\u95ED");
@@ -29633,7 +29826,7 @@ process.on("SIGTERM", () => {
29633
29826
  process.exit(0);
29634
29827
  });
29635
29828
  process.on("exit", () => {
29636
- if (isMainInstance) {
29829
+ if (IS_DAEMON) {
29637
29830
  try {
29638
29831
  if (fs2.existsSync(PORT_INFO_FILE)) {
29639
29832
  const info = JSON.parse(fs2.readFileSync(PORT_INFO_FILE, "utf-8"));