vite-plugin-aipanel 1.2.9 → 1.2.11

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.
Files changed (58) hide show
  1. package/README.md +53 -35
  2. package/es/core/chrome-project.d.ts +15 -0
  3. package/es/core/chrome-project.mjs +13 -0
  4. package/es/core/mcp-chrome.d.ts +12 -8
  5. package/es/core/mcp-chrome.mjs +52 -10
  6. package/es/core/mcp-proxy.d.ts +14 -1
  7. package/es/core/mcp-proxy.mjs +49 -10
  8. package/es/core/mcp-tools.d.ts +54 -3
  9. package/es/core/mcp-tools.mjs +111 -659
  10. package/es/core/official-meta.d.ts +9 -0
  11. package/es/core/official-meta.mjs +62 -0
  12. package/es/core/official-tools.d.ts +7 -0
  13. package/es/core/official-tools.mjs +54 -0
  14. package/es/core/service.mjs +4 -5
  15. package/es/endpoints/context.mjs +2 -2
  16. package/es/endpoints/host-events.mjs +2 -3
  17. package/es/endpoints/index.d.ts +2 -2
  18. package/es/endpoints/index.mjs +2 -2
  19. package/es/endpoints/mcp.d.ts +2 -2
  20. package/es/endpoints/mcp.mjs +116 -87
  21. package/es/endpoints/sse.mjs +3 -3
  22. package/es/endpoints/vue-devtools.d.ts +5 -2
  23. package/es/endpoints/vue-devtools.mjs +3 -4
  24. package/es/index.mjs +21 -4
  25. package/lib/client.css +1 -1
  26. package/lib/client.js +2590 -2435
  27. package/lib/core/chrome-project.cjs +36 -0
  28. package/lib/core/chrome-project.d.ts +15 -0
  29. package/lib/core/mcp-chrome.cjs +64 -10
  30. package/lib/core/mcp-chrome.d.ts +12 -8
  31. package/lib/core/mcp-proxy.cjs +50 -10
  32. package/lib/core/mcp-proxy.d.ts +14 -1
  33. package/lib/core/mcp-tools.cjs +126 -660
  34. package/lib/core/mcp-tools.d.ts +54 -3
  35. package/lib/core/official-meta.cjs +85 -0
  36. package/lib/core/official-meta.d.ts +9 -0
  37. package/lib/core/official-tools.cjs +70 -0
  38. package/lib/core/official-tools.d.ts +7 -0
  39. package/lib/core/service.cjs +4 -5
  40. package/lib/endpoints/context.cjs +1 -1
  41. package/lib/endpoints/host-events.cjs +1 -2
  42. package/lib/endpoints/index.cjs +2 -2
  43. package/lib/endpoints/index.d.ts +2 -2
  44. package/lib/endpoints/mcp.cjs +114 -85
  45. package/lib/endpoints/mcp.d.ts +2 -2
  46. package/lib/endpoints/sse.cjs +2 -2
  47. package/lib/endpoints/vue-devtools.cjs +4 -5
  48. package/lib/endpoints/vue-devtools.d.ts +5 -2
  49. package/lib/index.cjs +22 -5
  50. package/package.json +10 -7
  51. package/es/utils/format-bridge.d.ts +0 -9
  52. package/es/utils/format-bridge.mjs +0 -87
  53. package/es/utils/system.d.ts +0 -3
  54. package/es/utils/system.mjs +0 -76
  55. package/lib/utils/format-bridge.cjs +0 -103
  56. package/lib/utils/format-bridge.d.ts +0 -9
  57. package/lib/utils/system.cjs +0 -110
  58. package/lib/utils/system.d.ts +0 -3
package/README.md CHANGED
@@ -1,18 +1,33 @@
1
1
  # vite-plugin-aipanel
2
2
 
3
- 在 Vite 开发环境中嵌入 AIPanel AI 助手,边聊天边改代码,HMR 实时预览。
3
+ 在 Vite 开发环境中嵌入 AIPanel AI 助手:浏览器扩展在任意 `localhost` 开发页面唤起 AI 侧边栏,边聊天边改代码,HMR 实时预览。
4
4
 
