vite-plugin-aipanel 1.2.7 → 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.
@@ -45,7 +45,9 @@ var __privateWrapper = (obj, member, setter, getter) => ({
45
45
  });
46
46
  var mcp_proxy_exports = {};
47
47
  __export(mcp_proxy_exports, {
48
- McpProxy: () => McpProxy
48
+ CORE_MCP_ARGS: () => CORE_MCP_ARGS,
49
+ McpProxy: () => McpProxy,
50
+ filterUserMcpArgs: () => filterUserMcpArgs
49
51
  });
50
52
  module.exports = __toCommonJS(mcp_proxy_exports);
51
53
  var import_node_child_process = require("node:child_process");
@@ -53,8 +55,36 @@ var import_node_readline = require("node:readline");
53
55
  var import_node_crypto = __toESM(require("node:crypto"));
54
56
  var import_node_path = __toESM(require("node:path"));
55
57
  var import_node = require("@aipanel/core/node");
56
- var _proc, _rl, _messageId, _internalIdBase, _pending, _args, _startPromise, _lastExit, _stderrTail, _idleTimer, _idleTimeout, _McpProxy_instances, doStart_fn, formatNotAvailable_fn, resetIdleTimer_fn;
58
+ var _proc, _rl, _messageId, _internalIdBase, _pending, _args, _env, _startPromise, _lastExit, _stderrTail, _idleTimer, _idleTimeout, _McpProxy_instances, doStart_fn, formatNotAvailable_fn, resetIdleTimer_fn;
57
59
  const log = (0, import_node.createLogger)("McpProxy");
