@aipanel/core 1.2.19 → 1.2.20

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.
@@ -45,7 +45,7 @@ export declare const HOST_EVENTS_API_PATH = "/__aipanel_host_events__";
45
45
  export declare const CONTEXT_UPDATE_INTERVAL = 500;
46
46
  /** 服务器同步间隔(毫秒) */
47
47
  export declare const SERVER_SYNC_INTERVAL = 2000;
48
- /** Vue Inspector 检查间隔(毫秒) */
48
+ /** 框架 Inspector 检查间隔(毫秒) */
49
49
  export declare const INSPECTOR_CHECK_INTERVAL = 500;
50
50
  /** 自动打开延迟(毫秒) */
51
51
  export declare const AUTO_OPEN_DELAY = 1000;
@@ -185,3 +185,14 @@ export declare const SSE_EVENT_TYPES: {
185
185
  readonly CLEAR_ELEMENTS: "CLEAR_ELEMENTS";
186
186
  };
187
187
  export type SSEEventType = (typeof SSE_EVENT_TYPES)[keyof typeof SSE_EVENT_TYPES];
188
+ /** ==================== 元素选择器(click-to-source) ==================== */
189
+ /**
190
+ * 元素选择器适配器标识:服务端注入的构建期插件与客户端运行时适配器共用同一标识,
191
+ * 已登记 Vue / React;新增框架在此登记,两侧按同一 id 对齐。
192
+ */
193
+ export declare const INSPECTOR_ADAPTER_IDS: {
194
+ readonly vue: "vue";
195
+ readonly react: "react";
196
+ };
197
+ /** 元素选择器适配器标识类型 */
198
+ export type InspectorAdapterId = (typeof INSPECTOR_ADAPTER_IDS)[keyof typeof INSPECTOR_ADAPTER_IDS];
@@ -106,6 +106,10 @@ const SSE_EVENT_TYPES = {
106
106
  SESSION_EVENT: "SESSION_EVENT",
107
107
  CLEAR_ELEMENTS: "CLEAR_ELEMENTS"
108
108
  };
