ghost-bridge 0.7.1 → 0.9.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/server.js CHANGED
@@ -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 { fileURLToPath as fileURLToPath2 } from "url";
28526
28527
 
28527
28528
  // lib/version.js
28528
28529
  import fs from "fs";
@@ -28541,15 +28542,43 @@ var WS_TOKEN = process.env.GHOST_BRIDGE_TOKEN || DEFAULT_WS_TOKEN;
28541
28542
  var RESPONSE_TIMEOUT = 8e3;
28542
28543
  var PORT_INFO_FILE = path2.join(os.tmpdir(), "ghost-bridge-port.json");
28543
28544
  var SERVER_STARTED_AT = (/* @__PURE__ */ new Date()).toISOString();
28545
+ var SERVER_ENTRY_PATH = fileURLToPath2(import.meta.url);
28544
28546
  var chromeConnection = null;
28545
28547
  var activeConnection = null;
28546
28548
  var actualPort = BASE_PORT;
28547
28549
  var isMainInstance = false;
28548
28550
  var pendingRequests = /* @__PURE__ */ new Map();
28549
28551
  var mcpClients = /* @__PURE__ */ new Set();
28552
+ var mcpClientSeq = 0;
28553
+ var LOCAL_CLIENT_ID = "local";
28550
28554
  function log(msg) {
28551
28555
  console.error(`[ghost-bridge] ${msg}`);
28552
28556
  }
