@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,172 @@
1
+ /**
2
+ * 云控制台(consoleCloud)专用 PageAgentToolOptions。
3
+ *
4
+ * 控制台基于 Tiny3(ti3-*)+ Angular,大量自定义组件缺少 role / aria-*:
5
+ * - Tab:`.ti3-tab-li` 无 role=tab,选中靠 `.ti3-tab-active`
6
+ * - 下拉:`.ti3-select-dominator-container` / `.selected-label` 无 combobox 语义
7
+ * - 图标按钮:仅有 `cf-uba` / 子节点 `title`,无 aria-label
8
+ * - 服务列表侧栏、区域选择等用 class 表达选中态
9
+ *
10
+ * 书写约定:优先 selector;仅当需要计算样式等复杂判断时再用 match。
11
+ * (roles 的 selector 为自身 matches;states 的 selector 为 closest)
12
+ */
13
+
14
+ import { defineA11yConfig } from '../a11y/config'
15
+ import type { PageAgentToolOptions } from '../tool-config'
16
+
17
+ /** 云控制台 page-agent-tool 预设配置 */
18
+ export const consoleCloudPageAgentToolOptions: PageAgentToolOptions = {
19
+ enableHighlight: false,
20
+ a11yConfig: defineA11yConfig({
21
+ roles: [
22
+ // Tiny3 Tabs:真正可聚焦/可点击的是内部 .ti3-tabs-text(tabindex=0)
23
+ { role: 'tablist', selector: 'ul.ti3-tabs' },
24
+ { role: 'tab', selector: '.ti3-tabs-text' },
25
+ { role: 'tabpanel', selector: ['ti-tab.ti3-tab-pane', '.ti3-tab-pane'] },
26
+
27
+ // 自定义下拉 / 区域选择触发器
28
+ {
29
+ role: 'combobox',
30
+ selector: [
31
+ '.ti3-select-dominator-container',
32
+ '.selected-label',
33
+ '.sort-select',
34
+ '.service-select',
35
+ '.region-select',
36
+ '[cf-uba="searchableRegion..open"]'
37
+ ]
38
+ },
39
+
40
+ // 图标型 / 无语义按钮
41
+ {
42
+ role: 'button',
43
+ selector: [
44
+ '.modules-service-list-menu-service-icon-container',
45
+ '.ti3-btn-only-icon-noborder',
46
+ '.ti3-select-dominator-dropdown-btn',
47
+ '.modules-right-sidebar-icon-item-wrapper',
48
+ '.modules-right-sidebar-hide-state-plugin',
49
+ '.components-service-list-container-service-list-input-service-close',
50
+ '.modules-searchable-region-region-panel-close',
51
+ '.modules-searchable-region-region-panel-search-btn',
52
+ '[cf-uba="rightSidebar..hideBtn"]',
53
+ // Tiny3 图标组件:帮助中心固定/全屏/关闭等(ti-icon / tp-icon 无原生 button 语义)
54
+ '.ti-global-help-panel-header-icon',
55
+ 'ti-icon[name]',
56
+ 'tp-icon[name]',
57
+ // 无 name 的箭头/操作图标(如 latest news 区 common-icon 展开箭头)
58
+ 'tp-icon.common-icon',
59
+ 'ti-icon.common-icon',
60
+ 'ti-icon.ti3-icon-full-screen',
61
+ 'ti-icon.ti3-icon-close',
62
+ 'tp-icon.ti3-icon-full-screen',
63
+ 'tp-icon.ti3-icon-close'
64
+ ]
65
+ },
66
+
67
+ // 服务列表侧栏分类 / 顶栏菜单项
68
+ {
69
+ role: 'menuitem',
70
+ selector: [
71
+ '.components-service-list-left-box-sidebar-visit-panel > li',
72
+ '.modules-menus-menu-item',
73
+ '.components-menu-wrapper-menu-text'
74
+ ]
75
+ },
76
+
77
+ // 区域筛选项 / 下拉选项
78
+ {
79
+ role: 'option',
80
+ selector: [
81
+ '.region-selector-item',
82
+ '.modules-searchable-region-project-item-region-item',
83
+ '.modules-searchable-region-region-panel-recent-region-item',
84
+ '.ti3-multiselect-box-cell'
85
+ ]
86
+ }
87
+ ],
88
+ states: {
89
+ // 选中态:挂在容器 class 上,用 selector + closest 即可命中内部交互节点
90
+ selected: [
91
+ { selector: 'li.ti3-tab-li.ti3-tab-active' },
92
+ // Tiny3 按钮组(计费模式等):ti3-active 在 .ti3-btn-item-container 上,不在 button 上
93
+ { selector: '.ti3-btn-item-container.ti3-active' },
94
+ { selector: '.components-service-list-left-box-active' },
95
+ {
96
+ selector: [
97
+ '.modules-searchable-region-project-item-region-item-selected',
98
+ '.components-search-content-search-select-search-select-selected'
99
+ ]
100
+ }
101
+ ],
102
+ hasPopup: [
103
+ {
104
+ selector: [
105
+ '.ti3-select-dominator-container',
106
+ '.selected-label',
107
+ '.sort-select',
108
+ '.service-select',
109
+ '.region-select',
110
+ '[cf-uba="searchableRegion..open"]',
111
+ '[cf-uba="serviceList..open"]',
112
+ '[cf-uba="userinfo"]'
113
+ ]
114
+ }
115
+ ],
116
+ disabled: [{ selector: '.ti3-disabled' }],
117
+ // 展开态:需结合计算样式,选择器表达不了「可见」
118
+ expanded: [
119
+ {
120
+ match: (el) => {
121
+ try {
122
+ if (
123
+ !el.matches(
124
+ '.modules-layout-module-sidebar-panel, .modules-searchable-region-region-panel, .ti3-dropdown-container'
125
+ )
126
+ ) {
127
+ return false
128
+ }
129
+ const style = window.getComputedStyle(el as HTMLElement)
130
+ return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0'
131
+ } catch {
132
+ return false
133
+ }
134
+ }
135
+ }
136
+ ]
137
+ },
138
+ // exposedAttributes 输出属性 token;带 name 的 ti-icon/tp-icon 可交互性由 roles/whitelist 中 ti-icon[name] 等规则判定。
139
+ exposedAttributes: ['cf-uba', 'data-qa-id', 'name'],
140
+ whitelist: [
141
+ '.modules-service-list-menu-service-icon-container',
142
+ '.ti3-tabs-text',
143
+ '[cf-uba="cloudShell"]',
144
+ '[cf-uba="messageBox"]',
145
+ '[cf-uba="helpFeedback"]',
146
+ '[cf-uba="globalMessage"]',
147
+ '[cf-uba="rightSidebar..hideBtn"]',
148
+ // 帮助中心等区域的 ti-icon / tp-icon 图标按钮
149
+ '.ti-global-help-panel-header-icon',
150
+ 'ti-icon[name]',
151
+ 'tp-icon[name]',
152
+ 'tp-icon.common-icon',
153
+ 'ti-icon.common-icon',
154
+ 'ti-icon.ti3-icon-full-screen'
155
+ ],
156
+ blacklist: ['noscript', 'pan-gu', '#J_header'],
157
+ dialogSelectors: [
158
+ '[class*="ti3-modal"]',
159
+ '[class*="ti3-message-box"]',
160
+ '[class*="drawer"]',
161
+ '.modules-layout-module-sidebar-panel'
162
+ ]
163
+ })
164
+ }
165
+
166
+ /** 判断当前页面是否应使用云控制台(consoleCloud)预设 */
167
+ export function isConsoleCloudHost(
168
+ hostname: string = typeof location !== 'undefined' ? location.hostname : ''
169
+ ): boolean {
170
+ // 要求 console 前为域名边界(开头或 `.`),避免误匹配 xconsole.huaweicloud.com
171
+ return /(^|\.)console\.huaweicloud\.com$/i.test(hostname)
172
+ }
@@ -0,0 +1,12 @@
1
+ /** 校验错误默认选择器:ARIA 标准 + 主流 UI 框架(唯一来源为 a11y/constants.ts,此处重新导出保持旧路径可用) */
2
+ export { DEFAULT_ERROR_SELECTORS, DEFAULT_DIALOG_SELECTORS } from './a11y/constants'
3
+
4
+ /** registerPageAgentTool(options) 的入参类型,定义见 tool-config.ts(与运行期 setPageAgentToolConfig 的补丁类型一致) */
5
+ export type { PageAgentToolOptions } from './tool-config'
6
+
7
+ declare global {
8
+ interface Window {
9
+ /** 指定网站可覆盖该函数,在每次 getBrowserState 之前调用,常用于配合 setPageAgentToolConfig 动态调整当前页面的配置 */
10
+ __webmcpcli_beforeGetBrowserState?: (() => void) | null
11
+ }
12
+ }
@@ -0,0 +1,50 @@
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 type PageAgentToolContentResult = {
6
+ content: Array<{ type: 'text'; text: string }>
7
+ }
8
+
9
+ export type PageAgentToolErrorResult = PageAgentToolContentResult & {
10
+ isError: true
11
+ error: string
12
+ }
13
+
14
+ export async function createActionErrorResult(
15
+ message: string,
16
+ buildBrowserStateResponse: ActionContext['buildBrowserStateResponse']
17
+ ): Promise<PageAgentToolErrorResult> {
18
+ try {
19
+ const browserState = await buildBrowserStateResponse('full')
20
+ return {
21
+ isError: true,
22
+ error: message,
23
+ content: [{ type: 'text', text: `${message}\n\n${browserState.content[0].text}` }]
24
+ }
25
+ } catch (error) {
26
+ return {
27
+ isError: true,
28
+ error: message,
29
+ content: [
30
+ {
31
+ type: 'text',
32
+ text: `${message}\n\n获取最新浏览器状态失败: ${error instanceof Error ? error.message : String(error)}`
33
+ }
34
+ ]
35
+ }
36
+ }
37
+ }
38
+
39
+ export interface ActionContext {
40
+ pageController: PageController
41
+ stateCache: PageStateCache
42
+ getRefMap: () => RefMap
43
+ setRefMap: (map: RefMap) => void
44
+ buildBrowserStateResponse: (
45
+ mode?: 'full' | 'diff' | 'both'
46
+ ) => Promise<{ content: Array<{ type: 'text'; text: string }> }>
47
+ refreshOnStaleRef: (action: string, index: number) => Promise<{ content: Array<{ type: 'text'; text: string }> }>
48
+ errContent: (msg: string) => Promise<{ content: Array<{ type: 'text'; text: string }> }>
49
+ actionError: (msg: string) => Promise<PageAgentToolErrorResult>
50
+ }
@@ -0,0 +1,12 @@
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
+ }
@@ -0,0 +1,30 @@
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.actionError('点击结果: 缺少元素索引')
8
+ const el = ctx.getRefMap().get(args.index)
9
+ if (!el) {
10
+ return ctx.actionError(
11
+ `点击失败: ref 索引 ${args.index} 已失效(页面可能已刷新),已自动重新加载页面状态,请使用新的 ref 索引重试。`
12
+ )
13
+ }
14
+
15
+ // 若 ref 指向 shadow host,解析其内部真正的可点击元素
16
+ let targetEl = el as HTMLElement
17
+ if (el.shadowRoot && !(el instanceof HTMLButtonElement) && !(el instanceof HTMLAnchorElement)) {
18
+ const innerClickable = el.shadowRoot.querySelector(
19
+ 'button, a, [role="button"], [role="link"]'
20
+ ) as HTMLElement | null
21
+ if (innerClickable) {
22
+ targetEl = innerClickable
23
+ }
24
+ }
25
+ await clickElement(targetEl)
26
+ // 等待框架异步插入的校验错误/条件渲染内容稳定后再采集状态
27
+ await waitForDomSettled()
28
+ // 操作成功后自动返回 diff/both/full
29
+ return ctx.buildBrowserStateResponse(mode)
30
+ }
@@ -0,0 +1,22 @@
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
+ }
@@ -0,0 +1,65 @@
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')
8
+ return ctx.actionError('填写结果: 缺少元素索引或文本内容')
9
+ const el = ctx.getRefMap().get(args.index)
10
+ if (!el) {
11
+ return ctx.actionError(
12
+ `填写失败: ref 索引 ${args.index} 已失效(页面可能已刷新),已自动重新加载页面状态,请使用新的 ref 索引重试。`
13
+ )
14
+ }
15
+
16
+ let targetEl = el
17
+ if (!(targetEl instanceof HTMLInputElement) && !(targetEl instanceof HTMLTextAreaElement)) {
18
+ // querySelector 不穿透 shadow boundary,对 shadow host 补查其 shadowRoot
19
+ const innerInput =
20
+ el.querySelector('input, textarea') ?? (el.shadowRoot?.querySelector('input, textarea') as HTMLElement | null)
21
+ if (innerInput) {
22
+ targetEl = innerInput as HTMLElement
23
+ }
24
+ }
25
+
26
+ if (targetEl instanceof HTMLInputElement && targetEl.readOnly) {
27
+ targetEl.value = args.text
28
+ // composed:true 确保事件穿透 shadow boundary,事件委托框架可捕获
29
+ dispatchComposedEvents(targetEl, 'input', 'change', 'blur')
30
+ } else {
31
+ // 优先尝试标准方式(支持 contenteditable、普通 input/textarea)
32
+ let fillSuccess = false
33
+ try {
34
+ await inputTextElement(targetEl, args.text)
35
+ fillSuccess = true
36
+ } catch (fillErr) {
37
+ // inputTextElement 失败时(如 Angular/React 组件包装的密码框),
38
+ // 降级使用原生 input value 描述符触发框架变更检测
39
+ const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
40
+ (targetEl instanceof HTMLTextAreaElement ? HTMLTextAreaElement : HTMLInputElement).prototype,
41
+ 'value'
42
+ )?.set
43
+ if (nativeInputValueSetter && (targetEl instanceof HTMLInputElement || targetEl instanceof HTMLTextAreaElement)) {
44
+ nativeInputValueSetter.call(targetEl, args.text)
45
+ // 发送完整事件序列,触发 Angular ngModel / React 合成事件的变更检测
46
+ targetEl.dispatchEvent(new Event('focus', { bubbles: true, composed: true }))
47
+ targetEl.dispatchEvent(new Event('input', { bubbles: true, composed: true }))
48
+ targetEl.dispatchEvent(new Event('change', { bubbles: true, composed: true }))
49
+ targetEl.dispatchEvent(new Event('blur', { bubbles: true, composed: true }))
50
+ fillSuccess = true
51
+ }
52
+ }
53
+ if (!fillSuccess) {
54
+ return ctx.actionError(`填写结果: 无法填写元素 ${args.index},元素不是 input、textarea 或 contenteditable`)
55
+ }
56
+ // shadow DOM 内元素:inputTextElement 派发的合成事件 composed:false,
57
+ // 补发 composed:true 事件确保事件委托框架(如 React)能收到
58
+ if (isInShadowDom(targetEl)) {
59
+ dispatchComposedEvents(targetEl, 'input', 'change')
60
+ }
61
+ }
62
+ // 等待框架异步插入的校验错误/条件渲染内容稳定后再采集状态
63
+ await waitForDomSettled()
64
+ return ctx.buildBrowserStateResponse(mode)
65
+ }
@@ -0,0 +1,66 @@
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
+ }
@@ -0,0 +1,27 @@
1
+ import { searchA11yTree } from '../a11y-tree'
2
+ import { detectPageDialog, detectValidationErrors } from '../utils/dom'
3
+ import { getPageAgentToolConfig } from '../tool-config'
4
+ import type { ActionContext } from '../context'
5
+ import type { PageAgentToolInput } from '../schema'
6
+
7
+ export async function handleSearchTree(args: PageAgentToolInput, ctx: ActionContext) {
8
+ if (!args.query) return ctx.errContent('搜索失败: 缺少 query 参数')
9
+ const result = searchA11yTree(args.query, document.body, {
10
+ ...getPageAgentToolConfig().a11yConfig,
11
+ contextLines: args.contextLines,
12
+ maxMatches: args.maxMatches,
13
+ })
14
+
15
+ ctx.setRefMap(result.refMap)
16
+ ctx.stateCache.update(window.location.href, result.yaml)
17
+
18
+ await ctx.pageController.hideMask()
19
+ // 检测页面弹窗/遮罩层,帮助 AI 发现可能遮挡目标的确认弹窗
20
+ const dialogAlert = detectPageDialog()
21
+ // 检测表单校验错误,提醒 AI 优先修复
22
+ const validationErrors = detectValidationErrors()
23
+ const alerts = `${dialogAlert}${validationErrors}`
24
+ return {
25
+ content: [{ type: 'text' as const, text: alerts ? `${alerts}\n${result.text}` : result.text }]
26
+ }
27
+ }
@@ -0,0 +1,39 @@
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')
8
+ return ctx.actionError('选择结果: 缺少元素索引或文本内容')
9
+ let el = ctx.getRefMap().get(args.index) as HTMLSelectElement | HTMLElement | undefined
10
+ if (!el) {
11
+ return ctx.actionError(
12
+ `选择失败: ref 索引 ${args.index} 已失效(页面可能已刷新),已自动重新加载页面状态,请使用新的 ref 索引重试。`
13
+ )
14
+ }
15
+ // 若 ref 指向 shadow host,解析其内部真正的 <select>(与 fill 一致)
16
+ let targetSelect: HTMLSelectElement | null = null
17
+ if (el instanceof HTMLSelectElement) {
18
+ targetSelect = el
19
+ } else {
20
+ const innerSelect =
21
+ el.querySelector('select') ?? (el.shadowRoot?.querySelector('select') as HTMLSelectElement | null)
22
+ if (innerSelect) {
23
+ targetSelect = innerSelect
24
+ }
25
+ }
26
+
27
+ if (!targetSelect) {
28
+ return ctx.actionError(`选择结果: 无法选择元素 ${args.index},未找到对应的 <select> 元素`)
29
+ }
30
+
31
+ await selectOptionElement(targetSelect, args.text)
32
+ // shadow DOM 内元素:补发 composed:true 的 change 事件
33
+ if (isInShadowDom(targetSelect)) {
34
+ dispatchComposedEvents(targetSelect, 'change')
35
+ }
36
+ // 等待框架异步插入的校验错误/条件渲染内容稳定后再采集状态
37
+ await waitForDomSettled()
38
+ return ctx.buildBrowserStateResponse(mode)
39
+ }