@opentiny/next-sdk 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/agent/AgentModelProvider.ts +78 -81
  2. package/agent/type.ts +6 -9
  3. package/agent/utils/getAISDKTools.ts +0 -1
  4. package/agent/utils/getBuiltinMcpTools.ts +7 -7
  5. package/core.ts +0 -3
  6. package/dist/SimulatorMask-BHVXyogh-CARX3Rff.js +361 -0
  7. package/dist/agent/type.d.ts +4 -12
  8. package/dist/agent/utils/getBuiltinMcpTools.d.ts +3 -3
  9. package/dist/core.d.ts +0 -1
  10. package/dist/core.js +16 -17
  11. package/dist/index-R_HIbfUX.js +6604 -0
  12. package/dist/index.d.ts +11 -3
  13. package/dist/index.js +76 -4969
  14. package/dist/{initialize-builtin-WebMCP-HgObT902.js → initialize-builtin-WebMCP-JaoKwVlm.js} +1156 -1037
  15. package/dist/page-tools/a11y/build.d.ts +10 -0
  16. package/dist/page-tools/a11y/config.d.ts +96 -0
  17. package/dist/page-tools/a11y/constants.d.ts +11 -0
  18. package/dist/page-tools/a11y/search.d.ts +17 -0
  19. package/dist/page-tools/a11y/types.d.ts +95 -0
  20. package/dist/page-tools/a11y/utils.d.ts +55 -0
  21. package/dist/page-tools/a11y/vnode.d.ts +40 -0
  22. package/dist/page-tools/a11y-tree.d.ts +9 -99
  23. package/dist/page-tools/configs/console-cloud.d.ts +6 -0
  24. package/dist/page-tools/constants.d.ts +10 -0
  25. package/dist/page-tools/context.d.ts +40 -0
  26. package/dist/page-tools/handlers/browserState.d.ts +8 -0
  27. package/dist/page-tools/handlers/click.d.ts +8 -0
  28. package/dist/page-tools/handlers/executeJavascript.d.ts +8 -0
  29. package/dist/page-tools/handlers/fill.d.ts +8 -0
  30. package/dist/page-tools/handlers/scroll.d.ts +8 -0
  31. package/dist/page-tools/handlers/searchTree.d.ts +9 -0
  32. package/dist/page-tools/handlers/select.d.ts +8 -0
  33. package/dist/page-tools/page-agent-highlight/index.d.ts +21 -0
  34. package/dist/page-tools/page-agent-mask/SimulatorMask.d.ts +16 -0
  35. package/dist/page-tools/page-agent-mask/checkDarkMode.d.ts +5 -0
  36. package/dist/page-tools/page-agent-tool-event.d.ts +26 -0
  37. package/dist/page-tools/page-agent-tool.d.ts +3 -8
  38. package/dist/page-tools/schema.d.ts +44 -0
  39. package/dist/page-tools/tool-config.d.ts +50 -0
  40. package/dist/page-tools/utils/dom.d.ts +6 -0
  41. package/dist/page-tools/utils/scroll.d.ts +15 -0
  42. package/dist/runtime.d.ts +7 -0
  43. package/dist/runtime.js +732 -0
  44. package/dist/utils/builtinProxy.d.ts +1 -1
  45. package/dist/vitest.config.d.ts +2 -0
  46. package/index.ts +35 -5
  47. package/package.json +23 -29
  48. package/page-tools/a11y/build.ts +74 -0
  49. package/page-tools/a11y/config.ts +465 -0
  50. package/page-tools/a11y/constants.ts +131 -0
  51. package/page-tools/a11y/search.ts +127 -0
  52. package/page-tools/a11y/types.ts +105 -0
  53. package/page-tools/a11y/utils.ts +239 -0
  54. package/page-tools/a11y/vnode.ts +439 -0
  55. package/page-tools/a11y-tree.ts +9 -527
  56. package/page-tools/bridge.ts +23 -3
  57. package/page-tools/configs/console-cloud.ts +172 -0
  58. package/page-tools/constants.ts +12 -0
  59. package/page-tools/context.ts +50 -0
  60. package/page-tools/handlers/browserState.ts +12 -0
  61. package/page-tools/handlers/click.ts +30 -0
  62. package/page-tools/handlers/executeJavascript.ts +22 -0
  63. package/page-tools/handlers/fill.ts +65 -0
  64. package/page-tools/handlers/scroll.ts +66 -0
  65. package/page-tools/handlers/searchTree.ts +27 -0
  66. package/page-tools/handlers/select.ts +39 -0
  67. package/page-tools/page-agent-highlight/index.ts +245 -0
  68. package/page-tools/page-agent-mask/SimulatorMask.module.css +14 -0
  69. package/page-tools/page-agent-mask/SimulatorMask.ts +299 -0
  70. package/page-tools/page-agent-mask/checkDarkMode.ts +181 -0
  71. package/page-tools/page-agent-mask/cursor-border.svg +3 -0
  72. package/page-tools/page-agent-mask/cursor-fill.svg +5 -0
  73. package/page-tools/page-agent-mask/cursor.module.css +70 -0
  74. package/page-tools/page-agent-mask/hauwei.svg +25 -0
  75. package/page-tools/page-agent-prompt.md +34 -18
  76. package/page-tools/page-agent-tool-event.ts +113 -0
  77. package/page-tools/page-agent-tool.ts +146 -162
  78. package/page-tools/schema.ts +52 -0
  79. package/page-tools/tool-config.ts +100 -0
  80. package/page-tools/utils/dom.ts +158 -0
  81. package/page-tools/utils/scroll.ts +58 -0
  82. package/runtime.ts +44 -0
  83. package/test/page-tools/a11y/build.test.ts +638 -0
  84. package/test/page-tools/a11y/config.test.ts +370 -0
  85. package/test/page-tools/configs/console-cloud.test.ts +168 -0
  86. package/test/page-tools/page-agent-highlight.test.ts +110 -0
  87. package/test/page-tools/page-agent-tool-dispatch.test.ts +208 -0
  88. package/test/page-tools/page-agent-tool.test.ts +102 -0
  89. package/test/page-tools/tool-config.test.ts +112 -0
  90. package/test/page-tools/utils/dom.test.ts +122 -0
  91. package/utils/builtinProxy.ts +45 -13
  92. package/vite.config.runtime.ts +22 -0
  93. package/vite.config.ts +52 -8
  94. package/vitest.config.ts +10 -0
  95. package/McpSdk.ts +0 -14
  96. package/WebAgent.ts +0 -5
  97. package/WebMcp.ts +0 -26
  98. package/Zod.ts +0 -1
  99. package/dist/McpSdk.d.ts +0 -14
  100. package/dist/SimulatorMask-BHVXyogh-BFEGpD5S.js +0 -1048
  101. package/dist/SimulatorMask-BHVXyogh-CCYbrb84.js +0 -801
  102. package/dist/WebAgent.d.ts +0 -5
  103. package/dist/WebMcp.d.ts +0 -23
  104. package/dist/Zod.d.ts +0 -1
  105. package/dist/index.es.dev.js +0 -59017
  106. package/dist/index.es.js +0 -46795
  107. package/dist/index.umd.dev.js +0 -60355
  108. package/dist/index.umd.js +0 -1248
  109. package/dist/mcpsdk@1.25.3.dev.js +0 -22780
  110. package/dist/mcpsdk@1.25.3.es.dev.js +0 -22778
  111. package/dist/mcpsdk@1.25.3.es.js +0 -16960
  112. package/dist/mcpsdk@1.25.3.js +0 -48
  113. package/dist/transport/ExtensionClientTransport.d.ts +0 -24
  114. package/dist/transport/ExtensionContentServerTransport.d.ts +0 -39
  115. package/dist/transport/ExtensionPageServerTransport.d.ts +0 -36
  116. package/dist/transport/messages.d.ts +0 -9
  117. package/dist/vite.config.mcpSdk.d.ts +0 -2
  118. package/dist/vite.config.webAgent.d.ts +0 -2
  119. package/dist/vite.config.webMcp.d.ts +0 -2
  120. package/dist/vite.config.webMcpFull.d.ts +0 -2
  121. package/dist/vite.config.zod.d.ts +0 -2
  122. package/dist/webagent.dev.js +0 -49360
  123. package/dist/webagent.es.dev.js +0 -49071
  124. package/dist/webagent.es.js +0 -39219
  125. package/dist/webagent.js +0 -642
  126. package/dist/webmcp-full.dev.js +0 -31336
  127. package/dist/webmcp-full.es.dev.js +0 -30283
  128. package/dist/webmcp-full.es.js +0 -22889
  129. package/dist/webmcp-full.js +0 -645
  130. package/dist/webmcp.dev.js +0 -9572
  131. package/dist/webmcp.es.dev.js +0 -8518
  132. package/dist/webmcp.es.js +0 -6727
  133. package/dist/webmcp.js +0 -602
  134. package/dist/zod@3.25.76.dev.js +0 -4037
  135. package/dist/zod@3.25.76.es.dev.js +0 -4033
  136. package/dist/zod@3.25.76.es.js +0 -2945
  137. package/dist/zod@3.25.76.js +0 -1
  138. package/transport/ExtensionClientTransport.ts +0 -100
  139. package/transport/ExtensionContentServerTransport.ts +0 -162
  140. package/transport/ExtensionPageServerTransport.ts +0 -149
  141. package/transport/messages.ts +0 -63
  142. package/vite-build-tsc.ts +0 -63
  143. package/vite-env.d.ts +0 -10
  144. package/vite.config.mcpSdk.ts +0 -28
  145. package/vite.config.webAgent.ts +0 -19
  146. package/vite.config.webMcp.ts +0 -40
  147. package/vite.config.webMcpFull.ts +0 -19
  148. package/vite.config.zod.ts +0 -23
  149. /package/dist/{vite-build-tsc.d.ts → vite.config.runtime.d.ts} +0 -0
