@opentiny/webmcp-cli 0.1.0 → 0.1.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 +2 -0
- package/dist/bin.cjs +822 -150
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +794 -124
- 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 +17 -3
- package/scripts/ensure-pty-helper.mjs +24 -0
package/dist/bin.cjs
CHANGED
|
@@ -548,9 +548,17 @@ var init_package = __esm({
|
|
|
548
548
|
"package.json"() {
|
|
549
549
|
package_default = {
|
|
550
550
|
name: "@opentiny/webmcp-cli",
|
|
551
|
-
version: "0.1.
|
|
551
|
+
version: "0.1.1",
|
|
552
552
|
type: "module",
|
|
553
553
|
description: "WebMCP CLI for AI Agents to interact with browser via Puppeteer",
|
|
554
|
+
repository: {
|
|
555
|
+
type: "git",
|
|
556
|
+
url: "git+https://github.com/opentiny/webmcp-sdk.git",
|
|
557
|
+
directory: "packages/webmcp-cli"
|
|
558
|
+
},
|
|
559
|
+
bugs: {
|
|
560
|
+
url: "https://github.com/opentiny/webmcp-sdk/issues"
|
|
561
|
+
},
|
|
554
562
|
main: "dist/index.js",
|
|
555
563
|
types: "dist/index.d.ts",
|
|
556
564
|
bin: {
|
|
@@ -558,7 +566,8 @@ var init_package = __esm({
|
|
|
558
566
|
},
|
|
559
567
|
files: [
|
|
560
568
|
"dist",
|
|
561
|
-
"README.md"
|
|
569
|
+
"README.md",
|
|
570
|
+
"scripts/ensure-pty-helper.mjs"
|
|
562
571
|
],
|
|
563
572
|
scripts: {
|
|
564
573
|
dev: "tsup --watch",
|
|
@@ -567,6 +576,7 @@ var init_package = __esm({
|
|
|
567
576
|
prepublishOnly: "pnpm run build",
|
|
568
577
|
"link:global": "npm install -g .",
|
|
569
578
|
start: "node dist/bin.js",
|
|
579
|
+
postinstall: "node scripts/ensure-pty-helper.mjs",
|
|
570
580
|
test: "vitest run",
|
|
571
581
|
"test:watch": "vitest",
|
|
572
582
|
"test:browser": "vitest run --config vitest.e2e.config.ts"
|
|
@@ -579,7 +589,7 @@ var init_package = __esm({
|
|
|
579
589
|
license: "MIT",
|
|
580
590
|
author: "OpenTiny Team",
|
|
581
591
|
engines: {
|
|
582
|
-
node: ">=
|
|
592
|
+
node: ">=18.19.0"
|
|
583
593
|
},
|
|
584
594
|
publishConfig: {
|
|
585
595
|
access: "public"
|
|
@@ -591,6 +601,7 @@ var init_package = __esm({
|
|
|
591
601
|
"@page-agent/page-controller": "catalog:",
|
|
592
602
|
clipboardy: "^5.3.1",
|
|
593
603
|
commander: "^11.1.0",
|
|
604
|
+
execa: "^9.6.1",
|
|
594
605
|
json5: "^2.2.3",
|
|
595
606
|
marked: "^15.0.7",
|
|
596
607
|
picocolors: "^1.0.0",
|
|
@@ -598,6 +609,9 @@ var init_package = __esm({
|
|
|
598
609
|
ws: "^8.18.0",
|
|
599
610
|
zod: "^3.23.0"
|
|
600
611
|
},
|
|
612
|
+
optionalDependencies: {
|
|
613
|
+
"node-pty": "^1.1.0"
|
|
614
|
+
},
|
|
601
615
|
devDependencies: {
|
|
602
616
|
"@types/node": "^20.0.0",
|
|
603
617
|
"@types/ws": "^8.5.10",
|
|
@@ -819,12 +833,15 @@ var init_cdp_adapter = __esm({
|
|
|
819
833
|
});
|
|
820
834
|
|
|
821
835
|
// src/bridge/protocol.ts
|
|
822
|
-
|
|
836
|
+
function isTerminalMethod(method) {
|
|
837
|
+
return TERMINAL_METHOD_SET.has(method);
|
|
838
|
+
}
|
|
839
|
+
var DEFAULT_WS_PORT, PROTOCOL_VERSION, WS_CLOSE_CODE, BRIDGE_ERROR_CODE, BRIDGE_METHODS, TERMINAL_EVENTS, BASH_EVENTS, TERMINAL_METHOD_SET;
|
|
823
840
|
var init_protocol = __esm({
|
|
824
841
|
"src/bridge/protocol.ts"() {
|
|
825
842
|
"use strict";
|
|
826
843
|
DEFAULT_WS_PORT = 18999;
|
|
827
|
-
PROTOCOL_VERSION = "1.
|
|
844
|
+
PROTOCOL_VERSION = "1.2.0";
|
|
828
845
|
WS_CLOSE_CODE = {
|
|
829
846
|
/** 被新接入的扩展连接平滑替换(旧连接触发退避重连) */
|
|
830
847
|
REPLACED: 4e3,
|
|
@@ -858,8 +875,25 @@ var init_protocol = __esm({
|
|
|
858
875
|
SKILLS_GET: "skills/get",
|
|
859
876
|
SKILLS_READ_RESOURCE: "skills/read_resource",
|
|
860
877
|
SYSTEM_BASH: "system/bash",
|
|
861
|
-
SYSTEM_PICK_DIRECTORY: "system/pick_directory"
|
|
878
|
+
SYSTEM_PICK_DIRECTORY: "system/pick_directory",
|
|
879
|
+
TERMINAL_OPEN: "terminal/open",
|
|
880
|
+
TERMINAL_INPUT: "terminal/input",
|
|
881
|
+
TERMINAL_RESIZE: "terminal/resize",
|
|
882
|
+
TERMINAL_CLOSE: "terminal/close"
|
|
883
|
+
};
|
|
884
|
+
TERMINAL_EVENTS = {
|
|
885
|
+
OUTPUT: "notification/terminal_output",
|
|
886
|
+
EXIT: "notification/terminal_exit"
|
|
862
887
|
};
|
|
888
|
+
BASH_EVENTS = {
|
|
889
|
+
OUTPUT: "notification/bash_output"
|
|
890
|
+
};
|
|
891
|
+
TERMINAL_METHOD_SET = /* @__PURE__ */ new Set([
|
|
892
|
+
BRIDGE_METHODS.TERMINAL_OPEN,
|
|
893
|
+
BRIDGE_METHODS.TERMINAL_INPUT,
|
|
894
|
+
BRIDGE_METHODS.TERMINAL_RESIZE,
|
|
895
|
+
BRIDGE_METHODS.TERMINAL_CLOSE
|
|
896
|
+
]);
|
|
863
897
|
}
|
|
864
898
|
});
|
|
865
899
|
|
|
@@ -968,26 +1002,137 @@ function resolveShell(forcePlatform) {
|
|
|
968
1002
|
if (!forcePlatform) cachedShell = fallback;
|
|
969
1003
|
return fallback;
|
|
970
1004
|
}
|
|
971
|
-
function
|
|
972
|
-
|
|
1005
|
+
function stripAnsi(text) {
|
|
1006
|
+
return text.replace(ANSI_PATTERN, "");
|
|
1007
|
+
}
|
|
1008
|
+
function createAnsiStripper() {
|
|
1009
|
+
let pending = "";
|
|
1010
|
+
const take = (final) => {
|
|
1011
|
+
let out = "";
|
|
1012
|
+
let index = 0;
|
|
1013
|
+
while (index < pending.length) {
|
|
1014
|
+
if (pending[index] !== "\x1B") {
|
|
1015
|
+
out += pending[index];
|
|
1016
|
+
index += 1;
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
const rest = pending.slice(index);
|
|
1020
|
+
ANSI_PATTERN.lastIndex = 0;
|
|
1021
|
+
const match = ANSI_PATTERN.exec(rest);
|
|
1022
|
+
if (match && match.index === 0) {
|
|
1023
|
+
index += match[0].length;
|
|
1024
|
+
continue;
|
|
1025
|
+
}
|
|
1026
|
+
if (!final && rest.length < 128 && isOpenAnsi(rest)) {
|
|
1027
|
+
pending = rest;
|
|
1028
|
+
return out;
|
|
1029
|
+
}
|
|
1030
|
+
out += pending[index];
|
|
1031
|
+
index += 1;
|
|
1032
|
+
}
|
|
1033
|
+
pending = "";
|
|
1034
|
+
return out;
|
|
1035
|
+
};
|
|
1036
|
+
return {
|
|
1037
|
+
push(text) {
|
|
1038
|
+
pending += text;
|
|
1039
|
+
return take(false);
|
|
1040
|
+
},
|
|
1041
|
+
end() {
|
|
1042
|
+
return take(true);
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
function isOpenAnsi(text) {
|
|
1047
|
+
if (!text.startsWith("\x1B")) return false;
|
|
1048
|
+
if (text.length === 1) return true;
|
|
1049
|
+
if (text[1] === "[") {
|
|
1050
|
+
let index = 2;
|
|
1051
|
+
while (index < text.length && /[0-?]/.test(text[index])) index += 1;
|
|
1052
|
+
while (index < text.length && /[ -/]/.test(text[index])) index += 1;
|
|
1053
|
+
return index >= text.length;
|
|
1054
|
+
}
|
|
1055
|
+
if (text[1] === "]") return !text.includes("\x07") && !text.includes("\x1B\\");
|
|
1056
|
+
return false;
|
|
1057
|
+
}
|
|
1058
|
+
function withoutCarriageReturn(buf) {
|
|
1059
|
+
if (!buf.includes(13)) return buf;
|
|
1060
|
+
const out = Buffer.allocUnsafe(buf.length);
|
|
1061
|
+
let size = 0;
|
|
1062
|
+
for (let index = 0; index < buf.length; index += 1) {
|
|
1063
|
+
const byte = buf[index];
|
|
1064
|
+
if (byte === 13) continue;
|
|
1065
|
+
out[size] = byte;
|
|
1066
|
+
size += 1;
|
|
1067
|
+
}
|
|
1068
|
+
return out.subarray(0, size);
|
|
1069
|
+
}
|
|
1070
|
+
function collectLimited(stream, maxBuffer, onText, stripCarriageReturn = false, ansi) {
|
|
1071
|
+
const state = { chunks: [], truncated: false };
|
|
1072
|
+
if (!stream) return state;
|
|
1073
|
+
const decoder = new import_node_string_decoder.StringDecoder("utf8");
|
|
1074
|
+
let bytes = 0;
|
|
1075
|
+
const emitText = (text) => {
|
|
1076
|
+
const cleaned = ansi ? ansi.push(text) : text;
|
|
1077
|
+
if (cleaned) onText?.(cleaned);
|
|
1078
|
+
};
|
|
1079
|
+
const emit = (buf) => {
|
|
1080
|
+
emitText(decoder.write(buf));
|
|
1081
|
+
};
|
|
1082
|
+
stream.on("data", (chunk) => {
|
|
1083
|
+
const raw = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1084
|
+
const buf = stripCarriageReturn ? withoutCarriageReturn(raw) : raw;
|
|
1085
|
+
if (buf.length === 0) return;
|
|
1086
|
+
emit(buf);
|
|
1087
|
+
if (bytes >= maxBuffer) {
|
|
1088
|
+
state.truncated = true;
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
const remaining = maxBuffer - bytes;
|
|
1092
|
+
if (buf.length <= remaining) {
|
|
1093
|
+
state.chunks.push(buf);
|
|
1094
|
+
bytes += buf.length;
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
state.chunks.push(buf.subarray(0, remaining));
|
|
1098
|
+
bytes += remaining;
|
|
1099
|
+
state.truncated = true;
|
|
1100
|
+
});
|
|
1101
|
+
stream.on("end", () => {
|
|
1102
|
+
const rest = ansi ? `${ansi.push(decoder.end())}${ansi.end()}` : decoder.end();
|
|
1103
|
+
if (rest) onText?.(rest);
|
|
1104
|
+
});
|
|
1105
|
+
return state;
|
|
1106
|
+
}
|
|
1107
|
+
function decodeLimited(state, removeAnsi = false) {
|
|
1108
|
+
const decoder = new import_node_string_decoder.StringDecoder("utf8");
|
|
1109
|
+
const text = decoder.write(Buffer.concat(state.chunks));
|
|
1110
|
+
const flushed = state.truncated ? "" : decoder.end();
|
|
1111
|
+
const decoded = text + flushed;
|
|
1112
|
+
return (removeAnsi ? stripAnsi(decoded) : decoded).trim();
|
|
1113
|
+
}
|
|
1114
|
+
function signalProcessTree(pid, signal) {
|
|
1115
|
+
if (!pid || pid <= 0) return;
|
|
1116
|
+
if (process.platform === "win32") {
|
|
973
1117
|
try {
|
|
974
|
-
(0, import_node_child_process.
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
1118
|
+
(0, import_node_child_process.spawnSync)("taskkill", ["/pid", String(pid), "/T", "/F"], { windowsHide: true, timeout: 5e3 });
|
|
1119
|
+
} catch {
|
|
1120
|
+
}
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
try {
|
|
1124
|
+
process.kill(-pid, signal);
|
|
1125
|
+
} catch {
|
|
1126
|
+
try {
|
|
1127
|
+
process.kill(pid, signal);
|
|
978
1128
|
} catch {
|
|
979
|
-
child.kill("SIGKILL");
|
|
980
1129
|
}
|
|
981
|
-
} else {
|
|
982
|
-
child.kill("SIGTERM");
|
|
983
|
-
setTimeout(() => {
|
|
984
|
-
try {
|
|
985
|
-
child.kill("SIGKILL");
|
|
986
|
-
} catch {
|
|
987
|
-
}
|
|
988
|
-
}, 2e3);
|
|
989
1130
|
}
|
|
990
1131
|
}
|
|
1132
|
+
function ignoreStreamError(stream) {
|
|
1133
|
+
stream?.on("error", () => {
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
991
1136
|
async function executeBash(params) {
|
|
992
1137
|
const trimmedCommand = params.command?.trim();
|
|
993
1138
|
if (!trimmedCommand) {
|
|
@@ -1005,83 +1150,91 @@ async function executeBash(params) {
|
|
|
1005
1150
|
});
|
|
1006
1151
|
const normalizedTimeoutMs = typeof params.timeoutMs === "number" && Number.isFinite(params.timeoutMs) && params.timeoutMs > 0 ? Math.min(params.timeoutMs, MAX_TIMEOUT_MS) : DEFAULT_TIMEOUT_MS;
|
|
1007
1152
|
const { env = {}, maxBuffer = DEFAULT_MAX_BUFFER } = params;
|
|
1008
|
-
const startTime = Date.now();
|
|
1009
1153
|
const shell = resolveShell();
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
} else {
|
|
1040
|
-
truncated = true;
|
|
1041
|
-
}
|
|
1042
|
-
});
|
|
1043
|
-
child.stderr.on("data", (chunk) => {
|
|
1044
|
-
if (stderrBytes < maxBuffer) {
|
|
1045
|
-
const remaining = maxBuffer - stderrBytes;
|
|
1046
|
-
if (chunk.length <= remaining) {
|
|
1047
|
-
stderrChunks.push(chunk);
|
|
1048
|
-
stderrBytes += chunk.length;
|
|
1049
|
-
} else {
|
|
1050
|
-
stderrChunks.push(chunk.subarray(0, remaining));
|
|
1051
|
-
stderrBytes += remaining;
|
|
1052
|
-
truncated = true;
|
|
1053
|
-
}
|
|
1054
|
-
} else {
|
|
1055
|
-
truncated = true;
|
|
1056
|
-
}
|
|
1057
|
-
});
|
|
1058
|
-
child.on("error", (err) => {
|
|
1059
|
-
clearTimeout(killTimeoutTimer);
|
|
1060
|
-
reject(err);
|
|
1061
|
-
});
|
|
1062
|
-
child.on("close", (exitCode, signal) => {
|
|
1063
|
-
clearTimeout(killTimeoutTimer);
|
|
1064
|
-
if (isKilled) return;
|
|
1065
|
-
const stdout = Buffer.concat(stdoutChunks).toString("utf8").trim();
|
|
1066
|
-
const stderr = Buffer.concat(stderrChunks).toString("utf8").trim();
|
|
1067
|
-
resolve3({
|
|
1068
|
-
stdout,
|
|
1069
|
-
stderr,
|
|
1070
|
-
exitCode,
|
|
1071
|
-
signal,
|
|
1072
|
-
durationMs: Date.now() - startTime,
|
|
1073
|
-
...truncated ? { truncated: true } : {}
|
|
1074
|
-
});
|
|
1075
|
-
});
|
|
1154
|
+
const useTty = params.tty === true && process.platform !== "win32";
|
|
1155
|
+
const detached = process.platform !== "win32";
|
|
1156
|
+
const subprocess = useTty ? (0, import_execa.execa)("python3", ["-u", "-c", TTY_HELPER], {
|
|
1157
|
+
cwd,
|
|
1158
|
+
env: {
|
|
1159
|
+
...env,
|
|
1160
|
+
PAGER: "cat",
|
|
1161
|
+
GIT_PAGER: "cat",
|
|
1162
|
+
BASH_TTY_COMMAND: trimmedCommand,
|
|
1163
|
+
BASH_TTY_SHELL: shell
|
|
1164
|
+
},
|
|
1165
|
+
stdin: "ignore",
|
|
1166
|
+
buffer: false,
|
|
1167
|
+
encoding: "buffer",
|
|
1168
|
+
reject: false,
|
|
1169
|
+
windowsHide: true,
|
|
1170
|
+
stripFinalNewline: false,
|
|
1171
|
+
detached
|
|
1172
|
+
}) : (0, import_execa.execa)(trimmedCommand, {
|
|
1173
|
+
shell,
|
|
1174
|
+
cwd,
|
|
1175
|
+
env,
|
|
1176
|
+
stdin: "ignore",
|
|
1177
|
+
buffer: false,
|
|
1178
|
+
encoding: "buffer",
|
|
1179
|
+
reject: false,
|
|
1180
|
+
windowsHide: true,
|
|
1181
|
+
stripFinalNewline: false,
|
|
1182
|
+
detached
|
|
1076
1183
|
});
|
|
1184
|
+
ignoreStreamError(subprocess.stdout);
|
|
1185
|
+
ignoreStreamError(subprocess.stderr);
|
|
1186
|
+
let timedOut = false;
|
|
1187
|
+
let forceTimer;
|
|
1188
|
+
const timer = setTimeout(() => {
|
|
1189
|
+
timedOut = true;
|
|
1190
|
+
signalProcessTree(subprocess.pid, "SIGTERM");
|
|
1191
|
+
forceTimer = setTimeout(() => {
|
|
1192
|
+
signalProcessTree(subprocess.pid, "SIGKILL");
|
|
1193
|
+
subprocess.stdout?.destroy();
|
|
1194
|
+
subprocess.stderr?.destroy();
|
|
1195
|
+
}, FORCE_KILL_AFTER_DELAY_MS);
|
|
1196
|
+
}, normalizedTimeoutMs);
|
|
1197
|
+
const emit = (stream) => (text) => {
|
|
1198
|
+
params.onOutput?.({ stream, text });
|
|
1199
|
+
};
|
|
1200
|
+
const ansi = useTty ? createAnsiStripper() : void 0;
|
|
1201
|
+
const stdoutState = collectLimited(subprocess.stdout, maxBuffer, emit("stdout"), useTty, ansi);
|
|
1202
|
+
const stderrState = collectLimited(subprocess.stderr, maxBuffer, emit("stderr"));
|
|
1203
|
+
let result;
|
|
1204
|
+
try {
|
|
1205
|
+
result = await subprocess;
|
|
1206
|
+
} catch (err) {
|
|
1207
|
+
if (timedOut) throw new BashTimeoutError(normalizedTimeoutMs);
|
|
1208
|
+
throw err;
|
|
1209
|
+
} finally {
|
|
1210
|
+
clearTimeout(timer);
|
|
1211
|
+
if (forceTimer) clearTimeout(forceTimer);
|
|
1212
|
+
}
|
|
1213
|
+
if (timedOut) {
|
|
1214
|
+
throw new BashTimeoutError(normalizedTimeoutMs);
|
|
1215
|
+
}
|
|
1216
|
+
if (result.failed && result.exitCode == null && result.signal == null && !result.isTerminated) {
|
|
1217
|
+
throw new Error(result.shortMessage || result.message || "\u547D\u4EE4\u542F\u52A8\u5931\u8D25");
|
|
1218
|
+
}
|
|
1219
|
+
const truncated = stdoutState.truncated || stderrState.truncated;
|
|
1220
|
+
return {
|
|
1221
|
+
stdout: decodeLimited(stdoutState, useTty),
|
|
1222
|
+
stderr: decodeLimited(stderrState),
|
|
1223
|
+
exitCode: result.exitCode ?? null,
|
|
1224
|
+
signal: result.signal ?? null,
|
|
1225
|
+
durationMs: result.durationMs ?? 0,
|
|
1226
|
+
...truncated ? { truncated: true } : {}
|
|
1227
|
+
};
|
|
1077
1228
|
}
|
|
1078
|
-
var import_node_fs2, import_node_path2,
|
|
1229
|
+
var import_node_child_process, import_node_fs2, import_node_path2, import_node_string_decoder, import_execa, BashTimeoutError, DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS, DEFAULT_MAX_BUFFER, cachedShell, FORCE_KILL_AFTER_DELAY_MS, TTY_HELPER, ANSI_PATTERN;
|
|
1079
1230
|
var init_bash_executor = __esm({
|
|
1080
1231
|
"src/bridge/bash-executor.ts"() {
|
|
1081
1232
|
"use strict";
|
|
1233
|
+
import_node_child_process = require("child_process");
|
|
1082
1234
|
import_node_fs2 = __toESM(require("fs"), 1);
|
|
1083
1235
|
import_node_path2 = __toESM(require("path"), 1);
|
|
1084
|
-
|
|
1236
|
+
import_node_string_decoder = require("string_decoder");
|
|
1237
|
+
import_execa = require("execa");
|
|
1085
1238
|
init_workspace_manager();
|
|
1086
1239
|
BashTimeoutError = class extends Error {
|
|
1087
1240
|
code = "TIMEOUT";
|
|
@@ -1096,6 +1249,51 @@ var init_bash_executor = __esm({
|
|
|
1096
1249
|
MAX_TIMEOUT_MS = 6e5;
|
|
1097
1250
|
DEFAULT_MAX_BUFFER = 512 * 1024;
|
|
1098
1251
|
cachedShell = null;
|
|
1252
|
+
FORCE_KILL_AFTER_DELAY_MS = 2e3;
|
|
1253
|
+
TTY_HELPER = `
|
|
1254
|
+
import errno, os, sys
|
|
1255
|
+
cmd = os.environ.get("BASH_TTY_COMMAND") or ""
|
|
1256
|
+
shell = os.environ.get("BASH_TTY_SHELL") or "/bin/bash"
|
|
1257
|
+
if not cmd:
|
|
1258
|
+
sys.exit(127)
|
|
1259
|
+
import pty
|
|
1260
|
+
pid, fd = pty.fork()
|
|
1261
|
+
if pid == 0:
|
|
1262
|
+
os.environ.pop("BASH_TTY_COMMAND", None)
|
|
1263
|
+
os.environ.pop("BASH_TTY_SHELL", None)
|
|
1264
|
+
os.environ["TERM"] = "xterm-256color"
|
|
1265
|
+
try:
|
|
1266
|
+
# stdout/stderr \u4ECD\u662F\u7EC8\u7AEF\uFF1Bstdin \u6362\u6210 /dev/null\uFF0C\u8BFB\u53D6\u8F93\u5165\u7684\u547D\u4EE4\u4F1A\u7ACB\u523B\u9047\u5230 EOF
|
|
1267
|
+
null_fd = os.open(os.devnull, os.O_RDONLY)
|
|
1268
|
+
os.dup2(null_fd, 0)
|
|
1269
|
+
os.close(null_fd)
|
|
1270
|
+
os.execvp(shell, [shell, "-c", cmd])
|
|
1271
|
+
except OSError:
|
|
1272
|
+
os._exit(127)
|
|
1273
|
+
while True:
|
|
1274
|
+
try:
|
|
1275
|
+
data = os.read(fd, 65536)
|
|
1276
|
+
except OSError as exc:
|
|
1277
|
+
if exc.errno == errno.EINTR:
|
|
1278
|
+
continue
|
|
1279
|
+
break
|
|
1280
|
+
if not data:
|
|
1281
|
+
break
|
|
1282
|
+
try:
|
|
1283
|
+
os.write(1, data)
|
|
1284
|
+
except OSError:
|
|
1285
|
+
break
|
|
1286
|
+
try:
|
|
1287
|
+
_, status = os.waitpid(pid, 0)
|
|
1288
|
+
except OSError:
|
|
1289
|
+
sys.exit(1)
|
|
1290
|
+
if os.WIFEXITED(status):
|
|
1291
|
+
sys.exit(os.WEXITSTATUS(status))
|
|
1292
|
+
if os.WIFSIGNALED(status):
|
|
1293
|
+
sys.exit(128 + os.WTERMSIG(status))
|
|
1294
|
+
sys.exit(1)
|
|
1295
|
+
`;
|
|
1296
|
+
ANSI_PATTERN = /\u001B(?:\[[0-?]*[ -/]*[@-~]|\][^\u0007]*(?:\u0007|\u001B\\)|[@-Z\\-_])/g;
|
|
1099
1297
|
}
|
|
1100
1298
|
});
|
|
1101
1299
|
|
|
@@ -1307,6 +1505,387 @@ var init_directory_picker = __esm({
|
|
|
1307
1505
|
}
|
|
1308
1506
|
});
|
|
1309
1507
|
|
|
1508
|
+
// src/bridge/pty-backend.ts
|
|
1509
|
+
function interactiveArgs(shell) {
|
|
1510
|
+
if (process.platform !== "win32") return ["-i"];
|
|
1511
|
+
const base = import_node_path3.default.basename(shell).toLowerCase();
|
|
1512
|
+
if (base === "powershell.exe" || base === "pwsh.exe") return ["-NoLogo"];
|
|
1513
|
+
if (base === "cmd.exe") return [];
|
|
1514
|
+
return ["-i"];
|
|
1515
|
+
}
|
|
1516
|
+
function ptyEnvironment() {
|
|
1517
|
+
const env = {};
|
|
1518
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
1519
|
+
if (value !== void 0) env[key] = value;
|
|
1520
|
+
}
|
|
1521
|
+
env.TERM = "xterm-256color";
|
|
1522
|
+
return env;
|
|
1523
|
+
}
|
|
1524
|
+
function resolveSpawnFile(shell, pathEnv = process.env.PATH || "") {
|
|
1525
|
+
if (!shell || import_node_path3.default.isAbsolute(shell)) return shell;
|
|
1526
|
+
const names = process.platform === "win32" && !shell.toLowerCase().endsWith(".exe") ? [shell, `${shell}.exe`] : [shell];
|
|
1527
|
+
for (const dir of pathEnv.split(import_node_path3.default.delimiter)) {
|
|
1528
|
+
if (!dir) continue;
|
|
1529
|
+
for (const name of names) {
|
|
1530
|
+
const candidate = import_node_path3.default.join(dir, name);
|
|
1531
|
+
try {
|
|
1532
|
+
if ((0, import_node_fs3.existsSync)(candidate)) return candidate;
|
|
1533
|
+
} catch {
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
if (process.platform === "win32") {
|
|
1538
|
+
const systemRoot = process.env.SystemRoot || "C:\\Windows";
|
|
1539
|
+
const fallbacks = [
|
|
1540
|
+
import_node_path3.default.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe"),
|
|
1541
|
+
import_node_path3.default.join(systemRoot, "Sysnative", "WindowsPowerShell", "v1.0", "powershell.exe")
|
|
1542
|
+
];
|
|
1543
|
+
for (const candidate of fallbacks) {
|
|
1544
|
+
try {
|
|
1545
|
+
if ((0, import_node_fs3.existsSync)(candidate)) return candidate;
|
|
1546
|
+
} catch {
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
return shell;
|
|
1551
|
+
}
|
|
1552
|
+
function describeSpawnHelperChmodError(helper, code) {
|
|
1553
|
+
if (code !== "EPERM" && code !== "EACCES") return null;
|
|
1554
|
+
return `spawn-helper \u65E0\u6CD5\u8BBE\u7F6E\u4E3A\u53EF\u6267\u884C\uFF08${helper}\uFF09\u3002\u5F53\u524D\u7528\u6237\u6CA1\u6709\u6743\u9650\u4FEE\u6539\u8BE5\u6587\u4EF6\u3002\u8BF7\u7528\u5B89\u88C5\u65F6\u7684\u7528\u6237\u91CD\u65B0\u5B89\u88C5 @opentiny/webmcp-cli\uFF0C\u6216\u5BF9\u8BE5\u6587\u4EF6\u6267\u884C chmod 755\u3002`;
|
|
1555
|
+
}
|
|
1556
|
+
function loadNodePty() {
|
|
1557
|
+
try {
|
|
1558
|
+
return (0, import_node_module.createRequire)(import_meta2.url)("node-pty");
|
|
1559
|
+
} catch (err) {
|
|
1560
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
1561
|
+
throw new Error(`\u4EA4\u4E92\u7EC8\u7AEF\u4E0D\u53EF\u7528\uFF1A\u672A\u80FD\u52A0\u8F7D node-pty\uFF08${detail}\uFF09\u3002\u5176\u4F59\u6865\u63A5\u547D\u4EE4\u4ECD\u53EF\u4F7F\u7528\u3002`);
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
function spawnHelperPath() {
|
|
1565
|
+
try {
|
|
1566
|
+
const pkgJson = (0, import_node_module.createRequire)(import_meta2.url).resolve("node-pty/package.json");
|
|
1567
|
+
return import_node_path3.default.join(import_node_path3.default.dirname(pkgJson), "prebuilds", `${process.platform}-${process.arch}`, "spawn-helper");
|
|
1568
|
+
} catch {
|
|
1569
|
+
return null;
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
function ensureSpawnHelper() {
|
|
1573
|
+
if (process.platform === "win32") return null;
|
|
1574
|
+
const helper = spawnHelperPath();
|
|
1575
|
+
if (!helper || !(0, import_node_fs3.existsSync)(helper)) return null;
|
|
1576
|
+
try {
|
|
1577
|
+
(0, import_node_fs3.chmodSync)(helper, 493);
|
|
1578
|
+
return null;
|
|
1579
|
+
} catch (err) {
|
|
1580
|
+
const code = err && typeof err === "object" && "code" in err ? String(err.code) : void 0;
|
|
1581
|
+
return describeSpawnHelperChmodError(helper, code);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
function signalName(code) {
|
|
1585
|
+
if (!code) return null;
|
|
1586
|
+
for (const [name, value] of Object.entries(import_node_os2.constants.signals)) {
|
|
1587
|
+
if (value === code) return name;
|
|
1588
|
+
}
|
|
1589
|
+
return String(code);
|
|
1590
|
+
}
|
|
1591
|
+
function descendantPids(rootPid) {
|
|
1592
|
+
const result = (0, import_node_child_process3.spawnSync)("ps", ["-ax", "-o", "pid=,ppid="], { encoding: "utf8", timeout: 3e3 });
|
|
1593
|
+
if (result.status !== 0 || !result.stdout) return [];
|
|
1594
|
+
const children = /* @__PURE__ */ new Map();
|
|
1595
|
+
for (const line of result.stdout.split("\n")) {
|
|
1596
|
+
const [pidText, ppidText] = line.trim().split(/\s+/);
|
|
1597
|
+
const pid = Number(pidText);
|
|
1598
|
+
const ppid = Number(ppidText);
|
|
1599
|
+
if (!pid || !ppid) continue;
|
|
1600
|
+
const list = children.get(ppid);
|
|
1601
|
+
if (list) list.push(pid);
|
|
1602
|
+
else children.set(ppid, [pid]);
|
|
1603
|
+
}
|
|
1604
|
+
const out = [];
|
|
1605
|
+
const walk = (pid) => {
|
|
1606
|
+
for (const child of children.get(pid) || []) {
|
|
1607
|
+
walk(child);
|
|
1608
|
+
out.push(child);
|
|
1609
|
+
}
|
|
1610
|
+
};
|
|
1611
|
+
walk(rootPid);
|
|
1612
|
+
return out;
|
|
1613
|
+
}
|
|
1614
|
+
function killPtySession(term) {
|
|
1615
|
+
const pid = term.pid;
|
|
1616
|
+
if (pid && pid > 0) {
|
|
1617
|
+
if (process.platform === "win32") {
|
|
1618
|
+
try {
|
|
1619
|
+
(0, import_node_child_process3.spawnSync)("taskkill", ["/pid", String(pid), "/T", "/F"], { windowsHide: true, timeout: 3e3 });
|
|
1620
|
+
} catch {
|
|
1621
|
+
}
|
|
1622
|
+
} else {
|
|
1623
|
+
for (const victim of [...descendantPids(pid), pid]) {
|
|
1624
|
+
try {
|
|
1625
|
+
process.kill(victim, "SIGKILL");
|
|
1626
|
+
} catch {
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
try {
|
|
1632
|
+
term.kill("SIGHUP");
|
|
1633
|
+
} catch {
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
function spawnHostPty(options) {
|
|
1637
|
+
const pty = loadNodePty();
|
|
1638
|
+
const helperError = ensureSpawnHelper();
|
|
1639
|
+
const shell = resolveSpawnFile(options.shell || resolveShell());
|
|
1640
|
+
const pending = [];
|
|
1641
|
+
let dataCb = null;
|
|
1642
|
+
let exitCb = null;
|
|
1643
|
+
let pendingExit = null;
|
|
1644
|
+
let settled = false;
|
|
1645
|
+
let term = null;
|
|
1646
|
+
const emitData = (data) => {
|
|
1647
|
+
if (!data) return;
|
|
1648
|
+
if (dataCb) dataCb(data);
|
|
1649
|
+
else pending.push(data);
|
|
1650
|
+
};
|
|
1651
|
+
const finish = (info) => {
|
|
1652
|
+
if (settled) return;
|
|
1653
|
+
settled = true;
|
|
1654
|
+
if (exitCb) exitCb(info);
|
|
1655
|
+
else pendingExit = info;
|
|
1656
|
+
};
|
|
1657
|
+
try {
|
|
1658
|
+
term = pty.spawn(shell, interactiveArgs(shell), {
|
|
1659
|
+
name: "xterm-256color",
|
|
1660
|
+
cols: options.cols,
|
|
1661
|
+
rows: options.rows,
|
|
1662
|
+
cwd: options.cwd,
|
|
1663
|
+
env: ptyEnvironment()
|
|
1664
|
+
});
|
|
1665
|
+
term.onData((data) => emitData(data));
|
|
1666
|
+
term.onExit((event) => {
|
|
1667
|
+
finish({ exitCode: event.exitCode, signal: signalName(event.signal) });
|
|
1668
|
+
});
|
|
1669
|
+
} catch (err) {
|
|
1670
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1671
|
+
const detail = helperError ? `${helperError} ${message}` : message;
|
|
1672
|
+
setImmediate(() => {
|
|
1673
|
+
emitData(`\r
|
|
1674
|
+
[\u7EC8\u7AEF\u542F\u52A8\u5931\u8D25] ${detail}\r
|
|
1675
|
+
`);
|
|
1676
|
+
finish({ exitCode: null, signal: null });
|
|
1677
|
+
});
|
|
1678
|
+
}
|
|
1679
|
+
return {
|
|
1680
|
+
write(data) {
|
|
1681
|
+
try {
|
|
1682
|
+
term?.write(data);
|
|
1683
|
+
} catch {
|
|
1684
|
+
}
|
|
1685
|
+
},
|
|
1686
|
+
resize(cols, rows) {
|
|
1687
|
+
try {
|
|
1688
|
+
term?.resize(cols, rows);
|
|
1689
|
+
} catch {
|
|
1690
|
+
}
|
|
1691
|
+
},
|
|
1692
|
+
kill() {
|
|
1693
|
+
if (term) killPtySession(term);
|
|
1694
|
+
},
|
|
1695
|
+
pause() {
|
|
1696
|
+
try {
|
|
1697
|
+
term?.pause();
|
|
1698
|
+
} catch {
|
|
1699
|
+
}
|
|
1700
|
+
},
|
|
1701
|
+
resume() {
|
|
1702
|
+
try {
|
|
1703
|
+
term?.resume();
|
|
1704
|
+
} catch {
|
|
1705
|
+
}
|
|
1706
|
+
},
|
|
1707
|
+
onData(cb) {
|
|
1708
|
+
dataCb = cb;
|
|
1709
|
+
for (const chunk of pending) cb(chunk);
|
|
1710
|
+
pending.length = 0;
|
|
1711
|
+
},
|
|
1712
|
+
onExit(cb) {
|
|
1713
|
+
exitCb = cb;
|
|
1714
|
+
if (pendingExit) cb(pendingExit);
|
|
1715
|
+
}
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1718
|
+
var import_node_child_process3, import_node_fs3, import_node_module, import_node_path3, import_node_os2, import_meta2;
|
|
1719
|
+
var init_pty_backend = __esm({
|
|
1720
|
+
"src/bridge/pty-backend.ts"() {
|
|
1721
|
+
"use strict";
|
|
1722
|
+
import_node_child_process3 = require("child_process");
|
|
1723
|
+
import_node_fs3 = require("fs");
|
|
1724
|
+
import_node_module = require("module");
|
|
1725
|
+
import_node_path3 = __toESM(require("path"), 1);
|
|
1726
|
+
import_node_os2 = require("os");
|
|
1727
|
+
init_bash_executor();
|
|
1728
|
+
import_meta2 = {};
|
|
1729
|
+
}
|
|
1730
|
+
});
|
|
1731
|
+
|
|
1732
|
+
// src/bridge/terminal-session.ts
|
|
1733
|
+
function applyTerminalBackpressure(bufferedAmount, flow) {
|
|
1734
|
+
if (bufferedAmount > TERMINAL_OUTPUT_HIGH_WATER) flow.pauseOutput();
|
|
1735
|
+
else if (bufferedAmount <= TERMINAL_OUTPUT_HIGH_WATER / 2) flow.resumeOutput();
|
|
1736
|
+
}
|
|
1737
|
+
function clampDimension(value, fallback) {
|
|
1738
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
1739
|
+
if (!Number.isFinite(parsed)) return fallback;
|
|
1740
|
+
return Math.max(2, Math.min(500, Math.floor(parsed)));
|
|
1741
|
+
}
|
|
1742
|
+
var import_node_crypto, OUTPUT_CHUNK, MAX_INPUT, DEFAULT_COALESCE_MS, TERMINAL_OUTPUT_HIGH_WATER, TerminalSessionManager;
|
|
1743
|
+
var init_terminal_session = __esm({
|
|
1744
|
+
"src/bridge/terminal-session.ts"() {
|
|
1745
|
+
"use strict";
|
|
1746
|
+
import_node_crypto = require("crypto");
|
|
1747
|
+
init_workspace_manager();
|
|
1748
|
+
init_pty_backend();
|
|
1749
|
+
init_protocol();
|
|
1750
|
+
OUTPUT_CHUNK = 32 * 1024;
|
|
1751
|
+
MAX_INPUT = 64 * 1024;
|
|
1752
|
+
DEFAULT_COALESCE_MS = 16;
|
|
1753
|
+
TERMINAL_OUTPUT_HIGH_WATER = 1024 * 1024;
|
|
1754
|
+
TerminalSessionManager = class {
|
|
1755
|
+
session = null;
|
|
1756
|
+
buffer = "";
|
|
1757
|
+
timer = null;
|
|
1758
|
+
send;
|
|
1759
|
+
spawnPty;
|
|
1760
|
+
coalesceMs;
|
|
1761
|
+
constructor(options) {
|
|
1762
|
+
this.send = options.send;
|
|
1763
|
+
this.spawnPty = options.spawnPty ?? ((opts) => spawnHostPty(opts));
|
|
1764
|
+
this.coalesceMs = options.coalesceMs ?? DEFAULT_COALESCE_MS;
|
|
1765
|
+
}
|
|
1766
|
+
open(params = {}) {
|
|
1767
|
+
const cwd = resolveWorkspaceDir(params);
|
|
1768
|
+
const cols = clampDimension(params.cols, 80);
|
|
1769
|
+
const rows = clampDimension(params.rows, 24);
|
|
1770
|
+
this.closeActive();
|
|
1771
|
+
const id = (0, import_node_crypto.randomUUID)();
|
|
1772
|
+
const pty = this.spawnPty({ cwd, cols, rows });
|
|
1773
|
+
this.session = { id, pty, cwd };
|
|
1774
|
+
pty.onData((data) => this.pushOutput(id, data));
|
|
1775
|
+
pty.onExit((info) => {
|
|
1776
|
+
if (this.session?.id !== id) return;
|
|
1777
|
+
this.flush(id);
|
|
1778
|
+
this.session = null;
|
|
1779
|
+
this.send({
|
|
1780
|
+
jsonrpc: "2.0",
|
|
1781
|
+
method: TERMINAL_EVENTS.EXIT,
|
|
1782
|
+
params: {
|
|
1783
|
+
sessionId: id,
|
|
1784
|
+
exitCode: info.exitCode,
|
|
1785
|
+
signal: info.signal
|
|
1786
|
+
}
|
|
1787
|
+
});
|
|
1788
|
+
});
|
|
1789
|
+
return { sessionId: id, cwd };
|
|
1790
|
+
}
|
|
1791
|
+
input(params) {
|
|
1792
|
+
const session = this.requireSession(params.sessionId);
|
|
1793
|
+
if (typeof params.data !== "string") {
|
|
1794
|
+
throw new BashInvalidParamsError("data \u5FC5\u987B\u662F\u5B57\u7B26\u4E32");
|
|
1795
|
+
}
|
|
1796
|
+
if (params.data.length > MAX_INPUT) {
|
|
1797
|
+
throw new BashInvalidParamsError("\u5355\u6B21\u8F93\u5165\u8FC7\u957F");
|
|
1798
|
+
}
|
|
1799
|
+
session.pty.write(params.data);
|
|
1800
|
+
return { ok: true };
|
|
1801
|
+
}
|
|
1802
|
+
resize(params) {
|
|
1803
|
+
const session = this.requireSession(params.sessionId);
|
|
1804
|
+
session.pty.resize(clampDimension(params.cols, 80), clampDimension(params.rows, 24));
|
|
1805
|
+
return { ok: true };
|
|
1806
|
+
}
|
|
1807
|
+
close(params = {}) {
|
|
1808
|
+
if (!this.session || params.sessionId && params.sessionId !== this.session.id) {
|
|
1809
|
+
return { ok: true };
|
|
1810
|
+
}
|
|
1811
|
+
this.closeActive();
|
|
1812
|
+
return { ok: true };
|
|
1813
|
+
}
|
|
1814
|
+
disposeAll() {
|
|
1815
|
+
this.closeActive();
|
|
1816
|
+
}
|
|
1817
|
+
pauseOutput() {
|
|
1818
|
+
try {
|
|
1819
|
+
this.session?.pty.pause();
|
|
1820
|
+
} catch {
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
resumeOutput() {
|
|
1824
|
+
try {
|
|
1825
|
+
this.session?.pty.resume();
|
|
1826
|
+
} catch {
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
requireSession(sessionId) {
|
|
1830
|
+
if (!this.session || !sessionId || sessionId !== this.session.id) {
|
|
1831
|
+
throw new Error("\u7EC8\u7AEF\u4F1A\u8BDD\u4E0D\u5B58\u5728");
|
|
1832
|
+
}
|
|
1833
|
+
return this.session;
|
|
1834
|
+
}
|
|
1835
|
+
closeActive() {
|
|
1836
|
+
const current = this.session;
|
|
1837
|
+
this.session = null;
|
|
1838
|
+
this.buffer = "";
|
|
1839
|
+
if (this.timer) {
|
|
1840
|
+
clearTimeout(this.timer);
|
|
1841
|
+
this.timer = null;
|
|
1842
|
+
}
|
|
1843
|
+
current?.pty.kill();
|
|
1844
|
+
}
|
|
1845
|
+
pushOutput(sessionId, chunk) {
|
|
1846
|
+
if (this.session?.id !== sessionId || !chunk) return;
|
|
1847
|
+
this.buffer += chunk;
|
|
1848
|
+
this.flushFullChunks(sessionId);
|
|
1849
|
+
if (!this.buffer) return;
|
|
1850
|
+
if (this.coalesceMs <= 0) {
|
|
1851
|
+
this.flush(sessionId);
|
|
1852
|
+
return;
|
|
1853
|
+
}
|
|
1854
|
+
if (!this.timer) {
|
|
1855
|
+
this.timer = setTimeout(() => this.flush(sessionId), this.coalesceMs);
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
flushFullChunks(sessionId) {
|
|
1859
|
+
while (this.buffer.length >= OUTPUT_CHUNK) {
|
|
1860
|
+
const data = this.buffer.slice(0, OUTPUT_CHUNK);
|
|
1861
|
+
this.buffer = this.buffer.slice(OUTPUT_CHUNK);
|
|
1862
|
+
this.emitOutput(sessionId, data);
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
flush(sessionId) {
|
|
1866
|
+
if (this.timer) {
|
|
1867
|
+
clearTimeout(this.timer);
|
|
1868
|
+
this.timer = null;
|
|
1869
|
+
}
|
|
1870
|
+
if (!this.buffer || this.session?.id !== sessionId) {
|
|
1871
|
+
this.buffer = "";
|
|
1872
|
+
return;
|
|
1873
|
+
}
|
|
1874
|
+
const data = this.buffer;
|
|
1875
|
+
this.buffer = "";
|
|
1876
|
+
this.emitOutput(sessionId, data);
|
|
1877
|
+
}
|
|
1878
|
+
emitOutput(sessionId, data) {
|
|
1879
|
+
this.send({
|
|
1880
|
+
jsonrpc: "2.0",
|
|
1881
|
+
method: TERMINAL_EVENTS.OUTPUT,
|
|
1882
|
+
params: { sessionId, data }
|
|
1883
|
+
});
|
|
1884
|
+
}
|
|
1885
|
+
};
|
|
1886
|
+
}
|
|
1887
|
+
});
|
|
1888
|
+
|
|
1310
1889
|
// src/bridge/bridge-client.ts
|
|
1311
1890
|
var bridge_client_exports = {};
|
|
1312
1891
|
__export(bridge_client_exports, {
|
|
@@ -1319,14 +1898,18 @@ __export(bridge_client_exports, {
|
|
|
1319
1898
|
resolveOrCreateAuthToken: () => resolveOrCreateAuthToken,
|
|
1320
1899
|
verifyHmacConstantTime: () => verifyHmacConstantTime
|
|
1321
1900
|
});
|
|
1901
|
+
function isJsonRpcId(id) {
|
|
1902
|
+
if (id === null || typeof id === "string") return true;
|
|
1903
|
+
return typeof id === "number" && Number.isFinite(id);
|
|
1904
|
+
}
|
|
1322
1905
|
function resolveOrCreateAuthToken(explicitToken) {
|
|
1323
1906
|
if (explicitToken && explicitToken.trim()) {
|
|
1324
1907
|
const token = explicitToken.trim();
|
|
1325
1908
|
try {
|
|
1326
|
-
if (!
|
|
1327
|
-
|
|
1909
|
+
if (!import_node_fs4.default.existsSync(TOKEN_FILE_DIR)) {
|
|
1910
|
+
import_node_fs4.default.mkdirSync(TOKEN_FILE_DIR, { mode: 448, recursive: true });
|
|
1328
1911
|
}
|
|
1329
|
-
|
|
1912
|
+
import_node_fs4.default.writeFileSync(TOKEN_FILE_PATH, token, { mode: 384, encoding: "utf8" });
|
|
1330
1913
|
} catch {
|
|
1331
1914
|
}
|
|
1332
1915
|
return token;
|
|
@@ -1335,18 +1918,18 @@ function resolveOrCreateAuthToken(explicitToken) {
|
|
|
1335
1918
|
return process.env.ROBOT_WXT_BRIDGE_TOKEN.trim();
|
|
1336
1919
|
}
|
|
1337
1920
|
try {
|
|
1338
|
-
if (
|
|
1339
|
-
const existing =
|
|
1921
|
+
if (import_node_fs4.default.existsSync(TOKEN_FILE_PATH)) {
|
|
1922
|
+
const existing = import_node_fs4.default.readFileSync(TOKEN_FILE_PATH, "utf8").trim();
|
|
1340
1923
|
if (existing) return existing;
|
|
1341
1924
|
}
|
|
1342
1925
|
} catch {
|
|
1343
1926
|
}
|
|
1344
|
-
const newToken = (0,
|
|
1927
|
+
const newToken = (0, import_node_crypto2.randomBytes)(32).toString("hex");
|
|
1345
1928
|
try {
|
|
1346
|
-
if (!
|
|
1347
|
-
|
|
1929
|
+
if (!import_node_fs4.default.existsSync(TOKEN_FILE_DIR)) {
|
|
1930
|
+
import_node_fs4.default.mkdirSync(TOKEN_FILE_DIR, { mode: 448, recursive: true });
|
|
1348
1931
|
}
|
|
1349
|
-
|
|
1932
|
+
import_node_fs4.default.writeFileSync(TOKEN_FILE_PATH, newToken, { mode: 384, encoding: "utf8" });
|
|
1350
1933
|
} catch {
|
|
1351
1934
|
}
|
|
1352
1935
|
return newToken;
|
|
@@ -1355,10 +1938,10 @@ function parseMajorVersion(v) {
|
|
|
1355
1938
|
return parseInt(v.split(".")[0] ?? "0", 10);
|
|
1356
1939
|
}
|
|
1357
1940
|
function deriveSharedSecret(tokenOrExtensionId, hmacSalt) {
|
|
1358
|
-
return (0,
|
|
1941
|
+
return (0, import_node_crypto2.createHash)("sha256").update(`${tokenOrExtensionId}:${hmacSalt}`).digest();
|
|
1359
1942
|
}
|
|
1360
1943
|
function computeHmac(secret, challenge) {
|
|
1361
|
-
return (0,
|
|
1944
|
+
return (0, import_node_crypto2.createHmac)("sha256", secret).update(challenge).digest("hex");
|
|
1362
1945
|
}
|
|
1363
1946
|
function verifyHmacConstantTime(actualHex, expectedHex) {
|
|
1364
1947
|
if (!actualHex || typeof actualHex !== "string") return false;
|
|
@@ -1366,7 +1949,7 @@ function verifyHmacConstantTime(actualHex, expectedHex) {
|
|
|
1366
1949
|
const actualBuf = Buffer.from(actualHex, "hex");
|
|
1367
1950
|
const expectedBuf = Buffer.from(expectedHex, "hex");
|
|
1368
1951
|
if (actualBuf.length !== expectedBuf.length) return false;
|
|
1369
|
-
return (0,
|
|
1952
|
+
return (0, import_node_crypto2.timingSafeEqual)(actualBuf, expectedBuf);
|
|
1370
1953
|
} catch {
|
|
1371
1954
|
return false;
|
|
1372
1955
|
}
|
|
@@ -1377,25 +1960,26 @@ function getKillPortHint(port) {
|
|
|
1377
1960
|
}
|
|
1378
1961
|
return `lsof -ti:${port} | xargs kill -9`;
|
|
1379
1962
|
}
|
|
1380
|
-
var import_ws, import_node_events,
|
|
1963
|
+
var import_ws, import_node_events, import_node_crypto2, import_node_fs4, import_node_path4, import_node_os3, WS_PORT, REQUEST_TIMEOUT_MS, HANDSHAKE_TIMEOUT_MS, WS_OPEN, TOKEN_FILE_DIR, TOKEN_FILE_PATH, ExtensionBridgeClient;
|
|
1381
1964
|
var init_bridge_client = __esm({
|
|
1382
1965
|
"src/bridge/bridge-client.ts"() {
|
|
1383
1966
|
"use strict";
|
|
1384
1967
|
import_ws = require("ws");
|
|
1385
1968
|
import_node_events = require("events");
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1969
|
+
import_node_crypto2 = require("crypto");
|
|
1970
|
+
import_node_fs4 = __toESM(require("fs"), 1);
|
|
1971
|
+
import_node_path4 = __toESM(require("path"), 1);
|
|
1972
|
+
import_node_os3 = __toESM(require("os"), 1);
|
|
1390
1973
|
init_protocol();
|
|
1391
1974
|
init_bash_executor();
|
|
1392
1975
|
init_directory_picker();
|
|
1976
|
+
init_terminal_session();
|
|
1393
1977
|
WS_PORT = DEFAULT_WS_PORT;
|
|
1394
1978
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
1395
1979
|
HANDSHAKE_TIMEOUT_MS = 5e3;
|
|
1396
1980
|
WS_OPEN = 1;
|
|
1397
|
-
TOKEN_FILE_DIR =
|
|
1398
|
-
TOKEN_FILE_PATH =
|
|
1981
|
+
TOKEN_FILE_DIR = import_node_path4.default.join(import_node_os3.default.homedir(), ".robot-wxt");
|
|
1982
|
+
TOKEN_FILE_PATH = import_node_path4.default.join(TOKEN_FILE_DIR, "bridge-token");
|
|
1399
1983
|
ExtensionBridgeClient = class _ExtensionBridgeClient extends import_node_events.EventEmitter {
|
|
1400
1984
|
constructor(port = WS_PORT, authToken) {
|
|
1401
1985
|
super();
|
|
@@ -1411,6 +1995,7 @@ var init_bridge_client = __esm({
|
|
|
1411
1995
|
pending = /* @__PURE__ */ new Map();
|
|
1412
1996
|
socketAuthStates = /* @__PURE__ */ new Map();
|
|
1413
1997
|
destroyed = false;
|
|
1998
|
+
terminalSessions = null;
|
|
1414
1999
|
idCounter = 0;
|
|
1415
2000
|
isClientMode = false;
|
|
1416
2001
|
explicitAuthToken;
|
|
@@ -1665,8 +2250,8 @@ var init_bridge_client = __esm({
|
|
|
1665
2250
|
this.emit("error", err);
|
|
1666
2251
|
});
|
|
1667
2252
|
this.wss.on("connection", (socket) => {
|
|
1668
|
-
const challenge = (0,
|
|
1669
|
-
const hmacSalt = (0,
|
|
2253
|
+
const challenge = (0, import_node_crypto2.randomUUID)();
|
|
2254
|
+
const hmacSalt = (0, import_node_crypto2.randomBytes)(16).toString("hex");
|
|
1670
2255
|
const authState = {
|
|
1671
2256
|
authenticated: false,
|
|
1672
2257
|
challenge,
|
|
@@ -1707,6 +2292,7 @@ var init_bridge_client = __esm({
|
|
|
1707
2292
|
}
|
|
1708
2293
|
if (socket === this.extensionSocket) {
|
|
1709
2294
|
this.extensionSocket = null;
|
|
2295
|
+
this.terminalSessions?.disposeAll();
|
|
1710
2296
|
this.emit("disconnected");
|
|
1711
2297
|
for (const [reqId, fwdInfo] of this.forwardedRequests.entries()) {
|
|
1712
2298
|
if (fwdInfo.timer) clearTimeout(fwdInfo.timer);
|
|
@@ -1753,6 +2339,16 @@ var init_bridge_client = __esm({
|
|
|
1753
2339
|
return;
|
|
1754
2340
|
}
|
|
1755
2341
|
if (msg.method && msg.id) {
|
|
2342
|
+
if (typeof msg.method === "string" && isTerminalMethod(msg.method) && socket !== this.extensionSocket) {
|
|
2343
|
+
this.sendJsonRpcResponse(socket, {
|
|
2344
|
+
id: msg.id,
|
|
2345
|
+
error: {
|
|
2346
|
+
code: BRIDGE_ERROR_CODE.METHOD_NOT_FOUND,
|
|
2347
|
+
message: "\u7EC8\u7AEF\u4F1A\u8BDD\u4EC5\u5141\u8BB8\u5DF2\u8BA4\u8BC1\u7684\u6269\u5C55\u8FDE\u63A5"
|
|
2348
|
+
}
|
|
2349
|
+
});
|
|
2350
|
+
return;
|
|
2351
|
+
}
|
|
1756
2352
|
if (socket === this.extensionSocket) {
|
|
1757
2353
|
await this.handleExtensionSystemRpc(socket, msg);
|
|
1758
2354
|
return;
|
|
@@ -1825,6 +2421,7 @@ var init_bridge_client = __esm({
|
|
|
1825
2421
|
currentAuth.clientType = msg.clientType;
|
|
1826
2422
|
if (msg.clientType === "extension") {
|
|
1827
2423
|
if (this.extensionSocket && this.extensionSocket !== socket) {
|
|
2424
|
+
this.terminalSessions?.disposeAll();
|
|
1828
2425
|
try {
|
|
1829
2426
|
this.extensionSocket.close(WS_CLOSE_CODE.REPLACED, "Replaced by new connection");
|
|
1830
2427
|
} catch {
|
|
@@ -1916,9 +2513,6 @@ var init_bridge_client = __esm({
|
|
|
1916
2513
|
systemRpcHandlers = Object.assign(
|
|
1917
2514
|
/* @__PURE__ */ Object.create(null),
|
|
1918
2515
|
{
|
|
1919
|
-
[BRIDGE_METHODS.SYSTEM_BASH]: async (params) => {
|
|
1920
|
-
return executeBash(params || {});
|
|
1921
|
-
},
|
|
1922
2516
|
[BRIDGE_METHODS.SYSTEM_PICK_DIRECTORY]: async (params) => {
|
|
1923
2517
|
const selectedPath = await pickDirectory({
|
|
1924
2518
|
title: params?.title || void 0,
|
|
@@ -1929,11 +2523,61 @@ var init_bridge_client = __esm({
|
|
|
1929
2523
|
selectedPath,
|
|
1930
2524
|
canceled: selectedPath === null
|
|
1931
2525
|
};
|
|
1932
|
-
}
|
|
2526
|
+
},
|
|
2527
|
+
[BRIDGE_METHODS.TERMINAL_OPEN]: async (params) => this.getTerminalSessions().open(params || {}),
|
|
2528
|
+
[BRIDGE_METHODS.TERMINAL_INPUT]: async (params) => this.getTerminalSessions().input(params || {}),
|
|
2529
|
+
[BRIDGE_METHODS.TERMINAL_RESIZE]: async (params) => this.getTerminalSessions().resize(params || {}),
|
|
2530
|
+
[BRIDGE_METHODS.TERMINAL_CLOSE]: async (params) => this.getTerminalSessions().close(params || {})
|
|
1933
2531
|
}
|
|
1934
2532
|
);
|
|
2533
|
+
sendJsonRpcNotification(socket, method, params) {
|
|
2534
|
+
if (socket.readyState !== WS_OPEN) return;
|
|
2535
|
+
let payload;
|
|
2536
|
+
try {
|
|
2537
|
+
payload = JSON.stringify({ jsonrpc: "2.0", method, params });
|
|
2538
|
+
} catch {
|
|
2539
|
+
return;
|
|
2540
|
+
}
|
|
2541
|
+
try {
|
|
2542
|
+
socket.send(payload);
|
|
2543
|
+
} catch {
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
async executeHostBash(socket, id, params) {
|
|
2547
|
+
try {
|
|
2548
|
+
const requestParams = params && typeof params === "object" ? params : {};
|
|
2549
|
+
const result = await executeBash({
|
|
2550
|
+
...requestParams,
|
|
2551
|
+
onOutput: (chunk) => {
|
|
2552
|
+
if (!chunk.text) return;
|
|
2553
|
+
this.sendJsonRpcNotification(socket, BASH_EVENTS.OUTPUT, {
|
|
2554
|
+
id,
|
|
2555
|
+
stream: chunk.stream,
|
|
2556
|
+
text: chunk.text
|
|
2557
|
+
});
|
|
2558
|
+
}
|
|
2559
|
+
});
|
|
2560
|
+
this.sendJsonRpcResponse(socket, { id, result });
|
|
2561
|
+
} catch (err) {
|
|
2562
|
+
this.sendJsonRpcResponse(socket, { id, error: this.formatSystemRpcError(err) });
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
1935
2565
|
async handleExtensionSystemRpc(socket, msg) {
|
|
1936
2566
|
const { id, method, params } = msg;
|
|
2567
|
+
if (!isJsonRpcId(id)) {
|
|
2568
|
+
this.sendJsonRpcResponse(socket, {
|
|
2569
|
+
id: null,
|
|
2570
|
+
error: {
|
|
2571
|
+
code: BRIDGE_ERROR_CODE.INVALID_PARAMS,
|
|
2572
|
+
message: "\u65E0\u6548\u7684\u8BF7\u6C42 id"
|
|
2573
|
+
}
|
|
2574
|
+
});
|
|
2575
|
+
return;
|
|
2576
|
+
}
|
|
2577
|
+
if (method === BRIDGE_METHODS.SYSTEM_BASH) {
|
|
2578
|
+
await this.executeHostBash(socket, id, params);
|
|
2579
|
+
return;
|
|
2580
|
+
}
|
|
1937
2581
|
const handler = typeof method === "string" && Object.prototype.hasOwnProperty.call(this.systemRpcHandlers, method) ? this.systemRpcHandlers[method] : void 0;
|
|
1938
2582
|
if (!handler || typeof handler !== "function") {
|
|
1939
2583
|
this.sendJsonRpcResponse(socket, {
|
|
@@ -2029,9 +2673,37 @@ var init_bridge_client = __esm({
|
|
|
2029
2673
|
}
|
|
2030
2674
|
});
|
|
2031
2675
|
}
|
|
2676
|
+
getTerminalSessions() {
|
|
2677
|
+
if (!this.terminalSessions) {
|
|
2678
|
+
this.terminalSessions = new TerminalSessionManager({
|
|
2679
|
+
send: (frame) => {
|
|
2680
|
+
const socket = this.extensionSocket;
|
|
2681
|
+
if (!socket || socket.readyState !== WS_OPEN) return;
|
|
2682
|
+
const sessions = this.terminalSessions;
|
|
2683
|
+
if (sessions) applyTerminalBackpressure(socket.bufferedAmount ?? 0, sessions);
|
|
2684
|
+
let payload;
|
|
2685
|
+
try {
|
|
2686
|
+
payload = JSON.stringify(frame);
|
|
2687
|
+
} catch {
|
|
2688
|
+
return;
|
|
2689
|
+
}
|
|
2690
|
+
try {
|
|
2691
|
+
socket.send(payload, () => {
|
|
2692
|
+
if (this.terminalSessions && socket.readyState === WS_OPEN) {
|
|
2693
|
+
applyTerminalBackpressure(socket.bufferedAmount ?? 0, this.terminalSessions);
|
|
2694
|
+
}
|
|
2695
|
+
});
|
|
2696
|
+
} catch {
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
});
|
|
2700
|
+
}
|
|
2701
|
+
return this.terminalSessions;
|
|
2702
|
+
}
|
|
2032
2703
|
/** 销毁服务,断开所有连接 */
|
|
2033
2704
|
destroy() {
|
|
2034
2705
|
this.destroyed = true;
|
|
2706
|
+
this.terminalSessions?.disposeAll();
|
|
2035
2707
|
this._rejectAllPending("ExtensionBridgeClient \u5DF2\u9500\u6BC1");
|
|
2036
2708
|
this.forwardedRequests.clear();
|
|
2037
2709
|
if (this.clientSocket) {
|
|
@@ -2176,16 +2848,16 @@ __export(daemon_exports, {
|
|
|
2176
2848
|
isDaemonProcessAlive: () => isDaemonProcessAlive,
|
|
2177
2849
|
isProcessAlive: () => isProcessAlive,
|
|
2178
2850
|
killProcessOnPort: () => killProcessOnPort,
|
|
2179
|
-
killProcessTree: () =>
|
|
2851
|
+
killProcessTree: () => killProcessTree,
|
|
2180
2852
|
probeServerReady: () => probeServerReady,
|
|
2181
2853
|
runDaemonProcess: () => runDaemonProcess,
|
|
2182
2854
|
stopBridgeDaemon: () => stopBridgeDaemon
|
|
2183
2855
|
});
|
|
2184
2856
|
function getDaemonPidFile(port = WS_PORT2) {
|
|
2185
2857
|
if (port === WS_PORT2) {
|
|
2186
|
-
return
|
|
2858
|
+
return import_node_path5.default.join(DAEMON_DIR, "daemon.pid");
|
|
2187
2859
|
}
|
|
2188
|
-
return
|
|
2860
|
+
return import_node_path5.default.join(DAEMON_DIR, `daemon-${port}.pid`);
|
|
2189
2861
|
}
|
|
2190
2862
|
function isProcessAlive(pid) {
|
|
2191
2863
|
if (!pid || pid <= 0) return false;
|
|
@@ -2229,7 +2901,7 @@ async function findPidByPort(port, excludePid) {
|
|
|
2229
2901
|
return null;
|
|
2230
2902
|
}
|
|
2231
2903
|
}
|
|
2232
|
-
async function
|
|
2904
|
+
async function killProcessTree(pid) {
|
|
2233
2905
|
if (!pid || pid <= 0 || pid === process.pid) return false;
|
|
2234
2906
|
if (process.platform === "win32") {
|
|
2235
2907
|
try {
|
|
@@ -2267,7 +2939,7 @@ async function killProcessTree2(pid) {
|
|
|
2267
2939
|
async function killProcessOnPort(port) {
|
|
2268
2940
|
const pid = await findPidByPort(port, process.pid);
|
|
2269
2941
|
if (pid) {
|
|
2270
|
-
await
|
|
2942
|
+
await killProcessTree(pid);
|
|
2271
2943
|
for (let i = 0; i < 20; i++) {
|
|
2272
2944
|
await new Promise((r) => setTimeout(r, 100));
|
|
2273
2945
|
const remainingPid = await findPidByPort(port, process.pid);
|
|
@@ -2281,9 +2953,9 @@ async function probeServerReady(port = WS_PORT2, token, timeoutMs = 1e3) {
|
|
|
2281
2953
|
}
|
|
2282
2954
|
function isDaemonProcessAlive(port = WS_PORT2) {
|
|
2283
2955
|
const pidFile = getDaemonPidFile(port);
|
|
2284
|
-
if (!
|
|
2956
|
+
if (!import_node_fs5.default.existsSync(pidFile)) return false;
|
|
2285
2957
|
try {
|
|
2286
|
-
const pid = parseInt(
|
|
2958
|
+
const pid = parseInt(import_node_fs5.default.readFileSync(pidFile, "utf8").trim(), 10);
|
|
2287
2959
|
return Boolean(pid && isProcessAlive(pid));
|
|
2288
2960
|
} catch {
|
|
2289
2961
|
return false;
|
|
@@ -2331,19 +3003,19 @@ async function ensureBridgeDaemon(options = {}) {
|
|
|
2331
3003
|
throw new Error(`\u542F\u52A8\u540E\u53F0 WebSocket \u6865\u63A5\u670D\u52A1\u8D85\u65F6\uFF08${timeoutMs}ms\uFF09\uFF0C\u8BF7\u68C0\u67E5\u7AEF\u53E3 ${port} \u662F\u5426\u53EF\u7528\u3002`);
|
|
2332
3004
|
}
|
|
2333
3005
|
function resolveCliEntry() {
|
|
2334
|
-
const currentDir =
|
|
2335
|
-
const distCandidate =
|
|
2336
|
-
if (
|
|
2337
|
-
const projectDistCandidate =
|
|
2338
|
-
if (
|
|
3006
|
+
const currentDir = import_node_path5.default.dirname((0, import_node_url.fileURLToPath)(import_meta3.url));
|
|
3007
|
+
const distCandidate = import_node_path5.default.join(currentDir, "bin.js");
|
|
3008
|
+
if (import_node_fs5.default.existsSync(distCandidate)) return distCandidate;
|
|
3009
|
+
const projectDistCandidate = import_node_path5.default.resolve(currentDir, "../dist/bin.js");
|
|
3010
|
+
if (import_node_fs5.default.existsSync(projectDistCandidate)) return projectDistCandidate;
|
|
2339
3011
|
return distCandidate;
|
|
2340
3012
|
}
|
|
2341
3013
|
function startDaemonProcessBackground(token, port) {
|
|
2342
3014
|
const cliEntry = resolveCliEntry();
|
|
2343
|
-
if (!
|
|
2344
|
-
|
|
3015
|
+
if (!import_node_fs5.default.existsSync(DAEMON_DIR)) {
|
|
3016
|
+
import_node_fs5.default.mkdirSync(DAEMON_DIR, { mode: 448, recursive: true });
|
|
2345
3017
|
}
|
|
2346
|
-
const child = (0,
|
|
3018
|
+
const child = (0, import_node_child_process4.spawn)(process.execPath, [cliEntry, "--mode", "wxt", "daemon", "--port", String(port)], {
|
|
2347
3019
|
detached: true,
|
|
2348
3020
|
stdio: "ignore",
|
|
2349
3021
|
windowsHide: true,
|
|
@@ -2376,20 +3048,20 @@ async function runDaemonProcess(args) {
|
|
|
2376
3048
|
const pidFile = getDaemonPidFile(port);
|
|
2377
3049
|
client.on("listening", () => {
|
|
2378
3050
|
try {
|
|
2379
|
-
if (!
|
|
2380
|
-
|
|
3051
|
+
if (!import_node_fs5.default.existsSync(DAEMON_DIR)) {
|
|
3052
|
+
import_node_fs5.default.mkdirSync(DAEMON_DIR, { mode: 448, recursive: true });
|
|
2381
3053
|
}
|
|
2382
|
-
|
|
3054
|
+
import_node_fs5.default.writeFileSync(pidFile, String(process.pid), { mode: 384, encoding: "utf8" });
|
|
2383
3055
|
} catch {
|
|
2384
3056
|
}
|
|
2385
3057
|
});
|
|
2386
3058
|
client.connect();
|
|
2387
3059
|
const shutdown = () => {
|
|
2388
3060
|
try {
|
|
2389
|
-
if (
|
|
2390
|
-
const recordedPid =
|
|
3061
|
+
if (import_node_fs5.default.existsSync(pidFile)) {
|
|
3062
|
+
const recordedPid = import_node_fs5.default.readFileSync(pidFile, "utf8").trim();
|
|
2391
3063
|
if (recordedPid === String(process.pid)) {
|
|
2392
|
-
|
|
3064
|
+
import_node_fs5.default.unlinkSync(pidFile);
|
|
2393
3065
|
}
|
|
2394
3066
|
}
|
|
2395
3067
|
} catch {
|
|
@@ -2427,9 +3099,9 @@ async function stopBridgeDaemon(port = WS_PORT2, options = {}) {
|
|
|
2427
3099
|
let killedAny = false;
|
|
2428
3100
|
const pidFile = getDaemonPidFile(port);
|
|
2429
3101
|
let daemonPid = null;
|
|
2430
|
-
if (
|
|
3102
|
+
if (import_node_fs5.default.existsSync(pidFile)) {
|
|
2431
3103
|
try {
|
|
2432
|
-
const pidStr =
|
|
3104
|
+
const pidStr = import_node_fs5.default.readFileSync(pidFile, "utf8").trim();
|
|
2433
3105
|
const p = parseInt(pidStr, 10);
|
|
2434
3106
|
if (p && isProcessAlive(p)) {
|
|
2435
3107
|
daemonPid = p;
|
|
@@ -2449,22 +3121,22 @@ async function stopBridgeDaemon(port = WS_PORT2, options = {}) {
|
|
|
2449
3121
|
if (portPid && portPid !== daemonPid) {
|
|
2450
3122
|
return false;
|
|
2451
3123
|
}
|
|
2452
|
-
const killed = await
|
|
3124
|
+
const killed = await killProcessTree(daemonPid);
|
|
2453
3125
|
if (killed) {
|
|
2454
3126
|
killedAny = true;
|
|
2455
3127
|
try {
|
|
2456
|
-
if (
|
|
3128
|
+
if (import_node_fs5.default.existsSync(pidFile)) import_node_fs5.default.unlinkSync(pidFile);
|
|
2457
3129
|
} catch {
|
|
2458
3130
|
}
|
|
2459
3131
|
}
|
|
2460
3132
|
} else {
|
|
2461
3133
|
if (daemonPid) {
|
|
2462
3134
|
try {
|
|
2463
|
-
const killed = await
|
|
3135
|
+
const killed = await killProcessTree(daemonPid);
|
|
2464
3136
|
if (killed) {
|
|
2465
3137
|
killedAny = true;
|
|
2466
3138
|
try {
|
|
2467
|
-
if (
|
|
3139
|
+
if (import_node_fs5.default.existsSync(pidFile)) import_node_fs5.default.unlinkSync(pidFile);
|
|
2468
3140
|
} catch {
|
|
2469
3141
|
}
|
|
2470
3142
|
}
|
|
@@ -2493,21 +3165,21 @@ async function stopBridgeDaemon(port = WS_PORT2, options = {}) {
|
|
|
2493
3165
|
}
|
|
2494
3166
|
return killedAny;
|
|
2495
3167
|
}
|
|
2496
|
-
var
|
|
3168
|
+
var import_node_child_process4, import_node_util, import_node_fs5, import_node_path5, import_node_os4, import_node_url, import_meta3, execAsync, WS_PORT2, DAEMON_DIR, PID_FILE;
|
|
2497
3169
|
var init_daemon = __esm({
|
|
2498
3170
|
"src/bridge/daemon.ts"() {
|
|
2499
3171
|
"use strict";
|
|
2500
|
-
|
|
3172
|
+
import_node_child_process4 = require("child_process");
|
|
2501
3173
|
import_node_util = require("util");
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
3174
|
+
import_node_fs5 = __toESM(require("fs"), 1);
|
|
3175
|
+
import_node_path5 = __toESM(require("path"), 1);
|
|
3176
|
+
import_node_os4 = __toESM(require("os"), 1);
|
|
2505
3177
|
import_node_url = require("url");
|
|
2506
3178
|
init_bridge_client();
|
|
2507
|
-
|
|
2508
|
-
execAsync = (0, import_node_util.promisify)(
|
|
3179
|
+
import_meta3 = {};
|
|
3180
|
+
execAsync = (0, import_node_util.promisify)(import_node_child_process4.exec);
|
|
2509
3181
|
WS_PORT2 = 18999;
|
|
2510
|
-
DAEMON_DIR =
|
|
3182
|
+
DAEMON_DIR = import_node_path5.default.join(import_node_os4.default.homedir(), ".robot-wxt");
|
|
2511
3183
|
PID_FILE = getDaemonPidFile(WS_PORT2);
|
|
2512
3184
|
}
|
|
2513
3185
|
});
|