ms-vite-plugin 1.1.5 → 1.1.7

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/dist/mcp/tools.js CHANGED
@@ -302,6 +302,31 @@ function registerRuntimeTools(server) {
302
302
  await (0, project_2.ensureValidKuaiJSProject)(workspace);
303
303
  return workspace;
304
304
  }
305
+ /**
306
+ * 在后台启动一次日志监听,不阻塞 run/run_ui 调用返回
307
+ * @param ip 设备 IP
308
+ * @param port 设备端口
309
+ * @param durationSeconds 监听秒数
310
+ * @param maxLogs 最大日志条数
311
+ * @returns 返回监听目标说明文本
312
+ * @example
313
+ * const tip = startBackgroundLogWatch("192.168.1.10", 9800, 20, 300)
314
+ */
315
+ function startBackgroundLogWatch(ip, port, durationSeconds, maxLogs) {
316
+ const durationMs = durationSeconds > 0 ? durationSeconds * 1000 : 0;
317
+ void (0, project_1.watchDeviceLogsBySse)(ip, port, durationMs, maxLogs)
318
+ .then((result) => {
319
+ console.log(`[mcp] background logs ${ip}:${port} done, reason=${result.stopReason}, logs=${result.logs.length}, runtime_status=${result.runtimeStatus.length}`);
320
+ })
321
+ .catch((error) => {
322
+ const message = error instanceof Error ? error.message : String(error);
323
+ console.warn(`[mcp] background logs ${ip}:${port} failed: ${message}`);
324
+ });
325
+ if (durationSeconds === 0) {
326
+ return `已在后台启动持续日志监听: ${ip}:${port}(直到连接断开,max ${maxLogs})`;
327
+ }
328
+ return `已在后台启动日志监听: ${ip}:${port}(${durationSeconds}s,max ${maxLogs})`;
329
+ }
305
330
  /**
306
331
  * 解析运行目标:
307
332
  * - 显式 transport=ws: 强制走 ws
@@ -591,7 +616,7 @@ function registerRuntimeTools(server) {
591
616
  });
592
617
  server.registerTool("watch_logs", {
593
618
  title: "Watch Logs",
594
- description: "通过 SSE 监听设备日志并自动判定异常。首次可传 ip,也可复用 set_device 保存的默认设备。",
619
+ description: "通过 SSE 监听设备实时日志。首次可传 ip,也可复用 set_device 保存的默认设备。",
595
620
  inputSchema: {
596
621
  workspacePath: z
597
622
  .string()
@@ -613,11 +638,11 @@ function registerRuntimeTools(server) {
613
638
  durationSeconds: z
614
639
  .number()
615
640
  .int()
616
- .min(1)
641
+ .min(0)
617
642
  .max(300)
618
643
  .optional()
619
644
  .default(15)
620
- .describe("监听时长(秒),默认 15 秒,最大 300 "),
645
+ .describe("监听时长(秒),默认 15 秒;传 0 表示持续监听直到连接断开"),
621
646
  maxLogs: z
622
647
  .number()
623
648
  .int()
@@ -631,10 +656,6 @@ function registerRuntimeTools(server) {
631
656
  await ensureWorkspacePath(workspacePath);
632
657
  const device = await (0, device_config_1.resolveDeviceConfig)(ip, port);
633
658
  const result = await (0, project_1.watchDeviceLogsBySse)(device.ip, device.port, durationSeconds * 1000, maxLogs);
634
- const findingsPreview = result.findings
635
- .slice(0, 20)
636
- .map((item, index) => `${index + 1}. [${item.severity}] ${item.rule} | ${item.log.timestamp} | ${item.log.message}`)
637
- .join("\n");
638
659
  const logsPreview = result.logs
639
660
  .slice(-20)
640
661
  .map((log) => `[${log.timestamp}] [${log.level.toUpperCase()}] ${log.message}`)
@@ -645,11 +666,8 @@ function registerRuntimeTools(server) {
645
666
  type: "text",
646
667
  text: [
647
668
  `监听目标: ${device.ip}:${device.port}`,
648
- `监听时长: ${durationSeconds}s,收集日志: ${result.logs.length} 条,runtime_status: ${result.runtimeStatus.length} 条`,
649
- result.summary,
650
- "",
651
- "异常判定(最多展示前20条):",
652
- findingsPreview || "未命中异常规则",
669
+ `监听模式: ${durationSeconds === 0 ? "持续直到连接断开" : `${durationSeconds}s`}`,
670
+ `结束原因: ${result.stopReason},收集日志: ${result.logs.length} 条,runtime_status: ${result.runtimeStatus.length} 条`,
653
671
  "",
654
672
  "最新日志(最多展示最后20条):",
655
673
  logsPreview || "无日志",
@@ -737,9 +755,42 @@ function registerRuntimeTools(server) {
737
755
  .min(1)
738
756
  .optional()
739
757
  .describe("可选工作目录;不传时使用 set_workspace 记录值或 MCP 启动目录"),
758
+ watchLogs: z
759
+ .boolean()
760
+ .optional()
761
+ .default(true)
762
+ .describe("是否在运行前启动实时日志监听,默认 true"),
763
+ logDurationSeconds: z
764
+ .number()
765
+ .int()
766
+ .min(0)
767
+ .max(300)
768
+ .optional()
769
+ .default(0)
770
+ .describe("日志监听时长(秒),默认 0;0 表示持续监听直到 runtime_status 失败或连接断开"),
771
+ logMaxLogs: z
772
+ .number()
773
+ .int()
774
+ .min(10)
775
+ .max(5000)
776
+ .optional()
777
+ .default(300)
778
+ .describe("日志最大收集条数,默认 300"),
740
779
  },
741
- }, async ({ transport, ip, port, wsPort, wsWaitMs, workspacePath }) => {
780
+ }, async ({ transport, ip, port, wsPort, wsWaitMs, workspacePath, watchLogs, logDurationSeconds, logMaxLogs, }) => {
742
781
  const workspace = await ensureWorkspacePath(workspacePath);
782
+ let logNotice = "";
783
+ let logSetupWarning = "";
784
+ if (watchLogs) {
785
+ try {
786
+ const logDevice = await (0, device_config_1.resolveDeviceConfig)(ip, port);
787
+ // 先启动日志监听,再执行 run,且不等待日志结束,避免 run 被阻塞
788
+ logNotice = startBackgroundLogWatch(logDevice.ip, logDevice.port, logDurationSeconds, logMaxLogs);
789
+ }
790
+ catch (error) {
791
+ logSetupWarning = `日志监听未启动: ${error instanceof Error ? error.message : String(error)}`;
792
+ }
793
+ }
743
794
  const target = await resolvePreferredRuntimeTarget({
744
795
  transport,
745
796
  ip,
@@ -760,11 +811,16 @@ function registerRuntimeTools(server) {
760
811
  transport: "http",
761
812
  workspacePath: workspace,
762
813
  });
814
+ const logSection = logNotice
815
+ ? `\n\n${logNotice}`
816
+ : logSetupWarning
817
+ ? `\n\n${logSetupWarning}`
818
+ : "";
763
819
  return {
764
820
  content: [
765
821
  {
766
822
  type: "text",
767
- text: `运行请求已发送到 ${target.label}`,
823
+ text: `运行请求已发送到 ${target.label}${logSection}`,
768
824
  },
769
825
  ],
770
826
  };
@@ -808,9 +864,42 @@ function registerRuntimeTools(server) {
808
864
  .min(1)
809
865
  .optional()
810
866
  .describe("可选工作目录;不传时使用 set_workspace 记录值或 MCP 启动目录"),
867
+ watchLogs: z
868
+ .boolean()
869
+ .optional()
870
+ .default(true)
871
+ .describe("是否在预览前启动实时日志监听,默认 true"),
872
+ logDurationSeconds: z
873
+ .number()
874
+ .int()
875
+ .min(0)
876
+ .max(300)
877
+ .optional()
878
+ .default(0)
879
+ .describe("日志监听时长(秒),默认 0;0 表示持续监听直到 runtime_status 失败或连接断开"),
880
+ logMaxLogs: z
881
+ .number()
882
+ .int()
883
+ .min(10)
884
+ .max(5000)
885
+ .optional()
886
+ .default(300)
887
+ .describe("日志最大收集条数,默认 300"),
811
888
  },
812
- }, async ({ transport, ip, port, wsPort, wsWaitMs, workspacePath }) => {
889
+ }, async ({ transport, ip, port, wsPort, wsWaitMs, workspacePath, watchLogs, logDurationSeconds, logMaxLogs, }) => {
813
890
  const workspace = await ensureWorkspacePath(workspacePath);
891
+ let logNotice = "";
892
+ let logSetupWarning = "";
893
+ if (watchLogs) {
894
+ try {
895
+ const logDevice = await (0, device_config_1.resolveDeviceConfig)(ip, port);
896
+ // 先启动日志监听,再执行 run-ui,且不等待日志结束,避免 run-ui 被阻塞
897
+ logNotice = startBackgroundLogWatch(logDevice.ip, logDevice.port, logDurationSeconds, logMaxLogs);
898
+ }
899
+ catch (error) {
900
+ logSetupWarning = `日志监听未启动: ${error instanceof Error ? error.message : String(error)}`;
901
+ }
902
+ }
814
903
  const target = await resolvePreferredRuntimeTarget({
815
904
  transport,
816
905
  ip,
@@ -831,11 +920,16 @@ function registerRuntimeTools(server) {
831
920
  transport: "http",
832
921
  workspacePath: workspace,
833
922
  });
923
+ const logSection = logNotice
924
+ ? `\n\n${logNotice}`
925
+ : logSetupWarning
926
+ ? `\n\n${logSetupWarning}`
927
+ : "";
834
928
  return {
835
929
  content: [
836
930
  {
837
931
  type: "text",
838
- text: `UI 预览请求已发送到 ${target.label}`,
932
+ text: `UI 预览请求已发送到 ${target.label}${logSection}`,
839
933
  },
840
934
  ],
841
935
  };
package/dist/project.d.ts CHANGED
@@ -29,17 +29,6 @@ export interface DeviceRuntimeStatusEntry {
29
29
  /** 事件时间戳(本地接收时间) */
