@xiaou66/vite-plugin-vue-mcp-next 1.0.3 → 1.1.0

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.
@@ -281,6 +281,58 @@ function safeReadXhrResponseText(xhr) {
281
281
 
282
282
  // src/runtime/pageIdentity.ts
283
283
  import { nanoid as nanoid3 } from "nanoid";
284
+ var RUNTIME_CLIENT_ID_STORAGE_KEY = "vite-plugin-vue-mcp-next:runtime-client-id";
285
+ var RUNTIME_CLIENT_ID_WINDOW_NAME_PREFIX = "vite-plugin-vue-mcp-next:runtime-client-id=";
286
+ var RUNTIME_CLIENT_ID_WINDOW_NAME_SEPARATOR = "\n";
287
+ function createRuntimeClientId() {
288
+ return `runtime-client-${nanoid3()}`;
289
+ }
290
+ function readRuntimeClientIdFromTabScope(tabScope) {
291
+ if (!tabScope) {
292
+ return void 0;
293
+ }
294
+ if (tabScope.__VITE_MCP_NEXT_RUNTIME_CLIENT_ID__) {
295
+ return tabScope.__VITE_MCP_NEXT_RUNTIME_CLIENT_ID__;
296
+ }
297
+ return tabScope.name.split(RUNTIME_CLIENT_ID_WINDOW_NAME_SEPARATOR).find((item) => item.startsWith(RUNTIME_CLIENT_ID_WINDOW_NAME_PREFIX))?.slice(RUNTIME_CLIENT_ID_WINDOW_NAME_PREFIX.length);
298
+ }
299
+ function writeRuntimeClientIdToTabScope(tabScope, clientId) {
300
+ tabScope.__VITE_MCP_NEXT_RUNTIME_CLIENT_ID__ = clientId;
301
+ const preservedNameParts = tabScope.name.split(RUNTIME_CLIENT_ID_WINDOW_NAME_SEPARATOR).filter((item) => !item.startsWith(RUNTIME_CLIENT_ID_WINDOW_NAME_PREFIX)).filter(Boolean);
302
+ tabScope.name = [
303
+ ...preservedNameParts,
304
+ `${RUNTIME_CLIENT_ID_WINDOW_NAME_PREFIX}${clientId}`
305
+ ].join(RUNTIME_CLIENT_ID_WINDOW_NAME_SEPARATOR);
306
+ }
307
+ function persistRuntimeClientId(storage, clientId, tabScope) {
308
+ storage.setItem(RUNTIME_CLIENT_ID_STORAGE_KEY, clientId);
309
+ if (tabScope) {
310
+ writeRuntimeClientIdToTabScope(tabScope, clientId);
311
+ }
312
+ return clientId;
313
+ }
314
+ function getRuntimeClientId(storage, tabScope) {
315
+ const nextClientId = createRuntimeClientId();
316
+ if (!storage) {
317
+ return nextClientId;
318
+ }
319
+ try {
320
+ const tabScopedClientId = readRuntimeClientIdFromTabScope(tabScope);
321
+ if (tabScopedClientId) {
322
+ return persistRuntimeClientId(storage, tabScopedClientId, tabScope);
323
+ }
324
+ if (tabScope) {
325
+ return persistRuntimeClientId(storage, nextClientId, tabScope);
326
+ }
327
+ const currentClientId = storage.getItem(RUNTIME_CLIENT_ID_STORAGE_KEY);
328
+ if (currentClientId) {
329
+ return currentClientId;
330
+ }
331
+ return persistRuntimeClientId(storage, nextClientId);
332
+ } catch {
333
+ return nextClientId;
334
+ }
335
+ }
284
336
  function createRuntimePageId() {
285
337
  return `runtime-${nanoid3()}`;
286
338
  }
@@ -291,6 +343,7 @@ function getRuntimePageIdentity(input) {
291
343
  url: input.href,
292
344
  pathname: safeUrlPathname(input.href),
293
345
  title: input.title,
346
+ runtimeClientId: input.runtimeClientId,
294
347
  connected: true,
295
348
  readyState: input.readyState,
296
349
  viewport: {
@@ -300,6 +353,500 @@ function getRuntimePageIdentity(input) {
300
353
  };
301
354
  }
302
355
 
356
+ // src/runtime/performanceHook.ts
357
+ import { nanoid as nanoid4 } from "nanoid";
358
+
359
+ // src/shared/limits.ts
360
+ var DEFAULT_DOM_MAX_DEPTH = 8;
361
+ var DEFAULT_DOM_MAX_NODES = 2e3;
362
+ var DEFAULT_DOM_MAX_TEXT_LENGTH = 300;
363
+ var DEFAULT_CONSOLE_MAX_RECORDS = 1e3;
364
+ var DEFAULT_NETWORK_MAX_RECORDS = 500;
365
+ var DEFAULT_NETWORK_MAX_BODY_SIZE = 1e5;
366
+ var DEFAULT_MASK_HEADERS = [
367
+ "authorization",
368
+ "cookie",
369
+ "set-cookie"
370
+ ];
371
+
372
+ // src/constants.ts
373
+ var DEFAULT_MCP_PATH = "/__mcp";
374
+ var DEFAULT_SCREENSHOT_MAX_BYTES = 5 * 1024 * 1024;
375
+ var DEFAULT_SCREENSHOT_SAVE_DIR = ".vite-mcp/screenshot";
376
+ var DEFAULT_PERFORMANCE_SAVE_DIR = ".vite-mcp/performance";
377
+ var DEFAULT_PERFORMANCE_MAX_DURATION_MS = 3e4;
378
+ var DEFAULT_PERFORMANCE_SAMPLE_INTERVAL_MS = 250;
379
+ var DEFAULT_PERFORMANCE_LONG_TASK_THRESHOLD_MS = 50;
380
+ var VIRTUAL_RUNTIME_ID = "virtual:vite-plugin-vue-mcp-next/runtime";
381
+ var RESOLVED_VIRTUAL_RUNTIME_ID = `\0${VIRTUAL_RUNTIME_ID}`;
382
+ var VIRTUAL_SCREENSHOT_CONFIG_ID = "virtual:vite-plugin-vue-mcp-next/screenshot-config";
383
+ var RESOLVED_VIRTUAL_SCREENSHOT_CONFIG_ID = `\0${VIRTUAL_SCREENSHOT_CONFIG_ID}`;
384
+ var VIRTUAL_SNAPDOM_LOADER_ID = "virtual:vite-plugin-vue-mcp-next/snapdom-loader";
385
+ var RESOLVED_VIRTUAL_SNAPDOM_LOADER_ID = `\0${VIRTUAL_SNAPDOM_LOADER_ID}`;
386
+ var DEFAULT_MCP_CLIENT_SERVER_NAME = "vite-mcp-next";
387
+ var DEFAULT_OPTIONS = {
388
+ mcpPath: DEFAULT_MCP_PATH,
389
+ host: "localhost",
390
+ printUrl: true,
391
+ updateCursorMcpJson: {
392
+ enabled: true,
393
+ serverName: DEFAULT_MCP_CLIENT_SERVER_NAME
394
+ },
395
+ mcpClients: {
396
+ cursor: true,
397
+ codex: true,
398
+ claudeCode: true,
399
+ trae: true,
400
+ serverName: DEFAULT_MCP_CLIENT_SERVER_NAME
401
+ },
402
+ skill: {
403
+ autoConfig: true
404
+ },
405
+ runtime: {
406
+ mode: "auto",
407
+ evaluate: {
408
+ enabled: false,
409
+ timeoutMs: 3e3
410
+ }
411
+ },
412
+ cdp: {},
413
+ network: {
414
+ mode: "auto",
415
+ maxRecords: DEFAULT_NETWORK_MAX_RECORDS,
416
+ captureRequestBody: true,
417
+ captureResponseBody: true,
418
+ maxBodySize: DEFAULT_NETWORK_MAX_BODY_SIZE,
419
+ maskHeaders: [...DEFAULT_MASK_HEADERS]
420
+ },
421
+ dom: {
422
+ maxDepth: DEFAULT_DOM_MAX_DEPTH,
423
+ maxNodes: DEFAULT_DOM_MAX_NODES,
424
+ maxTextLength: DEFAULT_DOM_MAX_TEXT_LENGTH
425
+ },
426
+ console: {
427
+ maxRecords: DEFAULT_CONSOLE_MAX_RECORDS
428
+ },
429
+ screenshot: {
430
+ type: "path",
431
+ saveDir: DEFAULT_SCREENSHOT_SAVE_DIR,
432
+ prefer: "auto",
433
+ maxBytes: DEFAULT_SCREENSHOT_MAX_BYTES,
434
+ snapdom: {
435
+ options: {},
436
+ plugins: []
437
+ }
438
+ },
439
+ performance: {
440
+ mode: "auto",
441
+ maxDurationMs: DEFAULT_PERFORMANCE_MAX_DURATION_MS,
442
+ sampleIntervalMs: DEFAULT_PERFORMANCE_SAMPLE_INTERVAL_MS,
443
+ longTaskThresholdMs: DEFAULT_PERFORMANCE_LONG_TASK_THRESHOLD_MS,
444
+ saveDir: DEFAULT_PERFORMANCE_SAVE_DIR,
445
+ memory: {
446
+ enabled: true
447
+ },
448
+ stacks: {
449
+ enabled: true
450
+ }
451
+ }
452
+ };
453
+
454
+ // src/performance/summary.ts
455
+ function buildPerformanceSummary(input) {
456
+ const memory = buildMemorySummary(input.memorySamples);
457
+ const blockedTimeMs = input.longTasks.reduce((total, task) => {
458
+ return total + Math.max(0, task.durationMs - DEFAULT_PERFORMANCE_LONG_TASK_THRESHOLD_MS);
459
+ }, 0);
460
+ const longTaskCount = input.longTasks.length;
461
+ const durations = input.longTasks.map((task) => task.durationMs);
462
+ const maxTaskDurationMs = durations.length ? Math.max(...durations) : 0;
463
+ const averageTaskDurationMs = durations.length ? Math.round(
464
+ durations.reduce((total, duration) => total + duration, 0) / durations.length
465
+ ) : void 0;
466
+ const suspectedJank = blockedTimeMs > 0 || memory.trend === "growing" || longTaskCount > 0;
467
+ const severity = resolveSeverity({
468
+ blockedTimeMs,
469
+ longTaskCount,
470
+ memoryTrend: memory.trend
471
+ });
472
+ return {
473
+ blockedTimeMs,
474
+ longTaskCount,
475
+ maxTaskDurationMs,
476
+ averageTaskDurationMs,
477
+ suspectedJank,
478
+ severity
479
+ };
480
+ }
481
+ function buildMemorySummary(samples) {
482
+ const first = samples[0]?.usedJSHeapSize;
483
+ const last = samples.at(-1)?.usedJSHeapSize;
484
+ const peak = samples.reduce((currentPeak, sample) => {
485
+ if (typeof sample.usedJSHeapSize !== "number") {
486
+ return currentPeak;
487
+ }
488
+ return Math.max(currentPeak, sample.usedJSHeapSize);
489
+ }, 0);
490
+ const trend = resolveMemoryTrend(first, last);
491
+ return {
492
+ samples,
493
+ initialUsedJSHeapSize: first,
494
+ finalUsedJSHeapSize: last,
495
+ peakUsedJSHeapSize: peak || void 0,
496
+ deltaUsedJSHeapSize: typeof first === "number" && typeof last === "number" ? last - first : void 0,
497
+ trend
498
+ };
499
+ }
500
+ function buildStackSummary(frames, options = {}) {
501
+ return {
502
+ topFrames: [...frames].sort(sortByHotness).slice(0, 10),
503
+ rawProfilePath: options.rawProfilePath,
504
+ limitation: options.limitation
505
+ };
506
+ }
507
+ function resolveSeverity(input) {
508
+ if (input.blockedTimeMs >= 1e3 || input.longTaskCount >= 10) {
509
+ return "critical";
510
+ }
511
+ if (input.blockedTimeMs > 0 || input.memoryTrend === "growing") {
512
+ return "warning";
513
+ }
514
+ return "ok";
515
+ }
516
+ function resolveMemoryTrend(first, last) {
517
+ if (typeof first !== "number" || typeof last !== "number") {
518
+ return "unknown";
519
+ }
520
+ if (last > first) {
521
+ return "growing";
522
+ }
523
+ return "stable";
524
+ }
525
+ function sortByHotness(left, right) {
526
+ return compareNumber(right.totalTimeMs, left.totalTimeMs) || compareNumber(right.selfTimeMs, left.selfTimeMs) || compareNumber(right.hitCount, left.hitCount);
527
+ }
528
+ function compareNumber(left, right) {
529
+ return (left ?? -1) - (right ?? -1);
530
+ }
531
+
532
+ // src/runtime/performanceHook.ts
533
+ var activePerformanceCollector;
534
+ function installPerformanceHook(options) {
535
+ const collector = createPerformanceCollector({
536
+ pageId: options.pageId,
537
+ now: () => Date.now(),
538
+ readMemory: readBrowserMemory,
539
+ observeLongTask: (push) => observeLongTasks(push, options.longTaskThresholdMs),
540
+ observeAnimationFrame: observeAnimationFrameTasks,
541
+ observeErrorStack: observeWindowErrorStack,
542
+ observeUnhandledRejectionStack,
543
+ setTimeout: window.setTimeout.bind(window),
544
+ clearTimeout: window.clearTimeout.bind(window)
545
+ });
546
+ const decoratedCollector = options.send ? createDispatchingCollector(collector, options.send) : collector;
547
+ activePerformanceCollector = decoratedCollector;
548
+ return decoratedCollector;
549
+ }
550
+ function getPerformanceCollector() {
551
+ return activePerformanceCollector;
552
+ }
553
+ function createPerformanceCollector(deps) {
554
+ const state = {
555
+ longTasks: [],
556
+ stackFrames: [],
557
+ memorySamples: [],
558
+ latestReport: void 0,
559
+ activeRecordingId: void 0,
560
+ activeRecordingStartedAt: 0,
561
+ activeIncludeMemory: true,
562
+ activeIncludeStacks: true
563
+ };
564
+ const cleanups = [
565
+ deps.observeLongTask((task) => {
566
+ state.longTasks.push(task);
567
+ }),
568
+ deps.observeAnimationFrame((task) => {
569
+ state.longTasks.push(task);
570
+ })
571
+ ];
572
+ if (deps.observeErrorStack) {
573
+ cleanups.push(
574
+ deps.observeErrorStack((frame) => {
575
+ state.stackFrames.push(frame);
576
+ })
577
+ );
578
+ }
579
+ if (deps.observeUnhandledRejectionStack) {
580
+ cleanups.push(
581
+ deps.observeUnhandledRejectionStack((frame) => {
582
+ state.stackFrames.push(frame);
583
+ })
584
+ );
585
+ }
586
+ return {
587
+ async recordOnce(options) {
588
+ const recordingId = startSession(state, deps, {
589
+ includeMemory: options.includeMemory,
590
+ includeStacks: options.includeStacks
591
+ });
592
+ await waitForDuration(deps, options.durationMs);
593
+ return stopSession({
594
+ state,
595
+ deps,
596
+ recordingId,
597
+ source: "hook"
598
+ });
599
+ },
600
+ start(options) {
601
+ if (state.activeRecordingId) {
602
+ throw new Error("A performance recording is already active");
603
+ }
604
+ return startSession(state, deps, options);
605
+ },
606
+ stop(recordingId) {
607
+ return stopSession({
608
+ state,
609
+ deps,
610
+ recordingId,
611
+ source: "hook"
612
+ });
613
+ },
614
+ latest() {
615
+ return state.latestReport;
616
+ },
617
+ dispose() {
618
+ activePerformanceCollector = void 0;
619
+ for (const cleanup of cleanups) {
620
+ cleanup();
621
+ }
622
+ }
623
+ };
624
+ }
625
+ function createRecordingId() {
626
+ return `performance-${nanoid4()}`;
627
+ }
628
+ function startSession(state, deps, options) {
629
+ const recordingId = createRecordingId();
630
+ state.longTasks.length = 0;
631
+ state.stackFrames.length = 0;
632
+ state.memorySamples.length = 0;
633
+ if (options.includeMemory) {
634
+ const sample = deps.readMemory();
635
+ if (sample) {
636
+ state.memorySamples.push(sample);
637
+ }
638
+ }
639
+ state.activeRecordingId = recordingId;
640
+ state.activeRecordingStartedAt = deps.now();
641
+ state.activeIncludeMemory = options.includeMemory;
642
+ state.activeIncludeStacks = options.includeStacks;
643
+ return recordingId;
644
+ }
645
+ function createDispatchingCollector(collector, send) {
646
+ return {
647
+ async recordOnce(options) {
648
+ const report = await collector.recordOnce(options);
649
+ send(report);
650
+ return report;
651
+ },
652
+ start(options) {
653
+ return collector.start(options);
654
+ },
655
+ stop(recordingId) {
656
+ const report = collector.stop(recordingId);
657
+ send(report);
658
+ return report;
659
+ },
660
+ latest() {
661
+ return collector.latest();
662
+ },
663
+ dispose() {
664
+ collector.dispose();
665
+ }
666
+ };
667
+ }
668
+ function waitForDuration(deps, durationMs) {
669
+ return new Promise((resolve) => {
670
+ const timer = deps.setTimeout(() => {
671
+ deps.clearTimeout(timer);
672
+ resolve();
673
+ }, durationMs);
674
+ });
675
+ }
676
+ function buildReport(options) {
677
+ const memory = options.includeMemory ? buildMemorySummary(options.memorySamples) : void 0;
678
+ const summary = buildPerformanceSummary({
679
+ longTasks: options.longTasks,
680
+ memorySamples: options.includeMemory ? options.memorySamples : []
681
+ });
682
+ const stacks = options.includeStacks ? buildStackSummary(options.stackFrames, {
683
+ rawProfilePath: options.rawProfilePath,
684
+ limitation: options.stackFrames.length > 0 ? void 0 : "Runtime path only exposes error stacks when the page reports them"
685
+ }) : void 0;
686
+ const report = {
687
+ recordingId: options.recordingId,
688
+ pageId: options.pageId,
689
+ source: options.source,
690
+ startedAt: options.startedAt,
691
+ endedAt: options.endedAt,
692
+ durationMs: options.endedAt - options.startedAt,
693
+ summary,
694
+ longTasks: [...options.longTasks],
695
+ memory,
696
+ stacks,
697
+ artifacts: options.artifacts,
698
+ limitations: options.limitations
699
+ };
700
+ return report;
701
+ }
702
+ function stopSession(options) {
703
+ const { state, deps, recordingId, source } = options;
704
+ if (!state.activeRecordingId || state.activeRecordingId !== recordingId) {
705
+ throw new Error(`Performance recording not found: ${recordingId}`);
706
+ }
707
+ if (state.activeIncludeMemory) {
708
+ const sample = deps.readMemory();
709
+ if (sample) {
710
+ state.memorySamples.push(sample);
711
+ }
712
+ }
713
+ const report = buildReport({
714
+ recordingId: state.activeRecordingId,
715
+ pageId: deps.pageId,
716
+ startedAt: state.activeRecordingStartedAt,
717
+ endedAt: deps.now(),
718
+ source,
719
+ includeMemory: state.activeIncludeMemory,
720
+ includeStacks: state.activeIncludeStacks,
721
+ longTasks: state.longTasks,
722
+ memorySamples: state.memorySamples,
723
+ stackFrames: state.stackFrames,
724
+ limitations: [
725
+ "Runtime path only sees browser-observable signals",
726
+ "Runtime path cannot produce a full CPU profile or heap snapshot"
727
+ ]
728
+ });
729
+ state.latestReport = report;
730
+ state.activeRecordingId = void 0;
731
+ state.activeRecordingStartedAt = 0;
732
+ state.activeIncludeMemory = true;
733
+ state.activeIncludeStacks = true;
734
+ return report;
735
+ }
736
+ function readBrowserMemory() {
737
+ const memory = performance.memory;
738
+ if (!memory) {
739
+ return void 0;
740
+ }
741
+ return {
742
+ timestamp: Date.now(),
743
+ usedJSHeapSize: memory.usedJSHeapSize,
744
+ totalJSHeapSize: memory.totalJSHeapSize,
745
+ jsHeapSizeLimit: memory.jsHeapSizeLimit
746
+ };
747
+ }
748
+ function observeLongTasks(push, longTaskThresholdMs = 50) {
749
+ if (typeof PerformanceObserver === "undefined") {
750
+ return () => {
751
+ };
752
+ }
753
+ const observer = new PerformanceObserver((list) => {
754
+ for (const entry of list.getEntries()) {
755
+ if (entry.duration < longTaskThresholdMs) {
756
+ continue;
757
+ }
758
+ push({
759
+ startTime: entry.startTime,
760
+ durationMs: entry.duration,
761
+ name: entry.name,
762
+ source: "longtask"
763
+ });
764
+ }
765
+ });
766
+ observer.observe({ type: "longtask", buffered: true });
767
+ return () => {
768
+ observer.disconnect();
769
+ };
770
+ }
771
+ function observeAnimationFrameTasks(push) {
772
+ if (typeof PerformanceObserver === "undefined") {
773
+ return () => {
774
+ };
775
+ }
776
+ const supported = PerformanceObserver.supportedEntryTypes.includes(
777
+ "long-animation-frame"
778
+ );
779
+ if (!supported) {
780
+ return () => {
781
+ };
782
+ }
783
+ const observer = new PerformanceObserver((list) => {
784
+ for (const entry of list.getEntries()) {
785
+ push({
786
+ startTime: entry.startTime,
787
+ durationMs: entry.duration,
788
+ name: entry.name,
789
+ source: "long-animation-frame"
790
+ });
791
+ }
792
+ });
793
+ observer.observe({ type: "long-animation-frame", buffered: true });
794
+ return () => {
795
+ observer.disconnect();
796
+ };
797
+ }
798
+ function observeWindowErrorStack(push) {
799
+ const onError = (event) => {
800
+ const error = event.error;
801
+ const frames = parseStackFrames(error?.stack);
802
+ if (frames.length === 0 && event.message) {
803
+ push({ functionName: event.message });
804
+ return;
805
+ }
806
+ frames.forEach((frame) => {
807
+ push(frame);
808
+ });
809
+ };
810
+ window.addEventListener("error", onError);
811
+ return () => {
812
+ window.removeEventListener("error", onError);
813
+ };
814
+ }
815
+ function observeUnhandledRejectionStack(push) {
816
+ const onRejection = (event) => {
817
+ const reason = event.reason;
818
+ const frames = parseStackFrames(reason?.stack);
819
+ if (frames.length === 0 && reason?.message) {
820
+ push({ functionName: reason.message });
821
+ return;
822
+ }
823
+ frames.forEach((frame) => {
824
+ push(frame);
825
+ });
826
+ };
827
+ window.addEventListener("unhandledrejection", onRejection);
828
+ return () => {
829
+ window.removeEventListener("unhandledrejection", onRejection);
830
+ };
831
+ }
832
+ function parseStackFrames(stack) {
833
+ if (!stack) {
834
+ return [];
835
+ }
836
+ return stack.split("\n").map((line) => line.trim()).filter(Boolean).map((line) => {
837
+ const match = /at\s+(.*?)\s+\((.*?):(\d+):(\d+)\)$/.exec(line);
838
+ if (match) {
839
+ return {
840
+ functionName: match[1] || "<anonymous>",
841
+ url: match[2],
842
+ lineNumber: Number(match[3]),
843
+ columnNumber: Number(match[4])
844
+ };
845
+ }
846
+ return { functionName: line };
847
+ });
848
+ }
849
+
303
850
  // src/runtime/screenshot.ts
304
851
  var snapdomLoader = () => Promise.reject(createMissingSnapdomError());
305
852
  var screenshotModuleRegistry = {};
@@ -716,7 +1263,79 @@ function createClientVueRuntimeRpc(getRpc) {
716
1263
  });
717
1264
  getRpc().onPiniaInfoUpdated(query.event, stringify(result));
718
1265
  },