109
+ const INSPECTOR_ADAPTER_IDS = {
110
+ vue: "vue",
111
+ react: "react"
112
+ };
109
113
  export {
110
114
  AIPANEL_CACHE_DIR,
111
115
  AUTO_OPEN_DELAY,
@@ -124,6 +128,7 @@ export {
124
128
  EXT_MSG,
125
129
  HOST_EVENTS_API_PATH,
126
130
  INIT_MARKER,
131
+ INSPECTOR_ADAPTER_IDS,
127
132
  INSPECTOR_CHECK_INTERVAL,
128
133
  LOGS_API_PATH,
129
134
  LOG_PREFIX,
@@ -0,0 +1,6 @@
1
+ import type { InspectorAdapter } from "./types";
2
+ export * from "./types";
3
+ /** 全部已登记适配器:用于静态元数据(忽略标记、源码位置解析) */
4
+ export declare function listInspectorAdapters(): readonly InspectorAdapter[];
5
+ /** 当前可用的适配器(对应框架的 inspector 运行时已注入页面);无可用返回 null */
6
+ export declare function resolveInspectorAdapter(): InspectorAdapter | null;
@@ -0,0 +1,14 @@
1
+ import { reactInspectorAdapter } from "./react.mjs";
2
+ import { vueInspectorAdapter } from "./vue.mjs";
3
+ export * from "./types.mjs";
4
+ const adapters = [vueInspectorAdapter, reactInspectorAdapter];
5
+ function listInspectorAdapters() {
6
+ return adapters;
7
+ }
8
+ function resolveInspectorAdapter() {
9
+ return adapters.find((adapter) => adapter.isAvailable()) ?? null;
10
+ }
11
+ export {
12
+ listInspectorAdapters,
13
+ resolveInspectorAdapter
14
+ };
@@ -0,0 +1,8 @@
1
+ import { type InspectorAdapter } from "./types";
2
+ /**
3
+ * React 适配器:构建期由 vite 集成(@code-inspector/core transformCode)注入
4
+ * data-insp-path 源码坐标标记,运行时按「注入标记 → react-dev-inspector 兼容属性 →
5
+ * fiber._debugSource」依次解析;React 无底层 inspector 运行时,
6
+ * 点击接管由适配器自装的 document 捕获监听实现。
7
+ */
8
+ export declare const reactInspectorAdapter: InspectorAdapter;
@@ -0,0 +1,105 @@
1
+ import { INSPECTOR_ADAPTER_IDS } from "../constants.mjs";
2
+ import {
3
+ parseSourceLocation
4
+ } from "./types.mjs";
5
+ const SOURCE_LOCATION_ATTRIBUTE = "data-insp-path";
6
+ const RDI_PATH_ATTRIBUTE = "data-inspector-relative-path";
7
+ const RDI_LINE_ATTRIBUTE = "data-inspector-line";
8
+ const RDI_COLUMN_ATTRIBUTE = "data-inspector-column";
9
+ const FIBER_KEY_PREFIX = "__reactFiber$";
10
+ const CONTAINER_KEY_PREFIX = "__reactContainer$";
11
+ const FIBER_WALK_LIMIT = 50;
12
+ const DETECT_SCAN_LIMIT = 200;
13
+ let clickHandler = null;
14
+ let listenerInstalled = false;
15
+ let enabled = false;
16
+ function hasFiberKey(element) {
17
+ const keys = Object.keys(element);
18
+ return keys.some(
19
+ (key) => key.startsWith(FIBER_KEY_PREFIX) || key.startsWith(CONTAINER_KEY_PREFIX)
20
+ );
21
+ }
22
+ function detectReactRuntime() {
23
+ if (typeof document === "undefined") return false;
24
+ const roots = document.querySelectorAll("#root, #__next, [data-reactroot]");
25
+ for (const element of [document.body, ...roots]) {
26
+ if (element && hasFiberKey(element)) return true;
27
+ }
28
+ const descendants = document.body?.querySelectorAll("*") ?? [];
29
+ const limit = Math.min(descendants.length, DETECT_SCAN_LIMIT);
30
+ for (let index = 0; index < limit; index += 1) {
31
+ if (hasFiberKey(descendants[index])) return true;
32
+ }
33
+ return false;
34
+ }
35
+ function resolveFromAttributes(element) {
36
+ const marker = element.getAttribute(SOURCE_LOCATION_ATTRIBUTE);
37
+ if (marker) {
38
+ const location = parseSourceLocation(marker.replace(/:[^:]*$/, ""));
39
+ if (location) return location;
40
+ }
41
+ const rdiPath = element.getAttribute(RDI_PATH_ATTRIBUTE);
42
+ if (rdiPath) {
43
+ return {
44
+ file: rdiPath,
45
+ line: Number.parseInt(element.getAttribute(RDI_LINE_ATTRIBUTE) ?? "", 10) || null,
46
+ column: Number.parseInt(element.getAttribute(RDI_COLUMN_ATTRIBUTE) ?? "", 10) || null
47
+ };
48
+ }
49
+ return null;
50
+ }
51
+ function resolveFromFiber(element) {
52
+ const fiberKey = Object.keys(element).find((key) => key.startsWith(FIBER_KEY_PREFIX));
53
+ let fiber = fiberKey ? element[fiberKey] : null;
54
+ for (let depth = 0; fiber && depth < FIBER_WALK_LIMIT; depth += 1) {
55
+ const source = fiber._debugSource;
56
+ if (source?.fileName) {
57
+ return {
58
+ file: source.fileName,
59
+ line: source.lineNumber ?? null,
60
+ // React 的 columnNumber 为 0 基,统一转 1 基(与编辑器跳转一致)
61
+ column: source.columnNumber != null ? source.columnNumber + 1 : null
62
+ };
63
+ }
64
+ fiber = fiber.return;
65
+ }
66
+ return null;
67
+ }
68
+ function handleDocumentClick(event) {
69
+ if (!enabled) return;
70
+ const element = event.target instanceof Element ? event.target : null;
71
+ if (clickHandler?.(element, event)) {
72
+ event.preventDefault();
73
+ event.stopPropagation();
74
+ }
75
+ }
76
+ const reactInspectorAdapter = {
77
+ id: INSPECTOR_ADAPTER_IDS.react,
78
+ label: "React Inspector",
79
+ ignoreSelectors: [],
80
+ ignoreAttributes: [],
81
+ isAvailable() {
82
+ return detectReactRuntime();
83
+ },
84
+ resolveSourceLocation(element) {
85
+ let current = element;
86
+ while (current) {
87
+ const location = resolveFromAttributes(current) ?? resolveFromFiber(current);
88
+ if (location?.file) return location;
89
+ current = current.parentElement;
90
+ }
91
+ return null;
92
+ },
93
+ onElementClick(handler) {
94
+ clickHandler = handler;
95
+ if (listenerInstalled) return;
96
+ document.addEventListener("click", handleDocumentClick, true);
97
+ listenerInstalled = true;
98
+ },
99
+ setEnabled(next) {
100
+ enabled = next;
101
+ }
102
+ };
103
+ export {
104
+ reactInspectorAdapter
105
+ };
@@ -0,0 +1,35 @@
1
+ import type { InspectorAdapterId } from "../constants";
2
+ /** 元素对应的源码位置(跨框架统一形态;未解析到的字段为 null) */
3
+ export interface InspectorSourceLocation {
4
+ file: string | null;
5
+ line: number | null;
6
+ column: number | null;
7
+ }
8
+ /**
9
+ * 元素点击处理器。
10
+ * 返回 true 表示宿主已接管:适配器抑制底层 inspector 的默认行为(如打开编辑器)。
11
+ */
12
+ export type InspectorElementClickHandler = (element: Element | null, event: MouseEvent) => boolean;
13
+ /**
14
+ * 框架侧「点击元素 → 源码位置」适配器契约。
15
+ * 宿主(挂件 / 扩展)只依赖此接口,不感知 Vue、React 等框架差异。
16
+ */
17
+ export interface InspectorAdapter {
18
+ readonly id: InspectorAdapterId;
19
+ /** 用户可见名称(提示文案用) */
20
+ readonly label: string;
21
+ /** 运行时是否可用(对应框架的 inspector 已注入页面) */
22
+ isAvailable(): boolean;
23
+ /** 适配器自身覆盖层的选择器:宿主需忽略,不参与选择 */
24
+ readonly ignoreSelectors: readonly string[];
25
+ /** 适配器自带的忽略标记属性:宿主需忽略,不参与选择 */
26
+ readonly ignoreAttributes: readonly string[];
27
+ /** 元素 → 源码位置;解析不到返回 null */
28
+ resolveSourceLocation(element: Element): InspectorSourceLocation | null;
29
+ /** 注册元素点击处理器(幂等:同一底层 inspector 只安装一次) */
30
+ onElementClick(handler: InspectorElementClickHandler): void;
31
+ /** 同步底层 inspector 的启用状态 */
32
+ setEnabled(enabled: boolean): void;
33
+ }
34
+ /** 解析 "文件:行:列" 形式的源码坐标(各框架适配器共用的标记格式) */
35
+ export declare function parseSourceLocation(value: string): InspectorSourceLocation | null;
@@ -0,0 +1,12 @@
1
+ function parseSourceLocation(value) {
2
+ const match = /^(.+):([\d]+):([\d]+)$/.exec(value);
3
+ if (!match) return null;
4
+ return {
5
+ file: match[1],
6
+ line: Number.parseInt(match[2], 10),
7
+ column: Number.parseInt(match[3], 10)
8
+ };
9
+ }
10
+ export {
11
+ parseSourceLocation
12
+ };
@@ -0,0 +1,20 @@
1
+ import { type InspectorAdapter } from "./types";
2
+ /** unplugin-vue-inspector 暴露到页面的运行时控制对象 */
3
+ interface VueInspectorRuntime {
4
+ handleClick: (e: MouseEvent) => void;
5
+ enable: () => void;
6
+ disable: () => void;
7
+ /** AIPanel 已接管 handleClick 的标记(幂等安装) */
8
+ __aipanel_hooked?: boolean;
9
+ }
10
+ declare global {
11
+ interface Window {
12
+ __VUE_INSPECTOR__?: VueInspectorRuntime;
13
+ }
14
+ }
15
+ /**
16
+ * Vue 适配器:复用 unplugin-vue-inspector 注入的运行时与源码坐标标记,
17
+ * 在宿主选择模式下接管点击、解析元素源码位置。
18
+ */
19
+ export declare const vueInspectorAdapter: InspectorAdapter;
20
+ export {};
@@ -0,0 +1,120 @@
1
+ import { INSPECTOR_ADAPTER_IDS } from "../constants.mjs";
2
+ import {
3
+ parseSourceLocation
4
+ } from "./types.mjs";
5
+ const SOURCE_LOCATION_ATTRIBUTE = "data-v-inspector";
6
+ const VNODE_SOURCE_KEY = "__v_inspector";
7
+ const IGNORE_ATTRIBUTE = "data-v-inspector-ignore";
8
+ const OVERLAY_SELECTOR = "#vue-inspector-container";
9
+ function getRuntime() {
10
+ return typeof window === "undefined" ? void 0 : window.__VUE_INSPECTOR__;
11
+ }
12
+ function isAdapterOwned(element) {
13
+ return element.hasAttribute(IGNORE_ATTRIBUTE) || Boolean(element.closest(OVERLAY_SELECTOR));
14
+ }
15
+ function getMarkerData(element) {
16
+ const vnodeData = element.__vnode?.props?.[VNODE_SOURCE_KEY];
17
+ if (vnodeData) return vnodeData;
18
+ const ctxVNode = element.__vnode?.ctx?.vnode;
19
+ if (ctxVNode?.el === element) {
20
+ const ctxData = ctxVNode.props?.[VNODE_SOURCE_KEY];
21
+ if (ctxData) return ctxData;
22
+ }
23
+ const vueInstance = element.__vueParentComponent;
24
+ let currentParent = vueInstance?.parent;
25
+ while (currentParent) {
26
+ if (currentParent.vnode?.el === element) {
27
+ const parentData = currentParent.vnode.props?.[VNODE_SOURCE_KEY];
28
+ if (parentData) return parentData;
29
+ }
30
+ currentParent = currentParent.parent;
31
+ }
32
+ return element.getAttribute(SOURCE_LOCATION_ATTRIBUTE) ?? void 0;
33
+ }
34
+ function resolveFromMarker(element) {
35
+ const data = getMarkerData(element);
36
+ return data ? parseSourceLocation(data) : null;
37
+ }
38
+ function resolveFromVueInstance(element) {
39
+ const vue3Instance = element.__vueParentComponent;
40
+ if (vue3Instance) {
41
+ let current = vue3Instance;
42
+ while (current) {
43
+ const file = current.type?.__file || current.vnode?.type?.__file;
44
+ if (file) {
45
+ return { file, line: null, column: null };
46
+ }
47
+ current = current.parent;
48
+ }
49
+ }
50
+ const vue2Instance = element.__vue__;
51
+ if (vue2Instance) {
52
+ let current = vue2Instance;
53
+ while (current) {
54
+ const file = current.$options?.__file;
55
+ if (file) {
56
+ return { file, line: null, column: null };
57
+ }
58
+ current = current.$parent;
59
+ }
60
+ }
61
+ return null;
62
+ }
63
+ function mergeSourceLocations(markerLocation, instanceLocation) {
64
+ if (!markerLocation?.file && !instanceLocation?.file) return null;
65
+ if (markerLocation?.file && instanceLocation?.file) {
66
+ const isNodeModules = (path) => path.includes("node_modules");
67
+ if (!isNodeModules(markerLocation.file)) return markerLocation;
68
+ if (!isNodeModules(instanceLocation.file)) return instanceLocation;
69
+ return markerLocation;
70
+ }
71
+ return markerLocation?.file ? markerLocation : instanceLocation;
72
+ }
73
+ let clickHandler = null;
74
+ const vueInspectorAdapter = {
75
+ id: INSPECTOR_ADAPTER_IDS.vue,
76
+ label: "Vue Inspector",
77
+ ignoreSelectors: [OVERLAY_SELECTOR],
78
+ ignoreAttributes: [IGNORE_ATTRIBUTE],
79
+ isAvailable() {
80
+ return Boolean(getRuntime());
81
+ },
82
+ resolveSourceLocation(element) {
83
+ let current = element;
84
+ let markerLocation = null;
85
+ let instanceLocation = null;
86
+ while (current && !(markerLocation && instanceLocation)) {
87
+ if (!isAdapterOwned(current)) {
88
+ markerLocation ?? (markerLocation = resolveFromMarker(current));
89
+ instanceLocation ?? (instanceLocation = resolveFromVueInstance(current));
90
+ }
91
+ current = current.parentElement;
92
+ }
93
+ return mergeSourceLocations(markerLocation, instanceLocation);
94
+ },
95
+ onElementClick(handler) {
96
+ clickHandler = handler;
97
+ const runtime = getRuntime();
98
+ if (!runtime || runtime.__aipanel_hooked) return;
99
+ const originalHandleClick = runtime.handleClick.bind(runtime);
100
+ runtime.handleClick = (event) => {
101
+ const element = event.target instanceof Element ? event.target : null;
102
+ if (clickHandler?.(element, event)) {
103
+ event.preventDefault();
104
+ event.stopPropagation();
105
+ return;
106
+ }
107
+ originalHandleClick(event);
108
+ };
109
+ runtime.__aipanel_hooked = true;
110
+ },
111
+ setEnabled(enabled) {
112
+ const runtime = getRuntime();
113
+ if (!runtime) return;
114
+ if (enabled) runtime.enable();
115
+ else runtime.disable();
116
+ }
117
+ };
118
+ export {
119
+ vueInspectorAdapter
120
+ };
@@ -113,6 +113,18 @@ export interface PageContext {
113
113
  * 服务启动任务状态
114
114
  */
115
115
  export type ServiceStartupTask = "checking_provider" | "allocating_port" | "preparing_runtime" | "starting_web" | "waiting_web_ready" | "starting_proxy" | "warming_up_chrome" | "creating_session" | "provider_not_installed" | "web_start_timeout" | "proxy_start_failed" | "session_creation_failed" | "chrome_mcp_failed" | "ready";
116
+ /**
117
+ * 服务启动任务状态载荷
118
+ * currentTask / SSE TASK_UPDATE / STATUS_SYNC 共用同一形态(单一来源)
119
+ */
120
+ export interface ServiceTaskState {
121
+ /** 当前任务 */
122
+ task: ServiceStartupTask;
123
+ /** 失败原因类型(如 ChromeMcpWarmupErrorType) */
124
+ errorType?: string;
125
+ /** 失败原因文案 */
126
+ errorMessage?: string;
127
+ }
116
128
  /**
117
129
  * 服务启动任务状态映射
118
130
  */
package/es/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./common/constants";
2
+ export * from "./common/inspector";
2
3
  export * from "./common/logger-core";
3
4
  export * from "./common/options";
4
5
  export * from "./common/provider";
package/es/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./common/constants.mjs";
2
+ export * from "./common/inspector/index.mjs";
2
3
  export * from "./common/logger-core.mjs";
3
4
  export * from "./common/options.mjs";
4
5
  export * from "./common/provider.mjs";
@@ -34,6 +34,7 @@ __export(constants_exports, {
34
34
  EXT_MSG: () => EXT_MSG,
35
35
  HOST_EVENTS_API_PATH: () => HOST_EVENTS_API_PATH,
36
36
  INIT_MARKER: () => INIT_MARKER,
37
+ INSPECTOR_ADAPTER_IDS: () => INSPECTOR_ADAPTER_IDS,
37
38
  INSPECTOR_CHECK_INTERVAL: () => INSPECTOR_CHECK_INTERVAL,
38
39
  LOGS_API_PATH: () => LOGS_API_PATH,
39
40
  LOG_PREFIX: () => LOG_PREFIX,
@@ -174,6 +175,10 @@ const SSE_EVENT_TYPES = {
174
175
  SESSION_EVENT: "SESSION_EVENT",
175
176
  CLEAR_ELEMENTS: "CLEAR_ELEMENTS"
176
177
  };
178
+ const INSPECTOR_ADAPTER_IDS = {
179
+ vue: "vue",
180
+ react: "react"
181
+ };
177
182
  // Annotate the CommonJS export names for ESM import in node:
178
183
  0 && (module.exports = {
179
184
  AIPANEL_CACHE_DIR,
@@ -193,6 +198,7 @@ const SSE_EVENT_TYPES = {
193
198
  EXT_MSG,
194
199
  HOST_EVENTS_API_PATH,
195
200
  INIT_MARKER,
201
+ INSPECTOR_ADAPTER_IDS,
196
202
  INSPECTOR_CHECK_INTERVAL,
197
203
  LOGS_API_PATH,
198
204
  LOG_PREFIX,
@@ -45,7 +45,7 @@ export declare const HOST_EVENTS_API_PATH = "/__aipanel_host_events__";
45
45
  export declare const CONTEXT_UPDATE_INTERVAL = 500;
46
46
  /** 服务器同步间隔(毫秒) */
47
47
  export declare const SERVER_SYNC_INTERVAL = 2000;
48
- /** Vue Inspector 检查间隔(毫秒) */
48
+ /** 框架 Inspector 检查间隔(毫秒) */
49
49
  export declare const INSPECTOR_CHECK_INTERVAL = 500;
50
50
  /** 自动打开延迟(毫秒) */
51
51
  export declare const AUTO_OPEN_DELAY = 1000;
@@ -185,3 +185,14 @@ export declare const SSE_EVENT_TYPES: {
185
185
  readonly CLEAR_ELEMENTS: "CLEAR_ELEMENTS";
186
186
  };
187
187
  export type SSEEventType = (typeof SSE_EVENT_TYPES)[keyof typeof SSE_EVENT_TYPES];
188
+ /** ==================== 元素选择器(click-to-source) ==================== */
189
+ /**
190
+ * 元素选择器适配器标识:服务端注入的构建期插件与客户端运行时适配器共用同一标识,
191
+ * 已登记 Vue / React;新增框架在此登记,两侧按同一 id 对齐。
192
+ */
193
+ export declare const INSPECTOR_ADAPTER_IDS: {
194
+ readonly vue: "vue";
195
+ readonly react: "react";
196
+ };
197
+ /** 元素选择器适配器标识类型 */
198
+ export type InspectorAdapterId = (typeof INSPECTOR_ADAPTER_IDS)[keyof typeof INSPECTOR_ADAPTER_IDS];
@@ -0,0 +1,40 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var inspector_exports = {};
20
+ __export(inspector_exports, {
21
+ listInspectorAdapters: () => listInspectorAdapters,
22
+ resolveInspectorAdapter: () => resolveInspectorAdapter
23
+ });
24
+ module.exports = __toCommonJS(inspector_exports);
25
+ var import_react = require("./react.cjs");
26
+ var import_vue = require("./vue.cjs");
27
+ __reExport(inspector_exports, require("./types.cjs"), module.exports);
28
+ const adapters = [import_vue.vueInspectorAdapter, import_react.reactInspectorAdapter];
29
+ function listInspectorAdapters() {
30
+ return adapters;
31
+ }
32
+ function resolveInspectorAdapter() {
33
+ return adapters.find((adapter) => adapter.isAvailable()) ?? null;
34
+ }
35
+ // Annotate the CommonJS export names for ESM import in node:
36
+ 0 && (module.exports = {
37
+ listInspectorAdapters,
38
+ resolveInspectorAdapter,
39
+ ...require("./types.cjs")
40
+ });
@@ -0,0 +1,6 @@
1
+ import type { InspectorAdapter } from "./types";
2
+ export * from "./types";
3
+ /** 全部已登记适配器:用于静态元数据(忽略标记、源码位置解析) */
4
+ export declare function listInspectorAdapters(): readonly InspectorAdapter[];
5
+ /** 当前可用的适配器(对应框架的 inspector 运行时已注入页面);无可用返回 null */
6
+ export declare function resolveInspectorAdapter(): InspectorAdapter | null;
@@ -0,0 +1,126 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+ var react_exports = {};
19
+ __export(react_exports, {
20
+ reactInspectorAdapter: () => reactInspectorAdapter
21
+ });
22
+ module.exports = __toCommonJS(react_exports);
23
+ var import_constants = require("../constants.cjs");
24
+ var import_types = require("./types.cjs");
25
+ const SOURCE_LOCATION_ATTRIBUTE = "data-insp-path";
26
+ const RDI_PATH_ATTRIBUTE = "data-inspector-relative-path";
27
+ const RDI_LINE_ATTRIBUTE = "data-inspector-line";
28
+ const RDI_COLUMN_ATTRIBUTE = "data-inspector-column";
29
+ const FIBER_KEY_PREFIX = "__reactFiber$";
30
+ const CONTAINER_KEY_PREFIX = "__reactContainer$";
31
+ const FIBER_WALK_LIMIT = 50;
32
+ const DETECT_SCAN_LIMIT = 200;
33
+ let clickHandler = null;
34
+ let listenerInstalled = false;
35
+ let enabled = false;
36
+ function hasFiberKey(element) {
37
+ const keys = Object.keys(element);
38
+ return keys.some(
39
+ (key) => key.startsWith(FIBER_KEY_PREFIX) || key.startsWith(CONTAINER_KEY_PREFIX)
40
+ );
41
+ }
42
+ function detectReactRuntime() {
43
+ if (typeof document === "undefined") return false;
44
+ const roots = document.querySelectorAll("#root, #__next, [data-reactroot]");
45
+ for (const element of [document.body, ...roots]) {
46
+ if (element && hasFiberKey(element)) return true;
47
+ }
48
+ const descendants = document.body?.querySelectorAll("*") ?? [];
49
+ const limit = Math.min(descendants.length, DETECT_SCAN_LIMIT);
50
+ for (let index = 0; index < limit; index += 1) {
51
+ if (hasFiberKey(descendants[index])) return true;
52
+ }
53
+ return false;
54
+ }
55
+ function resolveFromAttributes(element) {
56
+ const marker = element.getAttribute(SOURCE_LOCATION_ATTRIBUTE);
57
+ if (marker) {
58
+ const location = (0, import_types.parseSourceLocation)(marker.replace(/:[^:]*$/, ""));
59
+ if (location) return location;
60
+ }
61
+ const rdiPath = element.getAttribute(RDI_PATH_ATTRIBUTE);
62
+ if (rdiPath) {
63
+ return {
64
+ file: rdiPath,
65
+ line: Number.parseInt(element.getAttribute(RDI_LINE_ATTRIBUTE) ?? "", 10) || null,
66
+ column: Number.parseInt(element.getAttribute(RDI_COLUMN_ATTRIBUTE) ?? "", 10) || null
67
+ };
68
+ }
69
+ return null;
70
+ }
71
+ function resolveFromFiber(element) {
72
+ const fiberKey = Object.keys(element).find((key) => key.startsWith(FIBER_KEY_PREFIX));
73
+ let fiber = fiberKey ? element[fiberKey] : null;
74
+ for (let depth = 0; fiber && depth < FIBER_WALK_LIMIT; depth += 1) {
75
+ const source = fiber._debugSource;
76
+ if (source?.fileName) {
77
+ return {
78
+ file: source.fileName,
79
+ line: source.lineNumber ?? null,
80
+ // React 的 columnNumber 为 0 基,统一转 1 基(与编辑器跳转一致)
81
+ column: source.columnNumber != null ? source.columnNumber + 1 : null
82
+ };
83
+ }
84
+ fiber = fiber.return;
85
+ }
86
+ return null;
87
+ }
88
+ function handleDocumentClick(event) {
89
+ if (!enabled) return;
90
+ const element = event.target instanceof Element ? event.target : null;
91
+ if (clickHandler?.(element, event)) {
92
+ event.preventDefault();
93
+ event.stopPropagation();
94
+ }
95
+ }
96
+ const reactInspectorAdapter = {
97
+ id: import_constants.INSPECTOR_ADAPTER_IDS.react,
98
+ label: "React Inspector",
99
+ ignoreSelectors: [],
100
+ ignoreAttributes: [],
101
+ isAvailable() {
102
+ return detectReactRuntime();
103
+ },
104
+ resolveSourceLocation(element) {
105
+ let current = element;
106
+ while (current) {
107
+ const location = resolveFromAttributes(current) ?? resolveFromFiber(current);
108
+ if (location?.file) return location;
109
+ current = current.parentElement;
110
+ }
111
+ return null;
112
+ },
113
+ onElementClick(handler) {
114
+ clickHandler = handler;
115
+ if (listenerInstalled) return;
116
+ document.addEventListener("click", handleDocumentClick, true);
117
+ listenerInstalled = true;
118
+ },
119
+ setEnabled(next) {
120
+ enabled = next;
121
+ }
122
+ };
123
+ // Annotate the CommonJS export names for ESM import in node:
124
+ 0 && (module.exports = {
125
+ reactInspectorAdapter
126
+ });
@@ -0,0 +1,8 @@
1
+ import { type InspectorAdapter } from "./types";
2
+ /**
3
+ * React 适配器:构建期由 vite 集成(@code-inspector/core transformCode)注入
4
+ * data-insp-path 源码坐标标记,运行时按「注入标记 → react-dev-inspector 兼容属性 →
5
+ * fiber._debugSource」依次解析;React 无底层 inspector 运行时,
6
+ * 点击接管由适配器自装的 document 捕获监听实现。
7
+ */
8
+ export declare const reactInspectorAdapter: InspectorAdapter;
@@ -0,0 +1,35 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+ var types_exports = {};
19
+ __export(types_exports, {
20
+ parseSourceLocation: () => parseSourceLocation
21
+ });
22
+ module.exports = __toCommonJS(types_exports);
23
+ function parseSourceLocation(value) {
24
+ const match = /^(.+):([\d]+):([\d]+)$/.exec(value);
25
+ if (!match) return null;
26
+ return {
27
+ file: match[1],
28
+ line: Number.parseInt(match[2], 10),
29
+ column: Number.parseInt(match[3], 10)
30
+ };
31
+ }
32
+ // Annotate the CommonJS export names for ESM import in node:
33
+ 0 && (module.exports = {
34
+ parseSourceLocation
35
+ });
@@ -0,0 +1,35 @@
1
+ import type { InspectorAdapterId } from "../constants";
2
+ /** 元素对应的源码位置(跨框架统一形态;未解析到的字段为 null) */
3
+ export interface InspectorSourceLocation {
4
+ file: string | null;
5
+ line: number | null;
6
+ column: number | null;
7
+ }
8
+ /**
9
+ * 元素点击处理器。
10
+ * 返回 true 表示宿主已接管:适配器抑制底层 inspector 的默认行为(如打开编辑器)。
11
+ */
12
+ export type InspectorElementClickHandler = (element: Element | null, event: MouseEvent) => boolean;
13
+ /**
14
+ * 框架侧「点击元素 → 源码位置」适配器契约。
15
+ * 宿主(挂件 / 扩展)只依赖此接口,不感知 Vue、React 等框架差异。
16
+ */
17
+ export interface InspectorAdapter {
18
+ readonly id: InspectorAdapterId;
19
+ /** 用户可见名称(提示文案用) */
20
+ readonly label: string;
21
+ /** 运行时是否可用(对应框架的 inspector 已注入页面) */
22
+ isAvailable(): boolean;
23
+ /** 适配器自身覆盖层的选择器:宿主需忽略,不参与选择 */
24
+ readonly ignoreSelectors: readonly string[];
25
+ /** 适配器自带的忽略标记属性:宿主需忽略,不参与选择 */
26
+ readonly ignoreAttributes: readonly string[];
27
+ /** 元素 → 源码位置;解析不到返回 null */
28
+ resolveSourceLocation(element: Element): InspectorSourceLocation | null;
29
+ /** 注册元素点击处理器(幂等:同一底层 inspector 只安装一次) */
30
+ onElementClick(handler: InspectorElementClickHandler): void;
31
+ /** 同步底层 inspector 的启用状态 */
32
+ setEnabled(enabled: boolean): void;
33
+ }
34
+ /** 解析 "文件:行:列" 形式的源码坐标(各框架适配器共用的标记格式) */
35
+ export declare function parseSourceLocation(value: string): InspectorSourceLocation | null;
@@ -0,0 +1,141 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+ var vue_exports = {};
19
+ __export(vue_exports, {
20
+ vueInspectorAdapter: () => vueInspectorAdapter
21
+ });
22
+ module.exports = __toCommonJS(vue_exports);
23
+ var import_constants = require("../constants.cjs");
24
+ var import_types = require("./types.cjs");
25
+ const SOURCE_LOCATION_ATTRIBUTE = "data-v-inspector";
26
+ const VNODE_SOURCE_KEY = "__v_inspector";
27
+ const IGNORE_ATTRIBUTE = "data-v-inspector-ignore";
28
+ const OVERLAY_SELECTOR = "#vue-inspector-container";
29
+ function getRuntime() {
30
+ return typeof window === "undefined" ? void 0 : window.__VUE_INSPECTOR__;
31
+ }
32
+ function isAdapterOwned(element) {
33
+ return element.hasAttribute(IGNORE_ATTRIBUTE) || Boolean(element.closest(OVERLAY_SELECTOR));
34
+ }
35
+ function getMarkerData(element) {
36
+ const vnodeData = element.__vnode?.props?.[VNODE_SOURCE_KEY];
37
+ if (vnodeData) return vnodeData;
38
+ const ctxVNode = element.__vnode?.ctx?.vnode;
39
+ if (ctxVNode?.el === element) {
40
+ const ctxData = ctxVNode.props?.[VNODE_SOURCE_KEY];
41
+ if (ctxData) return ctxData;
42
+ }
43
+ const vueInstance = element.__vueParentComponent;
44
+ let currentParent = vueInstance?.parent;
45
+ while (currentParent) {
46
+ if (currentParent.vnode?.el === element) {
47
+ const parentData = currentParent.vnode.props?.[VNODE_SOURCE_KEY];
48
+ if (parentData) return parentData;
49
+ }
50
+ currentParent = currentParent.parent;
51
+ }
52
+ return element.getAttribute(SOURCE_LOCATION_ATTRIBUTE) ?? void 0;
53
+ }
54
+ function resolveFromMarker(element) {
55
+ const data = getMarkerData(element);
56
+ return data ? (0, import_types.parseSourceLocation)(data) : null;
57
+ }
58
+ function resolveFromVueInstance(element) {
59
+ const vue3Instance = element.__vueParentComponent;
60
+ if (vue3Instance) {
61
+ let current = vue3Instance;
62
+ while (current) {
63
+ const file = current.type?.__file || current.vnode?.type?.__file;
64
+ if (file) {
65
+ return { file, line: null, column: null };
66
+ }
67
+ current = current.parent;
68
+ }
69
+ }
70
+ const vue2Instance = element.__vue__;
71
+ if (vue2Instance) {
72
+ let current = vue2Instance;
73
+ while (current) {
74
+ const file = current.$options?.__file;
75
+ if (file) {
76
+ return { file, line: null, column: null };
77
+ }
78
+ current = current.$parent;
79
+ }
80
+ }
81
+ return null;
82
+ }
83
+ function mergeSourceLocations(markerLocation, instanceLocation) {
84
+ if (!markerLocation?.file && !instanceLocation?.file) return null;
85
+ if (markerLocation?.file && instanceLocation?.file) {
86
+ const isNodeModules = (path) => path.includes("node_modules");
87
+ if (!isNodeModules(markerLocation.file)) return markerLocation;
88
+ if (!isNodeModules(instanceLocation.file)) return instanceLocation;
89
+ return markerLocation;
90
+ }
91
+ return markerLocation?.file ? markerLocation : instanceLocation;
92
+ }
93
+ let clickHandler = null;
94
+ const vueInspectorAdapter = {
95
+ id: import_constants.INSPECTOR_ADAPTER_IDS.vue,
96
+ label: "Vue Inspector",
97
+ ignoreSelectors: [OVERLAY_SELECTOR],
98
+ ignoreAttributes: [IGNORE_ATTRIBUTE],
99
+ isAvailable() {
100
+ return Boolean(getRuntime());
101
+ },
102
+ resolveSourceLocation(element) {
103
+ let current = element;
104
+ let markerLocation = null;
105
+ let instanceLocation = null;
106
+ while (current && !(markerLocation && instanceLocation)) {
107
+ if (!isAdapterOwned(current)) {
108
+ markerLocation ?? (markerLocation = resolveFromMarker(current));
109
+ instanceLocation ?? (instanceLocation = resolveFromVueInstance(current));
110
+ }
111
+ current = current.parentElement;
112
+ }
113
+ return mergeSourceLocations(markerLocation, instanceLocation);
114
+ },
115
+ onElementClick(handler) {
116
+ clickHandler = handler;
117
+ const runtime = getRuntime();
118
+ if (!runtime || runtime.__aipanel_hooked) return;
119
+ const originalHandleClick = runtime.handleClick.bind(runtime);
120
+ runtime.handleClick = (event) => {
121
+ const element = event.target instanceof Element ? event.target : null;
122
+ if (clickHandler?.(element, event)) {
123
+ event.preventDefault();
124
+ event.stopPropagation();
125
+ return;
126
+ }
127
+ originalHandleClick(event);
128
+ };
129
+ runtime.__aipanel_hooked = true;
130
+ },
131
+ setEnabled(enabled) {
132
+ const runtime = getRuntime();
133
+ if (!runtime) return;
134
+ if (enabled) runtime.enable();
135
+ else runtime.disable();
136
+ }
137
+ };
138
+ // Annotate the CommonJS export names for ESM import in node:
139
+ 0 && (module.exports = {
140
+ vueInspectorAdapter
141
+ });
@@ -0,0 +1,20 @@
1
+ import { type InspectorAdapter } from "./types";
2
+ /** unplugin-vue-inspector 暴露到页面的运行时控制对象 */
3
+ interface VueInspectorRuntime {
4
+ handleClick: (e: MouseEvent) => void;
5
+ enable: () => void;
6
+ disable: () => void;
7
+ /** AIPanel 已接管 handleClick 的标记(幂等安装) */
8
+ __aipanel_hooked?: boolean;
9
+ }
10
+ declare global {
11
+ interface Window {
12
+ __VUE_INSPECTOR__?: VueInspectorRuntime;
13
+ }
14
+ }
15
+ /**
16
+ * Vue 适配器:复用 unplugin-vue-inspector 注入的运行时与源码坐标标记,
17
+ * 在宿主选择模式下接管点击、解析元素源码位置。
18
+ */
19
+ export declare const vueInspectorAdapter: InspectorAdapter;
20
+ export {};
@@ -113,6 +113,18 @@ export interface PageContext {
113
113
  * 服务启动任务状态
114
114
  */
115
115
  export type ServiceStartupTask = "checking_provider" | "allocating_port" | "preparing_runtime" | "starting_web" | "waiting_web_ready" | "starting_proxy" | "warming_up_chrome" | "creating_session" | "provider_not_installed" | "web_start_timeout" | "proxy_start_failed" | "session_creation_failed" | "chrome_mcp_failed" | "ready";
116
+ /**
117
+ * 服务启动任务状态载荷
118
+ * currentTask / SSE TASK_UPDATE / STATUS_SYNC 共用同一形态(单一来源)
119
+ */
120
+ export interface ServiceTaskState {
121
+ /** 当前任务 */
122
+ task: ServiceStartupTask;
123
+ /** 失败原因类型(如 ChromeMcpWarmupErrorType) */
124
+ errorType?: string;
125
+ /** 失败原因文案 */
126
+ errorMessage?: string;
127
+ }
116
128
  /**
117
129
  * 服务启动任务状态映射
118
130
  */
package/lib/index.cjs CHANGED
@@ -15,6 +15,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
15
15
  var lib_exports = {};
16
16
  module.exports = __toCommonJS(lib_exports);
17
17
  __reExport(lib_exports, require("./common/constants.cjs"), module.exports);
18
+ __reExport(lib_exports, require("./common/inspector/index.cjs"), module.exports);
18
19
  __reExport(lib_exports, require("./common/logger-core.cjs"), module.exports);
19
20
  __reExport(lib_exports, require("./common/options.cjs"), module.exports);
20
21
  __reExport(lib_exports, require("./common/provider.cjs"), module.exports);
@@ -23,6 +24,7 @@ __reExport(lib_exports, require("./common/utils.cjs"), module.exports);
23
24
  // Annotate the CommonJS export names for ESM import in node:
24
25
  0 && (module.exports = {
25
26
  ...require("./common/constants.cjs"),
27
+ ...require("./common/inspector/index.cjs"),
26
28
  ...require("./common/logger-core.cjs"),
27
29
  ...require("./common/options.cjs"),
28
30
  ...require("./common/provider.cjs"),
package/lib/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./common/constants";
2
+ export * from "./common/inspector";
2
3
  export * from "./common/logger-core";
3
4
  export * from "./common/options";
4
5
  export * from "./common/provider";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aipanel/core",
3
- "version": "1.2.19",
3
+ "version": "1.2.20",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "main": "lib/index.cjs",
@@ -37,6 +37,7 @@
37
37
  "scripts": {
38
38
  "build": "pagoda-cli build",
39
39
  "test": "vitest run",
40
+ "test:coverage": "vitest run --coverage",
40
41
  "typecheck": "tsc -p tsconfig.declaration.json --noEmit"
41
42
  }
42
43
  }