@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/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.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: {
@@ -536,7 +544,8 @@ var init_package = __esm({
536
544
  },
537
545
  files: [
538
546
  "dist",
539
- "README.md"
547
+ "README.md",
548
+ "scripts/ensure-pty-helper.mjs"
540
549
  ],
541
550
  scripts: {
542
551
  dev: "tsup --watch",
@@ -545,6 +554,7 @@ var init_package = __esm({
545
554
  prepublishOnly: "pnpm run build",
546
555
  "link:global": "npm install -g .",
547
556
  start: "node dist/bin.js",
557
+ postinstall: "node scripts/ensure-pty-helper.mjs",
548
558
  test: "vitest run",
549
559
  "test:watch": "vitest",
550
560
  "test:browser": "vitest run --config vitest.e2e.config.ts"
@@ -557,7 +567,7 @@ var init_package = __esm({
557
567
  license: "MIT",
558
568
  author: "OpenTiny Team",
559
569
  engines: {
560
- node: ">=16"
570
+ node: ">=18.19.0"
561
571
  },
562
572
  publishConfig: {
563
573
  access: "public"
@@ -569,6 +579,7 @@ var init_package = __esm({
569
579
  "@page-agent/page-controller": "catalog:",
570
580
  clipboardy: "^5.3.1",
571
581
  commander: "^11.1.0",
582
+ execa: "^9.6.1",
572
583
  json5: "^2.2.3",
573
584
  marked: "^15.0.7",
574
585
  picocolors: "^1.0.0",
@@ -576,6 +587,9 @@ var init_package = __esm({
576
587
  ws: "^8.18.0",
577
588
  zod: "^3.23.0"
578
589
  },
590
+ optionalDependencies: {
591
+ "node-pty": "^1.1.0"
592
+ },
579
593
  devDependencies: {
580
594
  "@types/node": "^20.0.0",
581
595
  "@types/ws": "^8.5.10",
@@ -797,12 +811,15 @@ var init_cdp_adapter = __esm({
797
811
  });
798
812
 
799
813
  // src/bridge/protocol.ts
800
- var DEFAULT_WS_PORT, PROTOCOL_VERSION, WS_CLOSE_CODE, BRIDGE_ERROR_CODE, BRIDGE_METHODS;
814
+ function isTerminalMethod(method) {
815
+ return TERMINAL_METHOD_SET.has(method);
816
+ }
817
+ var DEFAULT_WS_PORT, PROTOCOL_VERSION, WS_CLOSE_CODE, BRIDGE_ERROR_CODE, BRIDGE_METHODS, TERMINAL_EVENTS, BASH_EVENTS, TERMINAL_METHOD_SET;
801
818
  var init_protocol = __esm({
802
819
  "src/bridge/protocol.ts"() {
803
820
  "use strict";
804
821
  DEFAULT_WS_PORT = 18999;
805
- PROTOCOL_VERSION = "1.1.0";
822
+ PROTOCOL_VERSION = "1.2.0";
806
823
  WS_CLOSE_CODE = {
807
824
  /** 被新接入的扩展连接平滑替换(旧连接触发退避重连) */
808
825
  REPLACED: 4e3,
@@ -836,8 +853,25 @@ var init_protocol = __esm({
836
853
  SKILLS_GET: "skills/get",
837
854
  SKILLS_READ_RESOURCE: "skills/read_resource",
838
855
  SYSTEM_BASH: "system/bash",
839
- SYSTEM_PICK_DIRECTORY: "system/pick_directory"
856
+ SYSTEM_PICK_DIRECTORY: "system/pick_directory",
857
+ TERMINAL_OPEN: "terminal/open",
858
+ TERMINAL_INPUT: "terminal/input",
859
+ TERMINAL_RESIZE: "terminal/resize",
860
+ TERMINAL_CLOSE: "terminal/close"
861
+ };
862
+ TERMINAL_EVENTS = {
863
+ OUTPUT: "notification/terminal_output",
864
+ EXIT: "notification/terminal_exit"
840
865
  };
866
+ BASH_EVENTS = {
867
+ OUTPUT: "notification/bash_output"
868
+ };
869
+ TERMINAL_METHOD_SET = /* @__PURE__ */ new Set([
870
+ BRIDGE_METHODS.TERMINAL_OPEN,
871
+ BRIDGE_METHODS.TERMINAL_INPUT,
872
+ BRIDGE_METHODS.TERMINAL_RESIZE,
873
+ BRIDGE_METHODS.TERMINAL_CLOSE
874
+ ]);
841
875
  }
842
876
  });
843
877
 
@@ -896,9 +930,11 @@ var init_workspace_manager = __esm({
896
930
  });
897
931
 
898
932
  // src/bridge/bash-executor.ts
933
+ import { spawnSync } from "child_process";
899
934
  import fs3 from "fs";
900
935
  import path3 from "path";
901
- import { spawn as spawn2 } from "child_process";
936
+ import { StringDecoder } from "string_decoder";
937
+ import { execa } from "execa";
902
938
  function findWindowsBash() {
903
939
  const pathDirs = (process.env.PATH || "").split(path3.delimiter);
904
940
  for (const dir of pathDirs) {
@@ -949,25 +985,136 @@ function resolveShell(forcePlatform) {
949
985
  if (!forcePlatform) cachedShell = fallback;
950
986
  return fallback;
951
987
  }
952
- function killProcessTree(child) {
953
- if (process.platform === "win32" && child.pid) {
988
+ function stripAnsi(text) {
989
+ return text.replace(ANSI_PATTERN, "");
990
+ }
991
+ function createAnsiStripper() {
992
+ let pending = "";
993
+ const take = (final) => {
994
+ let out = "";
995
+ let index = 0;
996
+ while (index < pending.length) {
997
+ if (pending[index] !== "\x1B") {
998
+ out += pending[index];
999
+ index += 1;
1000
+ continue;
1001
+ }
1002
+ const rest = pending.slice(index);
1003
+ ANSI_PATTERN.lastIndex = 0;
1004
+ const match = ANSI_PATTERN.exec(rest);
1005
+ if (match && match.index === 0) {
1006
+ index += match[0].length;
1007
+ continue;
1008
+ }
1009
+ if (!final && rest.length < 128 && isOpenAnsi(rest)) {
1010
+ pending = rest;
1011
+ return out;
1012
+ }
1013
+ out += pending[index];
1014
+ index += 1;
1015
+ }
1016
+ pending = "";
1017
+ return out;
1018
+ };
1019
+ return {
1020
+ push(text) {
1021
+ pending += text;
1022
+ return take(false);
1023
+ },
1024
+ end() {
1025
+ return take(true);
1026
+ }
1027
+ };
1028
+ }
1029
+ function isOpenAnsi(text) {
1030
+ if (!text.startsWith("\x1B")) return false;
1031
+ if (text.length === 1) return true;
1032
+ if (text[1] === "[") {
1033
+ let index = 2;
1034
+ while (index < text.length && /[0-?]/.test(text[index])) index += 1;
1035
+ while (index < text.length && /[ -/]/.test(text[index])) index += 1;
1036
+ return index >= text.length;
1037
+ }
1038
+ if (text[1] === "]") return !text.includes("\x07") && !text.includes("\x1B\\");
1039
+ return false;
1040
+ }
1041
+ function withoutCarriageReturn(buf) {
1042
+ if (!buf.includes(13)) return buf;
1043
+ const out = Buffer.allocUnsafe(buf.length);
1044
+ let size = 0;
1045
+ for (let index = 0; index < buf.length; index += 1) {
1046
+ const byte = buf[index];
1047
+ if (byte === 13) continue;
1048
+ out[size] = byte;
1049
+ size += 1;
1050
+ }
1051
+ return out.subarray(0, size);
1052
+ }
1053
+ function collectLimited(stream, maxBuffer, onText, stripCarriageReturn = false, ansi) {
1054
+ const state = { chunks: [], truncated: false };
1055
+ if (!stream) return state;
1056
+ const decoder = new StringDecoder("utf8");
1057
+ let bytes = 0;
1058
+ const emitText = (text) => {
1059
+ const cleaned = ansi ? ansi.push(text) : text;
1060
+ if (cleaned) onText?.(cleaned);
1061
+ };
1062
+ const emit = (buf) => {
1063
+ emitText(decoder.write(buf));
1064
+ };
1065
+ stream.on("data", (chunk) => {
1066
+ const raw = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
1067
+ const buf = stripCarriageReturn ? withoutCarriageReturn(raw) : raw;
1068
+ if (buf.length === 0) return;
1069
+ emit(buf);
1070
+ if (bytes >= maxBuffer) {
1071
+ state.truncated = true;
1072
+ return;
1073
+ }
1074
+ const remaining = maxBuffer - bytes;
1075
+ if (buf.length <= remaining) {
1076
+ state.chunks.push(buf);
1077
+ bytes += buf.length;
1078
+ return;
1079
+ }
1080
+ state.chunks.push(buf.subarray(0, remaining));
1081
+ bytes += remaining;
1082
+ state.truncated = true;
1083
+ });
1084
+ stream.on("end", () => {
1085
+ const rest = ansi ? `${ansi.push(decoder.end())}${ansi.end()}` : decoder.end();
1086
+ if (rest) onText?.(rest);
1087
+ });
1088
+ return state;
1089
+ }
1090
+ function decodeLimited(state, removeAnsi = false) {
1091
+ const decoder = new StringDecoder("utf8");
1092
+ const text = decoder.write(Buffer.concat(state.chunks));
1093
+ const flushed = state.truncated ? "" : decoder.end();
1094
+ const decoded = text + flushed;
1095
+ return (removeAnsi ? stripAnsi(decoded) : decoded).trim();
1096
+ }
1097
+ function signalProcessTree(pid, signal) {
1098
+ if (!pid || pid <= 0) return;
1099
+ if (process.platform === "win32") {
954
1100
  try {
955
- spawn2("taskkill", ["/pid", String(child.pid), "/T", "/F"], {
956
- windowsHide: true,
957
- stdio: "ignore"
958
- });
1101
+ spawnSync("taskkill", ["/pid", String(pid), "/T", "/F"], { windowsHide: true, timeout: 5e3 });
959
1102
  } catch {
960
- child.kill("SIGKILL");
961
1103
  }
962
- } else {
963
- child.kill("SIGTERM");
964
- setTimeout(() => {
965
- try {
966
- child.kill("SIGKILL");
967
- } catch {
968
- }
969
- }, 2e3);
1104
+ return;
970
1105
  }
1106
+ try {
1107
+ process.kill(-pid, signal);
1108
+ } catch {
1109
+ try {
1110
+ process.kill(pid, signal);
1111
+ } catch {
1112
+ }
1113
+ }
1114
+ }
1115
+ function ignoreStreamError(stream) {
1116
+ stream?.on("error", () => {
1117
+ });
971
1118
  }
972
1119
  async function executeBash(params) {
973
1120
  const trimmedCommand = params.command?.trim();
@@ -986,77 +1133,83 @@ async function executeBash(params) {
986
1133
  });
987
1134
  const normalizedTimeoutMs = typeof params.timeoutMs === "number" && Number.isFinite(params.timeoutMs) && params.timeoutMs > 0 ? Math.min(params.timeoutMs, MAX_TIMEOUT_MS) : DEFAULT_TIMEOUT_MS;
988
1135
  const { env = {}, maxBuffer = DEFAULT_MAX_BUFFER } = params;
989
- const startTime = Date.now();
990
1136
  const shell = resolveShell();
991
- return new Promise((resolve3, reject) => {
992
- const stdoutChunks = [];
993
- let stdoutBytes = 0;
994
- const stderrChunks = [];
995
- let stderrBytes = 0;
996
- let truncated = false;
997
- let isKilled = false;
998
- const child = spawn2(trimmedCommand, {
999
- shell,
1000
- cwd,
1001
- env: { ...process.env, ...env },
1002
- windowsHide: true
1003
- });
1004
- const killTimeoutTimer = setTimeout(() => {
1005
- isKilled = true;
1006
- killProcessTree(child);
1007
- reject(new BashTimeoutError(normalizedTimeoutMs));
1008
- }, normalizedTimeoutMs);
1009
- child.stdout.on("data", (chunk) => {
1010
- if (stdoutBytes < maxBuffer) {
1011
- const remaining = maxBuffer - stdoutBytes;
1012
- if (chunk.length <= remaining) {
1013
- stdoutChunks.push(chunk);
1014
- stdoutBytes += chunk.length;
1015
- } else {
1016
- stdoutChunks.push(chunk.subarray(0, remaining));
1017
- stdoutBytes += remaining;
1018
- truncated = true;
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
- });
1137
+ const useTty = params.tty === true && process.platform !== "win32";
1138
+ const detached = process.platform !== "win32";
1139
+ const subprocess = useTty ? execa("python3", ["-u", "-c", TTY_HELPER], {
1140
+ cwd,
1141
+ env: {
1142
+ ...env,
1143
+ PAGER: "cat",
1144
+ GIT_PAGER: "cat",
1145
+ BASH_TTY_COMMAND: trimmedCommand,
1146
+ BASH_TTY_SHELL: shell
1147
+ },
1148
+ stdin: "ignore",
1149
+ buffer: false,
1150
+ encoding: "buffer",
1151
+ reject: false,
1152
+ windowsHide: true,
1153
+ stripFinalNewline: false,
1154
+ detached
1155
+ }) : execa(trimmedCommand, {
1156
+ shell,
1157
+ cwd,
1158
+ env,
1159
+ stdin: "ignore",
1160
+ buffer: false,
1161
+ encoding: "buffer",
1162
+ reject: false,
1163
+ windowsHide: true,
1164
+ stripFinalNewline: false,
1165
+ detached
1057
1166
  });
1167
+ ignoreStreamError(subprocess.stdout);
1168
+ ignoreStreamError(subprocess.stderr);
1169
+ let timedOut = false;
1170
+ let forceTimer;
1171
+ const timer = setTimeout(() => {
1172
+ timedOut = true;
1173
+ signalProcessTree(subprocess.pid, "SIGTERM");
1174
+ forceTimer = setTimeout(() => {
1175
+ signalProcessTree(subprocess.pid, "SIGKILL");
1176
+ subprocess.stdout?.destroy();
1177
+ subprocess.stderr?.destroy();
1178
+ }, FORCE_KILL_AFTER_DELAY_MS);
1179
+ }, normalizedTimeoutMs);
1180
+ const emit = (stream) => (text) => {
1181
+ params.onOutput?.({ stream, text });
1182
+ };
1183
+ const ansi = useTty ? createAnsiStripper() : void 0;
1184
+ const stdoutState = collectLimited(subprocess.stdout, maxBuffer, emit("stdout"), useTty, ansi);
1185
+ const stderrState = collectLimited(subprocess.stderr, maxBuffer, emit("stderr"));
1186
+ let result;
1187
+ try {
1188
+ result = await subprocess;
1189
+ } catch (err) {
1190
+ if (timedOut) throw new BashTimeoutError(normalizedTimeoutMs);
1191
+ throw err;
1192
+ } finally {
1193
+ clearTimeout(timer);
1194
+ if (forceTimer) clearTimeout(forceTimer);
1195
+ }
1196
+ if (timedOut) {
1197
+ throw new BashTimeoutError(normalizedTimeoutMs);
1198
+ }
1199
+ if (result.failed && result.exitCode == null && result.signal == null && !result.isTerminated) {
1200
+ throw new Error(result.shortMessage || result.message || "\u547D\u4EE4\u542F\u52A8\u5931\u8D25");
1201
+ }
1202
+ const truncated = stdoutState.truncated || stderrState.truncated;
1203
+ return {
1204
+ stdout: decodeLimited(stdoutState, useTty),
1205
+ stderr: decodeLimited(stderrState),
1206
+ exitCode: result.exitCode ?? null,
1207
+ signal: result.signal ?? null,
1208
+ durationMs: result.durationMs ?? 0,
1209
+ ...truncated ? { truncated: true } : {}
1210
+ };
1058
1211
  }
1059
- var BashTimeoutError, DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS, DEFAULT_MAX_BUFFER, cachedShell;
1212
+ var BashTimeoutError, DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS, DEFAULT_MAX_BUFFER, cachedShell, FORCE_KILL_AFTER_DELAY_MS, TTY_HELPER, ANSI_PATTERN;
1060
1213
  var init_bash_executor = __esm({
1061
1214
  "src/bridge/bash-executor.ts"() {
1062
1215
  "use strict";
@@ -1074,11 +1227,56 @@ var init_bash_executor = __esm({
1074
1227
  MAX_TIMEOUT_MS = 6e5;
1075
1228
  DEFAULT_MAX_BUFFER = 512 * 1024;
1076
1229
  cachedShell = null;
1230
+ FORCE_KILL_AFTER_DELAY_MS = 2e3;
1231
+ TTY_HELPER = `
1232
+ import errno, os, sys
1233
+ cmd = os.environ.get("BASH_TTY_COMMAND") or ""
1234
+ shell = os.environ.get("BASH_TTY_SHELL") or "/bin/bash"
1235
+ if not cmd:
1236
+ sys.exit(127)
1237
+ import pty
1238
+ pid, fd = pty.fork()
1239
+ if pid == 0:
1240
+ os.environ.pop("BASH_TTY_COMMAND", None)
1241
+ os.environ.pop("BASH_TTY_SHELL", None)
1242
+ os.environ["TERM"] = "xterm-256color"
1243
+ try:
1244
+ # 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
1245
+ null_fd = os.open(os.devnull, os.O_RDONLY)
1246
+ os.dup2(null_fd, 0)
1247
+ os.close(null_fd)
1248
+ os.execvp(shell, [shell, "-c", cmd])
1249
+ except OSError:
1250
+ os._exit(127)
1251
+ while True:
1252
+ try:
1253
+ data = os.read(fd, 65536)
1254
+ except OSError as exc:
1255
+ if exc.errno == errno.EINTR:
1256
+ continue
1257
+ break
1258
+ if not data:
1259
+ break
1260
+ try:
1261
+ os.write(1, data)
1262
+ except OSError:
1263
+ break
1264
+ try:
1265
+ _, status = os.waitpid(pid, 0)
1266
+ except OSError:
1267
+ sys.exit(1)
1268
+ if os.WIFEXITED(status):
1269
+ sys.exit(os.WEXITSTATUS(status))
1270
+ if os.WIFSIGNALED(status):
1271
+ sys.exit(128 + os.WTERMSIG(status))
1272
+ sys.exit(1)
1273
+ `;
1274
+ ANSI_PATTERN = /\u001B(?:\[[0-?]*[ -/]*[@-~]|\][^\u0007]*(?:\u0007|\u001B\\)|[@-Z\\-_])/g;
1077
1275
  }
1078
1276
  });
1079
1277
 
1080
1278
  // src/bridge/directory-picker.ts
1081
- import { spawn as spawn3 } from "child_process";
1279
+ import { spawn as spawn2 } from "child_process";
1082
1280
  async function pickDirectory(optionsOrTitle) {
1083
1281
  const options = typeof optionsOrTitle === "string" ? { title: optionsOrTitle } : optionsOrTitle || {};
1084
1282
  const title = options.title || "\u8BF7\u9009\u62E9\u5DE5\u4F5C\u7A7A\u95F4\u6587\u4EF6\u5939";
@@ -1097,7 +1295,7 @@ function pickDirectoryMac(title, defaultPath) {
1097
1295
  const defaultClause = defaultPath ? ` default location POSIX file "${defaultPath.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"` : "";
1098
1296
  const appleScript = `POSIX path of (choose folder with prompt "${safeTitle}"${defaultClause})`;
1099
1297
  return new Promise((resolve3, reject) => {
1100
- const child = spawn3("osascript", ["-e", appleScript], {
1298
+ const child = spawn2("osascript", ["-e", appleScript], {
1101
1299
  stdio: ["ignore", "pipe", "pipe"]
1102
1300
  });
1103
1301
  let stdout = "";
@@ -1215,7 +1413,7 @@ $result = [FolderPicker]::Pick("${safeTitle}", "${safeDefaultPath}")
1215
1413
  if ($result) { Write-Output $result }
1216
1414
  `;
1217
1415
  return new Promise((resolve3, reject) => {
1218
- const child = spawn3("powershell", ["-STA", "-NoProfile", "-WindowStyle", "Hidden", "-Command", psScript], {
1416
+ const child = spawn2("powershell", ["-STA", "-NoProfile", "-WindowStyle", "Hidden", "-Command", psScript], {
1219
1417
  stdio: ["ignore", "pipe", "pipe"],
1220
1418
  windowsHide: true
1221
1419
  });
@@ -1246,7 +1444,7 @@ function pickDirectoryLinux(title, defaultPath) {
1246
1444
  args.push(`--filename=${defaultPath}`);
1247
1445
  }
1248
1446
  return new Promise((resolve3, reject) => {
1249
- const child = spawn3("zenity", args, {
1447
+ const child = spawn2("zenity", args, {
1250
1448
  stdio: ["ignore", "pipe", "pipe"]
1251
1449
  });
1252
1450
  let stdout = "";
@@ -1284,6 +1482,385 @@ var init_directory_picker = __esm({
1284
1482
  }
1285
1483
  });
1286
1484
 
1485
+ // src/bridge/pty-backend.ts
1486
+ import { spawnSync as spawnSync2 } from "child_process";
1487
+ import { chmodSync, existsSync } from "fs";
1488
+ import { createRequire } from "module";
1489
+ import path4 from "path";
1490
+ import { constants } from "os";
1491
+ function interactiveArgs(shell) {
1492
+ if (process.platform !== "win32") return ["-i"];
1493
+ const base = path4.basename(shell).toLowerCase();
1494
+ if (base === "powershell.exe" || base === "pwsh.exe") return ["-NoLogo"];
1495
+ if (base === "cmd.exe") return [];
1496
+ return ["-i"];
1497
+ }
1498
+ function ptyEnvironment() {
1499
+ const env = {};
1500
+ for (const [key, value] of Object.entries(process.env)) {
1501
+ if (value !== void 0) env[key] = value;
1502
+ }
1503
+ env.TERM = "xterm-256color";
1504
+ return env;
1505
+ }
1506
+ function resolveSpawnFile(shell, pathEnv = process.env.PATH || "") {
1507
+ if (!shell || path4.isAbsolute(shell)) return shell;
1508
+ const names = process.platform === "win32" && !shell.toLowerCase().endsWith(".exe") ? [shell, `${shell}.exe`] : [shell];
1509
+ for (const dir of pathEnv.split(path4.delimiter)) {
1510
+ if (!dir) continue;
1511
+ for (const name of names) {
1512
+ const candidate = path4.join(dir, name);
1513
+ try {
1514
+ if (existsSync(candidate)) return candidate;
1515
+ } catch {
1516
+ }
1517
+ }
1518
+ }
1519
+ if (process.platform === "win32") {
1520
+ const systemRoot = process.env.SystemRoot || "C:\\Windows";
1521
+ const fallbacks = [
1522
+ path4.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe"),
1523
+ path4.join(systemRoot, "Sysnative", "WindowsPowerShell", "v1.0", "powershell.exe")
1524
+ ];
1525
+ for (const candidate of fallbacks) {
1526
+ try {
1527
+ if (existsSync(candidate)) return candidate;
1528
+ } catch {
1529
+ }
1530
+ }
1531
+ }
1532
+ return shell;
1533
+ }
1534
+ function describeSpawnHelperChmodError(helper, code) {
1535
+ if (code !== "EPERM" && code !== "EACCES") return null;
1536
+ 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`;
1537
+ }
1538
+ function loadNodePty() {
1539
+ try {
1540
+ return createRequire(import.meta.url)("node-pty");
1541
+ } catch (err) {
1542
+ const detail = err instanceof Error ? err.message : String(err);
1543
+ 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`);
1544
+ }
1545
+ }
1546
+ function spawnHelperPath() {
1547
+ try {
1548
+ const pkgJson = createRequire(import.meta.url).resolve("node-pty/package.json");
1549
+ return path4.join(path4.dirname(pkgJson), "prebuilds", `${process.platform}-${process.arch}`, "spawn-helper");
1550
+ } catch {
1551
+ return null;
1552
+ }
1553
+ }
1554
+ function ensureSpawnHelper() {
1555
+ if (process.platform === "win32") return null;
1556
+ const helper = spawnHelperPath();
1557
+ if (!helper || !existsSync(helper)) return null;
1558
+ try {
1559
+ chmodSync(helper, 493);
1560
+ return null;
1561
+ } catch (err) {
1562
+ const code = err && typeof err === "object" && "code" in err ? String(err.code) : void 0;
1563
+ return describeSpawnHelperChmodError(helper, code);
1564
+ }
1565
+ }
1566
+ function signalName(code) {
1567
+ if (!code) return null;
1568
+ for (const [name, value] of Object.entries(constants.signals)) {
1569
+ if (value === code) return name;
1570
+ }
1571
+ return String(code);
1572
+ }
1573
+ function descendantPids(rootPid) {
1574
+ const result = spawnSync2("ps", ["-ax", "-o", "pid=,ppid="], { encoding: "utf8", timeout: 3e3 });
1575
+ if (result.status !== 0 || !result.stdout) return [];
1576
+ const children = /* @__PURE__ */ new Map();
1577
+ for (const line of result.stdout.split("\n")) {
1578
+ const [pidText, ppidText] = line.trim().split(/\s+/);
1579
+ const pid = Number(pidText);
1580
+ const ppid = Number(ppidText);
1581
+ if (!pid || !ppid) continue;
1582
+ const list = children.get(ppid);
1583
+ if (list) list.push(pid);
1584
+ else children.set(ppid, [pid]);
1585
+ }
1586
+ const out = [];
1587
+ const walk = (pid) => {
1588
+ for (const child of children.get(pid) || []) {
1589
+ walk(child);
1590
+ out.push(child);
1591
+ }
1592
+ };
1593
+ walk(rootPid);
1594
+ return out;
1595
+ }
1596
+ function killPtySession(term) {
1597
+ const pid = term.pid;
1598
+ if (pid && pid > 0) {
1599
+ if (process.platform === "win32") {
1600
+ try {
1601
+ spawnSync2("taskkill", ["/pid", String(pid), "/T", "/F"], { windowsHide: true, timeout: 3e3 });
1602
+ } catch {
1603
+ }
1604
+ } else {
1605
+ for (const victim of [...descendantPids(pid), pid]) {
1606
+ try {
1607
+ process.kill(victim, "SIGKILL");
1608
+ } catch {
1609
+ }
1610
+ }
1611
+ }
1612
+ }
1613
+ try {
1614
+ term.kill("SIGHUP");
1615
+ } catch {
1616
+ }
1617
+ }
1618
+ function spawnHostPty(options) {
1619
+ const pty = loadNodePty();
1620
+ const helperError = ensureSpawnHelper();
1621
+ const shell = resolveSpawnFile(options.shell || resolveShell());
1622
+ const pending = [];
1623
+ let dataCb = null;
1624
+ let exitCb = null;
1625
+ let pendingExit = null;
1626
+ let settled = false;
1627
+ let term = null;
1628
+ const emitData = (data) => {
1629
+ if (!data) return;
1630
+ if (dataCb) dataCb(data);
1631
+ else pending.push(data);
1632
+ };
1633
+ const finish = (info) => {
1634
+ if (settled) return;
1635
+ settled = true;
1636
+ if (exitCb) exitCb(info);
1637
+ else pendingExit = info;
1638
+ };
1639
+ try {
1640
+ term = pty.spawn(shell, interactiveArgs(shell), {
1641
+ name: "xterm-256color",
1642
+ cols: options.cols,
1643
+ rows: options.rows,
1644
+ cwd: options.cwd,
1645
+ env: ptyEnvironment()
1646
+ });
1647
+ term.onData((data) => emitData(data));
1648
+ term.onExit((event) => {
1649
+ finish({ exitCode: event.exitCode, signal: signalName(event.signal) });
1650
+ });
1651
+ } catch (err) {
1652
+ const message = err instanceof Error ? err.message : String(err);
1653
+ const detail = helperError ? `${helperError} ${message}` : message;
1654
+ setImmediate(() => {
1655
+ emitData(`\r
1656
+ [\u7EC8\u7AEF\u542F\u52A8\u5931\u8D25] ${detail}\r
1657
+ `);
1658
+ finish({ exitCode: null, signal: null });
1659
+ });
1660
+ }
1661
+ return {
1662
+ write(data) {
1663
+ try {
1664
+ term?.write(data);
1665
+ } catch {
1666
+ }
1667
+ },
1668
+ resize(cols, rows) {
1669
+ try {
1670
+ term?.resize(cols, rows);
1671
+ } catch {
1672
+ }
1673
+ },
1674
+ kill() {
1675
+ if (term) killPtySession(term);
1676
+ },
1677
+ pause() {
1678
+ try {
1679
+ term?.pause();
1680
+ } catch {
1681
+ }
1682
+ },
1683
+ resume() {
1684
+ try {
1685
+ term?.resume();
1686
+ } catch {
1687
+ }
1688
+ },
1689
+ onData(cb) {
1690
+ dataCb = cb;
1691
+ for (const chunk of pending) cb(chunk);
1692
+ pending.length = 0;
1693
+ },
1694
+ onExit(cb) {
1695
+ exitCb = cb;
1696
+ if (pendingExit) cb(pendingExit);
1697
+ }
1698
+ };
1699
+ }
1700
+ var init_pty_backend = __esm({
1701
+ "src/bridge/pty-backend.ts"() {
1702
+ "use strict";
1703
+ init_bash_executor();
1704
+ }
1705
+ });
1706
+
1707
+ // src/bridge/terminal-session.ts
1708
+ import { randomUUID } from "crypto";
1709
+ function applyTerminalBackpressure(bufferedAmount, flow) {
1710
+ if (bufferedAmount > TERMINAL_OUTPUT_HIGH_WATER) flow.pauseOutput();
1711
+ else if (bufferedAmount <= TERMINAL_OUTPUT_HIGH_WATER / 2) flow.resumeOutput();
1712
+ }
1713
+ function clampDimension(value, fallback) {
1714
+ const parsed = typeof value === "number" ? value : Number(value);
1715
+ if (!Number.isFinite(parsed)) return fallback;
1716
+ return Math.max(2, Math.min(500, Math.floor(parsed)));
1717
+ }
1718
+ var OUTPUT_CHUNK, MAX_INPUT, DEFAULT_COALESCE_MS, TERMINAL_OUTPUT_HIGH_WATER, TerminalSessionManager;
1719
+ var init_terminal_session = __esm({
1720
+ "src/bridge/terminal-session.ts"() {
1721
+ "use strict";
1722
+ init_workspace_manager();
1723
+ init_pty_backend();
1724
+ init_protocol();
1725
+ OUTPUT_CHUNK = 32 * 1024;
1726
+ MAX_INPUT = 64 * 1024;
1727
+ DEFAULT_COALESCE_MS = 16;
1728
+ TERMINAL_OUTPUT_HIGH_WATER = 1024 * 1024;
1729
+ TerminalSessionManager = class {
1730
+ session = null;
1731
+ buffer = "";
1732
+ timer = null;
1733
+ send;
1734
+ spawnPty;
1735
+ coalesceMs;
1736
+ constructor(options) {
1737
+ this.send = options.send;
1738
+ this.spawnPty = options.spawnPty ?? ((opts) => spawnHostPty(opts));
1739
+ this.coalesceMs = options.coalesceMs ?? DEFAULT_COALESCE_MS;
1740
+ }
1741
+ open(params = {}) {
1742
+ const cwd = resolveWorkspaceDir(params);
1743
+ const cols = clampDimension(params.cols, 80);
1744
+ const rows = clampDimension(params.rows, 24);
1745
+ this.closeActive();
1746
+ const id = randomUUID();
1747
+ const pty = this.spawnPty({ cwd, cols, rows });
1748
+ this.session = { id, pty, cwd };
1749
+ pty.onData((data) => this.pushOutput(id, data));
1750
+ pty.onExit((info) => {
1751
+ if (this.session?.id !== id) return;
1752
+ this.flush(id);
1753
+ this.session = null;
1754
+ this.send({
1755
+ jsonrpc: "2.0",
1756
+ method: TERMINAL_EVENTS.EXIT,
1757
+ params: {
1758
+ sessionId: id,
1759
+ exitCode: info.exitCode,
1760
+ signal: info.signal
1761
+ }
1762
+ });
1763
+ });
1764
+ return { sessionId: id, cwd };
1765
+ }
1766
+ input(params) {
1767
+ const session = this.requireSession(params.sessionId);
1768
+ if (typeof params.data !== "string") {
1769
+ throw new BashInvalidParamsError("data \u5FC5\u987B\u662F\u5B57\u7B26\u4E32");
1770
+ }
1771
+ if (params.data.length > MAX_INPUT) {
1772
+ throw new BashInvalidParamsError("\u5355\u6B21\u8F93\u5165\u8FC7\u957F");
1773
+ }
1774
+ session.pty.write(params.data);
1775
+ return { ok: true };
1776
+ }
1777
+ resize(params) {
1778
+ const session = this.requireSession(params.sessionId);
1779
+ session.pty.resize(clampDimension(params.cols, 80), clampDimension(params.rows, 24));
1780
+ return { ok: true };
1781
+ }
1782
+ close(params = {}) {
1783
+ if (!this.session || params.sessionId && params.sessionId !== this.session.id) {
1784
+ return { ok: true };
1785
+ }
1786
+ this.closeActive();
1787
+ return { ok: true };
1788
+ }
1789
+ disposeAll() {
1790
+ this.closeActive();
1791
+ }
1792
+ pauseOutput() {
1793
+ try {
1794
+ this.session?.pty.pause();
1795
+ } catch {
1796
+ }
1797
+ }
1798
+ resumeOutput() {
1799
+ try {
1800
+ this.session?.pty.resume();
1801
+ } catch {
1802
+ }
1803
+ }
1804
+ requireSession(sessionId) {
1805
+ if (!this.session || !sessionId || sessionId !== this.session.id) {
1806
+ throw new Error("\u7EC8\u7AEF\u4F1A\u8BDD\u4E0D\u5B58\u5728");
1807
+ }
1808
+ return this.session;
1809
+ }
1810
+ closeActive() {
1811
+ const current = this.session;
1812
+ this.session = null;
1813
+ this.buffer = "";
1814
+ if (this.timer) {
1815
+ clearTimeout(this.timer);
1816
+ this.timer = null;
1817
+ }
1818
+ current?.pty.kill();
1819
+ }
1820
+ pushOutput(sessionId, chunk) {
1821
+ if (this.session?.id !== sessionId || !chunk) return;
1822
+ this.buffer += chunk;
1823
+ this.flushFullChunks(sessionId);
1824
+ if (!this.buffer) return;
1825
+ if (this.coalesceMs <= 0) {
1826
+ this.flush(sessionId);
1827
+ return;
1828
+ }
1829
+ if (!this.timer) {
1830
+ this.timer = setTimeout(() => this.flush(sessionId), this.coalesceMs);
1831
+ }
1832
+ }
1833
+ flushFullChunks(sessionId) {
1834
+ while (this.buffer.length >= OUTPUT_CHUNK) {
1835
+ const data = this.buffer.slice(0, OUTPUT_CHUNK);
1836
+ this.buffer = this.buffer.slice(OUTPUT_CHUNK);
1837
+ this.emitOutput(sessionId, data);
1838
+ }
1839
+ }
1840
+ flush(sessionId) {
1841
+ if (this.timer) {
1842
+ clearTimeout(this.timer);
1843
+ this.timer = null;
1844
+ }
1845
+ if (!this.buffer || this.session?.id !== sessionId) {
1846
+ this.buffer = "";
1847
+ return;
1848
+ }
1849
+ const data = this.buffer;
1850
+ this.buffer = "";
1851
+ this.emitOutput(sessionId, data);
1852
+ }
1853
+ emitOutput(sessionId, data) {
1854
+ this.send({
1855
+ jsonrpc: "2.0",
1856
+ method: TERMINAL_EVENTS.OUTPUT,
1857
+ params: { sessionId, data }
1858
+ });
1859
+ }
1860
+ };
1861
+ }
1862
+ });
1863
+
1287
1864
  // src/bridge/bridge-client.ts
1288
1865
  var bridge_client_exports = {};
1289
1866
  __export(bridge_client_exports, {
@@ -1298,10 +1875,14 @@ __export(bridge_client_exports, {
1298
1875
  });
1299
1876
  import { WebSocketServer, WebSocket as WsClient } from "ws";
1300
1877
  import { EventEmitter } from "events";
1301
- import { randomUUID, randomBytes, createHmac, createHash, timingSafeEqual } from "crypto";
1878
+ import { randomUUID as randomUUID2, randomBytes, createHmac, createHash, timingSafeEqual } from "crypto";
1302
1879
  import fs4 from "fs";
1303
- import path4 from "path";
1880
+ import path5 from "path";
1304
1881
  import os3 from "os";
1882
+ function isJsonRpcId(id) {
1883
+ if (id === null || typeof id === "string") return true;
1884
+ return typeof id === "number" && Number.isFinite(id);
1885
+ }
1305
1886
  function resolveOrCreateAuthToken(explicitToken) {
1306
1887
  if (explicitToken && explicitToken.trim()) {
1307
1888
  const token = explicitToken.trim();
@@ -1367,12 +1948,13 @@ var init_bridge_client = __esm({
1367
1948
  init_protocol();
1368
1949
  init_bash_executor();
1369
1950
  init_directory_picker();
1951
+ init_terminal_session();
1370
1952
  WS_PORT = DEFAULT_WS_PORT;
1371
1953
  REQUEST_TIMEOUT_MS = 3e4;
1372
1954
  HANDSHAKE_TIMEOUT_MS = 5e3;
1373
1955
  WS_OPEN = 1;
1374
- TOKEN_FILE_DIR = path4.join(os3.homedir(), ".robot-wxt");
1375
- TOKEN_FILE_PATH = path4.join(TOKEN_FILE_DIR, "bridge-token");
1956
+ TOKEN_FILE_DIR = path5.join(os3.homedir(), ".robot-wxt");
1957
+ TOKEN_FILE_PATH = path5.join(TOKEN_FILE_DIR, "bridge-token");
1376
1958
  ExtensionBridgeClient = class _ExtensionBridgeClient extends EventEmitter {
1377
1959
  constructor(port = WS_PORT, authToken) {
1378
1960
  super();
@@ -1388,6 +1970,7 @@ var init_bridge_client = __esm({
1388
1970
  pending = /* @__PURE__ */ new Map();
1389
1971
  socketAuthStates = /* @__PURE__ */ new Map();
1390
1972
  destroyed = false;
1973
+ terminalSessions = null;
1391
1974
  idCounter = 0;
1392
1975
  isClientMode = false;
1393
1976
  explicitAuthToken;
@@ -1642,7 +2225,7 @@ var init_bridge_client = __esm({
1642
2225
  this.emit("error", err);
1643
2226
  });
1644
2227
  this.wss.on("connection", (socket) => {
1645
- const challenge = randomUUID();
2228
+ const challenge = randomUUID2();
1646
2229
  const hmacSalt = randomBytes(16).toString("hex");
1647
2230
  const authState = {
1648
2231
  authenticated: false,
@@ -1684,6 +2267,7 @@ var init_bridge_client = __esm({
1684
2267
  }
1685
2268
  if (socket === this.extensionSocket) {
1686
2269
  this.extensionSocket = null;
2270
+ this.terminalSessions?.disposeAll();
1687
2271
  this.emit("disconnected");
1688
2272
  for (const [reqId, fwdInfo] of this.forwardedRequests.entries()) {
1689
2273
  if (fwdInfo.timer) clearTimeout(fwdInfo.timer);
@@ -1730,6 +2314,16 @@ var init_bridge_client = __esm({
1730
2314
  return;
1731
2315
  }
1732
2316
  if (msg.method && msg.id) {
2317
+ if (typeof msg.method === "string" && isTerminalMethod(msg.method) && socket !== this.extensionSocket) {
2318
+ this.sendJsonRpcResponse(socket, {
2319
+ id: msg.id,
2320
+ error: {
2321
+ code: BRIDGE_ERROR_CODE.METHOD_NOT_FOUND,
2322
+ message: "\u7EC8\u7AEF\u4F1A\u8BDD\u4EC5\u5141\u8BB8\u5DF2\u8BA4\u8BC1\u7684\u6269\u5C55\u8FDE\u63A5"
2323
+ }
2324
+ });
2325
+ return;
2326
+ }
1733
2327
  if (socket === this.extensionSocket) {
1734
2328
  await this.handleExtensionSystemRpc(socket, msg);
1735
2329
  return;
@@ -1802,6 +2396,7 @@ var init_bridge_client = __esm({
1802
2396
  currentAuth.clientType = msg.clientType;
1803
2397
  if (msg.clientType === "extension") {
1804
2398
  if (this.extensionSocket && this.extensionSocket !== socket) {
2399
+ this.terminalSessions?.disposeAll();
1805
2400
  try {
1806
2401
  this.extensionSocket.close(WS_CLOSE_CODE.REPLACED, "Replaced by new connection");
1807
2402
  } catch {
@@ -1893,9 +2488,6 @@ var init_bridge_client = __esm({
1893
2488
  systemRpcHandlers = Object.assign(
1894
2489
  /* @__PURE__ */ Object.create(null),
1895
2490
  {
1896
- [BRIDGE_METHODS.SYSTEM_BASH]: async (params) => {
1897
- return executeBash(params || {});
1898
- },
1899
2491
  [BRIDGE_METHODS.SYSTEM_PICK_DIRECTORY]: async (params) => {
1900
2492
  const selectedPath = await pickDirectory({
1901
2493
  title: params?.title || void 0,
@@ -1906,11 +2498,61 @@ var init_bridge_client = __esm({
1906
2498
  selectedPath,
1907
2499
  canceled: selectedPath === null
1908
2500
  };
1909
- }
2501
+ },
2502
+ [BRIDGE_METHODS.TERMINAL_OPEN]: async (params) => this.getTerminalSessions().open(params || {}),
2503
+ [BRIDGE_METHODS.TERMINAL_INPUT]: async (params) => this.getTerminalSessions().input(params || {}),
2504
+ [BRIDGE_METHODS.TERMINAL_RESIZE]: async (params) => this.getTerminalSessions().resize(params || {}),
2505
+ [BRIDGE_METHODS.TERMINAL_CLOSE]: async (params) => this.getTerminalSessions().close(params || {})
1910
2506
  }
1911
2507
  );
2508
+ sendJsonRpcNotification(socket, method, params) {
2509
+ if (socket.readyState !== WS_OPEN) return;
2510
+ let payload;
2511
+ try {
2512
+ payload = JSON.stringify({ jsonrpc: "2.0", method, params });
2513
+ } catch {
2514
+ return;
2515
+ }
2516
+ try {
2517
+ socket.send(payload);
2518
+ } catch {
2519
+ }
2520
+ }
2521
+ async executeHostBash(socket, id, params) {
2522
+ try {
2523
+ const requestParams = params && typeof params === "object" ? params : {};
2524
+ const result = await executeBash({
2525
+ ...requestParams,
2526
+ onOutput: (chunk) => {
2527
+ if (!chunk.text) return;
2528
+ this.sendJsonRpcNotification(socket, BASH_EVENTS.OUTPUT, {
2529
+ id,
2530
+ stream: chunk.stream,
2531
+ text: chunk.text
2532
+ });
2533
+ }
2534
+ });
2535
+ this.sendJsonRpcResponse(socket, { id, result });
2536
+ } catch (err) {
2537
+ this.sendJsonRpcResponse(socket, { id, error: this.formatSystemRpcError(err) });
2538
+ }
2539
+ }
1912
2540
  async handleExtensionSystemRpc(socket, msg) {
1913
2541
  const { id, method, params } = msg;
2542
+ if (!isJsonRpcId(id)) {
2543
+ this.sendJsonRpcResponse(socket, {
2544
+ id: null,
2545
+ error: {
2546
+ code: BRIDGE_ERROR_CODE.INVALID_PARAMS,
2547
+ message: "\u65E0\u6548\u7684\u8BF7\u6C42 id"
2548
+ }
2549
+ });
2550
+ return;
2551
+ }
2552
+ if (method === BRIDGE_METHODS.SYSTEM_BASH) {
2553
+ await this.executeHostBash(socket, id, params);
2554
+ return;
2555
+ }
1914
2556
  const handler = typeof method === "string" && Object.prototype.hasOwnProperty.call(this.systemRpcHandlers, method) ? this.systemRpcHandlers[method] : void 0;
1915
2557
  if (!handler || typeof handler !== "function") {
1916
2558
  this.sendJsonRpcResponse(socket, {
@@ -2006,9 +2648,37 @@ var init_bridge_client = __esm({
2006
2648
  }
2007
2649
  });
2008
2650
  }
2651
+ getTerminalSessions() {
2652
+ if (!this.terminalSessions) {
2653
+ this.terminalSessions = new TerminalSessionManager({
2654
+ send: (frame) => {
2655
+ const socket = this.extensionSocket;
2656
+ if (!socket || socket.readyState !== WS_OPEN) return;
2657
+ const sessions = this.terminalSessions;
2658
+ if (sessions) applyTerminalBackpressure(socket.bufferedAmount ?? 0, sessions);
2659
+ let payload;
2660
+ try {
2661
+ payload = JSON.stringify(frame);
2662
+ } catch {
2663
+ return;
2664
+ }
2665
+ try {
2666
+ socket.send(payload, () => {
2667
+ if (this.terminalSessions && socket.readyState === WS_OPEN) {
2668
+ applyTerminalBackpressure(socket.bufferedAmount ?? 0, this.terminalSessions);
2669
+ }
2670
+ });
2671
+ } catch {
2672
+ }
2673
+ }
2674
+ });
2675
+ }
2676
+ return this.terminalSessions;
2677
+ }
2009
2678
  /** 销毁服务,断开所有连接 */
2010
2679
  destroy() {
2011
2680
  this.destroyed = true;
2681
+ this.terminalSessions?.disposeAll();
2012
2682
  this._rejectAllPending("ExtensionBridgeClient \u5DF2\u9500\u6BC1");
2013
2683
  this.forwardedRequests.clear();
2014
2684
  if (this.clientSocket) {
@@ -2153,22 +2823,22 @@ __export(daemon_exports, {
2153
2823
  isDaemonProcessAlive: () => isDaemonProcessAlive,
2154
2824
  isProcessAlive: () => isProcessAlive,
2155
2825
  killProcessOnPort: () => killProcessOnPort,
2156
- killProcessTree: () => killProcessTree2,
2826
+ killProcessTree: () => killProcessTree,
2157
2827
  probeServerReady: () => probeServerReady,
2158
2828
  runDaemonProcess: () => runDaemonProcess,
2159
2829
  stopBridgeDaemon: () => stopBridgeDaemon
2160
2830
  });
2161
- import { spawn as spawn4, exec } from "child_process";
2831
+ import { spawn as spawn3, exec } from "child_process";
2162
2832
  import { promisify } from "util";
2163
2833
  import fs5 from "fs";
2164
- import path5 from "path";
2834
+ import path6 from "path";
2165
2835
  import os4 from "os";
2166
2836
  import { fileURLToPath as fileURLToPath3 } from "url";
2167
2837
  function getDaemonPidFile(port = WS_PORT2) {
2168
2838
  if (port === WS_PORT2) {
2169
- return path5.join(DAEMON_DIR, "daemon.pid");
2839
+ return path6.join(DAEMON_DIR, "daemon.pid");
2170
2840
  }
2171
- return path5.join(DAEMON_DIR, `daemon-${port}.pid`);
2841
+ return path6.join(DAEMON_DIR, `daemon-${port}.pid`);
2172
2842
  }
2173
2843
  function isProcessAlive(pid) {
2174
2844
  if (!pid || pid <= 0) return false;
@@ -2212,7 +2882,7 @@ async function findPidByPort(port, excludePid) {
2212
2882
  return null;
2213
2883
  }
2214
2884
  }
2215
- async function killProcessTree2(pid) {
2885
+ async function killProcessTree(pid) {
2216
2886
  if (!pid || pid <= 0 || pid === process.pid) return false;
2217
2887
  if (process.platform === "win32") {
2218
2888
  try {
@@ -2250,7 +2920,7 @@ async function killProcessTree2(pid) {
2250
2920
  async function killProcessOnPort(port) {
2251
2921
  const pid = await findPidByPort(port, process.pid);
2252
2922
  if (pid) {
2253
- await killProcessTree2(pid);
2923
+ await killProcessTree(pid);
2254
2924
  for (let i = 0; i < 20; i++) {
2255
2925
  await new Promise((r) => setTimeout(r, 100));
2256
2926
  const remainingPid = await findPidByPort(port, process.pid);
@@ -2314,10 +2984,10 @@ async function ensureBridgeDaemon(options = {}) {
2314
2984
  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`);
2315
2985
  }
2316
2986
  function resolveCliEntry() {
2317
- const currentDir = path5.dirname(fileURLToPath3(import.meta.url));
2318
- const distCandidate = path5.join(currentDir, "bin.js");
2987
+ const currentDir = path6.dirname(fileURLToPath3(import.meta.url));
2988
+ const distCandidate = path6.join(currentDir, "bin.js");
2319
2989
  if (fs5.existsSync(distCandidate)) return distCandidate;
2320
- const projectDistCandidate = path5.resolve(currentDir, "../dist/bin.js");
2990
+ const projectDistCandidate = path6.resolve(currentDir, "../dist/bin.js");
2321
2991
  if (fs5.existsSync(projectDistCandidate)) return projectDistCandidate;
2322
2992
  return distCandidate;
2323
2993
  }
@@ -2326,7 +2996,7 @@ function startDaemonProcessBackground(token, port) {
2326
2996
  if (!fs5.existsSync(DAEMON_DIR)) {
2327
2997
  fs5.mkdirSync(DAEMON_DIR, { mode: 448, recursive: true });
2328
2998
  }
2329
- const child = spawn4(process.execPath, [cliEntry, "--mode", "wxt", "daemon", "--port", String(port)], {
2999
+ const child = spawn3(process.execPath, [cliEntry, "--mode", "wxt", "daemon", "--port", String(port)], {
2330
3000
  detached: true,
2331
3001
  stdio: "ignore",
2332
3002
  windowsHide: true,
@@ -2432,7 +3102,7 @@ async function stopBridgeDaemon(port = WS_PORT2, options = {}) {
2432
3102
  if (portPid && portPid !== daemonPid) {
2433
3103
  return false;
2434
3104
  }
2435
- const killed = await killProcessTree2(daemonPid);
3105
+ const killed = await killProcessTree(daemonPid);
2436
3106
  if (killed) {
2437
3107
  killedAny = true;
2438
3108
  try {
@@ -2443,7 +3113,7 @@ async function stopBridgeDaemon(port = WS_PORT2, options = {}) {
2443
3113
  } else {
2444
3114
  if (daemonPid) {
2445
3115
  try {
2446
- const killed = await killProcessTree2(daemonPid);
3116
+ const killed = await killProcessTree(daemonPid);
2447
3117
  if (killed) {
2448
3118
  killedAny = true;
2449
3119
  try {
@@ -2483,7 +3153,7 @@ var init_daemon = __esm({
2483
3153
  init_bridge_client();
2484
3154
  execAsync = promisify(exec);
2485
3155
  WS_PORT2 = 18999;
2486
- DAEMON_DIR = path5.join(os4.homedir(), ".robot-wxt");
3156
+ DAEMON_DIR = path6.join(os4.homedir(), ".robot-wxt");
2487
3157
  PID_FILE = getDaemonPidFile(WS_PORT2);
2488
3158
  }
2489
3159
  });
@@ -2945,7 +3615,7 @@ var init_skills = __esm({
2945
3615
 
2946
3616
  // src/bin.ts
2947
3617
  import fs6 from "fs";
2948
- import path6 from "path";
3618
+ import path7 from "path";
2949
3619
  import os5 from "os";
2950
3620
  import { Command } from "commander";
2951
3621
  import pc2 from "picocolors";
@@ -3434,7 +4104,7 @@ function tryParseBrowserStateText(text) {
3434
4104
  }
3435
4105
  function cleanOldLogs(baseDir, logDir) {
3436
4106
  try {
3437
- const oldLogFile = path6.join(baseDir, "webmcp-cli.log");
4107
+ const oldLogFile = path7.join(baseDir, "webmcp-cli.log");
3438
4108
  if (fs6.existsSync(oldLogFile)) {
3439
4109
  try {
3440
4110
  fs6.unlinkSync(oldLogFile);
@@ -3446,7 +4116,7 @@ function cleanOldLogs(baseDir, logDir) {
3446
4116
  for (const file of baseFiles) {
3447
4117
  if (/^webmcp-cli-\d{4}-\d{2}-\d{2}\.log$/.test(file)) {
3448
4118
  try {
3449
- fs6.unlinkSync(path6.join(baseDir, file));
4119
+ fs6.unlinkSync(path7.join(baseDir, file));
3450
4120
  } catch {
3451
4121
  }
3452
4122
  }
@@ -3465,7 +4135,7 @@ function cleanOldLogs(baseDir, logDir) {
3465
4135
  if (isNaN(fileDate)) continue;
3466
4136
  if (now - fileDate > maxAgeMs) {
3467
4137
  try {
3468
- fs6.unlinkSync(path6.join(logDir, file));
4138
+ fs6.unlinkSync(path7.join(logDir, file));
3469
4139
  } catch {
3470
4140
  }
3471
4141
  }
@@ -3521,13 +4191,13 @@ ${searchResultStr}`;
3521
4191
  }
3522
4192
  function writeLog(commandName, args, result, error) {
3523
4193
  try {
3524
- const baseDir = process.env.WEBMCP_WORKSPACE || path6.join(os5.homedir(), ".webmcp_chrome_profile");
3525
- const logDir = path6.join(baseDir, "logs");
4194
+ const baseDir = process.env.WEBMCP_WORKSPACE || path7.join(os5.homedir(), ".webmcp_chrome_profile");
4195
+ const logDir = path7.join(baseDir, "logs");
3526
4196
  if (!fs6.existsSync(logDir)) {
3527
4197
  fs6.mkdirSync(logDir, { recursive: true });
3528
4198
  }
3529
4199
  const todayStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
3530
- const logFile = path6.join(logDir, `webmcp-cli-${todayStr}.log`);
4200
+ const logFile = path7.join(logDir, `webmcp-cli-${todayStr}.log`);
3531
4201
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
3532
4202
  let logText = `========================================
3533
4203
  `;