@xiaou66/vite-plugin-vue-mcp-next 1.2.0 → 1.3.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/dist/index.cjs CHANGED
@@ -88,7 +88,8 @@ var MCP_TOOL_NAMES = {
88
88
  highlightComponent: "highlight_component",
89
89
  getRouterInfo: "get_router_info",
90
90
  getPiniaTree: "get_pinia_tree",
91
- getPiniaState: "get_pinia_state"
91
+ getPiniaState: "get_pinia_state",
92
+ getElementContext: "get_element_context"
92
93
  };
93
94
  var VIRTUAL_RUNTIME_ID = "virtual:vite-plugin-vue-mcp-next/runtime";
94
95
  var RESOLVED_VIRTUAL_RUNTIME_ID = `\0${VIRTUAL_RUNTIME_ID}`;
@@ -99,6 +100,12 @@ var RESOLVED_VIRTUAL_SNAPDOM_LOADER_ID = `\0${VIRTUAL_SNAPDOM_LOADER_ID}`;
99
100
  var DEFAULT_MCP_CLIENT_SERVER_NAME = "vite-mcp-next";
100
101
  var LEGACY_MCP_CLIENT_SERVER_NAMES = ["vue-mcp-next"];
101
102
  var RUNTIME_PAGE_RECONNECTED_EVENT = "vite-plugin-vue-mcp-next:page-reconnected";
