vite-plugin-aipanel 1.2.8 → 1.2.9

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.
@@ -1,5 +1,12 @@
1
+ /** chrome-devtools-mcp 的核心受保护参数(用户不可覆盖;始终最后注入确保生效) */
2
+ export declare const CORE_MCP_ARGS: readonly ["--auto-connect", "--no-usage-statistics", "--no-performance-crux", "--no-page-id-routing"];
3
+ /** 过滤用户透传参数:剔除与核心受保护 flag 冲突的项,其余原样保留 */
4
+ export declare function filterUserMcpArgs(userArgs: readonly string[]): string[];
1
5
  export interface McpProxyOptions {
2
- args?: string[];
6
+ /** 用户透传的额外 CLI 参数(追加;与核心受保护项冲突的会被剔除) */
7
+ userArgs?: string[];
8
+ /** 用户透传的额外环境变量(合并到 process.env 之上) */
9
+ env?: Record<string, string>;
3
10
  idleTimeout?: number;
4
11
  }
5
12
  export declare class McpProxy {
@@ -17,13 +17,41 @@ var __privateWrapper = (obj, member, setter, getter) => ({
17
17
  return __privateGet(obj, member, getter);
18
18
  }
19
19
  });
20
- var _proc, _rl, _messageId, _internalIdBase, _pending, _args, _startPromise, _lastExit, _stderrTail, _idleTimer, _idleTimeout, _McpProxy_instances, doStart_fn, formatNotAvailable_fn, resetIdleTimer_fn;
20
+ var _proc, _rl, _messageId, _internalIdBase, _pending, _args, _env, _startPromise, _lastExit, _stderrTail, _idleTimer, _idleTimeout, _McpProxy_instances, doStart_fn, formatNotAvailable_fn, resetIdleTimer_fn;
21
21
  import { spawn } from "node:child_process";
22
22
  import { createInterface } from "node:readline";
23
23
  import crypto from "node:crypto";
24
24
  import path from "node:path";
25
25
  import { createLogger, createPackageRequire, resolvePackageDir } from "@aipanel/core/node";
26
26
  const log = createLogger("McpProxy");