28557
+ function buildIdentityPayload() {
28558
+ return {
28559
+ type: "identity",
28560
+ service: "ghost-bridge",
28561
+ token: WS_TOKEN,
28562
+ pid: process.pid,
28563
+ port: actualPort,
28564
+ version: GHOST_BRIDGE_VERSION,
28565
+ serverPath: SERVER_ENTRY_PATH,
28566
+ startedAt: SERVER_STARTED_AT,
28567
+ capabilities: ["heartbeat"]
28568
+ };
28569
+ }
28570
+ function getServiceMismatch(probe) {
28571
+ if (!probe || probe.service !== "ghost-bridge") return null;
28572
+ if (!probe.version) return "\u65E7\u670D\u52A1\u7F3A\u5C11\u7248\u672C\u5143\u6570\u636E";
28573
+ if (probe.version !== GHOST_BRIDGE_VERSION) {
28574
+ return `\u7248\u672C\u4E0D\u4E00\u81F4\uFF08\u65E7: ${probe.version}, \u65B0: ${GHOST_BRIDGE_VERSION}\uFF09`;
28575
+ }
28576
+ if (!probe.serverPath) return "\u65E7\u670D\u52A1\u7F3A\u5C11\u8DEF\u5F84\u5143\u6570\u636E";
28577
+ if (path2.resolve(probe.serverPath) !== path2.resolve(SERVER_ENTRY_PATH)) {
28578
+ return `\u8DEF\u5F84\u4E0D\u4E00\u81F4\uFF08\u65E7: ${probe.serverPath}, \u65B0: ${SERVER_ENTRY_PATH}\uFF09`;
28579
+ }
28580
+ return null;
28581
+ }
28553
28582
  function isProcessRunning(pid) {
28554
28583
  try {
28555
28584
  process.kill(pid, 0);
@@ -28628,11 +28657,11 @@ async function waitForPortAvailable(port, timeoutMs = 5e3) {
28628
28657
  }
28629
28658
  return isPortAvailable(port);
28630
28659
  }
28631
- async function stopExistingService(pid, port) {
28660
+ async function stopExistingService(pid, port, reason = "\u9700\u8981\u7531\u65B0\u5B9E\u4F8B\u63A5\u7BA1") {
28632
28661
  if (!pid || pid === process.pid) {
28633
28662
  return false;
28634
28663
  }
28635
- log(`\u68C0\u6D4B\u5230\u65E7\u5B9E\u4F8B token \u4E0D\u4E00\u81F4\uFF0C\u51C6\u5907\u505C\u6B62\u65E7\u5B9E\u4F8B (PID: ${pid})`);
28664
+ log(`\u68C0\u6D4B\u5230\u65E7\u5B9E\u4F8B\u9700\u8981\u66FF\u6362\uFF1A${reason}\uFF0C\u51C6\u5907\u505C\u6B62\u65E7\u5B9E\u4F8B (PID: ${pid})`);
28636
28665
  try {
28637
28666
  process.kill(pid, "SIGTERM");
28638
28667
  } catch (e) {
@@ -28686,13 +28715,18 @@ async function initWebSocketService() {
28686
28715
  const probe = await probeExistingService(existing.port);
28687
28716
  if (probe?.service === "ghost-bridge") {
28688
28717
  if (probe.token === WS_TOKEN) {
28689
- actualPort = existing.port;
28690
- isMainInstance = false;
28691
- log(`\u2705 \u590D\u7528\u73B0\u6709\u670D\u52A1\uFF0C\u7AEF\u53E3 ${actualPort}`);
28692
- return null;
28718
+ const mismatch = getServiceMismatch(probe);
28719
+ if (!mismatch) {
28720
+ actualPort = existing.port;
28721
+ isMainInstance = false;
28722
+ log(`\u2705 \u590D\u7528\u73B0\u6709\u670D\u52A1\uFF0C\u7AEF\u53E3 ${actualPort}`);
28723
+ return null;
28724
+ }
28725
+ await stopExistingService(Number(probe.pid) || existing.pid, existing.port, mismatch);
28726
+ } else {
28727
+ const oldPid = Number(probe.pid) || existing.pid;
28728
+ await stopExistingService(oldPid, existing.port, "token \u4E0D\u4E00\u81F4");
28693
28729
  }
28694
- const oldPid = Number(probe.pid) || existing.pid;
28695
- await stopExistingService(oldPid, existing.port);
28696
28730
  } else {
28697
28731
  log(`\u274C \u73B0\u6709\u670D\u52A1\u9A8C\u8BC1\u5931\u8D25\uFF0C\u542F\u52A8\u65B0\u670D\u52A1...`);
28698
28732
  try {
@@ -28705,12 +28739,17 @@ async function initWebSocketService() {
28705
28739
  const probe = await probeExistingService(BASE_PORT);
28706
28740
  if (probe?.service === "ghost-bridge") {
28707
28741
  if (probe.token === WS_TOKEN) {
28708
- actualPort = BASE_PORT;
28709
- isMainInstance = false;
28710
- log(`\u2705 \u590D\u7528\u56FA\u5B9A\u7AEF\u53E3\u4E0A\u7684\u73B0\u6709\u670D\u52A1\uFF0C\u7AEF\u53E3 ${actualPort}`);
28711
- return null;
28742
+ const mismatch = getServiceMismatch(probe);
28743
+ if (!mismatch) {
28744
+ actualPort = BASE_PORT;
28745
+ isMainInstance = false;
28746
+ log(`\u2705 \u590D\u7528\u56FA\u5B9A\u7AEF\u53E3\u4E0A\u7684\u73B0\u6709\u670D\u52A1\uFF0C\u7AEF\u53E3 ${actualPort}`);
28747
+ return null;
28748
+ }
28749
+ await stopExistingService(Number(probe.pid), BASE_PORT, mismatch);
28750
+ } else {
28751
+ await stopExistingService(Number(probe.pid), BASE_PORT, "token \u4E0D\u4E00\u81F4");
28712
28752
  }
28713
- await stopExistingService(Number(probe.pid), BASE_PORT);
28714
28753
  }
28715
28754
  if (!await isPortAvailable(BASE_PORT)) {
28716
28755
  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`);
@@ -28724,6 +28763,8 @@ async function initWebSocketService() {
28724
28763
  port: actualPort,
28725
28764
  wsUrl: `ws://localhost:${actualPort}`,
28726
28765
  pid: process.pid,
28766
+ version: GHOST_BRIDGE_VERSION,
28767
+ serverPath: SERVER_ENTRY_PATH,
28727
28768
  startedAt: SERVER_STARTED_AT
28728
28769
  }, null, 2)
28729
28770
  );
@@ -28731,20 +28772,19 @@ async function initWebSocketService() {
28731
28772
  return wss2;
28732
28773
  }
28733
28774
  var wss = await initWebSocketService();
28775
+ var wsPingInterval = null;
28776
+ var WS_PING_INTERVAL_MS = 3e4;
28734
28777
  if (wss) {
28735
28778
  wss.on("connection", (ws, req) => {
28779
+ ws.isAlive = true;
28780
+ ws.on("pong", () => {
28781
+ ws.isAlive = true;
28782
+ });
28736
28783
  const url2 = new URL(req.url || "/", "http://localhost");
28737
28784
  const token = url2.searchParams.get("token") || "";
28738
28785
  const role = url2.searchParams.get("role") || "";
28739
28786
  if (role === "probe") {
28740
- ws.send(JSON.stringify({
28741
- type: "identity",
28742
- service: "ghost-bridge",
28743
- token: WS_TOKEN,
28744
- pid: process.pid,
28745
- port: actualPort,
28746
- startedAt: SERVER_STARTED_AT
28747
- }));
28787
+ ws.send(JSON.stringify(buildIdentityPayload()));
28748
28788
  ws.close(1e3, "Probe complete");
28749
28789
  return;
28750
28790
  }
@@ -28755,9 +28795,11 @@ if (wss) {
28755
28795
  }
28756
28796
  log(`\u8FDE\u63A5\u9A8C\u8BC1\u901A\u8FC7 (token: ${token})`);
28757
28797
  if (role === "mcp-client") {
28758
- log("\u{1F4E1} MCP \u5BA2\u6237\u7AEF\u5DF2\u8FDE\u63A5");
28798
+ const clientId = `c${++mcpClientSeq}`;
28799
+ ws.ghostClientId = clientId;
28800
+ log(`\u{1F4E1} MCP \u5BA2\u6237\u7AEF\u5DF2\u8FDE\u63A5 (${clientId})`);
28759
28801
  mcpClients.add(ws);
28760
- ws.send(JSON.stringify({ type: "identity", service: "ghost-bridge", token: WS_TOKEN }));
28802
+ ws.send(JSON.stringify(buildIdentityPayload()));
28761
28803
  ws.on("message", (data) => {
28762
28804
  try {
28763
28805
  const msg = JSON.parse(data.toString());
@@ -28767,7 +28809,11 @@ if (wss) {
28767
28809
  result: {
28768
28810
  chromeConnected: !!chromeConnection,
28769
28811
  mcpClientsCount: mcpClients.size,
28770
- port: actualPort
28812
+ port: actualPort,
28813
+ version: GHOST_BRIDGE_VERSION,
28814
+ serverPath: SERVER_ENTRY_PATH,
28815
+ pid: process.pid,
28816
+ startedAt: SERVER_STARTED_AT
28771
28817
  }
28772
28818
  }));
28773
28819
  return;
@@ -28779,15 +28825,26 @@ if (wss) {
28779
28825
  return;
28780
28826
  }
28781
28827
  if (msg.id) {
28782
- pendingRequests.set(msg.id, { source: ws });
28828
+ const wireId = `${clientId}:${msg.id}`;
28829
+ pendingRequests.set(wireId, { source: ws, originalId: msg.id });
28830
+ chromeConnection.send(JSON.stringify({
28831
+ id: wireId,
28832
+ command: msg.command,
28833
+ params: msg.params,
28834
+ clientId,
28835
+ ...WS_TOKEN ? { token: WS_TOKEN } : {}
28836
+ }));
28837
+ } else {
28838
+ chromeConnection.send(data);
28783
28839
  }
28784
- chromeConnection.send(data);
28785
28840
  } catch {
28786
28841
  }
28787
28842
  });
28788
28843
  ws.on("close", () => {
28789
- log("\u{1F4E1} MCP \u5BA2\u6237\u7AEF\u5DF2\u65AD\u5F00");
28844
+ log(`\u{1F4E1} MCP \u5BA2\u6237\u7AEF\u5DF2\u65AD\u5F00 (${ws.ghostClientId})`);
28790
28845
  mcpClients.delete(ws);
28846
+ askChrome("_clientDisconnected", { clientId: ws.ghostClientId }, { timeoutMs: 3e3 }).catch(() => {
28847
+ });
28791
28848
  });
28792
28849
  } else {
28793
28850
  if (chromeConnection && chromeConnection !== ws && chromeConnection.readyState === import_websocket.default.OPEN) {
@@ -28801,15 +28858,21 @@ if (wss) {
28801
28858
  log("\u{1F310} Chrome \u6269\u5C55\u5DF2\u8FDE\u63A5");
28802
28859
  chromeConnection = ws;
28803
28860
  activeConnection = ws;
28804
- ws.send(JSON.stringify({ type: "identity", service: "ghost-bridge", token: WS_TOKEN }));
28861
+ ws.send(JSON.stringify(buildIdentityPayload()));
28805
28862
  ws.on("message", (data) => {
28806
28863
  try {
28807
28864
  const msg = JSON.parse(data.toString());
28865
+ if (msg.type === "heartbeat") {
28866
+ ws.send(JSON.stringify({ type: "heartbeat_ack", ts: msg.ts || Date.now() }));
28867
+ return;
28868
+ }
28808
28869
  if (msg.id && pendingRequests.has(msg.id)) {
28809
28870
  const pending = pendingRequests.get(msg.id);
28810
- if (pending.source && pending.source.readyState === import_websocket.default.OPEN) {
28871
+ if (pending.source) {
28811
28872
  pendingRequests.delete(msg.id);
28812
- pending.source.send(data);
28873
+ if (pending.source.readyState === import_websocket.default.OPEN) {
28874
+ pending.source.send(JSON.stringify({ ...msg, id: pending.originalId }));
28875
+ }
28813
28876
  return;
28814
28877
  }
28815
28878
  }
@@ -28817,14 +28880,38 @@ if (wss) {
28817
28880
  }
28818
28881
  handleIncoming(data);
28819
28882
  });
28820
- ws.on("close", () => {
28821
- log("\u{1F310} Chrome \u8FDE\u63A5\u5DF2\u5173\u95ED");
28883
+ ws.on("close", (code, reason) => {
28884
+ const reasonText = reason ? reason.toString() : "";
28885
+ log(`\u{1F310} Chrome \u8FDE\u63A5\u5DF2\u5173\u95ED (code=${code}${reasonText ? ` reason="${reasonText}"` : ""})`);
28886
+ if (chromeConnection !== ws) {
28887
+ log("\u5FFD\u7565\u65E7 Chrome \u8FDE\u63A5\u7684\u5173\u95ED\u4E8B\u4EF6");
28888
+ return;
28889
+ }
28822
28890
  chromeConnection = null;
28823
- activeConnection = null;
28891
+ if (activeConnection === ws) {
28892
+ activeConnection = null;
28893
+ }
28824
28894
  failAllPending("Chrome \u8FDE\u63A5\u65AD\u5F00");
28825
28895
  });
28896
+ ws.on("error", (err) => {
28897
+ log(`\u{1F310} Chrome \u8FDE\u63A5\u9519\u8BEF: ${err.message}`);
28898
+ });
28826
28899
  }
28827
28900
  });
28901
+ wsPingInterval = setInterval(() => {
28902
+ for (const client of wss.clients) {
28903
+ if (client.isAlive === false) {
28904
+ log("\u{1F310} \u5FC3\u8DF3\u8D85\u65F6\uFF0C\u4E3B\u52A8\u65AD\u5F00\u6B7B\u8FDE\u63A5");
28905
+ client.terminate();
28906
+ continue;
28907
+ }
28908
+ client.isAlive = false;
28909
+ try {
28910
+ client.ping();
28911
+ } catch {
28912
+ }
28913
+ }
28914
+ }, WS_PING_INTERVAL_MS);
28828
28915
  } else {
28829
28916
  log(`\u{1F4E1} \u4F5C\u4E3A\u5BA2\u6237\u7AEF\u8FDE\u63A5\u5230\u4E3B\u5B9E\u4F8B (\u7AEF\u53E3 ${actualPort})...`);
28830
28917
  connectToMainInstance();
@@ -28935,7 +29022,7 @@ async function askMainInstance(command, params = {}) {
28935
29022
  async function askChrome(command, params = {}, options = {}) {
28936
29023
  if (!activeConnection) throw new Error("Chrome \u672A\u8FDE\u63A5\uFF0C\u8BF7\u786E\u8BA4\u6D4F\u89C8\u5668\u5F00\u542F\u4E14\u6269\u5C55\u5DF2\u542F\u7528");
28937
29024
  const id = crypto.randomUUID();
28938
- const payload = { id, command, params };
29025
+ const payload = { id, command, params, clientId: LOCAL_CLIENT_ID };
28939
29026
  if (WS_TOKEN) payload.token = WS_TOKEN;
28940
29027
  const timeoutMs = options.timeoutMs || RESPONSE_TIMEOUT;
28941
29028
  return new Promise((resolve, reject) => {
@@ -28956,6 +29043,10 @@ async function askChrome(command, params = {}, options = {}) {
28956
29043
  function jsonText(data) {
28957
29044
  return typeof data === "string" ? data : JSON.stringify(data);
28958
29045
  }
29046
+ var TARGET_ARG = {
29047
+ type: "string",
29048
+ 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"
29049
+ };
28959
29050
  function buildSnippet(source, line, column, { beautifyEnabled = true, contextLines = 20 } = {}) {
28960
29051
  const result = {};
28961
29052
  if (!source) {
@@ -29003,6 +29094,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29003
29094
  inputSchema: {
29004
29095
  type: "object",
29005
29096
  properties: {
29097
+ target: TARGET_ARG,
29006
29098
  selector: {
29007
29099
  type: "string",
29008
29100
  description: "CSS \u9009\u62E9\u5668\uFF0C\u9650\u5B9A\u5206\u6790\u8303\u56F4\u3002\u4E0D\u6307\u5B9A\u5219\u5206\u6790\u6574\u4E2A\u9875\u9762"
@@ -29023,12 +29115,75 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29023
29115
  description: "\u83B7\u53D6 ghost-bridge \u670D\u52A1\u5668\u72B6\u6001\uFF0C\u5305\u62EC\u5F53\u524D WebSocket \u7AEF\u53E3\u3001\u8FDE\u63A5\u72B6\u6001\u7B49",
29024
29116
  inputSchema: { type: "object", properties: {} }
29025
29117
  },
29118
+ {
29119
+ name: "list_tabs",
29120
+ 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",
29121
+ inputSchema: { type: "object", properties: {} }
29122
+ },
29123
+ {
29124
+ name: "bind_tab",
29125
+ 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",
29126
+ inputSchema: {
29127
+ type: "object",
29128
+ properties: {
29129
+ 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" },
29130
+ tabId: { type: "number", description: "Chrome \u6807\u7B7E\u9875 ID\uFF0C\u4F18\u5148\u4F7F\u7528" },
29131
+ urlContains: { type: "string", description: "URL \u7247\u6BB5\u3002\u672A\u63D0\u4F9B tabId \u65F6\u6309\u5B83\u5339\u914D" },
29132
+ titleContains: { type: "string", description: "\u6807\u9898\u7247\u6BB5\u3002\u53EF\u4E0E urlContains \u540C\u65F6\u4F7F\u7528" }
29133
+ },
29134
+ required: ["name"]
29135
+ }
29136
+ },
29137
+ {
29138
+ name: "unbind_tab",
29139
+ 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",
29140
+ inputSchema: {
29141
+ type: "object",
29142
+ properties: {
29143
+ name: { type: "string", description: "\u8981\u89E3\u7ED1\u7684 target \u540D\u79F0" }
29144
+ },
29145
+ required: ["name"]
29146
+ }
29147
+ },
29148
+ {
29149
+ name: "list_targets",
29150
+ description: "\u67E5\u770B\u5F53\u524D\u5DF2\u7ED1\u5B9A\u7684\u547D\u540D targets\uFF0C\u4EE5\u53CA\u9ED8\u8BA4 focused/pinned \u76EE\u6807\u72B6\u6001\u3002",
29151
+ inputSchema: { type: "object", properties: {} }
29152
+ },
29153
+ {
29154
+ name: "get_target_tab",
29155
+ 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",
29156
+ inputSchema: { type: "object", properties: {} }
29157
+ },
29158
+ {
29159
+ name: "pin_current_tab",
29160
+ description: "\u9501\u5B9A\u5F53\u524D\u805A\u7126\u7684 Chrome \u6807\u7B7E\u9875\u3002\u9501\u5B9A\u540E\u7528\u6237\u5207\u6362\u5230\u5176\u4ED6\u9875\u9762\u4E5F\u4E0D\u4F1A\u6539\u53D8 Ghost Bridge \u7684\u64CD\u4F5C\u76EE\u6807\u3002",
29161
+ inputSchema: { type: "object", properties: {} }
29162
+ },
29163
+ {
29164
+ name: "pin_tab",
29165
+ 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",
29166
+ inputSchema: {
29167
+ type: "object",
29168
+ properties: {
29169
+ tabId: { type: "number", description: "Chrome \u6807\u7B7E\u9875 ID\uFF0C\u4F18\u5148\u4F7F\u7528" },
29170
+ urlContains: { type: "string", description: "URL \u7247\u6BB5\u3002\u672A\u63D0\u4F9B tabId \u65F6\u6309\u5B83\u5339\u914D" },
29171
+ titleContains: { type: "string", description: "\u6807\u9898\u7247\u6BB5\u3002\u53EF\u4E0E urlContains \u540C\u65F6\u4F7F\u7528" }
29172
+ }
29173
+ }
29174
+ },
29175
+ {
29176
+ name: "unpin_tab",
29177
+ description: "\u89E3\u9664\u9501\u5B9A\uFF0C\u6062\u590D\u8DDF\u968F\u5F53\u524D\u805A\u7126\u6807\u7B7E\u9875\u3002",
29178
+ inputSchema: { type: "object", properties: {} }
29179
+ },
29026
29180
  {
29027
29181
  name: "get_last_error",
29028
- description: "\u83B7\u53D6\u5F53\u524D\u6807\u7B7E\u6700\u8FD1\u7684\u63A7\u5236\u53F0\u3001\u5F02\u5E38\u548C\u7F51\u7EDC\u9519\u8BEF\u4E8B\u4EF6\u3002\u9ED8\u8BA4\u53EA\u8FD4\u56DE error\uFF1B\u5982\u9700\u67E5\u770B console.log / console.warn\uFF0C\u8BF7\u4F20 severity=info / warn / all\u3002",
29182
+ description: "\u83B7\u53D6\u76EE\u6807\u6807\u7B7E\u9875\u6700\u8FD1\u7684\u63A7\u5236\u53F0\u3001\u5F02\u5E38\u548C\u7F51\u7EDC\u9519\u8BEF\u4E8B\u4EF6\u3002\u9ED8\u8BA4\u53EA\u8FD4\u56DE error\uFF1B\u5982\u9700\u67E5\u770B console.log / console.warn\uFF0C\u8BF7\u4F20 severity=info / warn / all\u3002",
29029
29183
  inputSchema: {
29030
29184
  type: "object",
29031
29185
  properties: {
29186
+ target: TARGET_ARG,
29032
29187
  severity: {
29033
29188
  type: "string",
29034
29189
  enum: ["error", "warn", "info", "all"],
@@ -29043,10 +29198,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29043
29198
  },
29044
29199
  {
29045
29200
  name: "get_script_source",
29046
- description: "\u6293\u53D6\u76EE\u6807\u811A\u672C\u6E90\u7801\u7247\u6BB5\uFF0C\u652F\u6301\u6309 URL \u7247\u6BB5\u7B5B\u9009\u548C\u53EF\u9009 beautify\u3002",
29201
+ description: "\u6293\u53D6\u76EE\u6807\u6807\u7B7E\u9875\u7684\u811A\u672C\u6E90\u7801\u7247\u6BB5\uFF0C\u652F\u6301\u6309 URL \u7247\u6BB5\u7B5B\u9009\u548C\u53EF\u9009 beautify\u3002",
29047
29202
  inputSchema: {
29048
29203
  type: "object",
29049
29204
  properties: {
29205
+ target: TARGET_ARG,
29050
29206
  scriptUrlContains: { type: "string" },
29051
29207
  line: { type: "number" },
29052
29208
  column: { type: "number" },
@@ -29061,16 +29217,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29061
29217
  inputSchema: {
29062
29218
  type: "object",
29063
29219
  properties: {
29220
+ target: TARGET_ARG,
29064
29221
  durationMs: { type: "number", description: "\u9ED8\u8BA4 1500ms" }
29065
29222
  }
29066
29223
  }
29067
29224
  },
29068
29225
  {
29069
29226
  name: "find_by_string",
29070
- description: "\u5728\u5F53\u524D\u9875\u9762\u811A\u672C\u5185\u6309\u5B57\u7B26\u4E32\u641C\u7D22\uFF0C\u8FD4\u56DE\u5339\u914D\u4E0A\u4E0B\u6587\u3002",
29227
+ description: "\u5728\u76EE\u6807\u6807\u7B7E\u9875\u811A\u672C\u5185\u6309\u5B57\u7B26\u4E32\u641C\u7D22\uFF0C\u8FD4\u56DE\u5339\u914D\u4E0A\u4E0B\u6587\u3002",
29071
29228
  inputSchema: {
29072
29229
  type: "object",
29073
29230
  properties: {
29231
+ target: TARGET_ARG,
29074
29232
  query: { type: "string" },
29075
29233
  scriptUrlContains: { type: "string" },
29076
29234
  maxMatches: { type: "number" }
@@ -29081,23 +29239,24 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29081
29239
  {
29082
29240
  name: "symbolic_hints",
29083
29241
  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",
29084
- inputSchema: { type: "object", properties: {} }
29242
+ inputSchema: { type: "object", properties: { target: TARGET_ARG } }
29085
29243
  },
29086
29244
  {
29087
29245
  name: "eval_script",
29088
- description: "\u5728\u5F53\u524D\u9875\u9762\u6267\u884C\u53EA\u8BFB JS \u8868\u8FBE\u5F0F\uFF08\u8C28\u614E\u4F7F\u7528\uFF09",
29246
+ description: "\u5728\u76EE\u6807\u6807\u7B7E\u9875\u6267\u884C\u53EA\u8BFB JS \u8868\u8FBE\u5F0F\uFF08\u8C28\u614E\u4F7F\u7528\uFF09",
29089
29247
  inputSchema: {
29090
29248
  type: "object",
29091
- properties: { code: { type: "string" } },
29249
+ properties: { target: TARGET_ARG, code: { type: "string" } },
29092
29250
  required: ["code"]
29093
29251
  }
29094
29252
  },
29095
29253
  {
29096
29254
  name: "list_network_requests",
29097
- description: "\u5217\u51FA\u6355\u83B7\u7684\u7F51\u7EDC\u8BF7\u6C42\uFF0C\u652F\u6301\u6309 URL\u3001\u65B9\u6CD5\u3001\u72B6\u6001\u548C\u7C7B\u578B\u8FC7\u6EE4\u3002\u9ED8\u8BA4\u6309\u6392\u969C\u4F18\u5148\u7EA7\u6392\u5E8F\uFF1Bdata URL \u548C\u8D85\u957F URL \u4F1A\u81EA\u52A8\u6458\u8981\u5316\u3002",
29255
+ description: "\u5217\u51FA\u76EE\u6807\u6807\u7B7E\u9875\u6355\u83B7\u7684\u7F51\u7EDC\u8BF7\u6C42\uFF0C\u652F\u6301\u6309 URL\u3001\u65B9\u6CD5\u3001\u72B6\u6001\u548C\u7C7B\u578B\u8FC7\u6EE4\u3002\u9ED8\u8BA4\u6309\u6392\u969C\u4F18\u5148\u7EA7\u6392\u5E8F\uFF1Bdata URL \u548C\u8D85\u957F URL \u4F1A\u81EA\u52A8\u6458\u8981\u5316\u3002",
29098
29256
  inputSchema: {
29099
29257
  type: "object",
29100
29258
  properties: {
29259
+ target: TARGET_ARG,
29101
29260
  filter: { type: "string", description: "URL \u5173\u952E\u8BCD\u8FC7\u6EE4" },
29102
29261
  method: { type: "string", description: "\u8BF7\u6C42\u65B9\u6CD5\uFF1AGET/POST/PUT/DELETE \u7B49" },
29103
29262
  status: { type: "string", description: "\u72B6\u6001\uFF1Asuccess/error/failed/pending" },
@@ -29117,6 +29276,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29117
29276
  inputSchema: {
29118
29277
  type: "object",
29119
29278
  properties: {
29279
+ target: TARGET_ARG,
29120
29280
  requestId: { type: "string", description: "\u8BF7\u6C42 ID\uFF08\u4ECE list_network_requests \u83B7\u53D6\uFF09" },
29121
29281
  includeBody: { type: "boolean", description: "\u662F\u5426\u5305\u542B\u54CD\u5E94\u4F53\uFF0C\u9ED8\u8BA4 false" }
29122
29282
  },
@@ -29126,7 +29286,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29126
29286
  {
29127
29287
  name: "clear_network_requests",
29128
29288
  description: "\u6E05\u7A7A\u5DF2\u6355\u83B7\u7684\u7F51\u7EDC\u8BF7\u6C42\u8BB0\u5F55",
29129
- inputSchema: { type: "object", properties: {} }
29289
+ inputSchema: { type: "object", properties: { target: TARGET_ARG } }
29130
29290
  },
29131
29291
  {
29132
29292
  name: "perf_metrics",
@@ -29134,6 +29294,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29134
29294
  inputSchema: {
29135
29295
  type: "object",
29136
29296
  properties: {
29297
+ target: TARGET_ARG,
29137
29298
  includeTimings: {
29138
29299
  type: "boolean",
29139
29300
  description: "\u662F\u5426\u5305\u542B Navigation Timing \u548C Web Vitals\uFF0C\u9ED8\u8BA4 true"
@@ -29147,10 +29308,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29147
29308
  },
29148
29309
  {
29149
29310
  name: "capture_screenshot",
29150
- description: "\u622A\u53D6\u5F53\u524D\u9875\u9762\u622A\u56FE\uFF0C\u9002\u5408\u770B\u9875\u9762\u5B9E\u9645\u89C6\u89C9\u6548\u679C\u3001UI \u6837\u5F0F\u548C\u5E03\u5C40\u3002\u9ED8\u8BA4\u4F18\u5148\u4F7F\u7528 JPEG\uFF1B\u9700\u8981\u6587\u5B57\u3001\u7EC6\u7EBF\u6216\u900F\u660E\u80CC\u666F\u7EC6\u8282\u65F6\u6539\u7528 PNG\u3002",
29311
+ description: "\u622A\u53D6\u76EE\u6807\u6807\u7B7E\u9875\u622A\u56FE\uFF0C\u9002\u5408\u770B\u9875\u9762\u5B9E\u9645\u89C6\u89C9\u6548\u679C\u3001UI \u6837\u5F0F\u548C\u5E03\u5C40\u3002\u9ED8\u8BA4\u4F18\u5148\u4F7F\u7528 JPEG\uFF1B\u9700\u8981\u6587\u5B57\u3001\u7EC6\u7EBF\u6216\u900F\u660E\u80CC\u666F\u7EC6\u8282\u65F6\u6539\u7528 PNG\u3002",
29151
29312
  inputSchema: {
29152
29313
  type: "object",
29153
29314
  properties: {
29315
+ target: TARGET_ARG,
29154
29316
  format: {
29155
29317
  type: "string",
29156
29318
  enum: ["png", "jpeg"],
@@ -29179,10 +29341,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29179
29341
  },
29180
29342
  {
29181
29343
  name: "get_page_content",
29182
- description: "\u63D0\u53D6\u5F53\u524D\u9875\u9762\u7684\u6587\u672C\u3001HTML \u6216\u7ED3\u6784\u5316\u6570\u636E\u3002\u6BD4\u622A\u56FE\u66F4\u8F7B\u91CF\uFF0C\u9002\u5408\u5148\u770B\u6587\u5B57\u3001DOM \u7ED3\u6784\u548C\u9875\u9762\u5143\u6570\u636E\uFF1B\u4E0D\u53CD\u6620 CSS\uFF0C\u4E5F\u4E0D\u542B iframe \u5185\u5BB9\u3002",
29344
+ description: "\u63D0\u53D6\u76EE\u6807\u6807\u7B7E\u9875\u7684\u6587\u672C\u3001HTML \u6216\u7ED3\u6784\u5316\u6570\u636E\u3002\u6BD4\u622A\u56FE\u66F4\u8F7B\u91CF\uFF0C\u9002\u5408\u5148\u770B\u6587\u5B57\u3001DOM \u7ED3\u6784\u548C\u9875\u9762\u5143\u6570\u636E\uFF1B\u4E0D\u53CD\u6620 CSS\uFF0C\u4E5F\u4E0D\u542B iframe \u5185\u5BB9\u3002",
29183
29345
  inputSchema: {
29184
29346
  type: "object",
29185
29347
  properties: {
29348
+ target: TARGET_ARG,
29186
29349
  mode: {
29187
29350
  type: "string",
29188
29351
  enum: ["text", "html", "structured"],
@@ -29209,6 +29372,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29209
29372
  inputSchema: {
29210
29373
  type: "object",
29211
29374
  properties: {
29375
+ target: TARGET_ARG,
29212
29376
  selector: {
29213
29377
  type: "string",
29214
29378
  description: "CSS \u9009\u62E9\u5668\uFF0C\u9650\u5B9A\u626B\u63CF\u8303\u56F4\u3002\u4E0D\u6307\u5B9A\u5219\u626B\u63CF\u6574\u4E2A\u9875\u9762"
@@ -29230,6 +29394,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29230
29394
  inputSchema: {
29231
29395
  type: "object",
29232
29396
  properties: {
29397
+ target: TARGET_ARG,
29233
29398
  ref: {
29234
29399
  type: "string",
29235
29400
  description: "\u76EE\u6807\u5143\u7D20\u7684 ref \u6807\u8BC6\uFF0C\u5982 'e1'\u3001'e5'\uFF08\u4ECE get_interactive_snapshot \u83B7\u53D6\uFF09"
@@ -29270,8 +29435,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
29270
29435
  const args = request.params.arguments || {};
29271
29436
  try {
29272
29437
  if (name === "inspect_page") {
29273
- const { selector, includeInteractive = true, maxElements = 30 } = args;
29438
+ const { target, selector, includeInteractive = true, maxElements = 30 } = args;
29274
29439
  const snapshot = await askChrome("inspectPageSnapshot", {
29440
+ target,
29275
29441
  selector,
29276
29442
  includeInteractive,
29277
29443
  maxElements
@@ -29305,13 +29471,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
29305
29471
  };
29306
29472
  }
29307
29473
  if (name === "get_server_info") {
29308
- let chromeOk, clientsCount;
29474
+ let chromeOk, clientsCount, mainStatus;
29309
29475
  if (isMainInstance) {
29310
29476
  chromeOk = !!chromeConnection;
29311
29477
  clientsCount = mcpClients.size;
29478
+ mainStatus = {
29479
+ pid: process.pid,
29480
+ version: GHOST_BRIDGE_VERSION,
29481
+ serverPath: SERVER_ENTRY_PATH,
29482
+ startedAt: SERVER_STARTED_AT
29483
+ };
29312
29484
  } else {
29313
29485
  try {
29314
- const mainStatus = await askMainInstance("_getMainStatus");
29486
+ mainStatus = await askMainInstance("_getMainStatus");
29315
29487
  chromeOk = mainStatus.chromeConnected;
29316
29488
  clientsCount = mainStatus.mcpClientsCount;
29317
29489
  } catch {
@@ -29330,6 +29502,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
29330
29502
  wsPort: actualPort,
29331
29503
  wsUrl: `ws://localhost:${actualPort}`,
29332
29504
  pid: process.pid,
29505
+ serverPath: SERVER_ENTRY_PATH,
29506
+ mainPid: mainStatus?.pid,
29507
+ mainVersion: mainStatus?.version,
29508
+ mainServerPath: mainStatus?.serverPath,
29509
+ mainStartedAt: mainStatus?.startedAt,
29333
29510
  chromeConnected: chromeOk,
29334
29511
  mcpClientsCount: clientsCount,
29335
29512
  portInfoFile: PORT_INFO_FILE,
@@ -29339,13 +29516,49 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
29339
29516
  ]
29340
29517
  };
29341
29518
  }
29519
+ if (name === "list_tabs") {
29520
+ const res = await askChrome("listTabs");
29521
+ return { content: [{ type: "text", text: jsonText(res) }] };
29522
+ }
29523
+ if (name === "bind_tab") {
29524
+ const { name: targetName, tabId, urlContains, titleContains } = args;
29525
+ const res = await askChrome("bindTab", { name: targetName, tabId, urlContains, titleContains }, { timeoutMs: 1e4 });
29526
+ return { content: [{ type: "text", text: jsonText(res) }] };
29527
+ }
29528
+ if (name === "unbind_tab") {
29529
+ const { name: targetName } = args;
29530
+ const res = await askChrome("unbindTab", { name: targetName }, { timeoutMs: 1e4 });
29531
+ return { content: [{ type: "text", text: jsonText(res) }] };
29532
+ }
29533
+ if (name === "list_targets") {
29534
+ const res = await askChrome("listTargets");
29535
+ return { content: [{ type: "text", text: jsonText(res) }] };
29536
+ }
29537
+ if (name === "get_target_tab") {
29538
+ const res = await askChrome("getTargetTab");
29539
+ return { content: [{ type: "text", text: jsonText(res) }] };
29540
+ }
29541
+ if (name === "pin_current_tab") {
29542
+ const res = await askChrome("pinCurrentTab", {}, { timeoutMs: 1e4 });
29543
+ return { content: [{ type: "text", text: jsonText(res) }] };
29544
+ }
29545
+ if (name === "pin_tab") {
29546
+ const { tabId, urlContains, titleContains } = args;
29547
+ const res = await askChrome("pinTab", { tabId, urlContains, titleContains }, { timeoutMs: 1e4 });
29548
+ return { content: [{ type: "text", text: jsonText(res) }] };
29549
+ }
29550
+ if (name === "unpin_tab") {
29551
+ const res = await askChrome("unpinTab", {}, { timeoutMs: 1e4 });
29552
+ return { content: [{ type: "text", text: jsonText(res) }] };
29553
+ }
29342
29554
  if (name === "get_last_error") {
29343
- const { severity = "error", limit = 20 } = args;
29344
- const data = await askChrome("getLastError", { severity, limit });
29555
+ const { target, severity = "error", limit = 20 } = args;
29556
+ const data = await askChrome("getLastError", { target, severity, limit });
29345
29557
  return { content: [{ type: "text", text: jsonText(data) }] };
29346
29558
  }
29347
29559
  if (name === "get_script_source") {
29348
29560
  const {
29561
+ target,
29349
29562
  scriptUrlContains,
29350
29563
  line,
29351
29564
  column,
@@ -29354,6 +29567,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
29354
29567
  } = args;
29355
29568
  const res = await askChrome("getScriptSource", {
29356
29569
  scriptUrlContains,
29570
+ target,
29357
29571
  line,
29358
29572
  column
29359
29573
  });
@@ -29380,45 +29594,46 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
29380
29594
  };
29381
29595
  }
29382
29596
  if (name === "coverage_snapshot") {
29597
+ const { target } = args;
29383
29598
  const durationMs = args.durationMs || 1500;
29384
- const res = await askChrome("coverageSnapshot", { durationMs }, { timeoutMs: durationMs + 4e3 });
29599
+ const res = await askChrome("coverageSnapshot", { target, durationMs }, { timeoutMs: durationMs + 4e3 });
29385
29600
  return { content: [{ type: "text", text: jsonText(res) }] };
29386
29601
  }
29387
29602
  if (name === "find_by_string") {
29388
- const { query, scriptUrlContains, maxMatches = 5 } = args;
29389
- const res = await askChrome("findByString", { query, scriptUrlContains, maxMatches });
29603
+ const { target, query, scriptUrlContains, maxMatches = 5 } = args;
29604
+ const res = await askChrome("findByString", { target, query, scriptUrlContains, maxMatches });
29390
29605
  return { content: [{ type: "text", text: jsonText(res) }] };
29391
29606
  }
29392
29607
  if (name === "symbolic_hints") {
29393
- const res = await askChrome("symbolicHints");
29608
+ const res = await askChrome("symbolicHints", { target: args.target });
29394
29609
  return { content: [{ type: "text", text: jsonText(res) }] };
29395
29610
  }
29396
29611
  if (name === "eval_script") {
29397
- const res = await askChrome("eval", { code: args.code });
29612
+ const res = await askChrome("eval", { target: args.target, code: args.code });
29398
29613
  return { content: [{ type: "text", text: jsonText(res) }] };
29399
29614
  }
29400
29615
  if (name === "list_network_requests") {
29401
- const { filter, method, status, resourceType, limit, priorityMode = "debug" } = args;
29402
- const res = await askChrome("listNetworkRequests", { filter, method, status, resourceType, limit, priorityMode });
29616
+ const { target, filter, method, status, resourceType, limit, priorityMode = "debug" } = args;
29617
+ const res = await askChrome("listNetworkRequests", { target, filter, method, status, resourceType, limit, priorityMode });
29403
29618
  return { content: [{ type: "text", text: jsonText(res) }] };
29404
29619
  }
29405
29620
  if (name === "get_network_detail") {
29406
- const { requestId, includeBody } = args;
29407
- const res = await askChrome("getNetworkDetail", { requestId, includeBody });
29621
+ const { target, requestId, includeBody } = args;
29622
+ const res = await askChrome("getNetworkDetail", { target, requestId, includeBody });
29408
29623
  return { content: [{ type: "text", text: jsonText(res) }] };
29409
29624
  }
29410
29625
  if (name === "clear_network_requests") {
29411
- const res = await askChrome("clearNetworkRequests");
29626
+ const res = await askChrome("clearNetworkRequests", { target: args.target });
29412
29627
  return { content: [{ type: "text", text: jsonText(res) }] };
29413
29628
  }
29414
29629
  if (name === "perf_metrics") {
29415
- const { includeTimings, includeResources } = args;
29416
- const res = await askChrome("perfMetrics", { includeTimings, includeResources });
29630
+ const { target, includeTimings, includeResources } = args;
29631
+ const res = await askChrome("perfMetrics", { target, includeTimings, includeResources });
29417
29632
  return { content: [{ type: "text", text: jsonText(res) }] };
29418
29633
  }
29419
29634
  if (name === "capture_screenshot") {
29420
- const { format, quality, fullPage, clip } = args;
29421
- const res = await askChrome("captureScreenshot", { format, quality, fullPage, clip }, { timeoutMs: 15e3 });
29635
+ const { target, format, quality, fullPage, clip } = args;
29636
+ const res = await askChrome("captureScreenshot", { target, format, quality, fullPage, clip }, { timeoutMs: 15e3 });
29422
29637
  const contents = [];
29423
29638
  if (res.imageData) {
29424
29639
  contents.push({
@@ -29442,7 +29657,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
29442
29657
  return { content: contents };
29443
29658
  }
29444
29659
  if (name === "get_page_content") {
29445
- const { mode = "text", selector, maxLength = 5e4, includeMetadata = true } = args;
29660
+ const { target, mode = "text", selector, maxLength = 5e4, includeMetadata = true } = args;
29446
29661
  const validModes = ["text", "html", "structured"];
29447
29662
  if (mode && !validModes.includes(mode)) {
29448
29663
  return {
@@ -29452,17 +29667,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
29452
29667
  }]
29453
29668
  };
29454
29669
  }
29455
- const res = await askChrome("getPageContent", { mode, selector, maxLength, includeMetadata });
29670
+ const res = await askChrome("getPageContent", { target, mode, selector, maxLength, includeMetadata });
29456
29671
  return { content: [{ type: "text", text: jsonText(res) }] };
29457
29672
  }
29458
29673
  if (name === "get_interactive_snapshot") {
29459
- const { selector, includeText, maxElements } = args;
29460
- const res = await askChrome("getInteractiveSnapshot", { selector, includeText, maxElements });
29674
+ const { target, selector, includeText, maxElements } = args;
29675
+ const res = await askChrome("getInteractiveSnapshot", { target, selector, includeText, maxElements });
29461
29676
  return { content: [{ type: "text", text: jsonText(res) }] };
29462
29677
  }
29463
29678
  if (name === "dispatch_action") {
29464
- const { ref, action, value, key, deltaX, deltaY, waitMs } = args;
29465
- const res = await askChrome("dispatchAction", { ref, action, value, key, deltaX, deltaY, waitMs }, { timeoutMs: 1e4 });
29679
+ const { target, ref, action, value, key, deltaX, deltaY, waitMs } = args;
29680
+ const res = await askChrome("dispatchAction", { target, ref, action, value, key, deltaX, deltaY, waitMs }, { timeoutMs: 1e4 });
29466
29681
  return { content: [{ type: "text", text: jsonText(res) }] };
29467
29682
  }
29468
29683
  return { content: [{ type: "text", text: `\u672A\u77E5\u5DE5\u5177\uFF1A${name}` }] };
@@ -29513,6 +29728,10 @@ function cleanup() {
29513
29728
  } catch (e) {
29514
29729
  log(`\u6E05\u7406\u7AEF\u53E3\u4FE1\u606F\u6587\u4EF6\u5931\u8D25: ${e.message}`);
29515
29730
  }
29731
+ if (wsPingInterval) {
29732
+ clearInterval(wsPingInterval);
29733
+ wsPingInterval = null;
29734
+ }
29516
29735
  if (wss) {
29517
29736
  wss.close(() => {
29518
29737
  log("\u{1F50C} WebSocket \u670D\u52A1\u5668\u5DF2\u5173\u95ED");