103
+ var RUNTIME_PAGE_CONNECTED_EVENT = "vite-plugin-vue-mcp-next:page-connected";
104
+ var RUNTIME_PAGE_DISCONNECTED_EVENT = "vite-plugin-vue-mcp-next:page-disconnected";
105
+ var RUNTIME_PAGE_HEARTBEAT_EVENT = "vite-plugin-vue-mcp-next:heartbeat";
106
+ var DEFAULT_RUNTIME_PAGE_HEARTBEAT_TIMEOUT_MS = 45e3;
107
+ var DEFAULT_RUNTIME_PAGE_HEARTBEAT_SCAN_INTERVAL_MS = 45e3;
108
+ var DEFAULT_ELEMENT_PICKER_TOAST_DURATION_MS = 2200;
102
109
  var DEFAULT_OPTIONS = {
103
110
  mcpPath: DEFAULT_MCP_PATH,
104
111
  host: "localhost",
@@ -117,6 +124,16 @@ var DEFAULT_OPTIONS = {
117
124
  skill: {
118
125
  autoConfig: true
119
126
  },
127
+ elementPicker: {
128
+ enabled: true,
129
+ shortcut: {
130
+ altKey: true,
131
+ shiftKey: true,
132
+ metaKey: false,
133
+ ctrlKey: false
134
+ },
135
+ toastDurationMs: DEFAULT_ELEMENT_PICKER_TOAST_DURATION_MS
136
+ },
120
137
  runtime: {
121
138
  mode: "auto",
122
139
  evaluate: {
@@ -191,6 +208,14 @@ function mergeOptions(options = {}) {
191
208
  ...DEFAULT_OPTIONS.skill,
192
209
  ...options.skill
193
210
  },
211
+ elementPicker: {
212
+ ...DEFAULT_OPTIONS.elementPicker,
213
+ ...options.elementPicker,
214
+ shortcut: {
215
+ ...DEFAULT_OPTIONS.elementPicker.shortcut,
216
+ ...options.elementPicker?.shortcut
217
+ }
218
+ },
194
219
  runtime: {
195
220
  ...DEFAULT_OPTIONS.runtime,
196
221
  ...options.runtime,
@@ -611,9 +636,164 @@ function registerDomTools(server, ctx) {
611
636
  );
612
637
  }
613
638
 
614
- // src/mcp/tools/evaluate.ts
639
+ // src/mcp/tools/elementContext.ts
615
640
  var import_zod3 = require("zod");
616
641
 
642
+ // src/shared/elementId.ts
643
+ var PROJECT_SOURCE_ID_PATTERN = /^(.+\.(?:vue|tsx|jsx|ts|js)):(\d+):(\d+)$/;
644
+ var RUNTIME_ID_PATTERN = /^runtime:([A-Za-z0-9_-]+)$/;
645
+ var PACKAGE_ID_PREFIX = "pkg:";
646
+ function parseElementId(elementId) {
647
+ const sourceMatch = PROJECT_SOURCE_ID_PATTERN.exec(elementId);
648
+ if (sourceMatch) {
649
+ return {
650
+ kind: "project-source",
651
+ elementId,
652
+ file: sourceMatch[1],
653
+ line: Number(sourceMatch[2]),
654
+ column: Number(sourceMatch[3])
655
+ };
656
+ }
657
+ if (elementId.startsWith(PACKAGE_ID_PREFIX)) {
658
+ return parsePackageElementId(elementId);
659
+ }
660
+ const runtimeMatch = RUNTIME_ID_PATTERN.exec(elementId);
661
+ if (runtimeMatch) {
662
+ return {
663
+ kind: "runtime",
664
+ elementId,
665
+ runtimeId: runtimeMatch[1]
666
+ };
667
+ }
668
+ return {
669
+ kind: "invalid",
670
+ elementId,
671
+ reason: "unsupported elementId format"
672
+ };
673
+ }
674
+ function parsePackageElementId(elementId) {
675
+ const value = elementId.slice(PACKAGE_ID_PREFIX.length);
676
+ const parts = value.split("/").filter(Boolean);
677
+ if (parts.length < 2) {
678
+ return {
679
+ kind: "invalid",
680
+ elementId,
681
+ reason: "package elementId must include packageName and entryFile"
682
+ };
683
+ }
684
+ const scoped = parts[0]?.startsWith("@");
685
+ const packageName = scoped ? parts.slice(0, 2).join("/") : parts[0];
686
+ const entryFile = parts.slice(scoped ? 2 : 1).join("/");
687
+ if (!entryFile) {
688
+ return {
689
+ kind: "invalid",
690
+ elementId,
691
+ reason: "package elementId must include entryFile"
692
+ };
693
+ }
694
+ return {
695
+ kind: "package",
696
+ elementId,
697
+ packageName,
698
+ entryFile
699
+ };
700
+ }
701
+
702
+ // src/mcp/tools/elementContext.ts
703
+ function registerElementContextTools(server, ctx) {
704
+ server.registerTool(
705
+ MCP_TOOL_NAMES.getElementContext,
706
+ {
707
+ description: "Get editable source, Vue component, and DOM context for a copied elementId.",
708
+ inputSchema: {
709
+ elementId: import_zod3.z.string(),
710
+ pageId: import_zod3.z.string().optional()
711
+ }
712
+ },
713
+ async (input) => {
714
+ const runtimeResult = await tryRuntimeElementContext(
715
+ ctx,
716
+ input.elementId,
717
+ input.pageId
718
+ );
719
+ if (runtimeResult) {
720
+ return createToolResponse(runtimeResult);
721
+ }
722
+ return createToolResponse(createStaticElementContext(input.elementId));
723
+ }
724
+ );
725
+ }
726
+ async function tryRuntimeElementContext(ctx, elementId, pageId) {
727
+ if (!ctx.rpcServer) {
728
+ return parseElementId(elementId).kind === "runtime" ? createRuntimeUnavailableContext(elementId) : void 0;
729
+ }
730
+ try {
731
+ resolvePageTarget(ctx, pageId);
732
+ } catch (error) {
733
+ const parsed = parseElementId(elementId);
734
+ if (parsed.kind === "project-source" || parsed.kind === "package") {
735
+ return void 0;
736
+ }
737
+ return {
738
+ ok: false,
739
+ elementId,
740
+ error: error instanceof Error ? error.message : String(error),
741
+ limitations: ["call list_pages and pass pageId when multiple pages exist"]
742
+ };
743
+ }
744
+ return await requestRuntimeData(ctx, (event) => {
745
+ void ctx.rpcServer?.getElementContext({ event, elementId });
746
+ });
747
+ }
748
+ function createStaticElementContext(elementId) {
749
+ const parsed = parseElementId(elementId);
750
+ if (parsed.kind === "project-source") {
751
+ return {
752
+ ok: true,
753
+ elementId,
754
+ editable: true,
755
+ codeLocation: {
756
+ file: parsed.file,
757
+ line: parsed.line,
758
+ column: parsed.column
759
+ },
760
+ limitations: ["runtime unavailable, DOM and component context omitted"]
761
+ };
762
+ }
763
+ if (parsed.kind === "package") {
764
+ return {
765
+ ok: true,
766
+ elementId,
767
+ editable: false,
768
+ packageLocation: {
769
+ packageName: parsed.packageName,
770
+ entryFile: parsed.entryFile
771
+ },
772
+ limitations: ["third-party package source is not editable from this project"]
773
+ };
774
+ }
775
+ if (parsed.kind === "runtime") {
776
+ return createRuntimeUnavailableContext(elementId);
777
+ }
778
+ return {
779
+ ok: false,
780
+ elementId,
781
+ error: parsed.reason,
782
+ limitations: ["please provide a copied elementId from the element picker"]
783
+ };
784
+ }
785
+ function createRuntimeUnavailableContext(elementId) {
786
+ return {
787
+ ok: false,
788
+ elementId,
789
+ error: "runtime bridge is not connected",
790
+ limitations: ["runtime ids are only valid while the page is connected"]
791
+ };
792
+ }
793
+
794
+ // src/mcp/tools/evaluate.ts
795
+ var import_zod4 = require("zod");
796
+
617
797
  // src/cdp/cdpEvaluate.ts
618
798
  async function cdpEvaluate(options) {
619
799
  const result = await options.client.Runtime.evaluate({
@@ -634,9 +814,9 @@ function registerEvaluateTools(server, ctx) {
634
814
  {
635
815
  description: "Evaluate a script in the selected page when explicitly enabled.",
636
816
  inputSchema: {
637
- pageId: import_zod3.z.string().optional(),
638
- expression: import_zod3.z.string(),
639
- awaitPromise: import_zod3.z.boolean().optional()
817
+ pageId: import_zod4.z.string().optional(),
818
+ expression: import_zod4.z.string(),
819
+ awaitPromise: import_zod4.z.boolean().optional()
640
820
  }
641
821
  },
642
822
  async (input) => {
@@ -685,18 +865,18 @@ function assertEvaluateEnabled(options) {
685
865
  }
686
866
 
687
867
  // src/mcp/tools/network.ts
688
- var import_zod4 = require("zod");
868
+ var import_zod5 = require("zod");
689
869
  function registerNetworkTools(server, ctx) {
690
870
  server.registerTool(
691
871
  MCP_TOOL_NAMES.getNetworkRequests,
692
872
  {
693
873
  description: "Get captured network request summaries.",
694
874
  inputSchema: {
695
- pageId: import_zod4.z.string().optional(),
696
- urlContains: import_zod4.z.string().optional(),
697
- method: import_zod4.z.string().optional(),
698
- status: import_zod4.z.number().optional(),
699
- limit: import_zod4.z.number().optional()
875
+ pageId: import_zod5.z.string().optional(),
876
+ urlContains: import_zod5.z.string().optional(),
877
+ method: import_zod5.z.string().optional(),
878
+ status: import_zod5.z.number().optional(),
879
+ limit: import_zod5.z.number().optional()
700
880
  }
701
881
  },
702
882
  (input) => {
@@ -719,7 +899,7 @@ function registerNetworkTools(server, ctx) {
719
899
  MCP_TOOL_NAMES.getNetworkRequestDetail,
720
900
  {
721
901
  description: "Get captured network request detail by id.",
722
- inputSchema: { id: import_zod4.z.string() }
902
+ inputSchema: { id: import_zod5.z.string() }
723
903
  },
724
904
  (input) => {
725
905
  if (ctx.options.network.mode === "off") {
@@ -736,7 +916,7 @@ function registerNetworkTools(server, ctx) {
736
916
  {
737
917
  description: "Clear cached network requests.",
738
918
  inputSchema: {
739
- pageId: import_zod4.z.string().optional()
919
+ pageId: import_zod5.z.string().optional()
740
920
  }
741
921
  },
742
922
  () => {
@@ -747,7 +927,7 @@ function registerNetworkTools(server, ctx) {
747
927
  }
748
928
 
749
929
  // src/mcp/tools/performance.ts
750
- var import_zod5 = require("zod");
930
+ var import_zod6 = require("zod");
751
931
 
752
932
  // src/performance/summary.ts
753
933
  function buildPerformanceSummary(input) {
@@ -1029,10 +1209,10 @@ function registerPerformanceTools(server, ctx) {
1029
1209
  {
1030
1210
  description: "Record a performance sample for the selected page.",
1031
1211
  inputSchema: {
1032
- pageId: import_zod5.z.string().optional(),
1033
- durationMs: import_zod5.z.number().optional(),
1034
- includeMemory: import_zod5.z.boolean().optional(),
1035
- includeStacks: import_zod5.z.boolean().optional()
1212
+ pageId: import_zod6.z.string().optional(),
1213
+ durationMs: import_zod6.z.number().optional(),
1214
+ includeMemory: import_zod6.z.boolean().optional(),
1215
+ includeStacks: import_zod6.z.boolean().optional()
1036
1216
  }
1037
1217
  },
1038
1218
  async (input) => handleRecordPerformance(ctx, input)
@@ -1042,9 +1222,9 @@ function registerPerformanceTools(server, ctx) {
1042
1222
  {
1043
1223
  description: "Start a performance recording session.",
1044
1224
  inputSchema: {
1045
- pageId: import_zod5.z.string().optional(),
1046
- includeMemory: import_zod5.z.boolean().optional(),
1047
- includeStacks: import_zod5.z.boolean().optional()
1225
+ pageId: import_zod6.z.string().optional(),
1226
+ includeMemory: import_zod6.z.boolean().optional(),
1227
+ includeStacks: import_zod6.z.boolean().optional()
1048
1228
  }
1049
1229
  },
1050
1230
  async (input) => handleStartPerformanceRecording(ctx, input)
@@ -1054,7 +1234,7 @@ function registerPerformanceTools(server, ctx) {
1054
1234
  {
1055
1235
  description: "Stop a performance recording session.",
1056
1236
  inputSchema: {
1057
- recordingId: import_zod5.z.string()
1237
+ recordingId: import_zod6.z.string()
1058
1238
  }
1059
1239
  },
1060
1240
  async (input) => handleStopPerformanceRecording(ctx, input)
@@ -1064,9 +1244,9 @@ function registerPerformanceTools(server, ctx) {
1064
1244
  {
1065
1245
  description: "Get cached performance reports and active sessions.",
1066
1246
  inputSchema: {
1067
- pageId: import_zod5.z.string().optional(),
1068
- recordingId: import_zod5.z.string().optional(),
1069
- limit: import_zod5.z.number().optional()
1247
+ pageId: import_zod6.z.string().optional(),
1248
+ recordingId: import_zod6.z.string().optional(),
1249
+ limit: import_zod6.z.number().optional()
1070
1250
  }
1071
1251
  },
1072
1252
  (input) => handleGetPerformanceReport(ctx, input)
@@ -1076,7 +1256,7 @@ function registerPerformanceTools(server, ctx) {
1076
1256
  {
1077
1257
  description: "Take a heap snapshot with CDP.",
1078
1258
  inputSchema: {
1079
- pageId: import_zod5.z.string().optional()
1259
+ pageId: import_zod6.z.string().optional()
1080
1260
  }
1081
1261
  },
1082
1262
  async (input) => handleTakeHeapSnapshot(ctx, input)
@@ -1333,7 +1513,7 @@ function toStructuredRecord(value) {
1333
1513
  }
1334
1514
 
1335
1515
  // src/mcp/tools/pages.ts
1336
- var import_zod6 = require("zod");
1516
+ var import_zod7 = require("zod");
1337
1517
 
1338
1518
  // src/plugin/entryDiscovery.ts
1339
1519
  var import_node_fs = __toESM(require("fs"), 1);
@@ -1358,10 +1538,10 @@ function walkHtmlEntries(root, dir, entries) {
1358
1538
  if (!item.isFile() || !item.name.endsWith(".html")) {
1359
1539
  continue;
1360
1540
  }
1361
- const relative2 = (0, import_vite.normalizePath)(import_node_path2.default.relative(root, fullPath));
1541
+ const relative3 = (0, import_vite.normalizePath)(import_node_path2.default.relative(root, fullPath));
1362
1542
  entries.push({
1363
- file: relative2,
1364
- pathname: relative2 === "index.html" ? "/" : `/${relative2}`
1543
+ file: relative3,
1544
+ pathname: relative3 === "index.html" ? "/" : `/${relative3}`
1365
1545
  });
1366
1546
  }
1367
1547
  }
@@ -1373,7 +1553,7 @@ function registerPageTools(server, ctx, vite) {
1373
1553
  {
1374
1554
  description: "List Vite page entries and connected runtime/CDP targets.",
1375
1555
  inputSchema: {
1376
- includeDisconnected: import_zod6.z.boolean().optional()
1556
+ includeDisconnected: import_zod7.z.boolean().optional()
1377
1557
  }
1378
1558
  },
1379
1559
  async (input) => {
@@ -1396,8 +1576,8 @@ function registerPageTools(server, ctx, vite) {
1396
1576
  {
1397
1577
  description: "Reload the selected page. CDP uses ignoreCache; Runtime Hook falls back to normal reload.",
1398
1578
  inputSchema: {
1399
- pageId: import_zod6.z.string().optional(),
1400
- ignoreCache: import_zod6.z.boolean().optional()
1579
+ pageId: import_zod7.z.string().optional(),
1580
+ ignoreCache: import_zod7.z.boolean().optional()
1401
1581
  }
1402
1582
  },
1403
1583
  async (input) => {
@@ -1549,7 +1729,7 @@ function getPathname(url) {
1549
1729
  }
1550
1730
 
1551
1731
  // src/mcp/tools/storage.ts
1552
- var import_zod7 = require("zod");
1732
+ var import_zod8 = require("zod");
1553
1733
 
1554
1734
  // src/cdp/cdpStorage.ts
1555
1735
  function createCdpStorageAdapter(client) {
@@ -1793,7 +1973,7 @@ function registerStorageTools(server, ctx) {
1793
1973
  registerStorageTool(server, MCP_TOOL_NAMES.listStorage, {
1794
1974
  description: "List same-origin storage and CDP cookies when available.",
1795
1975
  inputSchema: {
1796
- pageId: import_zod7.z.string().optional()
1976
+ pageId: import_zod8.z.string().optional()
1797
1977
  },
1798
1978
  action: "list",
1799
1979
  handler: (input) => handleListStorage(ctx, input.pageId)
@@ -1837,23 +2017,23 @@ function registerStorageTool(server, name, options) {
1837
2017
  }
1838
2018
  function createStorageInputSchema() {
1839
2019
  return {
1840
- pageId: import_zod7.z.string().optional(),
1841
- scope: import_zod7.z.enum(["localStorage", "sessionStorage", "indexedDB", "cookie"]).optional(),
1842
- key: import_zod7.z.string().optional(),
1843
- value: import_zod7.z.string().optional(),
1844
- databaseName: import_zod7.z.string().optional(),
1845
- objectStoreName: import_zod7.z.string().optional(),
1846
- indexName: import_zod7.z.string().optional(),
1847
- cookie: import_zod7.z.object({
1848
- name: import_zod7.z.string(),
1849
- value: import_zod7.z.string().optional(),
1850
- domain: import_zod7.z.string().optional(),
1851
- path: import_zod7.z.string().optional(),
1852
- url: import_zod7.z.string().optional(),
1853
- httpOnly: import_zod7.z.boolean().optional(),
1854
- secure: import_zod7.z.boolean().optional(),
1855
- sameSite: import_zod7.z.enum(["strict", "lax", "none"]).optional(),
1856
- expires: import_zod7.z.number().optional()
2020
+ pageId: import_zod8.z.string().optional(),
2021
+ scope: import_zod8.z.enum(["localStorage", "sessionStorage", "indexedDB", "cookie"]).optional(),
2022
+ key: import_zod8.z.string().optional(),
2023
+ value: import_zod8.z.string().optional(),
2024
+ databaseName: import_zod8.z.string().optional(),
2025
+ objectStoreName: import_zod8.z.string().optional(),
2026
+ indexName: import_zod8.z.string().optional(),
2027
+ cookie: import_zod8.z.object({
2028
+ name: import_zod8.z.string(),
2029
+ value: import_zod8.z.string().optional(),
2030
+ domain: import_zod8.z.string().optional(),
2031
+ path: import_zod8.z.string().optional(),
2032
+ url: import_zod8.z.string().optional(),
2033
+ httpOnly: import_zod8.z.boolean().optional(),
2034
+ secure: import_zod8.z.boolean().optional(),
2035
+ sameSite: import_zod8.z.enum(["strict", "lax", "none"]).optional(),
2036
+ expires: import_zod8.z.number().optional()
1857
2037
  }).optional()
1858
2038
  };
1859
2039
  }
@@ -1971,7 +2151,7 @@ function hasCdpConfig2(ctx) {
1971
2151
  }
1972
2152
 
1973
2153
  // src/mcp/tools/screenshot.ts
1974
- var import_zod8 = require("zod");
2154
+ var import_zod9 = require("zod");
1975
2155
 
1976
2156
  // src/cdp/cdpScreenshot.ts
1977
2157
  async function cdpCaptureScreenshot(options) {
@@ -2131,14 +2311,14 @@ function createProjectRelativePath(ctx, filePath) {
2131
2311
  var DEFAULT_SCREENSHOT_TARGET = "viewport";
2132
2312
  var DEFAULT_SCREENSHOT_FORMAT = "png";
2133
2313
  var screenshotInputSchema = {
2134
- pageId: import_zod8.z.string().optional(),
2135
- target: import_zod8.z.enum(["viewport", "fullPage", "element"]).optional(),
2136
- selector: import_zod8.z.string().optional(),
2137
- format: import_zod8.z.enum(["png", "jpeg", "webp"]).optional(),
2138
- prefer: import_zod8.z.enum(["auto", "cdp", "runtime"]).optional(),
2139
- quality: import_zod8.z.number().optional(),
2140
- scale: import_zod8.z.number().optional(),
2141
- snapdom: import_zod8.z.record(import_zod8.z.string(), import_zod8.z.unknown()).optional()
2314
+ pageId: import_zod9.z.string().optional(),
2315
+ target: import_zod9.z.enum(["viewport", "fullPage", "element"]).optional(),
2316
+ selector: import_zod9.z.string().optional(),
2317
+ format: import_zod9.z.enum(["png", "jpeg", "webp"]).optional(),
2318
+ prefer: import_zod9.z.enum(["auto", "cdp", "runtime"]).optional(),
2319
+ quality: import_zod9.z.number().optional(),
2320
+ scale: import_zod9.z.number().optional(),
2321
+ snapdom: import_zod9.z.record(import_zod9.z.string(), import_zod9.z.unknown()).optional()
2142
2322
  };
2143
2323
  function registerScreenshotTools(server, ctx) {
2144
2324
  server.registerTool(
@@ -2258,7 +2438,7 @@ function isScreenshotImagePayload(result) {
2258
2438
 
2259
2439
  // src/mcp/tools/vue.ts
2260
2440
  var import_nanoid3 = require("nanoid");
2261
- var import_zod9 = require("zod");
2441
+ var import_zod10 = require("zod");
2262
2442
  function registerVueTools(server, ctx) {
2263
2443
  server.registerTool(
2264
2444
  MCP_TOOL_NAMES.getComponentTree,
@@ -2271,7 +2451,7 @@ function registerVueTools(server, ctx) {
2271
2451
  MCP_TOOL_NAMES.getComponentState,
2272
2452
  {
2273
2453
  description: "Get Vue component state.",
2274
- inputSchema: { componentName: import_zod9.z.string() }
2454
+ inputSchema: { componentName: import_zod10.z.string() }
2275
2455
  },
2276
2456
  async ({ componentName }) => requestVueData(ctx, (event) => {
2277
2457
  void ctx.rpcServer?.getInspectorState({ event, componentName });
@@ -2282,10 +2462,10 @@ function registerVueTools(server, ctx) {
2282
2462
  {
2283
2463
  description: "Edit Vue component state.",
2284
2464
  inputSchema: {
2285
- componentName: import_zod9.z.string(),
2286
- path: import_zod9.z.array(import_zod9.z.string()),
2287
- value: import_zod9.z.string(),
2288
- valueType: import_zod9.z.enum(["string", "number", "boolean", "object", "array"])
2465
+ componentName: import_zod10.z.string(),
2466
+ path: import_zod10.z.array(import_zod10.z.string()),
2467
+ value: import_zod10.z.string(),
2468
+ valueType: import_zod10.z.enum(["string", "number", "boolean", "object", "array"])
2289
2469
  }
2290
2470
  },
2291
2471
  ({ componentName, path: path8, value, valueType }) => {
@@ -2305,7 +2485,7 @@ function registerVueTools(server, ctx) {
2305
2485
  MCP_TOOL_NAMES.highlightComponent,
2306
2486
  {
2307
2487
  description: "Highlight a Vue component.",
2308
- inputSchema: { componentName: import_zod9.z.string() }
2488
+ inputSchema: { componentName: import_zod10.z.string() }
2309
2489
  },
2310
2490
  ({ componentName }) => {
2311
2491
  if (!ctx.rpcServer) {
@@ -2333,7 +2513,7 @@ function registerVueTools(server, ctx) {
2333
2513
  MCP_TOOL_NAMES.getPiniaState,
2334
2514
  {
2335
2515
  description: "Get Pinia store state.",
2336
- inputSchema: { storeName: import_zod9.z.string() }
2516
+ inputSchema: { storeName: import_zod10.z.string() }
2337
2517
  },
2338
2518
  async ({ storeName }) => requestVueData(ctx, (event) => {
2339
2519
  void ctx.rpcServer?.getPiniaState({ event, storeName });
@@ -2376,6 +2556,7 @@ function createMcpServer(ctx, vite) {
2376
2556
  });
2377
2557
  registerPageTools(server, ctx, vite);
2378
2558
  registerDomTools(server, ctx);
2559
+ registerElementContextTools(server, ctx);
2379
2560
  registerScreenshotTools(server, ctx);
2380
2561
  registerConsoleTools(server, ctx);
2381
2562
  registerEvaluateTools(server, ctx);
@@ -2457,6 +2638,10 @@ function createServerVueRuntimeRpc(ctx) {
2457
2638
  onDomQueryUpdated: (event, data) => {
2458
2639
  void ctx.hooks.callHook(event, data);
2459
2640
  },
2641
+ getElementContext: () => void 0,
2642
+ onElementContextUpdated: (event, data) => {
2643
+ void ctx.hooks.callHook(event, data);
2644
+ },
2460
2645
  reloadPage: () => void 0,
2461
2646
  onPageReloaded: (event, data) => {
2462
2647
  void ctx.hooks.callHook(event, data);
@@ -2769,9 +2954,85 @@ async function startCdpObservers(ctx, target, client) {
2769
2954
  }
2770
2955
  }
2771
2956
 
2957
+ // src/plugin/elementInstrumentation.ts
2958
+ var import_compiler_sfc = require("@vue/compiler-sfc");
2959
+ var import_magic_string = __toESM(require("magic-string"), 1);
2960
+ var import_node_path4 = require("path");
2961
+ var VUE_FILE_SUFFIX = ".vue";
2962
+ var ELEMENT_NODE_TYPE = 1;
2963
+ var SKIPPED_TAGS = /* @__PURE__ */ new Set(["template", "slot", "script", "style"]);
2964
+ var MCP_ID_ATTR = "data-v-mcp-id";
2965
+ function createElementInstrumentationController(options) {
2966
+ return {
2967
+ transform(code, id, ssr) {
2968
+ if (ssr || shouldSkipInstrumentation(id)) {
2969
+ return void 0;
2970
+ }
2971
+ const filename = id.split("?", 1)[0];
2972
+ if (!filename.endsWith(VUE_FILE_SUFFIX)) {
2973
+ return void 0;
2974
+ }
2975
+ const parsed = (0, import_compiler_sfc.parse)(code, { filename });
2976
+ const template = parsed.descriptor.template;
2977
+ if (!template?.ast) {
2978
+ return void 0;
2979
+ }
2980
+ const s = new import_magic_string.default(code);
2981
+ const relativeFile = normalizePath2((0, import_node_path4.relative)(options.root, filename));
2982
+ for (const node of template.ast.children) {
2983
+ injectNodeId(s, node, relativeFile);
2984
+ }
2985
+ if (!s.hasChanged()) {
2986
+ return void 0;
2987
+ }
2988
+ return {
2989
+ code: s.toString(),
2990
+ map: s.generateMap({ hires: true })
2991
+ };
2992
+ }
2993
+ };
2994
+ }
2995
+ function shouldSkipInstrumentation(id) {
2996
+ if (id.startsWith("\0")) {
2997
+ return true;
2998
+ }
2999
+ const normalized = normalizePath2(id);
3000
+ if (normalized.includes("/node_modules/")) {
3001
+ return true;
3002
+ }
3003
+ return !normalized.startsWith("/") && !/^[A-Za-z]:\//.test(normalized);
3004
+ }
3005
+ function normalizePath2(path8) {
3006
+ return path8.replace(/\\/g, "/");
3007
+ }
3008
+ function injectNodeId(s, node, relativeFile) {
3009
+ if (node.type !== ELEMENT_NODE_TYPE || !node.tag || !node.loc) {
3010
+ return;
3011
+ }
3012
+ if (!SKIPPED_TAGS.has(node.tag) && !hasMcpIdAttr(node)) {
3013
+ const id = `${relativeFile}:${String(node.loc.start.line)}:${String(node.loc.start.column)}`;
3014
+ const insertAt = node.loc.start.offset + node.tag.length + 1;
3015
+ s.appendLeft(insertAt, ` ${MCP_ID_ATTR}="${id}"`);
3016
+ }
3017
+ for (const child of node.children ?? []) {
3018
+ injectNodeId(s, child, relativeFile);
3019
+ }
3020
+ }
3021
+ function hasMcpIdAttr(node) {
3022
+ return (node.props ?? []).some((prop) => {
3023
+ if (!isTemplateProp(prop)) {
3024
+ return false;
3025
+ }
3026
+ return prop.name === MCP_ID_ATTR;
3027
+ });
3028
+ }
3029
+ function isTemplateProp(value) {
3030
+ return Boolean(value && typeof value === "object" && "name" in value);
3031
+ }
3032
+
2772
3033
  // src/plugin/injectRuntime.ts
2773
3034
  var import_node_module = require("module");
2774
- var import_node_path4 = require("path");
3035
+ var import_node_path5 = require("path");
2775
3036
  function createRuntimeInjectionController(options, getConfig) {
2776
3037
  return {
2777
3038
  resolveId(importee) {
@@ -2788,7 +3049,7 @@ function createRuntimeInjectionController(options, getConfig) {
2788
3049
  },
2789
3050
  load(id) {
2790
3051
  if (id === RESOLVED_VIRTUAL_RUNTIME_ID) {
2791
- return createRuntimeModule();
3052
+ return createRuntimeModule(options, getConfig()?.root);
2792
3053
  }
2793
3054
  if (id === RESOLVED_VIRTUAL_SCREENSHOT_CONFIG_ID) {
2794
3055
  return createScreenshotConfigModule(options);
@@ -2831,14 +3092,17 @@ ${code}`;
2831
3092
  }
2832
3093
  };
2833
3094
  }
2834
- function createRuntimeModule() {
3095
+ function createRuntimeModule(options, root) {
2835
3096
  return [
2836
3097
  "import { setScreenshotModuleRegistry, setSnapdomLoader, startRuntimeClient } from '@xiaou66/vite-plugin-vue-mcp-next/runtime/client';",
2837
3098
  `import { screenshotModuleRegistry } from '${VIRTUAL_SCREENSHOT_CONFIG_ID}';`,
2838
3099
  `import { loadSnapdom } from '${VIRTUAL_SNAPDOM_LOADER_ID}';`,
2839
3100
  "setScreenshotModuleRegistry(screenshotModuleRegistry);",
2840
3101
  "setSnapdomLoader(loadSnapdom);",
2841
- "void startRuntimeClient();"
3102
+ `void startRuntimeClient(${JSON.stringify({
3103
+ elementPicker: options.elementPicker,
3104
+ projectRoot: root
3105
+ })});`
2842
3106
  ].join("\n");
2843
3107
  }
2844
3108
  function createSnapdomLoaderModule(root) {
@@ -2855,7 +3119,7 @@ function createSnapdomLoaderModule(root) {
2855
3119
  }
2856
3120
  function canResolveSnapdomFromProject(root) {
2857
3121
  try {
2858
- (0, import_node_module.createRequire)((0, import_node_path4.join)(root ?? process.cwd(), "package.json")).resolve(
3122
+ (0, import_node_module.createRequire)((0, import_node_path5.join)(root ?? process.cwd(), "package.json")).resolve(
2859
3123
  "@zumer/snapdom"
2860
3124
  );
2861
3125
  return true;
@@ -2895,16 +3159,16 @@ function getPluginPath(plugin) {
2895
3159
 
2896
3160
  // src/plugin/mcpClientConfig/index.ts
2897
3161
  var import_promises5 = __toESM(require("fs/promises"), 1);
2898
- var import_node_path7 = __toESM(require("path"), 1);
3162
+ var import_node_path8 = __toESM(require("path"), 1);
2899
3163
 
2900
3164
  // src/plugin/mcpClientConfig/codexConfig.ts
2901
3165
  var import_promises3 = __toESM(require("fs/promises"), 1);
2902
- var import_node_path5 = __toESM(require("path"), 1);
3166
+ var import_node_path6 = __toESM(require("path"), 1);
2903
3167
  async function updateCodexMcpClientConfig(options) {
2904
3168
  try {
2905
3169
  const current = await readOptionalTextFile(options.configPath);
2906
3170
  const next = replaceOrAppendOwnedBlock(current, options);
2907
- await import_promises3.default.mkdir(import_node_path5.default.dirname(options.configPath), { recursive: true });
3171
+ await import_promises3.default.mkdir(import_node_path6.default.dirname(options.configPath), { recursive: true });
2908
3172
  await import_promises3.default.writeFile(options.configPath, next);
2909
3173
  } catch (error) {
2910
3174
  console.warn(
@@ -3004,7 +3268,7 @@ function isNodeError(error) {
3004
3268
 
3005
3269
  // src/plugin/mcpClientConfig/jsonConfig.ts
3006
3270
  var import_promises4 = __toESM(require("fs/promises"), 1);
3007
- var import_node_path6 = __toESM(require("path"), 1);
3271
+ var import_node_path7 = __toESM(require("path"), 1);
3008
3272
  async function updateJsonMcpClientConfig(options) {
3009
3273
  try {
3010
3274
  const config = await readJsonConfig(options.configPath);
@@ -3044,7 +3308,7 @@ function renameLegacyServer(mcpServers, options) {
3044
3308
  );
3045
3309
  }
3046
3310
  async function writeJsonConfig(configPath, config) {
3047
- await import_promises4.default.mkdir(import_node_path6.default.dirname(configPath), { recursive: true });
3311
+ await import_promises4.default.mkdir(import_node_path7.default.dirname(configPath), { recursive: true });
3048
3312
  await import_promises4.default.writeFile(configPath, `${JSON.stringify(config, null, 2)}
3049
3313
  `);
3050
3314
  }
@@ -3089,12 +3353,12 @@ async function updateMcpClientConfigs(root, sseUrl, streamableHttpUrl, options,
3089
3353
  root,
3090
3354
  clientName: "cursor",
3091
3355
  enabled: options.cursor,
3092
- entryPath: import_node_path7.default.join(root, ".cursor"),
3356
+ entryPath: import_node_path8.default.join(root, ".cursor"),
3093
3357
  entryKind: "directory",
3094
3358
  userOptions,
3095
3359
  createJob: () => updateJsonMcpClientConfig({
3096
3360
  clientName: "Cursor",
3097
- configPath: import_node_path7.default.join(root, ".cursor", "mcp.json"),
3361
+ configPath: import_node_path8.default.join(root, ".cursor", "mcp.json"),
3098
3362
  mcpUrl: sseUrl,
3099
3363
  serverName,
3100
3364
  legacyServerNames
@@ -3104,11 +3368,11 @@ async function updateMcpClientConfigs(root, sseUrl, streamableHttpUrl, options,
3104
3368
  root,
3105
3369
  clientName: "codex",
3106
3370
  enabled: options.codex,
3107
- entryPath: import_node_path7.default.join(root, ".codex"),
3371
+ entryPath: import_node_path8.default.join(root, ".codex"),
3108
3372
  entryKind: "directory",
3109
3373
  userOptions,
3110
3374
  createJob: () => updateCodexMcpClientConfig({
3111
- configPath: import_node_path7.default.join(root, ".codex", "config.toml"),
3375
+ configPath: import_node_path8.default.join(root, ".codex", "config.toml"),
3112
3376
  mcpUrl: streamableHttpUrl,
3113
3377
  serverName,
3114
3378
  legacyServerNames
@@ -3118,12 +3382,12 @@ async function updateMcpClientConfigs(root, sseUrl, streamableHttpUrl, options,
3118
3382
  root,
3119
3383
  clientName: "claudeCode",
3120
3384
  enabled: options.claudeCode,
3121
- entryPath: import_node_path7.default.join(root, ".mcp.json"),
3385
+ entryPath: import_node_path8.default.join(root, ".mcp.json"),
3122
3386
  entryKind: "file",
3123
3387
  userOptions,
3124
3388
  createJob: () => updateJsonMcpClientConfig({
3125
3389
  clientName: "Claude Code",
3126
- configPath: import_node_path7.default.join(root, ".mcp.json"),
3390
+ configPath: import_node_path8.default.join(root, ".mcp.json"),
3127
3391
  mcpUrl: sseUrl,
3128
3392
  serverName,
3129
3393
  legacyServerNames
@@ -3133,12 +3397,12 @@ async function updateMcpClientConfigs(root, sseUrl, streamableHttpUrl, options,
3133
3397
  root,
3134
3398
  clientName: "trae",
3135
3399
  enabled: options.trae,
3136
- entryPath: import_node_path7.default.join(root, ".trae"),
3400
+ entryPath: import_node_path8.default.join(root, ".trae"),
3137
3401
  entryKind: "directory",
3138
3402
  userOptions,
3139
3403
  createJob: () => updateJsonMcpClientConfig({
3140
3404
  clientName: "Trae",
3141
- configPath: import_node_path7.default.join(root, ".trae", "mcp.json"),
3405
+ configPath: import_node_path8.default.join(root, ".trae", "mcp.json"),
3142
3406
  mcpUrl: sseUrl,
3143
3407
  serverName,
3144
3408
  legacyServerNames
@@ -3198,11 +3462,11 @@ function isNodeError3(error) {
3198
3462
 
3199
3463
  // src/plugin/skillConfig/index.ts
3200
3464
  var import_promises7 = __toESM(require("fs/promises"), 1);
3201
- var import_node_path9 = __toESM(require("path"), 1);
3465
+ var import_node_path10 = __toESM(require("path"), 1);
3202
3466
 
3203
3467
  // src/plugin/skillConfig/writers.ts
3204
3468
  var import_promises6 = __toESM(require("fs/promises"), 1);
3205
- var import_node_path8 = __toESM(require("path"), 1);
3469
+ var import_node_path9 = __toESM(require("path"), 1);
3206
3470
  var GENERATED_SKILL_CONFIG_MARKER = "<!-- Generated by vite-plugin-vue-mcp-next. Safe to edit, but automatic updates only apply while this marker remains. -->";
3207
3471
  async function writeGeneratedTextFile(options) {
3208
3472
  try {
@@ -3213,7 +3477,7 @@ async function writeGeneratedTextFile(options) {
3213
3477
  );
3214
3478
  return;
3215
3479
  }
3216
- await import_promises6.default.mkdir(import_node_path8.default.dirname(options.filePath), { recursive: true });
3480
+ await import_promises6.default.mkdir(import_node_path9.default.dirname(options.filePath), { recursive: true });
3217
3481
  await import_promises6.default.writeFile(options.filePath, options.content);
3218
3482
  } catch (error) {
3219
3483
  console.warn(
@@ -3240,7 +3504,7 @@ function isNodeError4(error) {
3240
3504
 
3241
3505
  // src/plugin/skillConfig/index.ts
3242
3506
  var PACKAGE_NAME = "@xiaou66/vite-plugin-vue-mcp-next";
3243
- var PACKAGED_SKILL_PATH = import_node_path9.default.join("skills", "vite-mcp-next", "SKILL.md");
3507
+ var PACKAGED_SKILL_PATH = import_node_path10.default.join("skills", "vite-mcp-next", "SKILL.md");
3244
3508
  async function updateSkillConfigs(root, options) {
3245
3509
  if (!options.autoConfig) {
3246
3510
  return;
@@ -3256,13 +3520,13 @@ async function updateSkillConfigs(root, options) {
3256
3520
  function createSkillConfigDescriptors(root) {
3257
3521
  return [
3258
3522
  {
3259
- entryPath: import_node_path9.default.join(root, ".codex"),
3260
- filePath: import_node_path9.default.join(root, ".codex", "skills", "vite-mcp-next", "SKILL.md"),
3523
+ entryPath: import_node_path10.default.join(root, ".codex"),
3524
+ filePath: import_node_path10.default.join(root, ".codex", "skills", "vite-mcp-next", "SKILL.md"),
3261
3525
  targetName: "Codex skill"
3262
3526
  },
3263
3527
  {
3264
- entryPath: import_node_path9.default.join(root, ".claude"),
3265
- filePath: import_node_path9.default.join(
3528
+ entryPath: import_node_path10.default.join(root, ".claude"),
3529
+ filePath: import_node_path10.default.join(
3266
3530
  root,
3267
3531
  ".claude",
3268
3532
  "skills",
@@ -3272,8 +3536,8 @@ function createSkillConfigDescriptors(root) {
3272
3536
  targetName: "Claude Code skill"
3273
3537
  },
3274
3538
  {
3275
- entryPath: import_node_path9.default.join(root, ".cursor"),
3276
- filePath: import_node_path9.default.join(root, ".cursor", "rules", "vite-mcp-next.mdc"),
3539
+ entryPath: import_node_path10.default.join(root, ".cursor"),
3540
+ filePath: import_node_path10.default.join(root, ".cursor", "rules", "vite-mcp-next.mdc"),
3277
3541
  targetName: "Cursor rule"
3278
3542
  }
3279
3543
  ];
@@ -3321,9 +3585,9 @@ async function safelyReadPackagedSkillContent(root) {
3321
3585
  }
3322
3586
  function getPackagedSkillCandidates(root) {
3323
3587
  return [
3324
- import_node_path9.default.resolve(root, "node_modules", PACKAGE_NAME, PACKAGED_SKILL_PATH),
3325
- import_node_path9.default.resolve(process.cwd(), "node_modules", PACKAGE_NAME, PACKAGED_SKILL_PATH),
3326
- import_node_path9.default.resolve(process.cwd(), PACKAGED_SKILL_PATH)
3588
+ import_node_path10.default.resolve(root, "node_modules", PACKAGE_NAME, PACKAGED_SKILL_PATH),
3589
+ import_node_path10.default.resolve(process.cwd(), "node_modules", PACKAGE_NAME, PACKAGED_SKILL_PATH),
3590
+ import_node_path10.default.resolve(process.cwd(), PACKAGED_SKILL_PATH)
3327
3591
  ];
3328
3592
  }
3329
3593
  async function hasDirectoryEntry(entryPath) {
@@ -3357,6 +3621,7 @@ function vueMcpNext(userOptions = {}) {
3357
3621
  options,
3358
3622
  () => config
3359
3623
  );
3624
+ let elementInstrumentation;
3360
3625
  const cdpLifecycle = createCdpLifecycleController(ctx);
3361
3626
  ctx.cdpLifecycle = cdpLifecycle;
3362
3627
  return {
@@ -3365,6 +3630,9 @@ function vueMcpNext(userOptions = {}) {
3365
3630
  apply: "serve",
3366
3631
  configResolved(resolvedConfig) {
3367
3632
  config = resolvedConfig;
3633
+ elementInstrumentation = createElementInstrumentationController({
3634
+ root: resolvedConfig.root
3635
+ });
3368
3636
  },
3369
3637
  async configureServer(server) {
3370
3638
  ctx.server = server;
@@ -3381,32 +3649,53 @@ function vueMcpNext(userOptions = {}) {
3381
3649
  () => createMcpServer(ctx, server),
3382
3650
  server
3383
3651
  );
3384
- server.ws.on(
3385
- "vite-plugin-vue-mcp-next:page-connected",
3386
- (payload) => {
3387
- if (isRuntimePageTarget(payload)) {
3388
- ctx.pages.upsert(payload);
3389
- void ctx.hooks.callHook(RUNTIME_PAGE_RECONNECTED_EVENT, payload);
3390
- void cdpLifecycle.connectPage(payload);
3652
+ const lastSeenAt = /* @__PURE__ */ new Map();
3653
+ const heartbeatTimer = setInterval(() => {
3654
+ const now = Date.now();
3655
+ for (const [pageId, seenAt] of lastSeenAt) {
3656
+ const target = ctx.pages.get(pageId);
3657
+ if (!target || target.source !== "runtime" || !target.connected) {
3658
+ lastSeenAt.delete(pageId);
3659
+ continue;
3391
3660
  }
3392
- }
3393
- );
3394
- server.ws.on(
3395
- "vite-plugin-vue-mcp-next:console-record",
3396
- (payload) => {
3397
- if (isConsoleRecord(payload)) {
3398
- ctx.consoleRecords.push(payload);
3661
+ if (now - seenAt >= DEFAULT_RUNTIME_PAGE_HEARTBEAT_TIMEOUT_MS) {
3662
+ ctx.pages.disconnect(pageId, now);
3663
+ lastSeenAt.delete(pageId);
3399
3664
  }
3400
3665
  }
3401
- );
3402
- server.ws.on(
3403
- "vite-plugin-vue-mcp-next:network-record",
3404
- (payload) => {
3405
- if (isNetworkRecord(payload)) {
3406
- ctx.networkRecords.push(payload);
3666
+ }, DEFAULT_RUNTIME_PAGE_HEARTBEAT_SCAN_INTERVAL_MS);
3667
+ server.ws.on(RUNTIME_PAGE_CONNECTED_EVENT, (payload) => {
3668
+ if (isRuntimePageTarget(payload)) {
3669
+ ctx.pages.upsert(payload);
3670
+ lastSeenAt.set(payload.pageId, Date.now());
3671
+ void ctx.hooks.callHook(RUNTIME_PAGE_RECONNECTED_EVENT, payload);
3672
+ void cdpLifecycle.connectPage(payload);
3673
+ }
3674
+ });
3675
+ server.ws.on(RUNTIME_PAGE_HEARTBEAT_EVENT, (payload) => {
3676
+ if (isRuntimeHeartbeatTarget(payload)) {
3677
+ const target = ctx.pages.get(payload.pageId);
3678
+ if (target?.source === "runtime" && target.connected) {
3679
+ lastSeenAt.set(payload.pageId, payload.timestamp);
3407
3680
  }
3408
3681
  }
3409
- );
3682
+ });
3683
+ server.ws.on(RUNTIME_PAGE_DISCONNECTED_EVENT, (payload) => {
3684
+ if (isRuntimeDisconnectTarget(payload)) {
3685
+ ctx.pages.disconnect(payload.pageId);
3686
+ lastSeenAt.delete(payload.pageId);
3687
+ }
3688
+ });
3689
+ server.ws.on("vite-plugin-vue-mcp-next:console-record", (payload) => {
3690
+ if (isConsoleRecord(payload)) {
3691
+ ctx.consoleRecords.push(payload);
3692
+ }
3693
+ });
3694
+ server.ws.on("vite-plugin-vue-mcp-next:network-record", (payload) => {
3695
+ if (isNetworkRecord(payload)) {
3696
+ ctx.networkRecords.push(payload);
3697
+ }
3698
+ });
3410
3699
  server.ws.on(
3411
3700
  "vite-plugin-vue-mcp-next:performance-record",
3412
3701
  (payload) => {
@@ -3436,6 +3725,7 @@ function vueMcpNext(userOptions = {}) {
3436
3725
  }, 300);
3437
3726
  }
3438
3727
  server.httpServer?.once("close", () => {
3728
+ clearInterval(heartbeatTimer);
3439
3729
  void cdpLifecycle.closeAll();
3440
3730
  });
3441
3731
  },
@@ -3446,7 +3736,21 @@ function vueMcpNext(userOptions = {}) {
3446
3736
  return runtimeInjection.load(id);
3447
3737
  },
3448
3738
  transform(code, id, transformOptions) {
3449
- return runtimeInjection.transform(code, id, transformOptions?.ssr);
3739
+ const instrumented = elementInstrumentation?.transform(
3740
+ code,
3741
+ id,
3742
+ transformOptions?.ssr
3743
+ );
3744
+ const nextCode = instrumented && typeof instrumented === "object" && "code" in instrumented && typeof instrumented.code === "string" ? instrumented.code : code;
3745
+ const runtimeInjected = runtimeInjection.transform(
3746
+ nextCode,
3747
+ id,
3748
+ transformOptions?.ssr
3749
+ );
3750
+ if (runtimeInjected) {
3751
+ return runtimeInjected;
3752
+ }
3753
+ return instrumented;
3450
3754
  },
3451
3755
  transformIndexHtml(html) {
3452
3756
  return runtimeInjection.transformIndexHtml(html);
@@ -3460,6 +3764,20 @@ function isRuntimePageTarget(payload) {
3460
3764
  const target = payload;
3461
3765
  return target.source === "runtime" && typeof target.pageId === "string" && typeof target.url === "string" && typeof target.pathname === "string" && typeof target.connected === "boolean" && (target.runtimeClientId === void 0 || typeof target.runtimeClientId === "string");
3462
3766
  }
3767
+ function isRuntimeHeartbeatTarget(payload) {
3768
+ if (!payload || typeof payload !== "object") {
3769
+ return false;
3770
+ }
3771
+ const target = payload;
3772
+ return typeof target.pageId === "string" && typeof target.timestamp === "number";
3773
+ }
3774
+ function isRuntimeDisconnectTarget(payload) {
3775
+ if (!payload || typeof payload !== "object") {
3776
+ return false;
3777
+ }
3778
+ const target = payload;
3779
+ return typeof target.pageId === "string";
3780
+ }
3463
3781
  function isConsoleRecord(payload) {
3464
3782
  if (!payload || typeof payload !== "object") {
3465
3783
  return false;