agentpage 0.0.54 → 0.0.56

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/README.md CHANGED
@@ -79,7 +79,8 @@ npm install agentpage
79
79
 
80
80
  - **Prompt + Tools + 路由三层解耦**:可以快速把"可执行 AI 能力"植入现有前端系统,按路由渐进式接入,支持"项目级工具 + 路由级工具"组合。
81
81
  - **增量任务消费协议(REMAINING)**:任务不是一次性执行,而是逐轮消费收敛。每轮只做当前快照可执行的动作,通过 `REMAINING` 协议跟踪进度,支持协议修复和启发式回退,确保复杂多步任务稳定收敛。
82
- - **9 层保护机制**:冗余拦截、快照防抖、元素恢复、Not-found 重试对话流、导航刷新、空转检测、重复批次防自转、协议修复、快照指纹变化检测 —— 目标是**稳定收敛**,而不是偶然成功。
82
+ - **原始目标锚定(Original Goal Anchor)**:Round 1+ 每轮消息注入用户原始输入作为任务对照组,防止模型在多步执行过程中偏航(如把“去 X 仓库创建 issue”误解为“创建 X 仓库”)。
83
+ - **12 层保护机制**:冗余拦截、快照防抖、元素恢复、Not-found 重试对话流、导航刷新、空转检测、重复批次防自转、协议修复、快照指纹变化检测、无效点击拦截与循环检测、附近可点击元素推荐、原始目标锚定 —— 目标是**稳定收敛**,而不是偶然成功。
83
84
  - **Playwright 级别交互语义**:完整 pointer/mouse 事件链、4 种 scrollIntoView 策略轮换、actionability 五重检查(可见/稳定/可用/可编辑/遮挡)、智能重定向 retarget、隐藏控件代理点击(ElementPlus/AntD)、`select_option` value/label/index 三策略。
84
85
  - **运行时事件信号追踪**:通过 `EventTarget.prototype` 补丁全局追踪事件绑定,快照中输出 `listeners="clk,inp,chg"` 信号,帮助 AI 精准识别真实可交互元素,而非猜测。
85
86
  - **效果验证机制(Effect Check)**:每轮行动前自动检查上轮操作是否在当前快照中生效,未生效则尝试邻近元素,避免重复点击无效目标。
@@ -233,6 +234,9 @@ Round 3: 执行 C → REMAINING: DONE
233
234
  | 协议修复回合 | remaining 未完成却无工具调用 | 注入强约束提示 |
234
235
  | 轮次稳定等待 | 本轮有 DOM 变化动作 | loading hidden + DOM quiet(200ms/4s) |
235
236
  | 快照指纹变化检测 | 本轮有 DOM 变更动作且行动后指纹不变 | 注入 `Snapshot unchanged` 提示,强制模型换目标 |
237
+ | 无效点击拦截与循环检测 | 快照未变时记录无效 click;近 4 轮在 ≤2 个目标间循环 | 拦截重复无效点击;循环检测后将所有循环目标加入拦截集 |
238
+ | 附近可点击元素推荐 | 点击被拦截或证实无效 | 从快照中查找目标附近 15 行内有点击信号的元素,按距离推荐 |
239
+ | 原始目标锚定 | Round 1+ 每轮 | 注入用户原始任务作为对照组,防止多步执行中偏航 |
236
240
 
237
241
  ### 5. 停机条件
238
242
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/core/tool-registry.ts","../src/core/types.ts","../src/core/agent-loop/types.ts","../src/web/ref-store.ts","../src/web/tools/page-info-tool.ts","../src/web/ui/panel.ts","../src/web/tools/dom-tool.ts","../src/web/tools/navigate-tool.ts","../src/web/tools/wait-tool.ts","../src/web/tools/evaluate-tool.ts","../src/web/messaging.ts","../src/web/index.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/core/tool-registry.ts","../src/core/types.ts","../src/core/agent-loop/types.ts","../src/web/ref-store.ts","../src/web/tools/page-info-tool.ts","../src/web/ui/panel.ts","../src/web/tools/dom-tool.ts","../src/web/tools/navigate-tool.ts","../src/web/tools/wait-tool.ts","../src/web/tools/evaluate-tool.ts","../src/web/messaging.ts","../src/web/index.ts"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -7,13 +7,32 @@ import { computePosition, flip, offset, shift } from "@floating-ui/dom";
7
7
  *
8
8
  * 统一集中在该文件,避免在主循环中散落“魔法数字”。
9
9
  */
10
+ /** 单次 chat 最大循环轮次(超过后强制停机) */
10
11
  const DEFAULT_MAX_ROUNDS = 40;
12
+ /** 元素未找到恢复时的等待时长(毫秒),等待后刷新快照重新定位目标 */
11
13
  const DEFAULT_RECOVERY_WAIT_MS = 100;
14
+ /** 同一工具调用(相同 name + input)命中元素未找到时的最大自动恢复轮次 */
12
15
  const DEFAULT_ACTION_RECOVERY_ROUNDS = 2;
