@opentiny/next-sdk 0.3.3 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) 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 +1 -3
  6. package/dist/SimulatorMask-BHVXyogh-iNUb4dyF.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 +1 -1
  10. package/dist/core.js +18 -18
  11. package/dist/index-CXK8CcKq.js +6076 -0
  12. package/dist/index.d.ts +2 -4
  13. package/dist/index.js +57 -2783
  14. package/dist/initialize-builtin-WebMCP-rALfqS-o.js +6389 -0
  15. package/dist/page-tools/a11y/build.d.ts +11 -0
  16. package/dist/page-tools/a11y/constants.d.ts +9 -0
  17. package/dist/page-tools/a11y/search.d.ts +17 -0
  18. package/dist/page-tools/a11y/types.d.ts +104 -0
  19. package/dist/page-tools/a11y/utils.d.ts +34 -0
  20. package/dist/page-tools/a11y/vnode.d.ts +37 -0
  21. package/dist/page-tools/a11y-tree.d.ts +12 -0
  22. package/dist/page-tools/bridge.d.ts +0 -6
  23. package/dist/page-tools/constants.d.ts +22 -0
  24. package/dist/page-tools/context.d.ts +28 -0
  25. package/dist/page-tools/handlers/browserState.d.ts +8 -0
  26. package/dist/page-tools/handlers/click.d.ts +8 -0
  27. package/dist/page-tools/handlers/executeJavascript.d.ts +8 -0
  28. package/dist/page-tools/handlers/fill.d.ts +8 -0
  29. package/dist/page-tools/handlers/scroll.d.ts +8 -0
  30. package/dist/page-tools/handlers/searchTree.d.ts +8 -0
  31. package/dist/page-tools/handlers/select.d.ts +8 -0
  32. package/dist/page-tools/initialize-builtin-WebMCP.d.ts +1 -0
  33. package/dist/page-tools/page-agent-highlight/index.d.ts +9 -0
  34. package/dist/page-tools/page-agent-mask/SimulatorMask.d.ts +14 -0
  35. package/dist/page-tools/page-agent-mask/checkDarkMode.d.ts +5 -0
  36. package/dist/page-tools/page-agent-tool.d.ts +4 -0
  37. package/dist/page-tools/page-state-cache.d.ts +36 -0
  38. package/dist/page-tools/schema.d.ts +43 -0
  39. package/dist/page-tools/utils/dom.d.ts +6 -0
  40. package/dist/page-tools/utils/scroll.d.ts +15 -0
  41. package/dist/runtime.d.ts +4 -0
  42. package/dist/runtime.js +744 -0
  43. package/dist/utils/builtinProxy.d.ts +1 -1
  44. package/index.ts +2 -19
  45. package/package.json +14 -18
  46. package/page-tools/a11y/build.ts +70 -0
  47. package/page-tools/a11y/constants.ts +110 -0
  48. package/page-tools/a11y/search.ts +129 -0
  49. package/page-tools/a11y/types.ts +108 -0
  50. package/page-tools/a11y/utils.ts +266 -0
  51. package/page-tools/a11y/vnode.ts +229 -0
  52. package/page-tools/a11y-tree.ts +13 -0
  53. package/page-tools/bridge.ts +49 -15
  54. package/page-tools/constants.ts +72 -0
  55. package/page-tools/context.ts +13 -0
  56. package/page-tools/handlers/browserState.ts +12 -0
  57. package/page-tools/handlers/click.ts +26 -0
  58. package/page-tools/handlers/executeJavascript.ts +22 -0
  59. package/page-tools/handlers/fill.ts +60 -0
  60. package/page-tools/handlers/scroll.ts +66 -0
  61. package/page-tools/handlers/searchTree.ts +30 -0
  62. package/page-tools/handlers/select.ts +34 -0
  63. package/page-tools/initialize-builtin-WebMCP.ts +20 -0
  64. package/page-tools/page-agent-highlight/index.ts +198 -0
  65. package/page-tools/page-agent-mask/SimulatorMask.module.css +14 -0
  66. package/page-tools/page-agent-mask/SimulatorMask.ts +279 -0
  67. package/page-tools/page-agent-mask/checkDarkMode.ts +181 -0
  68. package/page-tools/page-agent-mask/cursor-border.svg +3 -0
  69. package/page-tools/page-agent-mask/cursor-fill.svg +5 -0
  70. package/page-tools/page-agent-mask/cursor.module.css +70 -0
  71. package/page-tools/page-agent-prompt.md +139 -0
  72. package/page-tools/page-agent-tool.ts +175 -0
  73. package/page-tools/page-state-cache.ts +78 -0
  74. package/page-tools/schema.ts +51 -0
  75. package/page-tools/utils/dom.ts +155 -0
  76. package/page-tools/utils/scroll.ts +58 -0
  77. package/runtime.ts +13 -0
  78. package/utils/builtinProxy.ts +45 -13
  79. package/vite.config.runtime.ts +22 -0
  80. package/vite.config.ts +52 -8
  81. package/McpSdk.ts +0 -14
  82. package/WebAgent.ts +0 -5
  83. package/WebMcp.ts +0 -20
  84. package/Zod.ts +0 -1
  85. package/dist/AgentModelProvider-CwqOT28f.js +0 -4198
  86. package/dist/McpSdk.d.ts +0 -14
  87. package/dist/WebAgent.d.ts +0 -5
  88. package/dist/WebMcp.d.ts +0 -20
  89. package/dist/Zod.d.ts +0 -1
  90. package/dist/index.es.dev.js +0 -53427
  91. package/dist/index.es.js +0 -42147
  92. package/dist/index.umd.dev.js +0 -53820
  93. package/dist/index.umd.js +0 -814
  94. package/dist/mcpsdk@1.25.3.dev.js +0 -22621
  95. package/dist/mcpsdk@1.25.3.es.dev.js +0 -22619
  96. package/dist/mcpsdk@1.25.3.es.js +0 -16802
  97. package/dist/mcpsdk@1.25.3.js +0 -43
  98. package/dist/transport/ExtensionClientTransport.d.ts +0 -24
  99. package/dist/transport/ExtensionContentServerTransport.d.ts +0 -39
  100. package/dist/transport/ExtensionPageServerTransport.d.ts +0 -36
  101. package/dist/transport/messages.d.ts +0 -9
  102. package/dist/vite.config.mcpSdk.d.ts +0 -2
  103. package/dist/vite.config.webAgent.d.ts +0 -2
  104. package/dist/vite.config.webMcp.d.ts +0 -2
  105. package/dist/vite.config.webMcpFull.d.ts +0 -2
  106. package/dist/vite.config.zod.d.ts +0 -2
  107. package/dist/webagent.dev.js +0 -47597
  108. package/dist/webagent.es.dev.js +0 -47204
  109. package/dist/webagent.es.js +0 -37494
  110. package/dist/webagent.js +0 -634
  111. package/dist/webmcp-full.dev.js +0 -24430
  112. package/dist/webmcp-full.es.dev.js +0 -24426
  113. package/dist/webmcp-full.es.js +0 -18333
  114. package/dist/webmcp-full.js +0 -43
  115. package/dist/webmcp.dev.js +0 -1595
  116. package/dist/webmcp.es.dev.js +0 -1589
  117. package/dist/webmcp.es.js +0 -1406
  118. package/dist/webmcp.js +0 -1
  119. package/dist/zod@3.25.76.dev.js +0 -4037
  120. package/dist/zod@3.25.76.es.dev.js +0 -4033
  121. package/dist/zod@3.25.76.es.js +0 -2945
  122. package/dist/zod@3.25.76.js +0 -1
  123. package/transport/ExtensionClientTransport.ts +0 -100
  124. package/transport/ExtensionContentServerTransport.ts +0 -162
  125. package/transport/ExtensionPageServerTransport.ts +0 -149
  126. package/transport/messages.ts +0 -63
  127. package/vite-build-tsc.ts +0 -63
  128. package/vite-env.d.ts +0 -5
  129. package/vite.config.mcpSdk.ts +0 -28
  130. package/vite.config.webAgent.ts +0 -19
  131. package/vite.config.webMcp.ts +0 -40
  132. package/vite.config.webMcpFull.ts +0 -19
  133. package/vite.config.zod.ts +0 -23
  134. /package/dist/{vite-build-tsc.d.ts → vite.config.runtime.d.ts} +0 -0