30
30
  timestamp: string;
31
31
  }
32
- /**
33
- * 设备日志判定项
34
- */
35
- export interface DeviceLogFinding {
36
- /** 严重级别 */
37
- severity: "error" | "warn";
38
- /** 命中的判定规则 */
39
- rule: string;
40
- /** 证据日志 */
41
- log: DeviceLogEntry;
42
- }
43
32
  /**
44
33
  * SSE 日志监听结果
45
34
  */
@@ -48,10 +37,8 @@ export interface DeviceLogWatchResult {
48
37
  logs: DeviceLogEntry[];
49
38
  /** 收集到的 runtime_status 条目 */
50
39
  runtimeStatus: DeviceRuntimeStatusEntry[];
51
- /** 自动判定结果 */
52
- findings: DeviceLogFinding[];
53
- /** 结果摘要 */
54
- summary: string;
40
+ /** 监听结束原因 */
41
+ stopReason: "timeout" | "stream_closed" | "manual_abort" | "unknown";
55
42
  }
56
43
  /**
57
44
  * 在设备上获取截图 JPG 二进制数据(HTTP)
@@ -80,22 +67,14 @@ export declare function getScreenshotBase64OnDevice(options: DeviceCliOptions):
80
67
  */
81
68
  export declare function getSourceOnDevice(options: DeviceCliOptions, maxDepth?: number, timeout?: number): Promise<string>;
