ms-vite-plugin 1.1.11 → 1.1.13

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 (2) hide show
  1. package/dist/mcp/tools.js +451 -601
  2. package/package.json +1 -1
package/dist/mcp/tools.js CHANGED
@@ -40,16 +40,375 @@ const z = __importStar(require("zod/v4"));
40
40
  const fsExtra = __importStar(require("fs-extra"));
41
41
  const os = __importStar(require("os"));
42
42
  const path = __importStar(require("path"));
43
- const build_1 = require("../build");
44
43
  const packager_1 = require("../packager");
45
44
  const project_1 = require("../project");
46
- const ws_manager_1 = require("../ws-manager");
47
45
  const project_2 = require("./project");
48
46
  const device_config_1 = require("./device-config");
49
47
  Object.defineProperty(exports, "DEFAULT_DEVICE_PORT", { enumerable: true, get: function () { return device_config_1.DEFAULT_DEVICE_PORT; } });
50
48
  const docs_service_1 = require("./docs-service");
51
49
  const types_1 = require("./types");
52
50
  const version_1 = require("../version");
51
+ /**
52
+ * 设备日志内存缓存上限
53
+ * - 采用环形窗口思路,仅保留最新 5000 条,避免 MCP 进程长期运行时内存无限增长
54
+ */
55
+ const DEVICE_LOG_MEMORY_LIMIT = 5000;
56
+ /**
57
+ * SSE 重连等待时间(毫秒)
58
+ * - 当设备日志流异常断开时,后台会自动尝试重连
59
+ */
60
+ const DEVICE_LOG_RECONNECT_DELAY_MS = 1500;
61
+ /**
62
+ * 视为“仍在工作中”的日志订阅状态集合
63
+ * - 用于判断同一设备是否可以直接复用已有后台连接
64
+ */
65
+ const ACTIVE_DEVICE_LOG_SUBSCRIPTION_STATUSES = new Set([
66
+ "connecting",
67
+ "connected",
68
+ "reconnecting",
69
+ ]);
70
+ /**
71
+ * 进程级设备日志订阅状态
72
+ * - 当前 MCP 仅维护单设备日志缓存
73
+ * - MCP Tool 处理函数会复用同一份内存缓存
74
+ */
75
+ const deviceLogSubscriptionState = {
76
+ target: null,
77
+ status: "idle",
78
+ lastError: null,
79
+ lastEventAt: null,
80
+ startedAt: null,
81
+ logs: [],
82
+ runtimeStatus: [],
83
+ generation: 0,
84
+ controller: null,
85
+ reconnectTimer: null,
86
+ };
87
+ /**
88
+ * 向环形缓冲区追加数据
89
+ * @param buffer 目标数组
90
+ * @param entry 待写入的数据
91
+ * @param limit 数组最大长度
92
+ * @returns 无返回值
93
+ * @example
94
+ * pushRingBuffer([1, 2], 3, 2)
95
+ */
96
+ function pushRingBuffer(buffer, entry, limit) {
97
+ if (buffer.length >= limit) {
98
+ buffer.splice(0, buffer.length - limit + 1);
99
+ }
100
+ buffer.push(entry);
101
+ }
102
+ /**
103
+ * 创建标准 MCP 文本内容对象
104
+ * @param text 返回文本
105
+ * @returns 返回 `{ type: "text", text }` 结构
106
+ * @example
107
+ * createTextContent("ok")
108
+ */
109
+ function createTextContent(text) {
110
+ return {
111
+ type: "text",
112
+ text,
113
+ };
114
+ }
115
+ /**
116
+ * 创建标准 MCP 文本工具返回值
117
+ * @param text 返回文本
118
+ * @param isError 是否标记为错误响应
119
+ * @returns 返回包含单条文本内容的 MCP Tool 结果
120
+ * @example
121
+ * createTextToolResult("done")
122
+ */
123
+ function createTextToolResult(text, isError = false) {
124
+ return {
125
+ content: [createTextContent(text)],
126
+ ...(isError ? { isError: true } : {}),
127
+ };
128
+ }
129
+ /**
130
+ * 构建运行时 HTTP 请求参数
131
+ * @param target 已解析的设备目标
132
+ * @returns 返回项目内部设备请求参数
133
+ * @example
134
+ * const options = createRuntimeHttpRequestOptions(target)
135
+ */
136
+ function createRuntimeHttpRequestOptions(target) {
137
+ return {
138
+ ip: target.ip,
139
+ port: target.port,
140
+ transport: "http",
141
+ };
142
+ }
143
+ /**
144
+ * 重置当前日志缓存快照
145
+ * @returns 无返回值
146
+ * @example
147
+ * resetDeviceLogSnapshotState()
148
+ */
149
+ function resetDeviceLogSnapshotState() {
150
+ deviceLogSubscriptionState.lastError = null;
151
+ deviceLogSubscriptionState.lastEventAt = null;
152
+ deviceLogSubscriptionState.startedAt = new Date().toISOString();
153
+ deviceLogSubscriptionState.logs = [];
154
+ deviceLogSubscriptionState.runtimeStatus = [];
155
+ }
156
+ /**
157
+ * 判断日志订阅是否仍然活跃
158
+ * @returns 活跃返回 true,否则返回 false
159
+ * @example
160
+ * const active = isDeviceLogSubscriptionActive()
161
+ */
162
+ function isDeviceLogSubscriptionActive() {
163
+ return (ACTIVE_DEVICE_LOG_SUBSCRIPTION_STATUSES.has(deviceLogSubscriptionState.status) || deviceLogSubscriptionState.reconnectTimer !== null);
164
+ }
165
+ /**
166
+ * 解析单个 SSE 文本块
167
+ * @param rawBlock 原始 SSE 文本块
168
+ * @returns 返回事件名与 data 字符串
169
+ * @example
170
+ * parseSseEventBlock("event: log\ndata: {\"message\":\"ok\"}")
171
+ */
172
+ function parseSseEventBlock(rawBlock) {
173
+ const lines = rawBlock.split(/\r?\n/);
174
+ let eventName = "message";
175
+ const dataLines = [];
176
+ for (const line of lines) {
177
+ if (line.startsWith("event:")) {
178
+ eventName = line.slice(6).trim() || "message";
179
+ continue;
180
+ }
181
+ if (line.startsWith("data:")) {
182
+ dataLines.push(line.slice(5).trimStart());
183
+ }
184
+ }
185
+ return {
186
+ event: eventName,
187
+ data: dataLines.join("\n"),
188
+ };
189
+ }
190
+ /**
191
+ * 将原始日志对象标准化为统一结构
192
+ * @param payload 日志原始对象
193
+ * @returns 返回标准化后的日志
194
+ * @example
195
+ * normalizeBufferedLogEntry({ level: "info", message: "started" })
196
+ */
197
+ function normalizeBufferedLogEntry(payload) {
198
+ const levelText = String(payload.level ?? "info").toLowerCase();
199
+ const messageText = String(payload.message ?? "");
200
+ const timestampText = typeof payload.timestamp === "string" && payload.timestamp
201
+ ? payload.timestamp
202
+ : new Date().toISOString();
203
+ return {
204
+ level: levelText,
205
+ message: messageText,
206
+ timestamp: timestampText,
207
+ };
208
+ }
209
+ /**
210
+ * 清理当前后台日志订阅
211
+ * @returns 无返回值
212
+ * @example
213
+ * stopDeviceLogSubscription()
214
+ */
215
+ function stopDeviceLogSubscription() {
216
+ if (deviceLogSubscriptionState.reconnectTimer) {
217
+ clearTimeout(deviceLogSubscriptionState.reconnectTimer);
218
+ deviceLogSubscriptionState.reconnectTimer = null;
219
+ }
220
+ if (deviceLogSubscriptionState.controller) {
221
+ deviceLogSubscriptionState.controller.abort();
222
+ deviceLogSubscriptionState.controller = null;
223
+ }
224
+ }
225
+ /**
226
+ * 安排后台重连
227
+ * @param generation 当前订阅代次
228
+ * @returns 无返回值
229
+ * @example
230
+ * scheduleDeviceLogReconnect(1)
231
+ */
232
+ function scheduleDeviceLogReconnect(generation) {
233
+ if (generation !== deviceLogSubscriptionState.generation ||
234
+ !deviceLogSubscriptionState.target) {
235
+ return;
236
+ }
237
+ if (deviceLogSubscriptionState.reconnectTimer) {
238
+ return;
239
+ }
240
+ deviceLogSubscriptionState.status = "reconnecting";
241
+ deviceLogSubscriptionState.reconnectTimer = setTimeout(() => {
242
+ deviceLogSubscriptionState.reconnectTimer = null;
243
+ void startDeviceLogSubscriptionLoop(generation);
244
+ }, DEVICE_LOG_RECONNECT_DELAY_MS);
245
+ }
246
+ /**
247
+ * 启动一次 SSE 读取循环
248
+ * - 该函数仅负责单次连接;若连接中断,会由外层自动调度重连
249
+ * @param generation 当前订阅代次
250
+ * @returns 读取流程结束后返回 Promise<void>
251
+ * @example
252
+ * await startDeviceLogSubscriptionLoop(1)
253
+ */
254
+ async function startDeviceLogSubscriptionLoop(generation) {
255
+ if (generation !== deviceLogSubscriptionState.generation ||
256
+ !deviceLogSubscriptionState.target) {
257
+ return;
258
+ }
259
+ const { ip, port } = deviceLogSubscriptionState.target;
260
+ const controller = new AbortController();
261
+ const url = `http://${ip}:${port}/logger/sse`;
262
+ deviceLogSubscriptionState.controller = controller;
263
+ deviceLogSubscriptionState.status =
264
+ deviceLogSubscriptionState.lastEventAt === null
265
+ ? "connecting"
266
+ : "reconnecting";
267
+ try {
268
+ const response = await fetch(url, {
269
+ method: "GET",
270
+ signal: controller.signal,
271
+ headers: {
272
+ Accept: "text/event-stream",
273
+ },
274
+ });
275
+ if (!response.ok || !response.body) {
276
+ throw new Error(`连接日志流失败: ${response.status} ${response.statusText || "unknown"}`);
277
+ }
278
+ deviceLogSubscriptionState.status = "connected";
279
+ deviceLogSubscriptionState.lastError = null;
280
+ const reader = response.body.getReader();
281
+ const decoder = new TextDecoder("utf-8");
282
+ let buffer = "";
283
+ while (generation === deviceLogSubscriptionState.generation) {
284
+ const { value, done } = await reader.read();
285
+ if (done) {
286
+ break;
287
+ }
288
+ buffer += decoder.decode(value, { stream: true });
289
+ let splitIndex = buffer.search(/\r?\n\r?\n/);
290
+ while (splitIndex >= 0) {
291
+ const rawBlock = buffer.slice(0, splitIndex);
292
+ buffer = buffer.slice(splitIndex + (buffer[splitIndex] === "\r" ? 4 : 2));
293
+ const event = parseSseEventBlock(rawBlock);
294
+ if (event.data) {
295
+ try {
296
+ const parsed = JSON.parse(event.data);
297
+ deviceLogSubscriptionState.lastEventAt = new Date().toISOString();
298
+ if (event.event === "log") {
299
+ pushRingBuffer(deviceLogSubscriptionState.logs, normalizeBufferedLogEntry(parsed), DEVICE_LOG_MEMORY_LIMIT);
300
+ }
301
+ else if (event.event === "runtime_status") {
302
+ pushRingBuffer(deviceLogSubscriptionState.runtimeStatus, {
303
+ raw: parsed,
304
+ timestamp: new Date().toISOString(),
305
+ }, DEVICE_LOG_MEMORY_LIMIT);
306
+ }
307
+ }
308
+ catch {
309
+ // 忽略单条非法 JSON,避免因为异常日志阻塞整个后台连接
310
+ }
311
+ }
312
+ splitIndex = buffer.search(/\r?\n\r?\n/);
313
+ }
314
+ }
315
+ if (generation === deviceLogSubscriptionState.generation) {
316
+ scheduleDeviceLogReconnect(generation);
317
+ }
318
+ }
319
+ catch (error) {
320
+ if (error instanceof Error &&
321
+ (error.name === "AbortError" ||
322
+ error.message === "The operation was aborted.")) {
323
+ return;
324
+ }
325
+ if (generation === deviceLogSubscriptionState.generation) {
326
+ deviceLogSubscriptionState.status = "error";
327
+ deviceLogSubscriptionState.lastError =
328
+ error instanceof Error ? error.message : String(error);
329
+ scheduleDeviceLogReconnect(generation);
330
+ }
331
+ }
332
+ finally {
333
+ if (deviceLogSubscriptionState.controller === controller) {
334
+ deviceLogSubscriptionState.controller = null;
335
+ }
336
+ }
337
+ }
338
+ /**
339
+ * 确保后台日志订阅处于目标设备上
340
+ * - 若目标未变化且订阅仍在运行,则直接复用已有连接
341
+ * - 若目标发生变化,则重置缓存并重新建立订阅
342
+ * @param ip 设备 IP
343
+ * @param port 设备端口
344
+ * @returns 返回是否复用了现有订阅
345
+ * @example
346
+ * const reused = ensureDeviceLogSubscription("192.168.1.10", 9800)
347
+ */
348
+ function ensureDeviceLogSubscription(ip, port) {
349
+ const sameTarget = deviceLogSubscriptionState.target?.ip === ip &&
350
+ deviceLogSubscriptionState.target?.port === port;
351
+ const isActive = isDeviceLogSubscriptionActive();
352
+ if (sameTarget && isActive) {
353
+ return true;
354
+ }
355
+ stopDeviceLogSubscription();
356
+ deviceLogSubscriptionState.generation += 1;
357
+ deviceLogSubscriptionState.target = { ip, port };
358
+ deviceLogSubscriptionState.status = "connecting";
359
+ resetDeviceLogSnapshotState();
360
+ void startDeviceLogSubscriptionLoop(deviceLogSubscriptionState.generation);
361
+ return false;
362
+ }
363
+ /**
364
+ * 生成日志缓存快照文本
365
+ * @param limit 需要返回的日志条数
366
+ * @param runtimeStatusLimit 需要返回的 runtime_status 条数
367
+ * @returns 返回用于 MCP 文本响应的快照内容
368
+ * @example
369
+ * const text = buildDeviceLogSnapshotText(100, 20)
370
+ */
371
+ function buildDeviceLogSnapshotText(limit, runtimeStatusLimit) {
372
+ const targetText = deviceLogSubscriptionState.target
373
+ ? `${deviceLogSubscriptionState.target.ip}:${deviceLogSubscriptionState.target.port}`
374
+ : "未订阅";
375
+ const recentLogs = deviceLogSubscriptionState.logs
376
+ .slice(-limit)
377
+ .map((log) => `[${log.timestamp}] [${log.level.toUpperCase()}] ${log.message}`);
378
+ const recentRuntimeStatus = deviceLogSubscriptionState.runtimeStatus
379
+ .slice(-runtimeStatusLimit)
380
+ .map((entry) => `[${entry.timestamp}] ${JSON.stringify(entry.raw, null, 2).replace(/\n/g, " ")}`);
381
+ return [
382
+ `订阅目标: ${targetText}`,
383
+ `连接状态: ${deviceLogSubscriptionState.status}`,
384
+ `缓存日志: ${deviceLogSubscriptionState.logs.length}/${DEVICE_LOG_MEMORY_LIMIT}`,
385
+ `缓存 runtime_status: ${deviceLogSubscriptionState.runtimeStatus.length}/${DEVICE_LOG_MEMORY_LIMIT}`,
386
+ `startedAt: ${deviceLogSubscriptionState.startedAt ?? "unknown"}`,
387
+ `lastEventAt: ${deviceLogSubscriptionState.lastEventAt ?? "unknown"}`,
388
+ `lastError: ${deviceLogSubscriptionState.lastError ?? "none"}`,
389
+ "",
390
+ `最新日志(最多 ${limit} 条):`,
391
+ recentLogs.length > 0 ? recentLogs.join("\n") : "无日志",
392
+ "",
393
+ `最新 runtime_status(最多 ${runtimeStatusLimit} 条):`,
394
+ recentRuntimeStatus.length > 0
395
+ ? recentRuntimeStatus.join("\n")
396
+ : "无 runtime_status",
397
+ ].join("\n");
398
+ }
399
+ /**
400
+ * 格式化单条 API 文档摘要
401
+ * @param language 文档语言
402
+ * @param title 文档标题
403
+ * @param slug 文档 slug
404
+ * @param index 列表序号
405
+ * @returns 返回用于列表展示的文本块
406
+ * @example
407
+ * formatApiDocSummary("js", "Tap", "tap", 0)
408
+ */
409
+ function formatApiDocSummary(language, title, slug, index) {
410
+ return `${index + 1}. ${title}\nslug: ${slug}\nuri: ${(0, docs_service_1.getDocUri)(language, slug)}`;
411
+ }
53
412
  /**
54
413
  * 注册文档资源
55
414
  * @param server MCP 服务实例
@@ -125,14 +484,7 @@ function registerDocTools(server) {
125
484
  inputSchema: {},
126
485
  }, async () => {
127
486
  const language = (0, docs_service_1.getCurrentDocsLanguage)();
128
- return {
129
- content: [
130
- {
131
- type: "text",
132
- text: `当前文档语言: ${language} (${types_1.DOC_LANGUAGE_LABELS[language]})`,
133
- },
134
- ],
135
- };
487
+ return createTextToolResult(`当前文档语言: ${language} (${types_1.DOC_LANGUAGE_LABELS[language]})`);
136
488
  });
137
489
  server.registerTool("set_docs_language", {
138
490
  title: "Set Docs Language",
@@ -145,14 +497,7 @@ function registerDocTools(server) {
145
497
  }, async ({ language }) => {
146
498
  const active = (0, docs_service_1.setCurrentDocsLanguage)(language);
147
499
  const docsDir = (0, docs_service_1.getDocsDirByLanguage)(active);
148
- return {
149
- content: [
150
- {
151
- type: "text",
152
- text: `文档语言已切换为 ${active} (${types_1.DOC_LANGUAGE_LABELS[active]})\n目录: ${docsDir}`,
153
- },
154
- ],
155
- };
500
+ return createTextToolResult(`文档语言已切换为 ${active} (${types_1.DOC_LANGUAGE_LABELS[active]})\n目录: ${docsDir}`);
156
501
  });
157
502
  server.registerTool("list_api_docs", {
158
503
  title: "List API Docs",
@@ -166,21 +511,14 @@ function registerDocTools(server) {
166
511
  }, async ({ language }) => {
167
512
  const activeLanguage = (0, docs_service_1.resolveDocsLanguage)(language);
168
513
  const docs = await (0, docs_service_1.getApiDocsByLanguage)(activeLanguage);
169
- const lines = docs.map((doc, index) => `${index + 1}. ${doc.title}\nslug: ${doc.slug}\nuri: ${(0, docs_service_1.getDocUri)(activeLanguage, doc.slug)}`);
170
- return {
171
- content: [
172
- {
173
- type: "text",
174
- text: lines.length === 0
175
- ? `当前语言 ${activeLanguage} 下没有可用文档。`
176
- : [
177
- `当前语言: ${activeLanguage} (${types_1.DOC_LANGUAGE_LABELS[activeLanguage]})`,
178
- "",
179
- ...lines,
180
- ].join("\n"),
181
- },
182
- ],
183
- };
514
+ const lines = docs.map((doc, index) => formatApiDocSummary(activeLanguage, doc.title, doc.slug, index));
515
+ return createTextToolResult(lines.length === 0
516
+ ? `当前语言 ${activeLanguage} 下没有可用文档。`
517
+ : [
518
+ `当前语言: ${activeLanguage} (${types_1.DOC_LANGUAGE_LABELS[activeLanguage]})`,
519
+ "",
520
+ ...lines,
521
+ ].join("\n"));
184
522
  });
185
523
  server.registerTool("search_api_docs", {
186
524
  title: "Search API Docs",
@@ -215,16 +553,9 @@ function registerDocTools(server) {
215
553
  : [
216
554
  `当前语言: ${activeLanguage} (${types_1.DOC_LANGUAGE_LABELS[activeLanguage]})`,
217
555
  "",
218
- ...matches.map((doc, index) => `${index + 1}. ${doc.title}\nslug: ${doc.slug}\nuri: ${(0, docs_service_1.getDocUri)(activeLanguage, doc.slug)}`),
556
+ ...matches.map((doc, index) => formatApiDocSummary(activeLanguage, doc.title, doc.slug, index)),
219
557
  ].join("\n");
220
- return {
221
- content: [
222
- {
223
- type: "text",
224
- text,
225
- },
226
- ],
227
- };
558
+ return createTextToolResult(text);
228
559
  });
229
560
  server.registerTool("read_api_doc", {
230
561
  title: "Read API Doc",
@@ -241,24 +572,9 @@ function registerDocTools(server) {
241
572
  const docs = await (0, docs_service_1.getApiDocsByLanguage)(activeLanguage);
242
573
  const target = docs.find((doc) => doc.slug === slug);
243
574
  if (!target) {
244
- return {
245
- content: [
246
- {
247
- type: "text",
248
- text: `未找到文档 ${slug}.md(语言: ${activeLanguage})。`,
249
- },
250
- ],
251
- isError: true,
252
- };
575
+ return createTextToolResult(`未找到文档 ${slug}.md(语言: ${activeLanguage})。`, true);
253
576
  }
254
- return {
255
- content: [
256
- {
257
- type: "text",
258
- text: `标题: ${target.title}\n语言: ${activeLanguage}\nURI: ${(0, docs_service_1.getDocUri)(activeLanguage, target.slug)}\n\n${target.content}`,
259
- },
260
- ],
261
- };
577
+ return createTextToolResult(`标题: ${target.title}\n语言: ${activeLanguage}\nURI: ${(0, docs_service_1.getDocUri)(activeLanguage, target.slug)}\n\n${target.content}`);
262
578
  });
263
579
  }
264
580
  /**
@@ -315,14 +631,7 @@ function registerRuntimeTools(server) {
315
631
  },
316
632
  }, async ({ workspacePath }) => {
317
633
  const workspace = await ensureWorkspacePath(workspacePath);
318
- return {
319
- content: [
320
- {
321
- type: "text",
322
- text: `默认工作目录已设置为: ${workspace}`,
323
- },
324
- ],
325
- };
634
+ return createTextToolResult(`默认工作目录已设置为: ${workspace}`);
326
635
  });
327
636
  server.registerTool("get_workspace", {
328
637
  title: "Get Workspace",
@@ -330,98 +639,27 @@ function registerRuntimeTools(server) {
330
639
  inputSchema: {},
331
640
  }, async () => {
332
641
  if (!currentWorkspacePath) {
333
- return {
334
- content: [
335
- {
336
- type: "text",
337
- text: "当前未设置工作目录,请先调用 set_workspace。",
338
- },
339
- ],
340
- };
642
+ return createTextToolResult("当前未设置工作目录,请先调用 set_workspace。");
341
643
  }
342
- return {
343
- content: [
344
- {
345
- type: "text",
346
- text: `当前工作目录: ${currentWorkspacePath}`,
347
- },
348
- ],
349
- };
644
+ return createTextToolResult(`当前工作目录: ${currentWorkspacePath}`);
350
645
  });
351
646
  /**
352
- * 格式化运行期日志结果(供 run/run_ui 直接回传)
353
- * @param ip 设备 IP
354
- * @param port 设备端口
355
- * @param durationSeconds 监听秒数
356
- * @param result 日志监听结果
357
- * @returns 可拼接到 tool 响应的文本
358
- * @example
359
- * const text = formatRunLogSection("192.168.1.10", 9800, 10, result)
360
- */
361
- function formatRunLogSection(ip, port, result) {
362
- const logsPreview = result.logs
363
- .slice(-20)
364
- .map((log) => `[${log.timestamp}] [${log.level.toUpperCase()}] ${log.message}`)
365
- .join("\n");
366
- return [
367
- `运行日志来源: ${ip}:${port}`,
368
- `结束原因: ${result.stopReason},日志: ${result.logs.length} 条,runtime_status: ${result.runtimeStatus.length} 条`,
369
- "最新日志(最多展示最后20条):",
370
- logsPreview || "无日志",
371
- ].join("\n");
372
- }
373
- /**
374
- * 启动持续日志监听(后台,不阻塞当前 tool 返回)
375
- * @param ip 设备 IP
376
- * @param port 设备端口
377
- * @param maxLogs 日志环形缓冲上限
378
- * @returns 返回提示文本
379
- * @example
380
- * const tip = startBackgroundContinuousLogWatch("192.168.1.10", 9800, 300)
381
- */
382
- function startBackgroundContinuousLogWatch(ip, port, maxLogs) {
383
- void (0, project_1.watchDeviceLogsBySse)(ip, port, 0, maxLogs)
384
- .then((result) => {
385
- console.log(`[mcp] continuous logs ${ip}:${port} ended, reason=${result.stopReason}, logs=${result.logs.length}, runtime_status=${result.runtimeStatus.length}`);
386
- })
387
- .catch((error) => {
388
- const message = error instanceof Error ? error.message : String(error);
389
- console.warn(`[mcp] continuous logs ${ip}:${port} failed: ${message}`);
390
- });
391
- return `已切换为持续日志监听: ${ip}:${port}(直到连接断开,max ${maxLogs})`;
392
- }
393
- /**
394
- * 解析运行目标:
395
- * - 显式 transport=ws: 强制走 ws
396
- * - 显式 transport=http: 强制走 http
397
- * - 未显式 transport:
398
- * 1) 若传入 ip/port,则走 http
399
- * 2) 若当前已有 ws 设备连接,则优先走 ws
400
- * 3) 否则回退到 http 默认设备配置
401
- * @param options 工具调用参数
402
- * @returns 返回标准化后的请求目标
647
+ * 解析当前默认 HTTP 设备
648
+ * - 当前 MCP 仅支持单设备模型
649
+ * - 所有设备工具默认复用 set_device 保存的连接信息
650
+ * @returns 返回标准化后的 HTTP 请求目标
403
651
  * @example
404
- * const target = await resolvePreferredRuntimeTarget({ ip: "192.168.1.10" })
652
+ * const target = await resolveRuntimeHttpTarget()
405
653
  */
