vite-plugin-aipanel 1.2.0-beta.4 → 1.2.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/README.md CHANGED
@@ -2,15 +2,9 @@
2
2
 
3
3
  在 Vite 开发环境中嵌入 AIPanel AI 助手,边聊天边改代码,HMR 实时预览。
4
4
 
5
- ## 快速开始
6
-
7
- ### 前置条件
5
+ 支持 OpenCode 与 DeepSeek Harness (dsh) 两种 AI 引擎,通过 `provider` 配置一键切换。
8
6
 
9
- 安装 [OpenCode CLI](https://opencode.ai)(AI 引擎,必须):
10
-
11
- ```bash
12
- curl -fsSL https://opencode.ai/install | bash
13
- ```
7
+ ## 快速开始
14
8
 
15
9
  ### 1. 安装插件
16
10
 
@@ -30,6 +24,8 @@ export default defineConfig({
30
24
  });
31
25
  ```
32
26
 
27
+ 默认使用 **OpenCode** 引擎(详见下方「选择 AI 引擎」)。
28
+
33
29
  ### 3. 启动开发服务器
34
30
 
35
31
  ```bash
@@ -48,12 +44,54 @@ Vite 插件会自动启动 AIPanel Web 服务并创建当前项目的 AI 会话
48
44
 
49
45
  > Edge / Arc / Brave 操作步骤相同,入口分别是 `edge://extensions/` / `arc://extensions/` / `brave://extensions/`。
50
46
 
47
+ ## 选择 AI 引擎
48
+
49
+ 插件内置两套 AI 引擎,通过 `providerOptions` 段的 `provider` 字段选择:
50
+
51
+ | provider | 引擎 | 额外依赖 | 特点 |
52
+ | ------------ | ----------------------- | ------------------------------- | ------------------------------ |
53
+ | `opencode` | [OpenCode CLI](https://opencode.ai) | 无(插件内置) | 默认引擎,通用 AI CLI |
54
+ | `deepseek` | DeepSeek Harness (dsh) | 另装 `@aipanel/provider-deepseek` | DeepSeek 官方 Web 对话界面 |
55
+
56
+ 使用 DeepSeek Harness 引擎需先安装依赖:
57
+
58
+ ```bash
59
+ npm install -D @aipanel/provider-deepseek
60
+ npm install -g @deepseek-ai/dsh
61
+ ```
62
+
63
+ ```ts
64
+ // 使用 DeepSeek Harness 引擎
65
+ aipanelAssistant({
66
+ provider: "deepseek",
67
+ providerOptions: {
68
+ // dsh 专属配置,详见在线文档
69
+ // home: "~/.dsh", // dsh 数据目录
70
+ // agentPreset: "standard", // 新建会话的默认 Agent 预设
71
+ // permissionPreset: "read-only", // 默认权限预设
72
+ // busyEnter: "queue", // 繁忙时 Enter 行为
73
+ },
74
+ });
75
+ ```
76
+
77
+ > 已预装 OpenCode 与 dsh 其中之一的用户,插件会自动检测并使用。
78
+
79
+ ## 纯净 MCP 模式
80
+
81
+ `mcpOnly: true` 时,插件只暴露 MCP 工具服务(Chrome DevTools 控制、Vue DevTools、日志读取等),不注入挂件、不启动 AI 引擎,适合作为独立 MCP server 供外部 Agent 消费。
82
+
83
+ ```ts
84
+ aipanelAssistant({ mcpOnly: true });
85
+ ```
86
+
87
+ 启动后 MCP 端点挂在 Vite dev server 上,外部 MCP 客户端配置 Streamable HTTP 接入 `http://localhost:5173/__aipanel_mcp__`。
88
+
51
89
  ## 工作原理
52
90
 
53
91
  ```
54
92
  ┌────────────────┐ ┌────────────────┐ ┌─────────────┐
55
- │ Vite 插件 │ │ 浏览器扩展 │ │ OpenCode
56
- │ 启动 Web 服务 │◀────▶│ 侧边栏面板 │◀────▶│ AI 引擎
93
+ │ Vite 插件 │ │ 浏览器扩展 │ │ OpenCode/dsh
94
+ │ 启动 Web 服务 │◀────▶│ 侧边栏面板 │◀────▶│ AI 引擎
57
95
  │ 代理跨域 │ │ 页面上下文同步 │ │ 会话管理 │
58
96
  └────────────────┘ └────────────────┘ └─────────────┘
59
97
  ```
@@ -17,7 +17,7 @@ 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, _idleTimer, _idleTimeout, _McpProxy_instances, doStart_fn, resetIdleTimer_fn;
20
+ var _proc, _rl, _messageId, _internalIdBase, _pending, _args, _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";
@@ -44,6 +44,10 @@ class McpProxy {
44
44
  __privateAdd(this, _pending, /* @__PURE__ */ new Map());
45
45
  __privateAdd(this, _args);
46
46
  __privateAdd(this, _startPromise, null);
47
+ /** 最近一次进程退出信息,用于生成精确错误(无退出记录时为 null) */
48
+ __privateAdd(this, _lastExit, null);
49
+ /** 捕获的 stderr 最近若干行,进程异常退出时作为诊断信息 */
50
+ __privateAdd(this, _stderrTail, []);
47
51
  __privateAdd(this, _idleTimer, null);
48
52
  __privateAdd(this, _idleTimeout);
49
53
  __publicField(this, "sessionId");
@@ -61,14 +65,19 @@ class McpProxy {
61
65
  async start() {
62
66
  if (this.isRunning) return;
63
67
  if (__privateGet(this, _startPromise)) return __privateGet(this, _startPromise);
64
- __privateSet(this, _startPromise, __privateMethod(this, _McpProxy_instances, doStart_fn).call(this));
65
- return __privateGet(this, _startPromise);
68
+ const p = __privateMethod(this, _McpProxy_instances, doStart_fn).call(this);
69
+ __privateSet(this, _startPromise, p);
70
+ try {
71
+ await p;
72
+ } finally {
73
+ __privateSet(this, _startPromise, null);
74
+ }
66
75
  }
67
76
  /** 转发原始 JSON-RPC 请求,保留客户端 ID */
68
77
  async forward(rawRequest) {
69
78
  await this.start();
70
79
  if (!__privateGet(this, _proc) || !__privateGet(this, _proc).stdin) {
71
- throw new Error("MCP process not available");
80
+ throw new Error(__privateMethod(this, _McpProxy_instances, formatNotAvailable_fn).call(this));
72
81
  }
73
82
  __privateMethod(this, _McpProxy_instances, resetIdleTimer_fn).call(this);
74
83
  let msg;
@@ -93,7 +102,7 @@ class McpProxy {
93
102
  async call(method, params = {}) {
94
103
  await this.start();
95
104
  if (!__privateGet(this, _proc) || !__privateGet(this, _proc).stdin) {
96
- throw new Error("MCP process not available");
105
+ throw new Error(__privateMethod(this, _McpProxy_instances, formatNotAvailable_fn).call(this));
97
106
  }
98
107
  __privateMethod(this, _McpProxy_instances, resetIdleTimer_fn).call(this);
99
108
  const id = __privateGet(this, _internalIdBase) + ++__privateWrapper(this, _messageId)._;
@@ -141,6 +150,8 @@ _internalIdBase = new WeakMap();
141
150
  _pending = new WeakMap();
142
151
  _args = new WeakMap();
143
152
  _startPromise = new WeakMap();
153
+ _lastExit = new WeakMap();
154
+ _stderrTail = new WeakMap();
144
155
  _idleTimer = new WeakMap();
145
156
  _idleTimeout = new WeakMap();
146
157
  _McpProxy_instances = new WeakSet();
@@ -148,6 +159,8 @@ doStart_fn = async function() {
148
159
  log.debug("Starting MCP process", { args: __privateGet(this, _args) });
149
160
  try {
150
161
  const binPath = resolveChromeDevToolsMcpBin();
162
+ __privateSet(this, _lastExit, null);
163
+ __privateSet(this, _stderrTail, []);
151
164
  __privateSet(this, _proc, spawn(process.execPath, [binPath, ...__privateGet(this, _args)], {
152
165
  stdio: ["pipe", "pipe", "pipe"]
153
166
  }));
@@ -179,26 +192,56 @@ doStart_fn = async function() {
179
192
  __privateGet(this, _proc).stderr?.on("data", (d) => {
180
193
  const text = d.toString().trim();
181
194
  if (!text || /No handler registered for issue code \w+/i.test(text)) return;
195
+ __privateGet(this, _stderrTail).push(...text.split("\n"));
196
+ if (__privateGet(this, _stderrTail).length > 20) __privateGet(this, _stderrTail).splice(0, __privateGet(this, _stderrTail).length - 20);
182
197
  log.debug("[MCP stderr]", { text: text.substring(0, 200) });
183
198
  });
184
- __privateGet(this, _proc).on("close", (code) => {
185
- log.debug("MCP process closed", { code });
199
+ __privateGet(this, _proc).on("close", (code, signal) => {
200
+ const exitDesc = `code ${code ?? "null"}${signal ? ` (signal ${signal})` : ""}`;
201
+ const abnormalExit = code !== null && code !== 0 || signal !== null && signal !== "SIGTERM";
202
+ if (abnormalExit) {
203
+ log.warn("MCP process exited abnormally", {
204
+ code,
205
+ signal,
206
+ stderrTail: __privateGet(this, _stderrTail).join("\n").slice(0, 500)
207
+ });
208
+ } else {
209
+ log.debug("MCP process closed", { code, signal });
210
+ }
211
+ __privateSet(this, _lastExit, { code, signal, stderrTail: __privateGet(this, _stderrTail).join("\n") });
186
212
  for (const resolve of __privateGet(this, _pending).values()) {
187
- resolve({ error: { code: -32e3, message: `MCP process exited with code ${code}` } });
213
+ resolve({ error: { code: -32e3, message: `MCP process exited with ${exitDesc}` } });
188
214
  }
189
215
  __privateGet(this, _pending).clear();
190
216
  __privateSet(this, _proc, null);
191
217
  __privateSet(this, _rl, null);
192
218
  __privateSet(this, _startPromise, null);
193
219
  });
194
- await this.call("initialize", {
220
+ const initResult = await this.call("initialize", {
195
221
  protocolVersion: "2024-11-05",
196
222
  capabilities: {},
197
223
  clientInfo: { name: "vite-plugin-aipanel", version: "1.0.0" }
198
224
  });
225
+ if (!__privateGet(this, _proc)) {
226
+ throw new Error(__privateMethod(this, _McpProxy_instances, formatNotAvailable_fn).call(this));
227
+ }
228
+ if (initResult?.error) {
229
+ throw new Error(
230
+ `MCP initialize failed: ${initResult.error.message ?? JSON.stringify(initResult.error)}`
231
+ );
232
+ }
199
233
  log.debug("MCP proxy ready");
200
234
  __privateSet(this, _startPromise, null);
201
235
  };
236
+ /** 生成 "MCP process not available" 的精确错误信息(包含最近一次退出原因与 stderr) */
237
+ formatNotAvailable_fn = function() {
238
+ const exit = __privateGet(this, _lastExit);
239
+ if (!exit) return "MCP process not available";
240
+ const exitDesc = `code ${exit.code ?? "null"}${exit.signal ? ` (signal ${exit.signal})` : ""}`;
241
+ const tail = exit.stderrTail ? `
242
+ stderr: ${exit.stderrTail}` : "";
243
+ return `MCP process not available (last exit ${exitDesc})${tail}`;
244
+ };
202
245
  resetIdleTimer_fn = function() {
203
246
  if (__privateGet(this, _idleTimeout) <= 0) return;
204
247
  if (__privateGet(this, _idleTimer)) clearTimeout(__privateGet(this, _idleTimer));
@@ -53,7 +53,7 @@ var import_node_readline = require("node:readline");
53
53
  var import_node_crypto = __toESM(require("node:crypto"));
54
54
  var import_node_path = __toESM(require("node:path"));
55
55
  var import_node = require("@aipanel/core/node");
56
- var _proc, _rl, _messageId, _internalIdBase, _pending, _args, _startPromise, _idleTimer, _idleTimeout, _McpProxy_instances, doStart_fn, resetIdleTimer_fn;
56
+ var _proc, _rl, _messageId, _internalIdBase, _pending, _args, _startPromise, _lastExit, _stderrTail, _idleTimer, _idleTimeout, _McpProxy_instances, doStart_fn, formatNotAvailable_fn, resetIdleTimer_fn;
57
57
  const log = (0, import_node.createLogger)("McpProxy");
58
58
  function resolveChromeDevToolsMcpBin() {
59
59
  const pluginDir = (0, import_node.resolvePackageDir)("vite-plugin-aipanel");
@@ -75,6 +75,10 @@ class McpProxy {
75
75
  __privateAdd(this, _pending, /* @__PURE__ */ new Map());
76
76
  __privateAdd(this, _args);
77
77
  __privateAdd(this, _startPromise, null);
78
+ /** 最近一次进程退出信息,用于生成精确错误(无退出记录时为 null) */
79
+ __privateAdd(this, _lastExit, null);
80
+ /** 捕获的 stderr 最近若干行,进程异常退出时作为诊断信息 */
81
+ __privateAdd(this, _stderrTail, []);
78
82
  __privateAdd(this, _idleTimer, null);
79
83
  __privateAdd(this, _idleTimeout);
80
84
  __publicField(this, "sessionId");
@@ -92,14 +96,19 @@ class McpProxy {
92
96
  async start() {
93
97
  if (this.isRunning) return;
94
98
  if (__privateGet(this, _startPromise)) return __privateGet(this, _startPromise);
95
- __privateSet(this, _startPromise, __privateMethod(this, _McpProxy_instances, doStart_fn).call(this));
96
- return __privateGet(this, _startPromise);
99
+ const p = __privateMethod(this, _McpProxy_instances, doStart_fn).call(this);
100
+ __privateSet(this, _startPromise, p);
101
+ try {
102
+ await p;
103
+ } finally {
104
+ __privateSet(this, _startPromise, null);
105
+ }
97
106
  }
98
107
  /** 转发原始 JSON-RPC 请求,保留客户端 ID */
99
108
  async forward(rawRequest) {
100
109
  await this.start();
101
110
  if (!__privateGet(this, _proc) || !__privateGet(this, _proc).stdin) {
102
- throw new Error("MCP process not available");
111
+ throw new Error(__privateMethod(this, _McpProxy_instances, formatNotAvailable_fn).call(this));
103
112
  }
104
113
  __privateMethod(this, _McpProxy_instances, resetIdleTimer_fn).call(this);
105
114
  let msg;
@@ -124,7 +133,7 @@ class McpProxy {
124
133
  async call(method, params = {}) {
125
134
  await this.start();
126
135
  if (!__privateGet(this, _proc) || !__privateGet(this, _proc).stdin) {
127
- throw new Error("MCP process not available");
136
+ throw new Error(__privateMethod(this, _McpProxy_instances, formatNotAvailable_fn).call(this));
128
137
  }
129
138
  __privateMethod(this, _McpProxy_instances, resetIdleTimer_fn).call(this);
130
139
  const id = __privateGet(this, _internalIdBase) + ++__privateWrapper(this, _messageId)._;
@@ -172,6 +181,8 @@ _internalIdBase = new WeakMap();
172
181
  _pending = new WeakMap();
173
182
  _args = new WeakMap();
174
183
  _startPromise = new WeakMap();
184
+ _lastExit = new WeakMap();
185
+ _stderrTail = new WeakMap();
175
186
  _idleTimer = new WeakMap();
176
187
  _idleTimeout = new WeakMap();
177
188
  _McpProxy_instances = new WeakSet();
@@ -179,6 +190,8 @@ doStart_fn = async function() {
179
190
  log.debug("Starting MCP process", { args: __privateGet(this, _args) });
180
191
  try {
181
192
  const binPath = resolveChromeDevToolsMcpBin();
193
+ __privateSet(this, _lastExit, null);
194
+ __privateSet(this, _stderrTail, []);
182
195
  __privateSet(this, _proc, (0, import_node_child_process.spawn)(process.execPath, [binPath, ...__privateGet(this, _args)], {
183
196
  stdio: ["pipe", "pipe", "pipe"]
184
197
  }));
@@ -210,26 +223,56 @@ doStart_fn = async function() {
210
223
  __privateGet(this, _proc).stderr?.on("data", (d) => {
211
224
  const text = d.toString().trim();
212
225
  if (!text || /No handler registered for issue code \w+/i.test(text)) return;
226
+ __privateGet(this, _stderrTail).push(...text.split("\n"));
227
+ if (__privateGet(this, _stderrTail).length > 20) __privateGet(this, _stderrTail).splice(0, __privateGet(this, _stderrTail).length - 20);
213
228
  log.debug("[MCP stderr]", { text: text.substring(0, 200) });
214
229
  });
215
- __privateGet(this, _proc).on("close", (code) => {
216
- log.debug("MCP process closed", { code });
230
+ __privateGet(this, _proc).on("close", (code, signal) => {
231
+ const exitDesc = `code ${code ?? "null"}${signal ? ` (signal ${signal})` : ""}`;
232
+ const abnormalExit = code !== null && code !== 0 || signal !== null && signal !== "SIGTERM";
233
+ if (abnormalExit) {
234
+ log.warn("MCP process exited abnormally", {
235
+ code,
236
+ signal,
237
+ stderrTail: __privateGet(this, _stderrTail).join("\n").slice(0, 500)
238
+ });
239
+ } else {
240
+ log.debug("MCP process closed", { code, signal });
241
+ }
242
+ __privateSet(this, _lastExit, { code, signal, stderrTail: __privateGet(this, _stderrTail).join("\n") });
217
243
  for (const resolve of __privateGet(this, _pending).values()) {
218
- resolve({ error: { code: -32e3, message: `MCP process exited with code ${code}` } });
244
+ resolve({ error: { code: -32e3, message: `MCP process exited with ${exitDesc}` } });
219
245
  }
220
246
  __privateGet(this, _pending).clear();
221
247
  __privateSet(this, _proc, null);
222
248
  __privateSet(this, _rl, null);
223
249
  __privateSet(this, _startPromise, null);
224
250
  });
225
- await this.call("initialize", {
251
+ const initResult = await this.call("initialize", {
226
252
  protocolVersion: "2024-11-05",
227
253
  capabilities: {},
228
254
  clientInfo: { name: "vite-plugin-aipanel", version: "1.0.0" }
229
255
  });
256
+ if (!__privateGet(this, _proc)) {
257
+ throw new Error(__privateMethod(this, _McpProxy_instances, formatNotAvailable_fn).call(this));
258
+ }
259
+ if (initResult?.error) {
260
+ throw new Error(
261
+ `MCP initialize failed: ${initResult.error.message ?? JSON.stringify(initResult.error)}`
262
+ );
263
+ }
230
264
  log.debug("MCP proxy ready");
231
265
  __privateSet(this, _startPromise, null);
232
266
  };
267
+ /** 生成 "MCP process not available" 的精确错误信息(包含最近一次退出原因与 stderr) */
268
+ formatNotAvailable_fn = function() {
269
+ const exit = __privateGet(this, _lastExit);
270
+ if (!exit) return "MCP process not available";
271
+ const exitDesc = `code ${exit.code ?? "null"}${exit.signal ? ` (signal ${exit.signal})` : ""}`;
272
+ const tail = exit.stderrTail ? `
273
+ stderr: ${exit.stderrTail}` : "";
274
+ return `MCP process not available (last exit ${exitDesc})${tail}`;
275
+ };
233
276
  resetIdleTimer_fn = function() {
234
277
  if (__privateGet(this, _idleTimeout) <= 0) return;
235
278
  if (__privateGet(this, _idleTimer)) clearTimeout(__privateGet(this, _idleTimer));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-aipanel",
3
- "version": "1.2.0-beta.4",
3
+ "version": "1.2.1",
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",
@@ -37,16 +37,16 @@
37
37
  "dependencies": {
38
38
  "@vue/devtools-kit": "^8.0.0",
39
39
  "@vue/language-server": "^3.3.7",
40
- "chrome-devtools-mcp": "1.7.0",
40
+ "chrome-devtools-mcp": "^1.8.0",
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.0-beta.4",
45
- "@aipanel/provider-opencode": "1.2.0-beta.4"
44
+ "@aipanel/core": "1.2.1",
45
+ "@aipanel/provider-opencode": "1.2.1"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "vite": ">=5.0.0",
49
- "@aipanel/provider-deepseek": "1.2.0-beta.4"
49
+ "@aipanel/provider-deepseek": "1.2.1"
50
50
  },
51
51
  "peerDependenciesMeta": {
52
52
  "@aipanel/provider-deepseek": {
@@ -55,14 +55,10 @@
55
55
  },
56
56
  "devDependencies": {
57
57
  "sharp": "^0.34.5",
58
- "@aipanel/client": "1.2.0-beta.4"
58
+ "@aipanel/client": "1.2.1"
59
59
  },
60
60
  "scripts": {
61
- "prepare-eslint-lsp": "node scripts/prepare-eslint-lsp.mjs",
62
61
  "build": "pagoda-cli build && vite build -c vite.client.config.ts",
63
- "dev": "pagoda-cli dev",
64
- "site": "pagoda-cli site",
65
- "build:site": "pagoda-cli build-site",
66
62
  "typecheck": "tsc -p tsconfig.declaration.json --noEmit"
67
63
  }
68
64
  }