82
69
  /**
83
- * 基于关键字与日志级别做自动异常判定
84
- * @param logs 待判定日志列表
85
- * @returns 返回判定命中项
86
- * @example
87
- * const findings = analyzeDeviceLogs([{ level: "error", message: "crash", timestamp: "..." }])
88
- */
89
- export declare function analyzeDeviceLogs(logs: DeviceLogEntry[]): DeviceLogFinding[];
90
- /**
91
- * 监听设备 SSE 日志并返回自动判定结果
70
+ * 监听设备 SSE 日志并返回原始结果
92
71
  * @param ip 设备 IP 地址
93
72
  * @param port 设备端口
94
- * @param durationMs 监听时长(毫秒),默认 15000
73
+ * @param durationMs 监听时长(毫秒),默认 15000;传 0 表示不设超时,持续监听
95
74
  * @param maxLogs 最多收集日志条数,默认 200
96
- * @returns 返回日志、运行状态和判定结果
75
+ * @returns 返回日志与运行状态
97
76
  * @example
98
- * const result = await watchDeviceLogsBySse("192.168.1.10", 9800, 10000, 100)
77
+ * const result = await watchDeviceLogsBySse("192.168.1.10", 9800, 0, 300)
99
78
  */
100
79
  export declare function watchDeviceLogsBySse(ip: string, port: number, durationMs?: number, maxLogs?: number): Promise<DeviceLogWatchResult>;
