@opentiny/webmcp-cli 0.1.0-alpha.1 → 0.1.0-beta.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/bin.cjs +593 -113
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +587 -107
- package/dist/bin.js.map +1 -1
- package/dist/inject-bundle.js +108 -48
- package/dist/webmcp-tools/juejin.cn.js +86 -30
- package/package.json +11 -2
package/dist/bin.js
CHANGED
|
@@ -526,9 +526,17 @@ var init_package = __esm({
|
|
|
526
526
|
"package.json"() {
|
|
527
527
|
package_default = {
|
|
528
528
|
name: "@opentiny/webmcp-cli",
|
|
529
|
-
version: "0.1.0-
|
|
529
|
+
version: "0.1.0-beta.1",
|
|
530
530
|
type: "module",
|
|
531
531
|
description: "WebMCP CLI for AI Agents to interact with browser via Puppeteer",
|
|
532
|
+
repository: {
|
|
533
|
+
type: "git",
|
|
534
|
+
url: "git+https://github.com/opentiny/webmcp-sdk.git",
|
|
535
|
+
directory: "packages/webmcp-cli"
|
|
536
|
+
},
|
|
537
|
+
bugs: {
|
|
538
|
+
url: "https://github.com/opentiny/webmcp-sdk/issues"
|
|
539
|
+
},
|
|
532
540
|
main: "dist/index.js",
|
|
533
541
|
types: "dist/index.d.ts",
|
|
534
542
|
bin: {
|
|
@@ -557,7 +565,7 @@ var init_package = __esm({
|
|
|
557
565
|
license: "MIT",
|
|
558
566
|
author: "OpenTiny Team",
|
|
559
567
|
engines: {
|
|
560
|
-
node: ">=
|
|
568
|
+
node: ">=18.19.0"
|
|
561
569
|
},
|
|
562
570
|
publishConfig: {
|
|
563
571
|
access: "public"
|
|
@@ -569,6 +577,7 @@ var init_package = __esm({
|
|
|
569
577
|
"@page-agent/page-controller": "catalog:",
|
|
570
578
|
clipboardy: "^5.3.1",
|
|
571
579
|
commander: "^11.1.0",
|
|
580
|
+
execa: "^9.6.1",
|
|
572
581
|
json5: "^2.2.3",
|
|
573
582
|
marked: "^15.0.7",
|
|
574
583
|
picocolors: "^1.0.0",
|
|
@@ -797,12 +806,15 @@ var init_cdp_adapter = __esm({
|
|
|
797
806
|
});
|
|
798
807
|
|
|
799
808
|
// src/bridge/protocol.ts
|
|
800
|
-
|
|
809
|
+
function isTerminalMethod(method) {
|
|
810
|
+
return TERMINAL_METHOD_SET.has(method);
|
|
811
|
+
}
|
|
812
|
+
var DEFAULT_WS_PORT, PROTOCOL_VERSION, WS_CLOSE_CODE, BRIDGE_ERROR_CODE, BRIDGE_METHODS, TERMINAL_EVENTS, BASH_EVENTS, TERMINAL_METHOD_SET;
|
|
801
813
|
var init_protocol = __esm({
|
|
802
814
|
"src/bridge/protocol.ts"() {
|
|
803
815
|
"use strict";
|
|
804
816
|
DEFAULT_WS_PORT = 18999;
|
|
805
|
-
PROTOCOL_VERSION = "1.
|
|
817
|
+
PROTOCOL_VERSION = "1.2.0";
|
|
806
818
|
WS_CLOSE_CODE = {
|
|
807
819
|
/** 被新接入的扩展连接平滑替换(旧连接触发退避重连) */
|
|
808
820
|
REPLACED: 4e3,
|
|
@@ -836,8 +848,25 @@ var init_protocol = __esm({
|
|
|
836
848
|
SKILLS_GET: "skills/get",
|
|
837
849
|
SKILLS_READ_RESOURCE: "skills/read_resource",
|
|
838
850
|
SYSTEM_BASH: "system/bash",
|
|
839
|
-
SYSTEM_PICK_DIRECTORY: "system/pick_directory"
|
|
851
|
+
SYSTEM_PICK_DIRECTORY: "system/pick_directory",
|
|
852
|
+
TERMINAL_OPEN: "terminal/open",
|
|
853
|
+
TERMINAL_INPUT: "terminal/input",
|
|
854
|
+
TERMINAL_RESIZE: "terminal/resize",
|
|
855
|
+
TERMINAL_CLOSE: "terminal/close"
|
|
840
856
|
};
|
|
857
|
+
TERMINAL_EVENTS = {
|
|
858
|
+
OUTPUT: "notification/terminal_output",
|
|
859
|
+
EXIT: "notification/terminal_exit"
|
|
860
|
+
};
|
|
861
|
+
BASH_EVENTS = {
|
|
862
|
+
OUTPUT: "notification/bash_output"
|
|
863
|
+
};
|
|
864
|
+
TERMINAL_METHOD_SET = /* @__PURE__ */ new Set([
|
|
865
|
+
BRIDGE_METHODS.TERMINAL_OPEN,
|
|
866
|
+
BRIDGE_METHODS.TERMINAL_INPUT,
|
|
867
|
+
BRIDGE_METHODS.TERMINAL_RESIZE,
|
|
868
|
+
BRIDGE_METHODS.TERMINAL_CLOSE
|
|
869
|
+
]);
|
|
841
870
|
}
|
|
842
871
|
});
|
|
843
872
|
|
|
@@ -898,7 +927,8 @@ var init_workspace_manager = __esm({
|
|
|
898
927
|
// src/bridge/bash-executor.ts
|
|
899
928
|
import fs3 from "fs";
|
|
900
929
|
import path3 from "path";
|
|
901
|
-
import {
|
|
930
|
+
import { StringDecoder } from "string_decoder";
|
|
931
|
+
import { execa } from "execa";
|
|
902
932
|
function findWindowsBash() {
|
|
903
933
|
const pathDirs = (process.env.PATH || "").split(path3.delimiter);
|
|
904
934
|
for (const dir of pathDirs) {
|
|
@@ -949,25 +979,56 @@ function resolveShell(forcePlatform) {
|
|
|
949
979
|
if (!forcePlatform) cachedShell = fallback;
|
|
950
980
|
return fallback;
|
|
951
981
|
}
|
|
952
|
-
function
|
|
953
|
-
if (
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
982
|
+
function withoutCarriageReturn(buf) {
|
|
983
|
+
if (!buf.includes(13)) return buf;
|
|
984
|
+
const out = Buffer.allocUnsafe(buf.length);
|
|
985
|
+
let size = 0;
|
|
986
|
+
for (let index = 0; index < buf.length; index += 1) {
|
|
987
|
+
const byte = buf[index];
|
|
988
|
+
if (byte === 13) continue;
|
|
989
|
+
out[size] = byte;
|
|
990
|
+
size += 1;
|
|
991
|
+
}
|
|
992
|
+
return out.subarray(0, size);
|
|
993
|
+
}
|
|
994
|
+
function collectLimited(stream, maxBuffer, onText, stripCarriageReturn = false) {
|
|
995
|
+
const state = { chunks: [], truncated: false };
|
|
996
|
+
if (!stream) return state;
|
|
997
|
+
const decoder = new StringDecoder("utf8");
|
|
998
|
+
let bytes = 0;
|
|
999
|
+
const emit = (buf) => {
|
|
1000
|
+
const text = decoder.write(buf);
|
|
1001
|
+
if (text) onText?.(text);
|
|
1002
|
+
};
|
|
1003
|
+
stream.on("data", (chunk) => {
|
|
1004
|
+
const raw = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1005
|
+
const buf = stripCarriageReturn ? withoutCarriageReturn(raw) : raw;
|
|
1006
|
+
if (buf.length === 0) return;
|
|
1007
|
+
if (bytes >= maxBuffer) {
|
|
1008
|
+
state.truncated = true;
|
|
1009
|
+
return;
|
|
961
1010
|
}
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
1011
|
+
const remaining = maxBuffer - bytes;
|
|
1012
|
+
if (buf.length <= remaining) {
|
|
1013
|
+
state.chunks.push(buf);
|
|
1014
|
+
bytes += buf.length;
|
|
1015
|
+
emit(buf);
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
const kept = buf.subarray(0, remaining);
|
|
1019
|
+
state.chunks.push(kept);
|
|
1020
|
+
bytes += remaining;
|
|
1021
|
+
state.truncated = true;
|
|
1022
|
+
emit(kept);
|
|
1023
|
+
});
|
|
1024
|
+
stream.on("end", () => {
|
|
1025
|
+
const rest = decoder.end();
|
|
1026
|
+
if (rest) onText?.(rest);
|
|
1027
|
+
});
|
|
1028
|
+
return state;
|
|
1029
|
+
}
|
|
1030
|
+
function decodeLimited(state) {
|
|
1031
|
+
return Buffer.concat(state.chunks).toString("utf8").trim();
|
|
971
1032
|
}
|
|
972
1033
|
async function executeBash(params) {
|
|
973
1034
|
const trimmedCommand = params.command?.trim();
|
|
@@ -986,77 +1047,59 @@ async function executeBash(params) {
|
|
|
986
1047
|
});
|
|
987
1048
|
const normalizedTimeoutMs = typeof params.timeoutMs === "number" && Number.isFinite(params.timeoutMs) && params.timeoutMs > 0 ? Math.min(params.timeoutMs, MAX_TIMEOUT_MS) : DEFAULT_TIMEOUT_MS;
|
|
988
1049
|
const { env = {}, maxBuffer = DEFAULT_MAX_BUFFER } = params;
|
|
989
|
-
const startTime = Date.now();
|
|
990
1050
|
const shell = resolveShell();
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
}
|
|
1020
|
-
} else {
|
|
1021
|
-
truncated = true;
|
|
1022
|
-
}
|
|
1023
|
-
});
|
|
1024
|
-
child.stderr.on("data", (chunk) => {
|
|
1025
|
-
if (stderrBytes < maxBuffer) {
|
|
1026
|
-
const remaining = maxBuffer - stderrBytes;
|
|
1027
|
-
if (chunk.length <= remaining) {
|
|
1028
|
-
stderrChunks.push(chunk);
|
|
1029
|
-
stderrBytes += chunk.length;
|
|
1030
|
-
} else {
|
|
1031
|
-
stderrChunks.push(chunk.subarray(0, remaining));
|
|
1032
|
-
stderrBytes += remaining;
|
|
1033
|
-
truncated = true;
|
|
1034
|
-
}
|
|
1035
|
-
} else {
|
|
1036
|
-
truncated = true;
|
|
1037
|
-
}
|
|
1038
|
-
});
|
|
1039
|
-
child.on("error", (err) => {
|
|
1040
|
-
clearTimeout(killTimeoutTimer);
|
|
1041
|
-
reject(err);
|
|
1042
|
-
});
|
|
1043
|
-
child.on("close", (exitCode, signal) => {
|
|
1044
|
-
clearTimeout(killTimeoutTimer);
|
|
1045
|
-
if (isKilled) return;
|
|
1046
|
-
const stdout = Buffer.concat(stdoutChunks).toString("utf8").trim();
|
|
1047
|
-
const stderr = Buffer.concat(stderrChunks).toString("utf8").trim();
|
|
1048
|
-
resolve3({
|
|
1049
|
-
stdout,
|
|
1050
|
-
stderr,
|
|
1051
|
-
exitCode,
|
|
1052
|
-
signal,
|
|
1053
|
-
durationMs: Date.now() - startTime,
|
|
1054
|
-
...truncated ? { truncated: true } : {}
|
|
1055
|
-
});
|
|
1056
|
-
});
|
|
1051
|
+
const useTty = params.tty === true && process.platform !== "win32";
|
|
1052
|
+
const subprocess = useTty ? execa("python3", ["-u", "-c", TTY_HELPER], {
|
|
1053
|
+
cwd,
|
|
1054
|
+
env: {
|
|
1055
|
+
...env,
|
|
1056
|
+
BASH_TTY_COMMAND: trimmedCommand,
|
|
1057
|
+
BASH_TTY_SHELL: shell
|
|
1058
|
+
},
|
|
1059
|
+
stdin: "ignore",
|
|
1060
|
+
buffer: false,
|
|
1061
|
+
encoding: "buffer",
|
|
1062
|
+
reject: false,
|
|
1063
|
+
timeout: normalizedTimeoutMs,
|
|
1064
|
+
forceKillAfterDelay: FORCE_KILL_AFTER_DELAY_MS,
|
|
1065
|
+
windowsHide: true,
|
|
1066
|
+
stripFinalNewline: false
|
|
1067
|
+
}) : execa(trimmedCommand, {
|
|
1068
|
+
shell,
|
|
1069
|
+
cwd,
|
|
1070
|
+
env,
|
|
1071
|
+
stdin: "ignore",
|
|
1072
|
+
buffer: false,
|
|
1073
|
+
encoding: "buffer",
|
|
1074
|
+
reject: false,
|
|
1075
|
+
timeout: normalizedTimeoutMs,
|
|
1076
|
+
forceKillAfterDelay: FORCE_KILL_AFTER_DELAY_MS,
|
|
1077
|
+
windowsHide: true,
|
|
1078
|
+
stripFinalNewline: false
|
|
1057
1079
|
});
|
|
1080
|
+
const emit = (stream) => (text) => {
|
|
1081
|
+
params.onOutput?.({ stream, text });
|
|
1082
|
+
};
|
|
1083
|
+
const stdoutState = collectLimited(subprocess.stdout, maxBuffer, emit("stdout"), useTty);
|
|
1084
|
+
const stderrState = collectLimited(subprocess.stderr, maxBuffer, emit("stderr"));
|
|
1085
|
+
const result = await subprocess;
|
|
1086
|
+
if (result.timedOut) {
|
|
1087
|
+
throw new BashTimeoutError(normalizedTimeoutMs);
|
|
1088
|
+
}
|
|
1089
|
+
if (result.failed && result.exitCode == null && result.signal == null && !result.isTerminated) {
|
|
1090
|
+
throw new Error(result.shortMessage || result.message || "\u547D\u4EE4\u542F\u52A8\u5931\u8D25");
|
|
1091
|
+
}
|
|
1092
|
+
const truncated = stdoutState.truncated || stderrState.truncated;
|
|
1093
|
+
return {
|
|
1094
|
+
stdout: decodeLimited(stdoutState),
|
|
1095
|
+
stderr: decodeLimited(stderrState),
|
|
1096
|
+
exitCode: result.exitCode ?? null,
|
|
1097
|
+
signal: result.signal ?? null,
|
|
1098
|
+
durationMs: result.durationMs ?? 0,
|
|
1099
|
+
...truncated ? { truncated: true } : {}
|
|
1100
|
+
};
|
|
1058
1101
|
}
|
|
1059
|
-
var BashTimeoutError, DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS, DEFAULT_MAX_BUFFER, cachedShell;
|
|
1102
|
+
var BashTimeoutError, DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS, DEFAULT_MAX_BUFFER, cachedShell, FORCE_KILL_AFTER_DELAY_MS, TTY_HELPER;
|
|
1060
1103
|
var init_bash_executor = __esm({
|
|
1061
1104
|
"src/bridge/bash-executor.ts"() {
|
|
1062
1105
|
"use strict";
|
|
@@ -1074,11 +1117,51 @@ var init_bash_executor = __esm({
|
|
|
1074
1117
|
MAX_TIMEOUT_MS = 6e5;
|
|
1075
1118
|
DEFAULT_MAX_BUFFER = 512 * 1024;
|
|
1076
1119
|
cachedShell = null;
|
|
1120
|
+
FORCE_KILL_AFTER_DELAY_MS = 2e3;
|
|
1121
|
+
TTY_HELPER = `
|
|
1122
|
+
import errno, os, sys
|
|
1123
|
+
cmd = os.environ.get("BASH_TTY_COMMAND") or ""
|
|
1124
|
+
shell = os.environ.get("BASH_TTY_SHELL") or "/bin/bash"
|
|
1125
|
+
if not cmd:
|
|
1126
|
+
sys.exit(127)
|
|
1127
|
+
import pty
|
|
1128
|
+
pid, fd = pty.fork()
|
|
1129
|
+
if pid == 0:
|
|
1130
|
+
os.environ.pop("BASH_TTY_COMMAND", None)
|
|
1131
|
+
os.environ.pop("BASH_TTY_SHELL", None)
|
|
1132
|
+
os.environ["TERM"] = "xterm-256color"
|
|
1133
|
+
try:
|
|
1134
|
+
os.execvp(shell, [shell, "-c", cmd])
|
|
1135
|
+
except OSError:
|
|
1136
|
+
os._exit(127)
|
|
1137
|
+
while True:
|
|
1138
|
+
try:
|
|
1139
|
+
data = os.read(fd, 65536)
|
|
1140
|
+
except OSError as exc:
|
|
1141
|
+
if exc.errno == errno.EINTR:
|
|
1142
|
+
continue
|
|
1143
|
+
break
|
|
1144
|
+
if not data:
|
|
1145
|
+
break
|
|
1146
|
+
try:
|
|
1147
|
+
os.write(1, data)
|
|
1148
|
+
except OSError:
|
|
1149
|
+
break
|
|
1150
|
+
try:
|
|
1151
|
+
_, status = os.waitpid(pid, 0)
|
|
1152
|
+
except OSError:
|
|
1153
|
+
sys.exit(1)
|
|
1154
|
+
if os.WIFEXITED(status):
|
|
1155
|
+
sys.exit(os.WEXITSTATUS(status))
|
|
1156
|
+
if os.WIFSIGNALED(status):
|
|
1157
|
+
sys.exit(128 + os.WTERMSIG(status))
|
|
1158
|
+
sys.exit(1)
|
|
1159
|
+
`;
|
|
1077
1160
|
}
|
|
1078
1161
|
});
|
|
1079
1162
|
|
|
1080
1163
|
// src/bridge/directory-picker.ts
|
|
1081
|
-
import { spawn as
|
|
1164
|
+
import { spawn as spawn2 } from "child_process";
|
|
1082
1165
|
async function pickDirectory(optionsOrTitle) {
|
|
1083
1166
|
const options = typeof optionsOrTitle === "string" ? { title: optionsOrTitle } : optionsOrTitle || {};
|
|
1084
1167
|
const title = options.title || "\u8BF7\u9009\u62E9\u5DE5\u4F5C\u7A7A\u95F4\u6587\u4EF6\u5939";
|
|
@@ -1097,7 +1180,7 @@ function pickDirectoryMac(title, defaultPath) {
|
|
|
1097
1180
|
const defaultClause = defaultPath ? ` default location POSIX file "${defaultPath.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"` : "";
|
|
1098
1181
|
const appleScript = `POSIX path of (choose folder with prompt "${safeTitle}"${defaultClause})`;
|
|
1099
1182
|
return new Promise((resolve3, reject) => {
|
|
1100
|
-
const child =
|
|
1183
|
+
const child = spawn2("osascript", ["-e", appleScript], {
|
|
1101
1184
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1102
1185
|
});
|
|
1103
1186
|
let stdout = "";
|
|
@@ -1215,7 +1298,7 @@ $result = [FolderPicker]::Pick("${safeTitle}", "${safeDefaultPath}")
|
|
|
1215
1298
|
if ($result) { Write-Output $result }
|
|
1216
1299
|
`;
|
|
1217
1300
|
return new Promise((resolve3, reject) => {
|
|
1218
|
-
const child =
|
|
1301
|
+
const child = spawn2("powershell", ["-STA", "-NoProfile", "-WindowStyle", "Hidden", "-Command", psScript], {
|
|
1219
1302
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1220
1303
|
windowsHide: true
|
|
1221
1304
|
});
|
|
@@ -1246,7 +1329,7 @@ function pickDirectoryLinux(title, defaultPath) {
|
|
|
1246
1329
|
args.push(`--filename=${defaultPath}`);
|
|
1247
1330
|
}
|
|
1248
1331
|
return new Promise((resolve3, reject) => {
|
|
1249
|
-
const child =
|
|
1332
|
+
const child = spawn2("zenity", args, {
|
|
1250
1333
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1251
1334
|
});
|
|
1252
1335
|
let stdout = "";
|
|
@@ -1284,6 +1367,344 @@ var init_directory_picker = __esm({
|
|
|
1284
1367
|
}
|
|
1285
1368
|
});
|
|
1286
1369
|
|
|
1370
|
+
// src/bridge/pty-backend.ts
|
|
1371
|
+
import { spawn as spawn3 } from "child_process";
|
|
1372
|
+
function spawnHostPty(options) {
|
|
1373
|
+
if (process.platform === "win32") {
|
|
1374
|
+
throw new Error("\u5F53\u524D\u5E73\u53F0\u6682\u4E0D\u652F\u6301\u4EA4\u4E92\u5F0F\u7EC8\u7AEF");
|
|
1375
|
+
}
|
|
1376
|
+
const shell = options.shell || resolveShell();
|
|
1377
|
+
const child = spawn3("python3", ["-u", "-c", PTY_HELPER], {
|
|
1378
|
+
cwd: options.cwd,
|
|
1379
|
+
env: {
|
|
1380
|
+
...process.env,
|
|
1381
|
+
TERM: "xterm-256color",
|
|
1382
|
+
TERMINAL_SHELL: shell,
|
|
1383
|
+
TERMINAL_ROWS: String(options.rows),
|
|
1384
|
+
TERMINAL_COLS: String(options.cols)
|
|
1385
|
+
},
|
|
1386
|
+
stdio: ["pipe", "pipe", "pipe", "pipe"]
|
|
1387
|
+
});
|
|
1388
|
+
const pending = [];
|
|
1389
|
+
let dataCb = null;
|
|
1390
|
+
let exitCb = null;
|
|
1391
|
+
let pendingExit = null;
|
|
1392
|
+
let settled = false;
|
|
1393
|
+
const decoder = new TextDecoder("utf-8", { fatal: false });
|
|
1394
|
+
let stderrText = "";
|
|
1395
|
+
const emitData = (data) => {
|
|
1396
|
+
if (!data) return;
|
|
1397
|
+
if (dataCb) dataCb(data);
|
|
1398
|
+
else pending.push(data);
|
|
1399
|
+
};
|
|
1400
|
+
const finish = (info) => {
|
|
1401
|
+
if (settled) return;
|
|
1402
|
+
settled = true;
|
|
1403
|
+
if (stderrText.trim()) emitData(`\r
|
|
1404
|
+
${stderrText.trim()}\r
|
|
1405
|
+
`);
|
|
1406
|
+
if (exitCb) exitCb(info);
|
|
1407
|
+
else pendingExit = info;
|
|
1408
|
+
};
|
|
1409
|
+
child.stdout.on("data", (buf) => {
|
|
1410
|
+
emitData(decoder.decode(buf, { stream: true }));
|
|
1411
|
+
});
|
|
1412
|
+
child.stderr.on("data", (buf) => {
|
|
1413
|
+
stderrText += buf.toString("utf8");
|
|
1414
|
+
});
|
|
1415
|
+
child.on("error", (err) => {
|
|
1416
|
+
emitData(`\r
|
|
1417
|
+
[\u7EC8\u7AEF\u542F\u52A8\u5931\u8D25] ${err.message}\r
|
|
1418
|
+
`);
|
|
1419
|
+
finish({ exitCode: null, signal: null });
|
|
1420
|
+
});
|
|
1421
|
+
child.on("exit", (code, signal) => {
|
|
1422
|
+
const rest = decoder.decode();
|
|
1423
|
+
if (rest) emitData(rest);
|
|
1424
|
+
finish({ exitCode: code, signal: signal ?? null });
|
|
1425
|
+
});
|
|
1426
|
+
const control = child.stdio[3];
|
|
1427
|
+
const writeControl = (payload) => {
|
|
1428
|
+
if (!control || !("write" in control)) return;
|
|
1429
|
+
try {
|
|
1430
|
+
;
|
|
1431
|
+
control.write(`${JSON.stringify(payload)}
|
|
1432
|
+
`);
|
|
1433
|
+
} catch {
|
|
1434
|
+
}
|
|
1435
|
+
};
|
|
1436
|
+
return {
|
|
1437
|
+
write(data) {
|
|
1438
|
+
try {
|
|
1439
|
+
child.stdin.write(data);
|
|
1440
|
+
} catch {
|
|
1441
|
+
}
|
|
1442
|
+
},
|
|
1443
|
+
resize(cols, rows) {
|
|
1444
|
+
writeControl({ type: "resize", cols, rows });
|
|
1445
|
+
},
|
|
1446
|
+
kill() {
|
|
1447
|
+
writeControl({ type: "kill" });
|
|
1448
|
+
try {
|
|
1449
|
+
child.kill("SIGTERM");
|
|
1450
|
+
} catch {
|
|
1451
|
+
}
|
|
1452
|
+
},
|
|
1453
|
+
onData(cb) {
|
|
1454
|
+
dataCb = cb;
|
|
1455
|
+
for (const chunk of pending) cb(chunk);
|
|
1456
|
+
pending.length = 0;
|
|
1457
|
+
},
|
|
1458
|
+
onExit(cb) {
|
|
1459
|
+
exitCb = cb;
|
|
1460
|
+
if (pendingExit) cb(pendingExit);
|
|
1461
|
+
}
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
var PTY_HELPER;
|
|
1465
|
+
var init_pty_backend = __esm({
|
|
1466
|
+
"src/bridge/pty-backend.ts"() {
|
|
1467
|
+
"use strict";
|
|
1468
|
+
init_bash_executor();
|
|
1469
|
+
PTY_HELPER = `
|
|
1470
|
+
import errno, fcntl, json, os, pty, select, signal, struct, sys, termios
|
|
1471
|
+
|
|
1472
|
+
shell = os.environ.get("TERMINAL_SHELL") or "/bin/bash"
|
|
1473
|
+
rows = int(os.environ.get("TERMINAL_ROWS") or "24")
|
|
1474
|
+
cols = int(os.environ.get("TERMINAL_COLS") or "80")
|
|
1475
|
+
control_fd = 3
|
|
1476
|
+
|
|
1477
|
+
def set_winsize(fd, next_rows, next_cols):
|
|
1478
|
+
winsize = struct.pack("HHHH", int(next_rows), int(next_cols), 0, 0)
|
|
1479
|
+
fcntl.ioctl(fd, termios.TIOCSWINSZ, winsize)
|
|
1480
|
+
|
|
1481
|
+
pid, master = pty.fork()
|
|
1482
|
+
if pid == 0:
|
|
1483
|
+
try:
|
|
1484
|
+
os.close(control_fd)
|
|
1485
|
+
except OSError:
|
|
1486
|
+
pass
|
|
1487
|
+
os.environ["TERM"] = "xterm-256color"
|
|
1488
|
+
try:
|
|
1489
|
+
os.execvp(shell, [shell, "-i"])
|
|
1490
|
+
except OSError:
|
|
1491
|
+
os._exit(127)
|
|
1492
|
+
|
|
1493
|
+
def control_loop():
|
|
1494
|
+
try:
|
|
1495
|
+
reader = os.fdopen(control_fd, "r", buffering=1, encoding="utf-8", errors="replace")
|
|
1496
|
+
except OSError:
|
|
1497
|
+
return
|
|
1498
|
+
for line in reader:
|
|
1499
|
+
text = line.strip()
|
|
1500
|
+
if not text:
|
|
1501
|
+
continue
|
|
1502
|
+
try:
|
|
1503
|
+
msg = json.loads(text)
|
|
1504
|
+
except json.JSONDecodeError:
|
|
1505
|
+
continue
|
|
1506
|
+
kind = msg.get("type")
|
|
1507
|
+
if kind == "resize":
|
|
1508
|
+
try:
|
|
1509
|
+
set_winsize(master, msg.get("rows") or rows, msg.get("cols") or cols)
|
|
1510
|
+
os.kill(pid, signal.SIGWINCH)
|
|
1511
|
+
except OSError:
|
|
1512
|
+
pass
|
|
1513
|
+
elif kind == "kill":
|
|
1514
|
+
try:
|
|
1515
|
+
os.kill(pid, signal.SIGHUP)
|
|
1516
|
+
except OSError:
|
|
1517
|
+
pass
|
|
1518
|
+
os._exit(0)
|
|
1519
|
+
|
|
1520
|
+
import threading
|
|
1521
|
+
threading.Thread(target=control_loop, daemon=True).start()
|
|
1522
|
+
|
|
1523
|
+
try:
|
|
1524
|
+
set_winsize(master, rows, cols)
|
|
1525
|
+
except OSError:
|
|
1526
|
+
pass
|
|
1527
|
+
|
|
1528
|
+
stdin_fd = sys.stdin.fileno()
|
|
1529
|
+
stdout_fd = sys.stdout.fileno()
|
|
1530
|
+
|
|
1531
|
+
while True:
|
|
1532
|
+
try:
|
|
1533
|
+
readable, _, _ = select.select([master, stdin_fd], [], [])
|
|
1534
|
+
except InterruptedError:
|
|
1535
|
+
continue
|
|
1536
|
+
if master in readable:
|
|
1537
|
+
try:
|
|
1538
|
+
data = os.read(master, 65536)
|
|
1539
|
+
except OSError as err:
|
|
1540
|
+
if err.errno == errno.EIO:
|
|
1541
|
+
break
|
|
1542
|
+
raise
|
|
1543
|
+
if not data:
|
|
1544
|
+
break
|
|
1545
|
+
os.write(stdout_fd, data)
|
|
1546
|
+
if stdin_fd in readable:
|
|
1547
|
+
try:
|
|
1548
|
+
data = os.read(stdin_fd, 65536)
|
|
1549
|
+
except OSError:
|
|
1550
|
+
break
|
|
1551
|
+
if not data:
|
|
1552
|
+
break
|
|
1553
|
+
try:
|
|
1554
|
+
os.write(master, data)
|
|
1555
|
+
except OSError:
|
|
1556
|
+
break
|
|
1557
|
+
|
|
1558
|
+
try:
|
|
1559
|
+
_, status = os.waitpid(pid, 0)
|
|
1560
|
+
except ChildProcessError:
|
|
1561
|
+
status = 0
|
|
1562
|
+
code = os.waitstatus_to_exitcode(status)
|
|
1563
|
+
os._exit(0 if code < 0 else code)
|
|
1564
|
+
`;
|
|
1565
|
+
}
|
|
1566
|
+
});
|
|
1567
|
+
|
|
1568
|
+
// src/bridge/terminal-session.ts
|
|
1569
|
+
import { randomUUID } from "crypto";
|
|
1570
|
+
function clampDimension(value, fallback) {
|
|
1571
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
1572
|
+
if (!Number.isFinite(parsed)) return fallback;
|
|
1573
|
+
return Math.max(2, Math.min(500, Math.floor(parsed)));
|
|
1574
|
+
}
|
|
1575
|
+
var OUTPUT_CHUNK, MAX_INPUT, DEFAULT_COALESCE_MS, TerminalSessionManager;
|
|
1576
|
+
var init_terminal_session = __esm({
|
|
1577
|
+
"src/bridge/terminal-session.ts"() {
|
|
1578
|
+
"use strict";
|
|
1579
|
+
init_workspace_manager();
|
|
1580
|
+
init_pty_backend();
|
|
1581
|
+
init_protocol();
|
|
1582
|
+
OUTPUT_CHUNK = 32 * 1024;
|
|
1583
|
+
MAX_INPUT = 64 * 1024;
|
|
1584
|
+
DEFAULT_COALESCE_MS = 16;
|
|
1585
|
+
TerminalSessionManager = class {
|
|
1586
|
+
session = null;
|
|
1587
|
+
buffer = "";
|
|
1588
|
+
timer = null;
|
|
1589
|
+
send;
|
|
1590
|
+
spawnPty;
|
|
1591
|
+
coalesceMs;
|
|
1592
|
+
constructor(options) {
|
|
1593
|
+
this.send = options.send;
|
|
1594
|
+
this.spawnPty = options.spawnPty ?? ((opts) => spawnHostPty(opts));
|
|
1595
|
+
this.coalesceMs = options.coalesceMs ?? DEFAULT_COALESCE_MS;
|
|
1596
|
+
}
|
|
1597
|
+
open(params = {}) {
|
|
1598
|
+
const cwd = resolveWorkspaceDir(params);
|
|
1599
|
+
const cols = clampDimension(params.cols, 80);
|
|
1600
|
+
const rows = clampDimension(params.rows, 24);
|
|
1601
|
+
this.closeActive();
|
|
1602
|
+
const id = randomUUID();
|
|
1603
|
+
const pty = this.spawnPty({ cwd, cols, rows });
|
|
1604
|
+
this.session = { id, pty, cwd };
|
|
1605
|
+
pty.onData((data) => this.pushOutput(id, data));
|
|
1606
|
+
pty.onExit((info) => {
|
|
1607
|
+
if (this.session?.id !== id) return;
|
|
1608
|
+
this.flush(id);
|
|
1609
|
+
this.session = null;
|
|
1610
|
+
this.send({
|
|
1611
|
+
jsonrpc: "2.0",
|
|
1612
|
+
method: TERMINAL_EVENTS.EXIT,
|
|
1613
|
+
params: {
|
|
1614
|
+
sessionId: id,
|
|
1615
|
+
exitCode: info.exitCode,
|
|
1616
|
+
signal: info.signal
|
|
1617
|
+
}
|
|
1618
|
+
});
|
|
1619
|
+
});
|
|
1620
|
+
return { sessionId: id, cwd };
|
|
1621
|
+
}
|
|
1622
|
+
input(params) {
|
|
1623
|
+
const session = this.requireSession(params.sessionId);
|
|
1624
|
+
if (typeof params.data !== "string") {
|
|
1625
|
+
throw new BashInvalidParamsError("data \u5FC5\u987B\u662F\u5B57\u7B26\u4E32");
|
|
1626
|
+
}
|
|
1627
|
+
if (params.data.length > MAX_INPUT) {
|
|
1628
|
+
throw new BashInvalidParamsError("\u5355\u6B21\u8F93\u5165\u8FC7\u957F");
|
|
1629
|
+
}
|
|
1630
|
+
session.pty.write(params.data);
|
|
1631
|
+
return { ok: true };
|
|
1632
|
+
}
|
|
1633
|
+
resize(params) {
|
|
1634
|
+
const session = this.requireSession(params.sessionId);
|
|
1635
|
+
session.pty.resize(clampDimension(params.cols, 80), clampDimension(params.rows, 24));
|
|
1636
|
+
return { ok: true };
|
|
1637
|
+
}
|
|
1638
|
+
close(params = {}) {
|
|
1639
|
+
if (!this.session || params.sessionId && params.sessionId !== this.session.id) {
|
|
1640
|
+
return { ok: true };
|
|
1641
|
+
}
|
|
1642
|
+
this.closeActive();
|
|
1643
|
+
return { ok: true };
|
|
1644
|
+
}
|
|
1645
|
+
disposeAll() {
|
|
1646
|
+
this.closeActive();
|
|
1647
|
+
}
|
|
1648
|
+
requireSession(sessionId) {
|
|
1649
|
+
if (!this.session || !sessionId || sessionId !== this.session.id) {
|
|
1650
|
+
throw new Error("\u7EC8\u7AEF\u4F1A\u8BDD\u4E0D\u5B58\u5728");
|
|
1651
|
+
}
|
|
1652
|
+
return this.session;
|
|
1653
|
+
}
|
|
1654
|
+
closeActive() {
|
|
1655
|
+
const current = this.session;
|
|
1656
|
+
this.session = null;
|
|
1657
|
+
this.buffer = "";
|
|
1658
|
+
if (this.timer) {
|
|
1659
|
+
clearTimeout(this.timer);
|
|
1660
|
+
this.timer = null;
|
|
1661
|
+
}
|
|
1662
|
+
current?.pty.kill();
|
|
1663
|
+
}
|
|
1664
|
+
pushOutput(sessionId, chunk) {
|
|
1665
|
+
if (this.session?.id !== sessionId || !chunk) return;
|
|
1666
|
+
this.buffer += chunk;
|
|
1667
|
+
this.flushFullChunks(sessionId);
|
|
1668
|
+
if (!this.buffer) return;
|
|
1669
|
+
if (this.coalesceMs <= 0) {
|
|
1670
|
+
this.flush(sessionId);
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
if (!this.timer) {
|
|
1674
|
+
this.timer = setTimeout(() => this.flush(sessionId), this.coalesceMs);
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
flushFullChunks(sessionId) {
|
|
1678
|
+
while (this.buffer.length >= OUTPUT_CHUNK) {
|
|
1679
|
+
const data = this.buffer.slice(0, OUTPUT_CHUNK);
|
|
1680
|
+
this.buffer = this.buffer.slice(OUTPUT_CHUNK);
|
|
1681
|
+
this.emitOutput(sessionId, data);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
flush(sessionId) {
|
|
1685
|
+
if (this.timer) {
|
|
1686
|
+
clearTimeout(this.timer);
|
|
1687
|
+
this.timer = null;
|
|
1688
|
+
}
|
|
1689
|
+
if (!this.buffer || this.session?.id !== sessionId) {
|
|
1690
|
+
this.buffer = "";
|
|
1691
|
+
return;
|
|
1692
|
+
}
|
|
1693
|
+
const data = this.buffer;
|
|
1694
|
+
this.buffer = "";
|
|
1695
|
+
this.emitOutput(sessionId, data);
|
|
1696
|
+
}
|
|
1697
|
+
emitOutput(sessionId, data) {
|
|
1698
|
+
this.send({
|
|
1699
|
+
jsonrpc: "2.0",
|
|
1700
|
+
method: TERMINAL_EVENTS.OUTPUT,
|
|
1701
|
+
params: { sessionId, data }
|
|
1702
|
+
});
|
|
1703
|
+
}
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1706
|
+
});
|
|
1707
|
+
|
|
1287
1708
|
// src/bridge/bridge-client.ts
|
|
1288
1709
|
var bridge_client_exports = {};
|
|
1289
1710
|
__export(bridge_client_exports, {
|
|
@@ -1298,7 +1719,7 @@ __export(bridge_client_exports, {
|
|
|
1298
1719
|
});
|
|
1299
1720
|
import { WebSocketServer, WebSocket as WsClient } from "ws";
|
|
1300
1721
|
import { EventEmitter } from "events";
|
|
1301
|
-
import { randomUUID, randomBytes, createHmac, createHash, timingSafeEqual } from "crypto";
|
|
1722
|
+
import { randomUUID as randomUUID2, randomBytes, createHmac, createHash, timingSafeEqual } from "crypto";
|
|
1302
1723
|
import fs4 from "fs";
|
|
1303
1724
|
import path4 from "path";
|
|
1304
1725
|
import os3 from "os";
|
|
@@ -1367,6 +1788,7 @@ var init_bridge_client = __esm({
|
|
|
1367
1788
|
init_protocol();
|
|
1368
1789
|
init_bash_executor();
|
|
1369
1790
|
init_directory_picker();
|
|
1791
|
+
init_terminal_session();
|
|
1370
1792
|
WS_PORT = DEFAULT_WS_PORT;
|
|
1371
1793
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
1372
1794
|
HANDSHAKE_TIMEOUT_MS = 5e3;
|
|
@@ -1388,6 +1810,7 @@ var init_bridge_client = __esm({
|
|
|
1388
1810
|
pending = /* @__PURE__ */ new Map();
|
|
1389
1811
|
socketAuthStates = /* @__PURE__ */ new Map();
|
|
1390
1812
|
destroyed = false;
|
|
1813
|
+
terminalSessions = null;
|
|
1391
1814
|
idCounter = 0;
|
|
1392
1815
|
isClientMode = false;
|
|
1393
1816
|
explicitAuthToken;
|
|
@@ -1642,7 +2065,7 @@ var init_bridge_client = __esm({
|
|
|
1642
2065
|
this.emit("error", err);
|
|
1643
2066
|
});
|
|
1644
2067
|
this.wss.on("connection", (socket) => {
|
|
1645
|
-
const challenge =
|
|
2068
|
+
const challenge = randomUUID2();
|
|
1646
2069
|
const hmacSalt = randomBytes(16).toString("hex");
|
|
1647
2070
|
const authState = {
|
|
1648
2071
|
authenticated: false,
|
|
@@ -1684,6 +2107,7 @@ var init_bridge_client = __esm({
|
|
|
1684
2107
|
}
|
|
1685
2108
|
if (socket === this.extensionSocket) {
|
|
1686
2109
|
this.extensionSocket = null;
|
|
2110
|
+
this.terminalSessions?.disposeAll();
|
|
1687
2111
|
this.emit("disconnected");
|
|
1688
2112
|
for (const [reqId, fwdInfo] of this.forwardedRequests.entries()) {
|
|
1689
2113
|
if (fwdInfo.timer) clearTimeout(fwdInfo.timer);
|
|
@@ -1730,6 +2154,16 @@ var init_bridge_client = __esm({
|
|
|
1730
2154
|
return;
|
|
1731
2155
|
}
|
|
1732
2156
|
if (msg.method && msg.id) {
|
|
2157
|
+
if (typeof msg.method === "string" && isTerminalMethod(msg.method) && socket !== this.extensionSocket) {
|
|
2158
|
+
this.sendJsonRpcResponse(socket, {
|
|
2159
|
+
id: msg.id,
|
|
2160
|
+
error: {
|
|
2161
|
+
code: BRIDGE_ERROR_CODE.METHOD_NOT_FOUND,
|
|
2162
|
+
message: "\u7EC8\u7AEF\u4F1A\u8BDD\u4EC5\u5141\u8BB8\u5DF2\u8BA4\u8BC1\u7684\u6269\u5C55\u8FDE\u63A5"
|
|
2163
|
+
}
|
|
2164
|
+
});
|
|
2165
|
+
return;
|
|
2166
|
+
}
|
|
1733
2167
|
if (socket === this.extensionSocket) {
|
|
1734
2168
|
await this.handleExtensionSystemRpc(socket, msg);
|
|
1735
2169
|
return;
|
|
@@ -1893,9 +2327,6 @@ var init_bridge_client = __esm({
|
|
|
1893
2327
|
systemRpcHandlers = Object.assign(
|
|
1894
2328
|
/* @__PURE__ */ Object.create(null),
|
|
1895
2329
|
{
|
|
1896
|
-
[BRIDGE_METHODS.SYSTEM_BASH]: async (params) => {
|
|
1897
|
-
return executeBash(params || {});
|
|
1898
|
-
},
|
|
1899
2330
|
[BRIDGE_METHODS.SYSTEM_PICK_DIRECTORY]: async (params) => {
|
|
1900
2331
|
const selectedPath = await pickDirectory({
|
|
1901
2332
|
title: params?.title || void 0,
|
|
@@ -1906,11 +2337,43 @@ var init_bridge_client = __esm({
|
|
|
1906
2337
|
selectedPath,
|
|
1907
2338
|
canceled: selectedPath === null
|
|
1908
2339
|
};
|
|
1909
|
-
}
|
|
2340
|
+
},
|
|
2341
|
+
[BRIDGE_METHODS.TERMINAL_OPEN]: async (params) => this.getTerminalSessions().open(params || {}),
|
|
2342
|
+
[BRIDGE_METHODS.TERMINAL_INPUT]: async (params) => this.getTerminalSessions().input(params || {}),
|
|
2343
|
+
[BRIDGE_METHODS.TERMINAL_RESIZE]: async (params) => this.getTerminalSessions().resize(params || {}),
|
|
2344
|
+
[BRIDGE_METHODS.TERMINAL_CLOSE]: async (params) => this.getTerminalSessions().close(params || {})
|
|
1910
2345
|
}
|
|
1911
2346
|
);
|
|
2347
|
+
sendJsonRpcNotification(socket, method, params) {
|
|
2348
|
+
if (socket.readyState === WS_OPEN) {
|
|
2349
|
+
socket.send(JSON.stringify({ jsonrpc: "2.0", method, params }));
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
async executeHostBash(socket, id, params) {
|
|
2353
|
+
try {
|
|
2354
|
+
const requestParams = params && typeof params === "object" ? params : {};
|
|
2355
|
+
const result = await executeBash({
|
|
2356
|
+
...requestParams,
|
|
2357
|
+
onOutput: (chunk) => {
|
|
2358
|
+
if (!chunk.text) return;
|
|
2359
|
+
this.sendJsonRpcNotification(socket, BASH_EVENTS.OUTPUT, {
|
|
2360
|
+
id,
|
|
2361
|
+
stream: chunk.stream,
|
|
2362
|
+
text: chunk.text
|
|
2363
|
+
});
|
|
2364
|
+
}
|
|
2365
|
+
});
|
|
2366
|
+
this.sendJsonRpcResponse(socket, { id, result });
|
|
2367
|
+
} catch (err) {
|
|
2368
|
+
this.sendJsonRpcResponse(socket, { id, error: this.formatSystemRpcError(err) });
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
1912
2371
|
async handleExtensionSystemRpc(socket, msg) {
|
|
1913
2372
|
const { id, method, params } = msg;
|
|
2373
|
+
if (method === BRIDGE_METHODS.SYSTEM_BASH) {
|
|
2374
|
+
await this.executeHostBash(socket, id, params);
|
|
2375
|
+
return;
|
|
2376
|
+
}
|
|
1914
2377
|
const handler = typeof method === "string" && Object.prototype.hasOwnProperty.call(this.systemRpcHandlers, method) ? this.systemRpcHandlers[method] : void 0;
|
|
1915
2378
|
if (!handler || typeof handler !== "function") {
|
|
1916
2379
|
this.sendJsonRpcResponse(socket, {
|
|
@@ -2006,9 +2469,26 @@ var init_bridge_client = __esm({
|
|
|
2006
2469
|
}
|
|
2007
2470
|
});
|
|
2008
2471
|
}
|
|
2472
|
+
getTerminalSessions() {
|
|
2473
|
+
if (!this.terminalSessions) {
|
|
2474
|
+
this.terminalSessions = new TerminalSessionManager({
|
|
2475
|
+
send: (frame) => {
|
|
2476
|
+
const socket = this.extensionSocket;
|
|
2477
|
+
if (socket && socket.readyState === WS_OPEN) {
|
|
2478
|
+
try {
|
|
2479
|
+
socket.send(JSON.stringify(frame));
|
|
2480
|
+
} catch {
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
});
|
|
2485
|
+
}
|
|
2486
|
+
return this.terminalSessions;
|
|
2487
|
+
}
|
|
2009
2488
|
/** 销毁服务,断开所有连接 */
|
|
2010
2489
|
destroy() {
|
|
2011
2490
|
this.destroyed = true;
|
|
2491
|
+
this.terminalSessions?.disposeAll();
|
|
2012
2492
|
this._rejectAllPending("ExtensionBridgeClient \u5DF2\u9500\u6BC1");
|
|
2013
2493
|
this.forwardedRequests.clear();
|
|
2014
2494
|
if (this.clientSocket) {
|
|
@@ -2153,7 +2633,7 @@ __export(daemon_exports, {
|
|
|
2153
2633
|
isDaemonProcessAlive: () => isDaemonProcessAlive,
|
|
2154
2634
|
isProcessAlive: () => isProcessAlive,
|
|
2155
2635
|
killProcessOnPort: () => killProcessOnPort,
|
|
2156
|
-
killProcessTree: () =>
|
|
2636
|
+
killProcessTree: () => killProcessTree,
|
|
2157
2637
|
probeServerReady: () => probeServerReady,
|
|
2158
2638
|
runDaemonProcess: () => runDaemonProcess,
|
|
2159
2639
|
stopBridgeDaemon: () => stopBridgeDaemon
|
|
@@ -2212,7 +2692,7 @@ async function findPidByPort(port, excludePid) {
|
|
|
2212
2692
|
return null;
|
|
2213
2693
|
}
|
|
2214
2694
|
}
|
|
2215
|
-
async function
|
|
2695
|
+
async function killProcessTree(pid) {
|
|
2216
2696
|
if (!pid || pid <= 0 || pid === process.pid) return false;
|
|
2217
2697
|
if (process.platform === "win32") {
|
|
2218
2698
|
try {
|
|
@@ -2250,7 +2730,7 @@ async function killProcessTree2(pid) {
|
|
|
2250
2730
|
async function killProcessOnPort(port) {
|
|
2251
2731
|
const pid = await findPidByPort(port, process.pid);
|
|
2252
2732
|
if (pid) {
|
|
2253
|
-
await
|
|
2733
|
+
await killProcessTree(pid);
|
|
2254
2734
|
for (let i = 0; i < 20; i++) {
|
|
2255
2735
|
await new Promise((r) => setTimeout(r, 100));
|
|
2256
2736
|
const remainingPid = await findPidByPort(port, process.pid);
|
|
@@ -2432,7 +2912,7 @@ async function stopBridgeDaemon(port = WS_PORT2, options = {}) {
|
|
|
2432
2912
|
if (portPid && portPid !== daemonPid) {
|
|
2433
2913
|
return false;
|
|
2434
2914
|
}
|
|
2435
|
-
const killed = await
|
|
2915
|
+
const killed = await killProcessTree(daemonPid);
|
|
2436
2916
|
if (killed) {
|
|
2437
2917
|
killedAny = true;
|
|
2438
2918
|
try {
|
|
@@ -2443,7 +2923,7 @@ async function stopBridgeDaemon(port = WS_PORT2, options = {}) {
|
|
|
2443
2923
|
} else {
|
|
2444
2924
|
if (daemonPid) {
|
|
2445
2925
|
try {
|
|
2446
|
-
const killed = await
|
|
2926
|
+
const killed = await killProcessTree(daemonPid);
|
|
2447
2927
|
if (killed) {
|
|
2448
2928
|
killedAny = true;
|
|
2449
2929
|
try {
|