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/README.md +27 -5
- package/dist/cli.js +59 -0
- package/dist/server.js +315 -122
- package/extension/background.js +529 -216
- package/extension/manifest.json +3 -2
- package/extension/offscreen.js +149 -10
- package/package.json +4 -4
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(
|
|
27106
|
-
this._transport =
|
|
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
|
-
|
|
28699
|
-
|
|
28700
|
-
|
|
28701
|
-
|
|
28702
|
-
|
|
28703
|
-
|
|
28704
|
-
|
|
28705
|
-
|
|
28706
|
-
|
|
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
|
|
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
|
-
|
|
28719
|
-
|
|
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
|
-
|
|
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\
|
|
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
|
-
|
|
28743
|
-
|
|
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
|
-
|
|
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
|
|
28804
|
+
return acquired.wss;
|
|
28770
28805
|
}
|
|
28771
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
28941
|
+
if (pending.source) {
|
|
28846
28942
|
pendingRequests.delete(msg.id);
|
|
28847
|
-
pending.source.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
28870
|
-
var
|
|
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\
|
|
28878
|
-
|
|
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\
|
|
29016
|
+
log("\u26A0\uFE0F \u4E0E\u5E38\u9A7B\u670D\u52A1\u7684\u8FDE\u63A5\u5DF2\u65AD\u5F00");
|
|
28895
29017
|
activeConnection = null;
|
|
28896
|
-
failAllPending("\u4E0E\
|
|
28897
|
-
|
|
28898
|
-
|
|
28899
|
-
|
|
28900
|
-
|
|
28901
|
-
|
|
28902
|
-
|
|
28903
|
-
|
|
28904
|
-
|
|
28905
|
-
|
|
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\
|
|
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\
|
|
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)
|
|
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 ? "\
|
|
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
|
-
|
|
29573
|
-
|
|
29574
|
-
|
|
29575
|
-
log(`\u2705 MCP server \u5DF2\u542F\u52A8 | \u89D2\u8272:
|
|
29576
|
-
log(`\u{
|
|
29577
|
-
|
|
29578
|
-
|
|
29579
|
-
|
|
29580
|
-
|
|
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
|
-
|
|
29600
|
-
|
|
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 (
|
|
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 (
|
|
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"));
|