@opentiny/next-sdk 0.4.1 → 0.4.3
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/{SimulatorMask-BHVXyogh-iNUb4dyF.js → SimulatorMask-BHVXyogh-CKmEDsYG.js} +1 -1
- package/dist/core.js +1 -1
- package/dist/{index-CXK8CcKq.js → index-CHMa2zG4.js} +1985 -1434
- package/dist/index.d.ts +11 -0
- package/dist/index.js +69 -50
- package/dist/{initialize-builtin-WebMCP-rALfqS-o.js → initialize-builtin-WebMCP-JaoKwVlm.js} +355 -346
- package/dist/page-tools/a11y/build.d.ts +3 -4
- package/dist/page-tools/a11y/config.d.ts +99 -0
- package/dist/page-tools/a11y/constants.d.ts +22 -0
- package/dist/page-tools/a11y/search.d.ts +1 -1
- package/dist/page-tools/a11y/types.d.ts +13 -22
- package/dist/page-tools/a11y/utils.d.ts +46 -11
- package/dist/page-tools/a11y/vnode.d.ts +14 -11
- package/dist/page-tools/a11y-tree.d.ts +1 -0
- package/dist/page-tools/configs/console-cloud.d.ts +6 -0
- package/dist/page-tools/constants.d.ts +7 -17
- package/dist/page-tools/context.d.ts +14 -0
- package/dist/page-tools/handlers/hover.d.ts +9 -0
- package/dist/page-tools/handlers/searchTree.d.ts +2 -1
- package/dist/page-tools/page-agent-highlight/index.d.ts +12 -0
- package/dist/page-tools/page-agent-mask/SimulatorMask.d.ts +2 -0
- package/dist/page-tools/page-agent-tool-event.d.ts +26 -0
- package/dist/page-tools/schema.d.ts +4 -3
- package/dist/page-tools/tool-config.d.ts +50 -0
- package/dist/page-tools/utils/dom.d.ts +8 -2
- package/dist/runtime.d.ts +4 -1
- package/dist/runtime.js +22 -187
- package/dist/vitest.config.d.ts +2 -0
- package/package.json +26 -16
- package/WebMcpClient.ts +0 -505
- package/WebMcpServer.ts +0 -447
- package/agent/AgentModelProvider.ts +0 -932
- package/agent/type.ts +0 -91
- package/agent/utils/generateReActPrompt.ts +0 -71
- package/agent/utils/getAISDKTools.ts +0 -34
- package/agent/utils/getBuiltinMcpTools.ts +0 -86
- package/agent/utils/parseReActAction.ts +0 -34
- package/core.ts +0 -26
- package/index.ts +0 -56
- package/page-tools/a11y/build.ts +0 -70
- package/page-tools/a11y/constants.ts +0 -110
- package/page-tools/a11y/search.ts +0 -129
- package/page-tools/a11y/types.ts +0 -108
- package/page-tools/a11y/utils.ts +0 -266
- package/page-tools/a11y/vnode.ts +0 -229
- package/page-tools/a11y-tree.ts +0 -13
- package/page-tools/bridge.ts +0 -702
- package/page-tools/constants.ts +0 -72
- package/page-tools/context.ts +0 -13
- package/page-tools/effects.ts +0 -345
- package/page-tools/handlers/browserState.ts +0 -12
- package/page-tools/handlers/click.ts +0 -26
- package/page-tools/handlers/executeJavascript.ts +0 -22
- package/page-tools/handlers/fill.ts +0 -60
- package/page-tools/handlers/scroll.ts +0 -66
- package/page-tools/handlers/searchTree.ts +0 -30
- package/page-tools/handlers/select.ts +0 -34
- package/page-tools/initialize-builtin-WebMCP.ts +0 -20
- package/page-tools/page-agent-highlight/index.ts +0 -198
- package/page-tools/page-agent-mask/SimulatorMask.module.css +0 -14
- package/page-tools/page-agent-mask/SimulatorMask.ts +0 -279
- package/page-tools/page-agent-mask/checkDarkMode.ts +0 -181
- package/page-tools/page-agent-mask/cursor-border.svg +0 -3
- package/page-tools/page-agent-mask/cursor-fill.svg +0 -5
- package/page-tools/page-agent-mask/cursor.module.css +0 -70
- package/page-tools/page-agent-prompt.md +0 -139
- package/page-tools/page-agent-tool.ts +0 -175
- package/page-tools/page-state-cache.ts +0 -78
- package/page-tools/schema.ts +0 -51
- package/page-tools/utils/dom.ts +0 -155
- package/page-tools/utils/scroll.ts +0 -58
- package/remoter/QrCode.ts +0 -54
- package/remoter/createRemoter.ts +0 -957
- package/remoter/svgs/chat.svg +0 -43
- package/remoter/svgs/icon-copy.svg +0 -25
- package/remoter/svgs/link.svg +0 -28
- package/remoter/svgs/logo.svg +0 -45
- package/remoter/svgs/qrcode.svg +0 -35
- package/remoter/svgs/scan.svg +0 -21
- package/remoter/tooltips.ts +0 -260
- package/runtime.html +0 -39
- package/runtime.ts +0 -13
- package/script/utils.ts +0 -26
- package/skills/index.ts +0 -287
- package/tsconfig.json +0 -16
- package/utils/builtinProxy.ts +0 -144
- package/utils/env.ts +0 -13
- package/utils/uuid.ts +0 -10
- package/vite.config.runtime.ts +0 -22
- package/vite.config.ts +0 -63
package/page-tools/constants.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/** 校验错误默认选择器:ARIA 标准 + 主流 UI 框架 */
|
|
2
|
-
export const DEFAULT_ERROR_SELECTORS: string[] = [
|
|
3
|
-
// W3C ARIA 标准(最可靠,框架无关)
|
|
4
|
-
'[role="alert"]',
|
|
5
|
-
'[aria-invalid="true"]',
|
|
6
|
-
// Tiny3 / Lego(华为云)
|
|
7
|
-
'.ti3-unifyvalid-error',
|
|
8
|
-
'.ti3-error',
|
|
9
|
-
'.ti-error',
|
|
10
|
-
'.lego-text-error',
|
|
11
|
-
'.lego-error',
|
|
12
|
-
// Element UI / Element Plus
|
|
13
|
-
'.el-form-item__error',
|
|
14
|
-
// Ant Design
|
|
15
|
-
'.ant-form-item-explain-error',
|
|
16
|
-
// Bootstrap
|
|
17
|
-
'.is-invalid',
|
|
18
|
-
'.invalid-feedback',
|
|
19
|
-
// Angular
|
|
20
|
-
'.ng-invalid',
|
|
21
|
-
// 通用命名约定
|
|
22
|
-
'.error-msg',
|
|
23
|
-
'.error-message',
|
|
24
|
-
'.error-text',
|
|
25
|
-
'.field-error',
|
|
26
|
-
'.form-error',
|
|
27
|
-
'.is-error',
|
|
28
|
-
'.has-error',
|
|
29
|
-
'.validate-error',
|
|
30
|
-
'.valid-error'
|
|
31
|
-
]
|
|
32
|
-
|
|
33
|
-
/** 模态弹窗默认选择器:ARIA 标准 + 主流 UI 框架 */
|
|
34
|
-
export const DEFAULT_DIALOG_SELECTORS: string[] = [
|
|
35
|
-
// W3C ARIA 标准
|
|
36
|
-
'[role="dialog"]',
|
|
37
|
-
'[role="alertdialog"]',
|
|
38
|
-
// Tiny3 / Lego(华为云)
|
|
39
|
-
'[class*="ti3-modal"]',
|
|
40
|
-
'[class*="ti3-message-box"]',
|
|
41
|
-
// Element UI / Element Plus
|
|
42
|
-
'[class*="el-dialog"]',
|
|
43
|
-
'[class*="el-message-box"]',
|
|
44
|
-
// Ant Design
|
|
45
|
-
'[class*="ant-modal"]',
|
|
46
|
-
// Bootstrap
|
|
47
|
-
'[class*="modal-content"]',
|
|
48
|
-
// Vuetify
|
|
49
|
-
'[class*="v-dialog"]',
|
|
50
|
-
// Naive UI
|
|
51
|
-
'[class*="n-modal"]'
|
|
52
|
-
]
|
|
53
|
-
|
|
54
|
-
export interface PageAgentToolOptions {
|
|
55
|
-
/** 允许在无障碍树节点中额外暴露的 DOM 属性白名单 */
|
|
56
|
-
exposedAttributes?: string[]
|
|
57
|
-
/** 是否启用元素高亮 */
|
|
58
|
-
enableHighlight?: boolean
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
declare global {
|
|
62
|
-
interface Window {
|
|
63
|
-
__webmcpcli_interactiveWhitelist?: Element[]
|
|
64
|
-
__webmcpcli_interactiveBlacklist?: Element[]
|
|
65
|
-
__webmcpcli_exposedAttributes?: string[]
|
|
66
|
-
__webmcpcli_beforeGetBrowserState?: (() => void) | null
|
|
67
|
-
/** 校验错误元素 CSS 选择器列表(覆盖默认,用于检测页面可见的校验错误) */
|
|
68
|
-
__webmcpcli_errorSelectors?: string[]
|
|
69
|
-
/** 模态弹窗元素 CSS 选择器列表(覆盖默认,用于检测阻塞交互的弹窗) */
|
|
70
|
-
__webmcpcli_dialogSelectors?: string[]
|
|
71
|
-
}
|
|
72
|
-
}
|
package/page-tools/context.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { PageController } from '@page-agent/page-controller'
|
|
2
|
-
import type { PageStateCache } from './page-state-cache'
|
|
3
|
-
import type { RefMap } from './a11y-tree'
|
|
4
|
-
|
|
5
|
-
export interface ActionContext {
|
|
6
|
-
pageController: PageController
|
|
7
|
-
stateCache: PageStateCache
|
|
8
|
-
getRefMap: () => RefMap
|
|
9
|
-
setRefMap: (map: RefMap) => void
|
|
10
|
-
buildBrowserStateResponse: (mode?: 'full' | 'diff' | 'both') => Promise<{ content: Array<{ type: 'text'; text: string }> }>
|
|
11
|
-
refreshOnStaleRef: (action: string, index: number) => Promise<{ content: Array<{ type: 'text'; text: string }> }>
|
|
12
|
-
errContent: (msg: string) => Promise<{ content: Array<{ type: 'text'; text: string }> }>
|
|
13
|
-
}
|
package/page-tools/effects.ts
DELETED
|
@@ -1,345 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* page-tools/effects - 页面工具调用提示效果模块(框架无关)
|
|
3
|
-
*
|
|
4
|
-
* 作用:
|
|
5
|
-
* - 在调用通过 withPageTools 注册的页面工具时,显示页面级的调用状态提示
|
|
6
|
-
* - 以左下角小 tip 形式展示“当前正在调用的工具”文案,尽量不打扰用户操作
|
|
7
|
-
* - 纯 DOM + CSS 实现,不依赖 Vue/React 等框架
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
export type ToolInvokeEffectConfig = {
|
|
11
|
-
/**
|
|
12
|
-
* 自定义提示文案,默认使用“工具标题 || 工具名称”
|
|
13
|
-
* 例如:"正在为你整理订单数据"
|
|
14
|
-
*/
|
|
15
|
-
label?: string
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
type RuntimeEffectConfig = {
|
|
19
|
-
label: string
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
let overlayElement: HTMLDivElement | null = null
|
|
23
|
-
let labelElement: HTMLDivElement | null = null
|
|
24
|
-
let styleElement: HTMLStyleElement | null = null
|
|
25
|
-
let activeCount = 0
|
|
26
|
-
|
|
27
|
-
import { isDomAvailable } from '../utils/env'
|
|
28
|
-
|
|
29
|
-
const BODY_GLOW_CLASS = 'next-sdk-tool-body-glow'
|
|
30
|
-
|
|
31
|
-
function ensureDomReady() {
|
|
32
|
-
return isDomAvailable()
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function ensureStyleElement() {
|
|
36
|
-
if (!ensureDomReady()) return
|
|
37
|
-
if (styleElement) return
|
|
38
|
-
|
|
39
|
-
const style = document.createElement('style')
|
|
40
|
-
style.textContent = `
|
|
41
|
-
.${BODY_GLOW_CLASS} {
|
|
42
|
-
position: relative;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
.next-sdk-tool-overlay {
|
|
46
|
-
position: fixed;
|
|
47
|
-
inset: 0;
|
|
48
|
-
z-index: 999999;
|
|
49
|
-
pointer-events: none;
|
|
50
|
-
display: flex;
|
|
51
|
-
align-items: flex-end;
|
|
52
|
-
justify-content: flex-start;
|
|
53
|
-
padding: 0 0 18px 18px;
|
|
54
|
-
background: transparent;
|
|
55
|
-
animation: next-sdk-overlay-fade-in 260ms ease-out;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
.next-sdk-tool-overlay--exit {
|
|
59
|
-
animation: next-sdk-overlay-fade-out 220ms ease-in forwards;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
.next-sdk-tool-overlay__glow-ring {
|
|
63
|
-
display: none;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
.next-sdk-tool-overlay__panel {
|
|
67
|
-
position: relative;
|
|
68
|
-
min-width: min(320px, 78vw);
|
|
69
|
-
max-width: min(420px, 82vw);
|
|
70
|
-
padding: 10px 14px;
|
|
71
|
-
border-radius: 999px;
|
|
72
|
-
background:
|
|
73
|
-
linear-gradient(135deg, rgba(15, 23, 42, 0.9), rgba(17, 24, 39, 0.9)),
|
|
74
|
-
radial-gradient(circle at top left, rgba(96, 165, 250, 0.25), transparent 55%),
|
|
75
|
-
radial-gradient(circle at bottom right, rgba(45, 212, 191, 0.22), transparent 60%);
|
|
76
|
-
box-shadow:
|
|
77
|
-
0 12px 28px rgba(15, 23, 42, 0.78),
|
|
78
|
-
0 0 0 1px rgba(148, 163, 184, 0.26);
|
|
79
|
-
display: flex;
|
|
80
|
-
align-items: center;
|
|
81
|
-
gap: 10px;
|
|
82
|
-
pointer-events: none;
|
|
83
|
-
transform-origin: center;
|
|
84
|
-
animation: next-sdk-panel-pop-in 260ms cubic-bezier(0.18, 0.89, 0.32, 1.28);
|
|
85
|
-
color: #e5e7eb;
|
|
86
|
-
font-family: system-ui, -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
.next-sdk-tool-overlay__indicator {
|
|
90
|
-
width: 26px;
|
|
91
|
-
height: 26px;
|
|
92
|
-
border-radius: 999px;
|
|
93
|
-
background: radial-gradient(circle at 30% 10%, #f9fafb, #93c5fd);
|
|
94
|
-
box-shadow:
|
|
95
|
-
0 0 0 1px rgba(191, 219, 254, 0.6),
|
|
96
|
-
0 8px 18px rgba(37, 99, 235, 0.8),
|
|
97
|
-
0 0 28px rgba(56, 189, 248, 0.9);
|
|
98
|
-
position: relative;
|
|
99
|
-
flex-shrink: 0;
|
|
100
|
-
display: flex;
|
|
101
|
-
align-items: center;
|
|
102
|
-
justify-content: center;
|
|
103
|
-
overflow: hidden;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
.next-sdk-tool-overlay__indicator-orbit {
|
|
107
|
-
position: absolute;
|
|
108
|
-
inset: 2px;
|
|
109
|
-
border-radius: inherit;
|
|
110
|
-
border: 1px solid rgba(248, 250, 252, 0.6);
|
|
111
|
-
box-sizing: border-box;
|
|
112
|
-
opacity: 0.9;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
.next-sdk-tool-overlay__indicator-orbit::before {
|
|
116
|
-
content: '';
|
|
117
|
-
position: absolute;
|
|
118
|
-
width: 6px;
|
|
119
|
-
height: 6px;
|
|
120
|
-
border-radius: 999px;
|
|
121
|
-
background: #f9fafb;
|
|
122
|
-
box-shadow:
|
|
123
|
-
0 0 12px rgba(248, 250, 252, 0.9),
|
|
124
|
-
0 0 24px rgba(250, 249, 246, 0.9);
|
|
125
|
-
top: 0;
|
|
126
|
-
left: 50%;
|
|
127
|
-
transform: translate(-50%, -50%);
|
|
128
|
-
transform-origin: 50% 18px;
|
|
129
|
-
animation: next-sdk-indicator-orbit 1.4s linear infinite;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
.next-sdk-tool-overlay__indicator-core {
|
|
133
|
-
width: 14px;
|
|
134
|
-
height: 14px;
|
|
135
|
-
border-radius: inherit;
|
|
136
|
-
background: radial-gradient(circle at 30% 20%, #f9fafb, #bfdbfe);
|
|
137
|
-
box-shadow:
|
|
138
|
-
0 0 12px rgba(248, 250, 252, 0.9),
|
|
139
|
-
0 0 32px rgba(191, 219, 254, 0.8);
|
|
140
|
-
opacity: 0.96;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
.next-sdk-tool-overlay__content {
|
|
144
|
-
display: flex;
|
|
145
|
-
flex-direction: column;
|
|
146
|
-
gap: 1px;
|
|
147
|
-
min-width: 0;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
.next-sdk-tool-overlay__title {
|
|
151
|
-
font-size: 11px;
|
|
152
|
-
letter-spacing: 0.08em;
|
|
153
|
-
text-transform: uppercase;
|
|
154
|
-
color: rgba(156, 163, 175, 0.96);
|
|
155
|
-
display: flex;
|
|
156
|
-
align-items: center;
|
|
157
|
-
gap: 6px;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
.next-sdk-tool-overlay__title-dot {
|
|
161
|
-
width: 6px;
|
|
162
|
-
height: 6px;
|
|
163
|
-
border-radius: 999px;
|
|
164
|
-
background: #22c55e;
|
|
165
|
-
box-shadow:
|
|
166
|
-
0 0 8px rgba(34, 197, 94, 0.9),
|
|
167
|
-
0 0 14px rgba(22, 163, 74, 0.9);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
.next-sdk-tool-overlay__label {
|
|
171
|
-
font-size: 12px;
|
|
172
|
-
font-weight: 500;
|
|
173
|
-
color: #e5e7eb;
|
|
174
|
-
white-space: nowrap;
|
|
175
|
-
text-overflow: ellipsis;
|
|
176
|
-
overflow: hidden;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
@keyframes next-sdk-overlay-fade-in {
|
|
180
|
-
from { opacity: 0; }
|
|
181
|
-
to { opacity: 1; }
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
@keyframes next-sdk-overlay-fade-out {
|
|
185
|
-
from { opacity: 1; }
|
|
186
|
-
to { opacity: 0; }
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
@keyframes next-sdk-indicator-orbit {
|
|
190
|
-
from {
|
|
191
|
-
transform: translate(-50%, -50%) rotate(0deg) translateY(0);
|
|
192
|
-
}
|
|
193
|
-
to {
|
|
194
|
-
transform: translate(-50%, -50%) rotate(360deg) translateY(0);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
@keyframes next-sdk-panel-pop-in {
|
|
199
|
-
0% {
|
|
200
|
-
opacity: 0;
|
|
201
|
-
transform: scale(0.92) translateY(10px);
|
|
202
|
-
}
|
|
203
|
-
100% {
|
|
204
|
-
opacity: 1;
|
|
205
|
-
transform: scale(1) translateY(0);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
`
|
|
209
|
-
|
|
210
|
-
document.head.appendChild(style)
|
|
211
|
-
styleElement = style
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function ensureOverlayElement() {
|
|
215
|
-
if (!ensureDomReady()) return
|
|
216
|
-
if (overlayElement) return
|
|
217
|
-
|
|
218
|
-
ensureStyleElement()
|
|
219
|
-
|
|
220
|
-
const overlay = document.createElement('div')
|
|
221
|
-
overlay.className = 'next-sdk-tool-overlay'
|
|
222
|
-
|
|
223
|
-
const glowRing = document.createElement('div')
|
|
224
|
-
glowRing.className = 'next-sdk-tool-overlay__glow-ring'
|
|
225
|
-
|
|
226
|
-
const panel = document.createElement('div')
|
|
227
|
-
panel.className = 'next-sdk-tool-overlay__panel'
|
|
228
|
-
|
|
229
|
-
const indicator = document.createElement('div')
|
|
230
|
-
indicator.className = 'next-sdk-tool-overlay__indicator'
|
|
231
|
-
|
|
232
|
-
const indicatorOrbit = document.createElement('div')
|
|
233
|
-
indicatorOrbit.className = 'next-sdk-tool-overlay__indicator-orbit'
|
|
234
|
-
|
|
235
|
-
const indicatorCore = document.createElement('div')
|
|
236
|
-
indicatorCore.className = 'next-sdk-tool-overlay__indicator-core'
|
|
237
|
-
|
|
238
|
-
const content = document.createElement('div')
|
|
239
|
-
content.className = 'next-sdk-tool-overlay__content'
|
|
240
|
-
|
|
241
|
-
const titleRow = document.createElement('div')
|
|
242
|
-
titleRow.className = 'next-sdk-tool-overlay__title'
|
|
243
|
-
titleRow.textContent = 'AI 正在调用页面工具'
|
|
244
|
-
|
|
245
|
-
const titleDot = document.createElement('span')
|
|
246
|
-
titleDot.className = 'next-sdk-tool-overlay__title-dot'
|
|
247
|
-
|
|
248
|
-
const label = document.createElement('div')
|
|
249
|
-
label.className = 'next-sdk-tool-overlay__label'
|
|
250
|
-
|
|
251
|
-
titleRow.prepend(titleDot)
|
|
252
|
-
content.appendChild(titleRow)
|
|
253
|
-
content.appendChild(label)
|
|
254
|
-
|
|
255
|
-
indicator.appendChild(indicatorOrbit)
|
|
256
|
-
indicator.appendChild(indicatorCore)
|
|
257
|
-
panel.appendChild(indicator)
|
|
258
|
-
panel.appendChild(content)
|
|
259
|
-
overlay.appendChild(glowRing)
|
|
260
|
-
overlay.appendChild(panel)
|
|
261
|
-
|
|
262
|
-
document.body.appendChild(overlay)
|
|
263
|
-
|
|
264
|
-
overlayElement = overlay
|
|
265
|
-
labelElement = label
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function updateOverlay(config: RuntimeEffectConfig) {
|
|
269
|
-
if (!ensureDomReady()) return
|
|
270
|
-
ensureOverlayElement()
|
|
271
|
-
if (!overlayElement || !labelElement) return
|
|
272
|
-
overlayElement.classList.remove('next-sdk-tool-overlay--exit')
|
|
273
|
-
labelElement.textContent = config.label
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function removeOverlayWithAnimation() {
|
|
277
|
-
if (!overlayElement) return
|
|
278
|
-
overlayElement.classList.add('next-sdk-tool-overlay--exit')
|
|
279
|
-
const localOverlay = overlayElement
|
|
280
|
-
let handled = false
|
|
281
|
-
let timerId: ReturnType<typeof setTimeout> | undefined
|
|
282
|
-
const handle = () => {
|
|
283
|
-
if (handled) return
|
|
284
|
-
handled = true
|
|
285
|
-
if (timerId !== undefined) {
|
|
286
|
-
clearTimeout(timerId)
|
|
287
|
-
timerId = undefined
|
|
288
|
-
}
|
|
289
|
-
if (localOverlay.parentNode) {
|
|
290
|
-
localOverlay.parentNode.removeChild(localOverlay)
|
|
291
|
-
}
|
|
292
|
-
if (overlayElement === localOverlay) {
|
|
293
|
-
overlayElement = null
|
|
294
|
-
labelElement = null
|
|
295
|
-
}
|
|
296
|
-
localOverlay.removeEventListener('animationend', handle)
|
|
297
|
-
}
|
|
298
|
-
localOverlay.addEventListener('animationend', handle)
|
|
299
|
-
// 兜底:若 animationend 未触发(如 prefers-reduced-motion 或样式被覆盖),确保清理
|
|
300
|
-
timerId = setTimeout(handle, 500)
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* 在页面上显示工具调用提示效果。
|
|
305
|
-
* - 若当前已有其他工具在执行,仅更新文案,不重复创建 DOM
|
|
306
|
-
* - 会增加 activeCount 计数,需配对调用 hideToolInvokeEffect
|
|
307
|
-
*/
|
|
308
|
-
export function showToolInvokeEffect(config: RuntimeEffectConfig) {
|
|
309
|
-
if (!ensureDomReady()) return
|
|
310
|
-
activeCount += 1
|
|
311
|
-
updateOverlay(config)
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* 隐藏工具调用提示效果。
|
|
316
|
-
* - 使用引用计数:只有当所有调用结束后才真正移除 Overlay
|
|
317
|
-
*/
|
|
318
|
-
export function hideToolInvokeEffect() {
|
|
319
|
-
if (!ensureDomReady() || activeCount <= 0) return
|
|
320
|
-
activeCount -= 1
|
|
321
|
-
if (activeCount === 0) {
|
|
322
|
-
removeOverlayWithAnimation()
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* 将 RouteConfig 中的 invokeEffect 配置编译成运行时效果配置。
|
|
328
|
-
* - boolean / undefined:关闭或开启默认文案
|
|
329
|
-
* - 对象:允许自定义 label
|
|
330
|
-
*/
|
|
331
|
-
export function resolveRuntimeEffectConfig(
|
|
332
|
-
toolName: string,
|
|
333
|
-
toolTitle: string | undefined,
|
|
334
|
-
value: boolean | ToolInvokeEffectConfig | undefined
|
|
335
|
-
): RuntimeEffectConfig | undefined {
|
|
336
|
-
if (!value) return undefined
|
|
337
|
-
const baseLabel = toolTitle || toolName
|
|
338
|
-
if (typeof value === 'boolean') {
|
|
339
|
-
return value ? { label: baseLabel } : undefined
|
|
340
|
-
}
|
|
341
|
-
return {
|
|
342
|
-
label: value.label || baseLabel
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { ActionContext } from '../context'
|
|
2
|
-
|
|
3
|
-
export async function handleBrowserState(args: any, ctx: ActionContext) {
|
|
4
|
-
if (window.__webmcpcli_beforeGetBrowserState) {
|
|
5
|
-
try {
|
|
6
|
-
window.__webmcpcli_beforeGetBrowserState()
|
|
7
|
-
} catch (e) {
|
|
8
|
-
console.warn('__webmcpcli_beforeGetBrowserState error:', e)
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
return ctx.buildBrowserStateResponse(args.responseMode ?? 'diff')
|
|
12
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { clickElement } from '@page-agent/page-controller'
|
|
2
|
-
import { waitForDomSettled } from '../utils/dom'
|
|
3
|
-
import type { ActionContext } from '../context'
|
|
4
|
-
|
|
5
|
-
export async function handleClick(args: any, ctx: ActionContext) {
|
|
6
|
-
const mode = args.responseMode ?? 'diff'
|
|
7
|
-
if (args.index === undefined) return ctx.errContent('点击结果: 缺少元素索引')
|
|
8
|
-
const el = ctx.getRefMap().get(args.index)
|
|
9
|
-
if (!el) return ctx.refreshOnStaleRef('点击', args.index)
|
|
10
|
-
|
|
11
|
-
// 若 ref 指向 shadow host,解析其内部真正的可点击元素
|
|
12
|
-
let targetEl = el as HTMLElement
|
|
13
|
-
if (el.shadowRoot && !(el instanceof HTMLButtonElement) && !(el instanceof HTMLAnchorElement)) {
|
|
14
|
-
const innerClickable = el.shadowRoot.querySelector(
|
|
15
|
-
'button, a, [role="button"], [role="link"]'
|
|
16
|
-
) as HTMLElement | null
|
|
17
|
-
if (innerClickable) {
|
|
18
|
-
targetEl = innerClickable
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
await clickElement(targetEl)
|
|
22
|
-
// 等待框架异步插入的校验错误/条件渲染内容稳定后再采集状态
|
|
23
|
-
await waitForDomSettled()
|
|
24
|
-
// 操作成功后自动返回 diff/both/full
|
|
25
|
-
return ctx.buildBrowserStateResponse(mode)
|
|
26
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import type { ActionContext } from '../context'
|
|
2
|
-
|
|
3
|
-
export async function handleExecuteJavascript(args: any, ctx: ActionContext) {
|
|
4
|
-
if (!args.script) return ctx.errContent('脚本执行异常: 缺少javascript代码')
|
|
5
|
-
// eslint-disable-next-line no-new-func
|
|
6
|
-
// 方式1:将脚本包裹在 async IIFE 中执行,允许 return 语句
|
|
7
|
-
let result = await new Function(`return (async () => { ${args.script} })()`)()
|
|
8
|
-
// 方式2:若 result 为 undefined(脚本没有 return),降级尝试以表达式方式求值
|
|
9
|
-
// 场景:Agent 写了 "Array.from(...).map(...)" 但没有 return 关键字
|
|
10
|
-
if (result === undefined) {
|
|
11
|
-
try {
|
|
12
|
-
// eslint-disable-next-line no-new-func
|
|
13
|
-
result = await new Function(`return (async () => (${args.script}))()`)()
|
|
14
|
-
} catch {
|
|
15
|
-
// 表达式求值也失败(如含 await/let/const 等语句),保持 undefined
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
await ctx.pageController.hideMask()
|
|
19
|
-
return {
|
|
20
|
-
content: [{ type: 'text' as const, text: `脚本执行结果: ${JSON.stringify(result)}` }]
|
|
21
|
-
}
|
|
22
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { inputTextElement } from '@page-agent/page-controller'
|
|
2
|
-
import { dispatchComposedEvents, isInShadowDom, waitForDomSettled } from '../utils/dom'
|
|
3
|
-
import type { ActionContext } from '../context'
|
|
4
|
-
|
|
5
|
-
export async function handleFill(args: any, ctx: ActionContext) {
|
|
6
|
-
const mode = args.responseMode ?? 'diff'
|
|
7
|
-
if (args.index === undefined || typeof args.text !== 'string') return ctx.errContent('填写结果: 缺少元素索引或文本内容')
|
|
8
|
-
const el = ctx.getRefMap().get(args.index)
|
|
9
|
-
if (!el) return ctx.refreshOnStaleRef('填写', args.index)
|
|
10
|
-
|
|
11
|
-
let targetEl = el
|
|
12
|
-
if (!(targetEl instanceof HTMLInputElement) && !(targetEl instanceof HTMLTextAreaElement)) {
|
|
13
|
-
// querySelector 不穿透 shadow boundary,对 shadow host 补查其 shadowRoot
|
|
14
|
-
const innerInput = el.querySelector('input, textarea')
|
|
15
|
-
?? (el.shadowRoot?.querySelector('input, textarea') as HTMLElement | null)
|
|
16
|
-
if (innerInput) {
|
|
17
|
-
targetEl = innerInput as HTMLElement
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if (targetEl instanceof HTMLInputElement && targetEl.readOnly) {
|
|
22
|
-
targetEl.value = args.text
|
|
23
|
-
// composed:true 确保事件穿透 shadow boundary,事件委托框架可捕获
|
|
24
|
-
dispatchComposedEvents(targetEl, 'input', 'change', 'blur')
|
|
25
|
-
} else {
|
|
26
|
-
// 优先尝试标准方式(支持 contenteditable、普通 input/textarea)
|
|
27
|
-
let fillSuccess = false
|
|
28
|
-
try {
|
|
29
|
-
await inputTextElement(targetEl, args.text)
|
|
30
|
-
fillSuccess = true
|
|
31
|
-
} catch (fillErr) {
|
|
32
|
-
// inputTextElement 失败时(如 Angular/React 组件包装的密码框),
|
|
33
|
-
// 降级使用原生 input value 描述符触发框架变更检测
|
|
34
|
-
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
|
|
35
|
-
(targetEl instanceof HTMLTextAreaElement ? HTMLTextAreaElement : HTMLInputElement).prototype,
|
|
36
|
-
'value'
|
|
37
|
-
)?.set
|
|
38
|
-
if (nativeInputValueSetter && (targetEl instanceof HTMLInputElement || targetEl instanceof HTMLTextAreaElement)) {
|
|
39
|
-
nativeInputValueSetter.call(targetEl, args.text)
|
|
40
|
-
// 发送完整事件序列,触发 Angular ngModel / React 合成事件的变更检测
|
|
41
|
-
targetEl.dispatchEvent(new Event('focus', { bubbles: true, composed: true }))
|
|
42
|
-
targetEl.dispatchEvent(new Event('input', { bubbles: true, composed: true }))
|
|
43
|
-
targetEl.dispatchEvent(new Event('change', { bubbles: true, composed: true }))
|
|
44
|
-
targetEl.dispatchEvent(new Event('blur', { bubbles: true, composed: true }))
|
|
45
|
-
fillSuccess = true
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
if (!fillSuccess) {
|
|
49
|
-
return ctx.errContent(`填写结果: 无法填写元素 ${args.index},元素不是 input、textarea 或 contenteditable`)
|
|
50
|
-
}
|
|
51
|
-
// shadow DOM 内元素:inputTextElement 派发的合成事件 composed:false,
|
|
52
|
-
// 补发 composed:true 事件确保事件委托框架(如 React)能收到
|
|
53
|
-
if (isInShadowDom(targetEl)) {
|
|
54
|
-
dispatchComposedEvents(targetEl, 'input', 'change')
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
// 等待框架异步插入的校验错误/条件渲染内容稳定后再采集状态
|
|
58
|
-
await waitForDomSettled()
|
|
59
|
-
return ctx.buildBrowserStateResponse(mode)
|
|
60
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { getScrollInfo, waitForScrollEnd } from '../utils/scroll'
|
|
2
|
-
import type { ActionContext } from '../context'
|
|
3
|
-
|
|
4
|
-
export async function handleScroll(args: any, ctx: ActionContext) {
|
|
5
|
-
const mode = args.responseMode ?? 'diff'
|
|
6
|
-
if (args.down === undefined && args.right === undefined) return ctx.errContent('滚动结果: 缺少滚动方向参数')
|
|
7
|
-
|
|
8
|
-
// 确定滚动目标(有 index 时滚动该元素容器,否则滚动整个文档)
|
|
9
|
-
let scrollTarget: Element | Window = window
|
|
10
|
-
if (args.index !== undefined) {
|
|
11
|
-
const el = ctx.getRefMap().get(args.index)
|
|
12
|
-
if (!el) return ctx.refreshOnStaleRef('滚动', args.index)
|
|
13
|
-
scrollTarget = el
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// 滚动前快照
|
|
17
|
-
const before = getScrollInfo(scrollTarget)
|
|
18
|
-
|
|
19
|
-
if (args.right !== undefined) {
|
|
20
|
-
const pixels = args.pixels ?? 300
|
|
21
|
-
scrollTarget.scrollBy({ left: args.right ? pixels : -pixels, behavior: 'smooth' })
|
|
22
|
-
} else {
|
|
23
|
-
const pixels = args.pixels ?? Math.round((args.numPages ?? 1) * window.innerHeight)
|
|
24
|
-
scrollTarget.scrollBy({ top: args.down ? pixels : -pixels, behavior: 'smooth' })
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// 等待滚动动画完成后再采集状态(scrollend + 超时兜底)
|
|
28
|
-
await waitForScrollEnd(scrollTarget)
|
|
29
|
-
|
|
30
|
-
// 滚动后快照,计算实际位移
|
|
31
|
-
const after = getScrollInfo(scrollTarget)
|
|
32
|
-
const deltaY = Math.round(after.scrollY - before.scrollY)
|
|
33
|
-
const deltaX = Math.round(after.scrollX - before.scrollX)
|
|
34
|
-
const isHorizontal = args.right !== undefined
|
|
35
|
-
|
|
36
|
-
let scrollMsg: string
|
|
37
|
-
if (Math.abs(deltaY) < 1 && Math.abs(deltaX) < 1) {
|
|
38
|
-
// 实际未发生位移,说明已到达边界
|
|
39
|
-
if (isHorizontal) {
|
|
40
|
-
scrollMsg = args.right ? '⚠️ 已到达右边界,无法继续向右滚动' : '⚠️ 已到达左边界,无法继续向左滚动'
|
|
41
|
-
} else {
|
|
42
|
-
scrollMsg = args.down ? '⚠️ 已到达底部,无法继续向下滚动' : '⚠️ 已到达顶部,无法继续向上滚动'
|
|
43
|
-
}
|
|
44
|
-
} else {
|
|
45
|
-
const axis = isHorizontal ? `水平滚动 ${deltaX}px` : `垂直滚动 ${deltaY}px`
|
|
46
|
-
let boundary = ''
|
|
47
|
-
if (isHorizontal) {
|
|
48
|
-
if (args.right && after.atRight) boundary = ',已到达右边界'
|
|
49
|
-
else if (!args.right && after.atLeft) boundary = ',已到达左边界'
|
|
50
|
-
} else {
|
|
51
|
-
if (args.down && after.atBottom) boundary = ',已到达底部'
|
|
52
|
-
else if (!args.down && after.atTop) boundary = ',已到达顶部'
|
|
53
|
-
}
|
|
54
|
-
scrollMsg = `✅ ${axis}${boundary}`
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// 附加位置信息(参考 page-agent getPageInfo 格式)
|
|
58
|
-
const posInfo = args.right !== undefined
|
|
59
|
-
? `当前水平滚动位置: scrollX=${Math.round(after.scrollX)}px,左侧 ${after.pagesAbove.toFixed(1)} 屏,右侧 ${after.pagesBelow.toFixed(1)} 屏`
|
|
60
|
-
: `当前滚动位置: scrollY=${Math.round(after.scrollY)}px,上方 ${after.pagesAbove.toFixed(1)} 屏,下方 ${after.pagesBelow.toFixed(1)} 屏`
|
|
61
|
-
const scrollResult = `[滚动结果] ${scrollMsg}\n${posInfo}`
|
|
62
|
-
|
|
63
|
-
const stateResult = await ctx.buildBrowserStateResponse(mode)
|
|
64
|
-
stateResult.content[0].text = `${scrollResult}\n\n${stateResult.content[0].text}`
|
|
65
|
-
return stateResult
|
|
66
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { searchA11yTree } from '../a11y-tree'
|
|
2
|
-
import { detectPageDialog, detectValidationErrors } from '../utils/dom'
|
|
3
|
-
import { DEFAULT_ERROR_SELECTORS } from '../constants'
|
|
4
|
-
import type { ActionContext } from '../context'
|
|
5
|
-
|
|
6
|
-
export async function handleSearchTree(args: any, ctx: ActionContext) {
|
|
7
|
-
if (!args.query) return ctx.errContent('搜索失败: 缺少 query 参数')
|
|
8
|
-
const blacklist = (window.__webmcpcli_interactiveBlacklist ?? []) as Element[]
|
|
9
|
-
const whitelist = (window.__webmcpcli_interactiveWhitelist ?? []) as Element[]
|
|
10
|
-
const exposedAttributes = (window.__webmcpcli_exposedAttributes ?? []) as string[]
|
|
11
|
-
const result = searchA11yTree(args.query, document.body, blacklist, whitelist, {
|
|
12
|
-
contextLines: args.contextLines,
|
|
13
|
-
maxMatches: args.maxMatches,
|
|
14
|
-
exposedAttributes,
|
|
15
|
-
errorSelectors: (window.__webmcpcli_errorSelectors ?? DEFAULT_ERROR_SELECTORS).join(', '),
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
ctx.setRefMap(result.refMap)
|
|
19
|
-
ctx.stateCache.update(window.location.href, result.yaml)
|
|
20
|
-
|
|
21
|
-
await ctx.pageController.hideMask()
|
|
22
|
-
// 检测页面弹窗/遮罩层,帮助 AI 发现可能遮挡目标的确认弹窗
|
|
23
|
-
const dialogAlert = detectPageDialog()
|
|
24
|
-
// 检测表单校验错误,提醒 AI 优先修复
|
|
25
|
-
const validationErrors = detectValidationErrors()
|
|
26
|
-
const alerts = `${dialogAlert}${validationErrors}`
|
|
27
|
-
return {
|
|
28
|
-
content: [{ type: 'text' as const, text: alerts ? `${alerts}\n${result.text}` : result.text }]
|
|
29
|
-
}
|
|
30
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { selectOptionElement } from '@page-agent/page-controller'
|
|
2
|
-
import { dispatchComposedEvents, isInShadowDom, waitForDomSettled } from '../utils/dom'
|
|
3
|
-
import type { ActionContext } from '../context'
|
|
4
|
-
|
|
5
|
-
export async function handleSelect(args: any, ctx: ActionContext) {
|
|
6
|
-
const mode = args.responseMode ?? 'diff'
|
|
7
|
-
if (args.index === undefined || typeof args.text !== 'string') return ctx.errContent('选择结果: 缺少元素索引或文本内容')
|
|
8
|
-
let el = ctx.getRefMap().get(args.index) as HTMLSelectElement | HTMLElement | undefined
|
|
9
|
-
if (!el) return ctx.refreshOnStaleRef('选择', args.index)
|
|
10
|
-
// 若 ref 指向 shadow host,解析其内部真正的 <select>(与 fill 一致)
|
|
11
|
-
let targetSelect: HTMLSelectElement | null = null
|
|
12
|
-
if (el instanceof HTMLSelectElement) {
|
|
13
|
-
targetSelect = el
|
|
14
|
-
} else {
|
|
15
|
-
const innerSelect = el.querySelector('select')
|
|
16
|
-
?? (el.shadowRoot?.querySelector('select') as HTMLSelectElement | null)
|
|
17
|
-
if (innerSelect) {
|
|
18
|
-
targetSelect = innerSelect
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (!targetSelect) {
|
|
23
|
-
return ctx.errContent(`选择结果: 无法选择元素 ${args.index},未找到对应的 <select> 元素`)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
await selectOptionElement(targetSelect, args.text)
|
|
27
|
-
// shadow DOM 内元素:补发 composed:true 的 change 事件
|
|
28
|
-
if (isInShadowDom(targetSelect)) {
|
|
29
|
-
dispatchComposedEvents(targetSelect, 'change')
|
|
30
|
-
}
|
|
31
|
-
// 等待框架异步插入的校验错误/条件渲染内容稳定后再采集状态
|
|
32
|
-
await waitForDomSettled()
|
|
33
|
-
return ctx.buildBrowserStateResponse(mode)
|
|
34
|
-
}
|