16
+ /** 元素未找到重试对话流的最大尝试次数(聚合失败工具 + 快照 + attempt 标注发给 AI) */
13
17
  const DEFAULT_NOT_FOUND_RETRY_ROUNDS = 2;
18
+ /** 元素未找到重试对话流中每次重试前的等待时长(毫秒),等待页面异步渲染完成 */
14
19
  const DEFAULT_NOT_FOUND_RETRY_WAIT_MS = 1e3;
20
+ /** 轮次后稳定等待的总超时(毫秒):包含 loading 隐藏等待 + DOM 静默等待的总上限 */
15
21
  const DEFAULT_ROUND_STABILITY_WAIT_TIMEOUT_MS = 4e3;
22
+ /** 轮次后 DOM 静默窗口(毫秒):DOM 在此时间内无变化视为稳定 */
16
23
  const DEFAULT_ROUND_STABILITY_WAIT_QUIET_MS = 200;
24
+ /**
25
+ * 轮次后稳定等待的默认 loading 指示器选择器列表。
26
+ *
27
+ * 覆盖主流 UI 框架的加载态组件:
28
+ * - AntD:`.ant-spin` / `.ant-spin-spinning` / `.ant-skeleton`
29
+ * - Element Plus:`.el-loading-mask`
30
+ * - BK(蓝鲸):`.bk-loading` / `.bk-spin-loading` / `.bk-skeleton` / `.bk-sideslider-loading`
31
+ * - TDesign(TD):`.t-loading` / `.t-skeleton` / `.t-skeleton__row`
32
+ * - 通用:`[aria-busy="true"]` / `.skeleton` / `.loading`
33
+ *
34
+ * 用户自定义的 `roundStabilityWait.loadingSelectors` 会与此列表合并去重,不会覆盖默认值。
35
+ */
17
36
  const DEFAULT_ROUND_STABILITY_WAIT_LOADING_SELECTORS = [
18
37
  ".ant-spin",
19
38
  ".ant-spin-spinning",
@@ -30,11 +49,11 @@ const DEFAULT_ROUND_STABILITY_WAIT_LOADING_SELECTORS = [
30
49
  ".skeleton",
31
50
  ".loading"
32
51
  ];
33
- /** 快照起始标记 — 用于在消息中识别快照边界 */
52
+ /** 快照起始标记 — 用于在消息中定位快照边界,配合 stripSnapshotFromPrompt() 实现旧快照剥离 */
34
53
  const SNAPSHOT_START = "<!-- SNAPSHOT_START -->";
35
- /** 快照结束标记 */
54
+ /** 快照结束标记 — 与 SNAPSHOT_START 配对使用 */
36
55
  const SNAPSHOT_END = "<!-- SNAPSHOT_END -->";
37
- /** 旧快照被替换后的占位文本 */
56
+ /** 旧快照被替换后的占位文本 — 防止过期快照干扰模型决策 */
38
57
  const SNAPSHOT_OUTDATED = "[此快照已过期,请参考对话中最新的快照]";
39
58
 
40
59
  //#endregion
@@ -43,6 +62,12 @@ const SNAPSHOT_OUTDATED = "[此快照已过期,请参考对话中最新的快
43
62
  * 异步睡眠。
44
63
  *
45
64
  * 用于重试等待、节流等待等场景。
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * await sleep(1000); // 等待 1 秒
69
+ * await sleep(100); // 元素恢复前等待 100ms
70
+ * ```
46
71
  */
47
72
  function sleep$1(ms) {
48
73
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -52,6 +77,12 @@ function sleep$1(ms) {
52
77
  *
53
78
  * 工具返回 content 可能是 string 或 object;这里统一转成 string,
54
79
  * 便于日志、错误判定、摘要拼接。
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * toContentString("已点击按钮") // → "已点击按钮"
84
+ * toContentString({ code: "OK", n: 1 }) // → '{\n "code": "OK",\n "n": 1\n}'
85
+ * ```
55
86
  */
56
87
  function toContentString(content) {
57
88
  return typeof content === "string" ? content : JSON.stringify(content, null, 2);
@@ -62,6 +93,15 @@ function toContentString(content) {
62
93
  * 约定格式:`SNAPSHOT_HINT: EXPAND_CHILDREN #ref1 #ref2`
63
94
  *
64
95
  * 返回:去掉 `#` 前缀后的 ref id 列表。
96
+ *
97
+ * @example
98
+ * ```ts
99
+ * parseSnapshotExpandHints("SNAPSHOT_HINT: EXPAND_CHILDREN #a1b2c #x9k3d")
100
+ * // → ["a1b2c", "x9k3d"]
101
+ *
102
+ * parseSnapshotExpandHints("REMAINING: DONE")
103
+ * // → [](无匹配)
104
+ * ```
65
105
  */
66
106
  function parseSnapshotExpandHints(text) {
67
107
  if (!text) return [];
@@ -78,8 +118,14 @@ function parseSnapshotExpandHints(text) {
78
118
  * 提取 hash selector 的 ref。
79
119
  *
80
120
  * 仅处理“纯 hash 选择器”,例如 `#1rv01x`。
81
- * 如果是复杂 CSS(如 `.x #id`)会返回 null,避免误判。
82
- */
121
+ * 如果是复杂 CSS(如 `.x #id`)会返回 null,避免误判。 *
122
+ * @example
123
+ * ```ts
124
+ * extractHashSelectorRef({ selector: "#1rv01x" }) // → "1rv01x"
125
+ * extractHashSelectorRef({ selector: ".btn #id" }) // → null(复杂选择器)
126
+ * extractHashSelectorRef({ selector: "div" }) // → null(非 hash)
127
+ * extractHashSelectorRef({}) // → null
128
+ * ``` */
83
129
  function extractHashSelectorRef(toolInput) {
84
130
  if (!toolInput || typeof toolInput !== "object") return null;
85
131
  const selector = toolInput.selector;
@@ -95,6 +141,16 @@ function extractHashSelectorRef(toolInput) {
95
141
  * 再计算 djb2 哈希,确保指纹只反映真实页面结构和文本差异。
96
142
  *
97
143
  * 用途:轮次行动前后各算一次指纹,若一致说明操作未产生任何可见效果。
144
+ *
145
+ * @example
146
+ * ```ts
147
+ * const before = computeSnapshotFingerprint('[button] "提交" #a1b2c');
148
+ * const after = computeSnapshotFingerprint('[button] "提交" #x9y8z');
149
+ * before === after // → true(内容相同,仅 hashID 变化)
150
+ *
151
+ * const changed = computeSnapshotFingerprint('[button] "已提交" #a1b2c');
152
+ * before === changed // → false(文本变化 → 指纹不同)
153
+ * ```
98
154
  */
99
155
  function computeSnapshotFingerprint(snapshot) {
100
156
  if (!snapshot) return "";
@@ -129,6 +185,25 @@ function _djb2(str) {
129
185
  * - `INEFFECTIVE_CLICK_BLOCKED` 拦截消息中附带推荐
130
186
  * - "Snapshot unchanged" 提示中附带推荐
131
187
  * - 交替循环检测提示中附带推荐
188
+ *
189
+ * @example
190
+ * ```ts
191
+ * // 假设快照片段:
192
+ * // [tr] listeners="clk" #14d1zek
193
+ * // [td]
194
+ * // [span] "forkCte" listeners="blr,fcs" #fkbidm
195
+ * // [td]
196
+ * // [a] "admin/forkCte" href="/repo/1" listeners="clk" #c3hyqd
197
+ *
198
+ * findNearbyClickTargets(snapshot, "#fkbidm")
199
+ * // → [
200
+ * // '#c3hyqd ([a] "admin/forkCte" listeners="clk")', // 距离近
201
+ * // '#14d1zek ([tr] "" listeners="clk")', // 距离稍远
202
+ * // ]
203
+ *
204
+ * findNearbyClickTargets(snapshot, "#fkbidm", new Set(["#14d1zek"]))
205
+ * // → ['#c3hyqd ([a] "admin/forkCte" listeners="clk")'] // 排除 #14d1zek
206
+ * ```
132
207
  */
133
208
  function findNearbyClickTargets(snapshot, selector, excludeSelectors, windowSize = 15) {
134
209
  if (!snapshot || !selector) return [];
@@ -183,8 +258,18 @@ function findNearbyClickTargets(snapshot, selector, excludeSelectors, windowSize
183
258
  * 构建任务数组。
184
259
  *
185
260
  * 作用:把一轮工具调用规整成稳定字符串数组,
186
- * 用于“上一轮任务回显”和“重复批次检测”。
187
- */
261
+ * 用于“上一轮任务回显”和“重复批次检测”。 *
262
+ * @example
263
+ * ```ts
264
+ * buildTaskArray([
265
+ * { name: "dom", input: { action: "click", selector: "#a1b2c" } },
266
+ * { name: "dom", input: { action: "fill", selector: "#x9k3d", value: "hello" } },
267
+ * ])
268
+ * // → [
269
+ * // 'dom:{"action":"click","selector":"#a1b2c"}',
270
+ * // 'dom:{"action":"fill","selector":"#x9k3d","value":"hello"}',
271
+ * // ]
272
+ * ``` */
188
273
  function buildTaskArray(toolCalls) {
189
274
  return toolCalls.map((tc) => `${tc.name}:${JSON.stringify(tc.input)}`);
190
275
  }
@@ -193,8 +278,17 @@ function buildTaskArray(toolCalls) {
193
278
  *
194
279
  * 优先保留 REMAINING;否则保留首段摘要,避免长文本污染上下文。
195
280
  *
196
- * 返回字符串会被注入下一轮消息,作为“上一轮模型输出摘要”。
197
- */
281
+ * 返回字符串会被注入下一轮消息,作为“上一轮模型输出摘要”。 *
282
+ * @example
283
+ * ```ts
284
+ * normalizeModelOutput("操作完成\nREMAINING: 填写表单")
285
+ * // → "REMAINING: 填写表单"
286
+ *
287
+ * normalizeModelOutput("已点击按钮,等待页面跳转...")
288
+ * // → "已点击按钮,等待页面跳转..."(首段摘要,最多 220 字符)
289
+ *
290
+ * normalizeModelOutput(undefined) // → ""
291
+ * ``` */
198
292
  function normalizeModelOutput(text) {
199
293
  if (!text) return "";
200
294
  const trimmed = text.trim();
@@ -218,6 +312,21 @@ function normalizeModelOutput(text) {
218
312
  * - `REMAINING: DONE` → 返回 `""`(任务完成)
219
313
  * - `REMAINING: <text>` → 返回 `<text>`
220
314
  * - DONE 后面尾随的摘要文本会被忽略(模型常在 DONE 后附加总结)
315
+ *
316
+ * @example
317
+ * ```ts
318
+ * parseRemainingInstruction("REMAINING: 填写表单并提交")
319
+ * // → "填写表单并提交"
320
+ *
321
+ * parseRemainingInstruction("REMAINING: DONE")
322
+ * // → ""(任务完成)
323
+ *
324
+ * parseRemainingInstruction("REMAINING: DONE - 已完成所有操作")
325
+ * // → ""(DONE 后的摘要被忽略)
326
+ *
327
+ * parseRemainingInstruction("我已经点击了按钮")
328
+ * // → null(无 REMAINING 协议)
329
+ * ```
221
330
  */
222
331
  function parseRemainingInstruction(text) {
223
332
  if (!text) return null;
@@ -238,6 +347,18 @@ function parseRemainingInstruction(text) {
238
347
  * 策略:
239
348
  * - 有 REMAINING 协议 -> 使用模型给出的 nextInstruction
240
349
  * - 无协议 -> 保持 currentInstruction 不变(由上层决定是否启发式推进)
350
+ *
351
+ * @example
352
+ * ```ts
353
+ * deriveNextInstruction("REMAINING: 提交表单", "填写表单并提交")
354
+ * // → { nextInstruction: "提交表单", hasRemainingProtocol: true }
355
+ *
356
+ * deriveNextInstruction("REMAINING: DONE", "提交表单")
357
+ * // → { nextInstruction: "", hasRemainingProtocol: true }
358
+ *
359
+ * deriveNextInstruction("已点击按钮", "填写表单并提交")
360
+ * // → { nextInstruction: "填写表单并提交", hasRemainingProtocol: false }
361
+ * ```
241
362
  */
242
363
  function deriveNextInstruction(text, currentInstruction) {
243
364
  const parsed = parseRemainingInstruction(text);
@@ -255,8 +376,21 @@ function deriveNextInstruction(text, currentInstruction) {
255
376
  *
256
377
  * 用于协议缺失但本轮有执行动作时,按线性步骤剔除已执行数量。
257
378
  *
258
- * 这是“保守推进”策略,不保证语义完美,但能避免 remaining 长期不变。
259
- */
379
+ * 这是“保守推进”策略,不保证语义完美,但能避免 remaining 长期不变。 *
380
+ * @example
381
+ * ```ts
382
+ * reduceRemainingHeuristically("点击按钮 然后 填写表单 然后 提交", 1)
383
+ * // → "填写表单 -> 提交"(剔除第 1 步)
384
+ *
385
+ * reduceRemainingHeuristically("点击按钮 然后 填写表单 然后 提交", 2)
386
+ * // → "提交"(剔除前 2 步)
387
+ *
388
+ * reduceRemainingHeuristically("点击按钮 然后 填写表单 然后 提交", 5)
389
+ * // → ""(所有步骤已完成)
390
+ *
391
+ * reduceRemainingHeuristically("完成任务", 1)
392
+ * // → "完成任务"(无法拆分,原样返回)
393
+ * ``` */
260
394
  function reduceRemainingHeuristically(currentInstruction, executedCount) {
261
395
  if (!currentInstruction.trim() || executedCount <= 0) return currentInstruction;
262
396
  const parts = currentInstruction.replace(/\s+/g, " ").replace(/(->|=>|→)/g, " 然后 ").replace(/[,,。;;]/g, " 然后 ").split(/\s*(?:然后|再|并且|并|接着|随后|之后)\s*/g).map((part) => part.trim()).filter(Boolean);
@@ -272,9 +406,20 @@ function reduceRemainingHeuristically(currentInstruction, executedCount) {
272
406
  *
273
407
  * 当前规则:
274
408
  * - `navigate.*` 一律断轮
409
+ * - `dom.click` 断轮
275
410
  * - `dom.press` 仅 Enter 断轮
276
411
  * - `evaluate` 断轮
277
412
  * - 其他动作默认不断轮
413
+ *
414
+ * @example
415
+ * ```ts
416
+ * shouldForceRoundBreak("dom", { action: "click", selector: "#btn" }) // → true
417
+ * shouldForceRoundBreak("dom", { action: "fill", selector: "#in" }) // → false
418
+ * shouldForceRoundBreak("dom", { action: "press", key: "Enter" }) // → true
419
+ * shouldForceRoundBreak("dom", { action: "press", key: "Tab" }) // → false
420
+ * shouldForceRoundBreak("navigate", { action: "goto", url: "/home" }) // → true
421
+ * shouldForceRoundBreak("evaluate", { expression: "alert(1)" }) // → true
422
+ * ```
278
423
  */
279
424
  function shouldForceRoundBreak(toolName, toolInput) {
280
425
  const action = getToolAction(toolInput);
@@ -287,11 +432,20 @@ function shouldForceRoundBreak(toolName, toolInput) {
287
432
  return toolName === "evaluate";
288
433
  }
289
434
  /**
290
- * 判定动作是否可能引发页面结构或状态变化。
435
+ * 判定动作是否可能引发页面结构或状态变化(宽泛判定)。
291
436
  *
292
- * 用于“轮次后稳定等待”触发条件:
437
+ * 用于"轮次后稳定等待"触发条件:
293
438
  * - 命中 true:本轮结束后执行加载态 + DOM 静默双重等待
294
439
  * - 命中 false:跳过等待,直接进入下一轮
440
+ *
441
+ * @example
442
+ * ```ts
443
+ * isPotentialDomMutation("dom", { action: "click" }) // → true
444
+ * isPotentialDomMutation("dom", { action: "fill" }) // → true
445
+ * isPotentialDomMutation("dom", { action: "get_text" }) // → false(只读)
446
+ * isPotentialDomMutation("navigate", { action: "goto" }) // → true
447
+ * isPotentialDomMutation("page_info", { action: "snapshot" }) // → false
448
+ * ```
295
449
  */
296
450
  function isPotentialDomMutation(toolName, toolInput) {
297
451
  const action = getToolAction(toolInput);
@@ -330,6 +484,18 @@ function isPotentialDomMutation(toolName, toolInput) {
330
484
  *
331
485
  * 用途:协议缺失计数重置与豁免。仅当本轮有"确定性推进"时才重置协议缺失计数器,
332
486
  * 避免模型反复点击无效目标导致死循环。
487
+ *
488
+ * @example
489
+ * ```ts
490
+ * isConfirmedProgressAction("dom", { action: "fill" }) // → true
491
+ * isConfirmedProgressAction("dom", { action: "type" }) // → true
492
+ * isConfirmedProgressAction("dom", { action: "select_option" }) // → true
493
+ * isConfirmedProgressAction("dom", { action: "press" }) // → true
494
+ * isConfirmedProgressAction("dom", { action: "click" }) // → false(不确定是否有效)
495
+ * isConfirmedProgressAction("navigate", { action: "goto" }) // → true
496
+ * isConfirmedProgressAction("my_custom_tool", { query: "..." }) // → true(自定义工具)
497
+ * isConfirmedProgressAction("page_info", { action: "snapshot" }) // → false(只读)
498
+ * ```
333
499
  */
334
500
  function isConfirmedProgressAction(toolName, toolInput) {
335
501
  if (toolName === "navigate") return true;
@@ -357,8 +523,20 @@ function isConfirmedProgressAction(toolName, toolInput) {
357
523
  * 采集找不到元素任务。
358
524
  *
359
525
  * 返回 null 表示当前结果不属于“元素未找到”,
360
- * 返回对象表示可进入 not-found retry 对话流。
361
- */
526
+ * 返回对象表示可进入 not-found retry 对话流。 *
527
+ * @example
528
+ * ```ts
529
+ * collectMissingTask("dom", { action: "click", selector: "#xyz" }, {
530
+ * content: "未找到 #xyz 对应的元素",
531
+ * details: { error: true, code: "ELEMENT_NOT_FOUND" },
532
+ * })
533
+ * // → { name: "dom", input: {...}, reason: "未找到 #xyz 对应的元素" }
534
+ *
535
+ * collectMissingTask("dom", { action: "click", selector: "#btn" }, {
536
+ * content: "已点击按钮",
537
+ * })
538
+ * // → null(操作成功,非元素未找到)
539
+ * ``` */
362
540
  function collectMissingTask(name, input, result) {
363
541
  if (!isElementNotFoundResult(result)) return null;
364
542
  return {
@@ -373,6 +551,18 @@ function collectMissingTask(name, input, result) {
373
551
  * 判定顺序:
374
552
  * 1) 优先看结构化错误码 `ELEMENT_NOT_FOUND`
375
553
  * 2) 回退看中文错误文本关键词(兼容历史结果格式)
554
+ *
555
+ * @example
556
+ * ```ts
557
+ * isElementNotFoundResult({ content: "...", details: { code: "ELEMENT_NOT_FOUND" } })
558
+ * // → true(结构化错误码命中)
559
+ *
560
+ * isElementNotFoundResult({ content: "未找到 #abc 对应的元素" })
561
+ * // → true(中文关键词回退命中)
562
+ *
563
+ * isElementNotFoundResult({ content: "已点击按钮" })
564
+ * // → false
565
+ * ```
376
566
  */
377
567
  function isElementNotFoundResult(result) {
378
568
  const details = result.details;
@@ -386,15 +576,29 @@ function isElementNotFoundResult(result) {
386
576
  * 生成稳定调用键。
387
577
  *
388
578
  * 用于 recoveryAttempts 的 map key(同名 + 同参数视为同一调用)。
579
+ *
580
+ * @example
581
+ * ```ts
582
+ * buildToolCallKey("dom", { action: "click", selector: "#a1b2c" })
583
+ * // → 'dom:{"action":"click","selector":"#a1b2c"}'
584
+ * ```
389
585
  */
390
586
  function buildToolCallKey(name, input) {
391
587
  return `${name}:${JSON.stringify(input)}`;
392
588
  }
393
589
  /**
394
590
  * 解析恢复等待时长。
395
- * 优先级:waitMs > waitSeconds > 默认值。
591
+ * 优先级:waitMs > waitSeconds > 默认值(100ms)。
396
592
  *
397
593
  * 统一返回毫秒整数,且最小为 0。
594
+ *
595
+ * @example
596
+ * ```ts
597
+ * resolveRecoveryWaitMs({ waitMs: 500 }) // → 500
598
+ * resolveRecoveryWaitMs({ waitSeconds: 2 }) // → 2000
599
+ * resolveRecoveryWaitMs({}) // → 100(DEFAULT_RECOVERY_WAIT_MS)
600
+ * resolveRecoveryWaitMs(null) // → 100
601
+ * ```
398
602
  */
399
603
  function resolveRecoveryWaitMs(input) {
400
604
  if (!input || typeof input !== "object") return DEFAULT_RECOVERY_WAIT_MS;
@@ -409,6 +613,13 @@ function resolveRecoveryWaitMs(input) {
409
613
  * 读取工具 action。
410
614
  *
411
615
  * 仅在 input 是对象且 action 为字符串时返回值,否则返回 undefined。
616
+ *
617
+ * @example
618
+ * ```ts
619
+ * getToolAction({ action: "click", selector: "#btn" }) // → "click"
620
+ * getToolAction({ selector: "#btn" }) // → undefined(无 action)
621
+ * getToolAction(null) // → undefined
622
+ * ```
412
623
  */
413
624
  function getToolAction(input) {
414
625
  if (!input || typeof input !== "object") return void 0;
@@ -419,6 +630,15 @@ function getToolAction(input) {
419
630
  * 判定错误标记。
420
631
  *
421
632
  * 约定:`result.details.error === true` 视为错误结果。
633
+ *
634
+ * @example
635
+ * ```ts
636
+ * hasToolError({ content: "...", details: { error: true, code: "ELEMENT_NOT_FOUND" } })
637
+ * // → true
638
+ *
639
+ * hasToolError({ content: "已点击按钮" })
640
+ * // → false(无 details 或 error 不为 true)
641
+ * ```
422
642
  */
423
643
  function hasToolError(result) {
424
644
  return result.details && typeof result.details === "object" ? Boolean(result.details.error) : false;
@@ -688,40 +908,6 @@ function checkRedundantSnapshot(toolName, toolInput, _latestSnapshot, round) {
688
908
  return null;
689
909
  }
690
910
  /**
691
- * 快照防抖。
692
- *
693
- * 规则:连续触发 `page_info.snapshot` 时,第 2 次起标记为冗余,
694
- * 返回 `REDUNDANT_SNAPSHOT`,提醒模型直接使用已有快照继续执行。
695
- *
696
- * 返回值:
697
- * - `result`:可能被替换成防抖后的结果
698
- * - `consecutiveCount`:更新后的连续 snapshot 计数
699
- */
700
- function applySnapshotDebounce(toolName, toolInput, result, consecutiveCount) {
701
- if (toolName === "page_info" && getToolAction(toolInput) === "snapshot") {
702
- const newCount = consecutiveCount + 1;
703
- if (newCount >= 2) return {
704
- consecutiveCount: newCount,
705
- result: {
706
- content: [toContentString(result.content), "Redundant snapshot detected. Continue with remaining actionable steps using the latest snapshot; avoid additional snapshot unless navigation or uncertainty changes."].join("\n"),
707
- details: {
708
- error: true,
709
- code: "REDUNDANT_SNAPSHOT",
710
- consecutiveSnapshotCalls: newCount
711
- }
712
- }
713
- };
714
- return {
715
- result,
716
- consecutiveCount: newCount
717
- };
718
- }
719
- return {
720
- result,
721
- consecutiveCount: 0
722
- };
723
- }
724
- /**
725
911
  * 元素未找到恢复。
726
912
  *
727
913
  * 触发条件:
@@ -930,7 +1116,6 @@ async function executeAgentLoop(params) {
930
1116
  const actionRecoveryAttempts = /* @__PURE__ */ new Map();
931
1117
  const pageContext = { latestSnapshot: initialSnapshot };
932
1118
  let finalReply = "";
933
- let consecutiveSnapshotCalls = 0;
934
1119
  let consecutiveReadOnlyRounds = 0;
935
1120
  let consecutiveNoProtocolRounds = 0;
936
1121
  let usedRounds = 0;
@@ -1163,9 +1348,6 @@ async function executeAgentLoop(params) {
1163
1348
  }
1164
1349
  callbacks?.onToolCall?.(tc.name, tc.input);
1165
1350
  let result = await registry.dispatch(tc.name, tc.input);
1166
- const debounced = applySnapshotDebounce(tc.name, tc.input, result, consecutiveSnapshotCalls);
1167
- result = debounced.result;
1168
- consecutiveSnapshotCalls = debounced.consecutiveCount;
1169
1351
  const recovered = await handleElementRecovery(tc.name, tc.input, result, actionRecoveryAttempts, registry, pageContext, callbacks);
1170
1352
  if (recovered) result = recovered;
1171
1353
  if (recovered?.details && typeof recovered.details === "object" && recovered.details.code === "ELEMENT_NOT_FOUND_RECOVERY") recoveryCount += 1;
@@ -2435,7 +2617,6 @@ function normalizeExtraInstructions(input) {
2435
2617
  * - hash ID 定位:仅交互元素携带 #hashID,非交互元素为上下文
2436
2618
  * - 事件信号:listeners="..." 标注运行时事件绑定
2437
2619
  * - 批量执行:同轮完成所有独立可见操作
2438
- * - 输入顺序:fill/type 前必须先 focus/click 同一目标
2439
2620
  * - DOM 变化断轮:会改变 DOM 的动作执行后等待下一轮新快照
2440
2621
  * - 停机规则:任务完成后输出 REMAINING: DONE
2441
2622
  *
@@ -2471,9 +2652,8 @@ function buildSystemPrompt(params = {}) {
2471
2652
  "- If the text you want to click has no click signal, look at its parent row/container or nearby sibling that does have clk listener.",
2472
2653
  "- No-effect fallback: if a click produced no page change (snapshot unchanged), do NOT repeat the same target. Instead: (1) look for <a> links or <button> inside the clicked container; (2) try a parent or sibling with stronger click signal; (3) try a completely different approach (e.g., search, filter, sidebar navigation, or use evaluate to trigger the action programmatically).",
2473
2654
  "- Batch fill/type/check/select_option freely within one round. A click always ends the round — send at most ONE click as the LAST action in a batch.",
2474
- "- Input order (MANDATORY): focus/click fill/type/select_option per target. Multi-field: focus A→fill A→focus B→fill B.",
2655
+ "- fill/type/select_option auto-focus: these actions automatically click and focus the target before input do NOT send a separate focus/click before them.",
2475
2656
  "- Search/filter inputs: after fill, press Enter (or click search button) to trigger the search. Do not assume fill alone submits.",
2476
- "- Do NOT run focus-only batches. Each focus must be immediately followed by its fill/type/select_option.",
2477
2657
  "- Steppers: compute delta from visible value, click exactly |delta| times. Check/uncheck: target real input control.",
2478
2658
  "- DOM-changing action (click/modal/navigate): ends the round, next snapshot follows. Actions sent after a click in the same batch are discarded.",
2479
2659
  "- Intermediate progress is NOT completion: if an action only opens, expands, reveals, filters, paginates, switches context, or loads the next step, keep REMAINING on the final user goal until the requested end state/value/content is visible in the snapshot.",
@@ -3045,7 +3225,7 @@ function isCandidateFillTarget(el) {
3045
3225
  if (el instanceof HTMLElement && el.isContentEditable) return true;
3046
3226
  return false;
3047
3227
  }
3048
- function executeFillOnResolvedTarget(target, value, selector, action, sourceHint) {
3228
+ async function executeFillOnResolvedTarget(target, value, selector, action, sourceHint) {
3049
3229
  if (target instanceof HTMLInputElement) {
3050
3230
  const type = target.type.toLowerCase();
3051
3231
  if (INPUT_BLOCKED_TYPES.has(type)) return {
@@ -3084,7 +3264,8 @@ function executeFillOnResolvedTarget(target, value, selector, action, sourceHint
3084
3264
  }
3085
3265
  };
3086
3266
  scrollIntoViewIfNeeded(target);
3087
- target.focus();
3267
+ dispatchClickEvents(target);
3268
+ await sleep(0);
3088
3269
  selectText(target);
3089
3270
  setNativeValue(target, value);
3090
3271
  dispatchInputEvents(target);
@@ -3102,7 +3283,8 @@ function executeFillOnResolvedTarget(target, value, selector, action, sourceHint
3102
3283
  }
3103
3284
  if (target instanceof HTMLTextAreaElement) {
3104
3285
  scrollIntoViewIfNeeded(target);
3105
- target.focus();
3286
+ dispatchClickEvents(target);
3287
+ await sleep(0);
3106
3288
  selectText(target);
3107
3289
  setNativeValue(target, value);
3108
3290
  dispatchInputEvents(target);
@@ -3124,7 +3306,8 @@ function executeFillOnResolvedTarget(target, value, selector, action, sourceHint
3124
3306
  return { content: `已填写 ${describeElement(target)}: "${value}"${suffix}` };
3125
3307
  }
3126
3308
  if (target instanceof HTMLElement && target.isContentEditable) {
3127
- target.focus();
3309
+ dispatchClickEvents(target);
3310
+ await sleep(0);
3128
3311
  selectText(target);
3129
3312
  if (value) document.execCommand("insertText", false, value);
3130
3313
  else document.execCommand("delete", false, void 0);
@@ -3183,16 +3366,17 @@ function guessNearbyFillTarget(anchor, value) {
3183
3366
  }
3184
3367
  function selectText(el) {
3185
3368
  if (el instanceof HTMLInputElement) {
3186
- el.select();
3187
3369
  el.focus();
3370
+ el.select();
3188
3371
  return;
3189
3372
  }
3190
3373
  if (el instanceof HTMLTextAreaElement) {
3374
+ el.focus();
3191
3375
  el.selectionStart = 0;
3192
3376
  el.selectionEnd = el.value.length;
3193
- el.focus();
3194
3377
  return;
3195
3378
  }
3379
+ if (el instanceof HTMLElement) el.focus();
3196
3380
  const range = document.createRange();
3197
3381
  range.selectNodeContents(el);
3198
3382
  const sel = window.getSelection();
@@ -3200,7 +3384,6 @@ function selectText(el) {
3200
3384
  sel.removeAllRanges();
3201
3385
  sel.addRange(range);
3202
3386
  }
3203
- if (el instanceof HTMLElement) el.focus();
3204
3387
  }
3205
3388
  function splitKeyCombo(key) {
3206
3389
  const tokens = key.split("+");
@@ -3528,7 +3711,7 @@ function createDomTool() {
3528
3711
  if (!Number.isFinite(numericValue)) {
3529
3712
  const guessed = guessNearbyFillTarget(target, value);
3530
3713
  if (guessed) {
3531
- const guessedResult = executeFillOnResolvedTarget(guessed, value, selector, action, "heuristic-nearby-target");
3714
+ const guessedResult = await executeFillOnResolvedTarget(guessed, value, selector, action, "heuristic-nearby-target");
3532
3715
  if (guessedResult) return guessedResult;
3533
3716
  }
3534
3717
  return {
@@ -3543,7 +3726,7 @@ function createDomTool() {
3543
3726
  }
3544
3727
  const linkedInput = findAssociatedSliderInput(target);
3545
3728
  if (linkedInput) {
3546
- const filled = executeFillOnResolvedTarget(linkedInput, String(numericValue), selector, action, `from ${describeElement(target)}`);
3729
+ const filled = await executeFillOnResolvedTarget(linkedInput, String(numericValue), selector, action, `from ${describeElement(target)}`);
3547
3730
  if (filled) return filled;
3548
3731
  }
3549
3732
  const min = Number(target.getAttribute("aria-valuemin") ?? "1");
@@ -3559,7 +3742,7 @@ function createDomTool() {
3559
3742
  }
3560
3743
  const guessed = guessNearbyFillTarget(target, String(numericValue));
3561
3744
  if (guessed) {
3562
- const guessedResult = executeFillOnResolvedTarget(guessed, String(numericValue), selector, action, "heuristic-nearby-target");
3745
+ const guessedResult = await executeFillOnResolvedTarget(guessed, String(numericValue), selector, action, "heuristic-nearby-target");
3563
3746
  if (guessedResult) return guessedResult;
3564
3747
  }
3565
3748
  return {
@@ -3572,11 +3755,11 @@ function createDomTool() {
3572
3755
  }
3573
3756
  };
3574
3757
  }
3575
- const directFilled = executeFillOnResolvedTarget(target, value, selector, action);
3758
+ const directFilled = await executeFillOnResolvedTarget(target, value, selector, action);
3576
3759
  if (directFilled) return directFilled;
3577
3760
  const guessed = guessNearbyFillTarget(target, value);
3578
3761
  if (guessed) {
3579
- const guessedResult = executeFillOnResolvedTarget(guessed, value, selector, action, "heuristic-nearby-target");
3762
+ const guessedResult = await executeFillOnResolvedTarget(guessed, value, selector, action, "heuristic-nearby-target");
3580
3763
  if (guessedResult) return guessedResult;
3581
3764
  }
3582
3765
  return {
@@ -3616,7 +3799,8 @@ function createDomTool() {
3616
3799
  dispatchClickEvents(option);
3617
3800
  return { content: `已在自定义下拉中选择 "${wanted}"` };
3618
3801
  }
3619
- target.focus();
3802
+ scrollIntoViewIfNeeded(target);
3803
+ dispatchClickEvents(target);
3620
3804
  const options = Array.from(target.options);
3621
3805
  let selected;
3622
3806
  if (value !== void 0) selected = options.find((o) => o.value === value);
@@ -3711,7 +3895,8 @@ function createDomTool() {
3711
3895
  if (value === void 0) return { content: "缺少 value 参数" };
3712
3896
  const target = retarget(el, "follow-label");
3713
3897
  scrollIntoViewIfNeeded(target);
3714
- if (target instanceof HTMLElement) target.focus();
3898
+ if (target instanceof HTMLElement) dispatchClickEvents(target);
3899
+ else if (target instanceof SVGElement) target.focus();
3715
3900
  for (const char of value) {
3716
3901
  const init = {
3717
3902
  key: char,