27
+ const CORE_MCP_ARGS = [
28
+ "--auto-connect",
29
+ "--no-usage-statistics",
30
+ "--no-performance-crux",
31
+ // chrome-devtools-mcp >=1.8.0 默认开启 pageIdRouting(要求每个页面级工具都传 pageId)。
32
+ // 代理层已自行校验 pageId 并用 select_page 选中目标页面后再转发,故显式关闭,
33
+ // 避免底层工具 schema 强制必填 pageId 导致转发时的参数校验失败。
34
+ "--no-page-id-routing"
35
+ ];
36
+ const PROTECTED_FLAGS = /* @__PURE__ */ new Set([
37
+ "auto-connect",
38
+ "usage-statistics",
39
+ "performance-crux",
40
+ "page-id-routing"
41
+ ]);
42
+ function canonicalFlagName(arg) {
43
+ let flag = arg.trim();
44
+ if (!flag.startsWith("-")) return null;
45
+ flag = flag.replace(/^--?/, "").split("=")[0] ?? "";
46
+ if (flag.startsWith("no-")) flag = flag.slice(3);
47
+ return flag || null;
48
+ }
49
+ function filterUserMcpArgs(userArgs) {
50
+ return userArgs.filter((arg) => {
51
+ const name = canonicalFlagName(arg);
52
+ return name === null || !PROTECTED_FLAGS.has(name);
53
+ });
54
+ }
27
55
  function resolveChromeDevToolsMcpBin() {
28
56
  const pluginDir = resolvePackageDir("vite-plugin-aipanel");
29
57
  const require2 = createPackageRequire(pluginDir);
@@ -43,6 +71,7 @@ class McpProxy {
43
71
  __privateAdd(this, _internalIdBase, 1e6);
44
72
  __privateAdd(this, _pending, /* @__PURE__ */ new Map());
45
73
  __privateAdd(this, _args);
74
+ __privateAdd(this, _env);
46
75
  __privateAdd(this, _startPromise, null);
47
76
  /** 最近一次进程退出信息,用于生成精确错误(无退出记录时为 null) */
48
77
  __privateAdd(this, _lastExit, null);
@@ -51,15 +80,14 @@ class McpProxy {
51
80
  __privateAdd(this, _idleTimer, null);
52
81
  __privateAdd(this, _idleTimeout);
53
82
  __publicField(this, "sessionId");
54
- __privateSet(this, _args, options.args ?? [
55
- "--auto-connect",
56
- "--no-usage-statistics",
57
- "--no-performance-crux",
58
- // chrome-devtools-mcp >=1.8.0 默认开启 pageIdRouting(要求每个页面级工具都传 pageId)。
59
- // 代理层已自行校验 pageId 并用 select_page 选中目标页面后再转发,故显式关闭,
60
- // 避免底层工具 schema 强制必填 pageId 导致转发时的参数校验失败。
61
- "--no-page-id-routing"
62
- ]);
83
+ const userArgs = options.userArgs ?? [];
84
+ const filtered = filterUserMcpArgs(userArgs);
85
+ if (filtered.length !== userArgs.length) {
86
+ const dropped = userArgs.filter((a) => !filtered.includes(a));
87
+ log.warn("chrome MCP \u7528\u6237\u53C2\u6570\u4E0E\u6838\u5FC3\u53D7\u4FDD\u62A4\u9879\u51B2\u7A81\uFF0C\u5DF2\u5FFD\u7565", { dropped });
88
+ }
89
+ __privateSet(this, _args, [...filtered, ...CORE_MCP_ARGS]);
90
+ __privateSet(this, _env, options.env);
63
91
  __privateSet(this, _idleTimeout, options.idleTimeout ?? 0);
64
92
  this.sessionId = crypto.randomUUID();
65
93
  }
@@ -153,6 +181,7 @@ _messageId = new WeakMap();
153
181
  _internalIdBase = new WeakMap();
154
182
  _pending = new WeakMap();
155
183
  _args = new WeakMap();
184
+ _env = new WeakMap();
156
185
  _startPromise = new WeakMap();
157
186
  _lastExit = new WeakMap();
158
187
  _stderrTail = new WeakMap();
@@ -166,7 +195,8 @@ doStart_fn = async function() {
166
195
  __privateSet(this, _lastExit, null);
167
196
  __privateSet(this, _stderrTail, []);
168
197
  __privateSet(this, _proc, spawn(process.execPath, [binPath, ...__privateGet(this, _args)], {
169
- stdio: ["pipe", "pipe", "pipe"]
198
+ stdio: ["pipe", "pipe", "pipe"],
199
+ env: { ...process.env, ...__privateGet(this, _env) }
170
200
  }));
171
201
  log.debug("Using local chrome-devtools-mcp");
172
202
  } catch {
@@ -255,5 +285,7 @@ resetIdleTimer_fn = function() {
255
285
  }, __privateGet(this, _idleTimeout)));
256
286
  };
257
287
  export {
258
- McpProxy
288
+ CORE_MCP_ARGS,
289
+ McpProxy,
290
+ filterUserMcpArgs
259
291
  };
@@ -1,5 +1,6 @@
1
1
  import http from "http";
2
- import { createLogger, BRIDGE_SCRIPT_PATH } from "@aipanel/core";
2
+ import { BRIDGE_SCRIPT_PATH } from "@aipanel/core";
3
+ import { createLogger } from "@aipanel/core/node";
3
4
  const log = createLogger("ProxyServer");
