@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.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.0-
|
|
551
|
+
version: "0.1.0-beta.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: {
|
|
@@ -579,7 +587,7 @@ var init_package = __esm({
|
|
|
579
587
|
license: "MIT",
|
|
580
588
|
author: "OpenTiny Team",
|
|
581
589
|
engines: {
|
|
582
|
-
node: ">=
|
|
590
|
+
node: ">=18.19.0"
|
|
583
591
|
},
|
|
584
592
|
publishConfig: {
|
|
585
593
|
access: "public"
|
|
@@ -591,6 +599,7 @@ var init_package = __esm({
|
|
|
591
599
|
"@page-agent/page-controller": "catalog:",
|
|
592
600
|
clipboardy: "^5.3.1",
|
|
593
601
|
commander: "^11.1.0",
|
|
602
|
+
execa: "^9.6.1",
|
|
594
603
|
json5: "^2.2.3",
|
|
595
604
|
marked: "^15.0.7",
|
|
596
605
|
picocolors: "^1.0.0",
|
|
@@ -819,12 +828,15 @@ var init_cdp_adapter = __esm({
|
|
|
819
828
|
});
|
|
820
829
|
|
|
821
830
|
// src/bridge/protocol.ts
|
|
822
|
-
|
|
831
|
+
function isTerminalMethod(method) {
|
|
832
|
+
return TERMINAL_METHOD_SET.has(method);
|
|
833
|
+
}
|
|
834
|
+
var DEFAULT_WS_PORT, PROTOCOL_VERSION, WS_CLOSE_CODE, BRIDGE_ERROR_CODE, BRIDGE_METHODS, TERMINAL_EVENTS, BASH_EVENTS, TERMINAL_METHOD_SET;
|
|
823
835
|
var init_protocol = __esm({
|
|
824
836
|
"src/bridge/protocol.ts"() {
|
|
825
837
|
"use strict";
|
|
826
838
|
DEFAULT_WS_PORT = 18999;
|
|
827
|
-
PROTOCOL_VERSION = "1.
|
|
839
|
+
PROTOCOL_VERSION = "1.2.0";
|
|
828
840
|
WS_CLOSE_CODE = {
|
|
829
841
|
/** 被新接入的扩展连接平滑替换(旧连接触发退避重连) */
|
|
830
842
|
REPLACED: 4e3,
|
|
@@ -858,8 +870,25 @@ var init_protocol = __esm({
|
|
|
858
870
|
SKILLS_GET: "skills/get",
|
|
859
871
|
SKILLS_READ_RESOURCE: "skills/read_resource",
|
|
860
872
|
SYSTEM_BASH: "system/bash",
|
|
861
|
-
SYSTEM_PICK_DIRECTORY: "system/pick_directory"
|
|
873
|
+
SYSTEM_PICK_DIRECTORY: "system/pick_directory",
|
|
874
|
+
TERMINAL_OPEN: "terminal/open",
|
|
875
|
+
TERMINAL_INPUT: "terminal/input",
|
|
876
|
+
TERMINAL_RESIZE: "terminal/resize",
|
|
877
|
+
TERMINAL_CLOSE: "terminal/close"
|
|
862
878
|
};
|
|
879
|
+
TERMINAL_EVENTS = {
|
|
880
|
+
OUTPUT: "notification/terminal_output",
|
|
881
|
+
EXIT: "notification/terminal_exit"
|
|
882
|
+
};
|
|
883
|
+
BASH_EVENTS = {
|
|
884
|
+
OUTPUT: "notification/bash_output"
|
|
885
|
+
};
|
|
886
|
+
TERMINAL_METHOD_SET = /* @__PURE__ */ new Set([
|
|
887
|
+
BRIDGE_METHODS.TERMINAL_OPEN,
|
|
888
|
+
BRIDGE_METHODS.TERMINAL_INPUT,
|
|
889
|
+
BRIDGE_METHODS.TERMINAL_RESIZE,
|
|
890
|
+
BRIDGE_METHODS.TERMINAL_CLOSE
|
|
891
|
+
]);
|
|
863
892
|
}
|
|
864
893
|
});
|
|
865
894
|
|
|
@@ -968,25 +997,56 @@ function resolveShell(forcePlatform) {
|
|
|
968
997
|
if (!forcePlatform) cachedShell = fallback;
|
|
969
998
|
return fallback;
|
|
970
999
|
}
|
|
971
|
-
function
|
|
972
|
-
if (
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
1000
|
+
function withoutCarriageReturn(buf) {
|
|
1001
|
+
if (!buf.includes(13)) return buf;
|
|
1002
|
+
const out = Buffer.allocUnsafe(buf.length);
|
|
1003
|
+
let size = 0;
|
|
1004
|
+
for (let index = 0; index < buf.length; index += 1) {
|
|
1005
|
+
const byte = buf[index];
|
|
1006
|
+
if (byte === 13) continue;
|
|
1007
|
+
out[size] = byte;
|
|
1008
|
+
size += 1;
|
|
1009
|
+
}
|
|
1010
|
+
return out.subarray(0, size);
|
|
1011
|
+
}
|
|
1012
|
+
function collectLimited(stream, maxBuffer, onText, stripCarriageReturn = false) {
|
|
1013
|
+
const state = { chunks: [], truncated: false };
|
|
1014
|
+
if (!stream) return state;
|
|
1015
|
+
const decoder = new import_node_string_decoder.StringDecoder("utf8");
|
|
1016
|
+
let bytes = 0;
|
|
1017
|
+
const emit = (buf) => {
|
|
1018
|
+
const text = decoder.write(buf);
|
|
1019
|
+
if (text) onText?.(text);
|
|
1020
|
+
};
|
|
1021
|
+
stream.on("data", (chunk) => {
|
|
1022
|
+
const raw = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1023
|
+
const buf = stripCarriageReturn ? withoutCarriageReturn(raw) : raw;
|
|
1024
|
+
if (buf.length === 0) return;
|
|
1025
|
+
if (bytes >= maxBuffer) {
|
|
1026
|
+
state.truncated = true;
|
|
1027
|
+
return;
|
|
980
1028
|
}
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
1029
|
+
const remaining = maxBuffer - bytes;
|
|
1030
|
+
if (buf.length <= remaining) {
|
|
1031
|
+
state.chunks.push(buf);
|
|
1032
|
+
bytes += buf.length;
|
|
1033
|
+
emit(buf);
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
const kept = buf.subarray(0, remaining);
|
|
1037
|
+
state.chunks.push(kept);
|
|
1038
|
+
bytes += remaining;
|
|
1039
|
+
state.truncated = true;
|
|
1040
|
+
emit(kept);
|
|
1041
|
+
});
|
|
1042
|
+
stream.on("end", () => {
|
|
1043
|
+
const rest = decoder.end();
|
|
1044
|
+
if (rest) onText?.(rest);
|
|
1045
|
+
});
|
|
1046
|
+
return state;
|
|
1047
|
+
}
|
|
1048
|
+
function decodeLimited(state) {
|
|
1049
|
+
return Buffer.concat(state.chunks).toString("utf8").trim();
|
|
990
1050
|
}
|
|
991
1051
|
async function executeBash(params) {
|
|
992
1052
|
const trimmedCommand = params.command?.trim();
|
|
@@ -1005,83 +1065,66 @@ async function executeBash(params) {
|
|
|
1005
1065
|
});
|
|
1006
1066
|
const normalizedTimeoutMs = typeof params.timeoutMs === "number" && Number.isFinite(params.timeoutMs) && params.timeoutMs > 0 ? Math.min(params.timeoutMs, MAX_TIMEOUT_MS) : DEFAULT_TIMEOUT_MS;
|
|
1007
1067
|
const { env = {}, maxBuffer = DEFAULT_MAX_BUFFER } = params;
|
|
1008
|
-
const startTime = Date.now();
|
|
1009
1068
|
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
|
-
});
|
|
1069
|
+
const useTty = params.tty === true && process.platform !== "win32";
|
|
1070
|
+
const subprocess = useTty ? (0, import_execa.execa)("python3", ["-u", "-c", TTY_HELPER], {
|
|
1071
|
+
cwd,
|
|
1072
|
+
env: {
|
|
1073
|
+
...env,
|
|
1074
|
+
BASH_TTY_COMMAND: trimmedCommand,
|
|
1075
|
+
BASH_TTY_SHELL: shell
|
|
1076
|
+
},
|
|
1077
|
+
stdin: "ignore",
|
|
1078
|
+
buffer: false,
|
|
1079
|
+
encoding: "buffer",
|
|
1080
|
+
reject: false,
|
|
1081
|
+
timeout: normalizedTimeoutMs,
|
|
1082
|
+
forceKillAfterDelay: FORCE_KILL_AFTER_DELAY_MS,
|
|
1083
|
+
windowsHide: true,
|
|
1084
|
+
stripFinalNewline: false
|
|
1085
|
+
}) : (0, import_execa.execa)(trimmedCommand, {
|
|
1086
|
+
shell,
|
|
1087
|
+
cwd,
|
|
1088
|
+
env,
|
|
1089
|
+
stdin: "ignore",
|
|
1090
|
+
buffer: false,
|
|
1091
|
+
encoding: "buffer",
|
|
1092
|
+
reject: false,
|
|
1093
|
+
timeout: normalizedTimeoutMs,
|
|
1094
|
+
forceKillAfterDelay: FORCE_KILL_AFTER_DELAY_MS,
|
|
1095
|
+
windowsHide: true,
|
|
1096
|
+
stripFinalNewline: false
|
|
1076
1097
|
});
|
|
1098
|
+
const emit = (stream) => (text) => {
|
|
1099
|
+
params.onOutput?.({ stream, text });
|
|
1100
|
+
};
|
|
1101
|
+
const stdoutState = collectLimited(subprocess.stdout, maxBuffer, emit("stdout"), useTty);
|
|
1102
|
+
const stderrState = collectLimited(subprocess.stderr, maxBuffer, emit("stderr"));
|
|
1103
|
+
const result = await subprocess;
|
|
1104
|
+
if (result.timedOut) {
|
|
1105
|
+
throw new BashTimeoutError(normalizedTimeoutMs);
|
|
1106
|
+
}
|
|
1107
|
+
if (result.failed && result.exitCode == null && result.signal == null && !result.isTerminated) {
|
|
1108
|
+
throw new Error(result.shortMessage || result.message || "\u547D\u4EE4\u542F\u52A8\u5931\u8D25");
|
|
1109
|
+
}
|
|
1110
|
+
const truncated = stdoutState.truncated || stderrState.truncated;
|
|
1111
|
+
return {
|
|
1112
|
+
stdout: decodeLimited(stdoutState),
|
|
1113
|
+
stderr: decodeLimited(stderrState),
|
|
1114
|
+
exitCode: result.exitCode ?? null,
|
|
1115
|
+
signal: result.signal ?? null,
|
|
1116
|
+
durationMs: result.durationMs ?? 0,
|
|
1117
|
+
...truncated ? { truncated: true } : {}
|
|
1118
|
+
};
|
|
1077
1119
|
}
|
|
1078
|
-
var import_node_fs2, import_node_path2,
|
|
1120
|
+
var 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;
|
|
1079
1121
|
var init_bash_executor = __esm({
|
|
1080
1122
|
"src/bridge/bash-executor.ts"() {
|
|
1081
1123
|
"use strict";
|
|
1082
1124
|
import_node_fs2 = __toESM(require("fs"), 1);
|
|
1083
1125
|
import_node_path2 = __toESM(require("path"), 1);
|
|
1084
|
-
|
|
1126
|
+
import_node_string_decoder = require("string_decoder");
|
|
1127
|
+
import_execa = require("execa");
|
|
1085
1128
|
init_workspace_manager();
|
|
1086
1129
|
BashTimeoutError = class extends Error {
|
|
1087
1130
|
code = "TIMEOUT";
|
|
@@ -1096,6 +1139,46 @@ var init_bash_executor = __esm({
|
|
|
1096
1139
|
MAX_TIMEOUT_MS = 6e5;
|
|
1097
1140
|
DEFAULT_MAX_BUFFER = 512 * 1024;
|
|
1098
1141
|
cachedShell = null;
|
|
1142
|
+
FORCE_KILL_AFTER_DELAY_MS = 2e3;
|
|
1143
|
+
TTY_HELPER = `
|
|
1144
|
+
import errno, os, sys
|
|
1145
|
+
cmd = os.environ.get("BASH_TTY_COMMAND") or ""
|
|
1146
|
+
shell = os.environ.get("BASH_TTY_SHELL") or "/bin/bash"
|
|
1147
|
+
if not cmd:
|
|
1148
|
+
sys.exit(127)
|
|
1149
|
+
import pty
|
|
1150
|
+
pid, fd = pty.fork()
|
|
1151
|
+
if pid == 0:
|
|
1152
|
+
os.environ.pop("BASH_TTY_COMMAND", None)
|
|
1153
|
+
os.environ.pop("BASH_TTY_SHELL", None)
|
|
1154
|
+
os.environ["TERM"] = "xterm-256color"
|
|
1155
|
+
try:
|
|
1156
|
+
os.execvp(shell, [shell, "-c", cmd])
|
|
1157
|
+
except OSError:
|
|
1158
|
+
os._exit(127)
|
|
1159
|
+
while True:
|
|
1160
|
+
try:
|
|
1161
|
+
data = os.read(fd, 65536)
|
|
1162
|
+
except OSError as exc:
|
|
1163
|
+
if exc.errno == errno.EINTR:
|
|
1164
|
+
continue
|
|
1165
|
+
break
|
|
1166
|
+
if not data:
|
|
1167
|
+
break
|
|
1168
|
+
try:
|
|
1169
|
+
os.write(1, data)
|
|
1170
|
+
except OSError:
|
|
1171
|
+
break
|
|
1172
|
+
try:
|
|
1173
|
+
_, status = os.waitpid(pid, 0)
|
|
1174
|
+
except OSError:
|
|
1175
|
+
sys.exit(1)
|
|
1176
|
+
if os.WIFEXITED(status):
|
|
1177
|
+
sys.exit(os.WEXITSTATUS(status))
|
|
1178
|
+
if os.WIFSIGNALED(status):
|
|
1179
|
+
sys.exit(128 + os.WTERMSIG(status))
|
|
1180
|
+
sys.exit(1)
|
|
1181
|
+
`;
|
|
1099
1182
|
}
|
|
1100
1183
|
});
|
|
1101
1184
|
|
|
@@ -1118,7 +1201,7 @@ function pickDirectoryMac(title, defaultPath) {
|
|
|
1118
1201
|
const defaultClause = defaultPath ? ` default location POSIX file "${defaultPath.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"` : "";
|
|
1119
1202
|
const appleScript = `POSIX path of (choose folder with prompt "${safeTitle}"${defaultClause})`;
|
|
1120
1203
|
return new Promise((resolve3, reject) => {
|
|
1121
|
-
const child = (0,
|
|
1204
|
+
const child = (0, import_node_child_process.spawn)("osascript", ["-e", appleScript], {
|
|
1122
1205
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1123
1206
|
});
|
|
1124
1207
|
let stdout = "";
|
|
@@ -1236,7 +1319,7 @@ $result = [FolderPicker]::Pick("${safeTitle}", "${safeDefaultPath}")
|
|
|
1236
1319
|
if ($result) { Write-Output $result }
|
|
1237
1320
|
`;
|
|
1238
1321
|
return new Promise((resolve3, reject) => {
|
|
1239
|
-
const child = (0,
|
|
1322
|
+
const child = (0, import_node_child_process.spawn)("powershell", ["-STA", "-NoProfile", "-WindowStyle", "Hidden", "-Command", psScript], {
|
|
1240
1323
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1241
1324
|
windowsHide: true
|
|
1242
1325
|
});
|
|
@@ -1267,7 +1350,7 @@ function pickDirectoryLinux(title, defaultPath) {
|
|
|
1267
1350
|
args.push(`--filename=${defaultPath}`);
|
|
1268
1351
|
}
|
|
1269
1352
|
return new Promise((resolve3, reject) => {
|
|
1270
|
-
const child = (0,
|
|
1353
|
+
const child = (0, import_node_child_process.spawn)("zenity", args, {
|
|
1271
1354
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1272
1355
|
});
|
|
1273
1356
|
let stdout = "";
|
|
@@ -1299,11 +1382,349 @@ function pickDirectoryLinux(title, defaultPath) {
|
|
|
1299
1382
|
});
|
|
1300
1383
|
});
|
|
1301
1384
|
}
|
|
1302
|
-
var
|
|
1385
|
+
var import_node_child_process;
|
|
1303
1386
|
var init_directory_picker = __esm({
|
|
1304
1387
|
"src/bridge/directory-picker.ts"() {
|
|
1388
|
+
"use strict";
|
|
1389
|
+
import_node_child_process = require("child_process");
|
|
1390
|
+
}
|
|
1391
|
+
});
|
|
1392
|
+
|
|
1393
|
+
// src/bridge/pty-backend.ts
|
|
1394
|
+
function spawnHostPty(options) {
|
|
1395
|
+
if (process.platform === "win32") {
|
|
1396
|
+
throw new Error("\u5F53\u524D\u5E73\u53F0\u6682\u4E0D\u652F\u6301\u4EA4\u4E92\u5F0F\u7EC8\u7AEF");
|
|
1397
|
+
}
|
|
1398
|
+
const shell = options.shell || resolveShell();
|
|
1399
|
+
const child = (0, import_node_child_process2.spawn)("python3", ["-u", "-c", PTY_HELPER], {
|
|
1400
|
+
cwd: options.cwd,
|
|
1401
|
+
env: {
|
|
1402
|
+
...process.env,
|
|
1403
|
+
TERM: "xterm-256color",
|
|
1404
|
+
TERMINAL_SHELL: shell,
|
|
1405
|
+
TERMINAL_ROWS: String(options.rows),
|
|
1406
|
+
TERMINAL_COLS: String(options.cols)
|
|
1407
|
+
},
|
|
1408
|
+
stdio: ["pipe", "pipe", "pipe", "pipe"]
|
|
1409
|
+
});
|
|
1410
|
+
const pending = [];
|
|
1411
|
+
let dataCb = null;
|
|
1412
|
+
let exitCb = null;
|
|
1413
|
+
let pendingExit = null;
|
|
1414
|
+
let settled = false;
|
|
1415
|
+
const decoder = new TextDecoder("utf-8", { fatal: false });
|
|
1416
|
+
let stderrText = "";
|
|
1417
|
+
const emitData = (data) => {
|
|
1418
|
+
if (!data) return;
|
|
1419
|
+
if (dataCb) dataCb(data);
|
|
1420
|
+
else pending.push(data);
|
|
1421
|
+
};
|
|
1422
|
+
const finish = (info) => {
|
|
1423
|
+
if (settled) return;
|
|
1424
|
+
settled = true;
|
|
1425
|
+
if (stderrText.trim()) emitData(`\r
|
|
1426
|
+
${stderrText.trim()}\r
|
|
1427
|
+
`);
|
|
1428
|
+
if (exitCb) exitCb(info);
|
|
1429
|
+
else pendingExit = info;
|
|
1430
|
+
};
|
|
1431
|
+
child.stdout.on("data", (buf) => {
|
|
1432
|
+
emitData(decoder.decode(buf, { stream: true }));
|
|
1433
|
+
});
|
|
1434
|
+
child.stderr.on("data", (buf) => {
|
|
1435
|
+
stderrText += buf.toString("utf8");
|
|
1436
|
+
});
|
|
1437
|
+
child.on("error", (err) => {
|
|
1438
|
+
emitData(`\r
|
|
1439
|
+
[\u7EC8\u7AEF\u542F\u52A8\u5931\u8D25] ${err.message}\r
|
|
1440
|
+
`);
|
|
1441
|
+
finish({ exitCode: null, signal: null });
|
|
1442
|
+
});
|
|
1443
|
+
child.on("exit", (code, signal) => {
|
|
1444
|
+
const rest = decoder.decode();
|
|
1445
|
+
if (rest) emitData(rest);
|
|
1446
|
+
finish({ exitCode: code, signal: signal ?? null });
|
|
1447
|
+
});
|
|
1448
|
+
const control = child.stdio[3];
|
|
1449
|
+
const writeControl = (payload) => {
|
|
1450
|
+
if (!control || !("write" in control)) return;
|
|
1451
|
+
try {
|
|
1452
|
+
;
|
|
1453
|
+
control.write(`${JSON.stringify(payload)}
|
|
1454
|
+
`);
|
|
1455
|
+
} catch {
|
|
1456
|
+
}
|
|
1457
|
+
};
|
|
1458
|
+
return {
|
|
1459
|
+
write(data) {
|
|
1460
|
+
try {
|
|
1461
|
+
child.stdin.write(data);
|
|
1462
|
+
} catch {
|
|
1463
|
+
}
|
|
1464
|
+
},
|
|
1465
|
+
resize(cols, rows) {
|
|
1466
|
+
writeControl({ type: "resize", cols, rows });
|
|
1467
|
+
},
|
|
1468
|
+
kill() {
|
|
1469
|
+
writeControl({ type: "kill" });
|
|
1470
|
+
try {
|
|
1471
|
+
child.kill("SIGTERM");
|
|
1472
|
+
} catch {
|
|
1473
|
+
}
|
|
1474
|
+
},
|
|
1475
|
+
onData(cb) {
|
|
1476
|
+
dataCb = cb;
|
|
1477
|
+
for (const chunk of pending) cb(chunk);
|
|
1478
|
+
pending.length = 0;
|
|
1479
|
+
},
|
|
1480
|
+
onExit(cb) {
|
|
1481
|
+
exitCb = cb;
|
|
1482
|
+
if (pendingExit) cb(pendingExit);
|
|
1483
|
+
}
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
var import_node_child_process2, PTY_HELPER;
|
|
1487
|
+
var init_pty_backend = __esm({
|
|
1488
|
+
"src/bridge/pty-backend.ts"() {
|
|
1305
1489
|
"use strict";
|
|
1306
1490
|
import_node_child_process2 = require("child_process");
|
|
1491
|
+
init_bash_executor();
|
|
1492
|
+
PTY_HELPER = `
|
|
1493
|
+
import errno, fcntl, json, os, pty, select, signal, struct, sys, termios
|
|
1494
|
+
|
|
1495
|
+
shell = os.environ.get("TERMINAL_SHELL") or "/bin/bash"
|
|
1496
|
+
rows = int(os.environ.get("TERMINAL_ROWS") or "24")
|
|
1497
|
+
cols = int(os.environ.get("TERMINAL_COLS") or "80")
|
|
1498
|
+
control_fd = 3
|
|
1499
|
+
|
|
1500
|
+
def set_winsize(fd, next_rows, next_cols):
|
|
1501
|
+
winsize = struct.pack("HHHH", int(next_rows), int(next_cols), 0, 0)
|
|
1502
|
+
fcntl.ioctl(fd, termios.TIOCSWINSZ, winsize)
|
|
1503
|
+
|
|
1504
|
+
pid, master = pty.fork()
|
|
1505
|
+
if pid == 0:
|
|
1506
|
+
try:
|
|
1507
|
+
os.close(control_fd)
|
|
1508
|
+
except OSError:
|
|
1509
|
+
pass
|
|
1510
|
+
os.environ["TERM"] = "xterm-256color"
|
|
1511
|
+
try:
|
|
1512
|
+
os.execvp(shell, [shell, "-i"])
|
|
1513
|
+
except OSError:
|
|
1514
|
+
os._exit(127)
|
|
1515
|
+
|
|
1516
|
+
def control_loop():
|
|
1517
|
+
try:
|
|
1518
|
+
reader = os.fdopen(control_fd, "r", buffering=1, encoding="utf-8", errors="replace")
|
|
1519
|
+
except OSError:
|
|
1520
|
+
return
|
|
1521
|
+
for line in reader:
|
|
1522
|
+
text = line.strip()
|
|
1523
|
+
if not text:
|
|
1524
|
+
continue
|
|
1525
|
+
try:
|
|
1526
|
+
msg = json.loads(text)
|
|
1527
|
+
except json.JSONDecodeError:
|
|
1528
|
+
continue
|
|
1529
|
+
kind = msg.get("type")
|
|
1530
|
+
if kind == "resize":
|
|
1531
|
+
try:
|
|
1532
|
+
set_winsize(master, msg.get("rows") or rows, msg.get("cols") or cols)
|
|
1533
|
+
os.kill(pid, signal.SIGWINCH)
|
|
1534
|
+
except OSError:
|
|
1535
|
+
pass
|
|
1536
|
+
elif kind == "kill":
|
|
1537
|
+
try:
|
|
1538
|
+
os.kill(pid, signal.SIGHUP)
|
|
1539
|
+
except OSError:
|
|
1540
|
+
pass
|
|
1541
|
+
os._exit(0)
|
|
1542
|
+
|
|
1543
|
+
import threading
|
|
1544
|
+
threading.Thread(target=control_loop, daemon=True).start()
|
|
1545
|
+
|
|
1546
|
+
try:
|
|
1547
|
+
set_winsize(master, rows, cols)
|
|
1548
|
+
except OSError:
|
|
1549
|
+
pass
|
|
1550
|
+
|
|
1551
|
+
stdin_fd = sys.stdin.fileno()
|
|
1552
|
+
stdout_fd = sys.stdout.fileno()
|
|
1553
|
+
|
|
1554
|
+
while True:
|
|
1555
|
+
try:
|
|
1556
|
+
readable, _, _ = select.select([master, stdin_fd], [], [])
|
|
1557
|
+
except InterruptedError:
|
|
1558
|
+
continue
|
|
1559
|
+
if master in readable:
|
|
1560
|
+
try:
|
|
1561
|
+
data = os.read(master, 65536)
|
|
1562
|
+
except OSError as err:
|
|
1563
|
+
if err.errno == errno.EIO:
|
|
1564
|
+
break
|
|
1565
|
+
raise
|
|
1566
|
+
if not data:
|
|
1567
|
+
break
|
|
1568
|
+
os.write(stdout_fd, data)
|
|
1569
|
+
if stdin_fd in readable:
|
|
1570
|
+
try:
|
|
1571
|
+
data = os.read(stdin_fd, 65536)
|
|
1572
|
+
except OSError:
|
|
1573
|
+
break
|
|
1574
|
+
if not data:
|
|
1575
|
+
break
|
|
1576
|
+
try:
|
|
1577
|
+
os.write(master, data)
|
|
1578
|
+
except OSError:
|
|
1579
|
+
break
|
|
1580
|
+
|
|
1581
|
+
try:
|
|
1582
|
+
_, status = os.waitpid(pid, 0)
|
|
1583
|
+
except ChildProcessError:
|
|
1584
|
+
status = 0
|
|
1585
|
+
code = os.waitstatus_to_exitcode(status)
|
|
1586
|
+
os._exit(0 if code < 0 else code)
|
|
1587
|
+
`;
|
|
1588
|
+
}
|
|
1589
|
+
});
|
|
1590
|
+
|
|
1591
|
+
// src/bridge/terminal-session.ts
|
|
1592
|
+
function clampDimension(value, fallback) {
|
|
1593
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
1594
|
+
if (!Number.isFinite(parsed)) return fallback;
|
|
1595
|
+
return Math.max(2, Math.min(500, Math.floor(parsed)));
|
|
1596
|
+
}
|
|
1597
|
+
var import_node_crypto, OUTPUT_CHUNK, MAX_INPUT, DEFAULT_COALESCE_MS, TerminalSessionManager;
|
|
1598
|
+
var init_terminal_session = __esm({
|
|
1599
|
+
"src/bridge/terminal-session.ts"() {
|
|
1600
|
+
"use strict";
|
|
1601
|
+
import_node_crypto = require("crypto");
|
|
1602
|
+
init_workspace_manager();
|
|
1603
|
+
init_pty_backend();
|
|
1604
|
+
init_protocol();
|
|
1605
|
+
OUTPUT_CHUNK = 32 * 1024;
|
|
1606
|
+
MAX_INPUT = 64 * 1024;
|
|
1607
|
+
DEFAULT_COALESCE_MS = 16;
|
|
1608
|
+
TerminalSessionManager = class {
|
|
1609
|
+
session = null;
|
|
1610
|
+
buffer = "";
|
|
1611
|
+
timer = null;
|
|
1612
|
+
send;
|
|
1613
|
+
spawnPty;
|
|
1614
|
+
coalesceMs;
|
|
1615
|
+
constructor(options) {
|
|
1616
|
+
this.send = options.send;
|
|
1617
|
+
this.spawnPty = options.spawnPty ?? ((opts) => spawnHostPty(opts));
|
|
1618
|
+
this.coalesceMs = options.coalesceMs ?? DEFAULT_COALESCE_MS;
|
|
1619
|
+
}
|
|
1620
|
+
open(params = {}) {
|
|
1621
|
+
const cwd = resolveWorkspaceDir(params);
|
|
1622
|
+
const cols = clampDimension(params.cols, 80);
|
|
1623
|
+
const rows = clampDimension(params.rows, 24);
|
|
1624
|
+
this.closeActive();
|
|
1625
|
+
const id = (0, import_node_crypto.randomUUID)();
|
|
1626
|
+
const pty = this.spawnPty({ cwd, cols, rows });
|
|
1627
|
+
this.session = { id, pty, cwd };
|
|
1628
|
+
pty.onData((data) => this.pushOutput(id, data));
|
|
1629
|
+
pty.onExit((info) => {
|
|
1630
|
+
if (this.session?.id !== id) return;
|
|
1631
|
+
this.flush(id);
|
|
1632
|
+
this.session = null;
|
|
1633
|
+
this.send({
|
|
1634
|
+
jsonrpc: "2.0",
|
|
1635
|
+
method: TERMINAL_EVENTS.EXIT,
|
|
1636
|
+
params: {
|
|
1637
|
+
sessionId: id,
|
|
1638
|
+
exitCode: info.exitCode,
|
|
1639
|
+
signal: info.signal
|
|
1640
|
+
}
|
|
1641
|
+
});
|
|
1642
|
+
});
|
|
1643
|
+
return { sessionId: id, cwd };
|
|
1644
|
+
}
|
|
1645
|
+
input(params) {
|
|
1646
|
+
const session = this.requireSession(params.sessionId);
|
|
1647
|
+
if (typeof params.data !== "string") {
|
|
1648
|
+
throw new BashInvalidParamsError("data \u5FC5\u987B\u662F\u5B57\u7B26\u4E32");
|
|
1649
|
+
}
|
|
1650
|
+
if (params.data.length > MAX_INPUT) {
|
|
1651
|
+
throw new BashInvalidParamsError("\u5355\u6B21\u8F93\u5165\u8FC7\u957F");
|
|
1652
|
+
}
|
|
1653
|
+
session.pty.write(params.data);
|
|
1654
|
+
return { ok: true };
|
|
1655
|
+
}
|
|
1656
|
+
resize(params) {
|
|
1657
|
+
const session = this.requireSession(params.sessionId);
|
|
1658
|
+
session.pty.resize(clampDimension(params.cols, 80), clampDimension(params.rows, 24));
|
|
1659
|
+
return { ok: true };
|
|
1660
|
+
}
|
|
1661
|
+
close(params = {}) {
|
|
1662
|
+
if (!this.session || params.sessionId && params.sessionId !== this.session.id) {
|
|
1663
|
+
return { ok: true };
|
|
1664
|
+
}
|
|
1665
|
+
this.closeActive();
|
|
1666
|
+
return { ok: true };
|
|
1667
|
+
}
|
|
1668
|
+
disposeAll() {
|
|
1669
|
+
this.closeActive();
|
|
1670
|
+
}
|
|
1671
|
+
requireSession(sessionId) {
|
|
1672
|
+
if (!this.session || !sessionId || sessionId !== this.session.id) {
|
|
1673
|
+
throw new Error("\u7EC8\u7AEF\u4F1A\u8BDD\u4E0D\u5B58\u5728");
|
|
1674
|
+
}
|
|
1675
|
+
return this.session;
|
|
1676
|
+
}
|
|
1677
|
+
closeActive() {
|
|
1678
|
+
const current = this.session;
|
|
1679
|
+
this.session = null;
|
|
1680
|
+
this.buffer = "";
|
|
1681
|
+
if (this.timer) {
|
|
1682
|
+
clearTimeout(this.timer);
|
|
1683
|
+
this.timer = null;
|
|
1684
|
+
}
|
|
1685
|
+
current?.pty.kill();
|
|
1686
|
+
}
|
|
1687
|
+
pushOutput(sessionId, chunk) {
|
|
1688
|
+
if (this.session?.id !== sessionId || !chunk) return;
|
|
1689
|
+
this.buffer += chunk;
|
|
1690
|
+
this.flushFullChunks(sessionId);
|
|
1691
|
+
if (!this.buffer) return;
|
|
1692
|
+
if (this.coalesceMs <= 0) {
|
|
1693
|
+
this.flush(sessionId);
|
|
1694
|
+
return;
|
|
1695
|
+
}
|
|
1696
|
+
if (!this.timer) {
|
|
1697
|
+
this.timer = setTimeout(() => this.flush(sessionId), this.coalesceMs);
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
flushFullChunks(sessionId) {
|
|
1701
|
+
while (this.buffer.length >= OUTPUT_CHUNK) {
|
|
1702
|
+
const data = this.buffer.slice(0, OUTPUT_CHUNK);
|
|
1703
|
+
this.buffer = this.buffer.slice(OUTPUT_CHUNK);
|
|
1704
|
+
this.emitOutput(sessionId, data);
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
flush(sessionId) {
|
|
1708
|
+
if (this.timer) {
|
|
1709
|
+
clearTimeout(this.timer);
|
|
1710
|
+
this.timer = null;
|
|
1711
|
+
}
|
|
1712
|
+
if (!this.buffer || this.session?.id !== sessionId) {
|
|
1713
|
+
this.buffer = "";
|
|
1714
|
+
return;
|
|
1715
|
+
}
|
|
1716
|
+
const data = this.buffer;
|
|
1717
|
+
this.buffer = "";
|
|
1718
|
+
this.emitOutput(sessionId, data);
|
|
1719
|
+
}
|
|
1720
|
+
emitOutput(sessionId, data) {
|
|
1721
|
+
this.send({
|
|
1722
|
+
jsonrpc: "2.0",
|
|
1723
|
+
method: TERMINAL_EVENTS.OUTPUT,
|
|
1724
|
+
params: { sessionId, data }
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
};
|
|
1307
1728
|
}
|
|
1308
1729
|
});
|
|
1309
1730
|
|
|
@@ -1341,7 +1762,7 @@ function resolveOrCreateAuthToken(explicitToken) {
|
|
|
1341
1762
|
}
|
|
1342
1763
|
} catch {
|
|
1343
1764
|
}
|
|
1344
|
-
const newToken = (0,
|
|
1765
|
+
const newToken = (0, import_node_crypto2.randomBytes)(32).toString("hex");
|
|
1345
1766
|
try {
|
|
1346
1767
|
if (!import_node_fs3.default.existsSync(TOKEN_FILE_DIR)) {
|
|
1347
1768
|
import_node_fs3.default.mkdirSync(TOKEN_FILE_DIR, { mode: 448, recursive: true });
|
|
@@ -1355,10 +1776,10 @@ function parseMajorVersion(v) {
|
|
|
1355
1776
|
return parseInt(v.split(".")[0] ?? "0", 10);
|
|
1356
1777
|
}
|
|
1357
1778
|
function deriveSharedSecret(tokenOrExtensionId, hmacSalt) {
|
|
1358
|
-
return (0,
|
|
1779
|
+
return (0, import_node_crypto2.createHash)("sha256").update(`${tokenOrExtensionId}:${hmacSalt}`).digest();
|
|
1359
1780
|
}
|
|
1360
1781
|
function computeHmac(secret, challenge) {
|
|
1361
|
-
return (0,
|
|
1782
|
+
return (0, import_node_crypto2.createHmac)("sha256", secret).update(challenge).digest("hex");
|
|
1362
1783
|
}
|
|
1363
1784
|
function verifyHmacConstantTime(actualHex, expectedHex) {
|
|
1364
1785
|
if (!actualHex || typeof actualHex !== "string") return false;
|
|
@@ -1366,7 +1787,7 @@ function verifyHmacConstantTime(actualHex, expectedHex) {
|
|
|
1366
1787
|
const actualBuf = Buffer.from(actualHex, "hex");
|
|
1367
1788
|
const expectedBuf = Buffer.from(expectedHex, "hex");
|
|
1368
1789
|
if (actualBuf.length !== expectedBuf.length) return false;
|
|
1369
|
-
return (0,
|
|
1790
|
+
return (0, import_node_crypto2.timingSafeEqual)(actualBuf, expectedBuf);
|
|
1370
1791
|
} catch {
|
|
1371
1792
|
return false;
|
|
1372
1793
|
}
|
|
@@ -1377,19 +1798,20 @@ function getKillPortHint(port) {
|
|
|
1377
1798
|
}
|
|
1378
1799
|
return `lsof -ti:${port} | xargs kill -9`;
|
|
1379
1800
|
}
|
|
1380
|
-
var import_ws, import_node_events,
|
|
1801
|
+
var import_ws, import_node_events, import_node_crypto2, import_node_fs3, import_node_path3, import_node_os2, WS_PORT, REQUEST_TIMEOUT_MS, HANDSHAKE_TIMEOUT_MS, WS_OPEN, TOKEN_FILE_DIR, TOKEN_FILE_PATH, ExtensionBridgeClient;
|
|
1381
1802
|
var init_bridge_client = __esm({
|
|
1382
1803
|
"src/bridge/bridge-client.ts"() {
|
|
1383
1804
|
"use strict";
|
|
1384
1805
|
import_ws = require("ws");
|
|
1385
1806
|
import_node_events = require("events");
|
|
1386
|
-
|
|
1807
|
+
import_node_crypto2 = require("crypto");
|
|
1387
1808
|
import_node_fs3 = __toESM(require("fs"), 1);
|
|
1388
1809
|
import_node_path3 = __toESM(require("path"), 1);
|
|
1389
1810
|
import_node_os2 = __toESM(require("os"), 1);
|
|
1390
1811
|
init_protocol();
|
|
1391
1812
|
init_bash_executor();
|
|
1392
1813
|
init_directory_picker();
|
|
1814
|
+
init_terminal_session();
|
|
1393
1815
|
WS_PORT = DEFAULT_WS_PORT;
|
|
1394
1816
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
1395
1817
|
HANDSHAKE_TIMEOUT_MS = 5e3;
|
|
@@ -1411,6 +1833,7 @@ var init_bridge_client = __esm({
|
|
|
1411
1833
|
pending = /* @__PURE__ */ new Map();
|
|
1412
1834
|
socketAuthStates = /* @__PURE__ */ new Map();
|
|
1413
1835
|
destroyed = false;
|
|
1836
|
+
terminalSessions = null;
|
|
1414
1837
|
idCounter = 0;
|
|
1415
1838
|
isClientMode = false;
|
|
1416
1839
|
explicitAuthToken;
|
|
@@ -1665,8 +2088,8 @@ var init_bridge_client = __esm({
|
|
|
1665
2088
|
this.emit("error", err);
|
|
1666
2089
|
});
|
|
1667
2090
|
this.wss.on("connection", (socket) => {
|
|
1668
|
-
const challenge = (0,
|
|
1669
|
-
const hmacSalt = (0,
|
|
2091
|
+
const challenge = (0, import_node_crypto2.randomUUID)();
|
|
2092
|
+
const hmacSalt = (0, import_node_crypto2.randomBytes)(16).toString("hex");
|
|
1670
2093
|
const authState = {
|
|
1671
2094
|
authenticated: false,
|
|
1672
2095
|
challenge,
|
|
@@ -1707,6 +2130,7 @@ var init_bridge_client = __esm({
|
|
|
1707
2130
|
}
|
|
1708
2131
|
if (socket === this.extensionSocket) {
|
|
1709
2132
|
this.extensionSocket = null;
|
|
2133
|
+
this.terminalSessions?.disposeAll();
|
|
1710
2134
|
this.emit("disconnected");
|
|
1711
2135
|
for (const [reqId, fwdInfo] of this.forwardedRequests.entries()) {
|
|
1712
2136
|
if (fwdInfo.timer) clearTimeout(fwdInfo.timer);
|
|
@@ -1753,6 +2177,16 @@ var init_bridge_client = __esm({
|
|
|
1753
2177
|
return;
|
|
1754
2178
|
}
|
|
1755
2179
|
if (msg.method && msg.id) {
|
|
2180
|
+
if (typeof msg.method === "string" && isTerminalMethod(msg.method) && socket !== this.extensionSocket) {
|
|
2181
|
+
this.sendJsonRpcResponse(socket, {
|
|
2182
|
+
id: msg.id,
|
|
2183
|
+
error: {
|
|
2184
|
+
code: BRIDGE_ERROR_CODE.METHOD_NOT_FOUND,
|
|
2185
|
+
message: "\u7EC8\u7AEF\u4F1A\u8BDD\u4EC5\u5141\u8BB8\u5DF2\u8BA4\u8BC1\u7684\u6269\u5C55\u8FDE\u63A5"
|
|
2186
|
+
}
|
|
2187
|
+
});
|
|
2188
|
+
return;
|
|
2189
|
+
}
|
|
1756
2190
|
if (socket === this.extensionSocket) {
|
|
1757
2191
|
await this.handleExtensionSystemRpc(socket, msg);
|
|
1758
2192
|
return;
|
|
@@ -1916,9 +2350,6 @@ var init_bridge_client = __esm({
|
|
|
1916
2350
|
systemRpcHandlers = Object.assign(
|
|
1917
2351
|
/* @__PURE__ */ Object.create(null),
|
|
1918
2352
|
{
|
|
1919
|
-
[BRIDGE_METHODS.SYSTEM_BASH]: async (params) => {
|
|
1920
|
-
return executeBash(params || {});
|
|
1921
|
-
},
|
|
1922
2353
|
[BRIDGE_METHODS.SYSTEM_PICK_DIRECTORY]: async (params) => {
|
|
1923
2354
|
const selectedPath = await pickDirectory({
|
|
1924
2355
|
title: params?.title || void 0,
|
|
@@ -1929,11 +2360,43 @@ var init_bridge_client = __esm({
|
|
|
1929
2360
|
selectedPath,
|
|
1930
2361
|
canceled: selectedPath === null
|
|
1931
2362
|
};
|
|
1932
|
-
}
|
|
2363
|
+
},
|
|
2364
|
+
[BRIDGE_METHODS.TERMINAL_OPEN]: async (params) => this.getTerminalSessions().open(params || {}),
|
|
2365
|
+
[BRIDGE_METHODS.TERMINAL_INPUT]: async (params) => this.getTerminalSessions().input(params || {}),
|
|
2366
|
+
[BRIDGE_METHODS.TERMINAL_RESIZE]: async (params) => this.getTerminalSessions().resize(params || {}),
|
|
2367
|
+
[BRIDGE_METHODS.TERMINAL_CLOSE]: async (params) => this.getTerminalSessions().close(params || {})
|
|
1933
2368
|
}
|
|
1934
2369
|
);
|
|
2370
|
+
sendJsonRpcNotification(socket, method, params) {
|
|
2371
|
+
if (socket.readyState === WS_OPEN) {
|
|
2372
|
+
socket.send(JSON.stringify({ jsonrpc: "2.0", method, params }));
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
async executeHostBash(socket, id, params) {
|
|
2376
|
+
try {
|
|
2377
|
+
const requestParams = params && typeof params === "object" ? params : {};
|
|
2378
|
+
const result = await executeBash({
|
|
2379
|
+
...requestParams,
|
|
2380
|
+
onOutput: (chunk) => {
|
|
2381
|
+
if (!chunk.text) return;
|
|
2382
|
+
this.sendJsonRpcNotification(socket, BASH_EVENTS.OUTPUT, {
|
|
2383
|
+
id,
|
|
2384
|
+
stream: chunk.stream,
|
|
2385
|
+
text: chunk.text
|
|
2386
|
+
});
|
|
2387
|
+
}
|
|
2388
|
+
});
|
|
2389
|
+
this.sendJsonRpcResponse(socket, { id, result });
|
|
2390
|
+
} catch (err) {
|
|
2391
|
+
this.sendJsonRpcResponse(socket, { id, error: this.formatSystemRpcError(err) });
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
1935
2394
|
async handleExtensionSystemRpc(socket, msg) {
|
|
1936
2395
|
const { id, method, params } = msg;
|
|
2396
|
+
if (method === BRIDGE_METHODS.SYSTEM_BASH) {
|
|
2397
|
+
await this.executeHostBash(socket, id, params);
|
|
2398
|
+
return;
|
|
2399
|
+
}
|
|
1937
2400
|
const handler = typeof method === "string" && Object.prototype.hasOwnProperty.call(this.systemRpcHandlers, method) ? this.systemRpcHandlers[method] : void 0;
|
|
1938
2401
|
if (!handler || typeof handler !== "function") {
|
|
1939
2402
|
this.sendJsonRpcResponse(socket, {
|
|
@@ -2029,9 +2492,26 @@ var init_bridge_client = __esm({
|
|
|
2029
2492
|
}
|
|
2030
2493
|
});
|
|
2031
2494
|
}
|
|
2495
|
+
getTerminalSessions() {
|
|
2496
|
+
if (!this.terminalSessions) {
|
|
2497
|
+
this.terminalSessions = new TerminalSessionManager({
|
|
2498
|
+
send: (frame) => {
|
|
2499
|
+
const socket = this.extensionSocket;
|
|
2500
|
+
if (socket && socket.readyState === WS_OPEN) {
|
|
2501
|
+
try {
|
|
2502
|
+
socket.send(JSON.stringify(frame));
|
|
2503
|
+
} catch {
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
});
|
|
2508
|
+
}
|
|
2509
|
+
return this.terminalSessions;
|
|
2510
|
+
}
|
|
2032
2511
|
/** 销毁服务,断开所有连接 */
|
|
2033
2512
|
destroy() {
|
|
2034
2513
|
this.destroyed = true;
|
|
2514
|
+
this.terminalSessions?.disposeAll();
|
|
2035
2515
|
this._rejectAllPending("ExtensionBridgeClient \u5DF2\u9500\u6BC1");
|
|
2036
2516
|
this.forwardedRequests.clear();
|
|
2037
2517
|
if (this.clientSocket) {
|
|
@@ -2176,7 +2656,7 @@ __export(daemon_exports, {
|
|
|
2176
2656
|
isDaemonProcessAlive: () => isDaemonProcessAlive,
|
|
2177
2657
|
isProcessAlive: () => isProcessAlive,
|
|
2178
2658
|
killProcessOnPort: () => killProcessOnPort,
|
|
2179
|
-
killProcessTree: () =>
|
|
2659
|
+
killProcessTree: () => killProcessTree,
|
|
2180
2660
|
probeServerReady: () => probeServerReady,
|
|
2181
2661
|
runDaemonProcess: () => runDaemonProcess,
|
|
2182
2662
|
stopBridgeDaemon: () => stopBridgeDaemon
|
|
@@ -2229,7 +2709,7 @@ async function findPidByPort(port, excludePid) {
|
|
|
2229
2709
|
return null;
|
|
2230
2710
|
}
|
|
2231
2711
|
}
|
|
2232
|
-
async function
|
|
2712
|
+
async function killProcessTree(pid) {
|
|
2233
2713
|
if (!pid || pid <= 0 || pid === process.pid) return false;
|
|
2234
2714
|
if (process.platform === "win32") {
|
|
2235
2715
|
try {
|
|
@@ -2267,7 +2747,7 @@ async function killProcessTree2(pid) {
|
|
|
2267
2747
|
async function killProcessOnPort(port) {
|
|
2268
2748
|
const pid = await findPidByPort(port, process.pid);
|
|
2269
2749
|
if (pid) {
|
|
2270
|
-
await
|
|
2750
|
+
await killProcessTree(pid);
|
|
2271
2751
|
for (let i = 0; i < 20; i++) {
|
|
2272
2752
|
await new Promise((r) => setTimeout(r, 100));
|
|
2273
2753
|
const remainingPid = await findPidByPort(port, process.pid);
|
|
@@ -2449,7 +2929,7 @@ async function stopBridgeDaemon(port = WS_PORT2, options = {}) {
|
|
|
2449
2929
|
if (portPid && portPid !== daemonPid) {
|
|
2450
2930
|
return false;
|
|
2451
2931
|
}
|
|
2452
|
-
const killed = await
|
|
2932
|
+
const killed = await killProcessTree(daemonPid);
|
|
2453
2933
|
if (killed) {
|
|
2454
2934
|
killedAny = true;
|
|
2455
2935
|
try {
|
|
@@ -2460,7 +2940,7 @@ async function stopBridgeDaemon(port = WS_PORT2, options = {}) {
|
|
|
2460
2940
|
} else {
|
|
2461
2941
|
if (daemonPid) {
|
|
2462
2942
|
try {
|
|
2463
|
-
const killed = await
|
|
2943
|
+
const killed = await killProcessTree(daemonPid);
|
|
2464
2944
|
if (killed) {
|
|
2465
2945
|
killedAny = true;
|
|
2466
2946
|
try {
|