101
80
  /**
package/dist/project.js CHANGED
@@ -39,7 +39,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.getScreenshotOnDevice = getScreenshotOnDevice;
40
40
  exports.getScreenshotBase64OnDevice = getScreenshotBase64OnDevice;
41
41
  exports.getSourceOnDevice = getSourceOnDevice;
42
- exports.analyzeDeviceLogs = analyzeDeviceLogs;
43
42
  exports.watchDeviceLogsBySse = watchDeviceLogsBySse;
44
43
  exports.runOnDevice = runOnDevice;
45
44
  exports.runUIOnDevice = runUIOnDevice;
@@ -511,67 +510,14 @@ function normalizeLogEntry(payload) {
511
510
  };
512
511
  }
513
512
  /**
514
- * 基于关键字与日志级别做自动异常判定
515
- * @param logs 待判定日志列表
516
- * @returns 返回判定命中项
517
- * @example
518
- * const findings = analyzeDeviceLogs([{ level: "error", message: "crash", timestamp: "..." }])
519
- */
520
- function analyzeDeviceLogs(logs) {
521
- const findings = [];
522
- const criticalPatterns = [
523
- { rule: "崩溃关键词", regex: /\b(crash|fatal|abort|sigabrt|segfault)\b/i },
524
- {
525
- rule: "异常关键词",
526
- regex: /\b(exception|uncaught|assert|error:|failed|failure)\b/i,
527
- },
528
- {
529
- rule: "内存风险关键词",
530
- regex: /\b(out of memory|oom|memory leak|allocation failed)\b/i,
531
- },
532
- {
533
- rule: "设备离线关键词",
534
- regex: /\b(timeout|disconnected|connection reset|network error)\b/i,
535
- },
536
- ];
537
- for (const log of logs) {
538
- const level = log.level.toLowerCase();
539
- const message = log.message;
540
- if (level === "error") {
541
- findings.push({
542
- severity: "error",
543
- rule: "error 级别日志",
544
- log,
545
- });
546
- }
547
- else if (level === "warn") {
548
- findings.push({
549
- severity: "warn",
550
- rule: "warn 级别日志",
551
- log,
552
- });
553
- }
554
- for (const pattern of criticalPatterns) {
555
- if (pattern.regex.test(message)) {
556
- findings.push({
557
- severity: level === "error" ? "error" : "warn",
558
- rule: pattern.rule,
559
- log,
560
- });
561
- }
562
- }
563
- }
564
- return findings;
565
- }
566
- /**
567
- * 监听设备 SSE 日志并返回自动判定结果
513
+ * 监听设备 SSE 日志并返回原始结果
568
514
  * @param ip 设备 IP 地址
569
515
  * @param port 设备端口
570
- * @param durationMs 监听时长(毫秒),默认 15000
516
+ * @param durationMs 监听时长(毫秒),默认 15000;传 0 表示不设超时,持续监听
571
517
  * @param maxLogs 最多收集日志条数,默认 200
572
- * @returns 返回日志、运行状态和判定结果
518
+ * @returns 返回日志与运行状态
573
519
  * @example
574
- * const result = await watchDeviceLogsBySse("192.168.1.10", 9800, 10000, 100)
520
+ * const result = await watchDeviceLogsBySse("192.168.1.10", 9800, 0, 300)
575
521
  */