@@ -0,0 +1,245 @@
1
+ import { type RefMap } from '../a11y-tree'
2
+
3
+ export const HIGHLIGHT_CONTAINER_ID = 'webmcpcli-highlight-container'
4
+ export const HIGHLIGHT_CONTAINER_STYLE_ID = 'webmcpcli-highlight-container-style'
5
+ const colors = [
6
+ '#FF0000',
7
+ '#00FF00',
8
+ '#0000FF',
9
+ '#FFA500',
10
+ '#800080',
11
+ '#008080',
12
+ '#FF69B4',
13
+ '#4B0082',
14
+ '#FF4500',
15
+ '#2E8B57',
16
+ '#DC143C',
17
+ '#4682B4'
18
+ ]
19
+ // border + label opacity
20
+ const opacity = Math.floor(0.3 * 255)
21
+ .toString(16)
22
+ .padStart(2, '0')
23
+
24
+ const injectStyles = `
25
+ #${HIGHLIGHT_CONTAINER_ID} {
26
+ position: fixed;
27
+ pointer-events: none;
28
+ top: 0;
29
+ left: 0;
30
+ width: 100%;
31
+ height: 100%;
32
+ z-index: 2147483640;
33
+ background-color: transparent;
34
+ }
35
+
36
+ #${HIGHLIGHT_CONTAINER_ID} div {
37
+ position: fixed;
38
+ pointer-events: none;
39
+ background-color: transparent;
40
+ text-align: right;
41
+ box-sizing: border-box;
42
+ border-radius: 4px;
43
+ }
44
+
45
+ #${HIGHLIGHT_CONTAINER_ID} div span {
46
+ display: inline-block;
47
+ color: #fff;
48
+ padding: 0 2px;
49
+ border-radius: 4px;
50
+ }
51
+ `
52
+
53
+ /** 图标热区回退时父节点宽高上限,避免用到整行容器 */
54
+ const MAX_ICON_FALLBACK_SIZE = 48
55
+
56
+ export type HighlightRect = { top: number; left: number; width: number; height: number }
57
+
58
+ /**
59
+ * 解析高亮矩形。
60
+ * Tiny3 `ti-icon` / `tp-icon` 字形常画在 ::before 上,宿主 getBoundingClientRect 宽或高为 0,
61
+ * 旧逻辑直接跳过导致帮助中心关闭/全屏等图标有 ref 却不高亮。
62
+ */
63
+ export function resolveHighlightRect(el: HTMLElement): HighlightRect | null {
64
+ const rect = el.getBoundingClientRect()
65
+ if (rect.width > 0 && rect.height > 0) {
66
+ return { top: rect.top, left: rect.left, width: rect.width, height: rect.height }
67
+ }
68
+
69
+ // 优先用紧凑父节点(帮助中心 header 图标外包 28×28 的可点区域)
70
+ let parent: HTMLElement | null = el.parentElement
71
+ for (let depth = 0; depth < 3 && parent; depth++) {
72
+ const pr = parent.getBoundingClientRect()
73
+ if (
74
+ pr.width > 0 &&
75
+ pr.height > 0 &&
76
+ pr.width <= MAX_ICON_FALLBACK_SIZE &&
77
+ pr.height <= MAX_ICON_FALLBACK_SIZE
78
+ ) {
79
+ return { top: pr.top, left: pr.left, width: pr.width, height: pr.height }
80
+ }
81
+ parent = parent.parentElement
82
+ }
83
+
84
+ // 单边有尺寸时,用 font-size 补齐另一边
85
+ if (rect.width > 0 || rect.height > 0) {
86
+ try {
87
+ const fs = parseFloat(getComputedStyle(el).fontSize) || 16
88
+ const width = rect.width > 0 ? rect.width : fs
89
+ const height = rect.height > 0 ? rect.height : fs
90
+ return {
91
+ top: rect.height > 0 ? rect.top : rect.top - (height - rect.height) / 2,
92
+ left: rect.width > 0 ? rect.left : rect.left - (width - rect.width) / 2,
93
+ width,
94
+ height,
95
+ }
96
+ } catch {
97
+ /* ignore */
98
+ }
99
+ }
100
+
101
+ return null
102
+ }
103
+
104
+ /** 与视口有交集即可(侧栏轻微负 left、贴边控件不应被整框剔除) */
105
+ function isRectInViewport(rect: HighlightRect): boolean {
106
+ const vh = window.innerHeight || document.documentElement.clientHeight
107
+ const vw = window.innerWidth || document.documentElement.clientWidth
108
+ return rect.top < vh && rect.top + rect.height > 0 && rect.left < vw && rect.left + rect.width > 0
109
+ }
110
+
111
+ const doHighlight = (refMap: RefMap, parentIframe: HTMLIFrameElement | null = null) => {
112
+ // 1、保证注入脚本
113
+ if (!document.head.querySelector(`#${HIGHLIGHT_CONTAINER_STYLE_ID}`)) {
114
+ const style = document.head.appendChild(document.createElement('style'))
115
+ style.id = HIGHLIGHT_CONTAINER_STYLE_ID
116
+ style.textContent = injectStyles
117
+ document.head.appendChild(style)
118
+ }
119
+
120
+ // 2、保证contrainer( container 在 a11y 时已加入黑名单)
121
+ let container = document.getElementById(HIGHLIGHT_CONTAINER_ID)
122
+ if (!container) {
123
+ container = document.createElement('div')
124
+ container.id = HIGHLIGHT_CONTAINER_ID
125
+
126
+ document.body.appendChild(container)
127
+ } else {
128
+ container.innerHTML = ''
129
+ }
130
+
131
+ let iframeOffset = { x: 0, y: 0 }
132
+ if (parentIframe) {
133
+ const iframeRect = parentIframe.getBoundingClientRect() // Keep getBoundingClientRect for iframe offset
134
+ iframeOffset.x = iframeRect.left
135
+ iframeOffset.y = iframeRect.top
136
+ }
137
+ const fragment = document.createDocumentFragment()
138
+
139
+ // 3、 遍历 refMap
140
+ for (const [index, el] of refMap.entries()) {
141
+ const rect = resolveHighlightRect(el)
142
+ if (!rect) continue
143
+
144
+ const top = rect.top + iframeOffset.y
145
+ const left = rect.left + iframeOffset.x
146
+ const viewportRect = { top, left, width: rect.width, height: rect.height }
147
+ if (!isRectInViewport(viewportRect)) continue
148
+
149
+ const color = colors[index % colors.length]
150
+ const textColor = color + opacity
151
+
152
+ const overlay = document.createElement('div')
153
+ overlay.style.border = `2px solid ${textColor}`
154
+ overlay.dataset.refIndex = String(index)
155
+
156
+ overlay.style.top = `${top}px`
157
+ overlay.style.left = `${left}px`
158
+ overlay.style.width = `${rect.width}px`
159
+ overlay.style.height = `${rect.height}px`
160
+
161
+ // 文字
162
+ const text = document.createElement('span')
163
+ text.style.backgroundColor = textColor
164
+ text.style.height = `${Math.max(rect.height - 4, 0)}px`
165
+ text.style.lineHeight = `${Math.max(rect.height - 4, 0)}px`
166
+ text.textContent = index.toString()
167
+ overlay.appendChild(text)
168
+
169
+ fragment.appendChild(overlay)
170
+ }
171
+
172
+ container.appendChild(fragment)
173
+ }
174
+
175
+ export const highlight = (refMap: RefMap, parentIframe: HTMLIFrameElement | null = null) => {
176
+ doHighlight(refMap, parentIframe)
177
+
178
+ // 4、监听滚动和resize事件,更新位置
179
+ const updatePositions = () => doHighlight(refMap, parentIframe)
180
+ const throttleFunction = (func: (...args: any[]) => void, delay: number) => {
181
+ let lastCall = 0
182
+ return (...args: any[]) => {
183
+ const now = performance.now()
184
+ if (now - lastCall < delay) return
185
+ lastCall = now
186
+ return func(...args)
187
+ }
188
+ }
189
+
190
+ const throttledUpdatePositions = throttleFunction(updatePositions, 16)
191
+ window.addEventListener('scroll', throttledUpdatePositions, true)
192
+ window.addEventListener('resize', throttledUpdatePositions)
193
+
194
+ const cleanupFn = () => {
195
+ window.removeEventListener('scroll', throttledUpdatePositions, true)
196
+ window.removeEventListener('resize', throttledUpdatePositions)
197
+ }
198
+ const win = window as any
199
+ ;(win._highlightCleanupFunctions = win._highlightCleanupFunctions || []).push(cleanupFn)
200
+ }
201
+
202
+ /** 移除高亮, 可反复调用 */
203
+ export const unhighlight = () => {
204
+ document.getElementById(HIGHLIGHT_CONTAINER_ID)?.remove()
205
+
206
+ const cleanupFunctions = (window as any)._highlightCleanupFunctions || []
207
+ for (const cleanup of cleanupFunctions) {
208
+ if (typeof cleanup === 'function') {
209
+ cleanup()
210
+ }
211
+ }
212
+
213
+ ;(window as any)._highlightCleanupFunctions = []
214
+ }
215
+
216
+ /** 全局监听变化,随时准备移除高亮, 可反复调用 */
217
+ export const globalRemoveListener = () => {
218
+ if ((window as any).__registerGlobalRemoveListener) return
219
+ ;(window as any).__registerGlobalRemoveListener = true
220
+ window.addEventListener('popstate', () => {
221
+ unhighlight()
222
+ })
223
+ window.addEventListener('hashchange', () => {
224
+ unhighlight()
225
+ })
226
+ window.addEventListener('beforeunload', () => {
227
+ unhighlight()
228
+ })
229
+
230
+ const navigation = (window as any).navigation
231
+ if (navigation && typeof navigation.addEventListener === 'function') {
232
+ navigation.addEventListener('navigate', () => {
233
+ unhighlight()
234
+ })
235
+ } else {
236
+ // 定时器
237
+ let currentUrl = window.location.href
238
+ setInterval(() => {
239
+ if (window.location.href !== currentUrl) {
240
+ currentUrl = window.location.href
241
+ unhighlight()
242
+ }
243
+ }, 500)
244
+ }
245
+ }
@@ -0,0 +1,14 @@
1
+ .webmcp-page-agent-wrapper {
2
+ position: fixed;
3
+ inset: 0;
4
+ z-index: 2147483641;
5
+ /* 确保在所有元素之上,除了 panel */
6
+ cursor: wait;
7
+ overflow: hidden;
8
+
9
+ display: none;
10
+ }
11
+
12
+ .webmcp-page-agent-wrapper.visible {
13
+ display: block;
14
+ }
@@ -0,0 +1,299 @@
1
+ import { Motion } from 'ai-motion'
2
+
3
+ import { isPageDark } from './checkDarkMode'
4
+
5
+ const injectStyles = `
6
+ .webmcp-page-agent-cursor {
7
+ position: absolute;
8
+ width: var(--cursor-size, 75px);
9
+ height: var(--cursor-size, 75px);
10
+ pointer-events: none;
11
+ z-index: 10000;
12
+ }
13
+
14
+ .webmcp-page-agent-cursorFilling {
15
+ position: absolute;
16
+ width: 100%;
17
+ height: 100%;
18
+ background: url("data:image/svg+xml,%3Csvg%20viewBox%3D%270%200%2079.0834%2079.4553%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20%20width%3D%2779.083374%27%20height%3D%2779.455322%27%20fill%3D%27none%27%20%3E--%3Cdefs%3E%3Cfilter%20id%3D%27filter_0%27%20width%3D%2779.083374%27%20height%3D%2779.455322%27%20x%3D%270.000000%27%20y%3D%270.000000%27%20filterUnits%3D%27userSpaceOnUse%27%20%20color-interpolation-filters%3D%27sRGB%27%3E%3CfeFlood%20flood-opacity%3D%270%27%20result%3D%27BackgroundImageFix%27%20%2F%3E%3CfeOffset%20dx%3D%276.000000%27%20dy%3D%276.000000%27%20in%3D%27SourceAlpha%27%20%2F%3E%3CfeGaussianBlur%20stdDeviation%3D%276.66666651%27%20%2F%3E%3CfeColorMatrix%20type%3D%27matrix%27%20values%3D%270%200%200%200%200%200%200%200%200%200%200%200%200%200%200%200%200%200%200.18%200%20%27%20%2F%3E%3CfeBlend%20result%3D%27effect_dropShadow_1%27%20in2%3D%27BackgroundImageFix%27%20mode%3D%27normal%27%20%2F%3E%3CfeBlend%20result%3D%27shape%27%20in%3D%27SourceGraphic%27%20in2%3D%27effect_dropShadow_1%27%20mode%3D%27normal%27%20%2F%3E%3C%2Ffilter%3E%3ClinearGradient%20id%3D%27paint_linear_0%27%20x1%3D%2718.4743214%27%20x2%3D%2718.4213886%27%20y1%3D%27-1.86264515e-09%27%20y2%3D%2734.9221306%27%20gradientUnits%3D%27userSpaceOnUse%27%3E%3Cstop%20stop-color%3D%27rgb%28255%2C181%2C96%29%27%20offset%3D%270%27%20stop-opacity%3D%271%27%20%2F%3E%3Cstop%20stop-color%3D%27rgb%28255%2C120%2C114%29%27%20offset%3D%270.5%27%20stop-opacity%3D%271%27%20%2F%3E%3Cstop%20stop-color%3D%27rgb%28241%2C109%2C237%29%27%20offset%3D%271%27%20stop-opacity%3D%271%27%20%2F%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Cg%20filter%3D%27url%28%23filter_0%29%27%3E%3Cpath%20d%3D%27M20.2632%203.57771L34.6257%2032.3027C35.4444%2033.9402%2033.8152%2035.7258%2032.1097%2035.0603L19.2014%2030.0229C18.7339%2029.8404%2018.2148%2029.8404%2017.7472%2030.0229L4.83891%2035.0603C3.13343%2035.7258%201.50423%2033.9402%202.32297%2032.3027L16.6855%203.57771C17.4225%202.10361%2019.5261%202.10361%2020.2632%203.57771Z%27%20fill%3D%27url%28%23paint_linear_0%29%27%20fill-rule%3D%27evenodd%27%20transform%3D%27matrix%280.719908%2C-0.69407%2C0.69407%2C0.719908%2C2.00256%2C28.0095%29%27%20%2F%3E%3Cpath%20d%3D%27M37.2169%2032.6848C37.1578%2032.189%2037.0096%2031.7039%2036.7723%2031.2294L22.4098%202.5044C22.1973%202.07946%2021.9328%201.70554%2021.6163%201.38261C21.3524%201.11342%2021.0524%200.879671%2020.7162%200.681366C20.3925%200.490407%2020.0558%200.34434%2019.7061%200.243163C19.312%200.129145%2018.9014%200.0721366%2018.4743%200.0721366C18.0472%200.0721366%2017.6367%200.129145%2017.2426%200.243163C16.8929%200.34434%2016.5562%200.490407%2016.2324%200.681366C15.8962%200.879671%2015.5962%201.11342%2015.3323%201.38261C15.0158%201.70553%2014.7513%202.07946%2014.5388%202.5044L0.176342%2031.2294C-0.0609121%2031.7039%20-0.209128%2032.189%20-0.268305%2032.6848C-0.316251%2033.0865%20-0.305747%2033.4951%20-0.236792%2033.9108C-0.171059%2034.307%20-0.0570567%2034.6809%200.105216%2035.0326C0.292784%2035.4391%200.544845%2035.8158%200.861397%2036.1627C1.17794%2036.5097%201.53001%2036.7951%201.91761%2037.0191C2.25297%2037.2128%202.61492%2037.3606%203.00348%2037.4623C3.41106%2037.5689%203.81704%2037.6168%204.22142%2037.6058C4.72053%2037.5922%205.21719%2037.4889%205.71141%2037.2961L18.4743%2032.3154L31.2372%2037.2961C31.7315%2037.4889%2032.2281%2037.5922%2032.7272%2037.6058C33.1316%2037.6168%2033.5376%2037.5689%2033.9452%2037.4623C34.3337%2037.3606%2034.6957%2037.2128%2035.0311%2037.0191C35.4187%2036.7951%2035.7707%2036.5097%2036.0873%2036.1627C36.4038%2035.8158%2036.6558%2035.4391%2036.8434%2035.0327C37.0057%2034.681%2037.1197%2034.307%2037.1854%2033.9108C37.2544%2033.4951%2037.2649%2033.0865%2037.2169%2032.6848ZM34.6257%2032.3027L20.2632%203.57771C19.5261%202.10361%2017.4225%202.10361%2016.6855%203.57771L2.32297%2032.3027C1.50423%2033.9402%203.13343%2035.7258%204.83891%2035.0603L17.7472%2030.0229C18.2148%2029.8404%2018.7339%2029.8404%2019.2014%2030.0229L32.1097%2035.0603C33.8152%2035.7258%2035.4444%2033.9402%2034.6257%2032.3027Z%27%20fill%3D%27rgb%28255%2C255%2C255%29%27%20fill-rule%3D%27evenodd%27%20transform%3D%27matrix%280.719908%2C-0.69407%2C0.69407%2C0.719908%2C2.00256%2C28.0095%29%27%20%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
19
+ background-size: 100% 100%;
20
+ background-repeat: no-repeat;
21
+
22
+ transform-origin: center;
23
+ margin-left: -10px;
24
+ margin-top: -18px;
25
+ }
26
+
27
+ .webmcp-page-agent-cursorRipple {
28
+ position: absolute;
29
+ width: 100%;
30
+ height: 100%;
31
+ pointer-events: none;
32
+ margin-left: -50%;
33
+ margin-top: -50%;
34
+
35
+ &::after {
36
+ content: '';
37
+ opacity: 0;
38
+ position: absolute;
39
+ inset: 0;
40
+ border: 4px solid rgba(57, 182, 255, 1);
41
+ border-radius: 50%;
42
+ }
43
+ }
44
+
45
+ .webmcp-page-agent-cursor.clicking .webmcp-page-agent-cursorRipple::after {
46
+ animation: webmcp-page-agent-cursor-ripple 300ms ease-out forwards;
47
+ }
48
+
49
+ @keyframes webmcp-page-agent-cursor-ripple {
50
+ 0% {
51
+ transform: scale(0);
52
+ opacity: 1;
53
+ }
54
+
55
+ 100% {
56
+ transform: scale(2);
57
+ opacity: 0;
58
+ }
59
+ }
60
+
61
+ .webmcp-page-agent-wrapper {
62
+ position: fixed;
63
+ inset: 0;
64
+ z-index: 2147483641;
65
+ /* 确保在所有元素之上,除了 panel */
66
+ cursor: wait;
67
+ overflow: hidden;
68
+
69
+ display: none;
70
+ }
71
+
72
+ .webmcp-page-agent-wrapper.visible {
73
+ display: block;
74
+ }
75
+ `
76
+
77
+ export class SimulatorMask extends EventTarget {
78
+ shown: boolean = false
79
+ wrapper = document.createElement('div')
80
+ motion: Motion | null = null
81
+
82
+ #disposed = false
83
+
84
+ #cursor = document.createElement('div')
85
+
86
+ #currentCursorX = 0
87
+ #currentCursorY = 0
88
+
89
+ #targetCursorX = 0
90
+ #targetCursorY = 0
91
+
92
+ constructor() {
93
+ super()
94
+ // 注入样式,防止打包失败
95
+ document.head.appendChild(document.createElement('style')).textContent = injectStyles
96
+ this.wrapper.id = 'page-agent-runtime_simulator-mask'
97
+ this.wrapper.className = 'webmcp-page-agent-wrapper'
98
+ this.wrapper.setAttribute('data-browser-use-ignore', 'true')
99
+ this.wrapper.setAttribute('data-page-agent-ignore', 'true')
100
+
101
+ try {
102
+ const motion = new Motion({
103
+ mode: isPageDark() ? 'dark' : 'light',
104
+ styles: { position: 'absolute', inset: '0' },
105
+ glowWidth: 80,
106
+ borderWidth: 8,
107
+ colors: ['rgb(121, 216, 247)', 'rgb(171, 219, 110)', 'rgb(252, 188, 114)', 'rgb(235, 117, 231)']
108
+ })
109
+ this.motion = motion
110
+ this.wrapper.appendChild(motion.element)
111
+ motion.autoResize(this.wrapper)
112
+ } catch (e) {
113
+ console.warn('[SimulatorMask] Motion overlay unavailable:', e)
114
+ }
115
+
116
+ const stopEvent = (e: Event) => {
117
+ e.stopPropagation()
118
+ e.preventDefault()
119
+ }
120
+ ;['click', 'mousedown', 'mouseup', 'mousemove', 'wheel', 'keydown', 'keyup'].forEach((eventName) =>
121
+ this.wrapper.addEventListener(eventName, stopEvent)
122
+ )
123
+ // Create AI cursor
124
+ this.#createCursor()
125
+ // this.show()
126
+
127
+ document.body.appendChild(this.wrapper)
128
+
129
+ this.#moveCursorToTarget()
130
+
131
+ // global events
132
+ // @note Mask should be isolated from the rest of the code.
133
+ // Global events are easier to manage and cleanup.
134
+
135
+ const movePointerToListener = (event: Event) => {
136
+ const { x, y } = (event as CustomEvent).detail
137
+ this.setCursorPosition(x, y)
138
+ }
139
+ const clickPointerListener = () => {
140
+ this.triggerClickAnimation()
141
+ }
142
+ const enablePassThroughListener = () => {
143
+ this.wrapper.style.pointerEvents = 'none'
144
+ }
145
+ const disablePassThroughListener = () => {
146
+ this.wrapper.style.pointerEvents = 'auto'
147
+ }
148
+
149
+ window.addEventListener('PageAgent::MovePointerTo', movePointerToListener)
150
+ window.addEventListener('PageAgent::ClickPointer', clickPointerListener)
151
+ window.addEventListener('PageAgent::EnablePassThrough', enablePassThroughListener)
152
+ window.addEventListener('PageAgent::DisablePassThrough', disablePassThroughListener)
153
+
154
+ this.addEventListener('dispose', () => {
155
+ window.removeEventListener('PageAgent::MovePointerTo', movePointerToListener)
156
+ window.removeEventListener('PageAgent::ClickPointer', clickPointerListener)
157
+ window.removeEventListener('PageAgent::EnablePassThrough', enablePassThroughListener)
158
+ window.removeEventListener('PageAgent::DisablePassThrough', disablePassThroughListener)
159
+ })
160
+ }
161
+
162
+ #createCursor() {
163
+ this.#cursor.className = 'webmcp-page-agent-cursor'
164
+
165
+ // Create ripple effect container
166
+ const rippleContainer = document.createElement('div')
167
+ rippleContainer.className = 'webmcp-page-agent-cursorRipple'
168
+ this.#cursor.appendChild(rippleContainer)
169
+
170
+ // Create filling layer
171
+ const fillingLayer = document.createElement('div')
172
+ fillingLayer.className = 'webmcp-page-agent-cursorFilling'
173
+ this.#cursor.appendChild(fillingLayer)
174
+
175
+ // Create border layer
176
+ // const borderLayer = document.createElement('div')
177
+ // borderLayer.className = 'webmcp-page-agent-cursorBorder'
178
+ // this.#cursor.appendChild(borderLayer)
179
+
180
+ this.wrapper.appendChild(this.#cursor)
181
+ }
182
+
183
+ #moveCursorToTarget() {
184
+ if (this.#disposed) return
185
+
186
+ const newX = this.#currentCursorX + (this.#targetCursorX - this.#currentCursorX) * 0.2
187
+ const newY = this.#currentCursorY + (this.#targetCursorY - this.#currentCursorY) * 0.2
188
+
189
+ const xDistance = Math.abs(newX - this.#targetCursorX)
190
+ if (xDistance > 0) {
191
+ if (xDistance < 2) {
192
+ this.#currentCursorX = this.#targetCursorX
193
+ } else {
194
+ this.#currentCursorX = newX
195
+ }
196
+ this.#cursor.style.left = `${this.#currentCursorX}px`
197
+ }
198
+
199
+ const yDistance = Math.abs(newY - this.#targetCursorY)
200
+ if (yDistance > 0) {
201
+ if (yDistance < 2) {
202
+ this.#currentCursorY = this.#targetCursorY
203
+ } else {
204
+ this.#currentCursorY = newY
205
+ }
206
+ this.#cursor.style.top = `${this.#currentCursorY}px`
207
+ }
208
+
209
+ requestAnimationFrame(() => this.#moveCursorToTarget())
210
+ }
211
+
212
+ setCursorPosition(x: number, y: number) {
213
+ if (this.#disposed) return
214
+
215
+ this.#targetCursorX = x
216
+ this.#targetCursorY = y
217
+ }
218
+
219
+ triggerClickAnimation() {
220
+ if (this.#disposed) return
221
+
222
+ this.#cursor.classList.remove('clicking')
223
+ // Force reflow to restart animation
224
+ void this.#cursor.offsetHeight
225
+ this.#cursor.classList.add('clicking')
226
+ }
227
+
228
+ show() {
229
+ if (this.shown || this.#disposed) return
230
+
231
+ this.shown = true
232
+ this.motion?.start()
233
+ this.motion?.fadeIn()
234
+
235
+ this.wrapper.classList.add('visible')
236
+
237
+ // Initialize cursor position
238
+ this.#currentCursorX = window.innerWidth / 2
239
+ this.#currentCursorY = window.innerHeight / 2
240
+ this.#targetCursorX = this.#currentCursorX
241
+ this.#targetCursorY = this.#currentCursorY
242
+ this.#cursor.style.left = `${this.#currentCursorX}px`
243
+ this.#cursor.style.top = `${this.#currentCursorY}px`
244
+ }
245
+
246
+ hide() {
247
+ if (!this.shown || this.#disposed) return
248
+
249
+ this.shown = false
250
+ this.motion?.fadeOut()
251
+ this.motion?.pause()
252
+
253
+ this.#cursor.classList.remove('clicking')
254
+
255
+ setTimeout(() => {
256
+ this.wrapper.classList.remove('visible')
257
+ }, 800) // Match the animation duration
258
+ }
259
+
260
+ dispose() {
261
+ this.#disposed = true
262
+ this.motion?.dispose()
263
+ this.wrapper.remove()
264
+ this.dispatchEvent(new Event('dispose'))
265
+ }
266
+ // 根据目标元素大小,在 wrapper 添加绝对定位边框:2px 宽、与目标间距 2px,对角渐变 (0,0 → 1,1)
267
+ borderElement(targetElement: HTMLElement) {
268
+ if (!targetElement) return
269
+
270
+ this.wrapper.querySelector('.webmcp-page-agent-border')?.remove()
271
+
272
+ const rect = targetElement.getBoundingClientRect()
273
+ const gap = 2
274
+ const borderWidth = 2
275
+ const offset = gap + borderWidth
276
+
277
+ const el = document.createElement('div')
278
+ el.className = 'webmcp-page-agent-border'
279
+ el.style.position = 'absolute'
280
+ el.style.left = `${rect.left - offset}px`
281
+ el.style.top = `${rect.top - offset}px`
282
+ el.style.width = `${rect.width + offset * 2}px`
283
+ el.style.height = `${rect.height + offset * 2}px`
284
+ el.style.boxSizing = 'border-box'
285
+ el.style.pointerEvents = 'none'
286
+ el.style.zIndex = '10000'
287
+ el.style.border = `${borderWidth}px solid transparent`
288
+ // 对角渐变:左上 (0,0) → 右下 (1,1)
289
+ el.style.borderImageSource = 'linear-gradient(to bottom right, #79D8F7, #ABDB6E, #FCBC72, #FA8682, #EB75E7)'
290
+ el.style.borderImageSlice = '1'
291
+ el.style.borderImageWidth = `${borderWidth}px`
292
+
293
+ this.wrapper.appendChild(el)
294
+ }
295
+
296
+ removeBorderElement() {
297
+ this.wrapper.querySelector('.webmcp-page-agent-border')?.remove()
298
+ }
299
+ }