@@ -2,6 +2,6 @@ import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
2
2
 
3
3
  /**
4
4
  * 建立原生的 JSON-RPC 拦截层:
5
- * 将发向 remote 的 MCP 请求拦截,代理给所在浏览器的 navigator.modelContextTesting 上执行。
5
+ * 将发向 remote 的 MCP 请求拦截,代理给所在浏览器的 document.modelContext 上执行。
6
6
  */
7
7
  export declare const setupBuiltinProxy: (transport: Transport) => void;
package/index.ts CHANGED
@@ -21,11 +21,6 @@ export type * from '@modelcontextprotocol/sdk/server/mcp.js'
21
21
  export * from './WebMcpServer'
22
22
  export * from './WebMcpClient'
23
23
 
24
- // 浏览器扩展自定义传输层
25
- export * from './transport/ExtensionClientTransport'
26
- export * from './transport/ExtensionPageServerTransport'
27
- export * from './transport/ExtensionContentServerTransport'
28
-
29
24
  // 快速创建一个悬浮图标和菜单,是扫码和聊天框的入口
30
25
  export * from './remoter/createRemoter'
31
26
 
@@ -57,17 +52,5 @@ export {
57
52
 
58
53
  export * from '@mcp-b/webmcp-polyfill'
59
54
 
60
- import { initializeWebMCPPolyfill } from '@mcp-b/webmcp-polyfill'
61
- import { setupModelContextBridge } from './page-tools/bridge'
62
- import { isBrowser } from './utils/env'
63
-
64
- export const initializeBuiltinWebMCP = () => {
65
- if (isBrowser()) {
66
- try {
67
- initializeWebMCPPolyfill()
68
- setupModelContextBridge()
69
- } catch (err) {
70
- console.warn('[next-sdk] 自动注入 modelContext polyfill 和桥接同步失败:', err)
71
- }
72
- }
73
- }
55
+ export { initializeBuiltinWebMCP } from './page-tools/initialize-builtin-WebMCP'
56
+ export { registerPageAgentTool } from './page-tools/page-agent-tool'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opentiny/next-sdk",
3
- "version": "0.3.3",
3
+ "version": "0.4.1",
4
4
  "type": "module",
5
5
  "homepage": "https://docs.opentiny.design/next-sdk/guide/",
6
6
  "repository": {
@@ -40,36 +40,32 @@
40
40
  "@ai-sdk/deepseek": "1.0.30",
41
41
  "@ai-sdk/provider": "^3.0.0",
42
42
  "@ai-sdk/mcp": "^1.0.0",
43
- "@mcp-b/webmcp-polyfill": "^2.2.0",
43
+ "@mcp-b/webmcp-polyfill": "^3.0.0",
44
+ "@page-agent/page-controller": "^1.10.0",
44
45
  "qrcode": "^1.5.4",
45
46
  "zod": "^3.25.76",
47
+ "zod-to-json-schema": "^3.25.2",
46
48
  "ajv": "^8.17.1",
47
- "ai": "^6.0.0"
49
+ "ai": "^6.0.0",
50
+ "dom-accessibility-api": "^0.6.3",
51
+ "aria-query": "^5.3.2",
52
+ "tabbable": "^6.2.0",
53
+ "diff": "^7.0.0",
54
+ "ai-motion": "^0.4.8"
48
55
  },
49
56
  "devDependencies": {
50
57
  "typescript": "~5.9.3",
51
58
  "vite": "^7.1.7",
52
59
  "vite-plugin-dts": "^3.9.1",
53
60
  "@types/qrcode": "^1.5.5",
54
- "@types/node": "^24.3.0"
61
+ "@types/node": "^24.6.0",
62
+ "@types/diff": "^7.0.0"
55
63
  },
56
64
  "publishConfig": {
57
65
  "access": "public"
58
66
  },
59
67
  "scripts": {
60
- "build:all": "pnpm build:cdn && pnpm build:cdn:dev && pnpm build",
61
- "build": "vite build --config vite-build-tsc.ts",
62
- "build:cdn": "vite build && pnpm build:webAgent && pnpm build:webMcp && pnpm build:mcpSdk && pnpm build:zod && pnpm build:webMcpFull",
63
- "build:cdn:dev": "vite build --mode dev && pnpm build:webAgent:dev && pnpm build:webMcp:dev && pnpm build:mcpSdk:dev && pnpm build:zod:dev && pnpm build:webMcpFull:dev",
64
- "build:webAgent": "vite build --config vite.config.webAgent.ts",
65
- "build:webAgent:dev": "vite build --config vite.config.webAgent.ts --mode dev",
66
- "build:webMcp": "vite build --config vite.config.webMcp.ts",
67
- "build:webMcp:dev": "vite build --config vite.config.webMcp.ts --mode dev",
68
- "build:mcpSdk": "vite build --config vite.config.mcpSdk.ts",
69
- "build:mcpSdk:dev": "vite build --config vite.config.mcpSdk.ts --mode dev",
70
- "build:zod": "vite build --config vite.config.zod.ts",
71
- "build:zod:dev": "vite build --config vite.config.zod.ts --mode dev",
72
- "build:webMcpFull": "vite build --config vite.config.webMcpFull.ts",
73
- "build:webMcpFull:dev": "vite build --config vite.config.webMcpFull.ts --mode dev"
68
+ "build": "vite build && vite build --config vite.config.runtime.ts",
69
+ "build:runtime": "vite build --config vite.config.runtime.ts"
74
70
  }
75
71
  }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * a11y/build.ts