4
5
  function startProxyServer(targetUrl, port, options = {}) {
5
6
  return new Promise((resolve, reject) => {
@@ -53,18 +54,20 @@ function startProxyServer(targetUrl, port, options = {}) {
53
54
  });
54
55
  proxyRes.on("end", () => {
55
56
  let body = Buffer.concat(chunks).toString("utf-8");
56
- if (body.match(/<\/head>/i)) {
57
- body = body.replace(
58
- /<\/head>/i,
59
- `<script src="${BRIDGE_SCRIPT_PATH}"></script></head>`
60
- );
61
- } else if (body.match(/<\/body>/i)) {
62
- body = body.replace(
63
- /<\/body>/i,
64
- `<script src="${BRIDGE_SCRIPT_PATH}"></script></body>`
65
- );
66
- } else {
67
- body += `<script src="${BRIDGE_SCRIPT_PATH}"></script>`;
57
+ if (bridgeScript.trim().length > 0) {
58
+ if (body.match(/<\/head>/i)) {
59
+ body = body.replace(
60
+ /<\/head>/i,
61
+ `<script src="${BRIDGE_SCRIPT_PATH}"></script></head>`
62
+ );
63
+ } else if (body.match(/<\/body>/i)) {
64
+ body = body.replace(
65
+ /<\/body>/i,
66
+ `<script src="${BRIDGE_SCRIPT_PATH}"></script></body>`
67
+ );
68
+ } else {
69
+ body += `<script src="${BRIDGE_SCRIPT_PATH}"></script>`;
70
+ }
68
71
  }
69
72
  const headers = {};
70
73
  for (const [key, value] of Object.entries(proxyRes.headers)) {
package/es/index.mjs CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  CONTEXT_API_PATH,
6
6
  DEFAULT_PROXY_PORT,
7
7
  MCP_API_PATH,
8
+ SESSION_ID_KEY,
8
9
  resolvePluginConfig,
9
10
  setVerbose
10
11
  } from "@aipanel/core";
@@ -35,7 +36,7 @@ const SILENT_CONTEXT_SCRIPT = `<script>
35
36
  if (!force && key === last) return;
36
37
  last = key;
37
38
  var sessionId = "";
38
- try { sessionId = sessionStorage.getItem("_aipanel_pk") || ""; } catch (e) {}
39
+ try { sessionId = sessionStorage.getItem("${SESSION_ID_KEY}") || ""; } catch (e) {}
39
40
  fetch(API, {
40
41
  method: "POST",
41
42
  headers: { "Content-Type": "application/json" },
@@ -91,7 +92,12 @@ function createAIPanelPlugin(options = {}) {
91
92
  let activeTabId = DEFAULT_TAB;
92
93
  const serviceInstanceId = crypto.randomUUID();
93
94
  const sseClients = /* @__PURE__ */ new Set();
94
- const mcpProxy = new McpProxy({ idleTimeout: 5 * 60 * 1e3 });
95
+ const mcpProxy = new McpProxy({
96
+ idleTimeout: 5 * 60 * 1e3,
97
+ // 用户 chrome MCP 透传:追加 args/env(核心受保护参数不可覆盖,由 McpProxy 过滤)
98
+ userArgs: config.chromeMcp?.args,
99
+ env: config.chromeMcp?.env
100
+ });
95
101
  let provider = null;
96
102
  const service = new AIPanelService(
97
103
  config,
@@ -307,7 +313,7 @@ function createAIPanelPlugin(options = {}) {
307
313
  ];
308
314
  const titleInject = `<script>
309
315
  (function () {
310
- var KEY = "_aipanel_pk";
316
+ var KEY = "${SESSION_ID_KEY}";
311
317
  if (!sessionStorage.getItem(KEY)) {
312
318
  sessionStorage.setItem(KEY, "[" + Math.random().toString(36).slice(2, 10) + "]");
313
319
  }
@@ -2,20 +2,27 @@
2
2
  import fs from "fs";
3
3
  import http from "http";
4
4
  import path from "path";
5
+ import {
6
+ DEFAULT_HOSTNAME,
7
+ ENV_VSCODE_PORT,
8
+ VSCODE_EXTENSION_PORT,
9
+ VSCODE_ROUTE_FORMAT,
10
+ VSCODE_ROUTE_HEALTH
11
+ } from "@aipanel/core";
5
12
  const filePath = process.argv[2];
6
13
  if (!filePath) {
7
14
  console.error("Usage: node format-bridge.cjs <filePath>");
8
15
  process.exit(1);
9
16
  }
10
17
  const absPath = path.resolve(filePath);
11
- const PORT = parseInt(process.env.AIPANEL_VSCODE_PORT || "", 10) || 51939;
18
+ const PORT = parseInt(process.env[ENV_VSCODE_PORT] || "", 10) || VSCODE_EXTENSION_PORT;
12
19
  function touchFile() {
13
20
  const now = /* @__PURE__ */ new Date();
14
21
  fs.utimesSync(absPath, now, now);
15
22
  }
16
23
  function probeHealth() {
17
24
  return new Promise((resolve) => {
18
- const req = http.get(`http://127.0.0.1:${PORT}/health`, (res) => {
25
+ const req = http.get(`http://${DEFAULT_HOSTNAME}:${PORT}${VSCODE_ROUTE_HEALTH}`, (res) => {
19
26
  res.resume();
20
27
  resolve(res.statusCode === 200);
21
28
  });
@@ -31,9 +38,9 @@ function formatViaExtension() {
31
38
  const body = JSON.stringify({ filePath: absPath });
32
39
  const req = http.request(
33
40
  {
34
- hostname: "127.0.0.1",
41
+ hostname: DEFAULT_HOSTNAME,
35
42
  port: PORT,
36
- path: "/format",
43
+ path: VSCODE_ROUTE_FORMAT,
37
44
  method: "POST",
38
45
  headers: { "Content-Type": "application/json" },
39
46
  timeout: 5e3