60
+ const CORE_MCP_ARGS = [
61
+ "--auto-connect",
62
+ "--no-usage-statistics",
63
+ "--no-performance-crux",
64
+ // chrome-devtools-mcp >=1.8.0 默认开启 pageIdRouting(要求每个页面级工具都传 pageId)。
65
+ // 代理层已自行校验 pageId 并用 select_page 选中目标页面后再转发,故显式关闭,
66
+ // 避免底层工具 schema 强制必填 pageId 导致转发时的参数校验失败。
67
+ "--no-page-id-routing"
68
+ ];
69
+ const PROTECTED_FLAGS = /* @__PURE__ */ new Set([
70
+ "auto-connect",
71
+ "usage-statistics",
72
+ "performance-crux",
73
+ "page-id-routing"
74
+ ]);
75
+ function canonicalFlagName(arg) {
76
+ let flag = arg.trim();
77
+ if (!flag.startsWith("-")) return null;
78
+ flag = flag.replace(/^--?/, "").split("=")[0] ?? "";
79
+ if (flag.startsWith("no-")) flag = flag.slice(3);
80
+ return flag || null;
81
+ }
82
+ function filterUserMcpArgs(userArgs) {
83
+ return userArgs.filter((arg) => {
84
+ const name = canonicalFlagName(arg);
85
+ return name === null || !PROTECTED_FLAGS.has(name);
86
+ });
87
+ }
58
88
  function resolveChromeDevToolsMcpBin() {
59
89
  const pluginDir = (0, import_node.resolvePackageDir)("vite-plugin-aipanel");
60
90
  const require2 = (0, import_node.createPackageRequire)(pluginDir);
@@ -74,6 +104,7 @@ class McpProxy {
74
104
  __privateAdd(this, _internalIdBase, 1e6);
75
105
  __privateAdd(this, _pending, /* @__PURE__ */ new Map());
76
106
  __privateAdd(this, _args);
107
+ __privateAdd(this, _env);
77
108
  __privateAdd(this, _startPromise, null);
78
109
  /** 最近一次进程退出信息,用于生成精确错误(无退出记录时为 null) */
79
110
  __privateAdd(this, _lastExit, null);
@@ -82,15 +113,14 @@ class McpProxy {
82
113
  __privateAdd(this, _idleTimer, null);
83
114
  __privateAdd(this, _idleTimeout);
84
115
  __publicField(this, "sessionId");
85
- __privateSet(this, _args, options.args ?? [
86
- "--auto-connect",
87
- "--no-usage-statistics",
88
- "--no-performance-crux",
89
- // chrome-devtools-mcp >=1.8.0 默认开启 pageIdRouting(要求每个页面级工具都传 pageId)。
90
- // 代理层已自行校验 pageId 并用 select_page 选中目标页面后再转发,故显式关闭,
91
- // 避免底层工具 schema 强制必填 pageId 导致转发时的参数校验失败。
92
- "--no-page-id-routing"
93
- ]);
116
+ const userArgs = options.userArgs ?? [];
117
+ const filtered = filterUserMcpArgs(userArgs);
118
+ if (filtered.length !== userArgs.length) {
119
+ const dropped = userArgs.filter((a) => !filtered.includes(a));
120
+ log.warn("chrome MCP \u7528\u6237\u53C2\u6570\u4E0E\u6838\u5FC3\u53D7\u4FDD\u62A4\u9879\u51B2\u7A81\uFF0C\u5DF2\u5FFD\u7565", { dropped });
121
+ }
122
+ __privateSet(this, _args, [...filtered, ...CORE_MCP_ARGS]);
123
+ __privateSet(this, _env, options.env);
94
124
  __privateSet(this, _idleTimeout, options.idleTimeout ?? 0);
95
125
  this.sessionId = import_node_crypto.default.randomUUID();
96
126
  }
@@ -184,6 +214,7 @@ _messageId = new WeakMap();
184
214
  _internalIdBase = new WeakMap();
185
215
  _pending = new WeakMap();
186
216
  _args = new WeakMap();
217
+ _env = new WeakMap();
187
218
  _startPromise = new WeakMap();
188
219
  _lastExit = new WeakMap();
189
220
  _stderrTail = new WeakMap();
@@ -197,7 +228,8 @@ doStart_fn = async function() {
197
228
  __privateSet(this, _lastExit, null);
198
229
  __privateSet(this, _stderrTail, []);
199
230
  __privateSet(this, _proc, (0, import_node_child_process.spawn)(process.execPath, [binPath, ...__privateGet(this, _args)], {
200
- stdio: ["pipe", "pipe", "pipe"]
231
+ stdio: ["pipe", "pipe", "pipe"],
232
+ env: { ...process.env, ...__privateGet(this, _env) }
201
233
  }));
202
234
  log.debug("Using local chrome-devtools-mcp");
203
235
  } catch {
@@ -287,5 +319,7 @@ resetIdleTimer_fn = function() {
287
319
  };
288
320
  // Annotate the CommonJS export names for ESM import in node:
289
321
  0 && (module.exports = {
290
- McpProxy
322
+ CORE_MCP_ARGS,
323
+ McpProxy,
324
+ filterUserMcpArgs
291
325
  });
@@ -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 {
@@ -32,11 +32,13 @@ __export(proxy_server_exports, {
32
32
  module.exports = __toCommonJS(proxy_server_exports);
33
33
  var import_http = __toESM(require("http"));
34
34
  var import_core = require("@aipanel/core");
35
- const log = (0, import_core.createLogger)("ProxyServer");
35
+ var import_node = require("@aipanel/core/node");
36
+ const log = (0, import_node.createLogger)("ProxyServer");
36
37
  function startProxyServer(targetUrl, port, options = {}) {
37
38
  return new Promise((resolve, reject) => {
38
39
  const target = new URL(targetUrl);
39
40
  const bridgeScript = options.bridgeScript ?? "";
41
+ const webAuthCookie = options.webAuthCookie ?? "";
40
42
  const agent = new import_http.default.Agent({
41
43
  keepAlive: true,
42
44
  keepAliveMsecs: 3e4,
@@ -64,6 +66,9 @@ function startProxyServer(targetUrl, port, options = {}) {
64
66
  headers: {
65
67
  ...req.headers,
66
68
  host: target.host,
69
+ // 注入 Provider Web 服务的 browser-session 认证 Cookie:代理作为已认证客户端
70
+ // 替 iframe 里的浏览器完成 dsh 0.1.2+ 的会话认证(浏览器无需换取/携带该 Cookie)。
71
+ ...webAuthCookie ? { cookie: webAuthCookie } : {},
67
72
  // 上游(如 dsh 的 trust fence)校验 Origin 必须匹配自身 origin;
68
73
  // 页面经代理访问时浏览器发的 Origin 是代理端口,须改写为目标 origin 否则 403
69
74
  origin: `http://${target.host}`,
@@ -81,18 +86,20 @@ function startProxyServer(targetUrl, port, options = {}) {
81
86
  });
82
87
  proxyRes.on("end", () => {
83
88
  let body = Buffer.concat(chunks).toString("utf-8");
84
- if (body.match(/<\/head>/i)) {
85
- body = body.replace(
86
- /<\/head>/i,
87
- `<script src="${import_core.BRIDGE_SCRIPT_PATH}"></script></head>`
88
- );
89
- } else if (body.match(/<\/body>/i)) {
90
- body = body.replace(
91
- /<\/body>/i,
92
- `<script src="${import_core.BRIDGE_SCRIPT_PATH}"></script></body>`
93
- );
94
- } else {
95
- body += `<script src="${import_core.BRIDGE_SCRIPT_PATH}"></script>`;
89
+ if (bridgeScript.trim().length > 0) {
90
+ if (body.match(/<\/head>/i)) {
91
+ body = body.replace(
92
+ /<\/head>/i,
93
+ `<script src="${import_core.BRIDGE_SCRIPT_PATH}"></script></head>`
94
+ );
95
+ } else if (body.match(/<\/body>/i)) {
96
+ body = body.replace(
97
+ /<\/body>/i,
98
+ `<script src="${import_core.BRIDGE_SCRIPT_PATH}"></script></body>`
99
+ );
100
+ } else {
101
+ body += `<script src="${import_core.BRIDGE_SCRIPT_PATH}"></script>`;
102
+ }
96
103
  }
97
104
  const headers = {};
98
105
  for (const [key, value] of Object.entries(proxyRes.headers)) {
@@ -136,6 +143,8 @@ function startProxyServer(targetUrl, port, options = {}) {
136
143
  headers: {
137
144
  ...req.headers,
138
145
  host: target.host,
146
+ // 与 HTTP 分支一致:注入认证 Cookie,通过 dsh 0.1.2+ 的 browser-auth 门禁
147
+ ...webAuthCookie ? { cookie: webAuthCookie } : {},
139
148
  // 与 HTTP 分支一致:改写 Origin 为目标 origin,通过 dsh 的 trust fence
140
149
  origin: `http://${target.host}`
141
150
  }
@@ -4,6 +4,13 @@ export interface ProxyServerOptions {
4
4
  bridgeScript?: string;
5
5
  /** 绑定地址,需与端口检查使用的地址族一致,避免 IPv4/IPv6 不匹配 */
6
6
  hostname?: string;
7
+ /**
8
+ * Provider Web 服务的 browser-session 认证 Cookie(dsh 0.1.2+ 需要)。
9
+ * 代理在转发每个请求(含 WebSocket 升级)时把该 Cookie 注入到上游请求头,
10
+ * 使浏览器免于亲自换取/携带会话 Cookie(避免跨端口/跨站 SameSite 限制),
11
+ * 同时通过上游 /api 的 browser-auth 门禁。
12
+ */
13
+ webAuthCookie?: string;
7
14
  }
8
15
  export interface ProxyServerResult {
9
16
  server: http.Server;
@@ -22,6 +22,7 @@ __export(service_exports, {
22
22
  AIPanelService: () => AIPanelService
23
23
  });
24
24
  module.exports = __toCommonJS(service_exports);
25
+ var import_node_crypto = require("node:crypto");
25
26
  var import_core = require("@aipanel/core");
26
27
  var import_node = require("@aipanel/core/node");
27
28
  var import_system = require("../utils/system.cjs");
@@ -47,6 +48,11 @@ class AIPanelService {
47
48
  __publicField(this, "mcp", null);
48
49
  __publicField(this, "provider", null);
49
50
  __publicField(this, "unsubscribeEvents", null);
51
+ /**
52
+ * 宿主事件推送令牌(每轮 start 随机生成)。Provider 侧 Host 插件(dsh-plugin)推送
53
+ * ProviderEvent 时须携带该令牌,core 校验通过后按 SESSION_EVENT 广播给 SSE 客户端。
54
+ */
55
+ __publicField(this, "eventsToken", null);
50
56
  this.actualWebPort = config.webPort;
51
57
  this.actualProxyPort = config.proxyPort ?? import_core.DEFAULT_PROXY_PORT;
52
58
  }
@@ -81,6 +87,7 @@ class AIPanelService {
81
87
  throw new Error("Provider \u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 setProvider");
82
88
  }
83
89
  this.startPromise = (async () => {
90
+ this.eventsToken = (0, import_node_crypto.randomUUID)();
84
91
  const timer = log.timer("startServices", {
85
92
  corsOrigins,
86
93
  contextApiUrl,
@@ -117,6 +124,7 @@ class AIPanelService {
117
124
  this.sendTaskUpdate("preparing_runtime");
118
125
  this.sendTaskUpdate("starting_web");
119
126
  let webUrl;
127
+ let webAuthCookie;
120
128
  try {
121
129
  const startResult = await provider.start({
122
130
  port: this.actualWebPort,
@@ -127,11 +135,14 @@ class AIPanelService {
127
135
  contextApiUrl,
128
136
  logsApiUrl,
129
137
  verbose: this.config.verbose,
130
- vueDevtoolsApiUrl
138
+ vueDevtoolsApiUrl,
139
+ // 宿主事件推送令牌:provider 将其注入 Host 插件(dsh-plugin)配置,用于回推事件鉴权
140
+ eventsToken: this.eventsToken ?? void 0
131
141
  });
132
142
  this.webProcess = startResult.processHandle ?? null;
133
143
  timer.checkpoint("Web process started");
134
144
  webUrl = startResult.url;
145
+ webAuthCookie = startResult.webAuthCookie;
135
146
  log.debug(`Waiting for Web UI to become ready at ${webUrl}...`);
136
147
  this.sendTaskUpdate("waiting_web_ready");
137
148
  await (0, import_system.waitForServer)(webUrl, import_core.SERVER_START_TIMEOUT, this.webProcess ?? void 0);
@@ -168,7 +179,8 @@ class AIPanelService {
168
179
  try {
169
180
  const result = await (0, import_proxy_server.startProxyServer)(webUrl, this.actualProxyPort, {
170
181
  bridgeScript: provider.bridgeScript,
171
- hostname: this.config.hostname
182
+ hostname: this.config.hostname,
183
+ webAuthCookie
172
184
  });
173
185
  this.proxyServer = result.server;
174
186
  if (result.actualPort !== this.actualProxyPort) {
@@ -185,7 +197,8 @@ class AIPanelService {
185
197
  const nextPort = await (0, import_node.findAvailablePort)(this.actualProxyPort + 1, this.config.hostname);
186
198
  const result = await (0, import_proxy_server.startProxyServer)(webUrl, nextPort, {
187
199
  bridgeScript: provider.bridgeScript,
188
- hostname: this.config.hostname
200
+ hostname: this.config.hostname,
201
+ webAuthCookie
189
202
  });
190
203
  this.proxyServer = result.server;
191
204
  this.actualProxyPort = result.actualPort;
@@ -220,15 +233,7 @@ class AIPanelService {
220
233
  this.isStarted = true;
221
234
  this.unsubscribeEvents?.();
222
235
  this.unsubscribeEvents = provider.subscribeEvents((event) => {
223
- this.sseClients.forEach((client) => {
224
- try {
225
- client.write(`data: ${JSON.stringify({ type: "SESSION_EVENT", event })}
226
-
227
- `);
228
- } catch (e) {
229
- log.debug("Failed to send SESSION_EVENT", { error: e });
230
- }
231
- });
236
+ this.pushProviderEvent(event);
232
237
  });
233
238
  if (warmupFailed) {
234
239
  this.sendTaskUpdate("chrome_mcp_failed", {
@@ -242,6 +247,18 @@ class AIPanelService {
242
247
  })();
243
248
  return this.startPromise;
244
249
  }
250
+ /** 把一条 ProviderEvent 广播给所有 SSE 客户端(SESSION_EVENT 载荷) */
251
+ pushProviderEvent(event) {
252
+ this.sseClients.forEach((client) => {
253
+ try {
254
+ client.write(`data: ${JSON.stringify({ type: "SESSION_EVENT", event })}
255
+
256
+ `);
257
+ } catch (e) {
258
+ log.debug("Failed to send SESSION_EVENT", { error: e });
259
+ }
260
+ });
261
+ }
245
262
  async retryWarmupChromeMcp() {
246
263
  if (!this.mcp) {
247
264
  return { success: false, errorType: "UNKNOWN", errorMessage: "MCP not initialized" };
@@ -271,6 +288,7 @@ class AIPanelService {
271
288
  log.info("Stopping Web UI services...");
272
289
  this.unsubscribeEvents?.();
273
290
  this.unsubscribeEvents = null;
291
+ this.eventsToken = null;
274
292
  if (this.proxyServer) {
275
293
  log.debug("Closing proxy server");
276
294
  this.proxyServer.close();
@@ -1,6 +1,6 @@
1
1
  import type { ResultPromise } from "execa";
2
2
  import type http from "http";
3
- import type { PluginOptions, ServiceStartupTask, WebProvider } from "@aipanel/core";
3
+ import type { PluginOptions, ProviderEvent, ServiceStartupTask, WebProvider } from "@aipanel/core";
4
4
  import { ChromeMcpWarmupErrorType } from "@aipanel/core";
5
5
  import type { McpProxy } from "./mcp-proxy";
6
6
  export declare class AIPanelService {
@@ -25,11 +25,18 @@ export declare class AIPanelService {
25
25
  private mcp;
26
26
  private provider;
27
27
  private unsubscribeEvents;
28
+ /**
29
+ * 宿主事件推送令牌(每轮 start 随机生成)。Provider 侧 Host 插件(dsh-plugin)推送
30
+ * ProviderEvent 时须携带该令牌,core 校验通过后按 SESSION_EVENT 广播给 SSE 客户端。
31
+ */
32
+ eventsToken: string | null;
28
33
  constructor(config: Required<PluginOptions>, sseClients: Set<http.ServerResponse>, onPortAllocated: (port: number) => void, onProxyPortAllocated: (port: number) => void);
29
34
  /** 设置 Provider(由编排层动态加载后调用) */
30
35
  setProvider(provider: WebProvider): void;
31
36
  private sendTaskUpdate;
32
37
  start(vitePort: number, corsOrigins: string[], contextApiUrl: string, logsApiUrl: string, viteOrigin: string, mcp: McpProxy, vueDevtoolsApiUrl?: string): Promise<void>;
38
+ /** 把一条 ProviderEvent 广播给所有 SSE 客户端(SESSION_EVENT 载荷) */
39
+ pushProviderEvent(event: ProviderEvent): void;
33
40
  retryWarmupChromeMcp(): Promise<{
34
41
  success: boolean;
35
42
  errorType?: string;
@@ -0,0 +1,100 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+ var host_events_exports = {};
19
+ __export(host_events_exports, {
20
+ setupHostEventsEndpoint: () => setupHostEventsEndpoint
21
+ });
22
+ module.exports = __toCommonJS(host_events_exports);
23
+ var import_core = require("@aipanel/core");
24
+ var import_node = require("@aipanel/core/node");
25
+ const log = (0, import_node.createLogger)("Endpoints:HostEvents");
26
+ function setupHostEventsEndpoint(server, ctx) {
27
+ server.middlewares.use(import_core.HOST_EVENTS_API_PATH, async (req, res) => {
28
+ const reqCtx = new import_node.RequestContext(req.method || "GET", import_core.HOST_EVENTS_API_PATH);
29
+ res.setHeader("Content-Type", "application/json");
30
+ if (req.headers.origin) {
31
+ res.writeHead(403);
32
+ res.end(JSON.stringify({ error: "forbidden" }));
33
+ reqCtx.end(403);
34
+ return;
35
+ }
36
+ if (req.method === "OPTIONS") {
37
+ res.writeHead(204);
38
+ res.end();
39
+ reqCtx.end(204);
40
+ return;
41
+ }
42
+ if (req.method !== "POST") {
43
+ res.writeHead(405);
44
+ res.end(JSON.stringify({ error: "method not allowed" }));
45
+ reqCtx.end(405);
46
+ return;
47
+ }
48
+ let body = "";
49
+ req.on("data", (chunk) => body += chunk.toString());
50
+ req.on("error", () => {
51
+ res.writeHead(400);
52
+ res.end(JSON.stringify({ error: "request error" }));
53
+ reqCtx.end(400);
54
+ });
55
+ req.on("end", () => {
56
+ try {
57
+ const data = JSON.parse(body);
58
+ const expected = ctx.eventsToken;
59
+ if (!expected || data.token !== expected) {
60
+ res.writeHead(403);
61
+ res.end(JSON.stringify({ error: "invalid token" }));
62
+ reqCtx.end(403);
63
+ return;
64
+ }
65
+ const event = data.event;
66
+ if (!isProviderEvent(event)) {
67
+ res.writeHead(400);
68
+ res.end(JSON.stringify({ error: "invalid event" }));
69
+ reqCtx.end(400);
70
+ return;
71
+ }
72
+ ctx.pushProviderEvent(event);
73
+ res.writeHead(200);
74
+ res.end(JSON.stringify({ ok: true }));
75
+ reqCtx.end(200);
76
+ } catch {
77
+ res.writeHead(400);
78
+ res.end(JSON.stringify({ error: "invalid json" }));
79
+ reqCtx.end(400);
80
+ }
81
+ });
82
+ });
83
+ }
84
+ function isProviderEvent(value) {
85
+ if (typeof value !== "object" || value === null) return false;
86
+ const v = value;
87
+ if (v.type === "session.status" || v.type === "thinking") {
88
+ return typeof v.sessionId === "string" && v.sessionId.length > 0;
89
+ }
90
+ if (v.type === "session.updated") {
91
+ const s = v.session;
92
+ return typeof s === "object" && s !== null && typeof s.id === "string";
93
+ }
94
+ if (v.type === "connected") return true;
95
+ return false;
96
+ }
97
+ // Annotate the CommonJS export names for ESM import in node:
98
+ 0 && (module.exports = {
99
+ setupHostEventsEndpoint
100
+ });
@@ -0,0 +1,8 @@
1
+ import type { ViteDevServer } from "vite";
2
+ import type { EndpointContext } from "./types";
3
+ /**
4
+ * 宿主侧事件推送端点(dsh-plugin 等 Host 插件 → core → SSE 广播)。
5
+ * 只允许无浏览器 Origin 的服务端请求:携带每轮启动随机令牌(x-aipanel-token 头或 body.token),
6
+ * 通过校验后按 SESSION_EVENT 广播给全部 SSE 客户端(与 provider.subscribeEvents 同一通道)。
7
+ */
8
+ export declare function setupHostEventsEndpoint(server: ViteDevServer, ctx: EndpointContext): void;
@@ -28,6 +28,7 @@ var import_widget = require("./widget.cjs");
28
28
  var import_context = require("./context.cjs");
29
29
  var import_start = require("./start.cjs");
30
30
  var import_sse = require("./sse.cjs");
31
+ var import_host_events = require("./host-events.cjs");
31
32
  var import_sessions = require("./sessions.cjs");
32
33
  var import_warmup = require("./warmup.cjs");
33
34
  var import_logs = require("./logs.cjs");
@@ -40,6 +41,7 @@ function setupMiddlewares(server, ctx, mcp, logFiles) {
40
41
  (0, import_context.setupContextEndpoint)(server, ctx);
41
42
  (0, import_start.setupStartEndpoint)(server, ctx);
42
43
  (0, import_sse.setupSseEndpoint)(server, ctx);
44
+ (0, import_host_events.setupHostEventsEndpoint)(server, ctx);
43
45
  (0, import_sessions.setupSessionsEndpoint)(server, ctx);
44
46
  (0, import_warmup.setupWarmupEndpoint)(server, ctx);
45
47
  (0, import_logs.setupLogsEndpoint)(server);
@@ -39,8 +39,10 @@ function setupSessionsEndpoint(server, ctx) {
39
39
  try {
40
40
  if (req.method === "GET") {
41
41
  reqCtx.checkpoint("Fetching sessions");
42
+ const url = new URL(req.url || "", `http://${req.headers.host}`);
43
+ const activeSessionId = url.searchParams.get("current") || void 0;
42
44
  const [sessions, capabilities] = await Promise.all([
43
- ctx.getSessions(),
45
+ ctx.getSessions(activeSessionId),
44
46
  ctx.getCapabilities()
45
47
  ]);
46
48
  res.writeHead(200);
@@ -24,7 +24,7 @@ var import_core = require("@aipanel/core");
24
24
  var import_node = require("@aipanel/core/node");
25
25
  function setupStartEndpoint(server, ctx) {
26
26
  server.middlewares.use(import_core.START_API_PATH, async (_req, res) => {
27
- const reqCtx = new import_node.RequestContext("GET", import_core.START_API_PATH);
27
+ const reqCtx = new import_node.RequestContext("GET", import_core.START_API_PATH, { quiet: true });
28
28
  res.setHeader("Content-Type", "application/json");
29
29
  res.setHeader("Access-Control-Allow-Origin", "*");
30
30
  res.writeHead(200);
@@ -1,4 +1,4 @@
1
- import type { ChatSession, PageContext, ProviderCapabilities, ServiceStartupTask } from "@aipanel/core";
1
+ import type { ChatSession, PageContext, ProviderCapabilities, ProviderEvent, ServiceStartupTask } from "@aipanel/core";
2
2
  import type http from "http";
3
3
  export interface EndpointContext {
4
4
  get webUrl(): string | null;
@@ -19,11 +19,16 @@ export interface EndpointContext {
19
19
  get actualProxyPort(): number;
20
20
  get actualWebPort(): number;
21
21
  get serviceInstanceId(): string;
22
- getSessions: () => Promise<ChatSession[]>;
22
+ /** 获取会话列表;activeSessionId(当前选中会话)由客户端传入,供 Provider 对齐 UI 可见性规则 */
23
+ getSessions: (activeSessionId?: string) => Promise<ChatSession[]>;
23
24
  createSession: () => Promise<ChatSession>;
24
25
  deleteSession: (id: string) => Promise<void>;
25
26
  /** 获取当前 Provider 能力描述(客户端自适应行为依据) */
26
27
  getCapabilities: () => ProviderCapabilities;
28
+ /** 宿主事件推送令牌(Host 插件回推 ProviderEvent 时鉴权用;未启动时 null) */
29
+ get eventsToken(): string | null;
30
+ /** 广播一条 ProviderEvent 给所有 SSE 客户端(SESSION_EVENT 载荷;Host 插件回推与 Provider 订阅共用) */
31
+ pushProviderEvent: (event: ProviderEvent) => void;
27
32
  resolveWidgetPath: () => string;
28
33
  resolveWidgetStylePath: () => string;
29
34
  retryWarmupChromeMcp: () => Promise<{
package/lib/index.cjs CHANGED
@@ -57,7 +57,7 @@ const SILENT_CONTEXT_SCRIPT = `<script>
57
57
  if (!force && key === last) return;
58
58
  last = key;
59
59
  var sessionId = "";
60
- try { sessionId = sessionStorage.getItem("_aipanel_pk") || ""; } catch (e) {}
60
+ try { sessionId = sessionStorage.getItem("${import_core.SESSION_ID_KEY}") || ""; } catch (e) {}
61
61
  fetch(API, {
62
62
  method: "POST",
63
63
  headers: { "Content-Type": "application/json" },
@@ -113,7 +113,12 @@ function createAIPanelPlugin(options = {}) {
113
113
  let activeTabId = DEFAULT_TAB;
114
114
  const serviceInstanceId = import_crypto.default.randomUUID();
115
115
  const sseClients = /* @__PURE__ */ new Set();
116
- const mcpProxy = new import_mcp_proxy.McpProxy({ idleTimeout: 5 * 60 * 1e3 });
116
+ const mcpProxy = new import_mcp_proxy.McpProxy({
117
+ idleTimeout: 5 * 60 * 1e3,
118
+ // 用户 chrome MCP 透传:追加 args/env(核心受保护参数不可覆盖,由 McpProxy 过滤)
119
+ userArgs: config.chromeMcp?.args,
120
+ env: config.chromeMcp?.env
121
+ });
117
122
  let provider = null;
118
123
  const service = new import_service.AIPanelService(
119
124
  config,
@@ -180,9 +185,15 @@ function createAIPanelPlugin(options = {}) {
180
185
  get serviceInstanceId() {
181
186
  return serviceInstanceId;
182
187
  },
183
- getSessions: () => {
188
+ get eventsToken() {
189
+ return service.eventsToken;
190
+ },
191
+ pushProviderEvent(event) {
192
+ service.pushProviderEvent(event);
193
+ },
194
+ getSessions: (activeSessionId) => {
184
195
  if (!provider) return Promise.reject(new Error("Provider \u672A\u521D\u59CB\u5316"));
185
- return provider.listSessions(service.workspaceRoot);
196
+ return provider.listSessions(service.workspaceRoot, activeSessionId);
186
197
  },
187
198
  createSession: () => {
188
199
  if (!provider) return Promise.reject(new Error("Provider \u672A\u521D\u59CB\u5316"));
@@ -323,7 +334,7 @@ function createAIPanelPlugin(options = {}) {
323
334
  ];
324
335
  const titleInject = `<script>
325
336
  (function () {
326
- var KEY = "_aipanel_pk";
337
+ var KEY = "${import_core.SESSION_ID_KEY}";
327
338
  if (!sessionStorage.getItem(KEY)) {
328
339
  sessionStorage.setItem(KEY, "[" + Math.random().toString(36).slice(2, 10) + "]");
329
340
  }
@@ -24,20 +24,21 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  var import_fs = __toESM(require("fs"));
25
25
  var import_http = __toESM(require("http"));
26
26
  var import_path = __toESM(require("path"));
27
+ var import_core = require("@aipanel/core");
27
28
  const filePath = process.argv[2];
28
29
  if (!filePath) {
29
30
  console.error("Usage: node format-bridge.cjs <filePath>");
30
31
  process.exit(1);
31
32
  }
32
33
  const absPath = import_path.default.resolve(filePath);
33
- const PORT = parseInt(process.env.AIPANEL_VSCODE_PORT || "", 10) || 51939;
34
+ const PORT = parseInt(process.env[import_core.ENV_VSCODE_PORT] || "", 10) || import_core.VSCODE_EXTENSION_PORT;
34
35
  function touchFile() {
35
36
  const now = /* @__PURE__ */ new Date();
36
37
  import_fs.default.utimesSync(absPath, now, now);
37
38
  }
38
39
  function probeHealth() {
39
40
  return new Promise((resolve) => {
40
- const req = import_http.default.get(`http://127.0.0.1:${PORT}/health`, (res) => {
41
+ const req = import_http.default.get(`http://${import_core.DEFAULT_HOSTNAME}:${PORT}${import_core.VSCODE_ROUTE_HEALTH}`, (res) => {
41
42
  res.resume();
42
43
  resolve(res.statusCode === 200);
43
44
  });
@@ -53,9 +54,9 @@ function formatViaExtension() {
53
54
  const body = JSON.stringify({ filePath: absPath });
54
55
  const req = import_http.default.request(
55
56
  {
56
- hostname: "127.0.0.1",
57
+ hostname: import_core.DEFAULT_HOSTNAME,
57
58
  port: PORT,
58
- path: "/format",
59
+ path: import_core.VSCODE_ROUTE_FORMAT,
59
60
  method: "POST",
60
61
  headers: { "Content-Type": "application/json" },
61
62
  timeout: 5e3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-aipanel",
3
- "version": "1.2.7",
3
+ "version": "1.2.9",
4
4
  "description": "Embed OpenCode Web UI in your Vite dev server for real-time code modification and preview",
5
5
  "type": "module",
6
6
  "main": "lib/index.cjs",
@@ -41,12 +41,12 @@
41
41
  "execa": "^9.6.1",
42
42
  "typescript-language-server": "^5.3.0",
43
43
  "unplugin-vue-inspector": "^3.0.0",
44
- "@aipanel/core": "1.2.7",
45
- "@aipanel/provider-opencode": "1.2.7"
44
+ "@aipanel/core": "1.2.9",
45
+ "@aipanel/provider-opencode": "1.2.9"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "vite": ">=5.0.0",
49
- "@aipanel/provider-deepseek": "1.2.7"
49
+ "@aipanel/provider-deepseek": "1.2.9"
50
50
  },
51
51
  "peerDependenciesMeta": {
52
52
  "@aipanel/provider-deepseek": {
@@ -55,7 +55,7 @@
55
55
  },
56
56
  "devDependencies": {
57
57
  "sharp": "^0.34.5",
58
- "@aipanel/client": "1.2.7"
58
+ "@aipanel/client": "1.2.9"
59
59
  },
60
60
  "scripts": {
61
61
  "build": "pagoda-cli build && vite build -c vite.client.config.ts",