719
- onPiniaInfoUpdated: () => void 0
1266
+ onPiniaInfoUpdated: () => void 0,
1267
+ async recordPerformance(query) {
1268
+ const collector = getPerformanceCollector();
1269
+ if (!collector) {
1270
+ getRpc().onPerformanceRecorded(
1271
+ query.event,
1272
+ createPerformanceUnavailableError()
1273
+ );
1274
+ return;
1275
+ }
1276
+ try {
1277
+ const report = await collector.recordOnce({
1278
+ durationMs: query.durationMs,
1279
+ includeMemory: query.includeMemory,
1280
+ includeStacks: query.includeStacks
1281
+ });
1282
+ getRpc().onPerformanceRecorded(query.event, report);
1283
+ } catch (error) {
1284
+ getRpc().onPerformanceRecorded(
1285
+ query.event,
1286
+ createPerformanceError(error)
1287
+ );
1288
+ }
1289
+ },
1290
+ onPerformanceRecorded: () => void 0,
1291
+ startPerformanceRecording(query) {
1292
+ const collector = getPerformanceCollector();
1293
+ if (!collector) {
1294
+ getRpc().onPerformanceRecordingStarted(
1295
+ query.event,
1296
+ createPerformanceUnavailableError()
1297
+ );
1298
+ return;
1299
+ }
1300
+ try {
1301
+ const recordingId = collector.start({
1302
+ includeMemory: query.includeMemory,
1303
+ includeStacks: query.includeStacks
1304
+ });
1305
+ getRpc().onPerformanceRecordingStarted(query.event, {
1306
+ ok: true,
1307
+ recordingId,
1308
+ startedAt: Date.now(),
1309
+ source: "hook"
1310
+ });
1311
+ } catch (error) {
1312
+ getRpc().onPerformanceRecordingStarted(
1313
+ query.event,
1314
+ createPerformanceError(error)
1315
+ );
1316
+ }
1317
+ },
1318
+ onPerformanceRecordingStarted: () => void 0,
1319
+ stopPerformanceRecording(query) {
1320
+ const collector = getPerformanceCollector();
1321
+ if (!collector) {
1322
+ getRpc().onPerformanceRecordingStopped(
1323
+ query.event,
1324
+ createPerformanceUnavailableError()
1325
+ );
1326
+ return;
1327
+ }
1328
+ try {
1329
+ const report = collector.stop(query.recordingId);
1330
+ getRpc().onPerformanceRecordingStopped(query.event, report);
1331
+ } catch (error) {
1332
+ getRpc().onPerformanceRecordingStopped(
1333
+ query.event,
1334
+ createPerformanceError(error)
1335
+ );
1336
+ }
1337
+ },
1338
+ onPerformanceRecordingStopped: () => void 0
720
1339
  };