5
- 支持 OpenCode 与 DeepSeek Harness (dsh) 两种 AI 引擎,通过 `provider` 配置一键切换。
5
+ 支持 **OpenCode****DeepSeek Harness (dsh)** 两种 AI 引擎,通过 `provider` 配置一键切换。
6
+
7
+ ## 环境要求
8
+
9
+ - 任一 Vite ≥ 5 的 Node.js 项目
10
+ - Chrome / Edge / Arc / Brave 等 Chromium 内核浏览器
11
+ - 任选其一:OpenCode CLI(默认)或 DeepSeek Harness (dsh) CLI
6
12
 
7
13
  ## 快速开始
8
14
 
9
- ### 1. 安装插件
15
+ ### 1. 安装 AI 引擎
16
+
17
+ **默认引擎 OpenCode CLI:**
10
18
 
11
19
  ```bash
12
- npm install -D vite-plugin-aipanel
20
+ curl -fsSL https://opencode.ai/install | bash
21
+ opencode --version # 验证
13
22
  ```
14
23
 
15
- ### 2. 配置 Vite
24
+ > 使用 DeepSeek Harness 请改装:`npm i -g @deepseek-ai/dsh` + `npm i -D @aipanel/provider-deepseek`,并配置 `provider: "deepseek"`(见下)。
25
+
26
+ ### 2. 安装并配置 Vite 插件
27
+
28
+ ```bash
29
+ npm install -D vite-plugin-aipanel
30
+ ```
16
31
 
17
32
  ```ts
18
33
  // vite.config.ts
@@ -24,67 +39,70 @@ export default defineConfig({
24
39
  });
25
40
  ```
26
41
 
27
- 默认使用 **OpenCode** 引擎(详见下方「选择 AI 引擎」)。
28
-
29
42
  ### 3. 启动开发服务器
30
43
 
31
44
  ```bash
32
45
  npm run dev
33
46
  ```
34
47
 
35
- Vite 插件会自动启动 AIPanel Web 服务并创建当前项目的 AI 会话。
48
+ 插件自动完成:校验引擎 启动 AIPanel Web 服务(默认 5097)与代理(默认 6097,端口占用自动换)→ 复用/创建当前项目会话。
36
49
 
37
50
  ### 4. 安装浏览器扩展
38
51
 