576
522
  async function watchDeviceLogsBySse(ip, port, durationMs = 15000, maxLogs = 200) {
577
523
  if (!ip.trim()) {
@@ -580,7 +526,7 @@ async function watchDeviceLogsBySse(ip, port, durationMs = 15000, maxLogs = 200)
580
526
  if (!Number.isInteger(port) || port < 1 || port > 65535) {
581
527
  throw new Error(`监听日志失败: 无效端口 ${port}`);
582
528
  }
583
- if (!Number.isInteger(durationMs) || durationMs < 1000) {
529
+ if (!Number.isInteger(durationMs) || durationMs < 0) {
584
530
  throw new Error(`监听日志失败: 无效 durationMs ${durationMs}`);
585
531
  }
586
532
  if (!Number.isInteger(maxLogs) || maxLogs < 1) {
@@ -590,9 +536,13 @@ async function watchDeviceLogsBySse(ip, port, durationMs = 15000, maxLogs = 200)
590
536
  const logs = [];
591
537
  const runtimeStatus = [];
592
538
  const controller = new AbortController();
593
- const timeoutHandle = setTimeout(() => {
594
- controller.abort();
595
- }, durationMs);
539
+ let stopReason = "unknown";
540
+ const timeoutHandle = durationMs > 0
541
+ ? setTimeout(() => {
542
+ stopReason = "timeout";
543
+ controller.abort();
544
+ }, durationMs)
545
+ : undefined;
596
546
  try {
597
547
  const response = await fetch(url, {
598
548
  method: "GET",
@@ -610,6 +560,9 @@ async function watchDeviceLogsBySse(ip, port, durationMs = 15000, maxLogs = 200)
610
560
  while (true) {
611
561
  const { value, done } = await reader.read();
612
562
  if (done) {
563
+ if (stopReason === "unknown") {
564
+ stopReason = "stream_closed";
565
+ }
613
566
  break;
614
567
  }
615
568
  buffer += decoder.decode(value, { stream: true });
@@ -623,11 +576,16 @@ async function watchDeviceLogsBySse(ip, port, durationMs = 15000, maxLogs = 200)
623
576
  try {
624
577
  const parsed = JSON.parse(event.data);
625
578
  if (event.event === "log") {
626
- if (logs.length < maxLogs) {
627
- logs.push(normalizeLogEntry(parsed));
579
+ // 日志缓冲使用环形窗口,持续监听时不会无限增长
580
+ if (logs.length >= maxLogs) {
581
+ logs.shift();
628
582
  }
583
+ logs.push(normalizeLogEntry(parsed));
629
584
  }
630
585
  else if (event.event === "runtime_status") {
586
+ if (runtimeStatus.length >= maxLogs) {
587
+ runtimeStatus.shift();
588
+ }
631
589
  runtimeStatus.push({
632
590
  raw: parsed,
633
591
  timestamp: new Date().toISOString(),
@@ -638,10 +596,6 @@ async function watchDeviceLogsBySse(ip, port, durationMs = 15000, maxLogs = 200)
638
596
  // 兼容非 JSON 数据:不抛错,继续监听后续事件
639
597
  }
640
598
  }
641
- if (logs.length >= maxLogs) {
642
- controller.abort();
643
- break;
644
- }
645
599
  splitIndex = buffer.search(/\r?\n\r?\n/);
646
600
  }
647
601
  }
@@ -653,21 +607,19 @@ async function watchDeviceLogsBySse(ip, port, durationMs = 15000, maxLogs = 200)
653
607
  error.message === "The operation was aborted."))) {
654
608
  throw error;
655
609
  }
610
+ if (stopReason === "unknown") {
611
+ stopReason = "manual_abort";
612
+ }
656
613
  }
657
614
  finally {
658
- clearTimeout(timeoutHandle);
659
- }
660
- const findings = analyzeDeviceLogs(logs);
661
- const errorCount = findings.filter((item) => item.severity === "error").length;
662
- const warnCount = findings.filter((item) => item.severity === "warn").length;
663
- const summary = findings.length === 0
664
- ? `日志监听完成:共 ${logs.length} 条日志,未发现明显异常。`
665
- : `日志监听完成:共 ${logs.length} 条日志,发现 ${errorCount} 条 error 风险、${warnCount} 条 warn 风险。`;
615
+ if (timeoutHandle) {
616
+ clearTimeout(timeoutHandle);
617
+ }
618
+ }
666
619
  return {
667
620
  logs,
668
621
  runtimeStatus,
669
- findings,
670
- summary,
622
+ stopReason,
671
623
  };
672
624
  }
673
625
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ms-vite-plugin",
3
- "version": "1.1.5",
3
+ "version": "1.1.7",
4
4
  "type": "commonjs",
5
5
  "license": "MIT",
6
6
  "publishConfig": {