721
1340
  }
722
1341
  function setStateValue(object, path, value) {
@@ -751,6 +1370,18 @@ function callVueDevtoolsHook(name, payload) {
751
1370
  const hooks = devtools.ctx.hooks;
752
1371
  hooks.callHook(name, payload);
753
1372
  }
1373
+ function createPerformanceUnavailableError() {
1374
+ return {
1375
+ ok: false,
1376
+ error: "Performance collector is not initialized"
1377
+ };
1378
+ }
1379
+ function createPerformanceError(error) {
1380
+ return {
1381
+ ok: false,
1382
+ error: error instanceof Error ? error.message : String(error)
1383
+ };
1384
+ }
754
1385
  async function findComponentNode(componentName) {
755
1386
  const inspectorTree = await devtools.api.getInspectorTree({
756
1387
  inspectorId: COMPONENTS_INSPECTOR_ID,
@@ -809,11 +1440,18 @@ async function startRuntimeClient() {
809
1440
  const identity = getRuntimePageIdentity({
810
1441
  href: window.location.href,
811
1442
  title: document.title,
1443
+ runtimeClientId: getRuntimeClientId(window.sessionStorage, window),
812
1444
  innerWidth: window.innerWidth,
813
1445
  innerHeight: window.innerHeight,
814
1446
  readyState: document.readyState
815
1447
  });
816
1448
  hot.send("vite-plugin-vue-mcp-next:page-connected", identity);
1449
+ installPerformanceHook({
1450
+ pageId: identity.pageId,
1451
+ send(report) {
1452
+ hot.send("vite-plugin-vue-mcp-next:performance-record", report);
1453
+ }
1454
+ });
817
1455
  installConsoleHook({
818
1456
  pageId: identity.pageId,
819
1457
  send(record) {