406
- async function resolvePreferredRuntimeTarget(options) {
407
- const hasHttpHint = options.ip !== undefined || options.port !== undefined;
408
- const shouldUseWs = options.transport === "ws" ||
409
- (options.transport === undefined &&
410
- !hasHttpHint &&
411
- ws_manager_1.WSManager.isConnected());
412
- if (shouldUseWs) {
413
- const wsPort = String(options.wsPort ?? 31111);
414
- const wsWaitMs = String(options.wsWaitMs ?? 30000);
415
- return {
416
- transport: "ws",
417
- wsPort,
418
- wsWaitMs,
419
- label: `transport=ws (wsPort=${wsPort})`,
420
- };
654
+ async function resolveRuntimeHttpTarget() {
655
+ let device;
656
+ try {
657
+ device = await (0, device_config_1.resolveDeviceConfig)();
658
+ }
659
+ catch {
660
+ throw new Error("未设置设备:请先调用 set_device。");
421
661
  }
422
- const device = await (0, device_config_1.resolveDeviceConfig)(options.ip, options.port);
423
662
  return {
424
- transport: "http",
425
663
  ip: device.ip,
426
664
  port: (0, device_config_1.normalizePort)(device.port),
427
665
  label: `${device.ip}:${device.port}`,
@@ -429,7 +667,7 @@ function registerRuntimeTools(server) {
429
667
  }
430
668
  server.registerTool("set_device", {
431
669
  title: "Set Device",
432
- description: "设置默认设备连接信息。首次配置后,后续 run/stop 可不再传 ip。",
670
+ description: "设置当前唯一设备连接信息。设置后其余设备工具均复用该设备。",
433
671
  inputSchema: {
434
672
  ip: z.string().min(1).describe("设备 IP 地址,例如 192.168.1.100"),
435
673
  port: z
@@ -442,14 +680,13 @@ function registerRuntimeTools(server) {
442
680
  },
443
681
  }, async ({ ip, port }) => {
444
682
  const config = await (0, device_config_1.setDeviceConfig)(ip, port);
445
- return {
446
- content: [
447
- {
448
- type: "text",
449
- text: `默认设备已设置为 ${config.ip}:${config.port}`,
450
- },
451
- ],
452
- };
683
+ const reusedSubscription = ensureDeviceLogSubscription(config.ip, config.port);
684
+ return createTextToolResult([
685
+ `默认设备已设置为 ${config.ip}:${config.port}`,
686
+ reusedSubscription
687
+ ? "SSE 日志后台订阅已复用现有连接"
688
+ : `SSE 日志后台订阅已启动,内存缓存上限 ${DEVICE_LOG_MEMORY_LIMIT} 条`,
689
+ ].join("\n"));
453
690
  });
454
691
  server.registerTool("get_device", {
455
692
  title: "Get Device",
@@ -458,52 +695,14 @@ function registerRuntimeTools(server) {
458
695
  }, async () => {
459
696
  const config = await (0, device_config_1.readDeviceConfig)();
460
697
  if (!config) {
461
- return {
462
- content: [
463
- { type: "text", text: "当前未设置默认设备,请先调用 set_device。" },
464
- ],
465
- };
698
+ return createTextToolResult("当前未设置默认设备,请先调用 set_device。");
466
699
  }
467
- return {
468
- content: [
469
- { type: "text", text: `当前默认设备: ${config.ip}:${config.port}` },
470
- ],
471
- };
700
+ return createTextToolResult(`当前默认设备: ${config.ip}:${config.port}`);
472
701
  });
473
702
  server.registerTool("take_screenshot", {
474
703
  title: "Take Screenshot",
475
- description: "获取设备截图。可返回 base64,或落地到文件后返回文件路径(默认写入系统临时目录)。",
704
+ description: "获取当前默认设备截图。可返回 base64,或落地到文件后返回文件路径(默认写入系统临时目录)。",
476
705
  inputSchema: {
477
- transport: z
478
- .enum(["http", "ws"])
479
- .optional()
480
- .describe("传输方式:http|ws;不传时自动优先 ws(若已连接)"),
481
- ip: z
482
- .string()
483
- .min(1)
484
- .optional()
485
- .describe("设备 IP 地址(transport=http 时可传;未传则使用 set_device 默认值)"),
486
- port: z
487
- .number()
488
- .int()
489
- .min(1)
490
- .max(65535)
491
- .optional()
492
- .describe("设备端口,默认 9800"),
493
- wsPort: z
494
- .number()
495
- .int()
496
- .min(1)
497
- .max(65535)
498
- .optional()
499
- .describe("WS 服务端口(transport=ws 时可选,默认 31111)"),
500
- wsWaitMs: z
501
- .number()
502
- .int()
503
- .min(1)
504
- .max(600000)
505
- .optional()
506
- .describe("WS 等待连接超时毫秒(transport=ws 时可选,默认 30000)"),
507
706
  format: z
508
707
  .enum(["file", "base64"])
509
708
  .optional()
@@ -515,35 +714,12 @@ function registerRuntimeTools(server) {
515
714
  .optional()
516
715
  .describe("当 format=file 时可指定输出路径,不传则写入系统临时目录"),
517
716
  },
518
- }, async ({ transport, ip, port, wsPort, wsWaitMs, format, outputPath }) => {
519
- const target = await resolvePreferredRuntimeTarget({
520
- transport,
521
- ip,
522
- port,
523
- wsPort,
524
- wsWaitMs,
525
- });
526
- const requestOptions = target.transport === "ws"
527
- ? {
528
- transport: "ws",
529
- wsPort: target.wsPort,
530
- wsWaitMs: target.wsWaitMs,
531
- }
532
- : {
533
- ip: target.ip,
534
- port: target.port,
535
- transport: "http",
536
- };
717
+ }, async ({ format, outputPath }) => {
718
+ const target = await resolveRuntimeHttpTarget();
719
+ const requestOptions = createRuntimeHttpRequestOptions(target);
537
720
  if (format === "base64") {
538
721
  const base64 = await (0, project_1.getScreenshotBase64OnDevice)(requestOptions);
539
- return {
540
- content: [
541
- {
542
- type: "text",
543
- text: `截图成功: ${target.label}\nformat: base64\n${base64}`,
544
- },
545
- ],
546
- };
722
+ return createTextToolResult(`截图成功: ${target.label}\nformat: base64\n${base64}`);
547
723
  }
548
724
  const image = await (0, project_1.getScreenshotOnDevice)(requestOptions);
549
725
  const targetPath = outputPath && outputPath.trim()
@@ -553,49 +729,12 @@ function registerRuntimeTools(server) {
553
729
  .slice(2, 8)}.jpg`);
554
730
  await fsExtra.ensureDir(path.dirname(targetPath));
555
731
  await fsExtra.writeFile(targetPath, image);
556
- return {
557
- content: [
558
- {
559
- type: "text",
560
- text: `截图成功: ${target.label}\nformat: file\npath: ${targetPath}\nsize: ${image.length} bytes`,
561
- },
562
- ],
563
- };
732
+ return createTextToolResult(`截图成功: ${target.label}\nformat: file\npath: ${targetPath}\nsize: ${image.length} bytes`);
564
733
  });
565
734
  server.registerTool("get_node_source", {
566
735
  title: "Get Node Source",
567
- description: "获取设备当前页面节点 XML(/api/source)。",
736
+ description: "获取当前默认设备页面节点 XML(/api/source)。",
568
737
  inputSchema: {
569
- transport: z
570
- .enum(["http", "ws"])
571
- .optional()
572
- .describe("传输方式:http|ws;不传时自动优先 ws(若已连接)"),
573
- ip: z
574
- .string()
575
- .min(1)
576
- .optional()
577
- .describe("设备 IP 地址(transport=http 时可传;未传则使用 set_device 默认值)"),
578
- port: z
579
- .number()
580
- .int()
581
- .min(1)
582
- .max(65535)
583
- .optional()
584
- .describe("设备端口,默认 9800"),
585
- wsPort: z
586
- .number()
587
- .int()
588
- .min(1)
589
- .max(65535)
590
- .optional()
591
- .describe("WS 服务端口(transport=ws 时可选,默认 31111)"),
592
- wsWaitMs: z
593
- .number()
594
- .int()
595
- .min(1)
596
- .max(600000)
597
- .optional()
598
- .describe("WS 等待连接超时毫秒(transport=ws 时可选,默认 30000)"),
599
738
  maxDepth: z
600
739
  .number()
601
740
  .int()
@@ -613,113 +752,41 @@ function registerRuntimeTools(server) {
613
752
  .default(120)
614
753
  .describe("设备端节点抓取超时秒数,默认 120"),
615
754
  },
616
- }, async ({ transport, ip, port, wsPort, wsWaitMs, maxDepth, timeout }) => {
617
- const target = await resolvePreferredRuntimeTarget({
618
- transport,
619
- ip,
620
- port,
621
- wsPort,
622
- wsWaitMs,
623
- });
624
- const requestOptions = target.transport === "ws"
625
- ? {
626
- transport: "ws",
627
- wsPort: target.wsPort,
628
- wsWaitMs: target.wsWaitMs,
629
- }
630
- : {
631
- ip: target.ip,
632
- port: target.port,
633
- transport: "http",
634
- };
755
+ }, async ({ maxDepth, timeout }) => {
756
+ const target = await resolveRuntimeHttpTarget();
757
+ const requestOptions = createRuntimeHttpRequestOptions(target);
635
758
  const source = await (0, project_1.getSourceOnDevice)(requestOptions, maxDepth, timeout);
636
- return {
637
- content: [
638
- {
639
- type: "text",
640
- text: `节点获取成功: ${target.label}\nmaxDepth: ${maxDepth}\ntimeout: ${timeout}\n\n${source}`,
641
- },
642
- ],
643
- };
759
+ return createTextToolResult(`节点获取成功: ${target.label}\nmaxDepth: ${maxDepth}\ntimeout: ${timeout}\n\n${source}`);
644
760
  });
645
- server.registerTool("watch_logs", {
646
- title: "Watch Logs",
647
- description: "通过 SSE 监听设备实时日志。首次可传 ip,也可复用 set_device 保存的默认设备。",
761
+ server.registerTool("get_logs", {
762
+ title: "Get Device Logs",
763
+ description: "获取当前默认设备的日志缓存快照。调用 set_device 后会自动建立 SSE 后台订阅,此工具只返回当前已缓存的日志内容。",
648
764
  inputSchema: {
649
- ip: z
650
- .string()
651
- .min(1)
652
- .optional()
653
- .describe("设备 IP 地址,未传则使用 set_device 保存的默认值"),
654
- port: z
765
+ limit: z
655
766
  .number()
656
767
  .int()
657
768
  .min(1)
658
- .max(65535)
769
+ .max(5000)
659
770
  .optional()
660
- .describe("设备端口,默认 9800"),
661
- durationSeconds: z
771
+ .default(200)
772
+ .describe("返回最近日志条数,默认 200,最大 5000"),
773
+ runtimeStatusLimit: z
662
774
  .number()
663
775
  .int()
664
776
  .min(0)
665
- .max(300)
666
- .optional()
667
- .default(15)
668
- .describe("监听时长(秒),默认 15 秒;传 0 表示持续监听直到连接断开"),
669
- maxLogs: z
670
- .number()
671
- .int()
672
- .min(10)
673
- .max(5000)
674
- .optional()
675
- .default(200)
676
- .describe("最多收集日志条数,默认 200"),
677
- },
678
- }, async ({ ip, port, durationSeconds, maxLogs }) => {
679
- const device = await (0, device_config_1.resolveDeviceConfig)(ip, port);
680
- const result = await (0, project_1.watchDeviceLogsBySse)(device.ip, device.port, durationSeconds * 1000, maxLogs);
681
- const logsPreview = result.logs
682
- .slice(-20)
683
- .map((log) => `[${log.timestamp}] [${log.level.toUpperCase()}] ${log.message}`)
684
- .join("\n");
685
- return {
686
- content: [
687
- {
688
- type: "text",
689
- text: [
690
- `监听目标: ${device.ip}:${device.port}`,
691
- `监听模式: ${durationSeconds === 0 ? "持续直到连接断开" : `${durationSeconds}s`}`,
692
- `结束原因: ${result.stopReason},收集日志: ${result.logs.length} 条,runtime_status: ${result.runtimeStatus.length} 条`,
693
- "",
694
- "最新日志(最多展示最后20条):",
695
- logsPreview || "无日志",
696
- ].join("\n"),
697
- },
698
- ],
699
- };
700
- });
701
- server.registerTool("build_project", {
702
- title: "Build Project",
703
- description: "构建 KuaiJS 项目,支持开发模式与生产模式。",
704
- inputSchema: {
705
- workspacePath: z
706
- .string()
707
- .min(1)
708
- .optional()
709
- .describe("可选工作目录;不传时使用 set_workspace 记忆值"),
710
- dev: z
711
- .boolean()
777
+ .max(200)
712
778
  .optional()
713
- .describe("是否开发模式构建,true=开发模式,false=生产模式"),
779
+ .default(20)
780
+ .describe("返回最近 runtime_status 条数,默认 20"),
714
781
  },
715
- }, async ({ workspacePath, dev }) => {
716
- const workspace = await ensureWorkspacePath(workspacePath);
717
- await (0, build_1.buildAll)(Boolean(dev), workspace);
718
- return {
719
- content: [
720
- { type: "text", text: `构建完成,模式: ${dev ? "dev" : "prod"}` },
721
- ],
722
- };
782
+ }, async ({ limit, runtimeStatusLimit }) => {
783
+ if (!deviceLogSubscriptionState.target) {
784
+ const device = await (0, device_config_1.readDeviceConfig)();
785
+ if (device) {
786
+ ensureDeviceLogSubscription(device.ip, device.port);
787
+ }
788
+ }
789
+ return createTextToolResult(buildDeviceLogSnapshotText(limit, runtimeStatusLimit));
723
790
  });
724
791
  server.registerTool("package_project", {
725
792
  title: "Package Project",
@@ -734,271 +801,54 @@ function registerRuntimeTools(server) {
734
801
  }, async ({ workspacePath }) => {
735
802
  const workspace = await ensureWorkspacePath(workspacePath);
736
803
  const encryptedPath = await (0, packager_1.packageProject)(workspace);
737
- return {
738
- content: [{ type: "text", text: `打包完成: ${encryptedPath}` }],
739
- };
804
+ return createTextToolResult(`打包完成: ${encryptedPath}`);
740
805
  });
741
806
  server.registerTool("run_project", {
742
807
  title: "Run Project",
743
- description: "构建并同步到设备后运行项目。不传 transport 时会自动优先使用已连接的 WS 设备。",
808
+ description: "构建并同步到当前默认设备后运行项目,仅使用 HTTP 设备连接。",
744
809
  inputSchema: {
745
- transport: z
746
- .enum(["http", "ws"])
747
- .optional()
748
- .describe("传输方式:http|ws;不传时自动优先 ws(若已连接)"),
749
- ip: z
750
- .string()
751
- .min(1)
752
- .optional()
753
- .describe("设备 IP 地址(transport=http 时可传;未传则使用 set_device 默认值)"),
754
- port: z
755
- .number()
756
- .int()
757
- .min(1)
758
- .max(65535)
759
- .optional()
760
- .describe("设备端口,默认 9800"),
761
- wsPort: z
762
- .number()
763
- .int()
764
- .min(1)
765
- .max(65535)
766
- .optional()
767
- .describe("WS 服务端口(transport=ws 时可选,默认 31111)"),
768
- wsWaitMs: z
769
- .number()
770
- .int()
771
- .min(1)
772
- .max(600000)
773
- .optional()
774
- .describe("WS 等待连接超时毫秒(transport=ws 时可选,默认 30000)"),
775
810
  workspacePath: z
776
811
  .string()
777
812
  .min(1)
778
813
  .optional()
779
814
  .describe("可选工作目录;不传时使用 set_workspace 记忆值"),
780
- watchLogs: z
781
- .boolean()
782
- .optional()
783
- .default(false)
784
- .describe("是否在运行前启动日志监听,默认 false"),
785
- logMode: z
786
- .enum(["single", "continuous"])
787
- .optional()
788
- .default("single")
789
- .describe("日志模式:single=按 runtime_status 收敛,continuous=持续监听"),
790
- logMaxLogs: z
791
- .number()
792
- .int()
793
- .min(10)
794
- .max(5000)
795
- .optional()
796
- .default(300)
797
- .describe("日志最大收集条数,默认 300"),
798
815
  },
799
- }, async ({ transport, ip, port, wsPort, wsWaitMs, workspacePath, watchLogs, logMode, logMaxLogs, }) => {
816
+ }, async ({ workspacePath }) => {
800
817
  const workspace = await ensureWorkspacePath(workspacePath);
801
- let logPromise;
802
- let logDeviceIp = "";
803
- let logDevicePort = 0;
804
- let logSetupWarning = "";
805
- let logModeNotice = "";
806
- if (watchLogs) {
807
- try {
808
- const logDevice = await (0, device_config_1.resolveDeviceConfig)(ip, port);
809
- // 先挂监听,再执行 run,确保拿到启动期实时日志
810
- logDeviceIp = logDevice.ip;
811
- logDevicePort = logDevice.port;
812
- if (logMode === "continuous") {
813
- logModeNotice = startBackgroundContinuousLogWatch(logDevice.ip, logDevice.port, logMaxLogs);
814
- }
815
- else {
816
- logPromise = (0, project_1.watchDeviceLogsBySse)(logDevice.ip, logDevice.port, 0, logMaxLogs, true, true);
817
- }
818
- }
819
- catch (error) {
820
- logSetupWarning = `日志监听未启动: ${error instanceof Error ? error.message : String(error)}`;
821
- }
822
- }
823
- const target = await resolvePreferredRuntimeTarget({
824
- transport,
825
- ip,
826
- port,
827
- wsPort,
828
- wsWaitMs,
818
+ const target = await resolveRuntimeHttpTarget();
819
+ await (0, project_1.runOnDevice)({
820
+ ...createRuntimeHttpRequestOptions(target),
821
+ workspacePath: workspace,
829
822
  });
830
- await (0, project_1.runOnDevice)(target.transport === "ws"
831
- ? {
832
- transport: "ws",
833
- wsPort: target.wsPort,
834
- wsWaitMs: target.wsWaitMs,
835
- workspacePath: workspace,
836
- }
837
- : {
838
- ip: target.ip,
839
- port: target.port,
840
- transport: "http",
841
- workspacePath: workspace,
842
- });
843
- let logSection = "";
844
- if (logPromise) {
845
- const result = await logPromise;
846
- const sections = [
847
- formatRunLogSection(logDeviceIp, logDevicePort, result),
848
- ];
849
- if (result.stopReason === "runtime_continuous") {
850
- sections.push(startBackgroundContinuousLogWatch(logDeviceIp, logDevicePort, logMaxLogs));
851
- }
852
- logSection = `\n\n${sections.join("\n")}`;
853
- }
854
- else if (logModeNotice) {
855
- logSection = `\n\n${logModeNotice}`;
856
- }
857
- else if (logSetupWarning) {
858
- logSection = `\n\n${logSetupWarning}`;
859
- }
860
- return {
861
- content: [
862
- {
863
- type: "text",
864
- text: `运行请求已发送到 ${target.label}${logSection}`,
865
- },
866
- ],
867
- };
823
+ return createTextToolResult(`运行请求已发送到 ${target.label}`);
868
824
  });
869
825
  server.registerTool("run_ui_project", {
870
826
  title: "Run UI Project",
871
- description: "构建并同步到设备后预览 UI。不传 transport 时会自动优先使用已连接的 WS 设备。",
827
+ description: "构建并同步到当前默认设备后预览 UI,仅使用 HTTP 设备连接。",
872
828
  inputSchema: {
873
- transport: z
874
- .enum(["http", "ws"])
875
- .optional()
876
- .describe("传输方式:http|ws;不传时自动优先 ws(若已连接)"),
877
- ip: z
878
- .string()
879
- .min(1)
880
- .optional()
881
- .describe("设备 IP 地址(transport=http 时可传;未传则使用 set_device 默认值)"),
882
- port: z
883
- .number()
884
- .int()
885
- .min(1)
886
- .max(65535)
887
- .optional()
888
- .describe("设备端口,默认 9800"),
889
- wsPort: z
890
- .number()
891
- .int()
892
- .min(1)
893
- .max(65535)
894
- .optional()
895
- .describe("WS 服务端口(transport=ws 时可选,默认 31111)"),
896
- wsWaitMs: z
897
- .number()
898
- .int()
899
- .min(1)
900
- .max(600000)
901
- .optional()
902
- .describe("WS 等待连接超时毫秒(transport=ws 时可选,默认 30000)"),
903
829
  workspacePath: z
904
830
  .string()
905
831
  .min(1)
906
832
  .optional()
907
833
  .describe("可选工作目录;不传时使用 set_workspace 记忆值"),
908
834
  },
909
- }, async ({ transport, ip, port, wsPort, wsWaitMs, workspacePath }) => {
835
+ }, async ({ workspacePath }) => {
910
836
  const workspace = await ensureWorkspacePath(workspacePath);
911
- const target = await resolvePreferredRuntimeTarget({
912
- transport,
913
- ip,
914
- port,
915
- wsPort,
916
- wsWaitMs,
837
+ const target = await resolveRuntimeHttpTarget();
838
+ await (0, project_1.runUIOnDevice)({
839
+ ...createRuntimeHttpRequestOptions(target),
840
+ workspacePath: workspace,
917
841
  });
918
- await (0, project_1.runUIOnDevice)(target.transport === "ws"
919
- ? {
920
- transport: "ws",
921
- wsPort: target.wsPort,
922
- wsWaitMs: target.wsWaitMs,
923
- workspacePath: workspace,
924
- }
925
- : {
926
- ip: target.ip,
927
- port: target.port,
928
- transport: "http",
929
- workspacePath: workspace,
930
- });
931
- return {
932
- content: [
933
- {
934
- type: "text",
935
- text: `UI 预览请求已发送到 ${target.label}`,
936
- },
937
- ],
938
- };
842
+ return createTextToolResult(`UI 预览请求已发送到 ${target.label}`);
939
843
  });
940
844
  server.registerTool("stop_project", {
941
845
  title: "Stop Project",
942
- description: "停止设备上的项目。不传 transport 时会自动优先使用已连接的 WS 设备。",
943
- inputSchema: {
944
- transport: z
945
- .enum(["http", "ws"])
946
- .optional()
947
- .describe("传输方式:http|ws;不传时自动优先 ws(若已连接)"),
948
- ip: z
949
- .string()
950
- .min(1)
951
- .optional()
952
- .describe("设备 IP 地址(transport=http 时可传;未传则使用 set_device 默认值)"),
953
- port: z
954
- .number()
955
- .int()
956
- .min(1)
957
- .max(65535)
958
- .optional()
959
- .describe("设备端口,默认 9800"),
960
- wsPort: z
961
- .number()
962
- .int()
963
- .min(1)
964
- .max(65535)
965
- .optional()
966
- .describe("WS 服务端口(transport=ws 时可选,默认 31111)"),
967
- wsWaitMs: z
968
- .number()
969
- .int()
970
- .min(1)
971
- .max(600000)
972
- .optional()
973
- .describe("WS 等待连接超时毫秒(transport=ws 时可选,默认 30000)"),
974
- },
975
- }, async ({ transport, ip, port, wsPort, wsWaitMs }) => {
976
- const target = await resolvePreferredRuntimeTarget({
977
- transport,
978
- ip,
979
- port,
980
- wsPort,
981
- wsWaitMs,
982
- });
983
- await (0, project_1.stopOnDevice)(target.transport === "ws"
984
- ? {
985
- transport: "ws",
986
- wsPort: target.wsPort,
987
- wsWaitMs: target.wsWaitMs,
988
- }
989
- : {
990
- ip: target.ip,
991
- port: target.port,
992
- transport: "http",
993
- });
994
- return {
995
- content: [
996
- {
997
- type: "text",
998
- text: `停止请求已发送到 ${target.label}`,
999
- },
1000
- ],
1001
- };
846
+ description: "停止当前默认设备上的项目,仅使用 HTTP 设备连接。",
847
+ inputSchema: {},
848
+ }, async () => {
849
+ const target = await resolveRuntimeHttpTarget();
850
+ await (0, project_1.stopOnDevice)(createRuntimeHttpRequestOptions(target));
851
+ return createTextToolResult(`停止请求已发送到 ${target.label}`);
1002
852
  });
1003
853
  }
1004
854
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ms-vite-plugin",
3
- "version": "1.1.11",
3
+ "version": "1.1.13",
4
4
  "type": "commonjs",
5
5
  "license": "MIT",
6
6
  "publishConfig": {