39
52
  1. [下载扩展包](https://github.com/code-farmer-i/vite-plugin-aipanel/raw/main/packages/extension/aipanel-assistant.zip)
40
- 2. 打开 Chrome,地址栏输入 `chrome://extensions/`
41
- 3. 打开右上角**「开发者模式」**开关
42
- 4. 解压下载的 `.zip`,点击**「加载已解压的扩展程序」**,选择解压后的文件夹
43
- 5. 用 Chrome 打开 `localhost` 开发页面,点击工具栏中的 AIPanel 图标即可开始对话
44
-
45
- > Edge / Arc / Brave 操作步骤相同,入口分别是 `edge://extensions/` / `arc://extensions/` / `brave://extensions/`。
46
-
47
- ## 选择 AI 引擎
53
+ 2. Chrome 打开 `chrome://extensions/`,开启右上角**「开发者模式」**
54
+ 3. 解压 `.zip` → **「加载已解压的扩展程序」** → 选择解压目录
55
+ 4. 打开 `localhost` 开发页面,点击工具栏 **AIPanel 图标**(或 `Ctrl/Cmd + K`)唤起侧边栏
48
56
 
49
- 插件内置两套 AI 引擎,通过 `providerOptions` 段的 `provider` 字段选择:
57
+ > Edge / Arc / Brave 入口分别是 `edge://extensions/` / `arc://extensions/` / `brave://extensions/`。
50
58
 
51
- | provider | 引擎 | 额外依赖 | 特点 |
52
- | ------------ | ----------------------- | ------------------------------- | ------------------------------ |
53
- | `opencode` | [OpenCode CLI](https://opencode.ai) | 无(插件内置) | 默认引擎,通用 AI CLI |
54
- | `deepseek` | DeepSeek Harness (dsh) | 另装 `@aipanel/provider-deepseek` | DeepSeek 官方 Web 对话界面 |
59
+ ## 切换 AI 引擎
55
60
 
56
- 使用 DeepSeek Harness 引擎需先安装依赖:
61
+ 插件默认 `provider: "default"`(等价 `"opencode"`,适配已内置,仅需安装 opencode CLI)。
57
62
 
58
- ```bash
59
- npm install -D @aipanel/provider-deepseek
60
- npm install -g @deepseek-ai/dsh
61
- ```
63
+ | provider | 引擎 | 额外依赖 | 说明 |
64
+ | --------- | ---------------------- | ------------------------------------------ | --------------------------- |
65
+ | `default` | OpenCode CLI | 无(适配内置;需另装 opencode CLI) | 默认值,同 `opencode` |
66
+ | `opencode` | OpenCode CLI | 无(适配内置;需另装 opencode CLI) | 显式指定 OpenCode |
67
+ | `deepseek` | DeepSeek Harness (dsh) | `@aipanel/provider-deepseek` + dsh CLI | DeepSeek 官方 Web 对话界面 |
62
68
 
63
69
  ```ts
64
- // 使用 DeepSeek Harness 引擎
70
+ // 使用 DeepSeek Harness 引擎(先安装其依赖)
65
71
  aipanelAssistant({
66
72
  provider: "deepseek",
67
73
  providerOptions: {
68
- // dsh 专属配置,详见在线文档
69
- // home: "~/.dsh", // dsh 数据目录
70
- // agentPreset: "standard", // 新建会话的默认 Agent 预设
74
+ // home: "~/.dsh", // dsh 数据目录
75
+ // agentPreset: "standard", // 新建会话的默认 Agent 预设
71
76
  // permissionPreset: "read-only", // 默认权限预设
72
- // busyEnter: "queue", // 繁忙时 Enter 行为
77
+ // busyEnter: "queue", // 繁忙时 Enter 行为
73
78
  },
74
79
  });
75
80
  ```
76
81
 
77
- > 已预装 OpenCode dsh 其中之一的用户,插件会自动检测并使用。
82
+ > **注意**:插件**不会自动探测**已安装的引擎,引擎由 `provider` 字段决定;切换前请确认对应 CLI 已安装。
78
83
 
79
84
  ## 纯净 MCP 模式
80
85
 
81
- `mcpOnly: true` 时,插件只暴露 MCP 工具服务(Chrome DevTools 控制、Vue DevTools、日志读取等),不注入挂件、不启动 AI 引擎,适合作为独立 MCP server 供外部 Agent 消费。
86
+ `mcpOnly: true` 时,插件只暴露 MCP 工具服务(Chrome DevTools 控制、Vue DevTools、日志读取等),不启动 AI 引擎、不注入对话界面,适合作为独立 MCP server 供外部 Agent 消费:
82
87
 
83
88
  ```ts
84
89
  aipanelAssistant({ mcpOnly: true });
85
90
  ```
86
91
 
87
- 启动后 MCP 端点挂在 Vite dev server 上,外部 MCP 客户端配置 Streamable HTTP 接入 `http://localhost:5173/__aipanel_mcp__`。
92
+ 端点挂在 Vite dev server 上(需保持 `vite dev` 运行),外部 MCP 客户端按 Streamable HTTP 配置:
93
+
94
+ ```json
95
+ {
96
+ "mcpServers": {
97
+ "aipanel": {
98
+ "type": "http",
99
+ "url": "http://localhost:5173/__aipanel_mcp__"
100
+ }
101
+ }
102
+ }
103
+ ```
104
+
105
+ > 端口随 Vite dev server 变化(默认 5173),以启动日志中的 `MCP endpoint` 为准;`Claude Code` / `Cursor` 等客户端配置详见在线文档。
88
106
 
89
107
  ## 工作原理
90
108
 
@@ -107,4 +125,4 @@ aipanelAssistant({ mcpOnly: true });
107
125
 
108
126
  ## License
109
127
 
110
- MIT
128
+ MIT
@@ -0,0 +1,15 @@
1
+ /**
2
+ * 项目边界策略解析:在请求时把 chromeMcp.project 与 vite 自动 origins 合并为一份体。
3
+ * 所有边界判定(页面归属 / 导航目标 / 扩展页)均只读此解析结果,单一来源。
4
+ */
5
+ import type { ViteDevServer } from "vite";
6
+ import type { ChromeProjectOptions } from "@aipanel/core";
7
+ export interface ProjectScope {
8
+ /** 项目页 origins(自动,“项目页全局一个”分类 + vue-devtools 桥可用范围) */
9
+ origins: string[];
10
+ /** 可操作范围 = 项目页 ∪ allowOrigins:chrome 页面级 list/current/pageId 校验、navigate/new 目标用 */
11
+ operationsOrigins: string[];
12
+ /** 是否允许 chrome-extension:// 页面纳入可操作范围 */
13
+ includeExtensions: boolean;
14
+ }
15
+ export declare function resolveProjectScope(server: ViteDevServer, project?: ChromeProjectOptions): ProjectScope;
@@ -0,0 +1,13 @@
1
+ import { getProjectOrigins } from "./mcp-chrome.mjs";
2
+ function resolveProjectScope(server, project) {
3
+ const auto = getProjectOrigins(server);
4
+ const operationsOrigins = [.../* @__PURE__ */ new Set([...auto, ...project?.allowOrigins ?? []])];
5
+ return {
6
+ origins: auto,
7
+ operationsOrigins,
8
+ includeExtensions: project?.includeExtensionPages === true
9
+ };
10
+ }
11
+ export {
12
+ resolveProjectScope
13
+ };
@@ -7,8 +7,12 @@ import type { ViteDevServer } from "vite";
7
7
  import type { McpProxy } from "./mcp-proxy";
8
8
  /** 从 Vite 服务器解析的所有 URL 中提取项目 origin 列表 */
9
9
  export declare function getProjectOrigins(server: ViteDevServer): string[];
10
- /** 判断页面 URL 是否属于项目的某个 origin */
10
+ /** 判断页面 URL 是否命中某个范围条目(allowOrigins 三种写法,见模块注释) */
11
+ export declare function isOriginEntryMatch(url: string, entry: string): boolean;
12
+ /** 判断页面 URL 是否命中某组范围条目(可为精确 origin / 通配符 / 正则) */
11
13
  export declare function isProjectPage(url: string, origins: string[]): boolean;
14
+ /** 判断页面 URL 是否属于可操作范围(范围条目,或启用时的扩展页) */
15
+ export declare function isPageAllowed(url: string, origins: string[], includeExtensions: boolean): boolean;
12
16
  export interface PageInfo {
13
17
  pageId: number;
14
18
  url: string;
@@ -28,18 +32,18 @@ type PageIdResult = {
28
32
  error: string;
29
33
  };
30
34
  /**
31
- * 校验 pageId 是否属于项目页面
35
+ * 校验 pageId 是否属于可操作范围(项目页 ∪ allowOrigins)
32
36
  *
33
- * 调用 list_pages → 过滤项目页面 → 检查 pageId 是否在范围内。
37
+ * 调用 list_pages → 过滤允许页面 → 检查 pageId 是否在范围内。
34
38
  * 供 MCP 代理层和 Vue DevTools 端点共用。
35
39
  */
36
- export declare function validatePageId(mcp: McpProxy, pageId: number, projectOrigins: string[]): Promise<{
40
+ export declare function validatePageId(mcp: McpProxy, pageId: number, operationsOrigins: string[], includeExtensions?: boolean): Promise<{
37
41
  valid: true;
38
- projectPages: PageInfo[];
42
+ allowedPages: PageInfo[];
39
43
  } | {
40
44
  valid: false;
41
45
  error: string;
42
- projectPages: PageInfo[];
46
+ allowedPages: PageInfo[];
43
47
  }>;
44
48
  /**
45
49
  * 通过 MCP 解析当前页面对应的 Chrome DevTools pageId
@@ -50,7 +54,7 @@ export declare function validatePageId(mcp: McpProxy, pageId: number, projectOri
50
54
  *
51
55
  * 失败时返回具体原因,由调用方透传给 Agent。
52
56
  */
53
- export declare function resolveChromePageId(mcp: McpProxy | undefined, url: string, title: string, projectOrigins: string[], sessionId?: string, pages?: PageInfo[],
54
- /** Chrome 当前选中的 pageId(调用方传入,避免过滤后丢失非项目页面信息) */
57
+ export declare function resolveChromePageId(mcp: McpProxy | undefined, url: string, title: string, operationsOrigins: string[], sessionId?: string, pages?: PageInfo[],
58
+ /** Chrome 当前选中的 pageId(调用方传入,避免过滤后丢失范围外页面信息) */
55
59
  chromeSelectedPageId?: number): Promise<PageIdResult>;
56
60
  export {};
@@ -1,3 +1,4 @@
1
+ import picomatch from "picomatch";
1
2
  import { createLogger } from "@aipanel/core/node";
2
3
  const log = createLogger("McpChrome");
3
4
  function getProjectOrigins(server) {
@@ -7,8 +8,45 @@ function getProjectOrigins(server) {
7
8
  log.debug("project origins", { origins });
8
9
  return origins;
9
10
  }
11
+ const entryMatcherCache = /* @__PURE__ */ new Map();
12
+ function parseRegexEntry(entry) {
13
+ if (!entry.startsWith("/")) return void 0;
14
+ const lastSlash = entry.lastIndexOf("/");
15
+ if (lastSlash <= 0) return void 0;
16
+ const source = entry.slice(1, lastSlash);
17
+ const flags = entry.slice(lastSlash + 1);
18
+ if (!/^[a-z]*$/.test(flags)) return void 0;
19
+ try {
20
+ return { kind: "regex", re: new RegExp(source, flags) };
21
+ } catch {
22
+ return void 0;
23
+ }
24
+ }
25
+ function compileEntryMatcher(entry) {
26
+ const cached = entryMatcherCache.get(entry);
27
+ if (cached) return cached;
28
+ let compiled = { kind: "prefix" };
29
+ const regexMatcher = parseRegexEntry(entry);
30
+ if (regexMatcher) {
31
+ compiled = regexMatcher;
32
+ } else if (picomatch.scan(entry).isGlob) {
33
+ compiled = { kind: "glob", match: picomatch(entry) };
34
+ }
35
+ entryMatcherCache.set(entry, compiled);
36
+ return compiled;
37
+ }
38
+ function isOriginEntryMatch(url, entry) {
39
+ const matcher = compileEntryMatcher(entry);
40
+ if (matcher.kind === "regex") return matcher.re.test(url);
41
+ if (matcher.kind === "glob") return matcher.match(url);
42
+ return url.startsWith(entry);
43
+ }
10
44
  function isProjectPage(url, origins) {
11
- return origins.some((origin) => url.startsWith(origin));
45
+ return origins.some((entry) => isOriginEntryMatch(url, entry));
46
+ }
47
+ function isPageAllowed(url, origins, includeExtensions) {
48
+ if (origins.some((entry) => isOriginEntryMatch(url, entry))) return true;
49
+ return includeExtensions && url.startsWith("chrome-extension://");
12
50
  }
13
51
  function parseListPages(text) {
14
52
  const pages = [];
@@ -39,29 +77,31 @@ function extractEvalValue(text) {
39
77
  return jsonStr.trim();
40
78
  }
41
79
  }
42
- async function validatePageId(mcp, pageId, projectOrigins) {
80
+ async function validatePageId(mcp, pageId, operationsOrigins, includeExtensions = false) {
43
81
  const listResult = await mcp.callChromeDevTool("list_pages", {});
44
82
  if (listResult?.error || !listResult?.result) {
45
83
  return {
46
84
  valid: false,
47
85
  error: listResult?.error?.message ?? "\u65E0\u6CD5\u83B7\u53D6 Chrome \u9875\u9762\u5217\u8868\uFF0C\u8BF7\u786E\u8BA4 Chrome DevTools \u5DF2\u8FDE\u63A5",
48
- projectPages: []
86
+ allowedPages: []
49
87
  };
50
88
  }
51
89
  const text = listResult?.result?.content?.[0]?.text;
52
90
  const allPages = text ? parseListPages(text) : [];
53
- const projectPages = allPages.filter((p) => isProjectPage(p.url, projectOrigins));
54
- const isValid = projectPages.some((p) => p.pageId === pageId);
91
+ const allowedPages = allPages.filter(
92
+ (p) => isPageAllowed(p.url, operationsOrigins, includeExtensions)
93
+ );
94
+ const isValid = allowedPages.some((p) => p.pageId === pageId);
55
95
  if (!isValid) {
56
96
  return {
57
97
  valid: false,
58
- error: `pageId ${pageId} \u65E0\u6548\u6216\u975E\u9879\u76EE\u9875\u9762\uFF0C\u8BF7\u83B7\u53D6\u6709\u6548\u9875\u9762 ID`,
59
- projectPages
98
+ error: `pageId ${pageId} \u4E0D\u5728\u53EF\u64CD\u4F5C\u8303\u56F4\uFF08\u9879\u76EE\u9875\u6216 allowOrigins \u767D\u540D\u5355\u9875\uFF09\uFF0C\u8BF7\u5148\u7528 list_pages \u83B7\u53D6\u6709\u6548\u9875\u9762 ID`,
99
+ allowedPages
60
100
  };
61
101
  }
62
- return { valid: true, projectPages };
102
+ return { valid: true, allowedPages };
63
103
  }
64
- async function resolveChromePageId(mcp, url, title, projectOrigins, sessionId, pages, chromeSelectedPageId) {
104
+ async function resolveChromePageId(mcp, url, title, operationsOrigins, sessionId, pages, chromeSelectedPageId) {
65
105
  if (!mcp || !mcp.isRunning) {
66
106
  const reason = !mcp ? "MCP \u6A21\u5757\u672A\u521D\u59CB\u5316" : "Chrome DevTools MCP \u8FDB\u7A0B\u672A\u542F\u52A8";
67
107
  log.debug(`resolveChromePageId: ${reason}`);
@@ -79,7 +119,7 @@ async function resolveChromePageId(mcp, url, title, projectOrigins, sessionId, p
79
119
  }
80
120
  const allPages = parseListPages(text);
81
121
  chromeSelectedPageId = allPages.find((p) => p.selected)?.pageId;
82
- pages = allPages.filter((p) => isProjectPage(p.url, projectOrigins));
122
+ pages = allPages.filter((p) => isProjectPage(p.url, operationsOrigins));
83
123
  }
84
124
  log.debug("resolveChromePageId: list_pages result", {
85
125
  pages: pages.map((p) => ({ id: p.pageId, url: p.url, title: p.title.substring(0, 40) })),
@@ -126,6 +166,8 @@ async function resolveChromePageId(mcp, url, title, projectOrigins, sessionId, p
126
166
  export {
127
167
  extractEvalValue,
128
168
  getProjectOrigins,
169
+ isOriginEntryMatch,
170
+ isPageAllowed,
129
171
  isProjectPage,
130
172
  parseListPages,
131
173
  resolveChromePageId,
@@ -1,10 +1,23 @@
1
1
  /** chrome-devtools-mcp 的核心受保护参数(用户不可覆盖;始终最后注入确保生效) */
2
- export declare const CORE_MCP_ARGS: readonly ["--auto-connect", "--no-usage-statistics", "--no-performance-crux", "--no-page-id-routing"];
2
+ export declare const CORE_MCP_ARGS: readonly ["--auto-connect", "--no-usage-statistics", "--no-performance-crux", "--no-page-id-routing", "--allow-unrestricted-paths"];
3
3
  /** 过滤用户透传参数:剔除与核心受保护 flag 冲突的项,其余原样保留 */
4
+ export type McpArgDropKind = "protected" | "managed";
5
+ export interface McpArgDrop {
6
+ arg: string;
7
+ kind: McpArgDropKind;
8
+ }
9
+ /** 分类用户透传参数:保留可透传项,列出被剔除项及原因 */
10
+ export declare function classifyUserMcpArgs(userArgs: readonly string[]): {
11
+ kept: string[];
12
+ dropped: McpArgDrop[];
13
+ };
14
+ /** 过滤用户透传参数:剔除与核心受保护/策略接管 flag 冲突的项 */
4
15
  export declare function filterUserMcpArgs(userArgs: readonly string[]): string[];
5
16
  export interface McpProxyOptions {
6
17
  /** 用户透传的额外 CLI 参数(追加;与核心受保护项冲突的会被剔除) */
7
18
  userArgs?: string[];
19
+ /** 策略控制的内部注入参数(不经用户过滤,在用户参数之后、核心参数之前注入) */
20
+ managedArgs?: string[];
8
21
  /** 用户透传的额外环境变量(合并到 process.env 之上) */
9
22
  env?: Record<string, string>;
10
23
  idleTimeout?: number;
@@ -31,7 +31,11 @@ const CORE_MCP_ARGS = [
31
31
  // chrome-devtools-mcp >=1.8.0 默认开启 pageIdRouting(要求每个页面级工具都传 pageId)。
32
32
  // 代理层已自行校验 pageId 并用 select_page 选中目标页面后再转发,故显式关闭,
33
33
  // 避免底层工具 schema 强制必填 pageId 导致转发时的参数校验失败。
34
- "--no-page-id-routing"
34
+ "--no-page-id-routing",
35
+ // 客户端未协商 roots,官方默认把文件工具限制在 OS 临时目录;
36
+ // 为保持 upload_file “不限制项目目录”的设计,显式允许任意路径
37
+ // (工具层仍受白名单 + pageId 归属约束)。
38
+ "--allow-unrestricted-paths"
35
39
  ];
36
40
  const PROTECTED_FLAGS = /* @__PURE__ */ new Set([
37
41
  "auto-connect",
@@ -39,6 +43,23 @@ const PROTECTED_FLAGS = /* @__PURE__ */ new Set([
39
43
  "performance-crux",
40
44
  "page-id-routing"
41
45
  ]);
46
+ const MANAGED_FLAGS = /* @__PURE__ */ new Set([
47
+ "slim",
48
+ "category-extensions",
49
+ "category-pwa",
50
+ "category-experimental-third-party",
51
+ "category-experimental-webmcp",
52
+ "category-performance",
53
+ "category-network",
54
+ "category-emulation",
55
+ "experimental-vision",
56
+ "experimental-interop-tools",
57
+ "experimental-memory",
58
+ "experimental-screencast",
59
+ "experimental-structured-content",
60
+ "experimental-include-all-pages",
61
+ "allow-unrestricted-paths"
62
+ ]);
42
63
  function canonicalFlagName(arg) {
43
64
  let flag = arg.trim();
44
65
  if (!flag.startsWith("-")) return null;
@@ -46,11 +67,25 @@ function canonicalFlagName(arg) {
46
67
  if (flag.startsWith("no-")) flag = flag.slice(3);
47
68
  return flag || null;
48
69
  }
49
- function filterUserMcpArgs(userArgs) {
50
- return userArgs.filter((arg) => {
70
+ function classifyUserMcpArgs(userArgs) {
71
+ const kept = [];
72
+ const dropped = [];
73
+ for (const arg of userArgs) {
51
74
  const name = canonicalFlagName(arg);
52
- return name === null || !PROTECTED_FLAGS.has(name);
53
- });
75
+ if (name === null) {
76
+ kept.push(arg);
77
+ } else if (PROTECTED_FLAGS.has(name)) {
78
+ dropped.push({ arg, kind: "protected" });
79
+ } else if (MANAGED_FLAGS.has(name)) {
80
+ dropped.push({ arg, kind: "managed" });
81
+ } else {
82
+ kept.push(arg);
83
+ }
84
+ }
85
+ return { kept, dropped };
86
+ }
87
+ function filterUserMcpArgs(userArgs) {
88
+ return classifyUserMcpArgs(userArgs).kept;
54
89
  }
55
90
  function resolveChromeDevToolsMcpBin() {
56
91
  const pluginDir = resolvePackageDir("vite-plugin-aipanel");
@@ -81,12 +116,15 @@ class McpProxy {
81
116
  __privateAdd(this, _idleTimeout);
82
117
  __publicField(this, "sessionId");
83
118
  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 });
119
+ const { kept, dropped } = classifyUserMcpArgs(userArgs);
120
+ if (dropped.length > 0) {
121
+ log.warn(
122
+ "chrome MCP \u7528\u6237\u53C2\u6570\u4E0E\u53D7\u4FDD\u62A4/\u7B56\u7565\u63A5\u7BA1 flag \u51B2\u7A81\uFF0C\u5DF2\u5FFD\u7565\uFF1B\u8BF7\u7528 chromeMcp.project \u914D\u7F6E\u5BF9\u5E94\u884C\u4E3A",
123
+ { dropped }
124
+ );
88
125
  }
89
- __privateSet(this, _args, [...filtered, ...CORE_MCP_ARGS]);
126
+ const managedArgs = options.managedArgs ?? [];
127
+ __privateSet(this, _args, [...kept, ...managedArgs, ...CORE_MCP_ARGS]);
90
128
  __privateSet(this, _env, options.env);
91
129
  __privateSet(this, _idleTimeout, options.idleTimeout ?? 0);
92
130
  this.sessionId = crypto.randomUUID();
@@ -287,5 +325,6 @@ resetIdleTimer_fn = function() {
287
325
  export {
288
326
  CORE_MCP_ARGS,
289
327
  McpProxy,
328
+ classifyUserMcpArgs,
290
329
  filterUserMcpArgs
291
330
  };
@@ -1,8 +1,13 @@
1
1
  /**
2
- * 自定义 DevTools 工具定义
3
- * 所有工具(除 chrome-devtools_list_pages)必须传入 pageId 参数,
4
- * 代理层校验 pageId 是否为项目页面后方可调用 chrome-devtools-mcp
2
+ * Chrome DevTools 工具层的项目维度元数据与自定义工具。
3
+ *
4
+ * 模型可见的 chrome-devtools_* 工具 = 官方 chrome-devtools-mcp 工具的白名单子集
5
+ * (schema/描述运行时同步自官方 tools/list,见 official-tools.ts)+ 本项目自定义工具。
6
+ * 工具层限制(白名单、必填 pageId、仅项目页)由本文件 + endpoints/mcp.ts 的 tools/call 共同执行。
5
7
  */
8
+ /** 模型可见名前缀:官方短名 → chrome-devtools_<name> */
9
+ export declare const MCP_PREFIX = "chrome-devtools_";
10
+ export declare function displayToolName(short: string): string;
6
11
  export interface CustomTool {
7
12
  name: string;
8
13
  description: string;
@@ -12,4 +17,50 @@ export interface CustomTool {
12
17
  required?: string[];
13
18
  };
14
19
  }
20
+ /**
21
+ * 默认暴露的官方页面级工具 = 元数据规则推导(pageScoped && 无条件 && 分类安全),
22
+ * 具体由 officialDefaultShorts() 给出。
23
+ * 以下为 meta 无法从 pageScoped 推断、需要“按页面上下文使用/列表/新建”的产品全局特例(短名)。
24
+ */
25
+ export declare const OFFICIAL_GLOBAL_POLICY: readonly ["evaluate_script", "list_console_messages", "list_pages", "new_page"];
26
+ export type OfficialToolShortName = string;
27
+ /** 不需要 pageId 的官方工具(无目标页,由调用层特别处理) */
28
+ export declare const OFFICIAL_NO_PAGE_TOOLS: ReadonlySet<string>;
29
+ /** 目标页参数:pageId 必须在可操作范围(项目页或 allowOrigins 白名单页)内 */
30
+ export declare const PAGE_ID_PROP: {
31
+ readonly pageId: {
32
+ readonly type: "number";
33
+ readonly description: "The ID of a page within the operation scope (project pages or chromeMcp.project.allowOrigins pages) to operate on";
34
+ };
35
+ };
36
+ /** 往官方 schema 上统一追加必填 pageId(保持工具层项目限制) */
37
+ export declare function withPageIdSchema(schema: CustomTool["inputSchema"]): CustomTool["inputSchema"];
38
+ /**
39
+ * 追加到官方描述末尾的项目维度说明(英文,按工具特例)。
40
+ * 只填“调用层真正实现的语义与官方不一样”的工具,
41
+ * 避免描述过度承诺(如 navigate_page 目标 URL 未被调用层限制,不填“必须在项目内”)。
42
+ * 其余页面级工具的项目边界由必填 pageId 参数说明表达(PAGE_ID_PROP)。
43
+ */
44
+ export declare const PROJECT_DESCRIPTION_NOTES: Record<string, string>;
45
+ /** 自定义工具(官方无对应,工具层本地实现) */
15
46
  export declare const CUSTOM_TOOLS: CustomTool[];
47
+ /** 官方声明了条件、且属于可开启范围的工具(由官方元数据派生,无手写) */
48
+ export declare function isOfficialExtraTool(short: string): boolean;
49
+ /** 由官方 conditions 推导所需 flag(experimentalX → --experimental-x,与官方 CLI 一致) */
50
+ export declare function extraToolFlag(short: string): string | undefined;
51
+ /** 可开启的二级工具名单(用于验证与提示) */
52
+ export declare function officialExtraCandidates(): string[];
53
+ /**
54
+ * 默认暴露的官方工具集:
55
+ * 页面级(pageScoped && 无 conditions && 分类安全)由官方元数据规则推导 + 全局特例 GLOBAL_POLICY。
56
+ */
57
+ export declare function officialDefaultShorts(): string[];
58
+ /** 配置工具面范围(默认为纯白名单) */
59
+ export declare function configureToolScope(extra?: readonly string[], deny?: readonly string[], warn?: (msg: string) => void): void;
60
+ /**
61
+ * 当前生效的官方白名单(短名,含 extra 减 deny)。
62
+ * deny 同时作用于默认面与 extra,保证 tools/list 暴露面与 isAllowedToolName 调用守卫一致。
63
+ */
64
+ export declare function currentOfficialShorts(): string[];
65
+ /** tools/call 白名单守卫(含 current_page 与动态 extra/deny) */
66
+ export declare function isAllowedToolName(name: string): boolean;