3
+ *
4
+ * 暴露生成整棵无障碍树的主逻辑方法。
5
+ */
6
+
7
+ import type { A11yTreeOptions, A11yTreeResult, RefMap } from './types'
8
+ import { DEFAULT_ERROR_SELECTORS, DEFAULT_WARNING_SELECTORS } from './constants'
9
+ import { buildVNode, serializeVNode } from './vnode'
10
+ import { getComposedChildren } from './utils'
11
+ import { HIGHLIGHT_CONTAINER_ID } from '../page-agent-highlight'
12
+
13
+ const DEFAULT_OPTIONS: Required<A11yTreeOptions> = {
14
+ pruneUnnamed: true,
15
+ preserveRoles: [],
16
+ exposedAttributes: [],
17
+ errorSelectors: DEFAULT_ERROR_SELECTORS,
18
+ warningSelectors: DEFAULT_WARNING_SELECTORS
19
+ }
20
+
21
+ /**
22
+ * 生成当前页面的语义化 ARIA YAML 树
23
+ *
24
+ * @param root 遍历起点,默认 document.body
25
+ * @param blacklist 需要跳过的元素(用户自定义黑名单)
26
+ * @param whitelist 需要识别为可交互的白名单元素列表
27
+ * @param options 过滤选项
28
+ */
29
+ export function buildA11yTree(
30
+ root: Element = document.body,
31
+ blacklist: Element[] = [],
32
+ whitelist: Element[] = [],
33
+ options?: A11yTreeOptions
34
+ ): A11yTreeResult {
35
+ const opts: Required<A11yTreeOptions> = { ...DEFAULT_OPTIONS, ...options }
36
+ // 使用对象引用避免全局可变状态,消除并发调用隐患
37
+ const refCounter = { value: 0 }
38
+ const refMap: RefMap = new Map()
39
+ const blacklistSet = new Set(blacklist)
40
+ const whitelistSet = new Set(whitelist)
41
+ const lines: string[] = []
42
+
43
+ // 永远过滤高亮容器
44
+ blacklistSet.add(document.getElementById(HIGHLIGHT_CONTAINER_ID)!)
45
+
46
+ for (const child of getComposedChildren(root)) {
47
+ const vnode = buildVNode(
48
+ child,
49
+ refCounter,
50
+ refMap,
51
+ blacklistSet,
52
+ whitelistSet,
53
+ opts.exposedAttributes,
54
+ opts.errorSelectors,
55
+ opts.warningSelectors
56
+ )
57
+ if (vnode) {
58
+ lines.push(...serializeVNode(vnode, 0, opts))
59
+ }
60
+ }
61
+
62
+ const yaml = '```yaml\n' + lines.join('\n') + '\n```'
63
+
64
+ return {
65
+ yaml,
66
+ refMap,
67
+ interactiveCount: refMap.size,
68
+ lines
69
+ }
70
+ }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * a11y/constants.ts
3
+ *
4
+ * 存放无障碍树依赖的静态常量配置、默认选择器及角色映射表。
5
+ */
6
+
7
+ // ─── 默认校验错误/警告选择器(ARIA 标准 + 主流 UI 框架) ──────────────────
8
+
9
+ export const DEFAULT_ERROR_SELECTORS = [
10
+ '[role="alert"]', '[aria-invalid="true"]',
11
+ '.ti3-unifyvalid-error', '.ti3-error', '.ti-error',
12
+ '.lego-text-error', '.lego-error',
13
+ '.el-form-item__error',
14
+ '.ant-form-item-explain-error',
15
+ '.is-invalid', '.invalid-feedback',
16
+ '.ng-invalid',
17
+ '.error-msg', '.error-message', '.error-text',
18
+ '.field-error', '.form-error',
19
+ '.is-error', '.has-error',
20
+ '.validate-error', '.valid-error',
21
+ ]
22
+
23
+ export const DEFAULT_WARNING_SELECTORS = [
24
+ '.ti3-warning', '.ti-warning', '.lego-text-warning',
25
+ '.warning-msg', '.warning-text', '.is-warning', '.has-warning',
26
+ ]
27
+
28
+ // ─── ARIA 隐式角色静态映射表(覆盖页面 95%+ 的常用标签)───────────────────────
29
+
30
+ export const TAG_ROLE_MAP: Record<string, string> = {
31
+ a: 'link',
32
+ article: 'article',
33
+ aside: 'complementary',
34
+ button: 'button',
35
+ caption: 'caption',
36
+ cell: 'cell',
37
+ checkbox: 'checkbox',
38
+ code: 'code',
39
+ columnheader: 'columnheader',
40
+ combobox: 'combobox',
41
+ datalist: 'listbox',
42
+ dd: 'definition',
43
+ details: 'group',
44
+ dialog: 'dialog',
45
+ dt: 'term',
46
+ em: 'emphasis',
47
+ fieldset: 'group',
48
+ figure: 'figure',
49
+ footer: 'contentinfo',
50
+ form: 'form',
51
+ h1: 'heading',
52
+ h2: 'heading',
53
+ h3: 'heading',
54
+ h4: 'heading',
55
+ h5: 'heading',
56
+ h6: 'heading',
57
+ header: 'banner',
58
+ hr: 'separator',
59
+ img: 'img',
60
+ input: 'textbox', // 默认,具体 type 下面会覆盖
61
+ li: 'listitem',
62
+ link: 'link',
63
+ main: 'main',
64
+ mark: 'mark',
65
+ math: 'math',
66
+ menu: 'list',
67
+ menuitem: 'menuitem',
68
+ meter: 'meter',
69
+ nav: 'navigation',
70
+ ol: 'list',
71
+ option: 'option',
72
+ output: 'status',
73
+ p: 'paragraph',
74
+ progress: 'progressbar',
75
+ rowheader: 'rowheader',
76
+ search: 'search',
77
+ section: 'region',
78
+ select: 'listbox',
79
+ strong: 'strong',
80
+ summary: 'button',
81
+ table: 'table',
82
+ tbody: 'rowgroup',
83
+ td: 'cell',
84
+ textarea: 'textbox',
85
+ tfoot: 'rowgroup',
86
+ th: 'columnheader',
87
+ thead: 'rowgroup',
88
+ time: 'time',
89
+ tr: 'row',
90
+ ul: 'list',
91
+ }
92
+
93
+ // input[type=*] 的角色覆盖
94
+ export const INPUT_TYPE_ROLE: Record<string, string> = {
95
+ button: 'button',
96
+ checkbox: 'checkbox',
97
+ color: 'textbox',
98
+ email: 'textbox',
99
+ file: 'textbox',
100
+ image: 'button',
101
+ number: 'spinbutton',
102
+ radio: 'radio',
103
+ range: 'slider',
104
+ reset: 'button',
105
+ search: 'searchbox',
106
+ submit: 'button',
107
+ tel: 'textbox',
108
+ text: 'textbox',
109
+ url: 'textbox',
110
+ }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * a11y/search.ts
3
+ *
4
+ * 在无障碍树内执行基于文本或引用的搜索操作。
5
+ */
6
+
7
+ import type { SearchA11yTreeOptions, SearchA11yTreeResult, A11ySearchMatch } from './types'
8
+ import { buildA11yTree } from './build'
9
+
10
+ /**
11
+ * 在无障碍树中按关键词搜索,返回带行号的匹配结果和上下文
12
+ *
13
+ * 支持的搜索维度(均对同一个 query 字符串做包含匹配):
14
+ * - role:如 `button`、`link`、`heading`
15
+ * - accessible name:节点的语义化名称(引号内文本)
16
+ * - state token:如 `checked`、`disabled`、`expanded`
17
+ * - ref 索引:如 `#5`
18
+ *
19
+ * @example
20
+ * searchA11yTree('button') // 找全部按钮
21
+ * searchA11yTree('提交') // 找名称含"提交"的节点
22
+ * searchA11yTree('#3') // 找 ref #3
23
+ */
24
+ export function searchA11yTree(
25
+ query: string,
26
+ root: Element = document.body,
27
+ blacklist: Element[] = [],
28
+ whitelist: Element[] = [],
29
+ options?: SearchA11yTreeOptions,
30
+ ): SearchA11yTreeResult {
31
+ const {
32
+ contextLines = 2,
33
+ caseInsensitive = true,
34
+ maxMatches = 20,
35
+ ...treeOptions
36
+ } = options ?? {}
37
+
38
+ const safeContextLines = Math.max(0, contextLines)
39
+ const safeMaxMatches = Math.max(1, maxMatches)
40
+
41
+ // 复用 buildA11yTree 生成完整树,直接取 lines 数组(不重复构建 DOM 遍历)
42
+ const { lines, refMap, yaml } = buildA11yTree(root, blacklist, whitelist, treeOptions)
43
+
44
+ const needle = caseInsensitive ? query.toLowerCase() : query
45
+ const totalLines = lines.length
46
+
47
+ const isRefQuery = /^#\d+$/.test(query)
48
+ const refRegex = isRefQuery ? new RegExp(`\\s${query}(?:\\s|[\\[]|$)`) : null
49
+
50
+ // 找出所有命中行的下标(0-based)
51
+ const hitIndices: number[] = []
52
+ for (let i = 0; i < lines.length; i++) {
53
+ let matched = false
54
+ if (refRegex) {
55
+ matched = refRegex.test(lines[i])
56
+ } else {
57
+ const haystack = caseInsensitive ? lines[i].toLowerCase() : lines[i]
58
+ matched = haystack.includes(needle)
59
+ }
60
+ if (matched) {
61
+ hitIndices.push(i)
62
+ }
63
+ }
64
+
65
+ // 合并重叠的上下文区间,避免重复输出行
66
+ const mergedRanges: Array<{ start: number; end: number; hits: number[] }> = []
67
+ let isTruncated = false
68
+ for (const idx of hitIndices) {
69
+ const start = Math.max(0, idx - safeContextLines)
70
+ const end = Math.min(totalLines - 1, idx + safeContextLines)
71
+ const last = mergedRanges[mergedRanges.length - 1]
72
+ if (last && start <= last.end + 1) {
73
+ // 区间重叠或紧邻,合并
74
+ last.end = Math.max(last.end, end)
75
+ last.hits.push(idx)
76
+ } else {
77
+ if (mergedRanges.length >= safeMaxMatches) {
78
+ isTruncated = true
79
+ break
80
+ }
81
+ mergedRanges.push({ start, end, hits: [idx] })
82
+ }
83
+ }
84
+
85
+ // 构建结构化结果
86
+ const matches: A11ySearchMatch[] = mergedRanges.map((range) => ({
87
+ lineNumber: range.hits[0] + 1,
88
+ line: lines[range.hits[0]],
89
+ context: Array.from({ length: range.end - range.start + 1 }, (_, k) => ({
90
+ lineNumber: range.start + k + 1,
91
+ line: lines[range.start + k],
92
+ })),
93
+ }))
94
+
95
+ // 格式化为可读文本(模仿 grep -n -C 风格,便于 LLM 直接理解)
96
+ const textParts: string[] = [
97
+ `无障碍树搜索结果 — 关键词: "${query}" | 总行数: ${totalLines} | 命中: ${hitIndices.length} 行 | 返回分组: ${matches.length}`,
98
+ '',
99
+ ]
100
+
101
+ if (matches.length === 0) {
102
+ textParts.push('(未找到匹配项)')
103
+ } else {
104
+ matches.forEach((match, m) => {
105
+ const range = mergedRanges[m]
106
+ textParts.push(`── 分组 ${m + 1}(第 ${range.start + 1}–${range.end + 1} 行)──`)
107
+ match.context.forEach(({ lineNumber, line }) => {
108
+ const isHit = range.hits.includes(lineNumber - 1)
109
+ const ln = String(lineNumber).padStart(4)
110
+ // 命中行用 >>> 标记,上下文行用普通行号前缀
111
+ textParts.push(isHit ? `>>>${ln} | ${line}` : ` ${ln} | ${line}`)
112
+ })
113
+ textParts.push('')
114
+ })
115
+ if (isTruncated) {
116
+ textParts.push(`⚠️ 命中过多,已截断至前 ${safeMaxMatches} 个分组,建议缩小搜索范围`)
117
+ }
118
+ textParts.push(`提示:如需操作命中元素,使用其 #N 索引;如需查看完整树,请使用 browserState。`)
119
+ }
120
+
121
+ return {
122
+ text: textParts.join('\n'),
123
+ matches,
124
+ totalLines,
125
+ matchCount: hitIndices.length,
126
+ refMap,
127
+ yaml,
128
+ }
129
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * a11y/types.ts
3
+ *
4
+ * 定义无障碍树模块(A11y Tree)的核心接口和类型。
5
+ */
6
+
7
+ /** ref 索引 → HTMLElement 映射,供 click/fill/select 操作使用 */
8
+ export type RefMap = Map<number, HTMLElement>
9
+
10
+ /** 内部中间态节点,与 DOM 解耦,便于剪枝和序列化 */
11
+ export interface VNode {
12
+ /** 节点的 ARIA 角色 (如 'button', 'link', 'textbox') */
13
+ role: string
14
+ /** W3C AccName 算法计算出的语义化名称 */
15
+ name: string
16
+ /** 元素相关的状态和属性标记 (如 'checked', 'disabled', 'value="xxx"') */
17
+ tokens: string[]
18
+ /** 只有交互节点才有 ref (内部索引,用于标识可在其上执行交互动作的元素) */
19
+ ref?: number
20
+ /** 关联的 DOM 元素实例 */
21
+ el: HTMLElement
22
+ /** 包含的子节点列表 */
23
+ children: VNode[]
24
+ }
25
+
26
+ /** 构建无障碍树时的配置选项 */
27
+ export interface A11yTreeOptions {
28
+ /**
29
+ * 是否启用剪枝:无 ref 且无 accessible name 的节点透明穿透
30
+ * 默认 true(推荐)
31
+ */
32
+ pruneUnnamed?: boolean
33
+ /**
34
+ * 强制保留的角色列表,即使无 name 也不穿透(优先级最高)
35
+ * 例如:['table', 'row'] 用于保留表格结构
36
+ */
37
+ preserveRoles?: string[]
38
+ /**
39
+ * 允许在无障碍树节点中作为 token 额外输出的 DOM 属性白名单
40
+ * 包含这些属性的节点会被自动视为需要暴露/交互的节点(分配 ref 并保留),
41
+ * 且属性及其值会显示在节点的 token 列表中,如 [cf-uba="cloudShell"]
42
+ */
43
+ exposedAttributes?: string[]
44
+ /**
45
+ * 校验错误元素 CSS 选择器(逗号分隔或选择器数组),用于在 token 中标记 [error]
46
+ * 默认覆盖 ARIA 标准 + 主流 UI 框架
47
+ */
48
+ errorSelectors?: string | string[]
49
+ /**
50
+ * 校验警告元素 CSS 选择器(逗号分隔或选择器数组)
51
+ */
52
+ warningSelectors?: string | string[]
53
+ }
54
+
55
+ /** 构建无障碍树的返回结果 */
56
+ export interface A11yTreeResult {
57
+ /** 语义化 YAML 文本(供 AI 阅读和 Diff 计算) */
58
+ yaml: string
59
+ /** ref 索引 → HTMLElement 映射(供后续操作使用) */
60
+ refMap: RefMap
61
+ /** 可交互元素总数 */
62
+ interactiveCount: number
63
+ /** 原始行数组(不含 yaml 代码块包裹),供搜索使用 */
64
+ lines: string[]
65
+ }
66
+
67
+ /** 关键词搜索选项 */
68
+ export interface SearchA11yTreeOptions extends A11yTreeOptions {
69
+ /**
70
+ * 每个匹配行前后保留的上下文行数(类似 grep -C N)
71
+ * 默认 2
72
+ */
73
+ contextLines?: number
74
+ /**
75
+ * 是否大小写不敏感,默认 true
76
+ */
77
+ caseInsensitive?: boolean
78
+ /**
79
+ * 最大返回匹配分组数(防止命中过多撑爆上下文),默认 20
80
+ */
81
+ maxMatches?: number
82
+ }
83
+
84
+ /** 单个搜索匹配的分组结构 */
85
+ export interface A11ySearchMatch {
86
+ /** 主命中行行号(1-based) */
87
+ lineNumber: number
88
+ /** 主命中行内容 */
89
+ line: string
90
+ /** 含上下文的行列表(带行号) */
91
+ context: Array<{ lineNumber: number; line: string }>
92
+ }
93
+
94
+ /** searchA11yTree 函数的返回结果 */
95
+ export interface SearchA11yTreeResult {
96
+ /** 格式化后可直接发给 LLM 的文本 */
97
+ text: string
98
+ /** 结构化匹配列表 */
99
+ matches: A11ySearchMatch[]
100
+ /** 无障碍树总行数 */
101
+ totalLines: number
102
+ /** 原始命中行数(去重前) */
103
+ matchCount: number
104
+ /** 返回搜索时的 ref 映射 */
105
+ refMap: RefMap
106
+ /** 返回搜索时的 yaml 状态,用于更新缓存 */
107
+ yaml: string
108
+ }