agentpage 0.0.34 → 0.0.36

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["sleep","sleep","sleep","convertMessages"],"sources":["../src/core/agent-loop/constants.ts","../src/core/agent-loop/helpers.ts","../src/core/agent-loop/snapshot.ts","../src/core/agent-loop/messages.ts","../src/core/agent-loop/recovery.ts","../src/core/agent-loop/index.ts","../src/core/ai-client/constants.ts","../src/core/ai-client/sse.ts","../src/core/ai-client/custom.ts","../src/core/ai-client/openai.ts","../src/core/ai-client/anthropic.ts","../src/core/ai-client/deepseek.ts","../src/core/ai-client/doubao.ts","../src/core/ai-client/qwen.ts","../src/core/ai-client/index.ts","../src/core/tool-registry.ts","../src/core/system-prompt.ts","../src/web/event-listener-tracker.ts","../src/web/tools/dom-tool.ts","../src/web/tools/page-info-tool.ts","../src/web/tools/navigate-tool.ts","../src/web/tools/wait-tool.ts","../src/web/tools/evaluate-tool.ts","../src/web/ref-store.ts","../src/web/messaging.ts","../src/web/index.ts"],"sourcesContent":["/**\n * Agent Loop 默认配置常量。\n *\n * 统一集中在该文件,避免在主循环中散落“魔法数字”。\n */\nexport const DEFAULT_MAX_ROUNDS = 40;\nexport const DEFAULT_RECOVERY_WAIT_MS = 100;\nexport const DEFAULT_ACTION_RECOVERY_ROUNDS = 2;\nexport const DEFAULT_NOT_FOUND_RETRY_ROUNDS = 2;\nexport const DEFAULT_NOT_FOUND_RETRY_WAIT_MS = 1000;\nexport const DEFAULT_ROUND_STABILITY_WAIT_TIMEOUT_MS = 4000;\nexport const DEFAULT_ROUND_STABILITY_WAIT_QUIET_MS = 200;\nexport const DEFAULT_ROUND_STABILITY_WAIT_LOADING_SELECTORS = [\n\t\".ant-spin\",\n\t\".ant-spin-spinning\",\n\t\".ant-skeleton\",\n\t\".el-loading-mask\",\n\t\".bk-loading\",\n\t\".bk-spin-loading\",\n\t\".bk-skeleton\",\n\t\".bk-sideslider-loading\",\n\t\".t-loading\",\n\t\".t-skeleton\",\n\t\".t-skeleton__row\",\n\t\"[aria-busy=\\\"true\\\"]\",\n\t\".skeleton\",\n\t\".loading\",\n];\n// ─── DOM 快照去重标记 ───\n\n/** 快照起始标记 — 用于在消息中识别快照边界 */\nexport const SNAPSHOT_START = \"<!-- SNAPSHOT_START -->\";\n/** 快照结束标记 */\nexport const SNAPSHOT_END = \"<!-- SNAPSHOT_END -->\";\n/** 旧快照被替换后的占位文本 */\nexport const SNAPSHOT_OUTDATED = \"[此快照已过期,请参考对话中最新的快照]\";","/**\n * Agent Loop 辅助函数。\n *\n * 这个文件只放“纯函数”:\n * - 不访问外部可变状态\n * - 不做网络/DOM/I/O\n * - 输入相同,输出稳定\n *\n * 目的:把 index.ts 里的协议解析、文本规整、判定逻辑拆出来,\n * 让主循环只负责编排流程,方便阅读、测试和后续扩展。\n *\n * 函数能力速览:\n * - 基础工具:\n * - `sleep`:异步等待\n * - `toContentString`:统一工具结果内容为字符串\n * - 快照相关:\n * - `parseSnapshotExpandHints`:解析 `SNAPSHOT_HINT: EXPAND_CHILDREN`\n * - `extractHashSelectorRef`:从 `#ref` 选择器提取 ref id\n * - 任务推进与协议:\n * - `buildTaskArray`:将工具调用规整成稳定任务数组\n * - `normalizeModelOutput`:压缩模型输出供下一轮上下文使用\n * - `parseRemainingInstruction`:解析 `REMAINING` 协议\n * - `deriveNextInstruction`:推导下一轮 remaining(有协议优先)\n * - `reduceRemainingHeuristically`:协议缺失时做启发式推进\n * - 执行控制:\n * - `shouldForceRoundBreak`:判断动作后是否应断轮\n * - `collectMissingTask`:提取“元素未找到”任务用于重试流\n * - 错误与参数判定:\n * - `isElementNotFoundResult`:识别元素未找到错误\n * - `buildToolCallKey`:生成稳定调用键\n * - `resolveRecoveryWaitMs`:解析恢复等待时长\n * - `getToolAction`:读取工具输入里的 action\n * - `hasToolError`:判断结果是否标记为错误\n */\nimport type { ToolCallResult } from \"../tool-registry.js\";\nimport { DEFAULT_RECOVERY_WAIT_MS } from \"./constants.js\";\n\n/**\n * 异步睡眠。\n *\n * 用于重试等待、节流等待等场景。\n */\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * 统一内容为字符串。\n *\n * 工具返回 content 可能是 string 或 object;这里统一转成 string,\n * 便于日志、错误判定、摘要拼接。\n */\nexport function toContentString(content: ToolCallResult[\"content\"]): string {\n return typeof content === \"string\" ? content : JSON.stringify(content, null, 2);\n}\n\n/**\n * 解析快照放宽提示。\n *\n * 约定格式:`SNAPSHOT_HINT: EXPAND_CHILDREN #ref1 #ref2`\n *\n * 返回:去掉 `#` 前缀后的 ref id 列表。\n */\nexport function parseSnapshotExpandHints(text: string | undefined): string[] {\n if (!text) return [];\n const refs: string[] = [];\n const regex = /^\\s*SNAPSHOT_HINT\\s*:\\s*EXPAND_CHILDREN\\s+(.+)$/gim;\n let match: RegExpExecArray | null;\n while ((match = regex.exec(text)) !== null) {\n const tail = match[1] ?? \"\";\n const tokens = tail.match(/#[A-Za-z0-9_-]+/g) ?? [];\n for (const token of tokens) refs.push(token.replace(/^#/, \"\"));\n }\n return refs;\n}\n\n/**\n * 提取 hash selector 的 ref。\n *\n * 仅处理“纯 hash 选择器”,例如 `#1rv01x`。\n * 如果是复杂 CSS(如 `.x #id`)会返回 null,避免误判。\n */\nexport function extractHashSelectorRef(toolInput: unknown): string | null {\n if (!toolInput || typeof toolInput !== \"object\") return null;\n const selector = (toolInput as { selector?: unknown }).selector;\n if (typeof selector !== \"string\") return null;\n const m = selector.trim().match(/^#([A-Za-z0-9_-]+)$/);\n return m ? m[1] : null;\n}\n\n/**\n * 构建任务数组。\n *\n * 作用:把一轮工具调用规整成稳定字符串数组,\n * 用于“上一轮任务回显”和“重复批次检测”。\n */\nexport function buildTaskArray(toolCalls: Array<{ name: string; input: unknown }>): string[] {\n return toolCalls.map(tc => `${tc.name}:${JSON.stringify(tc.input)}`);\n}\n\n/**\n * 规范化模型输出。\n *\n * 优先保留 REMAINING;否则保留首段摘要,避免长文本污染上下文。\n *\n * 返回字符串会被注入下一轮消息,作为“上一轮模型输出摘要”。\n */\nexport function normalizeModelOutput(text: string | undefined): string {\n if (!text) return \"\";\n const trimmed = text.trim();\n if (!trimmed) return \"\";\n const remainingMatch = trimmed.match(/REMAINING\\s*:\\s*([\\s\\S]*)$/i);\n if (remainingMatch) return `REMAINING: ${remainingMatch[1].trim()}`;\n const firstBlock = trimmed.split(/\\n\\s*\\n/)[0]?.trim() ?? trimmed;\n return firstBlock.slice(0, 220);\n}\n\n/**\n * 解析 REMAINING。\n *\n * 返回值:\n * - `\"\"` 表示 DONE\n * - 非空字符串表示新的 remaining\n * - `null` 表示协议缺失\n *\n * 注意:这里只负责解析,不负责 fallback 策略。\n */\nexport function parseRemainingInstruction(text: string | undefined): string | null {\n if (!text) return null;\n const match = text.match(/REMAINING\\s*:\\s*([\\s\\S]*)$/i);\n if (!match) return null;\n const value = match[1].trim();\n return /^done$/i.test(value) ? \"\" : value;\n}\n\n/**\n * 推导下一轮 remaining。\n *\n * 策略:\n * - 有 REMAINING 协议 -> 使用模型给出的 nextInstruction\n * - 无协议 -> 保持 currentInstruction 不变(由上层决定是否启发式推进)\n */\nexport function deriveNextInstruction(\n text: string | undefined,\n currentInstruction: string,\n): { nextInstruction: string; hasRemainingProtocol: boolean } {\n const parsed = parseRemainingInstruction(text);\n if (parsed !== null) {\n return { nextInstruction: parsed, hasRemainingProtocol: true };\n }\n return { nextInstruction: currentInstruction, hasRemainingProtocol: false };\n}\n\n/**\n * 启发式剔除 remaining。\n *\n * 用于协议缺失但本轮有执行动作时,按线性步骤剔除已执行数量。\n *\n * 这是“保守推进”策略,不保证语义完美,但能避免 remaining 长期不变。\n */\nexport function reduceRemainingHeuristically(\n currentInstruction: string,\n executedCount: number,\n): string {\n if (!currentInstruction.trim() || executedCount <= 0) return currentInstruction;\n\n const normalized = currentInstruction\n .replace(/\\s+/g, \" \")\n .replace(/(->|=>|→)/g, \" 然后 \")\n .replace(/[,,。;;]/g, \" 然后 \");\n\n const parts = normalized\n .split(/\\s*(?:然后|再|并且|并|接着|随后|之后)\\s*/g)\n .map(part => part.trim())\n .filter(Boolean);\n\n if (parts.length <= 1) return currentInstruction;\n\n const nextParts = parts.slice(Math.min(executedCount, parts.length));\n if (nextParts.length === 0) return \"\";\n return nextParts.join(\" -> \");\n}\n\n/**\n * 判定是否强制断轮。\n *\n * 语义:潜在 DOM 结构变化动作后,等待下一轮新快照。\n *\n * 当前规则:\n * - `navigate.*` 一律断轮\n * - `dom.press` 仅 Enter 断轮\n * - `evaluate` 断轮\n * - 其他动作默认不断轮\n */\nexport function shouldForceRoundBreak(toolName: string, toolInput: unknown): boolean {\n const action = getToolAction(toolInput);\n\n if (toolName === \"navigate\") {\n return action === \"goto\" || action === \"back\" || action === \"forward\" || action === \"reload\";\n }\n\n if (toolName === \"dom\") {\n if (action === \"press\") {\n const key = typeof toolInput === \"object\" && toolInput !== null\n ? String((toolInput as { key?: unknown; value?: unknown }).key ?? (toolInput as { value?: unknown }).value ?? \"\")\n : \"\";\n return key === \"Enter\";\n }\n return false;\n }\n\n return toolName === \"evaluate\";\n}\n\n/**\n * 判定动作是否可能引发页面结构或状态变化。\n *\n * 用于“轮次后稳定等待”触发条件:\n * - 命中 true:本轮结束后执行加载态 + DOM 静默双重等待\n * - 命中 false:跳过等待,直接进入下一轮\n */\nexport function isPotentialDomMutation(toolName: string, toolInput: unknown): boolean {\n const action = getToolAction(toolInput);\n\n if (toolName === \"navigate\") return true;\n if (toolName === \"evaluate\") return true;\n if (toolName !== \"dom\") return false;\n\n if (!action) return false;\n return [\n \"click\",\n \"fill\",\n \"select_option\",\n \"clear\",\n \"check\",\n \"uncheck\",\n \"type\",\n \"focus\",\n \"hover\",\n \"scroll\",\n \"press\",\n \"set_attr\",\n \"add_class\",\n \"remove_class\",\n ].includes(action);\n}\n\n/**\n * 采集找不到元素任务。\n *\n * 返回 null 表示当前结果不属于“元素未找到”,\n * 返回对象表示可进入 not-found retry 对话流。\n */\nexport function collectMissingTask(\n name: string,\n input: unknown,\n result: ToolCallResult,\n): { name: string; input: unknown; reason: string } | null {\n if (!isElementNotFoundResult(result)) return null;\n return {\n name,\n input,\n reason: toContentString(result.content).slice(0, 240),\n };\n}\n\n/**\n * 元素不存在判定。\n *\n * 判定顺序:\n * 1) 优先看结构化错误码 `ELEMENT_NOT_FOUND`\n * 2) 回退看中文错误文本关键词(兼容历史结果格式)\n */\nexport function isElementNotFoundResult(result: ToolCallResult): boolean {\n const details = result.details;\n if (details && typeof details === \"object\") {\n const code = (details as { code?: unknown }).code;\n if (code === \"ELEMENT_NOT_FOUND\") return true;\n }\n\n const content = toContentString(result.content);\n return content.includes(\"未找到\") && content.includes(\"元素\");\n}\n\n/**\n * 生成稳定调用键。\n *\n * 用于 recoveryAttempts 的 map key(同名 + 同参数视为同一调用)。\n */\nexport function buildToolCallKey(name: string, input: unknown): string {\n return `${name}:${JSON.stringify(input)}`;\n}\n\n/**\n * 解析恢复等待时长。\n * 优先级:waitMs > waitSeconds > 默认值。\n *\n * 统一返回毫秒整数,且最小为 0。\n */\nexport function resolveRecoveryWaitMs(input: unknown): number {\n if (!input || typeof input !== \"object\") return DEFAULT_RECOVERY_WAIT_MS;\n\n const params = input as Record<string, unknown>;\n const waitMs = params.waitMs;\n if (typeof waitMs === \"number\" && Number.isFinite(waitMs)) {\n return Math.max(0, Math.floor(waitMs));\n }\n\n const waitSeconds = params.waitSeconds;\n if (typeof waitSeconds === \"number\" && Number.isFinite(waitSeconds)) {\n return Math.max(0, Math.floor(waitSeconds * 1000));\n }\n\n return DEFAULT_RECOVERY_WAIT_MS;\n}\n\n/**\n * 读取工具 action。\n *\n * 仅在 input 是对象且 action 为字符串时返回值,否则返回 undefined。\n */\nexport function getToolAction(input: unknown): string | undefined {\n if (!input || typeof input !== \"object\") return undefined;\n const action = (input as Record<string, unknown>).action;\n return typeof action === \"string\" ? action : undefined;\n}\n\n/**\n * 判定错误标记。\n *\n * 约定:`result.details.error === true` 视为错误结果。\n */\nexport function hasToolError(result: ToolCallResult): boolean {\n return result.details && typeof result.details === \"object\"\n ? Boolean((result.details as { error?: unknown }).error)\n : false;\n}\n","/**\n * DOM 快照生命周期管理。\n *\n * 负责 4 类能力:读取、包裹、去重、剥离。\n *\n * 快照读取主流程:\n * 1) 组装快照参数(默认偏完整性)\n * 2) 调用 `page_info.snapshot`\n * 3) 将工具返回内容统一成字符串\n * 4) 由消息层进行包裹与注入\n * 5) 在多轮对话中去重旧快照\n *\n * 调用链:\n * - `agent-loop/index.ts` 在“无快照、每轮结束、导航后、恢复后”触发读取。\n * - `messages.ts` 负责把最新快照注入到本轮上下文。\n * - 本文件只处理快照文本本身,不负责业务决策与停机判定。\n *\n * 压缩/剪枝实现位置:\n * - 具体算法在 `src/web/tools/page-info-tool.ts` 的 `generateSnapshot()`。\n * - 本文件通过 `readPageSnapshot()` 传参触发这些策略,不在 core 层直接操作 DOM。\n * - 这样保持分层:core 只声明策略参数,web 负责真实遍历与裁剪。\n */\nimport { ToolRegistry } from \"../tool-registry.js\";\nimport type { AIMessage } from \"../types.js\";\nimport {\n SNAPSHOT_END,\n SNAPSHOT_OUTDATED,\n SNAPSHOT_START,\n} from \"./constants.js\";\nimport { toContentString } from \"./helpers.js\";\n\n// ─── 快照读取 ───\n\n/**\n * 读取页面 URL。\n *\n * 步骤:\n * 1) 通过 registry 分发 `page_info.get_url`。\n * 2) 若 content 为字符串则直接返回。\n * 3) 否则返回 undefined,交由上层容错。\n *\n * 输入/输出:\n * - 输入:`ToolRegistry`\n * - 输出:`string | undefined`\n * - 副作用:无本地状态写入(仅发起一次工具调用)\n */\nexport async function readPageUrl(\n registry: ToolRegistry,\n): Promise<string | undefined> {\n const result = await registry.dispatch(\"page_info\", { action: \"get_url\" });\n return typeof result.content === \"string\" ? result.content : undefined;\n}\n\n/**\n * 读取页面快照。\n *\n * 默认关闭 viewportOnly,优先完整性。\n *\n * 步骤:\n * 1) 合并调用方 options 与默认值(深度/裁剪/剪枝/节点上限等)。\n * 2) 分发 `page_info.snapshot` 获取当前 DOM 文本快照。\n * 3) 使用 `toContentString` 归一化输出,避免 provider 差异导致结构不一致。\n * 4) 返回稳定字符串给 loop,供后续注入消息与统计。\n *\n * 默认参数意图:\n * - `maxDepth=12`: 保留更深层级,减少深层组件控件被截断。\n * - `viewportOnly=false`: 优先完整性,避免误判“元素不存在”。\n * - `pruneLayout=true`: 抑制纯布局噪声,降低 token 压力。\n * - `maxNodes=500` / `maxChildren=30`: 控制体积上限,兼顾可读性。\n * - `maxTextLength=40`: 防止长文本淹没结构信息。\n *\n * 压缩/剪枝是怎么做的:\n * - `viewportOnly=true` 时:仅保留与视口相交元素(根层容器保留),完全视口外元素跳过。\n * - `pruneLayout=true` 时:无 id/无语义/无交互/无直接文本的布局容器会被“折叠”,\n * 子节点直接提升输出,减少无意义层级;当同一折叠容器提升出多个相邻节点时,\n * 快照会用括号分组块标记其关联来源(collapsed-group)。\n * - `maxNodes`:全局节点预算,超限后停止继续遍历并追加 truncation 提示。\n * - `maxChildren`:每个父节点只保留前 N 个子元素,其余用 `... (n children omitted)` 汇总。\n * - `maxTextLength`:节点文本按长度截断,避免长段文案占满上下文。\n * - 交互优先排序:优先输出按钮/输入框/链接等交互元素,再输出普通元素。\n * - 属性压缩:仅保留关键属性(如 id、关键 class、交互属性、布尔状态、val),减少冗余 token。\n *\n * 输入/输出:\n * - 输入:`ToolRegistry` + 可选快照参数\n * - 输出:归一化后的快照字符串(始终 string)\n * - 副作用:无本地状态写入;仅依赖工具调用结果\n */\nexport async function readPageSnapshot(\n registry: ToolRegistry,\n options?: {\n maxDepth?: number;\n viewportOnly?: boolean;\n pruneLayout?: boolean;\n maxNodes?: number;\n maxChildren?: number;\n maxTextLength?: number;\n expandOptionLists?: boolean;\n expandChildrenRefs?: string[];\n expandedChildrenLimit?: number;\n },\n): Promise<string> {\n const result = await registry.dispatch(\"page_info\", {\n action: \"snapshot\",\n maxDepth: options?.maxDepth ?? 12,\n viewportOnly: options?.viewportOnly ?? false,\n pruneLayout: options?.pruneLayout ?? true,\n maxNodes: options?.maxNodes ?? 500,\n maxChildren: options?.maxChildren ?? 30,\n maxTextLength: options?.maxTextLength ?? 40,\n expandOptionLists: options?.expandOptionLists,\n expandChildrenRefs: options?.expandChildrenRefs,\n expandedChildrenLimit: options?.expandedChildrenLimit,\n });\n return toContentString(result.content);\n}\n\n// ─── 快照标记 ───\n\n/**\n * 包裹快照。\n *\n * 作用:\n * - 为快照加 `SNAPSHOT_START/END` 边界,便于后续正则定位。\n * - 支持去重与旧快照剥离,防止多轮 token 累积。\n * - 仅做纯字符串变换,不访问外部状态。\n */\nexport function wrapSnapshot(snapshot: string): string {\n return `${SNAPSHOT_START}\\n${snapshot}\\n${SNAPSHOT_END}`;\n}\n\n// ─── 快照去重 ───\n\n/** 转义正则字符。 */\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/** 快照块匹配正则。 */\nconst SNAPSHOT_REGEX = new RegExp(\n `${escapeRegex(SNAPSHOT_START)}[\\\\s\\\\S]*?${escapeRegex(SNAPSHOT_END)}`,\n \"g\",\n);\n\n/** 是否包含快照标记。 */\nfunction containsSnapshot(text: string): boolean {\n return text.includes(SNAPSHOT_START) && text.includes(SNAPSHOT_END);\n}\n\n/**\n * 去重消息快照。\n * 仅保留最后一份快照,旧快照替换为过期提示。\n *\n * 步骤:\n * 1) 扫描 tool 消息中的快照块引用。\n * 2) 保留最后一次快照,视为当前事实来源。\n * 3) 将更早快照替换为 `SNAPSHOT_OUTDATED`,避免模型引用旧状态。\n *\n * 返回语义:\n * - `true`: 至少发现了 1 份快照(可能发生替换,也可能只有一份无需替换)。\n * - `false`: 未发现任何快照标记。\n */\nexport function deduplicateSnapshots(messages: AIMessage[]): boolean {\n type SnapshotRef = {\n items: Array<{ toolCallId: string; result: string }>;\n index: number;\n };\n const refs: SnapshotRef[] = [];\n\n for (const msg of messages) {\n if (msg.role !== \"tool\" || !Array.isArray(msg.content)) continue;\n const items = msg.content as Array<{ toolCallId: string; result: string }>;\n for (let j = 0; j < items.length; j++) {\n if (typeof items[j].result === \"string\" && containsSnapshot(items[j].result)) {\n refs.push({ items, index: j });\n }\n }\n }\n\n if (refs.length <= 1) return refs.length > 0;\n\n // 保留最后一份快照,将更早的快照替换为过期提示\n for (let i = 0; i < refs.length - 1; i++) {\n const ref = refs[i];\n ref.items[ref.index].result = ref.items[ref.index].result.replace(\n SNAPSHOT_REGEX,\n SNAPSHOT_OUTDATED,\n );\n }\n\n return true;\n}\n\n/**\n * 剥离旧快照。\n *\n * 说明:\n * - 当 prompt 中已有历史快照时,将其替换为过期占位文本。\n * - 让每轮真正生效的只有“最新注入快照”,减少冲突上下文。\n * - 这是 prompt 级清理;不会触碰 tool trace 中的原始结果对象。\n */\nexport function stripSnapshotFromPrompt(prompt: string): string {\n if (!containsSnapshot(prompt)) return prompt;\n return prompt.replace(SNAPSHOT_REGEX, SNAPSHOT_OUTDATED);\n}\n\n/** 导出快照正则,供消息层做错误摘要清理等用途。 */\nexport { SNAPSHOT_REGEX };\n","/**\n * 紧凑消息构建。\n *\n * 这个文件专门负责“给模型喂什么消息”。\n *\n * 它把 Agent Loop 的运行状态压缩成模型可直接消费的消息内容,核心输入包括:\n * - 用户原始目标(userMessage)\n * - 当前剩余任务(remainingInstruction)\n * - 已执行轨迹(trace / previousRoundTasks)\n * - 上一轮模型输出摘要(previousRoundModelOutput)\n * - 最新页面快照(latestSnapshot)\n * - 协议修复提示(protocolViolationHint)\n *\n * 设计目标:\n * 1) 减少上下文噪音,避免模型复述与空转\n * 2) 强化“基于当前快照做增量决策”的行为\n * 3) 让 REMAINING 协议在每轮都可持续推进\n *\n * 这个文件主要做了 4 件事:\n * 1) UI 意图识别:\n * - `isExplicitAgentUiRequest` 判断用户是否“明确要求操作 AutoPilot 聊天 UI”。\n * - 默认情况下会在提示词里禁止模型点击聊天输入框/发送按钮等。\n *\n * 2) 轨迹可读化:\n * - `formatToolInputBrief`、`formatToolResultBrief`、`buildToolTrace`\n * 把工具输入/结果压成短文本,便于注入上下文和调试展示。\n * - 直观效果示例(最终会长这样):\n * 1. [round 2] dom (action=\"click\", selector=\"#a1b2c\") [ELEMENT_NOT_FOUND]\n * 2. [round 2] wait (action=\"wait_for_selector\", selector=\"#a1b2c\")\n * 3. [round 3] dom (action=\"fill\", selector=\"#x9k3d\")\n *\n * 3) Round 0 消息构建:\n * - 首轮注入“任务 + remaining + 最新快照 + 执行约束”。\n * - 明确要求模型输出 `REMAINING: ...` 或 `REMAINING: DONE`。\n *\n * 4) Round 1+ 消息构建:\n * - 不再重复整段原始任务,改为注入“已完成步骤 + 当前 remaining + 最新快照”。\n * - 追加错误摘要、上轮计划数组、协议修复提示,帮助模型持续收敛。\n *\n * 边界说明(这个文件不做的事):\n * - 不调用模型、不执行工具\n * - 不维护循环状态(状态维护在 index.ts)\n * - 不读取页面快照(快照读取在 snapshot.ts)\n *\n * 一句话:这里是 Agent Loop 的“消息编排层”,负责把运行态翻译成稳定、高信息密度的提示上下文。\n */\nimport type { ToolCallResult } from \"../tool-registry.js\";\nimport type { AIMessage } from \"../types.js\";\nimport { toContentString, hasToolError } from \"./helpers.js\";\nimport { wrapSnapshot, SNAPSHOT_REGEX } from \"./snapshot.js\";\nimport type { ToolTraceEntry } from \"./types.js\";\n\n/**\n * 显式 UI 意图判定。\n *\n * 用途:默认禁止模型操作 AutoPilot 自己的聊天 UI(输入框/发送按钮等),\n * 只有当用户文本里“同时出现 UI 关键词 + 操作动词”时才放行。\n *\n * 判定逻辑:\n * - `hasAgentUiKeyword`:是否提到聊天面板/输入框/发送按钮等\n * - `hasActionVerb`:是否包含点击/输入/发送等动作意图\n * - 二者都满足才返回 true\n */\nexport function isExplicitAgentUiRequest(userMessage: string): boolean {\n const lower = userMessage.toLowerCase();\n const compact = lower.replace(/[\\s\\p{P}\\p{S}]+/gu, \"\");\n\n const hasAgentUiKeyword =\n /(chat|dock|chatinput|sendbutton|shortcut|quicktest)/i.test(lower) ||\n /(聊天|对话|指令输入框|消息输入框|输入框|发送按钮|发送|快捷测试|测试按钮|聊天面板)/.test(compact);\n\n const hasActionVerb =\n /(press|click|type|fill|send|input|submit|enter)/i.test(lower) ||\n /(输入|点击|发送|填写|填入|操作|提交|回车|按下)/.test(compact);\n return hasAgentUiKeyword && hasActionVerb;\n}\n\n// ─── 格式化辅助 ───\n\n/**\n * 输入摘要。\n *\n * 把工具输入压缩成一段短文本(用于轨迹展示),\n * 只保留高价值字段,避免日志过长。\n */\nexport function formatToolInputBrief(input: unknown): string {\n if (!input || typeof input !== \"object\") return \"\";\n\n const params = input as Record<string, unknown>;\n const parts: string[] = [];\n\n for (const key of [\"action\", \"selector\", \"waitMs\", \"waitSeconds\", \"url\", \"text\"]) {\n const value = params[key];\n if (value === undefined || value === null) continue;\n if (typeof value === \"string\") {\n parts.push(`${key}=${JSON.stringify(value).slice(0, 80)}`);\n } else if (typeof value === \"number\" || typeof value === \"boolean\") {\n parts.push(`${key}=${String(value)}`);\n }\n }\n\n if (parts.length === 0) return \"\";\n return ` (${parts.join(\", \")})`;\n}\n\n/**\n * 结果摘要。\n *\n * 读取工具结果首行,拼接错误码,生成一行可读结论:\n * - 成功:`✓ ...`\n * - 失败:`✗ ... [CODE]`\n */\nfunction formatToolResultBrief(result: ToolCallResult): string {\n const content = toContentString(result.content);\n const firstLine = content.split(\"\\n\").find(l => l.trim())?.trim().slice(0, 80) ?? \"\";\n\n if (hasToolError(result)) {\n const code = result.details && typeof result.details === \"object\"\n ? (result.details as { code?: string }).code\n : undefined;\n return `✗ ${firstLine}${code ? ` [${code}]` : \"\"}`;\n }\n return `✓ ${firstLine}`;\n}\n\n// ─── 轨迹格式化 ───\n\n/**\n * 轨迹格式化。\n *\n * 将完整工具轨迹转为可读文本列表,供提示词注入或调试展示。\n * 支持附加 current 条目(未入库前的临时展示)。\n *\n * 输出样式示例:\n * 1. [round 1] dom (action=\"click\", selector=\"#btnCreate\")\n * 2. [round 1] dom (action=\"fill\", selector=\"#title\") [FILL_NOT_APPLIED]\n * 3. [round 2] wait (action=\"wait_for_selector\", selector=\"#dialog\")\n */\nexport function buildToolTrace(\n trace: ToolTraceEntry[],\n current?: {\n round: number;\n name: string;\n input: unknown;\n result?: ToolCallResult;\n marker?: string;\n },\n): string {\n const lines = trace.map((entry, index) => {\n const code =\n entry.result.details && typeof entry.result.details === \"object\"\n ? (entry.result.details as { code?: unknown }).code\n : undefined;\n const codeText = typeof code === \"string\" ? ` [${code}]` : \"\";\n const marker = entry.marker ? ` ${entry.marker}` : \"\";\n return `${index + 1}. [round ${entry.round}] ${entry.name}${formatToolInputBrief(entry.input)}${codeText}${marker}`;\n });\n\n if (current) {\n const code =\n current.result?.details && typeof current.result.details === \"object\"\n ? (current.result.details as { code?: unknown }).code\n : undefined;\n const codeText = typeof code === \"string\" ? ` [${code}]` : \"\";\n const marker = current.marker ? ` ${current.marker}` : \"\";\n lines.push(\n `${lines.length + 1}. [round ${current.round}] ${current.name}${formatToolInputBrief(current.input)}${codeText}${marker}`,\n );\n }\n\n return lines.length > 0 ? lines.join(\"\\n\") : \"(暂无工具执行记录)\";\n}\n\n// ─── 紧凑消息构建 ───\n\n/**\n * 构建紧凑消息数组。\n *\n * 两种轮次语义:\n * - Round 0:发送“初始任务 + 当前快照 + 执行约束”\n * - Round 1+:发送“已完成步骤 + 当前 remaining + 最新快照”\n *\n * 渐进式语义:\n * - `remainingInstruction`:当前轮次仍待执行的文本。\n * - `previousRoundTasks`:上一轮已执行的任务数组,避免重复计划。\n * - `previousRoundModelOutput`:上一轮模型输出摘要,用于 task-reduction 输入。\n * - `previousRoundPlannedTasks`:上一轮计划数组,用于对齐“计划 vs 实际执行”。\n * - `protocolViolationHint`:协议修复提示(当 remaining 未完成但模型无动作时)。\n *\n * 输出:符合 AIMessage 结构的消息数组,可直接传给 AIClient.chat。\n */\nexport function buildCompactMessages(\n userMessage: string,\n trace: ToolTraceEntry[],\n latestSnapshot: string | undefined,\n currentUrl: string | undefined,\n history?: AIMessage[],\n remainingInstruction?: string,\n previousRoundTasks?: string[],\n previousRoundModelOutput?: string,\n previousRoundPlannedTasks?: string[],\n protocolViolationHint?: string,\n): AIMessage[] {\n const messages: AIMessage[] = history ? [...history] : [];\n const allowAgentUiInteraction = isExplicitAgentUiRequest(userMessage);\n const activeInstruction = (remainingInstruction && remainingInstruction.trim())\n ? remainingInstruction.trim()\n : userMessage;\n\n // ─── Round 0:任务描述 + 快照,一条 user 消息完成注入 ───\n if (trace.length === 0) {\n // 结构说明:\n // 1) 用户目标\n // 2) 当前 remaining\n // 3) URL(可选)\n // 4) 快照 + 行为约束(禁 page_info、禁误触 Agent UI、要求 REMAINING 输出)\n const parts: string[] = [\n userMessage,\n \"\",\n \"## Progressive execution state\",\n \"Current remaining instruction to execute this round:\",\n activeInstruction,\n ];\n if (currentUrl) {\n parts.push(\"\", `URL: ${currentUrl}`);\n }\n if (latestSnapshot) {\n parts.push(\n \"\",\n \"## Current page snapshot\",\n \"Apply task-reduction model directly from this snapshot. Do NOT restate the task.\",\n \"Use hash IDs (e.g. #a1b2c) from the snapshot as selector params.\",\n \"Do NOT call page_info (get_url/get_title/query_all/snapshot).\",\n \"Batch independent visible actions in one round.\",\n \"Build the minimal action array from current snapshot to finish this remaining instruction in one round whenever possible.\",\n \"For deterministic increase/decrease controls, compute delta from current visible value and issue exactly that many clicks in one round (e.g., +2 => two increase clicks). Do not overshoot then undo.\",\n \"If action changes DOM (open modal/navigate), stop that batch and continue next round.\",\n \"For dropdown/select fields, use dom with action=select_option (or fill on a select).\",\n \"If a needed list shows `... (N children omitted)` under a specific container, output `SNAPSHOT_HINT: EXPAND_CHILDREN #<containerRef>` and wait for next round snapshot.\",\n \"Stop rule: once requested state is reached, stop tool calls. If verification is needed, verify once and then output REMAINING: DONE.\",\n allowAgentUiInteraction\n ? \"User explicitly asked to operate AutoPilot UI. You may interact with chat input/send/dock only as requested.\"\n : \"Do NOT interact with any AI chat UI elements (chat input, send button, dock). Only operate on the actual page content.\",\n \"Output one line: REMAINING: <new remaining task after this round> or REMAINING: DONE\",\n wrapSnapshot(latestSnapshot),\n );\n }\n if (protocolViolationHint) {\n parts.push(\"\", protocolViolationHint);\n }\n messages.push({ role: \"user\", content: parts.join(\"\\n\") });\n return messages;\n }\n\n // ─── Round 1+:注入“已完成步骤 + 执行上下文 + 最新快照” ───\n // 不再重复原始 userMessage,避免模型每轮回到起点重做。\n\n // 第 1 条 assistant 消息:已完成步骤摘要(从 trace 重建)\n const traceParts: string[] = [];\n for (let i = 0; i < trace.length; i++) {\n const entry = trace[i];\n const isError = hasToolError(entry.result);\n const brief = formatToolResultBrief(entry.result);\n const status = isError ? \"❌\" : \"✅\";\n const marker = entry.marker ? ` ${entry.marker}` : \"\";\n traceParts.push(\n `${status} ${i + 1}. ${entry.name}${formatToolInputBrief(entry.input)} → ${brief}${marker}`,\n );\n }\n messages.push({\n role: \"assistant\",\n content: `Done steps (do NOT repeat):\\n${traceParts.join(\"\\n\")}`,\n });\n\n // 第 2 条 user 消息:执行上下文 + 协议约束 + 最新快照\n const hasErrors = trace.some(e => hasToolError(e.result));\n const contextParts: string[] = [\n // 执行上下文语义:\n // - 当前 remaining 是唯一待消费目标\n // - 已完成步骤不重复\n // - 必须基于最新快照决策,不猜测未来 DOM\n // - 要求模型继续输出 REMAINING 协议\n \"## Execution context\",\n \"Current remaining instruction:\",\n activeInstruction,\n \"\",\n \"Task-reduction model:\",\n \"Input: current remaining instruction + previous round executed actions + this-round actions.\",\n \"Output: new remaining instruction after removing this-round actions.\",\n \"Start from visible page state directly. Do NOT restate task. Do NOT output planning text.\",\n \"Execute all independent visible sub-tasks in one round.\",\n \"Do NOT act on elements not present in this snapshot yet.\",\n \"If action changes DOM (open modal/navigate), stop after that batch and continue next round.\",\n \"Do NOT call page_info (get_url/get_title/query_all/snapshot).\",\n \"For dropdown/select fields, use dom with action=select_option (or fill on a select).\",\n \"If a needed list shows `... (N children omitted)` under a specific container, output `SNAPSHOT_HINT: EXPAND_CHILDREN #<containerRef>` and wait for next round snapshot.\",\n \"Build the minimal action array from current snapshot to finish this remaining instruction in one round whenever possible.\",\n \"For deterministic increase/decrease controls, compute delta from current visible value and issue exactly that many clicks in one round (e.g., +2 => two increase clicks). Do not overshoot then undo.\",\n \"Stop rule: once requested state is reached, stop tool calls. If verification is needed, verify once and then output REMAINING: DONE.\",\n allowAgentUiInteraction\n ? \"User explicitly asked to operate AutoPilot UI. You may interact with chat input/send/dock only as requested.\"\n : \"Do NOT interact with any AI chat UI elements (chat input, send button, dock). Only operate on the actual page content.\",\n ];\n\n if (hasErrors) {\n contextParts.push(\n \"\",\n \"The last step failed. Retry with a different approach, or skip and continue with other visible targets.\",\n );\n } else {\n contextParts.push(\n \"\",\n \"If the goal is fully done, reply with a short summary (no tool calls).\",\n );\n }\n\n if (previousRoundTasks && previousRoundTasks.length > 0) {\n contextParts.push(\n \"\",\n \"Previous round planned task array (already executed):\",\n ...previousRoundTasks.map((task, index) => `${index + 1}. ${task}`),\n );\n }\n\n if (previousRoundPlannedTasks && previousRoundPlannedTasks.length > 0) {\n contextParts.push(\n \"\",\n \"Previous round model planned task array (before execution):\",\n ...previousRoundPlannedTasks.map((task, index) => `${index + 1}. ${task}`),\n );\n }\n\n if (previousRoundModelOutput) {\n contextParts.push(\n \"\",\n \"Previous round model output (normalized, for task reduction input):\",\n previousRoundModelOutput,\n );\n }\n\n contextParts.push(\n // 协议约束:要求模型显式返回下一轮 remaining\n \"\",\n \"After this round, include one plain text line:\",\n \"REMAINING: <new remaining instruction after this-round actions>\",\n \"or REMAINING: DONE\",\n );\n\n // 最近失败摘要:若最近一步报错,把错误摘要附在上下文尾部\n const lastEntry = trace[trace.length - 1];\n if (hasToolError(lastEntry.result)) {\n const detail = toContentString(lastEntry.result.content);\n const stripped = detail.replace(SNAPSHOT_REGEX, \"\").trim();\n if (stripped && stripped.length < 300) {\n contextParts.push(\"\", \"Last error: \" + stripped);\n }\n }\n\n if (currentUrl) {\n contextParts.push(\"\", `URL: ${currentUrl}`);\n }\n\n if (protocolViolationHint) {\n contextParts.push(\"\", protocolViolationHint);\n }\n\n if (latestSnapshot) {\n // 始终注入“最新快照”,并强调无需再调用 page_info 获取页面信息。\n contextParts.push(\n \"\",\n \"## Latest DOM snapshot\",\n \"Use hash IDs from this snapshot. Do NOT call page_info — this is already the latest.\",\n wrapSnapshot(latestSnapshot),\n );\n }\n\n messages.push({ role: \"user\", content: contextParts.join(\"\\n\") });\n\n return messages;\n}\n","/**\n * 保护与恢复机制。\n *\n * 这个文件负责给 Agent Loop 提供“防失败、防空转、防重复”的保护链。\n * 目标是:即使某一步失败,也尽量让循环继续推进,而不是直接崩掉。\n *\n * 主要能力:\n * 1) 冗余拦截:拦住无意义的 `page_info.*` 调用\n * 2) 快照防抖:连续 snapshot 触发时给出警告并限制空转\n * 3) 找不到元素恢复:自动等待 + 刷新快照 + 重试上限\n * 4) 导航后刷新:导航成功后立刻更新快照上下文\n * 5) 空转检测:连续只读轮次触发停机信号\n *\n * 一句话:这里是主循环的“保险丝层”。\n */\nimport type { ToolCallResult } from \"../tool-registry.js\";\nimport type { AgentLoopCallbacks } from \"./types.js\";\nimport { DEFAULT_ACTION_RECOVERY_ROUNDS } from \"./constants.js\";\nimport { readPageSnapshot } from \"./snapshot.js\";\nimport {\n getToolAction,\n hasToolError,\n isElementNotFoundResult,\n resolveRecoveryWaitMs,\n buildToolCallKey,\n sleep,\n toContentString,\n} from \"./helpers.js\";\nimport { ToolRegistry } from \"../tool-registry.js\";\nimport type { PageContextState } from \"./types.js\";\n\n// ─── 冗余 page_info 拦截 ───\n\n/** 冗余 page_info 动作集合。 */\nconst REDUNDANT_PAGE_INFO_ACTIONS = new Set([\"snapshot\", \"query_all\", \"get_url\", \"get_title\", \"get_viewport\"]);\n\n/**\n * 冗余 page_info 检查。\n *\n * 场景:模型在 loop 中频繁请求 page_info,导致“只看不做”。\n * 处理:命中白名单动作时直接返回拦截结果,不真正执行工具。\n *\n * 示例:\n * - 输入:`page_info.snapshot`\n * - 输出:`REDUNDANT_PAGE_INFO_SKIPPED`\n */\nexport function checkRedundantSnapshot(\n toolName: string,\n toolInput: unknown,\n _latestSnapshot: string | undefined,\n round: number,\n): ToolCallResult | null {\n if (toolName !== \"page_info\") return null;\n\n const action = getToolAction(toolInput);\n if (action && REDUNDANT_PAGE_INFO_ACTIONS.has(action)) {\n return {\n content:\n `page_info.${action} is blocked in loop execution. A snapshot is provided by the framework; continue with actionable tools directly.`,\n details: {\n code: \"REDUNDANT_PAGE_INFO_SKIPPED\",\n action,\n round,\n },\n };\n }\n return null;\n}\n\n/**\n * 快照防抖。\n *\n * 规则:连续触发 `page_info.snapshot` 时,第 2 次起标记为冗余,\n * 返回 `REDUNDANT_SNAPSHOT`,提醒模型直接使用已有快照继续执行。\n *\n * 返回值:\n * - `result`:可能被替换成防抖后的结果\n * - `consecutiveCount`:更新后的连续 snapshot 计数\n */\nexport function applySnapshotDebounce(\n toolName: string,\n toolInput: unknown,\n result: ToolCallResult,\n consecutiveCount: number,\n): { result: ToolCallResult; consecutiveCount: number } {\n if (toolName === \"page_info\" && getToolAction(toolInput) === \"snapshot\") {\n const newCount = consecutiveCount + 1;\n if (newCount >= 2) {\n return {\n consecutiveCount: newCount,\n result: {\n content: [\n toContentString(result.content),\n \"Redundant snapshot detected. Continue with remaining actionable steps using the latest snapshot; avoid additional snapshot unless navigation or uncertainty changes.\",\n ].join(\"\\n\"),\n details: {\n error: true,\n code: \"REDUNDANT_SNAPSHOT\",\n consecutiveSnapshotCalls: newCount,\n },\n },\n };\n }\n return { result, consecutiveCount: newCount };\n }\n // 非 snapshot 调用,重置计数\n return { result, consecutiveCount: 0 };\n}\n\n// ─── 元素未找到自动恢复 ───\n\n/**\n * 元素未找到恢复。\n *\n * 触发条件:\n * - 工具是 `dom`\n * - 结果被识别为“元素未找到”\n *\n * 处理流程:\n * 1) 按调用键统计恢复次数(同 name + input 视为同一调用)\n * 2) 在上限内:等待 -> 刷新快照 -> 返回 `ELEMENT_NOT_FOUND_RECOVERY`\n * 3) 超过上限:返回 `ELEMENT_NOT_FOUND_MAX_RECOVERY_REACHED`\n *\n * 说明:函数只返回“恢复后的结果描述”,是否继续下一轮由主循环决定。\n */\nexport async function handleElementRecovery(\n toolName: string,\n toolInput: unknown,\n result: ToolCallResult,\n recoveryAttempts: Map<string, number>,\n registry: ToolRegistry,\n pageContext: PageContextState,\n callbacks?: AgentLoopCallbacks,\n): Promise<ToolCallResult | null> {\n if (toolName !== \"dom\" || !isElementNotFoundResult(result)) {\n return null;\n }\n\n const key = buildToolCallKey(toolName, toolInput);\n const attempts = (recoveryAttempts.get(key) ?? 0) + 1;\n recoveryAttempts.set(key, attempts);\n const recoveryWaitMs = resolveRecoveryWaitMs(toolInput);\n\n if (attempts <= DEFAULT_ACTION_RECOVERY_ROUNDS) {\n await sleep(recoveryWaitMs);\n callbacks?.onBeforeRecoverySnapshot?.();\n pageContext.latestSnapshot = await readPageSnapshot(registry);\n\n return {\n content: [\n toContentString(result.content),\n `Recovery ${attempts}/${DEFAULT_ACTION_RECOVERY_ROUNDS}: snapshot refreshed, re-locate target.`,\n ].join(\"\\n\"),\n details: {\n error: true,\n code: \"ELEMENT_NOT_FOUND_RECOVERY\",\n recoveryAttempt: attempts,\n recoveryMaxRounds: DEFAULT_ACTION_RECOVERY_ROUNDS,\n },\n };\n }\n\n return {\n content: [\n toContentString(result.content),\n `Max recovery attempts (${DEFAULT_ACTION_RECOVERY_ROUNDS}) reached. Try a different target.`,\n ].join(\"\\n\"),\n details: {\n error: true,\n code: \"ELEMENT_NOT_FOUND_MAX_RECOVERY_REACHED\",\n recoveryAttempt: attempts,\n recoveryMaxRounds: DEFAULT_ACTION_RECOVERY_ROUNDS,\n },\n };\n}\n\n// ─── 导航后 URL 变化检测 ───\n\n/**\n * 导航后快照刷新。\n *\n * 当 `navigate.goto/back/forward/reload` 成功后,立即刷新快照,\n * 防止后续动作还在旧页面上下文里决策。\n */\nexport async function handleNavigationUrlChange(\n toolName: string,\n toolInput: unknown,\n result: ToolCallResult,\n registry: ToolRegistry,\n pageContext: PageContextState,\n callbacks?: AgentLoopCallbacks,\n): Promise<void> {\n if (toolName !== \"navigate\") return;\n\n const action = getToolAction(toolInput);\n if (\n (action === \"goto\" || action === \"back\" || action === \"forward\" || action === \"reload\") &&\n !hasToolError(result)\n ) {\n callbacks?.onBeforeRecoverySnapshot?.();\n pageContext.latestSnapshot = await readPageSnapshot(registry);\n }\n}\n\n// ─── 空转检测 ───\n\n/** 只读工具集合。 */\nconst READ_ONLY_TOOLS = new Set([\"page_info\"]);\n\n/** DOM 只读动作集合。 */\nconst READ_ONLY_DOM_ACTIONS = new Set([\"get_text\", \"get_attr\"]);\n\n/**\n * 空转检测:识别连续只读轮次并终止。\n *\n * 判定口径:\n * - `page_info.*` 视为只读\n * - `dom.get_text/get_attr` 视为只读\n *\n * 返回值语义:\n * - `-1`:触发停机(连续 2 轮纯只读)\n * - `0`:本轮有实质操作,计数清零\n * - `>0`:当前连续只读轮次\n */\nexport function detectIdleLoop(\n toolCalls: Array<{ name: string; input: unknown }>,\n consecutiveReadOnlyRounds: number,\n): number {\n const allReadOnly = toolCalls.length > 0 && toolCalls.every(({ name, input }) => {\n if (READ_ONLY_TOOLS.has(name)) return true;\n if (name !== \"dom\") return false;\n const action = getToolAction(input);\n return Boolean(action && READ_ONLY_DOM_ACTIONS.has(action));\n });\n if (allReadOnly) {\n const newCount = consecutiveReadOnlyRounds + 1;\n // 连续 2 轮纯只读 → 返回 -1 表示强制终止\n return newCount >= 2 ? -1 : newCount;\n }\n return 0; // 有实际操作,重置\n}\n","/**\n * Agent Loop 主流程(口语版)\n *\n * 流程图(文本):\n *\n * 轮次开始\n * │\n * ├─ 先看有没有最新快照\n * │ └─ 没有就先拍一张(可带 expandChildrenRefs)\n * │\n * ├─ 组装本轮上下文消息\n * │ └─ remaining + 上轮任务 + 最新快照 +(必要时)重试提示\n * │\n * ├─ 调用模型拿决策\n * │ └─ 同时解析 `REMAINING` 和 `SNAPSHOT_HINT`\n * │\n * ├─ 有 toolCalls 吗?\n * │ ├─ 没有:走收敛/协议修复判断(必要时等待后重试)\n * │ └─ 有:逐个执行工具\n * │ ├─ 冗余拦截(例如 page_info 空转)\n * │ ├─ 失败恢复(元素未找到重试)\n * │ ├─ 导航后更新快照\n * │ └─ 命中断轮条件则提前结束本轮\n * │\n * ├─ 更新 remaining(优先协议,缺失时启发式剔除)\n * │\n * ├─ 防空转 / 防自转检查\n * │ └─ 连续只读或重复批次会触发停机\n * │\n * ├─ 刷新快照\n * ▼\n * 下一轮或停机\n *\n * 停机条件(任一命中):\n * - `REMAINING: DONE`(或 remaining 为空)\n * - 协议修复后仍无推进\n * - 连续只读(空转)\n * - 重复批次(自转)\n * - 达到 maxRounds\n */\nimport {\n DEFAULT_MAX_ROUNDS,\n DEFAULT_NOT_FOUND_RETRY_ROUNDS,\n DEFAULT_NOT_FOUND_RETRY_WAIT_MS,\n DEFAULT_ROUND_STABILITY_WAIT_LOADING_SELECTORS,\n DEFAULT_ROUND_STABILITY_WAIT_QUIET_MS,\n DEFAULT_ROUND_STABILITY_WAIT_TIMEOUT_MS,\n} from \"./constants.js\";\nimport {\n buildTaskArray,\n collectMissingTask,\n deriveNextInstruction,\n extractHashSelectorRef,\n getToolAction,\n normalizeModelOutput,\n parseSnapshotExpandHints,\n reduceRemainingHeuristically,\n shouldForceRoundBreak,\n isPotentialDomMutation,\n sleep,\n toContentString,\n hasToolError,\n} from \"./helpers.js\";\nimport { readPageSnapshot, stripSnapshotFromPrompt } from \"./snapshot.js\";\nimport { buildCompactMessages } from \"./messages.js\";\nimport {\n checkRedundantSnapshot,\n applySnapshotDebounce,\n handleElementRecovery,\n handleNavigationUrlChange,\n detectIdleLoop,\n} from \"./recovery.js\";\nimport type {\n AgentLoopParams,\n AgentLoopResult,\n AgentLoopMetrics,\n PageContextState,\n ToolTraceEntry,\n} from \"./types.js\";\nimport type { AIMessage } from \"../types.js\";\n\n/**\n * 执行 Agent 循环。\n *\n * 你可以把这个函数理解成“任务执行调度器”:\n * - 输入:用户任务、系统提示词、工具注册表、历史消息、初始快照\n * - 过程:按轮次持续执行“看页面 -> 让模型决策 -> 跑工具 -> 更新上下文”\n * - 输出:最终回复、完整工具调用记录、可复用消息、结构化指标\n *\n * 每轮主流程(固定顺序):\n * 1) Ensure Snapshot:确保当前有最新快照(必要时读取)\n * 2) Build Messages:构建紧凑上下文(remaining + 上轮轨迹 + 最新快照)\n * 3) Call AI:请求模型并解析协议字段(`REMAINING` / `SNAPSHOT_HINT`)\n * 4) Execute Tools:执行工具调用并应用保护机制(冗余拦截、恢复、导航刷新)\n * 5) Reduce Remaining:推进剩余任务(优先协议,缺失时启发式剔除)\n * 6) Guard & Refresh:防空转/防自转判定,并刷新快照进入下一轮\n *\n * 核心状态语义:\n * - `remainingInstruction`:当前轮还未消费完的任务文本\n * - `previousRoundTasks`:上一轮已执行动作,防止模型原样重复\n * - `previousRoundPlannedTasks`:上一轮模型计划,用于重复批次检测\n * - `protocolViolationHint`:协议修复提示(remaining 未完成却无工具调用时注入)\n *\n * 停机条件(命中任意一条即结束):\n * - 模型无工具调用且 remaining 已收敛(`REMAINING: DONE` 或空)\n * - 协议修复后仍无推进\n * - 连续只读轮次(防空转)\n * - 连续重复计划批次(防自转)\n * - 达到 `maxRounds`\n */\nexport async function executeAgentLoop(\n params: AgentLoopParams,\n): Promise<AgentLoopResult> {\n const {\n client,\n registry,\n systemPrompt,\n message,\n initialSnapshot,\n history,\n dryRun = false,\n maxRounds = DEFAULT_MAX_ROUNDS,\n roundStabilityWait,\n callbacks,\n } = params;\n\n // 固定依赖与运行态容器\n const tools = registry.getDefinitions();\n const allToolCalls: AgentLoopResult[\"toolCalls\"] = [];\n const fullToolTrace: ToolTraceEntry[] = [];\n const actionRecoveryAttempts = new Map<string, number>();\n const pageContext: PageContextState = {\n latestSnapshot: initialSnapshot,\n };\n\n // 最终输出\n let finalReply = \"\";\n\n // 循环控制状态\n let consecutiveSnapshotCalls = 0;\n let consecutiveReadOnlyRounds = 0;\n let usedRounds = 0;\n\n // token 统计\n let inputTokens = 0;\n let outputTokens = 0;\n // 渐进式任务状态\n // remainingInstruction: 当前轮次要继续消费的剩余文本。\n // previousRoundTasks: 上一轮已经执行过的任务数组,用于提醒 AI 不要原样重复。\n // lastPlannedBatchKey + consecutiveSamePlannedBatch: 防止 AI 连续给出相同任务批次导致自转。\n // lastRoundHadError: 如果上一轮有错误,不触发“重复批次即停机”,避免误停。\n let remainingInstruction = message.trim();\n let previousRoundTasks: string[] = [];\n let previousRoundPlannedTasks: string[] = [];\n let previousRoundModelOutput = \"\";\n let lastPlannedBatchKey = \"\";\n let consecutiveSamePlannedBatch = 0;\n let lastRoundHadError = false;\n let protocolViolationHint: string | undefined;\n const snapshotExpandRefIds = new Set<string>();\n const effectiveRoundStabilityWait = {\n enabled: roundStabilityWait?.enabled ?? true,\n timeoutMs: Math.max(200, Math.floor(roundStabilityWait?.timeoutMs ?? DEFAULT_ROUND_STABILITY_WAIT_TIMEOUT_MS)),\n quietMs: Math.max(50, Math.floor(roundStabilityWait?.quietMs ?? DEFAULT_ROUND_STABILITY_WAIT_QUIET_MS)),\n loadingSelectors: [\n ...new Set(\n [\n ...DEFAULT_ROUND_STABILITY_WAIT_LOADING_SELECTORS,\n ...(roundStabilityWait?.loadingSelectors ?? []),\n ]\n .map(selector => selector.trim())\n .filter(Boolean),\n ),\n ],\n };\n // 恢复与拦截统计\n let recoveryCount = 0;\n let redundantInterceptCount = 0;\n\n type MissingToolTask = {\n name: string;\n input: unknown;\n reason: string;\n };\n\n let pendingNotFoundRetry:\n | {\n attempt: number;\n tasks: MissingToolTask[];\n }\n | undefined;\n\n // 快照体积统计\n let snapshotReadCount = 0;\n let snapshotSizeTotal = 0;\n let snapshotSizeMax = 0;\n\n /**\n * 记录快照统计。\n *\n * 用于输出可观测指标:读取次数、平均长度、最大长度。\n * Used for observability metrics: read count, avg size, max size.\n */\n const recordSnapshotStats = (snapshot: string | undefined): void => {\n if (typeof snapshot !== \"string\") return;\n snapshotReadCount += 1;\n snapshotSizeTotal += snapshot.length;\n if (snapshot.length > snapshotSizeMax) snapshotSizeMax = snapshot.length;\n };\n\n /**\n * 刷新页面快照。\n *\n * 只做两件事:读取最新快照 + 更新快照统计。\n * Does exactly two things: read latest snapshot + update metrics.\n */\n const refreshSnapshot = async (): Promise<void> => {\n pageContext.latestSnapshot = await readPageSnapshot(\n registry,\n snapshotExpandRefIds.size > 0\n ? { expandChildrenRefs: Array.from(snapshotExpandRefIds), expandedChildrenLimit: 120 }\n : undefined,\n );\n recordSnapshotStats(pageContext.latestSnapshot);\n };\n\n /**\n * 轮次后稳定等待(双重等待)。\n *\n * 顺序固定为:\n * 1) 等待 loading 指示器隐藏\n * 2) 等待 DOM quiet window\n */\n const runRoundStabilityBarrier = async (): Promise<void> => {\n if (!effectiveRoundStabilityWait.enabled) return;\n if (!registry.has(\"wait\")) return;\n\n const timeout = effectiveRoundStabilityWait.timeoutMs;\n const loadingSelector = effectiveRoundStabilityWait.loadingSelectors.join(\", \");\n\n if (loadingSelector) {\n await registry.dispatch(\"wait\", {\n action: \"wait_for_selector\",\n selector: loadingSelector,\n state: \"hidden\",\n timeout,\n });\n }\n\n await registry.dispatch(\"wait\", {\n action: \"wait_for_stable\",\n timeout,\n quietMs: effectiveRoundStabilityWait.quietMs,\n });\n };\n\n\n if (pageContext.latestSnapshot) {\n recordSnapshotStats(pageContext.latestSnapshot);\n }\n\n /**\n * 追加工具轨迹。\n *\n * 同时写入:\n * - allToolCalls:对外返回结果\n * - fullToolTrace:下一轮消息上下文\n */\n const appendToolTrace = (\n round: number,\n name: string,\n input: unknown,\n result: AgentLoopResult[\"toolCalls\"][number][\"result\"],\n ): void => {\n allToolCalls.push({ name, input, result });\n fullToolTrace.push({ round, name, input, result });\n };\n\n // 主循环\n for (let round = 0; round < maxRounds; round++) {\n callbacks?.onRound?.(round);\n usedRounds = round + 1;\n\n // ═══ 阶段 1:确保快照 ═══\n if (!pageContext.latestSnapshot) {\n await refreshSnapshot();\n }\n\n // ═══ 阶段 2:构建紧凑消息 ═══\n // 每轮消息都自带快照(buildCompactMessages 注入),因此始终剥离\n // system prompt 中的旧快照,避免重复。\n const effectivePrompt = stripSnapshotFromPrompt(systemPrompt);\n\n const chatMessages = buildCompactMessages(\n message,\n fullToolTrace,\n pageContext.latestSnapshot,\n pageContext.currentUrl,\n history,\n remainingInstruction,\n previousRoundTasks,\n previousRoundModelOutput,\n previousRoundPlannedTasks,\n protocolViolationHint,\n );\n\n if (pendingNotFoundRetry && pendingNotFoundRetry.tasks.length > 0) {\n chatMessages.push({\n role: \"user\",\n content: [\n \"## Not-found retry context\",\n `Retry attempt: ${pendingNotFoundRetry.attempt}/${DEFAULT_NOT_FOUND_RETRY_ROUNDS}`,\n \"These tool targets were not found in previous execution:\",\n ...pendingNotFoundRetry.tasks.map((task, i) =>\n `${i + 1}. ${task.name}(${JSON.stringify(task.input)}) -> ${task.reason}`,\n ),\n \"Only retry unresolved targets that are now visible in the latest snapshot.\",\n \"If still not found, return no tool calls and include REMAINING with the unresolved part.\",\n ].join(\"\\n\"),\n });\n }\n\n // ═══ 阶段 3:调用 AI ═══\n const response = await client.chat({\n systemPrompt: effectivePrompt,\n messages: chatMessages,\n tools,\n });\n\n // 计费/观测数据累计\n inputTokens += response.usage?.inputTokens ?? 0;\n outputTokens += response.usage?.outputTokens ?? 0;\n\n // 先解析协议,最终推进在本轮执行后统一决定。\n const parsedInstructionState = deriveNextInstruction(response.text, remainingInstruction);\n const snapshotHintRefs = parseSnapshotExpandHints(response.text);\n for (const ref of snapshotHintRefs.slice(0, 8)) {\n snapshotExpandRefIds.add(ref);\n }\n\n // 没有工具调用:若处于找不到重试流程,先等待再重试;否则正常结束\n if (!response.toolCalls || response.toolCalls.length === 0) {\n if (pendingNotFoundRetry) {\n const unresolvedHint = response.text?.toLowerCase() ?? \"\";\n const stillUnresolved =\n unresolvedHint.includes(\"找不到\") ||\n unresolvedHint.includes(\"未找到\") ||\n unresolvedHint.includes(\"not found\") ||\n unresolvedHint.includes(\"cannot find\") ||\n unresolvedHint.includes(\"unable to locate\");\n\n if (stillUnresolved && pendingNotFoundRetry.attempt < DEFAULT_NOT_FOUND_RETRY_ROUNDS) {\n pendingNotFoundRetry = {\n ...pendingNotFoundRetry,\n attempt: pendingNotFoundRetry.attempt + 1,\n };\n callbacks?.onText?.(\n `未命中目标,准备第 ${pendingNotFoundRetry.attempt} 次重试(等待 ${DEFAULT_NOT_FOUND_RETRY_WAIT_MS}ms)...`,\n );\n await sleep(DEFAULT_NOT_FOUND_RETRY_WAIT_MS);\n await refreshSnapshot();\n continue;\n }\n pendingNotFoundRetry = undefined;\n }\n\n if (parsedInstructionState.hasRemainingProtocol) {\n remainingInstruction = parsedInstructionState.nextInstruction;\n }\n\n const unresolvedRemaining = remainingInstruction.trim().length > 0;\n if (unresolvedRemaining && round < maxRounds - 1) {\n protocolViolationHint = [\n \"Protocol violation in previous round:\",\n \"- Remaining task is not DONE, but no tool calls were returned.\",\n \"This round MUST do one of:\",\n \"1) Return actionable tool calls for visible targets; or\",\n \"2) If truly complete, return a short summary and EXACTLY `REMAINING: DONE`.\",\n \"Do NOT output planning/explaining text.\",\n ].join(\"\\n\");\n lastRoundHadError = true;\n await refreshSnapshot();\n continue;\n }\n\n finalReply = response.text ?? \"\";\n if (finalReply) callbacks?.onText?.(finalReply);\n break;\n }\n\n protocolViolationHint = undefined;\n const plannedTasksCurrentRound = buildTaskArray(\n response.toolCalls.map(tc => ({ name: tc.name, input: tc.input })),\n );\n\n const plannedBatchKey = JSON.stringify(\n response.toolCalls.map(tc => ({ name: tc.name, input: tc.input })),\n );\n // 比较“本轮计划”与“上一轮计划”是否完全一致。\n if (plannedBatchKey === lastPlannedBatchKey) {\n consecutiveSamePlannedBatch += 1;\n } else {\n consecutiveSamePlannedBatch = 1;\n lastPlannedBatchKey = plannedBatchKey;\n }\n\n // 防自转:连续两轮给出相同计划且上一轮无错误,判定任务已完成或模型卡住,直接结束。\n if (consecutiveSamePlannedBatch >= 2 && !lastRoundHadError) {\n finalReply = response.text?.trim() || \"任务已完成。\";\n if (finalReply) callbacks?.onText?.(finalReply);\n break;\n }\n\n // ─── Dry-run 模式 ───\n if (dryRun) {\n finalReply = response.text ? response.text + \"\\n\\n\" : \"\";\n finalReply += \"🔧 AI 请求调用以下工具(dry-run 模式,未执行):\\n\";\n for (const tc of response.toolCalls) {\n callbacks?.onToolCall?.(tc.name, tc.input);\n finalReply += `\\n┌─ 工具: ${tc.name}\\n`;\n finalReply += `│ ID: ${tc.id}\\n`;\n finalReply += `│ 参数:\\n`;\n const inputStr = JSON.stringify(tc.input, null, 2);\n for (const line of inputStr.split(\"\\n\")) {\n finalReply += `│ ${line}\\n`;\n }\n finalReply += `└────────────────────\\n`;\n }\n break;\n }\n\n // ═══ 阶段 4:执行工具调用(带保护机制)═══\n\n // 批量执行所有工具调用\n // roundHasError 用于控制“重复批次停机”:上一轮有错误时,不应武断终止。\n let roundHasError = false;\n let roundHasPotentialDomMutation = false;\n const executedTaskCalls: Array<{ name: string; input: unknown }> = [];\n const roundMissingTasks: MissingToolTask[] = [];\n for (const tc of response.toolCalls) {\n\n // 自动策略:当 AI 对 hash 列表执行 scroll 时,默认下一轮对该节点放宽 children 截断。\n // 这样即使模型未显式输出 SNAPSHOT_HINT,也能尽快拿到完整列表选项。\n if (tc.name === \"dom\" && getToolAction(tc.input) === \"scroll\") {\n const ref = extractHashSelectorRef(tc.input);\n if (ref) snapshotExpandRefIds.add(ref);\n }\n\n // 保护 1:冗余快照拦截\n const redundant = checkRedundantSnapshot(\n tc.name, tc.input, pageContext.latestSnapshot, round,\n );\n if (redundant) {\n appendToolTrace(round, tc.name, tc.input, redundant);\n redundantInterceptCount += 1;\n callbacks?.onToolResult?.(tc.name, redundant);\n continue;\n }\n\n callbacks?.onToolCall?.(tc.name, tc.input);\n\n // 执行工具\n let result = await registry.dispatch(tc.name, tc.input);\n\n // 保护 2:连续快照防抖\n const debounced = applySnapshotDebounce(\n tc.name, tc.input, result, consecutiveSnapshotCalls,\n );\n result = debounced.result;\n consecutiveSnapshotCalls = debounced.consecutiveCount;\n\n // 保护 3:元素未找到自动恢复\n const recovered = await handleElementRecovery(\n tc.name, tc.input, result,\n actionRecoveryAttempts, registry, pageContext, callbacks,\n );\n if (recovered) result = recovered;\n if (\n recovered?.details &&\n typeof recovered.details === \"object\" &&\n (recovered.details as { code?: unknown }).code === \"ELEMENT_NOT_FOUND_RECOVERY\"\n ) {\n recoveryCount += 1;\n }\n\n appendToolTrace(round, tc.name, tc.input, result);\n executedTaskCalls.push({ name: tc.name, input: tc.input });\n\n const missingTask = collectMissingTask(tc.name, tc.input, result);\n if (missingTask) {\n roundMissingTasks.push(missingTask);\n }\n\n if (result.details && typeof result.details === \"object\") {\n roundHasError = roundHasError || Boolean((result.details as { error?: unknown }).error);\n }\n if (!hasToolError(result) && isPotentialDomMutation(tc.name, tc.input)) {\n roundHasPotentialDomMutation = true;\n }\n\n // 捕获显式 snapshot 结果\n if (tc.name === \"page_info\" && getToolAction(tc.input) === \"snapshot\") {\n pageContext.latestSnapshot = toContentString(result.content);\n recordSnapshotStats(pageContext.latestSnapshot);\n }\n\n // 保护 4:导航后 URL 变化检测\n await handleNavigationUrlChange(\n tc.name, tc.input, result, registry, pageContext, callbacks,\n );\n\n callbacks?.onToolResult?.(tc.name, result);\n\n if (shouldForceRoundBreak(tc.name, tc.input)) {\n break;\n }\n }\n\n if (roundMissingTasks.length > 0) {\n pendingNotFoundRetry = {\n attempt: 1,\n tasks: roundMissingTasks,\n };\n } else {\n pendingNotFoundRetry = undefined;\n }\n\n // 将本轮执行状态传给下一轮上下文。\n if (parsedInstructionState.hasRemainingProtocol) {\n remainingInstruction = parsedInstructionState.nextInstruction;\n } else {\n const nextByHeuristic = reduceRemainingHeuristically(remainingInstruction, executedTaskCalls.length);\n if (nextByHeuristic !== remainingInstruction) {\n remainingInstruction = nextByHeuristic;\n } else {\n roundHasError = true;\n }\n }\n\n previousRoundModelOutput = parsedInstructionState.hasRemainingProtocol\n ? normalizeModelOutput(response.text)\n : `REMAINING: ${remainingInstruction || \"DONE\"}`;\n\n lastRoundHadError = roundHasError;\n previousRoundTasks = buildTaskArray(executedTaskCalls);\n previousRoundPlannedTasks = plannedTasksCurrentRound;\n\n // 协议显式 DONE 且本轮已完成执行且无错误:直接收敛,避免后续重复动作。\n if (\n parsedInstructionState.hasRemainingProtocol &&\n remainingInstruction.trim().length === 0 &&\n !roundHasError\n ) {\n finalReply = response.text?.trim() || \"任务已完成。\";\n if (finalReply) callbacks?.onText?.(finalReply);\n break;\n }\n\n // 保护 5:空转检测\n const attemptedTaskCalls = response.toolCalls.map(tc => ({ name: tc.name, input: tc.input }));\n const idleResult = detectIdleLoop(attemptedTaskCalls, consecutiveReadOnlyRounds);\n if (idleResult === -1) {\n finalReply = response.text?.trim() || \"任务已完成。\";\n if (finalReply) callbacks?.onText?.(finalReply);\n break;\n }\n consecutiveReadOnlyRounds = idleResult;\n\n if (roundHasPotentialDomMutation) {\n await runRoundStabilityBarrier();\n }\n\n // ═══ 阶段 5:刷新快照(供下一轮使用)═══\n await refreshSnapshot();\n }\n\n // 构建紧凑的 result.messages 供多轮记忆使用\n // Build compact result.messages for optional multi-turn memory reuse.\n const resultMessages: AIMessage[] = [...(history ?? []), { role: \"user\", content: message }];\n if (finalReply) {\n resultMessages.push({ role: \"assistant\", content: finalReply });\n }\n\n // 结果统计\n const successfulToolCalls = allToolCalls.filter(tc => {\n const details = tc.result.details;\n return !(details && typeof details === \"object\" && Boolean((details as { error?: unknown }).error));\n }).length;\n const failedToolCalls = allToolCalls.length - successfulToolCalls;\n\n const metrics: AgentLoopMetrics = {\n roundCount: usedRounds,\n totalToolCalls: allToolCalls.length,\n successfulToolCalls,\n failedToolCalls,\n toolSuccessRate: allToolCalls.length > 0\n ? Number((successfulToolCalls / allToolCalls.length).toFixed(4))\n : 1,\n recoveryCount,\n redundantInterceptCount,\n snapshotReadCount,\n latestSnapshotSize: pageContext.latestSnapshot?.length ?? 0,\n avgSnapshotSize: snapshotReadCount > 0 ? Math.round(snapshotSizeTotal / snapshotReadCount) : 0,\n maxSnapshotSize: snapshotSizeMax,\n inputTokens,\n outputTokens,\n };\n\n // 统一发出指标回调\n callbacks?.onMetrics?.(metrics);\n\n return { reply: finalReply, toolCalls: allToolCalls, messages: resultMessages, metrics };\n}\n\n// ─── Re-exports(维持外部 API 不变)───\nexport { wrapSnapshot } from \"./snapshot.js\";\nexport type {\n AgentLoopParams,\n AgentLoopResult,\n AgentLoopCallbacks,\n AgentLoopMetrics,\n RoundStabilityWaitOptions,\n} from \"./types.js\";\n","/**\n * AI Client 共享常量(中)/ Shared constants and helpers for AI clients (EN).\n */\nimport type { AIClientConfig } from \"./index.js\";\n\n// ─── Provider 端点映射 ───\n\n/** 默认端点映射(中)/ Default API endpoints by provider (EN). */\nexport const PROVIDER_ENDPOINTS: Record<string, string> = {\n openai: \"https://api.openai.com/v1\",\n copilot: \"https://models.inference.ai.azure.com\",\n anthropic: \"https://api.anthropic.com\",\n deepseek: \"https://api.deepseek.com\",\n doubao: \"https://ark.cn-beijing.volces.com/api/v3\",\n qwen: \"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n};\n\n// ─── 共享工具函数 ───\n\n/** 校验 provider(中)/ Validate provider support (EN). */\nexport function validateProvider(provider: string): void {\n if (!PROVIDER_ENDPOINTS[provider]) {\n const supported = Object.keys(PROVIDER_ENDPOINTS).join(\", \");\n throw new Error(\n `Unknown AI provider: ${provider}. Supported: ${supported}`,\n );\n }\n}\n\n/** 解析 baseURL(中)/ Resolve API base URL (EN). */\nexport function resolveBaseURL(config: AIClientConfig): string {\n return config.baseURL ?? PROVIDER_ENDPOINTS[config.provider] ?? \"\";\n}\n\n/**\n * 清理 schema(中)/ Clean non-serializable fields from schema (EN).\n */\nexport function cleanSchema(schema: unknown): unknown {\n return JSON.parse(JSON.stringify(schema));\n}\n","/** SSE 事件处理器(中)/ SSE JSON event handler (EN). Return false to stop early. */\nexport type SSEJSONHandler = (\n event: Record<string, unknown>,\n meta: { event?: string; rawData: string },\n) => void | boolean | Promise<void | boolean>;\n\n/** SSE 配置(中)/ SSE consume options (EN). */\nexport type SSEConsumeOptions = {\n /** 单次读取超时(毫秒)。不传则不超时。 */\n readTimeoutMs?: number;\n /** 是否在遇到 [DONE] 时提前结束(默认 true)。 */\n stopOnDone?: boolean;\n};\n\n/**\n * 通用 SSE(JSON) 消费器(中)/ Generic SSE(JSON) consumer (EN).\n *\n * 读取 response.body,按 SSE 规则拼装并分发 JSON data 事件。\n * Reads response body, assembles SSE frames, and dispatches JSON data events.\n */\nexport async function consumeSSEJSON(\n response: Response,\n onEvent: SSEJSONHandler,\n options: SSEConsumeOptions = {},\n): Promise<void> {\n if (!response.body) return;\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n const stopOnDone = options.stopOnDone ?? true;\n\n let buffer = \"\";\n let currentEvent: string | undefined;\n let dataLines: string[] = [];\n let stoppedByDone = false;\n\n async function readChunk() {\n const readTimeoutMs = options.readTimeoutMs;\n if (!readTimeoutMs || readTimeoutMs <= 0) {\n return reader.read();\n }\n\n return new Promise<ReadableStreamReadResult<Uint8Array>>((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(new Error(`SSE read timeout (${readTimeoutMs}ms)`));\n }, readTimeoutMs);\n\n reader.read().then(\n (value) => {\n clearTimeout(timer);\n resolve(value);\n },\n (error) => {\n clearTimeout(timer);\n reject(error);\n },\n );\n });\n }\n\n async function flushEvent(): Promise<boolean> {\n if (dataLines.length === 0) {\n currentEvent = undefined;\n return true;\n }\n\n const rawData = dataLines.join(\"\\n\").trim();\n const event = currentEvent;\n dataLines = [];\n currentEvent = undefined;\n\n if (!rawData) return true;\n if (stopOnDone && rawData === \"[DONE]\") {\n stoppedByDone = true;\n return false;\n }\n\n try {\n const parsed = JSON.parse(rawData) as Record<string, unknown>;\n const shouldContinue = await onEvent(parsed, { event, rawData });\n if (shouldContinue === false) return false;\n } catch {\n // 非 JSON data 事件忽略\n }\n\n return true;\n }\n\n while (true) {\n const { done, value } = await readChunk();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() ?? \"\";\n\n for (const rawLine of lines) {\n const line = rawLine.endsWith(\"\\r\") ? rawLine.slice(0, -1) : rawLine;\n const trimmed = line.trim();\n\n if (!trimmed) {\n const shouldContinue = await flushEvent();\n if (!shouldContinue) break;\n continue;\n }\n if (trimmed.startsWith(\":\")) continue;\n if (trimmed.startsWith(\"event:\")) {\n currentEvent = trimmed.slice(6).trim() || undefined;\n continue;\n }\n if (trimmed.startsWith(\"data:\")) {\n dataLines.push(trimmed.slice(5).trimStart());\n }\n }\n\n if (stoppedByDone) break;\n }\n\n if (!stoppedByDone) {\n await flushEvent();\n } else {\n await reader.cancel().catch(() => undefined);\n }\n}\n","/**\n * 可继承 AI 客户端基类(中)/ Extensible base AI client class (EN).\n *\n * 支持注入 chatHandler 或子类覆写 chat。\n * Supports injected chatHandler or subclass override.\n */\nimport type { AIChatResponse, AIClient, AIMessage } from \"../types.js\";\nimport type { ToolDefinition } from \"../tool-registry.js\";\nimport {\n consumeSSEJSON,\n type SSEConsumeOptions,\n type SSEJSONHandler,\n} from \"./sse.js\";\n\n// ─── 类型定义 ───\n\n/** chat 入参(中)/ Chat handler params aligned with AIClient.chat (EN). */\nexport type ChatHandlerParams = {\n /** 系统提示词 */\n systemPrompt: string;\n /** 对话消息列表 */\n messages: AIMessage[];\n /** 可用工具定义列表 */\n tools?: ToolDefinition[];\n};\n\n/** BaseAIClient 选项(中)/ BaseAIClient constructor options (EN). */\nexport type BaseAIClientOptions = {\n /** 对话处理函数 — 接收 ChatHandlerParams,返回 AIChatResponse */\n chatHandler: (params: ChatHandlerParams) => Promise<AIChatResponse>;\n};\n\nexport {\n consumeSSEJSON,\n type SSEConsumeOptions,\n type SSEJSONHandler,\n} from \"./sse.js\";\n\n// ─── BaseAIClient 类 ───\n\n/**\n * BaseAIClient 实现(中)/ BaseAIClient implementation of AIClient (EN).\n */\nexport class BaseAIClient implements AIClient {\n /** 用户提供的对话处理函数 */\n protected chatHandler: (params: ChatHandlerParams) => Promise<AIChatResponse>;\n\n constructor(options: BaseAIClientOptions) {\n this.chatHandler = options.chatHandler;\n }\n\n /**\n * 发送对话请求(中)/ Dispatch chat request via handler (EN).\n */\n async chat(params: ChatHandlerParams): Promise<AIChatResponse> {\n return this.chatHandler(params);\n }\n\n /** SSE 消费复用入口(中)/ Reusable SSE(JSON) consumer for subclasses (EN). */\n protected async consumeSSEJSON(\n response: Response,\n onEvent: SSEJSONHandler,\n options?: SSEConsumeOptions,\n ): Promise<void> {\n return consumeSSEJSON(response, onEvent, options);\n }\n}\n","/**\n * OpenAI/Copilot 客户端(中)/ OpenAI-compatible client implementation (EN).\n */\nimport type { AIChatResponse, AIMessage, AIToolCall } from \"../types.js\";\nimport type { AIClientConfig, ChatParams, ChatRequestInit } from \"./index.js\";\nimport { BaseAIClient } from \"./custom.js\";\nimport type { ChatHandlerParams } from \"./custom.js\";\nimport { consumeSSEJSON } from \"./sse.js\";\nimport { resolveBaseURL, cleanSchema } from \"./constants.js\";\n\n// ─── OpenAI 原始 API 响应类型 ───\n\n/** OpenAI 工具调用原始类型(中)/ Raw OpenAI tool_call shape (EN). */\ntype OpenAIRawToolCall = {\n id: string;\n type: \"function\";\n function: { name: string; arguments: string };\n};\n\n/** OpenAI 原始响应类型(中)/ Raw OpenAI chat completion response (EN). */\ntype OpenAIRawResponse = {\n choices?: Array<{\n message: {\n content: string | null;\n tool_calls?: OpenAIRawToolCall[];\n };\n }>;\n usage?: {\n prompt_tokens?: number;\n completion_tokens?: number;\n };\n};\n\n// ─── OpenAIClient 类 ───\n\n/**\n * OpenAIClient 类(中)/ OpenAIClient class for OpenAI & Copilot (EN).\n */\nexport class OpenAIClient extends BaseAIClient {\n /** AI 客户端配置(provider / model / apiKey / baseURL) */\n protected config: AIClientConfig;\n\n constructor(config: AIClientConfig) {\n // 注入 chatHandler — 根据 config.stream 选择流式或 JSON(默认流式)\n super({\n chatHandler: async (params: ChatHandlerParams): Promise<AIChatResponse> => {\n const req = buildOpenAIRequest(this.config, params);\n const useStream = this.config.stream ?? true;\n\n if (!useStream) {\n const res = await fetch(req.url, {\n method: req.method,\n headers: req.headers,\n body: req.body,\n });\n\n if (!res.ok) {\n const errText = await res.text();\n throw new Error(`AI API ${res.status}: ${errText.slice(0, 500)}`);\n }\n\n const data = await res.json();\n return parseOpenAIResponse(data);\n }\n\n // 流式模式:请求体已在 buildOpenAIRequest 中包含 stream 字段\n const streamRes = await fetch(req.url, {\n method: req.method,\n headers: req.headers,\n body: req.body,\n });\n\n if (!streamRes.ok) {\n const errText = await streamRes.text();\n throw new Error(`AI API ${streamRes.status}: ${errText.slice(0, 500)}`);\n }\n\n const contentType = streamRes.headers.get(\"content-type\") ?? \"\";\n if (contentType.includes(\"application/json\")) {\n const data = await streamRes.json();\n return parseOpenAIResponse(data);\n }\n\n return parseOpenAIStream(streamRes, 20000);\n },\n });\n this.config = config;\n }\n}\n\n// ─── 底层 API:请求构建 ───\n\n/**\n * 构建 OpenAI 请求(中)/ Build OpenAI chat request payload (EN).\n */\nexport function buildOpenAIRequest(\n config: AIClientConfig,\n params: ChatParams,\n): ChatRequestInit {\n const baseURL = resolveBaseURL(config);\n const { systemPrompt, messages, tools } = params;\n\n // 转换工具定义为 OpenAI function calling 格式\n const openaiTools = tools?.map((t) => ({\n type: \"function\" as const,\n function: {\n name: t.name,\n description: t.description,\n parameters: cleanSchema(t.schema),\n },\n }));\n\n // 转换消息为 OpenAI 格式\n const openaiMessages = convertMessages(systemPrompt, messages);\n\n // 构建请求体\n const body: Record<string, unknown> = {\n model: config.model,\n messages: openaiMessages,\n temperature: 0.3,\n max_tokens: 4096,\n };\n\n if (config.stream ?? true) {\n body.stream = true;\n body.stream_options = { include_usage: true };\n }\n\n if (openaiTools && openaiTools.length > 0) {\n body.tools = openaiTools;\n body.tool_choice = \"auto\";\n body.parallel_tool_calls = true;\n }\n\n return {\n url: `${baseURL}/chat/completions`,\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${config.apiKey}`,\n },\n body: JSON.stringify(body),\n };\n}\n\n// ─── 响应解析 ───\n\n/**\n * 解析 OpenAI 响应(中)/ Parse raw OpenAI response into AIChatResponse (EN).\n */\nexport function parseOpenAIResponse(data: unknown): AIChatResponse {\n const d = data as OpenAIRawResponse;\n const choice = d.choices?.[0];\n if (!choice) throw new Error(\"AI 未返回有效响应\");\n\n const msg = choice.message;\n\n // 解析工具调用:arguments 是 JSON 字符串,需要 parse 为对象\n const toolCalls: AIToolCall[] | undefined = msg.tool_calls?.map((tc) => ({\n id: tc.id,\n name: tc.function.name,\n input: JSON.parse(tc.function.arguments),\n }));\n\n return {\n text: msg.content || undefined,\n toolCalls: toolCalls?.length ? toolCalls : undefined,\n usage: d.usage\n ? {\n inputTokens: d.usage.prompt_tokens ?? 0,\n outputTokens: d.usage.completion_tokens ?? 0,\n }\n : undefined,\n };\n}\n\n// ─── 内部辅助函数 ───\n\n/**\n * 消息转换(中)/ Convert unified messages to OpenAI format (EN).\n */\nfunction convertMessages(\n systemPrompt: string,\n messages: AIMessage[],\n): Record<string, unknown>[] {\n const result: Record<string, unknown>[] = [\n { role: \"system\", content: systemPrompt },\n ];\n\n for (const m of messages) {\n if (m.role === \"tool\" && Array.isArray(m.content)) {\n // 工具结果 → 每个结果单独一条 tool 消息(OpenAI 要求按 tool_call_id 对应)\n for (const tc of m.content) {\n result.push({\n role: \"tool\",\n content: tc.result,\n tool_call_id: tc.toolCallId,\n });\n }\n } else if (m.role === \"assistant\" && m.toolCalls?.length) {\n // AI 回复含工具调用 → 带 tool_calls 字段\n result.push({\n role: \"assistant\",\n content: typeof m.content === \"string\" ? m.content : null,\n tool_calls: m.toolCalls.map((tc) => ({\n id: tc.id,\n type: \"function\",\n function: {\n name: tc.name,\n arguments: JSON.stringify(tc.input),\n },\n })),\n });\n } else {\n // 普通消息(user / assistant 纯文本)\n result.push({\n role: m.role,\n content:\n typeof m.content === \"string\"\n ? m.content\n : JSON.stringify(m.content),\n });\n }\n }\n\n return result;\n}\n\n// ─── 流式响应解析 ───\n\n/** 流式 tool_call 增量类型(中)/ Tool-call delta type in SSE stream (EN). */\ntype OpenAIStreamToolCallDelta = {\n index: number;\n id?: string;\n function?: { name?: string; arguments?: string };\n};\n\n/** 流式 chunk 类型(中)/ SSE chunk type (EN). */\ntype OpenAIStreamChunk = {\n choices?: Array<{\n delta: {\n content?: string;\n tool_calls?: OpenAIStreamToolCallDelta[];\n };\n }>;\n usage?: { prompt_tokens?: number; completion_tokens?: number };\n};\n\n/**\n * 解析 OpenAI SSE(中)/ Parse OpenAI SSE stream into unified response (EN).\n */\nexport async function parseOpenAIStream(\n response: Response,\n readTimeoutMs = 20000,\n): Promise<AIChatResponse> {\n // 回退:无 ReadableStream 支持\n if (!response.body) {\n const data = await response.json();\n return parseOpenAIResponse(data);\n }\n\n let text = \"\";\n const toolCallMap = new Map<number, { id: string; name: string; arguments: string }>();\n let usage: AIChatResponse[\"usage\"];\n await consumeSSEJSON(\n response,\n (event) => {\n const chunk = event as OpenAIStreamChunk;\n const delta = chunk.choices?.[0]?.delta;\n\n if (delta?.content) text += delta.content;\n\n if (delta?.tool_calls) {\n for (const tc of delta.tool_calls) {\n const idx = tc.index ?? 0;\n const existing = toolCallMap.get(idx);\n if (existing) {\n if (tc.function?.arguments) existing.arguments += tc.function.arguments;\n } else {\n toolCallMap.set(idx, {\n id: tc.id ?? \"\",\n name: tc.function?.name ?? \"\",\n arguments: tc.function?.arguments ?? \"\",\n });\n }\n }\n }\n\n if (chunk.usage) {\n usage = {\n inputTokens: chunk.usage.prompt_tokens ?? 0,\n outputTokens: chunk.usage.completion_tokens ?? 0,\n };\n }\n },\n { readTimeoutMs, stopOnDone: true },\n );\n\n // 组装工具调用\n const toolCalls: AIToolCall[] = [];\n for (const [, tc] of [...toolCallMap.entries()].sort((a, b) => a[0] - b[0])) {\n try {\n toolCalls.push({ id: tc.id, name: tc.name, input: JSON.parse(tc.arguments) });\n } catch {\n // 工具参数 JSON 解析失败,跳过\n }\n }\n\n return {\n text: text || undefined,\n toolCalls: toolCalls.length > 0 ? toolCalls : undefined,\n usage,\n };\n}\n","/**\n * Anthropic 客户端实现(中)/ Anthropic Messages API client implementation (EN).\n */\nimport type { AIChatResponse, AIMessage, AIToolCall } from \"../types.js\";\nimport type { AIClientConfig, ChatParams, ChatRequestInit } from \"./index.js\";\nimport { BaseAIClient } from \"./custom.js\";\nimport type { ChatHandlerParams } from \"./custom.js\";\nimport { consumeSSEJSON } from \"./sse.js\";\nimport { resolveBaseURL, cleanSchema } from \"./constants.js\";\n\n// ─── Anthropic 原始 API 响应类型 ───\n\n/** Anthropic 文本块(中)/ Anthropic text block (EN). */\ntype AnthropicTextBlock = {\n type: \"text\";\n text: string;\n};\n\n/** Anthropic 工具调用块(中)/ Anthropic tool_use block (EN). */\ntype AnthropicToolUseBlock = {\n type: \"tool_use\";\n id: string;\n name: string;\n input: unknown;\n};\n\n/** Anthropic 内容块联合类型(中)/ Anthropic content block union (EN). */\ntype AnthropicContentBlock = AnthropicTextBlock | AnthropicToolUseBlock;\n\n/** Anthropic 原始响应类型(中)/ Raw Anthropic response type (EN). */\ntype AnthropicRawResponse = {\n content?: AnthropicContentBlock[];\n usage?: {\n input_tokens: number;\n output_tokens: number;\n };\n};\n\n// ─── AnthropicClient 类 ───\n\n/**\n * AnthropicClient 类(中)/ AnthropicClient class (EN).\n */\nexport class AnthropicClient extends BaseAIClient {\n /** AI 客户端配置(provider / model / apiKey / baseURL) */\n protected config: AIClientConfig;\n\n constructor(config: AIClientConfig) {\n // 注入 chatHandler — 根据 config.stream 选择流式或 JSON(默认流式)\n super({\n chatHandler: async (params: ChatHandlerParams): Promise<AIChatResponse> => {\n const req = buildAnthropicRequest(this.config, params);\n const useStream = this.config.stream ?? true;\n\n if (!useStream) {\n const res = await fetch(req.url, {\n method: req.method,\n headers: req.headers,\n body: req.body,\n });\n\n if (!res.ok) {\n const errText = await res.text();\n throw new Error(`AI API ${res.status}: ${errText.slice(0, 500)}`);\n }\n\n const data = await res.json();\n return parseAnthropicResponse(data);\n }\n\n // 流式模式:请求体已在 buildAnthropicRequest 中包含 stream 字段\n const res = await fetch(req.url, {\n method: req.method,\n headers: req.headers,\n body: req.body,\n });\n\n if (!res.ok) {\n const errText = await res.text();\n throw new Error(`AI API ${res.status}: ${errText.slice(0, 500)}`);\n }\n\n const contentType = res.headers.get(\"content-type\") ?? \"\";\n if (contentType.includes(\"application/json\")) {\n const data = await res.json();\n return parseAnthropicResponse(data);\n }\n\n return parseAnthropicStream(res);\n },\n });\n this.config = config;\n }\n}\n\n// ─── 底层 API:请求构建 ───\n\n/**\n * 构建 Anthropic 请求(中)/ Build Anthropic Messages API request (EN).\n */\nexport function buildAnthropicRequest(\n config: AIClientConfig,\n params: ChatParams,\n): ChatRequestInit {\n const baseURL = resolveBaseURL(config);\n const { systemPrompt, messages, tools } = params;\n\n // 转换工具定义为 Anthropic 格式(input_schema 而非 parameters)\n const anthropicTools = tools?.map((t) => ({\n name: t.name,\n description: t.description,\n input_schema: cleanSchema(t.schema),\n }));\n\n // 转换消息为 Anthropic 格式(过滤掉 system 角色消息)\n const anthropicMessages = convertMessages(messages);\n\n // 构建请求体 — system 作为顶层字段\n const body: Record<string, unknown> = {\n model: config.model,\n max_tokens: config.model.includes(\"opus\") ? 16384 : 8192,\n system: systemPrompt,\n messages: anthropicMessages,\n };\n\n if (config.stream ?? true) {\n body.stream = true;\n }\n\n if (anthropicTools && anthropicTools.length > 0) {\n body.tools = anthropicTools;\n }\n\n return {\n url: `${baseURL}/v1/messages`,\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": config.apiKey,\n \"anthropic-version\": \"2023-06-01\",\n },\n body: JSON.stringify(body),\n };\n}\n\n// ─── 响应解析 ───\n\n/**\n * 解析 Anthropic 响应(中)/ Parse raw Anthropic response (EN).\n */\nexport function parseAnthropicResponse(data: unknown): AIChatResponse {\n const d = data as AnthropicRawResponse;\n\n // 提取所有文本块,合并为单个字符串\n const text = d.content\n ?.filter((b): b is AnthropicTextBlock => b.type === \"text\")\n .map((b) => b.text)\n .join(\"\");\n\n // 提取所有工具调用块\n const toolCalls: AIToolCall[] | undefined = d.content\n ?.filter((b): b is AnthropicToolUseBlock => b.type === \"tool_use\")\n .map((b) => ({\n id: b.id,\n name: b.name,\n input: b.input,\n }));\n\n return {\n text: text || undefined,\n toolCalls: toolCalls?.length ? toolCalls : undefined,\n usage: d.usage\n ? {\n inputTokens: d.usage.input_tokens,\n outputTokens: d.usage.output_tokens,\n }\n : undefined,\n };\n}\n\n// ─── 内部辅助函数 ───\n\n/**\n * 消息格式转换(中)/ Convert unified messages to Anthropic format (EN).\n */\nfunction convertMessages(\n messages: AIMessage[],\n): Record<string, unknown>[] {\n return messages\n .filter((m) => m.role !== \"system\")\n .map((m) => {\n if (m.role === \"tool\" && Array.isArray(m.content)) {\n // 工具结果 → Anthropic 用 user 角色 + tool_result content block\n return {\n role: \"user\" as const,\n content: m.content.map((tc) => ({\n type: \"tool_result\" as const,\n tool_use_id: tc.toolCallId,\n content: tc.result,\n })),\n };\n }\n if (m.role === \"assistant\" && m.toolCalls?.length) {\n // AI 回复含工具调用 → text block + tool_use blocks\n const content: Record<string, unknown>[] = [];\n if (m.content && typeof m.content === \"string\") {\n content.push({ type: \"text\", text: m.content });\n }\n for (const tc of m.toolCalls) {\n content.push({\n type: \"tool_use\",\n id: tc.id,\n name: tc.name,\n input: tc.input,\n });\n }\n return { role: \"assistant\" as const, content };\n }\n // 普通消息(user / assistant 纯文本)\n return {\n role: m.role as \"user\" | \"assistant\",\n content:\n typeof m.content === \"string\"\n ? m.content\n : JSON.stringify(m.content),\n };\n });\n}\n\n// ─── 流式响应解析 ───\n\n/**\n * 解析 Anthropic SSE(中)/ Parse Anthropic SSE stream (EN).\n */\nexport async function parseAnthropicStream(response: Response): Promise<AIChatResponse> {\n // 回退:无 ReadableStream 支持\n if (!response.body) {\n const data = await response.json();\n return parseAnthropicResponse(data);\n }\n\n let text = \"\";\n const toolCalls: AIToolCall[] = [];\n let currentToolUse: { id: string; name: string; inputJson: string } | null = null;\n let inputTokens = 0;\n let outputTokens = 0;\n await consumeSSEJSON(\n response,\n (event) => {\n switch (event.type) {\n case \"message_start\": {\n const msg = event.message as { usage?: { input_tokens?: number } } | undefined;\n inputTokens = msg?.usage?.input_tokens ?? 0;\n break;\n }\n\n case \"content_block_start\": {\n const block = event.content_block as { type: string; id?: string; name?: string } | undefined;\n if (block?.type === \"tool_use\") {\n currentToolUse = { id: block.id ?? \"\", name: block.name ?? \"\", inputJson: \"\" };\n }\n break;\n }\n\n case \"content_block_delta\": {\n const delta = event.delta as { type: string; text?: string; partial_json?: string } | undefined;\n if (delta?.type === \"text_delta\") {\n text += delta.text ?? \"\";\n } else if (delta?.type === \"input_json_delta\" && currentToolUse) {\n currentToolUse.inputJson += delta.partial_json ?? \"\";\n }\n break;\n }\n\n case \"content_block_stop\":\n if (currentToolUse) {\n try {\n toolCalls.push({\n id: currentToolUse.id,\n name: currentToolUse.name,\n input: JSON.parse(currentToolUse.inputJson || \"{}\"),\n });\n } catch {\n // 工具参数 JSON 解析失败,跳过\n }\n currentToolUse = null;\n }\n break;\n\n case \"message_delta\": {\n const deltaUsage = (event as { usage?: { output_tokens?: number } }).usage;\n outputTokens = deltaUsage?.output_tokens ?? 0;\n break;\n }\n }\n },\n { stopOnDone: false },\n );\n\n return {\n text: text || undefined,\n toolCalls: toolCalls.length > 0 ? toolCalls : undefined,\n usage: inputTokens > 0 || outputTokens > 0 ? { inputTokens, outputTokens } : undefined,\n };\n}\n","/**\n * DeepSeek 客户端封装(中)/ DeepSeek client wrapper (EN).\n *\n * DeepSeek 与 OpenAI Chat Completions 兼容,直接复用 OpenAIClient。\n * DeepSeek is OpenAI-compatible, so it reuses OpenAIClient behavior.\n */\nimport { OpenAIClient } from \"./openai.js\";\n\n/**\n * DeepSeek 客户端类(中)/ DeepSeek client class extending OpenAIClient (EN).\n */\nexport class DeepSeekClient extends OpenAIClient {}\n","/**\n * Doubao 客户端封装(中)/ Doubao client wrapper (EN).\n *\n * Doubao(火山引擎 Ark)与 OpenAI Chat Completions 兼容,直接复用 OpenAIClient。\n * Doubao (Volcengine Ark) is OpenAI-compatible, so it reuses OpenAIClient behavior.\n */\nimport { OpenAIClient } from \"./openai.js\";\n\n/**\n * Doubao 客户端类(中)/ Doubao client class extending OpenAIClient (EN).\n */\nexport class DoubaoClient extends OpenAIClient {}\n","/**\n * Qwen 客户端封装(中)/ Qwen client wrapper (EN).\n *\n * Qwen(阿里云百炼兼容模式)与 OpenAI Chat Completions 兼容,直接复用 OpenAIClient。\n * Qwen (DashScope compatible mode) is OpenAI-compatible, so it reuses OpenAIClient behavior.\n */\nimport { OpenAIClient } from \"./openai.js\";\n\n/**\n * Qwen 客户端类(中)/ Qwen client class extending OpenAIClient (EN).\n */\nexport class QwenClient extends OpenAIClient {}\n","/**\n * AI 客户端主入口(中)/ AI client entrypoint based on fetch (EN).\n *\n * 提供 provider 路由与统一类型导出。\n * Provides provider routing and unified type exports.\n */\nimport type { AIClient, AIChatResponse, AIMessage } from \"../types.js\";\nimport type { ToolDefinition } from \"../tool-registry.js\";\nimport { validateProvider } from \"./constants.js\";\nimport { OpenAIClient } from \"./openai.js\";\nimport { AnthropicClient } from \"./anthropic.js\";\nimport { DeepSeekClient } from \"./deepseek.js\";\nimport { DoubaoClient } from \"./doubao.js\";\nimport { QwenClient } from \"./qwen.js\";\n\n// Re-export 类型,方便外部统一从 ai-client 导入\nexport type { AIClient, AIChatResponse, AIMessage, AIToolCall } from \"../types.js\";\n\n// Re-export 客户端类(基类 + OpenAI + Anthropic)\nexport { BaseAIClient, type BaseAIClientOptions, type ChatHandlerParams } from \"./custom.js\";\nexport { OpenAIClient, parseOpenAIStream } from \"./openai.js\";\nexport { AnthropicClient, parseAnthropicStream } from \"./anthropic.js\";\nexport { DeepSeekClient } from \"./deepseek.js\";\nexport { DoubaoClient } from \"./doubao.js\";\nexport { QwenClient } from \"./qwen.js\";\n\n// ─── 公共类型定义 ───\n\n/** AI 客户端配置(中)/ AI client configuration (EN). */\nexport type AIClientConfig = {\n /** AI 提供商: \"openai\" | \"copilot\" | \"anthropic\" | \"deepseek\" | \"doubao\" | \"qwen\" */\n provider: string;\n /** 模型名称,如 \"gpt-4o\"、\"claude-sonnet-4-20250514\" */\n model: string;\n /** API Key / Token */\n apiKey: string;\n /** 自定义 API 基础 URL(可选,如本地 Ollama: http://localhost:11434/v1) */\n baseURL?: string;\n /** 是否启用流式输出(SSE)。默认 true;传 false 时使用 JSON 非流式响应。 */\n stream?: boolean;\n};\n\n/** 统一 chat 入参(中)/ Unified chat parameters (EN). */\nexport type ChatParams = {\n /** 系统提示词 */\n systemPrompt: string;\n /** 对话消息列表 */\n messages: AIMessage[];\n /** 可用工具定义列表 */\n tools?: ToolDefinition[];\n};\n\n/**\n * HTTP 请求对象(中)/ Built HTTP request init payload (EN).\n */\nexport type ChatRequestInit = {\n /** 请求 URL */\n url: string;\n /** HTTP 方法 */\n method: \"POST\";\n /** 请求头 */\n headers: Record<string, string>;\n /** 请求体(JSON 字符串) */\n body: string;\n};\n\n// ─── 高层 API ───\n\n/**\n * 创建 AI 客户端(中)/ Create AI client by provider (EN).\n */\nexport function createAIClient(config: AIClientConfig): AIClient {\n validateProvider(config.provider);\n\n switch (config.provider) {\n case \"openai\":\n case \"copilot\":\n return new OpenAIClient(config);\n case \"doubao\":\n return new DoubaoClient(config);\n case \"qwen\":\n return new QwenClient(config);\n case \"anthropic\":\n return new AnthropicClient(config);\n case \"deepseek\":\n return new DeepSeekClient(config);\n default:\n throw new Error(\n `Unknown AI provider: ${config.provider}. Supported: openai, copilot, anthropic, deepseek, doubao, qwen`,\n );\n }\n}\n","/**\n * Tool Registry — 工具注册表,负责工具的注册、查询和分发。\n *\n * 实例化设计 — 每个 Agent 创建独立的 ToolRegistry,避免全局状态污染:\n *\n * // Node 端\n * const registry = new ToolRegistry();\n * registerBuiltinTools(registry); // 注册 exec, file, browser...\n * await executeAgentLoop({ registry, ... });\n *\n * // Web 端\n * const registry = new ToolRegistry();\n * registerWebTools(registry); // 注册 dom, navigate...\n * await executeAgentLoop({ registry, ... });\n *\n * 优点:\n * - 多实例安全:Node Agent 和 Web Agent 可并行运行,工具列表互不干扰\n * - 测试隔离:每个 test case 创建独立 registry,无需清理全局状态\n * - 可组合:可按需注册不同工具子集\n */\nimport type { TObject } from \"@sinclair/typebox\";\nexport { jsonResult, readNumberParam, readStringParam } from \"./tool-params.js\";\n\n/**\n * 工具执行结果 — 每个工具的 execute() 必须返回此类型。\n */\nexport type ToolCallResult = {\n /** 返回内容(字符串文本或结构化对象,最终会序列化后发给 AI) */\n content: string | Record<string, unknown>;\n /** 可选的额外细节(用于日志记录、调试等,不直接发给 AI) */\n details?: Record<string, unknown>;\n};\n\n/**\n * 工具定义 — 注册工具时需要提供的完整描述。\n *\n * 这四个字段分别告诉 AI「叫什么名字」「能做什么」「需要什么参数」「怎么执行」:\n * - name + description → AI 根据用户意图选择合适的工具\n * - schema → AI 生成符合格式的参数 JSON\n * - execute → 实际执行逻辑\n */\nexport type ToolDefinition = {\n /** 工具名称(AI 通过此名称调用,如 \"exec\"、\"file_read\") */\n name: string;\n /** 工具描述(AI 据此判断何时使用这个工具) */\n description: string;\n /** 参数的 JSON Schema(TypeBox 定义,描述工具接受哪些参数及其类型) */\n schema: TObject;\n /** 执行函数 — 接收 AI 传入的参数,返回执行结果 */\n execute: (params: Record<string, unknown>) => Promise<ToolCallResult>;\n};\n\n/**\n * 工具注册表实例 — 管理一组工具的注册、查询和分发。\n *\n * 每个 Agent 拥有独立的 ToolRegistry 实例,从而:\n * - Node Agent 的 exec/file 工具不会泄漏到 Web Agent\n * - Web Agent 的 dom/navigate 工具不会泄漏到 Node Agent\n * - 测试中不同 case 互不影响\n */\nexport class ToolRegistry {\n private tools = new Map<string, ToolDefinition>();\n\n /** 注册一个工具 */\n register(tool: ToolDefinition): void {\n this.tools.set(tool.name, tool);\n }\n\n /** 获取所有已注册的工具定义列表(发给 AI,告知可用工具) */\n getDefinitions(): ToolDefinition[] {\n return Array.from(this.tools.values());\n }\n\n /** 按名称检查工具是否已注册。 */\n has(name: string): boolean {\n return this.tools.has(name);\n }\n\n /** 按名称注销工具,返回是否删除成功。 */\n unregister(name: string): boolean {\n return this.tools.delete(name);\n }\n\n /**\n * 根据工具名分发并执行工具调用。\n * - 找到工具 → 执行 execute() → 返回结果\n * - 找不到 → 返回错误信息(不抛异常,让 AI 知道工具不存在)\n * - 执行出错 → 捕获异常,返回错误信息(不中断 Agent 循环)\n */\n async dispatch(name: string, input: unknown): Promise<ToolCallResult> {\n const tool = this.tools.get(name);\n if (!tool) {\n return {\n content: `Unknown tool: ${name}`,\n details: { error: true, toolName: name },\n };\n }\n\n try {\n const params = (input ?? {}) as Record<string, unknown>;\n return await tool.execute(params);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return {\n content: `Tool \"${name}\" failed: ${message}`,\n details: { error: true, toolName: name, message },\n };\n }\n }\n}\n","/**\n * 极简系统提示词构建器。\n *\n * 纯函数,不依赖运行时环境;调用方只需传入工具定义和可选扩展指令。\n *\n * 职责:\n * - 组装发送给 AI 的 system prompt(英文正文)\n * - 包含核心规则、工具列表、事件简写表、输出协议\n * - 支持额外自定义指令注入\n *\n * 约束(来自 AGENTS.md §11):\n * - 发送给模型的 prompt 正文统一英文\n * - 中文仅用于源码注释\n *\n * 调用方:\n * - `agent-loop/index.ts` 在循环启动时调用 `buildSystemPrompt()` 构建系统消息\n * - `web/index.ts` 的 WebAgent 通过 systemPrompt 配置传入额外指令\n */\nimport type { ToolDefinition } from \"./tool-registry.js\";\n\n/**\n * 系统提示词构建参数。\n *\n * 所有字段可选:\n * - tools:当前注册的工具列表,用于生成 \"## Available Tools\" 章节\n * - thinkingLevel:AI 思考深度标签(如 \"high\"/\"medium\"),影响推理行为\n * - extraInstructions:额外英文指令,追加到 \"## Extra Instructions\" 章节\n */\nexport type SystemPromptParams = {\n /** 已注册工具列表。 */\n tools?: ToolDefinition[];\n /** AI 思考深度标签。 */\n thinkingLevel?: string;\n /** 额外英文指令(字符串或字符串数组)。 */\n extraInstructions?: string | string[];\n};\n\n/**\n * 规范化额外指令:统一转为非空字符串数组。\n *\n * - 单字符串 → 单元素数组\n * - 字符串数组 → 过滤空值\n * - undefined → 空数组\n */\nfunction normalizeExtraInstructions(input?: string | string[]): string[] {\n if (!input) return [];\n const rawList = Array.isArray(input) ? input : [input];\n return rawList.map(s => s.trim()).filter(Boolean);\n}\n\n/**\n * 构建系统提示词。\n *\n * 输出结构(按章节顺序):\n * 1. **Core Rules** — Agent 核心行为规则\n * - 快照驱动决策:仅基于当前快照 + 剩余任务工作\n * - 增量消费模型:每轮执行后输出 REMAINING 推进任务\n * - hash ID 定位:仅交互元素携带 #hashID,非交互元素为上下文\n * - 事件信号:listeners=\"...\" 标注运行时事件绑定\n * - 批量执行:同轮完成所有独立可见操作\n * - 输入顺序:fill/type 前必须先 focus/click 同一目标\n * - DOM 变化断轮:会改变 DOM 的动作执行后等待下一轮新快照\n * - 停机规则:任务完成后输出 REMAINING: DONE\n *\n * 2. **Listener Abbrevs** — 事件简写对照表\n * - 快照中 listeners=\"clk,inp,chg\" 的简写含义\n * - 与 page-info-tool.ts 的 EVENT_ABBREV 映射一致\n *\n * 3. **Output Contract** — 输出协议\n * - 每轮返回工具调用 + REMAINING 文本行\n *\n * 4. **Available Tools**(可选) — 当前注册的工具及描述\n *\n * 5. **Reasoning Profile**(可选) — 思考深度配置\n *\n * 6. **Extra Instructions**(可选) — 用户自定义额外指令\n *\n * @param params - 构建参数(工具列表、思考深度、额外指令)\n * @returns 完整的系统提示词字符串(英文)\n */\nexport function buildSystemPrompt(params: SystemPromptParams = {}): string {\n const sections: string[] = [];\n\n // ─── 章节 1:角色定义 + 核心规则 ───\n // 这是 prompt 最核心的部分,定义了 Agent 的行为模式和约束。\n // 规则按重要性排列,每条规则对应一个具体的行为约束。\n sections.push(\n [\n \"You are AutoPilot, an AI agent controlling the current web page via tools.\",\n \"\",\n \"## Core Rules\",\n\n // ── 快照驱动决策:不回顾历史,只看当前快照 + 当前剩余任务 ──\n \"- Work from CURRENT snapshot + CURRENT remaining task directly. Do not restate the request.\",\n\n // ── 增量消费模型:每轮输入 = (剩余任务, 上轮已执行, 本轮执行),输出 = 新的剩余任务 ──\n \"- Treat each round as task reduction:\",\n \" Input: (1) current remaining task, (2) previous round executed actions, (3) actions you execute this round.\",\n \" Output: new remaining task after removing this-round actions.\",\n\n // ── hash ID 定位:仅交互元素有 #hashID,非交互元素(标题/标签/文本)无 ID ──\n \"- Use only visible targets from snapshot. Use #hashID as selector. Do not guess CSS selectors.\",\n \"- Only interactive elements (with events, inputs, buttons, links, etc.) carry #hashID. Elements without #hashID are context-only (labels, headings, text) and cannot be targeted.\",\n\n // ── 角色优先标签:[combobox] 表示 role=\"combobox\" 的元素,标签已反映交互模式 ──\n \"- Snapshot tag in brackets may show ARIA role instead of HTML tag when it better describes the interaction pattern (e.g. [combobox] for input with role=\\\"combobox\\\", [slider] for div with role=\\\"slider\\\"). Treat the bracket tag as the primary interaction hint.\",\n\n // ── 事件信号:listeners=\"clk,inp\" 标注运行时事件绑定,辅助 AI 选择操作目标 ──\n \"- listeners=\\\"...\\\" on snapshot indicates bound event handlers (see Listener Abbrevs below). Prefer targets with relevant listeners when multiple candidates look similar.\",\n\n // ── 批量执行:同轮完成所有独立可见操作,减少轮次消耗 ──\n \"- Batch independent visible actions in one round. Do not split one form into many rounds unnecessarily.\",\n\n // ── 输入顺序(强制):fill/type/select_option 前必须先 focus/click 同一目标 ──\n \"- Strict input order (MANDATORY): before every fill/type/select_option, click or focus the SAME target immediately in the SAME round.\",\n \"- Multi-field rule (MANDATORY): execute alternating pairs in one batch: focus/click field A -> fill/type A -> focus/click field B -> fill/type B.\",\n \"- Build the minimal action array from CURRENT snapshot to satisfy the target in one round whenever possible.\",\n \"- Do NOT run focus-only batches (e.g., focus A -> focus B). Each focused input/select target must be followed by its input/select action right away.\",\n \"- Fixed sequence examples: dom.focus(#name) -> dom.fill(#name, \\\"new-name\\\") -> dom.focus(#desc) -> dom.fill(#desc, \\\"new-desc\\\"); dom.click(#select) -> dom.select_option(#select, ...).\",\n\n // ── 步进器规则:计算目标差值,精确点击 |delta| 次 ──\n \"- Deterministic delta rule: for increase/decrease steppers, compute target delta from visible current value and emit exactly |delta| clicks in one round (e.g., +2 => click increase twice). Never overshoot then undo.\",\n\n // ── checkbox/radio:必须瞄准真实 input 控件,不要点 label/容器 ──\n \"- For check/uncheck, target the real input control (checkbox/radio), not nearby text/container nodes.\",\n\n // ── 表单批量规则:一个表单的所有独立字段应在同一轮填完 ──\n \"- Form batch rule: for one visible form, complete all independent fields in one round; do not fill one field then verify repeatedly.\",\n\n // ── DOM 变化断轮:会改变 DOM 的动作(弹窗/导航)执行后停止,等下一轮新快照 ──\n \"- If an action will change DOM (open modal, navigate), stop after that action batch and continue next round with new snapshot.\",\n\n // ── 禁止冗余快照调用:每轮已自动注入快照,不需要手动调用 page_info ──\n \"- Do NOT call page_info (snapshot/query/get_url/get_title). Snapshot is already provided every round.\",\n\n // ── 下拉选择:使用 dom.select_option 或 fill ──\n \"- For dropdown/select, use dom action=select_option (or fill on select).\",\n\n // ── children omitted 定向展开:输出 SNAPSHOT_HINT 请求展开被截断的子节点 ──\n \"- If a required list shows `... (N children omitted)` under a specific container, request focused expansion by outputting `SNAPSHOT_HINT: EXPAND_CHILDREN #<containerRef>`.\",\n \"- After outputting snapshot expansion hint, wait for the next refreshed snapshot before further scrolling/clicking on that list.\",\n\n // ── 验证白名单:除非用户明确要求,否则不验证 input/select 值 ──\n \"- Verification whitelist: do NOT use get_text/get_attr to verify input/select values unless the user explicitly asks for verification.\",\n\n // ── 停机规则:任务完成后立即输出 REMAINING: DONE,不做多余操作 ──\n \"- Stop rule: when the requested state is achieved, stop calling tools. If verification is requested, verify once and then return REMAINING: DONE (no repeated get_text/get_attr on the same target).\",\n\n // ── 自我隔离:不操作 AutoPilot 自身 UI ──\n \"- Do NOT interact with AutoPilot UI unless user explicitly asks.\",\n \"\",\n\n // ─── 章节 2:事件简写对照表 ───\n // 与 page-info-tool.ts 的 EVENT_ABBREV 映射保持一致。\n // AI 通过此表理解快照中 listeners=\"clk,inp,fcs\" 的含义。\n \"## Listener Abbrevs\",\n \"clk=click dbl=dblclick mdn=mousedown mup=mouseup mmv=mousemove mov=mouseover mot=mouseout men=mouseenter mlv=mouseleave pdn=pointerdown pup=pointerup pmv=pointermove tst=touchstart ted=touchend kdn=keydown kup=keyup inp=input chg=change sub=submit fcs=focus blr=blur scl=scroll whl=wheel drg=drag drs=dragstart dre=dragend drp=drop ctx=contextmenu\",\n \"\",\n\n // ─── 章节 3:输出协议 ───\n // 与 agent-loop/messages.ts 的 REMAINING 协议一致:\n // - 有剩余任务 → REMAINING: <剩余任务文本>\n // - 全部完成 → REMAINING: DONE\n \"## Output Contract\",\n \"- Return tool calls for this round.\",\n \"- Also include one plain text line:\",\n \" REMAINING: <new remaining task after this round>\",\n \" or REMAINING: DONE\",\n \"\",\n\n // ─── 章节 4:最小示例 ───\n // 帮助模型理解增量消费模型的具体执行方式。\n \"## Minimal Example\",\n \"Task: click button -> type \\\"abc\\\" in input -> send\",\n \"Round1 execute: click button\",\n \"Remaining: type \\\"abc\\\" in input -> send\",\n \"Round2 execute: type \\\"abc\\\" in input\",\n \"Remaining: send\",\n \"Round3 execute: send\",\n \"Remaining: DONE\",\n ].join(\"\\n\"),\n );\n\n // ─── 章节 5(可选):工具列表 ───\n // 列出当前注册的所有工具及其描述,供 AI 选择使用。\n const tools = params.tools ?? [];\n if (tools.length > 0) {\n const toolLines = tools.map(t => `- **${t.name}**: ${t.description}`);\n sections.push(\n \"## Available Tools\\n\\n\" +\n toolLines.join(\"\\n\") + \"\\n\\n\" +\n \"Use tools when needed to complete the user's request.\"\n );\n }\n\n // ─── 章节 6(可选):思考深度配置 ───\n // 影响模型的推理深度(如 \"high\" 表示复杂任务需深度思考)。\n if (params.thinkingLevel) {\n sections.push(\n [\n \"## Reasoning Profile\",\n `- Thinking level: ${params.thinkingLevel}`,\n ].join(\"\\n\"),\n );\n }\n\n // ─── 章节 7(可选):额外自定义指令 ───\n // 由 WebAgent 使用方通过 extraInstructions 配置传入。\n // 典型用途:业务特定规则、UI 框架提示、测试场景约束等。\n const extraInstructions = normalizeExtraInstructions(params.extraInstructions);\n if (extraInstructions.length > 0) {\n sections.push(\n [\n \"## Extra Instructions\",\n ...extraInstructions.map(line => `- ${line}`),\n ].join(\"\\n\"),\n );\n }\n\n return sections.join(\"\\n\\n\");\n}\n","/**\n * 全局事件监听追踪器(浏览器端)。\n *\n * 设计目标:\n * 1. 从 EventTarget.prototype 统一拦截 add/removeEventListener\n * 2. 仅记录 Element 实例,避免污染 document/window 等\n * 3. 不改变原调用语义:先执行原方法,再做追踪记录\n * 4. 追踪失败时静默兜底,不影响业务代码执行\n */\n\ntype AddEventListenerFn = EventTarget[\"addEventListener\"];\ntype RemoveEventListenerFn = EventTarget[\"removeEventListener\"];\n\nconst elementEventMap = new WeakMap<Element, Set<string>>();\n\nlet installed = false;\nlet originalAddEventListener: AddEventListenerFn | undefined;\nlet originalRemoveEventListener: RemoveEventListenerFn | undefined;\n\nfunction normalizeEventType(type: unknown): string | null {\n if (typeof type !== \"string\") return null;\n const normalized = type.trim().toLowerCase();\n return normalized || null;\n}\n\nfunction canTrackElementTarget(target: EventTarget): target is Element {\n if (typeof Element === \"undefined\") return false;\n return target instanceof Element;\n}\n\nfunction trackElementEvent(target: EventTarget, type: string): void {\n if (!canTrackElementTarget(target)) return;\n const prev = elementEventMap.get(target);\n if (prev) {\n prev.add(type);\n return;\n }\n elementEventMap.set(target, new Set([type]));\n}\n\nfunction untrackElementEvent(target: EventTarget, type: string): void {\n if (!canTrackElementTarget(target)) return;\n const prev = elementEventMap.get(target);\n if (!prev) return;\n prev.delete(type);\n if (prev.size === 0) {\n elementEventMap.delete(target);\n }\n}\n\n/**\n * 安装全局监听追踪补丁(幂等)。\n */\nexport function installEventListenerTracking(): void {\n if (installed) return;\n if (typeof EventTarget === \"undefined\") return;\n\n const proto = EventTarget.prototype;\n const nativeAdd = proto.addEventListener;\n const nativeRemove = proto.removeEventListener;\n\n if (typeof nativeAdd !== \"function\" || typeof nativeRemove !== \"function\") return;\n\n originalAddEventListener = nativeAdd;\n originalRemoveEventListener = nativeRemove;\n\n proto.addEventListener = function patchedAddEventListener(\n this: EventTarget,\n type: string,\n listener: EventListenerOrEventListenerObject | null,\n options?: boolean | AddEventListenerOptions,\n ): void {\n originalAddEventListener?.call(this, type, listener, options);\n try {\n const normalizedType = normalizeEventType(type);\n if (!normalizedType || listener == null) return;\n trackElementEvent(this, normalizedType);\n } catch {\n // 追踪失败不应影响业务逻辑\n }\n };\n\n proto.removeEventListener = function patchedRemoveEventListener(\n this: EventTarget,\n type: string,\n listener: EventListenerOrEventListenerObject | null,\n options?: boolean | EventListenerOptions,\n ): void {\n originalRemoveEventListener?.call(this, type, listener, options);\n try {\n const normalizedType = normalizeEventType(type);\n if (!normalizedType || listener == null) return;\n untrackElementEvent(this, normalizedType);\n } catch {\n // 追踪失败不应影响业务逻辑\n }\n };\n\n installed = true;\n}\n\n/**\n * 读取元素已记录的事件名(排序后返回,便于稳定输出)。\n */\nexport function getTrackedElementEvents(el: Element): string[] {\n const set = elementEventMap.get(el);\n if (!set || set.size === 0) return [];\n return Array.from(set).sort();\n}\n\n/**\n * 判断元素是否存在至少一个被追踪到的事件绑定。\n */\nexport function hasTrackedElementEvents(el: Element): boolean {\n return (elementEventMap.get(el)?.size ?? 0) > 0;\n}\n","/**\n * DOM Tool — 浏览器 DOM 操作工具(结合 Playwright 核心交互模式增强)。\n *\n * 关键改进(参考 Playwright):\n * 1. retarget — 点击时自动重定向到 button/link/label.control\n * 2. scrollIntoView 多策略 — 4 种 block 对齐轮换,解决 sticky 遮挡\n * 3. stable 检查 — rAF 逐帧检测元素位置稳定后再操作\n * 4. hit-target 验证 — elementsFromPoint 检查是否被遮挡\n * 5. 完整点击事件链 — pointermove→pointerdown→mousedown→pointerup→mouseup→click\n * 6. check/uncheck 通过 click — 先检查→click 切换→验证状态\n * 7. press 组合键 — 支持 Control+a, Shift+Enter 等修饰键\n * 8. fill 分类型 — date/color/range 走 setValue,text 类走 selectAll+原生写入\n * 9. 自定义下拉增强 — 更广泛的 option 选择器 + 等待弹出\n * 10. ARIA disabled — 检查祖先链 aria-disabled\n *\n * 运行环境:浏览器 Content Script(直接访问 DOM,无 CDP)。\n */\nimport { Type } from \"@sinclair/typebox\";\nimport type { ToolDefinition, ToolCallResult } from \"../../core/tool-registry.js\";\nimport type { RefStore } from \"../ref-store.js\";\nimport { getTrackedElementEvents } from \"../event-listener-tracker.js\";\n\n// ─── 常量 ───\n\nconst DEFAULT_WAIT_MS = 1200;\n\n/** scrollIntoView 轮换策略(参考 Playwright dom.ts) */\nconst SCROLL_OPTIONS: (ScrollIntoViewOptions | undefined)[] = [\n undefined,\n { block: \"end\", inline: \"end\" },\n { block: \"center\", inline: \"center\" },\n { block: \"start\", inline: \"start\" },\n];\n\n/** fill 时直接 setValue 的 input 类型(参考 Playwright kInputTypesToSetValue) */\nconst INPUT_SET_VALUE_TYPES = new Set([\n \"color\", \"date\", \"time\", \"datetime-local\", \"month\", \"range\", \"week\",\n]);\n\n/** fill 时走 selectText+写入 的 input 类型 */\nconst INPUT_TYPE_INTO_TYPES = new Set([\n \"\", \"email\", \"number\", \"password\", \"search\", \"tel\", \"text\", \"url\",\n]);\n\n/** 不可 fill 的 input 类型 */\nconst INPUT_BLOCKED_TYPES = new Set([\n \"checkbox\", \"radio\", \"file\", \"button\", \"submit\", \"reset\", \"image\",\n]);\n\n/** 修饰键集合 */\nconst MODIFIER_KEYS = new Set([\"Control\", \"Shift\", \"Alt\", \"Meta\"]);\n\n/** 键名→code 映射 */\nconst KEY_CODE_MAP: Record<string, string> = {\n Enter: \"Enter\", Escape: \"Escape\", Esc: \"Escape\",\n Tab: \"Tab\", Space: \"Space\", \" \": \"Space\",\n Backspace: \"Backspace\", Delete: \"Delete\",\n ArrowUp: \"ArrowUp\", ArrowDown: \"ArrowDown\",\n ArrowLeft: \"ArrowLeft\", ArrowRight: \"ArrowRight\",\n Home: \"Home\", End: \"End\", PageUp: \"PageUp\", PageDown: \"PageDown\",\n Control: \"ControlLeft\", Shift: \"ShiftLeft\", Alt: \"AltLeft\", Meta: \"MetaLeft\",\n};\n\nconst FILL_RELEVANT_EVENTS = new Set([\n \"input\", \"change\", \"focus\", \"blur\", \"keydown\",\n \"click\", \"mousedown\", \"pointerdown\",\n]);\n\n// ─── 模块状态 ───\n\nlet activeRefStore: RefStore | undefined;\n\nexport function setActiveRefStore(store: RefStore | undefined): void {\n activeRefStore = store;\n}\n\nexport function getActiveRefStore(): RefStore | undefined {\n return activeRefStore;\n}\n\n// ─── 基础工具 ───\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n\n/** 查询元素:优先 RefStore hash,回退 CSS 选择器 */\nfunction queryElement(selector: string): Element | string {\n try {\n if (selector.startsWith(\"#\") && activeRefStore) {\n const id = selector.slice(1);\n if (activeRefStore.has(id)) {\n const el = activeRefStore.get(id);\n if (!el) return `未找到 ref \"${selector}\" 对应的元素(可能已被移除或快照已过期)`;\n return el;\n }\n }\n const el = document.querySelector(selector);\n if (!el) return `未找到匹配 \"${selector}\" 的元素`;\n return el;\n } catch {\n return `选择器语法错误: ${selector}`;\n }\n}\n\n/** 轮询等待元素出现 */\nasync function waitForElement(selector: string, timeoutMs: number): Promise<Element | string | null> {\n const start = Date.now();\n while (Date.now() - start <= timeoutMs) {\n const r = queryElement(selector);\n if (typeof r !== \"string\") return r;\n if (r.startsWith(\"选择器语法错误\")) return r;\n await sleep(100);\n }\n return null;\n}\n\nfunction resolveWaitMs(params: Record<string, unknown>): number {\n const waitMs = params.waitMs;\n if (typeof waitMs === \"number\" && Number.isFinite(waitMs)) return Math.max(0, Math.floor(waitMs));\n const waitSeconds = params.waitSeconds;\n if (typeof waitSeconds === \"number\" && Number.isFinite(waitSeconds)) return Math.max(0, Math.floor(waitSeconds * 1000));\n return DEFAULT_WAIT_MS;\n}\n\n// ─── 可见性判定(参考 Playwright domUtils.ts) ───\n\n/** 检查元素样式可见性(处理 checkVisibility / details 折叠 / visibility) */\nfunction isStyleVisible(el: Element, style?: CSSStyleDeclaration): boolean {\n style = style ?? window.getComputedStyle(el);\n if (typeof el.checkVisibility === \"function\") {\n if (!el.checkVisibility()) return false;\n } else {\n const det = el.closest(\"details,summary\");\n if (det !== el && det?.nodeName === \"DETAILS\" && !(det as HTMLDetailsElement).open) return false;\n }\n return style.visibility === \"visible\";\n}\n\n/**\n * 元素可见性检查(参考 Playwright isElementVisible+computeBox)。\n * 处理 display:contents / display:none / visibility / opacity / 尺寸为 0。\n */\nfunction isElementVisible(el: Element): boolean {\n if (!(el instanceof HTMLElement || el instanceof SVGElement)) return false;\n if (!el.isConnected) return false;\n const style = window.getComputedStyle(el);\n\n if (style.display === \"contents\") {\n for (let child = el.firstChild; child; child = child.nextSibling) {\n if (child.nodeType === Node.ELEMENT_NODE && isElementVisible(child as Element)) return true;\n if (child.nodeType === Node.TEXT_NODE) {\n const range = document.createRange();\n range.selectNodeContents(child);\n const rects = range.getClientRects();\n for (let i = 0; i < rects.length; i++) {\n if (rects[i].width > 0 && rects[i].height > 0) return true;\n }\n }\n }\n return false;\n }\n if (style.display === \"none\") return false;\n if (!isStyleVisible(el, style)) return false;\n if (style.opacity === \"0\") return false;\n const rect = el.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n}\n\n// ─── disabled / editable 检查(参考 Playwright) ───\n\n/** ARIA disabled:检查元素自身 + 祖先链 aria-disabled(参考 Playwright getAriaDisabled) */\nfunction isElementDisabled(el: Element): boolean {\n if (el instanceof HTMLButtonElement || el instanceof HTMLInputElement ||\n el instanceof HTMLSelectElement || el instanceof HTMLTextAreaElement) {\n if ((el as HTMLButtonElement).disabled) return true;\n }\n let cursor: Element | null = el;\n while (cursor) {\n if (cursor.getAttribute(\"aria-disabled\") === \"true\") return true;\n cursor = cursor.parentElement;\n }\n return false;\n}\n\nfunction isEditableElement(el: Element): boolean {\n if (el instanceof HTMLTextAreaElement) return !el.readOnly;\n if (el instanceof HTMLInputElement) {\n return !INPUT_BLOCKED_TYPES.has(el.type) && !el.readOnly;\n }\n if (el instanceof HTMLSelectElement) return true;\n return el instanceof HTMLElement && el.isContentEditable;\n}\n\n// ─── 稳定性检查(参考 Playwright _checkElementIsStable) ───\n\n/** rAF 逐帧检查元素位置是否连续 3 帧不变 */\nfunction checkElementStable(el: Element, timeoutMs = 800): Promise<boolean> {\n return new Promise((resolve) => {\n let lastRect: DOMRect | undefined;\n let stableCount = 0;\n const start = performance.now();\n function check() {\n if (performance.now() - start > timeoutMs || !el.isConnected) { resolve(false); return; }\n const rect = el.getBoundingClientRect();\n if (lastRect) {\n const same = rect.x === lastRect.x && rect.y === lastRect.y &&\n rect.width === lastRect.width && rect.height === lastRect.height;\n if (!same) { stableCount = 0; } else if (++stableCount >= 3) { resolve(true); return; }\n }\n lastRect = rect;\n requestAnimationFrame(check);\n }\n requestAnimationFrame(check);\n });\n}\n\n// ─── retarget(参考 Playwright injectedScript.retarget) ───\n\ntype RetargetMode = \"none\" | \"follow-label\" | \"button-link\";\n\n/**\n * 将目标重定向到关联的交互控件。\n * - button-link:非交互元素→最近 button/[role=button]/a/[role=link]\n * - follow-label:label→control + 非交互→button/[role=button]/[role=checkbox]/[role=radio]\n */\nfunction retarget(el: Element, mode: RetargetMode): Element {\n if (mode === \"none\") return el;\n if (!el.matches(\"input, textarea, select\") && !(el as HTMLElement).isContentEditable) {\n if (mode === \"button-link\") {\n el = el.closest(\"button, [role=button], a, [role=link]\") || el;\n } else {\n el = el.closest(\"button, [role=button], [role=checkbox], [role=radio]\") || el;\n }\n }\n if (mode === \"follow-label\") {\n if (!el.matches(\"a, input, textarea, button, select, [role=link], [role=button], [role=checkbox], [role=radio]\") &&\n !(el as HTMLElement).isContentEditable) {\n const label = el.closest(\"label\") as HTMLLabelElement | null;\n if (label?.control) el = label.control;\n }\n }\n return el;\n}\n\n// ─── scrollIntoView(参考 Playwright 4 种策略轮换) ───\n\nfunction scrollIntoViewIfNeeded(el: Element, retry = 0): void {\n if (retry === 0 && \"scrollIntoViewIfNeeded\" in el) {\n (el as HTMLElement & { scrollIntoViewIfNeeded: (c?: boolean) => void }).scrollIntoViewIfNeeded(true);\n return;\n }\n const opts = SCROLL_OPTIONS[retry % SCROLL_OPTIONS.length];\n el.scrollIntoView(opts ?? { block: \"center\", inline: \"nearest\" });\n}\n\n// ─── hit-target 检查 ───\n\n/** 检查元素中心点是否被遮挡,返回遮挡元素描述或 null */\nfunction checkHitTarget(el: Element): string | null {\n const rect = el.getBoundingClientRect();\n const x = rect.left + rect.width / 2;\n const y = rect.top + rect.height / 2;\n const topEl = document.elementFromPoint(x, y);\n if (!topEl) return null;\n if (topEl === el || el.contains(topEl) || topEl.contains(el)) return null;\n const sharedLabel = topEl.closest(\"label\");\n if (sharedLabel && sharedLabel.contains(el)) return null;\n return describeElement(topEl);\n}\n\n// ─── actionability 综合检查 ───\n\nfunction ensureActionable(el: Element, action: string, selector: string, force: boolean): ToolCallResult | null {\n if (force) return null;\n if (!el.isConnected) {\n return { content: `\"${selector}\" 元素已脱离文档,无法执行 ${action}`, details: { error: true, code: \"ELEMENT_DETACHED\", action, selector } };\n }\n const readOnlyActions = new Set([\"get_text\", \"get_attr\"]);\n if (!readOnlyActions.has(action) && !isElementVisible(el)) {\n return { content: `\"${selector}\" 元素不可见,无法执行 ${action}`, details: { error: true, code: \"ELEMENT_NOT_VISIBLE\", action, selector } };\n }\n const mutationActions = new Set([\"click\", \"fill\", \"type\", \"press\", \"select_option\", \"clear\", \"check\", \"uncheck\"]);\n if (mutationActions.has(action) && isElementDisabled(el)) {\n return { content: `\"${selector}\" 元素已禁用(disabled/aria-disabled),无法执行 ${action}`, details: { error: true, code: \"ELEMENT_DISABLED\", action, selector } };\n }\n if ([\"fill\", \"type\", \"clear\"].includes(action) && !isEditableElement(el)) {\n // 允许 fill 作用于 role=slider(后续在 fill 分支做专门处理)\n if (action === \"fill\" && el.getAttribute(\"role\") === \"slider\") {\n return null;\n }\n return { content: `\"${selector}\" 不是可编辑元素,无法执行 ${action}`, details: { error: true, code: \"UNSUPPORTED_FILL_TARGET\", action, selector } };\n }\n return null;\n}\n\n/**\n * 为 role=slider 查找关联的数值输入框。\n * 典型场景:Element Plus slider + input-number 同属一个 form-item。\n */\nfunction findAssociatedSliderInput(slider: Element): HTMLInputElement | null {\n const candidates: Element[] = [];\n\n const formItem = slider.closest(\".el-form-item\");\n if (formItem) candidates.push(formItem);\n\n let cursor: Element | null = slider.parentElement;\n for (let depth = 0; cursor && depth < 4; depth++, cursor = cursor.parentElement) {\n candidates.push(cursor);\n }\n\n for (const scope of candidates) {\n const input = scope.querySelector(\n 'input[type=\"number\"], input[role=\"spinbutton\"], .el-input-number input:not([type=\"hidden\"])',\n );\n if (input instanceof HTMLInputElement && isEditableElement(input) && isElementVisible(input)) {\n return input;\n }\n }\n return null;\n}\n\n// ─── 事件派发(参考 Playwright input.ts 事件链) ───\n\nfunction getClickPoint(el: Element): { x: number; y: number } {\n const r = el.getBoundingClientRect();\n return { x: r.left + r.width / 2, y: r.top + r.height / 2 };\n}\n\n/**\n * 完整点击事件链(参考 Playwright Mouse.click):\n * pointermove → mousemove → (per clickCount) pointerdown → mousedown → focus → pointerup → mouseup → click\n */\nfunction dispatchClickEvents(el: HTMLElement, clickCount = 1): void {\n const { x, y } = getClickPoint(el);\n const base: MouseEventInit = { bubbles: true, cancelable: true, view: window, clientX: x, clientY: y, button: 0 };\n\n el.dispatchEvent(new PointerEvent(\"pointermove\", { ...base, pointerId: 1 }));\n el.dispatchEvent(new MouseEvent(\"mousemove\", base));\n\n for (let cc = 1; cc <= clickCount; cc++) {\n el.dispatchEvent(new PointerEvent(\"pointerdown\", { ...base, detail: cc, buttons: 1, pointerId: 1 }));\n el.dispatchEvent(new MouseEvent(\"mousedown\", { ...base, detail: cc, buttons: 1 }));\n if (cc === 1 && el !== document.activeElement) el.focus({ preventScroll: true });\n el.dispatchEvent(new PointerEvent(\"pointerup\", { ...base, detail: cc, pointerId: 1 }));\n el.dispatchEvent(new MouseEvent(\"mouseup\", { ...base, detail: cc }));\n el.dispatchEvent(new MouseEvent(\"click\", { ...base, detail: cc }));\n }\n}\n\n/** hover 事件链 */\nfunction dispatchHoverEvents(el: HTMLElement): void {\n const { x, y } = getClickPoint(el);\n const base: MouseEventInit = { bubbles: true, cancelable: true, view: window, clientX: x, clientY: y };\n el.dispatchEvent(new PointerEvent(\"pointerenter\", { ...base, bubbles: false }));\n el.dispatchEvent(new MouseEvent(\"mouseenter\", { ...base, bubbles: false }));\n el.dispatchEvent(new PointerEvent(\"pointermove\", { ...base, pointerId: 1 }));\n el.dispatchEvent(new MouseEvent(\"mousemove\", base));\n el.dispatchEvent(new MouseEvent(\"mouseover\", base));\n}\n\n/** 派发 input + change 事件(兼容 React/Vue 受控组件) */\nfunction dispatchInputEvents(el: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement): void {\n el.dispatchEvent(new Event(\"input\", { bubbles: true, composed: true }));\n el.dispatchEvent(new Event(\"change\", { bubbles: true }));\n}\n\n/** 原生 setter 写入表单值(绕过 React/Vue getter/setter 拦截) */\nfunction setNativeValue(el: HTMLInputElement | HTMLTextAreaElement, value: string): void {\n const proto = el instanceof HTMLInputElement ? HTMLInputElement.prototype : HTMLTextAreaElement.prototype;\n const desc = Object.getOwnPropertyDescriptor(proto, \"value\");\n if (desc?.set) desc.set.call(el, value);\n else el.value = value;\n}\n\nfunction getFillEventSupportScore(el: Element): number {\n let score = 0;\n\n if (el.hasAttribute(\"oninput\") || el.hasAttribute(\"onchange\")) score += 80;\n if (el.hasAttribute(\"onfocus\") || el.hasAttribute(\"onblur\")) score += 60;\n if (el.hasAttribute(\"onclick\")) score += 40;\n\n const tracked = getTrackedElementEvents(el);\n for (const eventName of tracked) {\n if (!FILL_RELEVANT_EVENTS.has(eventName)) continue;\n if (eventName === \"input\") score += 40;\n else if (eventName === \"change\") score += 35;\n else if (eventName === \"focus\" || eventName === \"blur\") score += 28;\n else if (eventName === \"keydown\") score += 24;\n else score += 14;\n }\n\n return score;\n}\n\nfunction isCandidateFillTarget(el: Element): boolean {\n if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) {\n return !isElementDisabled(el);\n }\n if (el instanceof HTMLElement && el.isContentEditable) return true;\n return false;\n}\n\nfunction executeFillOnResolvedTarget(\n target: Element,\n value: string,\n selector: string,\n action: string,\n sourceHint?: string,\n): ToolCallResult | null {\n if (target instanceof HTMLInputElement) {\n const type = target.type.toLowerCase();\n if (INPUT_BLOCKED_TYPES.has(type)) {\n return { content: `\"${selector}\" 为 input[type=${type}],不支持 fill;请使用 click/check 等动作。`, details: { error: true, code: \"UNSUPPORTED_FILL_TARGET\", action, selector } };\n }\n if (INPUT_SET_VALUE_TYPES.has(type)) {\n const finalVal = type === \"color\" ? value.toLowerCase().trim() : value.trim();\n target.focus();\n target.value = finalVal;\n if (target.value !== finalVal) {\n return { content: `\"${selector}\" 填写格式不匹配(type=${type})`, details: { error: true, code: \"MALFORMED_VALUE\", action, selector } };\n }\n dispatchInputEvents(target);\n const suffix = sourceHint ? `(${sourceHint})` : \"\";\n return { content: `已填写 ${describeElement(target)}: \"${finalVal}\"${suffix}` };\n }\n if (type === \"number\" && Number.isNaN(Number(value.trim()))) {\n return { content: `\"${selector}\" 为 input[type=number],无法填写非数字 \"${value}\"`, details: { error: true, code: \"INVALID_NUMBER\", action, selector } };\n }\n scrollIntoViewIfNeeded(target);\n target.focus();\n selectText(target);\n setNativeValue(target, value);\n dispatchInputEvents(target);\n if (target.value !== value) {\n return { content: `\"${selector}\" 填写后值不一致:期望 \"${value}\",实际 \"${target.value}\"`, details: { error: true, code: \"FILL_NOT_APPLIED\", action, selector } };\n }\n const suffix = sourceHint ? `(${sourceHint})` : \"\";\n return { content: `已填写 ${describeElement(target)}: \"${value}\"${suffix}` };\n }\n\n if (target instanceof HTMLTextAreaElement) {\n scrollIntoViewIfNeeded(target);\n target.focus();\n selectText(target);\n setNativeValue(target, value);\n dispatchInputEvents(target);\n const suffix = sourceHint ? `(${sourceHint})` : \"\";\n return { content: `已填写 ${describeElement(target)}: \"${value}\"${suffix}` };\n }\n\n if (target instanceof HTMLSelectElement) {\n target.focus();\n const options = Array.from(target.options);\n let matched = options.find(o => o.value === value);\n if (!matched) {\n const normalized = value.trim().toLowerCase();\n matched = options.find(o => o.text.trim().toLowerCase() === normalized);\n }\n if (!matched) return { content: `\"${selector}\" 下拉框中不存在选项 \"${value}\"` };\n target.value = matched.value;\n dispatchInputEvents(target);\n const suffix = sourceHint ? `(${sourceHint})` : \"\";\n return { content: `已填写 ${describeElement(target)}: \"${value}\"${suffix}` };\n }\n\n if (target instanceof HTMLElement && target.isContentEditable) {\n target.focus();\n selectText(target);\n if (value) document.execCommand(\"insertText\", false, value);\n else document.execCommand(\"delete\", false, undefined);\n const suffix = sourceHint ? `(${sourceHint})` : \"\";\n return { content: `已填写 ${describeElement(target)}: \"${value}\"${suffix}` };\n }\n\n return null;\n}\n\nfunction guessNearbyFillTarget(anchor: Element, value: string): Element | null {\n const preferNumeric = Number.isFinite(Number(value));\n const scopeEntries: Array<{ scope: Element; level: number }> = [];\n\n const formItem = anchor.closest(\".el-form-item\");\n if (formItem) scopeEntries.push({ scope: formItem, level: 0 });\n\n let cursor: Element | null = anchor.parentElement;\n for (let level = 1; cursor && level <= 4; level++, cursor = cursor.parentElement) {\n scopeEntries.push({ scope: cursor, level });\n }\n\n const visited = new Set<Element>();\n let best: { el: Element; score: number } | null = null;\n\n for (const { scope, level } of scopeEntries) {\n const candidates = Array.from(scope.querySelectorAll(\n 'input:not([type=\"hidden\"]), textarea, select, [contenteditable=\"true\"], [role=\"spinbutton\"]',\n ));\n\n for (const candidate of candidates) {\n if (!(candidate instanceof Element)) continue;\n if (visited.has(candidate)) continue;\n visited.add(candidate);\n\n if (!isCandidateFillTarget(candidate)) continue;\n if (!isElementVisible(candidate)) continue;\n\n let score = 100 - level * 18;\n score += getFillEventSupportScore(candidate);\n\n if (candidate instanceof HTMLInputElement) {\n const type = candidate.type.toLowerCase();\n if (preferNumeric && (type === \"number\" || candidate.getAttribute(\"role\") === \"spinbutton\")) score += 80;\n if (!preferNumeric && [\"text\", \"\", \"search\", \"email\", \"tel\", \"url\", \"password\"].includes(type)) score += 36;\n }\n\n if (candidate.getAttribute(\"placeholder\")) score += 8;\n if (candidate.getAttribute(\"aria-label\")) score += 8;\n\n if (!best || score > best.score) {\n best = { el: candidate, score };\n }\n }\n }\n\n return best?.el ?? null;\n}\n\n// ─── selectText(参考 Playwright:input/textarea/contenteditable 三种策略) ───\n\nfunction selectText(el: Element): void {\n if (el instanceof HTMLInputElement) { el.select(); el.focus(); return; }\n if (el instanceof HTMLTextAreaElement) { el.selectionStart = 0; el.selectionEnd = el.value.length; el.focus(); return; }\n const range = document.createRange();\n range.selectNodeContents(el);\n const sel = window.getSelection();\n if (sel) { sel.removeAllRanges(); sel.addRange(range); }\n if (el instanceof HTMLElement) el.focus();\n}\n\n// ─── 键盘:组合键支持(参考 Playwright Keyboard.press) ───\n\nfunction splitKeyCombo(key: string): string[] {\n const tokens = key.split(\"+\");\n for (let i = 0; i < tokens.length; i++) {\n if (tokens[i] === \"\" && i + 1 < tokens.length) { tokens[i + 1] = \"+\" + tokens[i + 1]; tokens.splice(i, 1); }\n }\n return tokens.filter(Boolean);\n}\n\nfunction resolveKeyCode(key: string): string {\n return KEY_CODE_MAP[key] ?? (key.length === 1 ? `Key${key.toUpperCase()}` : key);\n}\n\n/**\n * 执行 press:修饰键按正序 down → 主键 down/up → 修饰键逆序 up(参考 Playwright)。\n * 修饰键按下时抑制文本输入(只发 keydown/keyup,不发 keypress)。\n */\nfunction executePress(el: Element, key: string): void {\n const tokens = splitKeyCombo(key);\n const mainKey = tokens[tokens.length - 1];\n const mods = tokens.slice(0, -1);\n const modState = {\n ctrlKey: mods.includes(\"Control\"),\n shiftKey: mods.includes(\"Shift\"),\n altKey: mods.includes(\"Alt\"),\n metaKey: mods.includes(\"Meta\"),\n };\n const hasNonShiftMod = modState.ctrlKey || modState.altKey || modState.metaKey;\n\n for (const m of mods) {\n el.dispatchEvent(new KeyboardEvent(\"keydown\", { key: m, code: resolveKeyCode(m), bubbles: true, cancelable: true, ...modState }));\n }\n const allowed = el.dispatchEvent(new KeyboardEvent(\"keydown\", { key: mainKey, code: resolveKeyCode(mainKey), bubbles: true, cancelable: true, ...modState }));\n // 只有无非 Shift 修饰键且是单字符时才发 keypress(参考 Playwright 文本抑制逻辑)\n if (allowed && mainKey.length === 1 && !hasNonShiftMod) {\n el.dispatchEvent(new KeyboardEvent(\"keypress\", { key: mainKey, code: resolveKeyCode(mainKey), bubbles: true, cancelable: true, ...modState }));\n }\n el.dispatchEvent(new KeyboardEvent(\"keyup\", { key: mainKey, code: resolveKeyCode(mainKey), bubbles: true, cancelable: true, ...modState }));\n for (let i = mods.length - 1; i >= 0; i--) {\n el.dispatchEvent(new KeyboardEvent(\"keyup\", { key: mods[i], code: resolveKeyCode(mods[i]), bubbles: true, cancelable: true, ...modState }));\n }\n}\n\n// ─── 元素描述 ───\n\nfunction describeElement(el: Element): string {\n const tag = el.tagName.toLowerCase();\n const id = el.id ? `#${el.id}` : \"\";\n const cls = el.className && typeof el.className === \"string\"\n ? el.className.trim().split(/\\s+/).filter(Boolean).slice(0, 3).map(c => `.${c}`).join(\"\") : \"\";\n const text = el instanceof HTMLSelectElement\n ? el.selectedOptions[0]?.textContent?.trim().slice(0, 40) ?? \"\"\n : el.textContent?.trim().slice(0, 40) ?? \"\";\n const textHint = text ? ` \"${text}\"` : \"\";\n const hints: string[] = [];\n for (const attr of [\"type\", \"name\", \"placeholder\", \"href\", \"role\"]) {\n const v = el.getAttribute(attr);\n if (v) hints.push(`${attr}=${v}`);\n }\n if (el instanceof HTMLSelectElement && el.value) hints.push(`val=${el.value}`);\n const attrHint = hints.length > 0 ? ` [${hints.join(\", \")}]` : \"\";\n return `<${tag}${id}${cls}>${textHint}${attrHint}`;\n}\n\n// ─── checkable 目标归一化 ───\n\nfunction isCheckableInput(el: Element | null): el is HTMLInputElement {\n return el instanceof HTMLInputElement && (el.type === \"checkbox\" || el.type === \"radio\");\n}\n\nfunction getChecked(el: Element): boolean | \"error\" {\n if (el instanceof HTMLInputElement && (el.type === \"checkbox\" || el.type === \"radio\")) return el.checked;\n const role = el.getAttribute(\"role\");\n if (role === \"checkbox\" || role === \"radio\" || role === \"switch\") return el.getAttribute(\"aria-checked\") === \"true\";\n return \"error\";\n}\n\n/**\n * 归一化 check/uncheck 目标:允许命中文本容器/label/div,回溯到关联 checkbox/radio。\n */\nfunction resolveCheckableTarget(el: Element): Element {\n if (getChecked(el) !== \"error\") return el;\n if (el instanceof HTMLLabelElement && el.control && getChecked(el.control) !== \"error\") return el.control;\n const ownerLabel = el.closest(\"label\") as HTMLLabelElement | null;\n if (ownerLabel?.control && getChecked(ownerLabel.control) !== \"error\") return ownerLabel.control;\n const inner = el.querySelector('input[type=\"checkbox\"], input[type=\"radio\"], [role=\"checkbox\"], [role=\"radio\"], [role=\"switch\"]');\n if (inner && getChecked(inner) !== \"error\") return inner;\n const prev = el.previousElementSibling;\n if (prev && getChecked(prev) !== \"error\") return prev;\n const next = el.nextElementSibling;\n if (next && getChecked(next) !== \"error\") return next;\n const parent = el.parentElement;\n if (parent) {\n const inP = parent.querySelector('input[type=\"checkbox\"], input[type=\"radio\"], [role=\"checkbox\"], [role=\"radio\"], [role=\"switch\"]');\n if (inP && getChecked(inP) !== \"error\") return inP;\n }\n return el;\n}\n\n/**\n * 为 pointer 类动作(click/check/uncheck)解析可点击代理目标:\n * 当命中隐藏的原生 checkbox/radio/switch input 时,优先改点其可见 label/容器。\n */\nfunction resolvePointerActionTarget(el: Element): Element {\n if (!(el instanceof HTMLInputElement)) return el;\n\n const inputType = el.type?.toLowerCase() ?? \"\";\n const isCheckable = inputType === \"checkbox\" || inputType === \"radio\";\n if (!isCheckable && el.getAttribute(\"role\") !== \"switch\") return el;\n if (isElementVisible(el)) return el;\n\n const label = el.labels?.[0] ?? (el.closest(\"label\") as HTMLLabelElement | null);\n if (label && isElementVisible(label)) return label;\n\n const proxy = el.closest(\".el-switch, .el-checkbox, .el-radio, [role='switch'], [role='checkbox'], [role='radio']\");\n if (proxy && isElementVisible(proxy)) return proxy;\n\n const siblingProxy = el.parentElement?.querySelector(\n \".el-switch__core, .el-checkbox__inner, .el-radio__inner, [role='switch'], [role='checkbox'], [role='radio']\",\n );\n if (siblingProxy && isElementVisible(siblingProxy)) return siblingProxy;\n\n return el;\n}\n\n/**\n * 当命中表单项说明 label(如 Element Plus el-form-item__label)时,\n * 自动重定向到同一表单项中的首个可交互控件。\n */\nfunction resolveFormItemControlTarget(el: Element): Element {\n if (!(el instanceof HTMLElement)) return el;\n const isLabelLike = el.tagName === \"LABEL\" || el.classList.contains(\"el-form-item__label\");\n if (!isLabelLike) return el;\n\n const htmlLabel = el as HTMLLabelElement;\n if (htmlLabel.control && isElementVisible(htmlLabel.control)) return htmlLabel.control;\n\n const formItem = el.closest(\".el-form-item\");\n if (!formItem) return el;\n const content = formItem.querySelector(\".el-form-item__content\") ?? formItem;\n const control = content.querySelector(\n \"input:not([type='hidden']), textarea, select, button, [role='switch'], [role='checkbox'], [role='radio'], [role='button'], .el-switch, .el-checkbox, .el-radio, [tabindex]:not([tabindex='-1'])\",\n );\n if (control && isElementVisible(control)) return control;\n return el;\n}\n\n// ─── 自定义下拉增强 ───\n\nfunction findVisibleOptionByText(text: string): HTMLElement | null {\n const target = text.trim().toLowerCase();\n if (!target) return null;\n const selectors = [\n '[role=\"option\"]', '[role=\"listbox\"] li',\n \".el-select-dropdown__item\", \".el-option\", // Element Plus\n \".ant-select-item-option\", // Ant Design\n \".el-cascader-node\", \".el-dropdown-menu__item\",\n '[class*=\"option\"]', \"li[data-value]\", \"option\",\n ].join(\", \");\n const nodes = Array.from(document.querySelectorAll(selectors));\n const visible = nodes.filter(n => n instanceof HTMLElement && isElementVisible(n));\n for (const n of visible) { if (n.textContent?.trim().toLowerCase() === target) return n as HTMLElement; }\n for (const n of visible) { if (n.textContent?.trim().toLowerCase().includes(target)) return n as HTMLElement; }\n return null;\n}\n\nasync function waitForDropdownPopup(maxWait = 500): Promise<void> {\n const start = Date.now();\n while (Date.now() - start < maxWait) {\n const popup = document.querySelector('[role=\"listbox\"], .el-select-dropdown, .el-popper, .ant-select-dropdown, [class*=\"dropdown\"]');\n if (popup && isElementVisible(popup)) return;\n await sleep(50);\n }\n}\n\n// ─── 工具定义 ───\n\nexport function createDomTool(): ToolDefinition {\n return {\n name: \"dom\",\n description: [\n \"Perform DOM operations on the current page.\",\n \"Actions: click, fill, select_option, clear, check, uncheck, type, focus, hover, scroll, press, get_text, get_attr, set_attr, add_class, remove_class.\",\n \"Input/Select rule: before each fill/type/select_option, click or focus the same target immediately in the same round.\",\n \"For multiple fields, use alternating pairs in one batch: focus/click A -> fill/type A -> focus/click B -> fill/type B.\",\n \"Use the hash ID from DOM snapshot (e.g. #a1b2c) as selector.\",\n \"press supports combo keys like 'Control+a', 'Shift+Enter'.\",\n \"check/uncheck is done via click — state change is verified after action.\",\n \"Ordinal/index rule: treat visual order as 1-based when the instruction says 'the Nth item' (e.g. 4th star = 4th visible icon from left to right), and avoid off-by-one mistakes.\",\n \"Disambiguation rule: distinguish descriptive text/labels from actionable options. Do not click nearby label/help text; click the actual interactive option/control item (icon/button/option) that changes state.\",\n \"Unknown/complex components: if a container element (e.g. role=slider, rating, custom widget) has multiple child icons/items in the snapshot but you don't know how to operate it directly, try clicking the appropriate child element instead. For example, a rating component with 5 star icon children — click the 4th icon child to set 4 stars. A slider with a runway — clicking the runway at the right position may work. Always prefer interacting with visible children when the parent container doesn't respond to fill/click as expected.\",\n \"fill supports role=slider elements: use fill with a numeric value on a role=slider container (rating/slider) to set its value programmatically.\",\n \"For wheel/virtualized pickers where target option is not visible yet, use scroll on the picker column first, then click/select the newly visible option. scroll supports steps for repeated scrolling in one call.\",\n ].join(\" \"),\n\n schema: Type.Object({\n action: Type.String({\n description: \"DOM action: click | fill | select_option | clear | check | uncheck | type | focus | hover | scroll | press | get_text | get_attr | set_attr | add_class | remove_class.\",\n }),\n selector: Type.String({ description: \"Element ref ID from snapshot (e.g. #r0, #r5) or CSS selector\" }),\n value: Type.Optional(Type.String({ description: \"Value for fill/type/set_attr actions.\" })),\n key: Type.Optional(Type.String({ description: \"Key for press action. Supports combo: 'Enter', 'Control+a', 'Shift+Enter', 'Meta+c'\" })),\n label: Type.Optional(Type.String({ description: \"Label text for select_option action.\" })),\n index: Type.Optional(Type.Number({ description: \"0-based option index for select_option action\" })),\n attribute: Type.Optional(Type.String({ description: \"Attribute name for get_attr/set_attr\" })),\n className: Type.Optional(Type.String({ description: \"CSS class name for add_class/remove_class\" })),\n clickCount: Type.Optional(Type.Number({ description: \"Click count (default 1). 2 = double-click, 3 = triple-click.\" })),\n deltaY: Type.Optional(Type.Number({ description: \"Vertical scroll delta for scroll action. Positive = down, negative = up.\" })),\n deltaX: Type.Optional(Type.Number({ description: \"Horizontal scroll delta for scroll action.\" })),\n steps: Type.Optional(Type.Number({ description: \"Repeat count for scroll action (default 1, max 20).\" })),\n waitMs: Type.Optional(Type.Number({ description: \"Wait timeout in ms before action (default: 1200).\" })),\n waitSeconds: Type.Optional(Type.Number({ description: \"Wait timeout in seconds (fallback for waitMs).\" })),\n force: Type.Optional(Type.Boolean({ description: \"Skip actionability checks (default false).\" })),\n }),\n\n execute: async (params): Promise<ToolCallResult> => {\n const action = params.action as string;\n const selector = params.selector as string;\n const waitMs = resolveWaitMs(params);\n const force = params.force === true;\n\n if (!selector) return { content: \"缺少 selector 参数\" };\n\n // ── 元素查找 ──\n let el: Element;\n if (waitMs > 0) {\n const found = await waitForElement(selector, waitMs);\n if (typeof found === \"string\") return { content: found, details: { error: true, code: \"INVALID_SELECTOR\", action, selector } };\n if (!found) return { content: `未找到匹配 \"${selector}\" 的元素`, details: { error: true, code: \"ELEMENT_NOT_FOUND\", action, selector, waitMs } };\n el = found;\n } else {\n const r = queryElement(selector);\n if (typeof r === \"string\") return { content: r, details: { error: true, code: r.startsWith(\"未找到\") ? \"ELEMENT_NOT_FOUND\" : \"INVALID_SELECTOR\", action, selector, waitMs } };\n el = r;\n }\n\n // check/uncheck 归一化\n if (action === \"check\" || action === \"uncheck\") {\n el = resolveCheckableTarget(el);\n }\n\n const actionabilityTarget =\n action === \"click\" || action === \"check\" || action === \"uncheck\"\n ? resolvePointerActionTarget(resolveFormItemControlTarget(el))\n : el;\n\n try {\n // actionability(skip for force / read-only actions)\n const checkResult = ensureActionable(actionabilityTarget, action, selector, force);\n if (checkResult) return checkResult;\n\n switch (action) {\n // ─── click ───\n case \"click\": {\n const target = resolvePointerActionTarget(resolveFormItemControlTarget(retarget(el, force ? \"none\" : \"button-link\")));\n const clickCount = typeof params.clickCount === \"number\" ? params.clickCount : 1;\n\n // option 元素自动写回 select\n if (target instanceof HTMLOptionElement) {\n const parent = target.parentElement;\n if (parent instanceof HTMLSelectElement) {\n parent.focus(); parent.value = target.value;\n dispatchInputEvents(parent);\n return { content: `已选择 ${describeElement(parent)} 的选项 \"${target.value}\"` };\n }\n }\n\n if (target instanceof HTMLElement) {\n scrollIntoViewIfNeeded(target);\n // stable 检查(参考 Playwright)\n if (!force) await checkElementStable(target, 500);\n // hit-target 检查\n if (!force) {\n const blocker = checkHitTarget(target);\n if (blocker) {\n scrollIntoViewIfNeeded(target, 1);\n await sleep(100);\n // 第二次检查仍被遮挡时 warn 但不阻断\n }\n }\n dispatchClickEvents(target, clickCount);\n } else {\n target.dispatchEvent(new MouseEvent(\"click\", { bubbles: true }));\n }\n return { content: `已点击 ${describeElement(target)}` };\n }\n\n // ─── fill(参考 Playwright 分类型策略) ───\n case \"fill\": {\n const value = params.value as string;\n if (value === undefined) return { content: \"缺少 value 参数\" };\n const target = retarget(el, \"follow-label\");\n\n // role=slider 特化:优先写关联数字输入框,其次点击离散子项(评分星级)\n if (target instanceof HTMLElement && target.getAttribute(\"role\") === \"slider\") {\n const numericValue = Number(value);\n if (!Number.isFinite(numericValue)) {\n const guessed = guessNearbyFillTarget(target, value);\n if (guessed) {\n const guessedResult = executeFillOnResolvedTarget(guessed, value, selector, action, \"heuristic-nearby-target\");\n if (guessedResult) return guessedResult;\n }\n return { content: `\"${selector}\" 为 role=slider,未找到可推断填写目标`, details: { error: true, code: \"UNSUPPORTED_FILL_TARGET\", action, selector } };\n }\n\n const linkedInput = findAssociatedSliderInput(target);\n if (linkedInput) {\n const filled = executeFillOnResolvedTarget(linkedInput, String(numericValue), selector, action, `from ${describeElement(target)}`);\n if (filled) return filled;\n }\n\n const min = Number(target.getAttribute(\"aria-valuemin\") ?? \"1\");\n const max = Number(target.getAttribute(\"aria-valuemax\") ?? String(target.children.length || 5));\n const discreteCount = Number.isFinite(max - min + 1) ? Math.max(1, Math.round(max - min + 1)) : target.children.length;\n const desiredIndex = Math.round(numericValue - min);\n const children = Array.from(target.children).filter((node): node is HTMLElement => node instanceof HTMLElement);\n\n if (children.length >= discreteCount && desiredIndex >= 0 && desiredIndex < children.length) {\n const item = children[desiredIndex];\n scrollIntoViewIfNeeded(item);\n dispatchClickEvents(item);\n return { content: `已点击 ${describeElement(item)},设置 ${describeElement(target)} 值为 ${numericValue}` };\n }\n\n const guessed = guessNearbyFillTarget(target, String(numericValue));\n if (guessed) {\n const guessedResult = executeFillOnResolvedTarget(guessed, String(numericValue), selector, action, \"heuristic-nearby-target\");\n if (guessedResult) return guessedResult;\n }\n\n return { content: `\"${selector}\" 为 role=slider,但未找到可写入输入框或可点击离散子项`, details: { error: true, code: \"UNSUPPORTED_FILL_TARGET\", action, selector } };\n }\n\n const directFilled = executeFillOnResolvedTarget(target, value, selector, action);\n if (directFilled) return directFilled;\n\n const guessed = guessNearbyFillTarget(target, value);\n if (guessed) {\n const guessedResult = executeFillOnResolvedTarget(guessed, value, selector, action, \"heuristic-nearby-target\");\n if (guessedResult) return guessedResult;\n }\n\n return { content: `\"${selector}\" 不是可编辑元素,且未在附近找到可推断填写目标`, details: { error: true, code: \"UNSUPPORTED_FILL_TARGET\", action, selector } };\n }\n\n // ─── select_option(参考 Playwright selectOptions 多策略匹配) ───\n case \"select_option\": {\n const value = params.value as string | undefined;\n const label = params.label as string | undefined;\n const index = typeof params.index === \"number\" ? Math.floor(params.index) : undefined;\n if (value === undefined && label === undefined && index === undefined) {\n return { content: \"缺少可选参数:value 或 label 或 index\" };\n }\n\n const target = retarget(el, \"follow-label\");\n\n // 非原生 <select>:自定义下拉\n if (!(target instanceof HTMLSelectElement)) {\n if (!(target instanceof HTMLElement)) return { content: `\"${selector}\" 不是下拉框元素` };\n scrollIntoViewIfNeeded(target);\n const wanted = (label ?? value ?? \"\").trim();\n if (!wanted) return { content: `\"${selector}\" 为自定义下拉时,需提供 value 或 label` };\n dispatchClickEvents(target); // 点击触发器打开\n await waitForDropdownPopup(800);\n const option = findVisibleOptionByText(wanted);\n if (!option) return { content: `未找到与 \"${wanted}\" 匹配的可见下拉选项(自定义下拉)`, details: { error: true, code: \"OPTION_NOT_FOUND\", action, selector, wanted } };\n dispatchClickEvents(option);\n return { content: `已在自定义下拉中选择 \"${wanted}\"` };\n }\n\n // 原生 <select>\n target.focus();\n const options = Array.from(target.options);\n let selected: HTMLOptionElement | undefined;\n if (value !== undefined) selected = options.find(o => o.value === value);\n if (!selected && label !== undefined) { const nl = label.trim().toLowerCase(); selected = options.find(o => o.text.trim().toLowerCase() === nl); }\n if (!selected && value !== undefined) { const nv = value.trim().toLowerCase(); selected = options.find(o => o.text.trim().toLowerCase() === nv); }\n if (!selected && index !== undefined) {\n if (index < 0 || index >= options.length) return { content: `\"${selector}\" 下拉框不存在 index=${index} 的选项` };\n selected = options[index];\n }\n if (!selected) return { content: `\"${selector}\" 下拉框中不存在选项 \"${value ?? label ?? `index=${index}`}\"` };\n // option disabled 检查(参考 Playwright)\n if (selected.disabled) return { content: `\"${selector}\" 目标选项已禁用:${selected.value}`, details: { error: true, code: \"OPTION_DISABLED\", action, selector } };\n if (!target.multiple) { for (const o of options) o.selected = false; }\n selected.selected = true;\n target.value = selected.value;\n dispatchInputEvents(target);\n return { content: `已选择 ${describeElement(target)}: value=\"${selected.value}\", label=\"${selected.text.trim()}\"` };\n }\n\n // ─── clear ───\n case \"clear\": {\n const target = retarget(el, \"follow-label\");\n if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {\n scrollIntoViewIfNeeded(target);\n target.focus(); selectText(target);\n setNativeValue(target as HTMLInputElement, \"\");\n dispatchInputEvents(target);\n return { content: `已清空 ${describeElement(target)}` };\n }\n if (target instanceof HTMLSelectElement) {\n target.focus(); target.value = \"\";\n dispatchInputEvents(target);\n return { content: `已清空 ${describeElement(target)}` };\n }\n if (target instanceof HTMLElement && target.isContentEditable) {\n target.focus(); selectText(target);\n document.execCommand(\"delete\", false, undefined);\n return { content: `已清空 ${describeElement(target)}` };\n }\n return { content: `\"${selector}\" 不是可清空元素` };\n }\n\n // ─── check / uncheck(参考 Playwright:通过 click 切换 + 验证状态) ───\n case \"check\":\n case \"uncheck\": {\n const wantChecked = action === \"check\";\n const current = getChecked(el);\n if (current === \"error\") {\n return { content: `\"${selector}\" 不是 checkbox/radio/[role=checkbox]/[role=radio],无法 ${action}`, details: { error: true, code: \"NOT_CHECKABLE\", action, selector } };\n }\n // 已是目标状态(幂等,参考 Playwright)\n if (current === wantChecked) return { content: `${describeElement(el)} 已经是${wantChecked ? \"选中\" : \"未选中\"}状态` };\n // radio 不能 uncheck\n if (!wantChecked && el instanceof HTMLInputElement && el.type === \"radio\") {\n return { content: `无法取消 radio 按钮的选中状态`, details: { error: true, code: \"CANNOT_UNCHECK_RADIO\", action, selector } };\n }\n // 通过 click 切换(参考 Playwright _setChecked)\n const pointerTarget = resolvePointerActionTarget(el);\n scrollIntoViewIfNeeded(pointerTarget);\n if (pointerTarget instanceof HTMLElement) dispatchClickEvents(pointerTarget);\n else pointerTarget.dispatchEvent(new MouseEvent(\"click\", { bubbles: true }));\n // 验证状态变更\n await sleep(50);\n const finalState = getChecked(el);\n if (finalState !== wantChecked && el instanceof HTMLInputElement) {\n el.checked = wantChecked;\n dispatchInputEvents(el);\n }\n return { content: `已${wantChecked ? \"勾选\" : \"取消勾选\"} ${describeElement(el)}` };\n }\n\n // ─── type(逐字符键入) ───\n case \"type\": {\n const value = params.value as string;\n if (value === undefined) return { content: \"缺少 value 参数\" };\n const target = retarget(el, \"follow-label\");\n scrollIntoViewIfNeeded(target);\n if (target instanceof HTMLElement) target.focus();\n\n for (const char of value) {\n const init: KeyboardEventInit = { key: char, code: resolveKeyCode(char), bubbles: true, cancelable: true };\n target.dispatchEvent(new KeyboardEvent(\"keydown\", init));\n target.dispatchEvent(new KeyboardEvent(\"keypress\", init));\n if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {\n const proto = target instanceof HTMLInputElement ? HTMLInputElement.prototype : HTMLTextAreaElement.prototype;\n const nativeSet = Object.getOwnPropertyDescriptor(proto, \"value\")?.set;\n if (nativeSet) nativeSet.call(target, target.value + char); else target.value += char;\n } else if (target instanceof HTMLElement && target.isContentEditable) {\n document.execCommand(\"insertText\", false, char);\n }\n target.dispatchEvent(new Event(\"input\", { bubbles: true, composed: true }));\n target.dispatchEvent(new KeyboardEvent(\"keyup\", init));\n }\n if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {\n target.dispatchEvent(new Event(\"change\", { bubbles: true }));\n }\n return { content: `已逐字输入到 ${describeElement(target)}: \"${value}\"` };\n }\n\n // ─── focus(参考 Playwright:双次 focus) ───\n case \"focus\": {\n const target = retarget(el, \"follow-label\");\n if (target instanceof HTMLElement || target instanceof SVGElement) {\n target.focus(); target.focus(); // Playwright workaround: 双次 focus\n }\n return { content: `已聚焦 ${describeElement(target)}` };\n }\n\n // ─── hover ───\n case \"hover\": {\n const target = retarget(el, \"none\");\n scrollIntoViewIfNeeded(target);\n if (!force) await checkElementStable(target, 500);\n if (target instanceof HTMLElement) dispatchHoverEvents(target);\n return { content: `已悬停 ${describeElement(target)}` };\n }\n\n // ─── scroll(组件内滚动,适配时间滚轮/虚拟列表) ───\n case \"scroll\": {\n const target = retarget(el, \"none\");\n const deltaY = typeof params.deltaY === \"number\"\n ? params.deltaY\n : (typeof params.value === \"string\" && !Number.isNaN(Number(params.value)) ? Number(params.value) : 180);\n const deltaX = typeof params.deltaX === \"number\" ? params.deltaX : 0;\n const rawSteps = typeof params.steps === \"number\" ? Math.floor(params.steps) : 1;\n const steps = Math.min(20, Math.max(1, rawSteps));\n\n if (target instanceof HTMLElement) {\n scrollIntoViewIfNeeded(target);\n for (let i = 0; i < steps; i++) {\n target.scrollBy({ top: deltaY, left: deltaX, behavior: \"auto\" });\n target.dispatchEvent(new WheelEvent(\"wheel\", {\n bubbles: true,\n cancelable: true,\n deltaY,\n deltaX,\n }));\n }\n return { content: `已滚动 ${describeElement(target)}: deltaY=${deltaY}, deltaX=${deltaX}, steps=${steps}` };\n }\n\n for (let i = 0; i < steps; i++) {\n target.dispatchEvent(new WheelEvent(\"wheel\", {\n bubbles: true,\n cancelable: true,\n deltaY,\n deltaX,\n }));\n }\n return { content: `已滚动 ${describeElement(target)}: deltaY=${deltaY}, deltaX=${deltaX}, steps=${steps}` };\n }\n\n // ─── press(支持组合键) ───\n case \"press\": {\n const key = (params.key as string) || (params.value as string);\n if (!key) return { content: \"缺少 key 参数(如 Enter, Escape, Tab, Control+a)\" };\n const target = retarget(el, \"none\");\n scrollIntoViewIfNeeded(target);\n if (target instanceof HTMLElement) target.focus();\n executePress(target, key);\n // Enter 特殊:触发 form submit\n const mainKey = splitKeyCombo(key).pop();\n if (mainKey === \"Enter\") {\n const form = (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) ? (target.form ?? target.closest(\"form\")) : target.closest(\"form\");\n form?.dispatchEvent(new Event(\"submit\", { bubbles: true, cancelable: true }));\n }\n return { content: `已在 ${describeElement(target)} 上按下 ${key}` };\n }\n\n // ─── 读取类 ───\n case \"get_text\": {\n const text = el.textContent?.trim() ?? \"\";\n return { content: `${describeElement(el)} 的文本内容:${text || \"(空)\"}` };\n }\n case \"get_attr\": {\n const attribute = params.attribute as string;\n if (!attribute) return { content: \"缺少 attribute 参数\" };\n const attrName = attribute.toLowerCase();\n if (attrName === \"checked\") {\n if (el instanceof HTMLInputElement) return { content: `${describeElement(el)} 的 checked = ${String(el.checked)}` };\n return { content: `${describeElement(el)} 的 checked = ${el.getAttribute(\"aria-checked\") ?? \"(不存在)\"}` };\n }\n if (attrName === \"selected\") {\n if (el instanceof HTMLOptionElement) return { content: `${describeElement(el)} 的 selected = ${String(el.selected)}` };\n return { content: `${describeElement(el)} 的 selected = ${el.getAttribute(\"aria-selected\") ?? \"(不存在)\"}` };\n }\n if (attrName === \"disabled\") {\n if (el instanceof HTMLButtonElement || el instanceof HTMLInputElement || el instanceof HTMLSelectElement || el instanceof HTMLTextAreaElement) {\n return { content: `${describeElement(el)} 的 disabled = ${String(el.disabled)}` };\n }\n }\n if (attrName === \"readonly\" && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {\n return { content: `${describeElement(el)} 的 readonly = ${String(el.readOnly)}` };\n }\n if (attrName === \"value\" && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement)) {\n return { content: `${describeElement(el)} 的 value = ${el.value || \"(空)\"}` };\n }\n return { content: `${describeElement(el)} 的 ${attribute} = ${el.getAttribute(attribute) ?? \"(不存在)\"}` };\n }\n\n // ─── 修改类 ───\n case \"set_attr\": {\n const attribute = params.attribute as string;\n const value = params.value as string;\n if (!attribute || value === undefined) return { content: \"缺少 attribute 或 value 参数\" };\n el.setAttribute(attribute, value);\n return { content: `已设置 ${describeElement(el)} 的 ${attribute}=\"${value}\"` };\n }\n case \"add_class\": {\n const className = params.className as string;\n if (!className) return { content: \"缺少 className 参数\" };\n el.classList.add(className);\n return { content: `已添加 class \"${className}\" 到 ${describeElement(el)}` };\n }\n case \"remove_class\": {\n const className = params.className as string;\n if (!className) return { content: \"缺少 className 参数\" };\n el.classList.remove(className);\n return { content: `已移除 ${describeElement(el)} 的 class \"${className}\"` };\n }\n\n default:\n return { content: `未知的 DOM 动作: ${action}` };\n }\n } catch (err) {\n return { content: `DOM 操作 \"${action}\" 失败: ${err instanceof Error ? err.message : String(err)}`, details: { error: true, action, selector } };\n }\n },\n };\n}","/**\n * Page Info Tool — 基于 Web API 的页面信息获取工具。\n *\n * 替代 Playwright 的 getTitle/getUrl/snapshot 等。\n * 运行环境:浏览器 Content Script。\n *\n * 支持 6 种动作:\n * get_url — 获取当前页面 URL\n * get_title — 获取页面标题\n * get_selection — 获取用户选中的文本\n * get_viewport — 获取视口尺寸和滚动位置\n * snapshot — 获取页面 DOM 结构快照(AI 可读的文本描述)\n * query_all — 查询所有匹配选择器的元素,返回摘要信息\n */\nimport { Type } from \"@sinclair/typebox\";\nimport type { ToolDefinition, ToolCallResult } from \"../../core/tool-registry.js\";\nimport type { RefStore } from \"../ref-store.js\";\nimport { getTrackedElementEvents, hasTrackedElementEvents } from \"../event-listener-tracker.js\";\nimport { getActiveRefStore } from \"./dom-tool.js\";\n\n/** 快照配置选项 */\nexport type SnapshotOptions = {\n /** 最大遍历深度(默认 12) */\n maxDepth?: number;\n /**\n * 视口裁剪:只保留与视口相交的元素(默认 true)。\n * 开启后,完全在视口外的元素会被跳过,大幅减少 token 消耗。\n * 注意:祖先容器即使自身不在视口内,只要有子元素在视口内就会保留。\n */\n viewportOnly?: boolean;\n /**\n * 智能剪枝:折叠无意义的纯布局容器(默认 true)。\n * 开启后,没有文本、没有 id、没有交互属性的纯布局元素(div/span/section 等)\n * 如果自身无意义,会被折叠——子元素直接提升到父级输出,减少嵌套噪音。\n */\n pruneLayout?: boolean;\n /**\n * hash ID 映射表(可选)。\n * 传入 RefStore 实例后,每个元素使用确定性 hash ID 替代完整 XPath,\n * 大幅减少 token 消耗。dom-tool 通过 RefStore.get(id) 解析回 DOM 元素。\n */\n refStore?: RefStore;\n /** 最大输出节点数(默认 220),超过后停止继续遍历。 */\n maxNodes?: number;\n /** 每个父节点最多输出的子元素数(默认 25),超出部分会折叠。 */\n maxChildren?: number;\n /** 文本截断长度(默认 40)。 */\n maxTextLength?: number;\n /**\n * 是否对“选项列表”容器放宽子节点截断(默认 false)。\n * 典型场景:时间选择器/下拉选项列表,避免关键选项被 `...children omitted` 折叠。\n */\n expandOptionLists?: boolean;\n /**\n * 仅对指定 hash ref 节点放宽子节点截断(优先级高于默认 maxChildren)。\n * 例如:[#abc123, #def456],用于 AI 在看到 children omitted 后定向请求放宽。\n */\n expandChildrenRefs?: string[];\n /** 对 expandChildrenRefs 节点生效的子节点上限(默认 120)。 */\n expandedChildrenLimit?: number;\n};\n\n/** 快照属性值最大保留长度(超出截断)。 */\nconst MAX_SNAPSHOT_ATTR_VALUE_LENGTH = 120;\n/** 选项列表放宽时的子节点上限(仍保留硬上限,避免快照无限膨胀)。 */\nconst MAX_EXPANDED_LIST_CHILDREN = 120;\n/** 定向放宽 children 的硬上限。 */\nconst MAX_EXPANDED_CHILDREN_LIMIT = 300;\n\n/** 事件名 → 快照简写映射(压缩 token)。 */\nconst EVENT_ABBREV: Record<string, string> = {\n click: \"clk\", dblclick: \"dbl\",\n mousedown: \"mdn\", mouseup: \"mup\", mousemove: \"mmv\",\n mouseover: \"mov\", mouseout: \"mot\", mouseenter: \"men\", mouseleave: \"mlv\",\n pointerdown: \"pdn\", pointerup: \"pup\", pointermove: \"pmv\",\n touchstart: \"tst\", touchend: \"ted\",\n keydown: \"kdn\", keyup: \"kup\",\n input: \"inp\", change: \"chg\", submit: \"sub\",\n focus: \"fcs\", blur: \"blr\",\n scroll: \"scl\", wheel: \"whl\",\n drag: \"drg\", dragstart: \"drs\", dragend: \"dre\", drop: \"drp\",\n contextmenu: \"ctx\",\n};\n\nfunction abbrevEvent(name: string): string {\n return EVENT_ABBREV[name] ?? name.slice(0, 3);\n}\n\n/**\n * 规整快照属性值,避免把长 base64/data URL 原样注入快照。\n */\nfunction sanitizeSnapshotAttrValue(value: string): string {\n const trimmed = value.trim();\n if (!trimmed) return \"\";\n\n const dataUrlMatch = trimmed.match(/^data:([^,]*?),(.*)$/i);\n if (dataUrlMatch) {\n const meta = dataUrlMatch[1] || \"\";\n const payload = dataUrlMatch[2] || \"\";\n const isBase64 = /;base64/i.test(meta);\n const payloadLength = payload.length;\n const previewMeta = meta.slice(0, 48);\n if (isBase64 || payloadLength > 64) {\n return `data:${previewMeta},<omitted:${payloadLength}>`;\n }\n }\n\n const base64ChunkMatch = trimmed.match(/^[A-Za-z0-9+/]{80,}={0,2}$/);\n if (base64ChunkMatch) {\n return `<base64:${trimmed.length}>`;\n }\n\n if (trimmed.length > MAX_SNAPSHOT_ATTR_VALUE_LENGTH) {\n return `${trimmed.slice(0, MAX_SNAPSHOT_ATTR_VALUE_LENGTH)}...`;\n }\n return trimmed;\n}\n\n/**\n * 生成页面 DOM 快照 — 将 DOM 树转为 AI 可理解的文本描述。\n *\n * 基于 Web API 实现,只遍历可见元素,跳过 script/style/svg 等无意义节点。\n * 传入 RefStore 时,每个元素生成确定性 hash ID(如 #a1b2c),\n * AI 通过 hash ID 精确定位元素,无需猜测 CSS 选择器。\n *\n * 输出格式示例:\n * [header] #k9f2a\n * [nav] #m3d7e\n * [a] \"首页\" href=\"/\" #p1c4b\n * [a] \"关于\" href=\"/about\" #q8e5f\n * [main] #r2a6d\n * [h1] \"欢迎\" #s7g3h\n * [input] type=\"text\" placeholder=\"搜索...\" #t4j8k\n * [button] \"搜索\" id=\"search-btn\" onclick #u5n2m\n *\n * @param root - 快照根元素(默认 document.body)\n * @param options - 快照选项对象,或传入数字作为 maxDepth(向后兼容)\n */\nexport function generateSnapshot(\n root: Element = document.body,\n options: SnapshotOptions | number = {},\n): string {\n // 向后兼容:数字参数视为 maxDepth\n const opts: SnapshotOptions = typeof options === \"number\"\n ? { maxDepth: options }\n : options;\n\n const maxDepth = opts.maxDepth ?? 12;\n const viewportOnly = opts.viewportOnly ?? true;\n const pruneLayout = opts.pruneLayout ?? true;\n const maxNodes = opts.maxNodes ?? 220;\n const maxChildren = opts.maxChildren ?? 25;\n const maxTextLength = opts.maxTextLength ?? 40;\n const expandOptionLists = opts.expandOptionLists ?? false;\n const expandedChildrenLimit = Math.min(\n MAX_EXPANDED_CHILDREN_LIMIT,\n Math.max(1, opts.expandedChildrenLimit ?? MAX_EXPANDED_LIST_CHILDREN),\n );\n const expandChildrenRefSet = new Set(\n (opts.expandChildrenRefs ?? [])\n .map(ref => ref.trim().replace(/^#/, \"\"))\n .filter(Boolean),\n );\n\n let emittedNodes = 0;\n let truncatedByNodeBudget = false;\n\n const refStore = opts.refStore;\n\n const SKIP_TAGS = new Set([\n \"SCRIPT\", \"STYLE\", \"SVG\", \"NOSCRIPT\", \"LINK\", \"META\", \"BR\", \"HR\",\n ]);\n\n /** 纯布局容器标签 — 智能剪枝时可能被折叠 */\n const LAYOUT_TAGS = new Set([\n \"DIV\", \"SPAN\", \"SECTION\", \"ARTICLE\", \"ASIDE\", \"MAIN\",\n \"HEADER\", \"FOOTER\", \"NAV\", \"FIGURE\", \"FIGCAPTION\",\n ]);\n\n /** 视口尺寸(viewportOnly 开启时使用) */\n const vpWidth = viewportOnly ? window.innerWidth : 0;\n const vpHeight = viewportOnly ? window.innerHeight : 0;\n\n const INTERACTIVE_ATTRS = [\n \"href\", \"type\", \"placeholder\", \"value\", \"name\", \"role\", \"aria-label\",\n \"src\", \"alt\", \"title\", \"for\", \"action\", \"method\",\n ];\n\n const INTERACTIVE_TAGS = new Set([\n \"A\", \"BUTTON\", \"INPUT\", \"TEXTAREA\", \"SELECT\", \"OPTION\", \"LABEL\", \"SUMMARY\",\n ]);\n\n const INTERACTIVE_EVENTS = new Set([\n \"click\", \"dblclick\", \"mousedown\", \"mouseup\", \"pointerdown\", \"pointerup\",\n \"touchstart\", \"touchend\", \"input\", \"change\", \"keydown\", \"keyup\",\n \"submit\", \"focus\", \"blur\",\n ]);\n\n /** 交互性 ARIA role — 需要分配 hash ID 的角色集合 */\n const INTERACTIVE_ROLES = new Set([\n \"button\", \"link\", \"tab\", \"switch\", \"slider\", \"checkbox\", \"radio\",\n \"combobox\", \"listbox\", \"option\", \"menuitem\", \"textbox\", \"spinbutton\",\n \"searchbox\", \"treeitem\", \"gridcell\", \"scrollbar\",\n ]);\n\n /**\n * 事件优先级(值越大越优先):\n * 输入链路(input/change/focus/blur) > 点击链路(click/pointer) > 其他事件。\n */\n const EVENT_PRIORITY: Record<string, number> = {\n input: 140,\n change: 130,\n focus: 120,\n blur: 110,\n keydown: 100,\n keyup: 90,\n click: 80,\n dblclick: 70,\n pointerdown: 60,\n pointerup: 55,\n mousedown: 50,\n mouseup: 45,\n touchstart: 40,\n touchend: 35,\n submit: 30,\n };\n\n /** 布尔状态属性 — 只在存在时输出(无值),如 disabled、checked */\n const BOOLEAN_ATTRS = [\n \"disabled\", \"checked\", \"readonly\", \"required\", \"selected\",\n \"hidden\",\n ];\n\n /**\n * 计算元素在父节点中同标签兄弟里的序号(1-based,XPath 规范)。\n * 如果同标签兄弟只有一个,返回空字符串(无需索引消歧)。\n */\n function getSiblingIndex(el: Element): string {\n const parent = el.parentElement;\n if (!parent) return \"\";\n const tag = el.tagName;\n const siblings = Array.from(parent.children).filter((c) => c.tagName === tag);\n if (siblings.length <= 1) return \"\";\n return `[${siblings.indexOf(el) + 1}]`;\n }\n\n /**\n * 判断元素是否与视口相交(部分可见也算)。\n * 对根级容器(depth <= 1)始终返回 true,确保不丢失顶层结构。\n */\n function isInViewport(el: Element, depth: number): boolean {\n if (!viewportOnly) return true;\n // 根级容器始终保留(body/html 等),否则整棵树会被跳过\n if (depth <= 1) return true;\n const rect = el.getBoundingClientRect();\n // 元素完全在视口外则跳过\n if (rect.bottom < 0 || rect.top > vpHeight) return false;\n if (rect.right < 0 || rect.left > vpWidth) return false;\n // 零尺寸元素(如隐藏的 position:absolute 元素)也跳过\n if (rect.width === 0 && rect.height === 0) return false;\n return true;\n }\n\n /**\n * 判断元素是否为「无意义布局容器」(智能剪枝候选)。\n * 满足所有条件时返回 true:\n * 1. 标签是常见布局容器(div/span/section 等)\n * 2. 没有 id\n * 3. 没有交互属性(href/role/aria-label/onclick 等)\n * 4. 没有直接文本内容\n */\n function isEmptyLayoutContainer(el: Element, directText: string): boolean {\n if (!pruneLayout) return false;\n if (!LAYOUT_TAGS.has(el.tagName)) return false;\n // 有 id 的元素可能是重要锚点\n if (el.getAttribute(\"id\")) return false;\n // 有 role/aria-label 的元素有语义\n if (el.getAttribute(\"role\") || el.getAttribute(\"aria-label\")) return false;\n // 有内联事件(onclick 等)的元素有交互\n for (const attr of Array.from(el.attributes)) {\n if (attr.name.startsWith(\"on\")) return false;\n }\n // 运行时追踪到事件绑定的元素有交互语义\n if (hasTrackedElementEvents(el)) return false;\n // 有直接文本内容的元素有意义\n if (directText) return false;\n return true;\n }\n\n function hasInteractiveTrackedEvents(el: Element): boolean {\n const tracked = getTrackedElementEvents(el);\n if (tracked.length === 0) return false;\n return tracked.some(name => INTERACTIVE_EVENTS.has(name));\n }\n\n function getTrackedEventPriorityScore(el: Element): number {\n const tracked = getTrackedElementEvents(el);\n if (tracked.length === 0) return 0;\n\n let score = 0;\n for (const name of tracked) {\n score += EVENT_PRIORITY[name] ?? 8;\n }\n return score;\n }\n\n /**\n * 元素优先级:\n * 1) 输入控件/按钮等语义控件\n * 2) 事件追踪优先级(输入、点击、失焦等)\n * 3) inline 事件与可聚焦能力补充加分\n */\n function getElementPriorityScore(el: Element): number {\n let score = 0;\n\n if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) {\n score += 200;\n } else if (el instanceof HTMLButtonElement || el instanceof HTMLAnchorElement) {\n score += 180;\n } else if (el.getAttribute(\"role\") === \"button\" || el.getAttribute(\"role\") === \"switch\" || el.getAttribute(\"role\") === \"slider\") {\n score += 160;\n }\n\n score += getTrackedEventPriorityScore(el);\n\n if (el.hasAttribute(\"onclick\")) score += 60;\n if (el.hasAttribute(\"oninput\") || el.hasAttribute(\"onchange\")) score += 80;\n if (el.hasAttribute(\"onfocus\") || el.hasAttribute(\"onblur\")) score += 70;\n if (el.hasAttribute(\"tabindex\")) score += 20;\n\n return score;\n }\n\n function orderChildrenByPriority(children: Element[]): Element[] {\n return children\n .map((child, index) => ({\n child,\n index,\n interactive: isInteractiveElement(child),\n score: getElementPriorityScore(child),\n }))\n .sort((a, b) => {\n if (a.interactive !== b.interactive) return a.interactive ? -1 : 1;\n if (b.score !== a.score) return b.score - a.score;\n return a.index - b.index;\n })\n .map(entry => entry.child);\n }\n\n function isInteractiveElement(el: Element): boolean {\n if (INTERACTIVE_TAGS.has(el.tagName)) return true;\n if (el.hasAttribute(\"onclick\")) return true;\n if (el.hasAttribute(\"role\")) return true;\n if (el.hasAttribute(\"tabindex\")) return true;\n if (el.hasAttribute(\"aria-label\")) return true;\n if (hasInteractiveTrackedEvents(el)) return true;\n return false;\n }\n\n /**\n * 判断元素是否需要分配 hash ID(仅交互节点分配,节省 token)。\n *\n * 核心依据:元素是否绑定了交互事件(INTERACTIVE_EVENTS 集合)。\n * 辅助依据:语义交互标签、内联事件、ARIA role、tabindex 等兜底。\n */\n function needsHashId(el: Element): boolean {\n // 核心判定:有追踪到的交互事件(click/input/change/focus/blur 等)\n if (hasInteractiveTrackedEvents(el)) return true;\n // 内联事件处理器(onclick/oninput 等)\n for (const attr of Array.from(el.attributes)) {\n if (attr.name.startsWith(\"on\")) return true;\n }\n // 语义交互标签兜底(即使没有事件追踪也应可操作)\n if (INTERACTIVE_TAGS.has(el.tagName)) return true;\n // 交互性 ARIA role 兜底\n const role = el.getAttribute(\"role\");\n if (role && INTERACTIVE_ROLES.has(role)) return true;\n // tabindex → 可聚焦\n if (el.hasAttribute(\"tabindex\")) return true;\n // contenteditable → 可编辑\n if ((el as HTMLElement).isContentEditable && el.getAttribute(\"contenteditable\") !== \"inherit\") return true;\n return false;\n }\n\n /** 判断是否为“选项列表”容器(时间/下拉/listbox 等)。 */\n function isOptionListContainer(el: Element): boolean {\n if (el.getAttribute(\"role\") === \"listbox\") return true;\n const cls = (el.getAttribute(\"class\") || \"\").toLowerCase();\n if (\n cls.includes(\"time-spinner__list\") ||\n cls.includes(\"select-dropdown\") ||\n cls.includes(\"virtual-list\") ||\n cls.includes(\"option\")\n ) {\n return true;\n }\n\n if (el.tagName === \"UL\") {\n const children = Array.from(el.children);\n if (children.length >= 20) {\n const liCount = children.filter(child => child.tagName === \"LI\").length;\n if (liCount / children.length >= 0.8) return true;\n }\n }\n return false;\n }\n\n /** 针对子节点截断计算动态上限。 */\n function resolveChildLimit(el: Element, defaultLimit: number, hashId?: string): number {\n let nextLimit = defaultLimit;\n if (expandOptionLists && isOptionListContainer(el)) {\n nextLimit = Math.max(nextLimit, MAX_EXPANDED_LIST_CHILDREN);\n }\n if (hashId && expandChildrenRefSet.has(hashId)) {\n nextLimit = Math.max(nextLimit, expandedChildrenLimit);\n }\n return nextLimit;\n }\n\n function walk(el: Element, depth: number, parentPath: string): string {\n if (emittedNodes >= maxNodes) {\n truncatedByNodeBudget = true;\n return \"\";\n }\n\n if (depth > maxDepth) return \"\";\n if (SKIP_TAGS.has(el.tagName)) return \"\";\n\n // 跳过标记为 autopilot 内部 UI 的元素(避免 AI 操作自身界面)\n if (el.hasAttribute(\"data-autopilot-ignore\")) return \"\";\n\n // 跳过不可见元素\n const style = window.getComputedStyle(el);\n if (style.display === \"none\" || style.visibility === \"hidden\") return \"\";\n\n // ─── 视口裁剪 ───\n // 检查元素是否在视口内(viewportOnly 关闭时始终通过)\n if (!isInViewport(el, depth)) return \"\";\n\n const indent = \" \".repeat(depth);\n const tag = el.tagName.toLowerCase();\n\n // ─── 角色优先标签 ───\n // 当元素有 INTERACTIVE_ROLES 内的 role 且与 tag 不等价时,\n // 用 role 替代 tag 作为显示标签(如 [combobox] 替代 [input] role=\"combobox\")。\n // 这让 AI 直接从标签判断交互模式,同时省去冗余的 role=\"...\" 属性。\n const rawRole = el.getAttribute(\"role\");\n const useRoleAsTag = !!(rawRole && INTERACTIVE_ROLES.has(rawRole) && rawRole !== tag);\n const displayTag = useRoleAsTag ? rawRole : tag;\n\n // 构建当前元素的内部路径(始终计算,子元素路径依赖它)\n // 注意:路径使用原始 tag 而非 displayTag,保证 hash 计算一致性\n const index = getSiblingIndex(el);\n const currentPath = `${parentPath}/${tag}${index}`;\n // 仅交互节点分配 hash ID 并注册到 RefStore,非交互节点不占 token\n const shouldAssignHash = refStore && needsHashId(el);\n const hashId = shouldAssignHash ? refStore.set(el, currentPath) : undefined;\n\n // 收集有意义的属性(精简版:只保留对 AI 操作有用的信息)\n const attrs: string[] = [];\n\n // 1. id — 最重要的标识信息\n const elId = el.getAttribute(\"id\");\n if (elId) attrs.push(`id=\"${elId}\"`);\n\n // 2. class — 只保留第 1 个有语义的类名(大幅减少 token)\n const className = el.getAttribute(\"class\")?.trim();\n if (className) {\n const cls = className.split(/\\s+/)\n .find(c => c && !c.startsWith(\"data-v-\") && c.length < 25 && !/^[a-z]{1,2}\\d|^_|^css-/.test(c));\n if (cls) attrs.push(`class=\"${cls}\"`);\n }\n\n // 3. 交互属性(href, type, placeholder 等)\n for (const attr of INTERACTIVE_ATTRS) {\n // 若 role 已提升为显示标签,跳过 role 属性避免重复输出\n if (attr === \"role\" && useRoleAsTag) continue;\n const val = el.getAttribute(attr);\n if (val) {\n const safeVal = sanitizeSnapshotAttrValue(val);\n if (safeVal) attrs.push(`${attr}=\"${safeVal}\"`);\n }\n }\n\n // 4. 布尔状态属性(disabled, checked 等)\n for (const attr of BOOLEAN_ATTRS) {\n if (el.hasAttribute(attr)) attrs.push(attr);\n }\n\n // 4.1 运行时布尔状态(property 级别),避免仅靠 attribute 导致状态丢失\n if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement || el instanceof HTMLButtonElement) {\n if (el.disabled && !attrs.includes(\"disabled\")) attrs.push(\"disabled\");\n }\n if ((el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) && el.readOnly) {\n if (!attrs.includes(\"readonly\")) attrs.push(\"readonly\");\n }\n\n // 5. 事件绑定 — 只标记有 onclick(最重要的交互信号)\n if (el.hasAttribute(\"onclick\")) attrs.push(\"onclick\");\n\n // 5.1 运行时事件绑定(addEventListener 追踪)\n const trackedEvents = getTrackedElementEvents(el);\n if (trackedEvents.length > 0) {\n const preview = trackedEvents.slice(0, 6).map(abbrevEvent).join(\",\");\n const suffix = trackedEvents.length > 6 ? \",...\" : \"\";\n attrs.push(`listeners=\"${preview}${suffix}\"`);\n }\n\n // 6. data-* 属性 — 只保留 data-testid(自动化测试定位用)\n const testId = el.getAttribute(\"data-testid\") || el.getAttribute(\"data-test-id\");\n if (testId) {\n const safeTestId = sanitizeSnapshotAttrValue(testId).slice(0, 25);\n if (safeTestId) attrs.push(`data-testid=\"${safeTestId}\"`);\n }\n\n // 7. 对于 input/textarea,补充当前实际 value(截短到 40 字符)\n if ((el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) && el.value) {\n const currentVal = sanitizeSnapshotAttrValue(el.value).slice(0, 40);\n const attrVal = el.getAttribute(\"value\");\n if (attrVal !== currentVal) {\n attrs.push(`val=\"${currentVal}\"`);\n }\n }\n\n // 7.1 对于 checkbox/radio,补充运行时 checked 状态(property 级别)\n if (el instanceof HTMLInputElement && (el.type === \"checkbox\" || el.type === \"radio\") && el.checked) {\n if (!attrs.includes(\"checked\")) attrs.push(\"checked\");\n }\n\n // 8. 对于 select,补充当前选中 value;对于 option,按运行时 selected 状态输出\n if (el instanceof HTMLSelectElement && el.value) {\n attrs.push(`val=\"${sanitizeSnapshotAttrValue(el.value).slice(0, 40)}\"`);\n }\n if (el instanceof HTMLOptionElement && el.selected) {\n if (!attrs.includes(\"selected\")) attrs.push(\"selected\");\n }\n\n // 获取直接文本(不含子元素文本)\n let directText = \"\";\n for (let i = 0; i < el.childNodes.length; i++) {\n const node = el.childNodes[i];\n if (node.nodeType === Node.TEXT_NODE) {\n const t = node.textContent?.trim();\n if (t) directText += t + \" \";\n }\n }\n directText = directText.trim();\n\n // ─── 智能剪枝 ───\n // 无意义布局容器:默认不输出自身行,直接将子元素提升到当前层级。\n // 若提升后同层出现多个孩子(如 a 与 b(c) 折叠成 a 与 c),\n // 则输出括号分组块,显式保留这些节点的关联来源。\n if (isEmptyLayoutContainer(el, directText)) {\n const allChildren = Array.from(el.children);\n const orderedChildren = orderChildrenByPriority(allChildren);\n const childLimit = resolveChildLimit(el, maxChildren, hashId);\n const selectedChildren = orderedChildren.slice(0, childLimit);\n const omittedChildren = orderedChildren.length - selectedChildren.length;\n\n const childBlocks: string[] = [];\n for (let i = 0; i < selectedChildren.length; i++) {\n // 子元素继承当前路径(保证 hash 计算正确),但不增加缩进\n const childResult = walk(selectedChildren[i], depth, currentPath);\n if (childResult) childBlocks.push(childResult);\n }\n\n // 如果子树也全部为空,整个容器就被剪掉\n if (childBlocks.length === 0 && omittedChildren <= 0) {\n return \"\";\n }\n\n const shouldGroupCollapsedChildren = childBlocks.length >= 2 || omittedChildren > 0;\n if (!shouldGroupCollapsedChildren) {\n return childBlocks.join(\"\\n\");\n }\n\n const groupLines: string[] = [\n `${\" \".repeat(depth)}([${displayTag}] collapsed-group`,\n ];\n for (const block of childBlocks) {\n groupLines.push(indentMultiline(block, 1));\n }\n\n if (omittedChildren > 0) {\n groupLines.push(`${\" \".repeat(depth + 1)}... (${omittedChildren} children omitted)`);\n }\n\n groupLines.push(`${\" \".repeat(depth)})`);\n return groupLines.join(\"\\n\");\n }\n\n // 构建当前元素描述:[显示标签] \"文本\" 属性 #hashID(仅交互节点)\n // displayTag 在有交互性 role 时为 role 值,否则为原始 HTML tag\n let line = `${indent}[${displayTag}]`;\n if (directText) line += ` \"${directText.slice(0, maxTextLength)}\"`;\n if (attrs.length) line += ` ${attrs.join(\" \")}`;\n // 仅交互节点输出 hash ID,非交互节点不附加标识(省 token)\n if (hashId) {\n line += ` #${hashId}`;\n }\n\n const lines: string[] = [line];\n emittedNodes++;\n\n // 递归子元素(优先保留可交互元素,再保留普通元素)\n const allChildren = Array.from(el.children);\n const orderedChildren = orderChildrenByPriority(allChildren);\n const childLimit = resolveChildLimit(el, maxChildren, hashId);\n const selectedChildren = orderedChildren.slice(0, childLimit);\n const omittedChildren = orderedChildren.length - selectedChildren.length;\n\n for (let i = 0; i < selectedChildren.length; i++) {\n const childResult = walk(selectedChildren[i], depth + 1, currentPath);\n if (childResult) lines.push(childResult);\n }\n\n if (omittedChildren > 0) {\n lines.push(`${indent} ... (${omittedChildren} children omitted)`);\n }\n\n return lines.join(\"\\n\");\n }\n\n // 根元素自身的标签作为路径起点,walk 内部不再重复追加\n // 例如 root=body 时,parentPath=\"\",walk 中 currentPath=\"/body\"\n const output = walk(root, 0, \"\") || \"(空页面)\";\n if (!truncatedByNodeBudget) return output;\n return `${output}\\n... (snapshot truncated: maxNodes=${maxNodes})`;\n}\n\n/**\n * 查询所有匹配元素并返回摘要信息(标签、文本、关键属性)。\n */\nfunction queryAllElements(selector: string, limit = 20): string {\n try {\n const elements = document.querySelectorAll(selector);\n if (elements.length === 0) return `未找到匹配 \"${selector}\" 的元素`;\n\n const results: string[] = [`找到 ${elements.length} 个元素:`];\n const count = Math.min(elements.length, limit);\n\n for (let i = 0; i < count; i++) {\n const el = elements[i];\n const tag = el.tagName.toLowerCase();\n const text = el.textContent?.trim().slice(0, 60) ?? \"\";\n const id = el.id ? `#${el.id}` : \"\";\n const cls = el.className && typeof el.className === \"string\"\n ? `.${el.className.split(\" \").filter(Boolean).join(\".\")}`\n : \"\";\n results.push(` ${i + 1}. <${tag}${id}${cls}> \"${text}\"`);\n }\n\n if (elements.length > limit) {\n results.push(` ...还有 ${elements.length - limit} 个元素`);\n }\n\n return results.join(\"\\n\");\n } catch {\n return `选择器语法错误: ${selector}`;\n }\n}\n\n/**\n * 多行文本块缩进(中)/ Indent each line of a multiline block (EN).\n */\nfunction indentMultiline(block: string, indentLevel: number): string {\n const prefix = \" \".repeat(indentLevel);\n return block\n .split(\"\\n\")\n .map(line => `${prefix}${line}`)\n .join(\"\\n\");\n}\n\nexport function createPageInfoTool(): ToolDefinition {\n return {\n name: \"page_info\",\n description: [\n \"Get information about the current page.\",\n \"Actions: get_url, get_title, get_selection (selected text),\",\n \"get_viewport (size & scroll), snapshot (DOM structure), query_all (find all matching elements).\",\n ].join(\" \"),\n\n schema: Type.Object({\n action: Type.String({\n description:\n \"Info action: get_url | get_title | get_selection | get_viewport | snapshot | query_all\",\n }),\n selector: Type.Optional(\n Type.String({ description: \"CSS selector for query_all action\" }),\n ),\n maxDepth: Type.Optional(\n Type.Number({ description: \"Max depth for snapshot (default: 12)\" }),\n ),\n viewportOnly: Type.Optional(\n Type.Boolean({ description: \"Only snapshot elements visible in viewport (default: true)\" }),\n ),\n pruneLayout: Type.Optional(\n Type.Boolean({ description: \"Collapse empty layout containers like div/span (default: true)\" }),\n ),\n maxNodes: Type.Optional(\n Type.Number({ description: \"Maximum nodes to include in snapshot (default: 220)\" }),\n ),\n maxChildren: Type.Optional(\n Type.Number({ description: \"Maximum children per element (default: 25)\" }),\n ),\n maxTextLength: Type.Optional(\n Type.Number({ description: \"Maximum text length per node (default: 40)\" }),\n ),\n expandOptionLists: Type.Optional(\n Type.Boolean({ description: \"Expand option-list containers to avoid child truncation (default: false)\" }),\n ),\n expandChildrenRefs: Type.Optional(\n Type.Array(Type.String({ description: \"Hash refs to expand child truncation for (e.g. #abc123)\" })),\n ),\n expandedChildrenLimit: Type.Optional(\n Type.Number({ description: \"Child limit for expandChildrenRefs nodes (default: 120, max: 300)\" }),\n ),\n }),\n\n execute: async (params): Promise<ToolCallResult> => {\n const action = params.action as string;\n\n try {\n switch (action) {\n case \"get_url\":\n return { content: window.location.href };\n\n case \"get_title\":\n return { content: document.title || \"(无标题)\" };\n\n case \"get_selection\": {\n // 获取用户当前选中的文本\n const selection = window.getSelection();\n const text = selection?.toString().trim() ?? \"\";\n return { content: text || \"(未选中任何文本)\" };\n }\n\n case \"get_viewport\": {\n // 获取视口和滚动信息\n const info = {\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n scrollX: window.scrollX,\n scrollY: window.scrollY,\n pageWidth: document.documentElement.scrollWidth,\n pageHeight: document.documentElement.scrollHeight,\n };\n return { content: JSON.stringify(info, null, 2) };\n }\n\n case \"snapshot\": {\n // 生成 DOM 快照 — AI 理解当前页面结构的主要方式\n const maxDepth = (params.maxDepth as number) ?? 12;\n const viewportOnly = (params.viewportOnly as boolean) ?? true;\n const pruneLayout = (params.pruneLayout as boolean) ?? true;\n const maxNodes = (params.maxNodes as number) ?? 220;\n const maxChildren = (params.maxChildren as number) ?? 25;\n const maxTextLength = (params.maxTextLength as number) ?? 40;\n const expandOptionLists = (params.expandOptionLists as boolean) ?? false;\n const expandChildrenRefs = Array.isArray(params.expandChildrenRefs)\n ? (params.expandChildrenRefs as unknown[]).filter((ref): ref is string => typeof ref === \"string\")\n : undefined;\n const expandedChildrenLimit = typeof params.expandedChildrenLimit === \"number\"\n ? params.expandedChildrenLimit as number\n : undefined;\n const snapshot = generateSnapshot(document.body, {\n maxDepth,\n viewportOnly,\n pruneLayout,\n maxNodes,\n maxChildren,\n maxTextLength,\n expandOptionLists,\n expandChildrenRefs,\n expandedChildrenLimit,\n refStore: getActiveRefStore(),\n });\n return { content: snapshot };\n }\n\n case \"query_all\": {\n // 查询所有匹配元素\n const selector = params.selector as string;\n if (!selector) return { content: \"缺少 selector 参数\" };\n return { content: queryAllElements(selector) };\n }\n\n default:\n return { content: `未知的页面信息动作: ${action}` };\n }\n } catch (err) {\n return {\n content: `页面信息操作 \"${action}\" 失败: ${err instanceof Error ? err.message : String(err)}`,\n details: { error: true, action },\n };\n }\n },\n };\n}\n","/**\n * Navigate Tool — 页面导航工具(增强版)。\n *\n * 支持 5 种动作:\n * goto — 跳转到指定 URL\n * back — 浏览器后退\n * forward — 浏览器前进\n * reload — 刷新当前页面\n * scroll — 滚动页面到指定位置或元素(支持 RefStore hash ID + 多策略对齐)\n */\nimport { Type } from \"@sinclair/typebox\";\nimport type { ToolDefinition, ToolCallResult } from \"../../core/tool-registry.js\";\nimport { getActiveRefStore } from \"./dom-tool.js\";\n\n/** 解析 selector(支持 RefStore hash ID 和 CSS 选择器) */\nfunction resolveElement(selector: string): Element | null {\n if (selector.startsWith(\"#\")) {\n const store = getActiveRefStore();\n if (store) {\n const id = selector.slice(1);\n if (store.has(id)) return store.get(id) ?? null;\n }\n }\n try { return document.querySelector(selector); } catch { return null; }\n}\n\nexport function createNavigateTool(): ToolDefinition {\n return {\n name: \"navigate\",\n description: [\n \"Navigate the current page.\",\n \"Actions: goto (open URL), back, forward, reload, scroll (to position or element).\",\n \"scroll supports hash ID from snapshot (e.g. #r0) or CSS selector.\",\n ].join(\" \"),\n\n schema: Type.Object({\n action: Type.String({\n description: \"Navigation action: goto | back | forward | reload | scroll\",\n }),\n url: Type.Optional(Type.String({ description: \"URL for goto action\" })),\n selector: Type.Optional(\n Type.String({ description: \"Element ref ID from snapshot (e.g. #r0) or CSS selector for scroll action\" }),\n ),\n x: Type.Optional(Type.Number({ description: \"Horizontal scroll position (pixels)\" })),\n y: Type.Optional(Type.Number({ description: \"Vertical scroll position (pixels)\" })),\n }),\n\n execute: async (params): Promise<ToolCallResult> => {\n const action = params.action as string;\n\n try {\n switch (action) {\n case \"goto\": {\n const url = params.url as string;\n if (!url) return { content: \"缺少 url 参数\" };\n window.location.href = url;\n return { content: `正在导航到 ${url}` };\n }\n\n case \"back\": {\n window.history.back();\n return { content: \"已后退\" };\n }\n\n case \"forward\": {\n window.history.forward();\n return { content: \"已前进\" };\n }\n\n case \"reload\": {\n window.location.reload();\n return { content: \"正在刷新页面\" };\n }\n\n case \"scroll\": {\n const selector = params.selector as string | undefined;\n\n if (selector) {\n const el = resolveElement(selector);\n if (!el) return { content: `未找到元素 \"${selector}\"` };\n // 尝试 scrollIntoViewIfNeeded(Chrome),回退 scrollIntoView center\n if (\"scrollIntoViewIfNeeded\" in el) {\n (el as HTMLElement & { scrollIntoViewIfNeeded: (c?: boolean) => void }).scrollIntoViewIfNeeded(true);\n } else {\n el.scrollIntoView({ behavior: \"smooth\", block: \"center\" });\n }\n return { content: `已滚动到元素 \"${selector}\"` };\n }\n\n const x = (params.x as number) ?? 0;\n const y = (params.y as number) ?? 0;\n window.scrollTo({ left: x, top: y, behavior: \"smooth\" });\n return { content: `已滚动到 (${x}, ${y})` };\n }\n\n default:\n return { content: `未知的导航动作: ${action}` };\n }\n } catch (err) {\n return {\n content: `导航操作 \"${action}\" 失败: ${err instanceof Error ? err.message : String(err)}`,\n details: { error: true, action },\n };\n }\n },\n };\n}\n","/**\n * Wait Tool 等待工具 / Wait utility for DOM conditions.\n *\n * 支持动作 / Supported actions:\n * - wait_for_selector: 等待选择器达到状态 / wait selector state\n * - wait_for_hidden: 等待元素隐藏或移除 / wait element hidden or detached\n * - wait_for_text: 等待页面出现文本 / wait text appears in page\n * - wait_for_stable: 等待 DOM 进入静默窗口 / wait DOM quiet window\n *\n * 说明 / Notes:\n * - hash selector(如 #abc123)优先通过 RefStore 解析。\n * - 可见性语义与 dom-tool 保持一致(参考 Playwright 风格)。\n */\nimport { Type } from \"@sinclair/typebox\";\nimport type { ToolDefinition, ToolCallResult } from \"../../core/tool-registry.js\";\nimport { getActiveRefStore } from \"./dom-tool.js\";\n\nconst DEFAULT_TIMEOUT = 6_000;\nconst POLL_INTERVAL_MS = 80;\nconst STABLE_TICK_MS = 50;\nconst OBSERVER_OPTIONS: MutationObserverInit = {\n childList: true,\n subtree: true,\n attributes: true,\n characterData: true,\n};\nconst TEXT_OBSERVER_OPTIONS: MutationObserverInit = {\n childList: true,\n subtree: true,\n characterData: true,\n};\n\ntype SelectorState = \"attached\" | \"visible\" | \"hidden\" | \"detached\";\n\n/**\n * 可见性判定 / Visibility check.\n *\n * 与 dom-tool 保持一致,处理 display:contents、visibility、opacity、零尺寸等场景。\n */\nfunction isVisible(el: Element): boolean {\n if (!(el instanceof HTMLElement || el instanceof SVGElement)) return false;\n if (!el.isConnected) return false;\n const style = window.getComputedStyle(el);\n\n if (style.display === \"contents\") {\n for (let child = el.firstChild; child; child = child.nextSibling) {\n if (child.nodeType === Node.ELEMENT_NODE && isVisible(child as Element)) return true;\n if (child.nodeType === Node.TEXT_NODE) {\n const range = document.createRange();\n range.selectNodeContents(child);\n const rects = range.getClientRects();\n for (let i = 0; i < rects.length; i++) {\n if (rects[i].width > 0 && rects[i].height > 0) return true;\n }\n }\n }\n return false;\n }\n if (style.display === \"none\") return false;\n if (typeof el.checkVisibility === \"function\") {\n if (!el.checkVisibility()) return false;\n }\n if (style.visibility !== \"visible\") return false;\n if (style.opacity === \"0\") return false;\n const rect = el.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n}\n\n/**\n * 解析选择器 / Resolve selector.\n *\n * 先尝试 RefStore hash,再回退到 document.querySelector。\n */\nfunction resolveSelector(selector: string): Element | null {\n if (selector.startsWith(\"#\")) {\n const store = getActiveRefStore();\n if (store) {\n const id = selector.slice(1);\n if (store.has(id)) return store.get(id) ?? null;\n }\n }\n try { return document.querySelector(selector); } catch { return null; }\n}\n\n/**\n * 计算选择器状态 / Evaluate selector state.\n *\n * @returns matched 表示是否达到目标状态;element 为当前命中的元素(如果存在)。\n */\nfunction evaluateSelectorState(selector: string, state: SelectorState): { matched: boolean; element?: Element } {\n const el = resolveSelector(selector) ?? undefined;\n switch (state) {\n case \"attached\":\n return { matched: Boolean(el), element: el };\n case \"visible\":\n return { matched: Boolean(el && isVisible(el)), element: el };\n case \"hidden\":\n return { matched: !el || !isVisible(el), element: el };\n case \"detached\":\n return { matched: !el, element: el };\n default:\n return { matched: false };\n }\n}\n\n/**\n * 等待选择器达到指定状态 / Wait selector reaches state.\n *\n * 策略:轮询 + MutationObserver 双通道,既保证及时性也降低漏检概率。\n */\nfunction waitForSelectorState(\n selector: string,\n state: SelectorState,\n timeoutMs: number,\n): Promise<{ element?: Element }> {\n return new Promise((resolve, reject) => {\n let finished = false;\n\n const finish = (handler: () => void): void => {\n if (finished) return;\n finished = true;\n clearTimeout(timer);\n clearInterval(interval);\n observer.disconnect();\n handler();\n };\n\n const check = (): void => {\n let result: { matched: boolean; element?: Element };\n try {\n result = evaluateSelectorState(selector, state);\n } catch {\n finish(() => reject(new Error(`选择器语法错误: ${selector}`)));\n return;\n }\n if (result.matched) {\n finish(() => resolve({ element: result.element }));\n }\n };\n\n const timer = setTimeout(() => {\n finish(() => reject(new Error(`等待 \"${selector}\" 达到状态 \"${state}\" 超时 (${timeoutMs}ms)`)));\n }, timeoutMs);\n\n const interval = setInterval(check, POLL_INTERVAL_MS);\n const observer = new MutationObserver(check);\n observer.observe(document.body, OBSERVER_OPTIONS);\n\n check();\n });\n}\n\n/**\n * 等待文本出现 / Wait text appears.\n *\n * 先做一次即时检查,再监听 DOM 变化。\n */\nfunction waitForText(text: string, timeoutMs: number): Promise<void> {\n return new Promise((resolve, reject) => {\n // 先检查是否已包含\n if (document.body.textContent?.includes(text)) {\n resolve();\n return;\n }\n\n const timer = setTimeout(() => {\n observer.disconnect();\n reject(new Error(`等待文本 \"${text}\" 出现超时 (${timeoutMs}ms)`));\n }, timeoutMs);\n\n const observer = new MutationObserver(() => {\n if (document.body.textContent?.includes(text)) {\n clearTimeout(timer);\n observer.disconnect();\n resolve();\n }\n });\n\n observer.observe(document.body, TEXT_OBSERVER_OPTIONS);\n });\n}\n\n/**\n * 等待 DOM 稳定 / Wait DOM stable.\n *\n * 定义:quietMs 窗口内没有任何 MutationObserver 事件。\n */\nfunction waitForDomStable(timeoutMs: number, quietMs: number): Promise<void> {\n return new Promise((resolve, reject) => {\n const startedAt = Date.now();\n let lastMutationAt = Date.now();\n\n const finish = (ok: boolean, err?: Error): void => {\n clearInterval(tick);\n observer.disconnect();\n if (ok) resolve();\n else reject(err ?? new Error(\"等待页面稳定失败\"));\n };\n\n const observer = new MutationObserver(() => {\n lastMutationAt = Date.now();\n });\n\n observer.observe(document.body, OBSERVER_OPTIONS);\n\n const tick = setInterval(() => {\n const now = Date.now();\n if (now - startedAt > timeoutMs) {\n finish(false, new Error(`等待页面稳定超时 (${timeoutMs}ms)`));\n return;\n }\n if (now - lastMutationAt >= quietMs) {\n finish(true);\n }\n }, STABLE_TICK_MS);\n });\n}\n\nexport function createWaitTool(): ToolDefinition {\n return {\n name: \"wait\",\n description: [\n \"Wait for DOM changes on the current page.\",\n \"Actions: wait_for_selector (element appears), wait_for_hidden (element disappears),\",\n \"wait_for_text (specific text appears in page), wait_for_stable (DOM stops changing).\",\n ].join(\" \"),\n\n schema: Type.Object({\n action: Type.String({\n description: \"Wait action: wait_for_selector | wait_for_hidden | wait_for_text | wait_for_stable\",\n }),\n selector: Type.Optional(\n Type.String({ description: \"CSS selector for wait_for_selector/wait_for_hidden\" }),\n ),\n state: Type.Optional(\n Type.String({ description: \"Selector state for wait_for_selector: attached | visible | hidden | detached (default: attached)\" }),\n ),\n text: Type.Optional(\n Type.String({ description: \"Text to wait for in wait_for_text\" }),\n ),\n timeout: Type.Optional(\n Type.Number({ description: \"Timeout in milliseconds (default: 6000)\" }),\n ),\n quietMs: Type.Optional(\n Type.Number({ description: \"Quiet window for wait_for_stable in milliseconds (default: 300)\" }),\n ),\n }),\n\n execute: async (params): Promise<ToolCallResult> => {\n const action = params.action as string;\n const timeoutMs = (params.timeout as number) ?? DEFAULT_TIMEOUT;\n\n try {\n switch (action) {\n case \"wait_for_selector\": {\n const selector = params.selector as string;\n if (!selector) return { content: \"缺少 selector 参数\" };\n const state = (params.state as SelectorState | undefined) ?? \"attached\";\n if (![\"attached\", \"visible\", \"hidden\", \"detached\"].includes(state)) {\n return { content: `无效 state: ${state}` };\n }\n const result = await waitForSelectorState(selector, state, timeoutMs);\n if (state === \"attached\" || state === \"visible\") {\n const tag = result.element?.tagName?.toLowerCase();\n return { content: `元素 \"${selector}\" 已达到状态 \"${state}\"${tag ? ` (${tag})` : \"\"}` };\n }\n return { content: `元素 \"${selector}\" 已达到状态 \"${state}\"` };\n }\n\n case \"wait_for_hidden\": {\n const selector = params.selector as string;\n if (!selector) return { content: \"缺少 selector 参数\" };\n await waitForSelectorState(selector, \"hidden\", timeoutMs);\n return { content: `元素 \"${selector}\" 已隐藏或消失` };\n }\n\n case \"wait_for_text\": {\n const text = params.text as string;\n if (!text) return { content: \"缺少 text 参数\" };\n await waitForText(text, timeoutMs);\n return { content: `文本 \"${text}\" 已出现` };\n }\n\n case \"wait_for_stable\": {\n const quietMs = Math.max(50, Math.floor((params.quietMs as number) ?? 300));\n await waitForDomStable(timeoutMs, quietMs);\n return { content: `页面已稳定(静默窗口 ${quietMs}ms)` };\n }\n\n default:\n return { content: `未知的等待动作: ${action}` };\n }\n } catch (err) {\n return {\n content: `等待操作 \"${action}\" 失败: ${err instanceof Error ? err.message : String(err)}`,\n details: { error: true, action },\n };\n }\n },\n };\n}\n","/**\n * Evaluate Tool — 在页面上下文中执行任意 JavaScript 表达式。\n *\n * 替代 Playwright 的 page.evaluate()。\n * 运行环境:浏览器 Content Script。\n *\n * 这是最灵活的工具 — 当其他 tools 无法满足需求时,\n * AI 可以直接编写 JS 代码来操作页面。\n *\n * 支持 2 种动作:\n * evaluate — 执行 JS 表达式并返回结果\n * evaluate_handle — 执行 JS 并返回序列化的 DOM 信息\n */\nimport { Type } from \"@sinclair/typebox\";\nimport type { ToolDefinition, ToolCallResult } from \"../../core/tool-registry.js\";\n\n/**\n * 安全执行 JS 表达式,捕获错误并序列化结果。\n */\nfunction safeEvaluate(expression: string): { result?: unknown; error?: string } {\n try {\n // 使用 Function 构造器代替 eval,避免污染当前作用域\n const fn = new Function(`\"use strict\"; return (${expression});`);\n const result = fn();\n return { result };\n } catch {\n // 如果作为表达式失败,尝试作为语句块执行\n try {\n const fn = new Function(`\"use strict\"; ${expression}`);\n const result = fn();\n return { result };\n } catch (err2) {\n return { error: err2 instanceof Error ? err2.message : String(err2) };\n }\n }\n}\n\n/**\n * 将执行结果序列化为字符串(处理 DOM 元素、循环引用等)。\n */\nfunction serializeResult(value: unknown): string {\n if (value === undefined) return \"undefined\";\n if (value === null) return \"null\";\n\n // DOM 元素 → 返回 outerHTML 片段\n if (value instanceof Element) {\n const tag = value.tagName.toLowerCase();\n const id = value.id ? `#${value.id}` : \"\";\n const text = value.textContent?.trim().slice(0, 100) ?? \"\";\n return `<${tag}${id}> \"${text}\"`;\n }\n\n // NodeList / HTMLCollection → 逐个序列化\n if (value instanceof NodeList || value instanceof HTMLCollection) {\n const items = Array.from(value).map((el, i) => ` ${i}: ${serializeResult(el)}`);\n return `[${value.length} elements]\\n${items.join(\"\\n\")}`;\n }\n\n // 普通值 → JSON 序列化\n try {\n return JSON.stringify(value, null, 2);\n } catch {\n return String(value);\n }\n}\n\nexport function createEvaluateTool(): ToolDefinition {\n return {\n name: \"evaluate\",\n description: [\n \"Execute JavaScript code in the current page context.\",\n \"Use this when other tools cannot accomplish the task.\",\n \"Can access document, window, and all page APIs.\",\n ].join(\" \"),\n\n schema: Type.Object({\n expression: Type.String({\n description:\n \"JavaScript expression or code block to execute. Has access to document, window, etc.\",\n }),\n }),\n\n execute: async (params): Promise<ToolCallResult> => {\n const expression = params.expression as string;\n if (!expression) return { content: \"缺少 expression 参数\" };\n\n const { result, error } = safeEvaluate(expression);\n\n if (error) {\n return {\n content: `JS 执行错误: ${error}`,\n details: { error: true, expression },\n };\n }\n\n return { content: serializeResult(result) };\n },\n };\n}\n","/**\n * RefStore — 快照 hash ID 与 DOM 元素的映射表。\n *\n * 快照生成时,根据元素的 DOM 路径 + 页面 URL 生成确定性 hash ID,\n * 同时保存 ID → Element 的映射。AI 使用 hash ID 作为 selector 定位元素,\n * 免去超长 XPath 路径,大幅减少 token 消耗。\n *\n * 优势:\n * - **确定性**:同一元素无论快照顺序,始终得到相同 ID\n * - **并发安全**:多次快照不会产生 ID 冲突\n * - **跨页面隔离**:URL hash 作为命名空间,不同页面元素 ID 互不碰撞\n *\n * 生命周期:每次 WebAgent.chat() 调用时创建,对话结束后清空。\n *\n * 使用方:\n * page-info-tool.ts — generateSnapshot() 写入映射\n * dom-tool.ts — queryElement() 读取映射\n * index.ts — WebAgent 持有实例,管理生命周期\n */\n\n/**\n * FNV-1a 32-bit hash — 简单高效的字符串散列。\n * 分布均匀,碰撞率低,适合生成短 ID。\n */\nfunction fnv1a(str: string): number {\n let h = 0x811c9dc5; // FNV offset basis\n for (let i = 0; i < str.length; i++) {\n h ^= str.charCodeAt(i);\n h = Math.imul(h, 0x01000193); // FNV prime\n }\n return h >>> 0; // 转为无符号 32-bit\n}\n\n/**\n * hash ID → DOM 元素的映射存储。\n *\n * - `set(el, path)` 由快照生成时调用,返回确定性 hash ID\n * - `get(id)` 由 dom-tool 查询时调用,根据 hash ID 取回元素\n * - `has(id)` 检查 ID 是否存在(用于 selector 类型判断)\n * - `clear()` 每次对话结束后清空\n */\nexport class RefStore {\n private map = new Map<string, Element>();\n /** 页面 URL 的 hash 前缀,用于跨页面命名空间隔离 */\n private urlKey: string;\n\n /**\n * @param url 当前页面 URL(可选)。传入后作为 hash 命名空间,\n * 使不同页面的相同 DOM 路径产生不同 ID。\n */\n constructor(url?: string) {\n this.urlKey = url ?? \"\";\n }\n\n /**\n * 注册一个元素,返回确定性 hash ID。\n * 相同 URL + path 始终产生相同 ID(并发安全)。\n *\n * @param el DOM 元素引用\n * @param path 元素的 XPath-like 路径(如 \"/body/div[1]/main/button\")\n */\n set(el: Element, path: string): string {\n const baseId = fnv1a(this.urlKey + path).toString(36);\n let id = baseId;\n // 极小概率碰撞处理:不同 path 映射到相同 hash 时追加后缀\n let suffix = 2;\n while (this.map.has(id) && this.map.get(id) !== el) {\n id = baseId + suffix++;\n }\n this.map.set(id, el);\n return id;\n }\n\n /**\n * 根据 hash ID 获取 DOM 元素。\n * 返回 Element 或 undefined(ID 不存在或元素已被移除)。\n */\n get(id: string): Element | undefined {\n return this.map.get(id);\n }\n\n /** 检查 hash ID 是否存在 */\n has(id: string): boolean {\n return this.map.has(id);\n }\n\n /** 清空所有映射 */\n clear(): void {\n this.map.clear();\n }\n\n /**\n * 重置映射表:清空所有映射,并可选更新 URL 命名空间。\n *\n * 用于页面导航后刷新 RefStore:旧的 hash ID → Element 映射已失效,\n * 需要用新 URL 重新生成确定性 hash。\n *\n * @param url 新的页面 URL(不传则保持原 URL 命名空间)\n */\n reset(url?: string): void {\n this.map.clear();\n if (url !== undefined) {\n this.urlKey = url;\n }\n }\n\n /** 当前映射数量 */\n get size(): number {\n return this.map.size;\n }\n}\n","/**\n * Web Tools 消息通信桥接层。\n *\n * 解决 Chrome Extension 的作用域隔离问题:\n *\n * Service Worker (后台) Content Script (页面)\n * ┌──────────────────┐ ┌──────────────────────┐\n * │ agent-core │ │ document / window │\n * │ tool-registry │ chrome.tabs │ │\n * │ │ .sendMessage() │ DOM 操作实际执行 │\n * │ tool.execute() │ ─────────────────► │ handleToolMessage() │\n * │ ↓ │ │ ↓ │\n * │ sendToContent() │ ◄───────────────── │ 返回执行结果 │\n * └──────────────────┘ response └──────────────────────┘\n *\n * 使用方式:\n * Service Worker 端:\n * import { createProxyExecutor } from \"./messaging.js\";\n * const execute = createProxyExecutor();\n * // execute 会把调用转发到 content script\n *\n * Content Script 端:\n * import { registerToolHandler } from \"./messaging.js\";\n * registerToolHandler(actualExecutors);\n * // 监听来自 service worker 的工具调用请求\n */\n\n// ─── 消息类型定义 ───\n\n/** Service Worker → Content Script 的工具调用请求 */\nexport type ToolCallMessage = {\n type: \"AUTOPILOT_TOOL_CALL\";\n toolName: string;\n params: Record<string, unknown>;\n callId: string;\n};\n\n/** Content Script → Service Worker 的工具调用结果 */\nexport type ToolCallResponse = {\n type: \"AUTOPILOT_TOOL_RESULT\";\n callId: string;\n result: {\n content: string | Record<string, unknown>;\n details?: Record<string, unknown>;\n };\n};\n\n// ─── Service Worker 端(发送方) ───\n\n/**\n * 创建代理执行器 — 在 Service Worker 端使用。\n *\n * 它不直接执行 DOM 操作,而是通过 chrome.tabs.sendMessage\n * 把调用请求发给当前活动 tab 的 content script 执行。\n *\n * @returns execute 函数,签名与 ToolDefinition.execute 相同\n */\nexport function createProxyExecutor() {\n return async (\n toolName: string,\n params: Record<string, unknown>,\n ): Promise<{ content: string | Record<string, unknown>; details?: Record<string, unknown> }> => {\n const callId = `${toolName}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;\n\n // 获取当前活动 tab\n const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });\n if (!tab?.id) {\n return { content: \"错误:没有活动的浏览器标签页\" };\n }\n\n // 发送消息到 content script 并等待结果\n const message: ToolCallMessage = {\n type: \"AUTOPILOT_TOOL_CALL\",\n toolName,\n params,\n callId,\n };\n\n try {\n const response = await chrome.tabs.sendMessage(tab.id, message) as ToolCallResponse;\n return response.result;\n } catch (err) {\n return {\n content: `工具调用失败(content script 可能未加载): ${err instanceof Error ? err.message : String(err)}`,\n details: { error: true, toolName },\n };\n }\n };\n}\n\n// ─── Content Script 端(接收方) ───\n\n/** 工具执行器映射:toolName → execute 函数 */\nexport type ToolExecutorMap = Map<\n string,\n (params: Record<string, unknown>) => Promise<{\n content: string | Record<string, unknown>;\n details?: Record<string, unknown>;\n }>\n>;\n\n/**\n * 在 Content Script 端注册工具执行处理器。\n *\n * 监听来自 Service Worker 的 AUTOPILOT_TOOL_CALL 消息,\n * 根据 toolName 找到对应的执行函数,执行后返回结果。\n *\n * @param executors 工具名称 → 执行函数的映射\n */\nexport function registerToolHandler(executors: ToolExecutorMap): void {\n chrome.runtime.onMessage.addListener(\n (message: unknown, _sender: chrome.runtime.MessageSender, sendResponse: (response: ToolCallResponse) => void) => {\n // 只处理我们的消息类型\n const msg = message as ToolCallMessage;\n if (msg?.type !== \"AUTOPILOT_TOOL_CALL\") return false;\n\n const executor = executors.get(msg.toolName);\n if (!executor) {\n sendResponse({\n type: \"AUTOPILOT_TOOL_RESULT\",\n callId: msg.callId,\n result: { content: `未知工具: ${msg.toolName}` },\n });\n return true; // 同步返回 true 表示我们会异步 sendResponse\n }\n\n // 异步执行工具并返回结果\n executor(msg.params)\n .then((result) => {\n sendResponse({\n type: \"AUTOPILOT_TOOL_RESULT\",\n callId: msg.callId,\n result,\n });\n })\n .catch((err) => {\n sendResponse({\n type: \"AUTOPILOT_TOOL_RESULT\",\n callId: msg.callId,\n result: {\n content: `工具 ${msg.toolName} 执行异常: ${err instanceof Error ? err.message : String(err)}`,\n details: { error: true },\n },\n });\n });\n\n return true; // 告诉 Chrome 我们会异步调用 sendResponse\n },\n );\n}\n","/**\n * WebAgent — 浏览器端 AI Agent 类。\n *\n * 封装了完整的 Agent 能力,可在浏览器中独立运行:\n * - 对话(chat) → 发消息、获取 AI 回复\n * - 工具注册 → 注册内置 Web 工具或自定义工具\n * - 决策循环 → 复用 core/agent-loop.ts 的通用逻辑\n * - AI 连接 → 复用 core/ai-client.ts(基于 fetch,跨平台)\n *\n * 使用示例:\n * ```ts\n * const agent = new WebAgent({ token: \"ghp_xxx\", provider: \"copilot\" });\n * agent.registerTools(); // 注册内置 Web 工具\n * agent.callbacks.onText = (text) => console.log(text);\n *\n * const result = await agent.chat(\"获取页面标题\");\n * console.log(result.reply);\n * ```\n *\n * 架构位置:\n * ┌──────────────────────────────────────────────────┐\n * │ WebAgent(浏览器端入口) │\n * │ ┌──────────┐ ┌────────────┐ ┌──────────────┐ │\n * │ │ core/ │ │ core/ │ │ web/ │ │\n * │ │ ai-client│ │ agent-loop │ │ (DOM/导航等)│ │\n * │ │ (fetch) │ │ (通用循环) │ │ │ │\n * │ └──────────┘ └────────────┘ └──────────────┘ │\n * └──────────────────────────────────────────────────┘\n */\nimport {\n executeAgentLoop,\n type AgentLoopCallbacks,\n type AgentLoopResult,\n type RoundStabilityWaitOptions,\n wrapSnapshot,\n} from \"../core/agent-loop/index.js\";\nimport type { AIMessage } from \"../core/types.js\";\nimport { createAIClient } from \"../core/ai-client/index.js\";\nimport type { AIClient } from \"../core/types.js\";\nimport { ToolRegistry, type ToolDefinition } from \"../core/tool-registry.js\";\nimport { buildSystemPrompt } from \"../core/system-prompt.js\";\nimport { generateSnapshot, type SnapshotOptions } from \"./tools/page-info-tool.js\";\nimport { createDomTool, setActiveRefStore } from \"./tools/dom-tool.js\";\nimport { createNavigateTool } from \"./tools/navigate-tool.js\";\nimport { createPageInfoTool } from \"./tools/page-info-tool.js\";\nimport { createWaitTool } from \"./tools/wait-tool.js\";\nimport { createEvaluateTool } from \"./tools/evaluate-tool.js\";\nimport { RefStore } from \"./ref-store.js\";\nimport { installEventListenerTracking } from \"./event-listener-tracker.js\";\n\n// 默认安装全局事件监听追踪(幂等),用于快照输出 listeners 信号。\ninstallEventListenerTracking();\n\n// ─── 回调类型 ───\n\n/** WebAgent 事件回调(扩展 AgentLoopCallbacks,增加快照事件) */\nexport type WebAgentCallbacks = AgentLoopCallbacks & {\n /** 自动快照生成完成时触发 */\n onSnapshot?: (snapshot: string) => void;\n};\n\n// ─── 配置 ───\n\nexport type WebAgentOptions = {\n /**\n * 自定义 AI 客户端实例(可选)。\n *\n * 传入后将直接使用该实例进行对话,忽略 token / provider / model / baseURL。\n * 支持 BaseAIClient 或任何实现 AIClient 接口的对象。\n *\n * ```ts\n * const client = new BaseAIClient({ chatHandler: async (params) => { ... } });\n * const agent = new WebAgent({ client });\n * ```\n */\n client?: AIClient;\n /** API 认证 Token (GitHub PAT / OpenAI key / Anthropic key) */\n token?: string;\n /** AI 提供商: \"copilot\" | \"openai\" | \"anthropic\" | \"deepseek\" | \"doubao\" | \"qwen\"(默认 \"copilot\") */\n provider?: string;\n /** 模型名称(默认 \"gpt-4o\") */\n model?: string;\n /** 自定义 API 基础 URL(可选,覆盖 provider 默认值) */\n baseURL?: string;\n /** 是否启用流式输出(SSE)。默认 true;false 时使用 JSON 非流式响应。 */\n stream?: boolean;\n /** 是否启用干运行模式 */\n dryRun?: boolean;\n /**\n * 系统提示词注册项。\n * - string:按默认 key 注册一条\n * - Record<string, string>:按 key 批量注册多条\n */\n systemPrompt?: string | Record<string, string>;\n /** 最大工具调用轮次(默认 10) */\n maxRounds?: number;\n /** 是否启用多轮对话记忆(默认 false) */\n memory?: boolean;\n /** 是否在每次对话前自动生成页面快照(默认 true) */\n autoSnapshot?: boolean;\n /** 快照选项(视口裁剪、智能剪枝等,autoSnapshot 开启时生效) */\n snapshotOptions?: SnapshotOptions;\n /** 轮次后稳定等待(加载态 + DOM 静默)配置 */\n roundStabilityWait?: RoundStabilityWaitOptions;\n};\n\n// ─── WebAgent 类 ───\n\nexport class WebAgent {\n /** 默认系统提示词 key(兼容旧版 setSystemPrompt(prompt))。 */\n private static readonly DEFAULT_SYSTEM_PROMPT_KEY = \"default\";\n /** 默认内置工具名(注册后受保护,不允许删除)。 */\n private static readonly DEFAULT_TOOL_NAMES = [\"dom\", \"navigate\", \"page_info\", \"wait\", \"evaluate\"] as const;\n\n /** 用户传入的自定义 AI 客户端实例(优先级高于 token/provider) */\n private client?: AIClient;\n private token: string;\n private provider: string;\n private model: string;\n private baseURL?: string;\n private stream: boolean;\n private dryRun: boolean;\n private maxRounds: number;\n /** system prompt 注册表(key -> prompt 文本)。 */\n private systemPromptRegistry = new Map<string, string>();\n /** 受保护工具集合(默认工具)。 */\n private protectedToolNames = new Set<string>();\n\n /** 多轮对话记忆开关 */\n private memory: boolean;\n /** 对话历史(memory 开启时自动累积) */\n private history: AIMessage[] = [];\n /** 自动快照开关 */\n private autoSnapshot: boolean;\n /** 快照选项 */\n private snapshotOptions: SnapshotOptions;\n /** 轮次后稳定等待配置 */\n private roundStabilityWait?: RoundStabilityWaitOptions;\n\n /** 工具注册表实例 — 每个 WebAgent 拥有独立的工具集 */\n private registry = new ToolRegistry();\n\n /** 事件回调 — 绑定后可实时获取 Agent 进度,用于 UI 展示 */\n callbacks: WebAgentCallbacks = {};\n\n constructor(options: WebAgentOptions) {\n this.client = options.client;\n this.token = options.token || \"\";\n this.provider = options.provider ?? \"copilot\";\n this.model = options.model ?? \"gpt-4o\";\n this.baseURL = options.baseURL;\n this.stream = options.stream ?? true;\n this.dryRun = options.dryRun ?? false;\n this.maxRounds = options.maxRounds ?? 40;\n this.memory = options.memory ?? false;\n this.autoSnapshot = options.autoSnapshot ?? true;\n this.snapshotOptions = options.snapshotOptions ?? {};\n this.roundStabilityWait = options.roundStabilityWait;\n\n if (typeof options.systemPrompt === \"string\") {\n this.setSystemPrompt(options.systemPrompt);\n } else if (options.systemPrompt && typeof options.systemPrompt === \"object\") {\n this.setSystemPrompts(options.systemPrompt);\n }\n }\n\n // ─── 工具管理 ───\n\n /** 注册所有内置 Web 工具(dom, navigate, page_info, wait, evaluate) */\n registerTools(): void {\n this.registry.register(createDomTool());\n this.registry.register(createNavigateTool());\n this.registry.register(createPageInfoTool());\n this.registry.register(createWaitTool());\n this.registry.register(createEvaluateTool());\n\n for (const name of WebAgent.DEFAULT_TOOL_NAMES) {\n this.protectedToolNames.add(name);\n }\n }\n\n /** 注册一个自定义工具 */\n registerTool(tool: ToolDefinition): void {\n this.registry.register(tool);\n }\n\n /**\n * 删除一个已注册工具。\n * - 默认内置工具(registerTools 注册)不允许删除\n * - 返回 true 表示删除成功,false 表示不存在或受保护\n */\n removeTool(name: string): boolean {\n if (this.protectedToolNames.has(name)) return false;\n return this.registry.unregister(name);\n }\n\n /** 检查工具是否已注册。 */\n hasTool(name: string): boolean {\n return this.registry.has(name);\n }\n\n /** 获取当前所有已注册工具名。 */\n getToolNames(): string[] {\n return this.registry.getDefinitions().map(tool => tool.name);\n }\n\n /**\n * 删除所有“非默认”工具。\n * 返回值为本次被删除的工具名数组。\n */\n clearCustomTools(): string[] {\n const removed: string[] = [];\n for (const tool of this.registry.getDefinitions()) {\n if (this.protectedToolNames.has(tool.name)) continue;\n if (this.registry.unregister(tool.name)) {\n removed.push(tool.name);\n }\n }\n return removed;\n }\n\n /** 获取所有已注册的工具定义列表 */\n getTools(): ToolDefinition[] {\n return this.registry.getDefinitions();\n }\n\n // ─── 配置修改 ───\n\n /** 设置 API Token */\n setToken(token: string): void {\n this.token = token;\n }\n\n /**\n * 设置自定义 AI 客户端实例。\n *\n * 传入后将优先使用该实例进行对话,忽略 token / provider / model / baseURL。\n * 传入 undefined 可恢复使用内置客户端。\n */\n setClient(client: AIClient | undefined): void {\n this.client = client;\n }\n\n /** 设置 AI 提供商 */\n setProvider(provider: string): void {\n this.provider = provider;\n }\n\n /** 设置模型 */\n setModel(model: string): void {\n this.model = model;\n }\n\n /** 设置是否启用流式输出(SSE) */\n setStream(enabled: boolean): void {\n this.stream = enabled;\n }\n\n /** 获取当前流式输出开关状态 */\n getStream(): boolean {\n return this.stream;\n }\n\n /** 切换干运行模式 */\n setDryRun(enabled: boolean): void {\n this.dryRun = enabled;\n }\n\n /**\n * 注册系统提示词。\n * - setSystemPrompt(prompt):使用默认 key 注册\n * - setSystemPrompt(key, prompt):按指定 key 注册\n */\n setSystemPrompt(prompt: string): void;\n setSystemPrompt(key: string, prompt: string): void;\n setSystemPrompt(keyOrPrompt: string, maybePrompt?: string): void {\n const key = maybePrompt === undefined\n ? WebAgent.DEFAULT_SYSTEM_PROMPT_KEY\n : keyOrPrompt.trim();\n const prompt = maybePrompt === undefined ? keyOrPrompt : maybePrompt;\n\n if (!key) throw new Error(\"system prompt 的 key 不能为空\");\n const value = prompt.trim();\n if (!value) throw new Error(\"system prompt 不能为空\");\n\n this.systemPromptRegistry.set(key, value);\n }\n\n /** 批量注册系统提示词(key -> prompt)。 */\n setSystemPrompts(prompts: Record<string, string>): void {\n for (const [key, prompt] of Object.entries(prompts)) {\n this.setSystemPrompt(key, prompt);\n }\n }\n\n /** 注销指定 key 的系统提示词。 */\n removeSystemPrompt(key: string): boolean {\n return this.systemPromptRegistry.delete(key);\n }\n\n /** 只保留指定 key 的系统提示词,其余全部删除。 */\n keepOnlySystemPrompt(key: string): boolean {\n if (!this.systemPromptRegistry.has(key)) return false;\n const value = this.systemPromptRegistry.get(key)!;\n this.systemPromptRegistry.clear();\n this.systemPromptRegistry.set(key, value);\n return true;\n }\n\n /** 获取当前已注册的全部系统提示词(浅拷贝)。 */\n getSystemPrompts(): Record<string, string> {\n return Object.fromEntries(this.systemPromptRegistry.entries());\n }\n\n /** 删除全部系统提示词。 */\n clearSystemPrompts(): void {\n this.systemPromptRegistry.clear();\n }\n\n /** 开启或关闭多轮对话记忆 */\n setMemory(enabled: boolean): void {\n this.memory = enabled;\n if (!enabled) this.history = [];\n }\n\n /** 获取当前记忆开关状态 */\n getMemory(): boolean {\n return this.memory;\n }\n\n /** 开启或关闭自动快照 */\n setAutoSnapshot(enabled: boolean): void {\n this.autoSnapshot = enabled;\n }\n\n /** 获取当前自动快照开关状态 */\n getAutoSnapshot(): boolean {\n return this.autoSnapshot;\n }\n\n /** 设置快照选项(视口裁剪、智能剪枝等) */\n setSnapshotOptions(options: SnapshotOptions): void {\n this.snapshotOptions = options;\n }\n\n /** 获取当前快照选项 */\n getSnapshotOptions(): SnapshotOptions {\n return { ...this.snapshotOptions };\n }\n\n /** 清空对话历史(不影响记忆开关) */\n clearHistory(): void {\n this.history = [];\n }\n\n // ─── 核心能力 ───\n\n /**\n * 发送消息并获取 AI 回复(含完整工具调用循环)。\n *\n * 内部流程(全部复用 core):\n * 1. createAIClient() → 创建 fetch AI 客户端\n * 2. buildSystemPrompt() → 构建系统提示词\n * 3. executeAgentLoop() → 执行决策循环\n * 4. callbacks → 实时通知 UI\n */\n async chat(message: string): Promise<AgentLoopResult> {\n // 优先使用自定义 client,否则使用内置 createAIClient\n const client = this.client ?? this.createBuiltinClient();\n\n // 先构建基础系统提示词,再追加已注册的 system prompt 扩展。\n let systemPrompt = buildSystemPrompt({ tools: this.registry.getDefinitions() });\n if (this.systemPromptRegistry.size > 0) {\n const extensionText = Array.from(this.systemPromptRegistry.entries())\n .map(([key, prompt]) => `- [${key}]\\n${prompt}`)\n .join(\"\\n\\n\");\n systemPrompt += `\\n\\n## Registered System Prompt Extensions\\n${extensionText}`;\n }\n\n // ─── 自动快照:注入 system prompt,不污染对话历史 ───\n // 创建本次对话的 RefStore,快照结束后保持活跃,对话结束后清空\n const refStore = new RefStore(globalThis.location?.href);\n setActiveRefStore(refStore);\n let initialSnapshot: string | undefined;\n\n try {\n const snapshot = generateSnapshot(document.body, {\n maxDepth: 12,\n viewportOnly: false,\n maxNodes: 500,\n maxChildren: 30,\n ...this.snapshotOptions,\n refStore,\n });\n initialSnapshot = snapshot;\n if (this.autoSnapshot) {\n this.callbacks.onSnapshot?.(snapshot);\n }\n\n systemPrompt += wrapSnapshot(\n `\\n\\n## DOM Snapshot\\n\\`\\`\\`\\n${snapshot}\\n\\`\\`\\``,\n );\n } catch {\n // 快照失败不阻塞正常流程\n }\n\n // 包装回调:在恢复快照前重置 RefStore,确保新快照的 hash ID 有效\n const wrappedCallbacks: WebAgentCallbacks = {\n ...this.callbacks,\n onBeforeRecoverySnapshot: (newUrl?: string) => {\n // URL 变化 → 清空映射 + 更新 URL 命名空间\n // 元素定位失败 → 仅清空可能失效的映射(URL 不变)\n if (newUrl !== undefined) {\n refStore.reset(newUrl);\n } else {\n refStore.clear();\n }\n // 转发到用户回调(如有设置)\n this.callbacks.onBeforeRecoverySnapshot?.(newUrl);\n },\n };\n\n // 复用 core/agent-loop — 同一份决策循环\n const result = await executeAgentLoop({\n client,\n registry: this.registry,\n systemPrompt,\n message,\n initialSnapshot,\n history: this.memory ? this.history : undefined,\n dryRun: this.dryRun,\n maxRounds: this.maxRounds,\n roundStabilityWait: this.roundStabilityWait,\n callbacks: wrappedCallbacks,\n });\n\n // 记忆模式:累积对话历史供下次 chat() 使用\n if (this.memory) {\n this.history = result.messages;\n }\n\n // 对话结束,清空 RefStore\n refStore.clear();\n setActiveRefStore(undefined);\n\n return result;\n }\n\n // ─── 内部方法 ───\n\n /**\n * 创建内置 AI 客户端(基于 token / provider / model 配置)。\n *\n * @throws 未设置 token 时抛出 Error\n */\n private createBuiltinClient(): AIClient {\n if (!this.token) {\n throw new Error(\"未设置 Token,请先调用 setToken() 或传入自定义 client\");\n }\n return createAIClient({\n provider: this.provider,\n model: this.model,\n apiKey: this.token,\n baseURL: this.baseURL,\n stream: this.stream,\n });\n }\n}\n\n// ─── Re-exports ───\n// 从入口文件统一导出所有公共 API,消费方只需 import from \"agentpage\"\n\nexport {\n generateSnapshot,\n type SnapshotOptions,\n} from \"./tools/page-info-tool.js\";\nexport { createDomTool } from \"./tools/dom-tool.js\";\nexport { createNavigateTool } from \"./tools/navigate-tool.js\";\nexport { createPageInfoTool } from \"./tools/page-info-tool.js\";\nexport { createWaitTool } from \"./tools/wait-tool.js\";\nexport { createEvaluateTool } from \"./tools/evaluate-tool.js\";\nexport {\n createProxyExecutor,\n registerToolHandler,\n type ToolCallMessage,\n type ToolCallResponse,\n type ToolExecutorMap,\n} from \"./messaging.js\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["sleep","sleep","sleep","convertMessages"],"sources":["../src/core/agent-loop/constants.ts","../src/core/agent-loop/helpers.ts","../src/core/agent-loop/snapshot.ts","../src/core/agent-loop/messages.ts","../src/core/agent-loop/recovery.ts","../src/core/agent-loop/index.ts","../src/core/ai-client/constants.ts","../src/core/ai-client/sse.ts","../src/core/ai-client/custom.ts","../src/core/ai-client/openai.ts","../src/core/ai-client/anthropic.ts","../src/core/ai-client/deepseek.ts","../src/core/ai-client/doubao.ts","../src/core/ai-client/qwen.ts","../src/core/ai-client/index.ts","../src/core/tool-registry.ts","../src/core/system-prompt.ts","../src/web/event-listener-tracker.ts","../src/web/tools/dom-tool.ts","../src/web/tools/page-info-tool.ts","../src/web/tools/navigate-tool.ts","../src/web/tools/wait-tool.ts","../src/web/tools/evaluate-tool.ts","../src/web/ref-store.ts","../src/web/messaging.ts","../src/web/index.ts"],"sourcesContent":["/**\n * Agent Loop 默认配置常量。\n *\n * 统一集中在该文件,避免在主循环中散落“魔法数字”。\n */\nexport const DEFAULT_MAX_ROUNDS = 40;\nexport const DEFAULT_RECOVERY_WAIT_MS = 100;\nexport const DEFAULT_ACTION_RECOVERY_ROUNDS = 2;\nexport const DEFAULT_NOT_FOUND_RETRY_ROUNDS = 2;\nexport const DEFAULT_NOT_FOUND_RETRY_WAIT_MS = 1000;\nexport const DEFAULT_ROUND_STABILITY_WAIT_TIMEOUT_MS = 4000;\nexport const DEFAULT_ROUND_STABILITY_WAIT_QUIET_MS = 200;\nexport const DEFAULT_ROUND_STABILITY_WAIT_LOADING_SELECTORS = [\n\t\".ant-spin\",\n\t\".ant-spin-spinning\",\n\t\".ant-skeleton\",\n\t\".el-loading-mask\",\n\t\".bk-loading\",\n\t\".bk-spin-loading\",\n\t\".bk-skeleton\",\n\t\".bk-sideslider-loading\",\n\t\".t-loading\",\n\t\".t-skeleton\",\n\t\".t-skeleton__row\",\n\t\"[aria-busy=\\\"true\\\"]\",\n\t\".skeleton\",\n\t\".loading\",\n];\n// ─── DOM 快照去重标记 ───\n\n/** 快照起始标记 — 用于在消息中识别快照边界 */\nexport const SNAPSHOT_START = \"<!-- SNAPSHOT_START -->\";\n/** 快照结束标记 */\nexport const SNAPSHOT_END = \"<!-- SNAPSHOT_END -->\";\n/** 旧快照被替换后的占位文本 */\nexport const SNAPSHOT_OUTDATED = \"[此快照已过期,请参考对话中最新的快照]\";","/**\n * Agent Loop 辅助函数。\n *\n * 这个文件只放“纯函数”:\n * - 不访问外部可变状态\n * - 不做网络/DOM/I/O\n * - 输入相同,输出稳定\n *\n * 目的:把 index.ts 里的协议解析、文本规整、判定逻辑拆出来,\n * 让主循环只负责编排流程,方便阅读、测试和后续扩展。\n *\n * 函数能力速览:\n * - 基础工具:\n * - `sleep`:异步等待\n * - `toContentString`:统一工具结果内容为字符串\n * - 快照相关:\n * - `parseSnapshotExpandHints`:解析 `SNAPSHOT_HINT: EXPAND_CHILDREN`\n * - `extractHashSelectorRef`:从 `#ref` 选择器提取 ref id\n * - 任务推进与协议:\n * - `buildTaskArray`:将工具调用规整成稳定任务数组\n * - `normalizeModelOutput`:压缩模型输出供下一轮上下文使用\n * - `parseRemainingInstruction`:解析 `REMAINING` 协议\n * - `deriveNextInstruction`:推导下一轮 remaining(有协议优先)\n * - `reduceRemainingHeuristically`:协议缺失时做启发式推进\n * - 执行控制:\n * - `shouldForceRoundBreak`:判断动作后是否应断轮\n * - `collectMissingTask`:提取“元素未找到”任务用于重试流\n * - 错误与参数判定:\n * - `isElementNotFoundResult`:识别元素未找到错误\n * - `buildToolCallKey`:生成稳定调用键\n * - `resolveRecoveryWaitMs`:解析恢复等待时长\n * - `getToolAction`:读取工具输入里的 action\n * - `hasToolError`:判断结果是否标记为错误\n */\nimport type { ToolCallResult } from \"../tool-registry.js\";\nimport { DEFAULT_RECOVERY_WAIT_MS } from \"./constants.js\";\n\n/**\n * 异步睡眠。\n *\n * 用于重试等待、节流等待等场景。\n */\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * 统一内容为字符串。\n *\n * 工具返回 content 可能是 string 或 object;这里统一转成 string,\n * 便于日志、错误判定、摘要拼接。\n */\nexport function toContentString(content: ToolCallResult[\"content\"]): string {\n return typeof content === \"string\" ? content : JSON.stringify(content, null, 2);\n}\n\n/**\n * 解析快照放宽提示。\n *\n * 约定格式:`SNAPSHOT_HINT: EXPAND_CHILDREN #ref1 #ref2`\n *\n * 返回:去掉 `#` 前缀后的 ref id 列表。\n */\nexport function parseSnapshotExpandHints(text: string | undefined): string[] {\n if (!text) return [];\n const refs: string[] = [];\n const regex = /^\\s*SNAPSHOT_HINT\\s*:\\s*EXPAND_CHILDREN\\s+(.+)$/gim;\n let match: RegExpExecArray | null;\n while ((match = regex.exec(text)) !== null) {\n const tail = match[1] ?? \"\";\n const tokens = tail.match(/#[A-Za-z0-9_-]+/g) ?? [];\n for (const token of tokens) refs.push(token.replace(/^#/, \"\"));\n }\n return refs;\n}\n\n/**\n * 提取 hash selector 的 ref。\n *\n * 仅处理“纯 hash 选择器”,例如 `#1rv01x`。\n * 如果是复杂 CSS(如 `.x #id`)会返回 null,避免误判。\n */\nexport function extractHashSelectorRef(toolInput: unknown): string | null {\n if (!toolInput || typeof toolInput !== \"object\") return null;\n const selector = (toolInput as { selector?: unknown }).selector;\n if (typeof selector !== \"string\") return null;\n const m = selector.trim().match(/^#([A-Za-z0-9_-]+)$/);\n return m ? m[1] : null;\n}\n\n/**\n * 构建任务数组。\n *\n * 作用:把一轮工具调用规整成稳定字符串数组,\n * 用于“上一轮任务回显”和“重复批次检测”。\n */\nexport function buildTaskArray(toolCalls: Array<{ name: string; input: unknown }>): string[] {\n return toolCalls.map(tc => `${tc.name}:${JSON.stringify(tc.input)}`);\n}\n\n/**\n * 规范化模型输出。\n *\n * 优先保留 REMAINING;否则保留首段摘要,避免长文本污染上下文。\n *\n * 返回字符串会被注入下一轮消息,作为“上一轮模型输出摘要”。\n */\nexport function normalizeModelOutput(text: string | undefined): string {\n if (!text) return \"\";\n const trimmed = text.trim();\n if (!trimmed) return \"\";\n const remainingMatch = trimmed.match(/REMAINING\\s*:\\s*([\\s\\S]*)$/i);\n if (remainingMatch) return `REMAINING: ${remainingMatch[1].trim()}`;\n const firstBlock = trimmed.split(/\\n\\s*\\n/)[0]?.trim() ?? trimmed;\n return firstBlock.slice(0, 220);\n}\n\n/**\n * 解析 REMAINING。\n *\n * 返回值:\n * - `\"\"` 表示 DONE\n * - 非空字符串表示新的 remaining\n * - `null` 表示协议缺失\n *\n * 注意:这里只负责解析,不负责 fallback 策略。\n */\nexport function parseRemainingInstruction(text: string | undefined): string | null {\n if (!text) return null;\n const match = text.match(/REMAINING\\s*:\\s*([\\s\\S]*)$/i);\n if (!match) return null;\n const value = match[1].trim();\n return /^done$/i.test(value) ? \"\" : value;\n}\n\n/**\n * 推导下一轮 remaining。\n *\n * 策略:\n * - 有 REMAINING 协议 -> 使用模型给出的 nextInstruction\n * - 无协议 -> 保持 currentInstruction 不变(由上层决定是否启发式推进)\n */\nexport function deriveNextInstruction(\n text: string | undefined,\n currentInstruction: string,\n): { nextInstruction: string; hasRemainingProtocol: boolean } {\n const parsed = parseRemainingInstruction(text);\n if (parsed !== null) {\n return { nextInstruction: parsed, hasRemainingProtocol: true };\n }\n return { nextInstruction: currentInstruction, hasRemainingProtocol: false };\n}\n\n/**\n * 启发式剔除 remaining。\n *\n * 用于协议缺失但本轮有执行动作时,按线性步骤剔除已执行数量。\n *\n * 这是“保守推进”策略,不保证语义完美,但能避免 remaining 长期不变。\n */\nexport function reduceRemainingHeuristically(\n currentInstruction: string,\n executedCount: number,\n): string {\n if (!currentInstruction.trim() || executedCount <= 0) return currentInstruction;\n\n const normalized = currentInstruction\n .replace(/\\s+/g, \" \")\n .replace(/(->|=>|→)/g, \" 然后 \")\n .replace(/[,,。;;]/g, \" 然后 \");\n\n const parts = normalized\n .split(/\\s*(?:然后|再|并且|并|接着|随后|之后)\\s*/g)\n .map(part => part.trim())\n .filter(Boolean);\n\n if (parts.length <= 1) return currentInstruction;\n\n const nextParts = parts.slice(Math.min(executedCount, parts.length));\n if (nextParts.length === 0) return \"\";\n return nextParts.join(\" -> \");\n}\n\n/**\n * 判定是否强制断轮。\n *\n * 语义:潜在 DOM 结构变化动作后,等待下一轮新快照。\n *\n * 当前规则:\n * - `navigate.*` 一律断轮\n * - `dom.press` 仅 Enter 断轮\n * - `evaluate` 断轮\n * - 其他动作默认不断轮\n */\nexport function shouldForceRoundBreak(toolName: string, toolInput: unknown): boolean {\n const action = getToolAction(toolInput);\n\n if (toolName === \"navigate\") {\n return action === \"goto\" || action === \"back\" || action === \"forward\" || action === \"reload\";\n }\n\n if (toolName === \"dom\") {\n if (action === \"press\") {\n const key = typeof toolInput === \"object\" && toolInput !== null\n ? String((toolInput as { key?: unknown; value?: unknown }).key ?? (toolInput as { value?: unknown }).value ?? \"\")\n : \"\";\n return key === \"Enter\";\n }\n return false;\n }\n\n return toolName === \"evaluate\";\n}\n\n/**\n * 判定动作是否可能引发页面结构或状态变化。\n *\n * 用于“轮次后稳定等待”触发条件:\n * - 命中 true:本轮结束后执行加载态 + DOM 静默双重等待\n * - 命中 false:跳过等待,直接进入下一轮\n */\nexport function isPotentialDomMutation(toolName: string, toolInput: unknown): boolean {\n const action = getToolAction(toolInput);\n\n if (toolName === \"navigate\") return true;\n if (toolName === \"evaluate\") return true;\n if (toolName !== \"dom\") return false;\n\n if (!action) return false;\n return [\n \"click\",\n \"fill\",\n \"select_option\",\n \"clear\",\n \"check\",\n \"uncheck\",\n \"type\",\n \"focus\",\n \"hover\",\n \"scroll\",\n \"press\",\n \"set_attr\",\n \"add_class\",\n \"remove_class\",\n ].includes(action);\n}\n\n/**\n * 采集找不到元素任务。\n *\n * 返回 null 表示当前结果不属于“元素未找到”,\n * 返回对象表示可进入 not-found retry 对话流。\n */\nexport function collectMissingTask(\n name: string,\n input: unknown,\n result: ToolCallResult,\n): { name: string; input: unknown; reason: string } | null {\n if (!isElementNotFoundResult(result)) return null;\n return {\n name,\n input,\n reason: toContentString(result.content).slice(0, 240),\n };\n}\n\n/**\n * 元素不存在判定。\n *\n * 判定顺序:\n * 1) 优先看结构化错误码 `ELEMENT_NOT_FOUND`\n * 2) 回退看中文错误文本关键词(兼容历史结果格式)\n */\nexport function isElementNotFoundResult(result: ToolCallResult): boolean {\n const details = result.details;\n if (details && typeof details === \"object\") {\n const code = (details as { code?: unknown }).code;\n if (code === \"ELEMENT_NOT_FOUND\") return true;\n }\n\n const content = toContentString(result.content);\n return content.includes(\"未找到\") && content.includes(\"元素\");\n}\n\n/**\n * 生成稳定调用键。\n *\n * 用于 recoveryAttempts 的 map key(同名 + 同参数视为同一调用)。\n */\nexport function buildToolCallKey(name: string, input: unknown): string {\n return `${name}:${JSON.stringify(input)}`;\n}\n\n/**\n * 解析恢复等待时长。\n * 优先级:waitMs > waitSeconds > 默认值。\n *\n * 统一返回毫秒整数,且最小为 0。\n */\nexport function resolveRecoveryWaitMs(input: unknown): number {\n if (!input || typeof input !== \"object\") return DEFAULT_RECOVERY_WAIT_MS;\n\n const params = input as Record<string, unknown>;\n const waitMs = params.waitMs;\n if (typeof waitMs === \"number\" && Number.isFinite(waitMs)) {\n return Math.max(0, Math.floor(waitMs));\n }\n\n const waitSeconds = params.waitSeconds;\n if (typeof waitSeconds === \"number\" && Number.isFinite(waitSeconds)) {\n return Math.max(0, Math.floor(waitSeconds * 1000));\n }\n\n return DEFAULT_RECOVERY_WAIT_MS;\n}\n\n/**\n * 读取工具 action。\n *\n * 仅在 input 是对象且 action 为字符串时返回值,否则返回 undefined。\n */\nexport function getToolAction(input: unknown): string | undefined {\n if (!input || typeof input !== \"object\") return undefined;\n const action = (input as Record<string, unknown>).action;\n return typeof action === \"string\" ? action : undefined;\n}\n\n/**\n * 判定错误标记。\n *\n * 约定:`result.details.error === true` 视为错误结果。\n */\nexport function hasToolError(result: ToolCallResult): boolean {\n return result.details && typeof result.details === \"object\"\n ? Boolean((result.details as { error?: unknown }).error)\n : false;\n}\n","/**\n * DOM 快照生命周期管理。\n *\n * 负责 4 类能力:读取、包裹、去重、剥离。\n *\n * 快照读取主流程:\n * 1) 组装快照参数(默认偏完整性)\n * 2) 调用 `page_info.snapshot`\n * 3) 将工具返回内容统一成字符串\n * 4) 由消息层进行包裹与注入\n * 5) 在多轮对话中去重旧快照\n *\n * 调用链:\n * - `agent-loop/index.ts` 在“无快照、每轮结束、导航后、恢复后”触发读取。\n * - `messages.ts` 负责把最新快照注入到本轮上下文。\n * - 本文件只处理快照文本本身,不负责业务决策与停机判定。\n *\n * 压缩/剪枝实现位置:\n * - 具体算法在 `src/web/tools/page-info-tool.ts` 的 `generateSnapshot()`。\n * - 本文件通过 `readPageSnapshot()` 传参触发这些策略,不在 core 层直接操作 DOM。\n * - 这样保持分层:core 只声明策略参数,web 负责真实遍历与裁剪。\n */\nimport { ToolRegistry } from \"../tool-registry.js\";\nimport type { AIMessage } from \"../types.js\";\nimport {\n SNAPSHOT_END,\n SNAPSHOT_OUTDATED,\n SNAPSHOT_START,\n} from \"./constants.js\";\nimport { toContentString } from \"./helpers.js\";\n\n// ─── 快照读取 ───\n\n/**\n * 读取页面 URL。\n *\n * 步骤:\n * 1) 通过 registry 分发 `page_info.get_url`。\n * 2) 若 content 为字符串则直接返回。\n * 3) 否则返回 undefined,交由上层容错。\n *\n * 输入/输出:\n * - 输入:`ToolRegistry`\n * - 输出:`string | undefined`\n * - 副作用:无本地状态写入(仅发起一次工具调用)\n */\nexport async function readPageUrl(\n registry: ToolRegistry,\n): Promise<string | undefined> {\n const result = await registry.dispatch(\"page_info\", { action: \"get_url\" });\n return typeof result.content === \"string\" ? result.content : undefined;\n}\n\n/**\n * 读取页面快照。\n *\n * 默认关闭 viewportOnly,优先完整性。\n *\n * 步骤:\n * 1) 合并调用方 options 与默认值(深度/裁剪/剪枝/节点上限等)。\n * 2) 分发 `page_info.snapshot` 获取当前 DOM 文本快照。\n * 3) 使用 `toContentString` 归一化输出,避免 provider 差异导致结构不一致。\n * 4) 返回稳定字符串给 loop,供后续注入消息与统计。\n *\n * 默认参数意图:\n * - `maxDepth=12`: 保留更深层级,减少深层组件控件被截断。\n * - `viewportOnly=false`: 优先完整性,避免误判“元素不存在”。\n * - `pruneLayout=true`: 抑制纯布局噪声,降低 token 压力。\n * - `maxNodes=500` / `maxChildren=30`: 控制体积上限,兼顾可读性。\n * - `maxTextLength=40`: 防止长文本淹没结构信息。\n *\n * 压缩/剪枝是怎么做的:\n * - `viewportOnly=true` 时:仅保留与视口相交元素(根层容器保留),完全视口外元素跳过。\n * - `pruneLayout=true` 时:无 id/无语义/无交互/无直接文本的布局容器会被“折叠”,\n * 子节点直接提升输出,减少无意义层级;当同一折叠容器提升出多个相邻节点时,\n * 快照会用括号分组块标记其关联来源(collapsed-group)。\n * - `maxNodes`:全局节点预算,超限后停止继续遍历并追加 truncation 提示。\n * - `maxChildren`:每个父节点只保留前 N 个子元素,其余用 `... (n children omitted)` 汇总。\n * - `maxTextLength`:节点文本按长度截断,避免长段文案占满上下文。\n * - 交互优先排序:优先输出按钮/输入框/链接等交互元素,再输出普通元素。\n * - 属性压缩:仅保留关键属性(如 id、关键 class、交互属性、布尔状态、val),减少冗余 token。\n *\n * 输入/输出:\n * - 输入:`ToolRegistry` + 可选快照参数\n * - 输出:归一化后的快照字符串(始终 string)\n * - 副作用:无本地状态写入;仅依赖工具调用结果\n */\nexport async function readPageSnapshot(\n registry: ToolRegistry,\n options?: {\n maxDepth?: number;\n viewportOnly?: boolean;\n pruneLayout?: boolean;\n maxNodes?: number;\n maxChildren?: number;\n maxTextLength?: number;\n expandOptionLists?: boolean;\n expandChildrenRefs?: string[];\n expandedChildrenLimit?: number;\n },\n): Promise<string> {\n const result = await registry.dispatch(\"page_info\", {\n action: \"snapshot\",\n maxDepth: options?.maxDepth ?? 12,\n viewportOnly: options?.viewportOnly ?? false,\n pruneLayout: options?.pruneLayout ?? true,\n maxNodes: options?.maxNodes ?? 500,\n maxChildren: options?.maxChildren ?? 30,\n maxTextLength: options?.maxTextLength ?? 40,\n expandOptionLists: options?.expandOptionLists,\n expandChildrenRefs: options?.expandChildrenRefs,\n expandedChildrenLimit: options?.expandedChildrenLimit,\n });\n return toContentString(result.content);\n}\n\n// ─── 快照标记 ───\n\n/**\n * 包裹快照。\n *\n * 作用:\n * - 为快照加 `SNAPSHOT_START/END` 边界,便于后续正则定位。\n * - 支持去重与旧快照剥离,防止多轮 token 累积。\n * - 仅做纯字符串变换,不访问外部状态。\n */\nexport function wrapSnapshot(snapshot: string): string {\n return `${SNAPSHOT_START}\\n${snapshot}\\n${SNAPSHOT_END}`;\n}\n\n// ─── 快照去重 ───\n\n/** 转义正则字符。 */\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/** 快照块匹配正则。 */\nconst SNAPSHOT_REGEX = new RegExp(\n `${escapeRegex(SNAPSHOT_START)}[\\\\s\\\\S]*?${escapeRegex(SNAPSHOT_END)}`,\n \"g\",\n);\n\n/** 是否包含快照标记。 */\nfunction containsSnapshot(text: string): boolean {\n return text.includes(SNAPSHOT_START) && text.includes(SNAPSHOT_END);\n}\n\n/**\n * 去重消息快照。\n * 仅保留最后一份快照,旧快照替换为过期提示。\n *\n * 步骤:\n * 1) 扫描 tool 消息中的快照块引用。\n * 2) 保留最后一次快照,视为当前事实来源。\n * 3) 将更早快照替换为 `SNAPSHOT_OUTDATED`,避免模型引用旧状态。\n *\n * 返回语义:\n * - `true`: 至少发现了 1 份快照(可能发生替换,也可能只有一份无需替换)。\n * - `false`: 未发现任何快照标记。\n */\nexport function deduplicateSnapshots(messages: AIMessage[]): boolean {\n type SnapshotRef = {\n items: Array<{ toolCallId: string; result: string }>;\n index: number;\n };\n const refs: SnapshotRef[] = [];\n\n for (const msg of messages) {\n if (msg.role !== \"tool\" || !Array.isArray(msg.content)) continue;\n const items = msg.content as Array<{ toolCallId: string; result: string }>;\n for (let j = 0; j < items.length; j++) {\n if (typeof items[j].result === \"string\" && containsSnapshot(items[j].result)) {\n refs.push({ items, index: j });\n }\n }\n }\n\n if (refs.length <= 1) return refs.length > 0;\n\n // 保留最后一份快照,将更早的快照替换为过期提示\n for (let i = 0; i < refs.length - 1; i++) {\n const ref = refs[i];\n ref.items[ref.index].result = ref.items[ref.index].result.replace(\n SNAPSHOT_REGEX,\n SNAPSHOT_OUTDATED,\n );\n }\n\n return true;\n}\n\n/**\n * 剥离旧快照。\n *\n * 说明:\n * - 当 prompt 中已有历史快照时,将其替换为过期占位文本。\n * - 让每轮真正生效的只有“最新注入快照”,减少冲突上下文。\n * - 这是 prompt 级清理;不会触碰 tool trace 中的原始结果对象。\n */\nexport function stripSnapshotFromPrompt(prompt: string): string {\n if (!containsSnapshot(prompt)) return prompt;\n return prompt.replace(SNAPSHOT_REGEX, SNAPSHOT_OUTDATED);\n}\n\n/** 导出快照正则,供消息层做错误摘要清理等用途。 */\nexport { SNAPSHOT_REGEX };\n","/**\n * 紧凑消息构建。\n *\n * 这个文件专门负责“给模型喂什么消息”。\n *\n * 它把 Agent Loop 的运行状态压缩成模型可直接消费的消息内容,核心输入包括:\n * - 用户原始目标(userMessage)\n * - 当前剩余任务(remainingInstruction)\n * - 已执行轨迹(trace / previousRoundTasks)\n * - 上一轮模型输出摘要(previousRoundModelOutput)\n * - 最新页面快照(latestSnapshot)\n * - 协议修复提示(protocolViolationHint)\n *\n * 设计目标:\n * 1) 减少上下文噪音,避免模型复述与空转\n * 2) 强化“基于当前快照做增量决策”的行为\n * 3) 让 REMAINING 协议在每轮都可持续推进\n *\n * 这个文件主要做了 4 件事:\n * 1) UI 意图识别:\n * - `isExplicitAgentUiRequest` 判断用户是否“明确要求操作 AutoPilot 聊天 UI”。\n * - 默认情况下会在提示词里禁止模型点击聊天输入框/发送按钮等。\n *\n * 2) 轨迹可读化:\n * - `formatToolInputBrief`、`formatToolResultBrief`、`buildToolTrace`\n * 把工具输入/结果压成短文本,便于注入上下文和调试展示。\n * - 直观效果示例(最终会长这样):\n * 1. [round 2] dom (action=\"click\", selector=\"#a1b2c\") [ELEMENT_NOT_FOUND]\n * 2. [round 2] wait (action=\"wait_for_selector\", selector=\"#a1b2c\")\n * 3. [round 3] dom (action=\"fill\", selector=\"#x9k3d\")\n *\n * 3) Round 0 消息构建:\n * - 首轮注入“任务 + remaining + 最新快照 + 执行约束”。\n * - 明确要求模型输出 `REMAINING: ...` 或 `REMAINING: DONE`。\n *\n * 4) Round 1+ 消息构建:\n * - 不再重复整段原始任务,改为注入“已完成步骤 + 当前 remaining + 最新快照”。\n * - 追加错误摘要、上轮计划数组、协议修复提示,帮助模型持续收敛。\n *\n * 边界说明(这个文件不做的事):\n * - 不调用模型、不执行工具\n * - 不维护循环状态(状态维护在 index.ts)\n * - 不读取页面快照(快照读取在 snapshot.ts)\n *\n * 一句话:这里是 Agent Loop 的“消息编排层”,负责把运行态翻译成稳定、高信息密度的提示上下文。\n */\nimport type { ToolCallResult } from \"../tool-registry.js\";\nimport type { AIMessage } from \"../types.js\";\nimport { toContentString, hasToolError } from \"./helpers.js\";\nimport { wrapSnapshot, SNAPSHOT_REGEX } from \"./snapshot.js\";\nimport type { ToolTraceEntry } from \"./types.js\";\n\n/**\n * 显式 UI 意图判定。\n *\n * 用途:默认禁止模型操作 AutoPilot 自己的聊天 UI(输入框/发送按钮等),\n * 只有当用户文本里“同时出现 UI 关键词 + 操作动词”时才放行。\n *\n * 判定逻辑:\n * - `hasAgentUiKeyword`:是否提到聊天面板/输入框/发送按钮等\n * - `hasActionVerb`:是否包含点击/输入/发送等动作意图\n * - 二者都满足才返回 true\n */\nexport function isExplicitAgentUiRequest(userMessage: string): boolean {\n const lower = userMessage.toLowerCase();\n const compact = lower.replace(/[\\s\\p{P}\\p{S}]+/gu, \"\");\n\n const hasAgentUiKeyword =\n /(chat|dock|chatinput|sendbutton|shortcut|quicktest)/i.test(lower) ||\n /(聊天|对话|指令输入框|消息输入框|输入框|发送按钮|发送|快捷测试|测试按钮|聊天面板)/.test(compact);\n\n const hasActionVerb =\n /(press|click|type|fill|send|input|submit|enter)/i.test(lower) ||\n /(输入|点击|发送|填写|填入|操作|提交|回车|按下)/.test(compact);\n return hasAgentUiKeyword && hasActionVerb;\n}\n\n// ─── 格式化辅助 ───\n\n/**\n * 输入摘要。\n *\n * 把工具输入压缩成一段短文本(用于轨迹展示),\n * 只保留高价值字段,避免日志过长。\n */\nexport function formatToolInputBrief(input: unknown): string {\n if (!input || typeof input !== \"object\") return \"\";\n\n const params = input as Record<string, unknown>;\n const parts: string[] = [];\n\n for (const key of [\"action\", \"selector\", \"waitMs\", \"waitSeconds\", \"url\", \"text\"]) {\n const value = params[key];\n if (value === undefined || value === null) continue;\n if (typeof value === \"string\") {\n parts.push(`${key}=${JSON.stringify(value).slice(0, 80)}`);\n } else if (typeof value === \"number\" || typeof value === \"boolean\") {\n parts.push(`${key}=${String(value)}`);\n }\n }\n\n if (parts.length === 0) return \"\";\n return ` (${parts.join(\", \")})`;\n}\n\n/**\n * 结果摘要。\n *\n * 读取工具结果首行,拼接错误码,生成一行可读结论:\n * - 成功:`✓ ...`\n * - 失败:`✗ ... [CODE]`\n */\nfunction formatToolResultBrief(result: ToolCallResult): string {\n const content = toContentString(result.content);\n const firstLine = content.split(\"\\n\").find(l => l.trim())?.trim().slice(0, 80) ?? \"\";\n\n if (hasToolError(result)) {\n const code = result.details && typeof result.details === \"object\"\n ? (result.details as { code?: string }).code\n : undefined;\n return `✗ ${firstLine}${code ? ` [${code}]` : \"\"}`;\n }\n return `✓ ${firstLine}`;\n}\n\n// ─── 轨迹格式化 ───\n\n/**\n * 轨迹格式化。\n *\n * 将完整工具轨迹转为可读文本列表,供提示词注入或调试展示。\n * 支持附加 current 条目(未入库前的临时展示)。\n *\n * 输出样式示例:\n * 1. [round 1] dom (action=\"click\", selector=\"#btnCreate\")\n * 2. [round 1] dom (action=\"fill\", selector=\"#title\") [FILL_NOT_APPLIED]\n * 3. [round 2] wait (action=\"wait_for_selector\", selector=\"#dialog\")\n */\nexport function buildToolTrace(\n trace: ToolTraceEntry[],\n current?: {\n round: number;\n name: string;\n input: unknown;\n result?: ToolCallResult;\n marker?: string;\n },\n): string {\n const lines = trace.map((entry, index) => {\n const code =\n entry.result.details && typeof entry.result.details === \"object\"\n ? (entry.result.details as { code?: unknown }).code\n : undefined;\n const codeText = typeof code === \"string\" ? ` [${code}]` : \"\";\n const marker = entry.marker ? ` ${entry.marker}` : \"\";\n return `${index + 1}. [round ${entry.round}] ${entry.name}${formatToolInputBrief(entry.input)}${codeText}${marker}`;\n });\n\n if (current) {\n const code =\n current.result?.details && typeof current.result.details === \"object\"\n ? (current.result.details as { code?: unknown }).code\n : undefined;\n const codeText = typeof code === \"string\" ? ` [${code}]` : \"\";\n const marker = current.marker ? ` ${current.marker}` : \"\";\n lines.push(\n `${lines.length + 1}. [round ${current.round}] ${current.name}${formatToolInputBrief(current.input)}${codeText}${marker}`,\n );\n }\n\n return lines.length > 0 ? lines.join(\"\\n\") : \"(暂无工具执行记录)\";\n}\n\n// ─── 紧凑消息构建 ───\n\n/**\n * 构建紧凑消息数组。\n *\n * 两种轮次语义:\n * - Round 0:发送“初始任务 + 当前快照 + 执行约束”\n * - Round 1+:发送“已完成步骤 + 当前 remaining + 最新快照”\n *\n * 渐进式语义:\n * - `remainingInstruction`:当前轮次仍待执行的文本。\n * - `previousRoundTasks`:上一轮已执行的任务数组,避免重复计划。\n * - `previousRoundModelOutput`:上一轮模型输出摘要,用于 task-reduction 输入。\n * - `previousRoundPlannedTasks`:上一轮计划数组,用于对齐“计划 vs 实际执行”。\n * - `protocolViolationHint`:协议修复提示(当 remaining 未完成但模型无动作时)。\n *\n * 输出:符合 AIMessage 结构的消息数组,可直接传给 AIClient.chat。\n */\nexport function buildCompactMessages(\n userMessage: string,\n trace: ToolTraceEntry[],\n latestSnapshot: string | undefined,\n currentUrl: string | undefined,\n history?: AIMessage[],\n remainingInstruction?: string,\n previousRoundTasks?: string[],\n previousRoundModelOutput?: string,\n previousRoundPlannedTasks?: string[],\n protocolViolationHint?: string,\n): AIMessage[] {\n const messages: AIMessage[] = history ? [...history] : [];\n const allowAgentUiInteraction = isExplicitAgentUiRequest(userMessage);\n const activeInstruction = (remainingInstruction && remainingInstruction.trim())\n ? remainingInstruction.trim()\n : userMessage;\n\n // ─── Round 0:任务描述 + 快照,一条 user 消息完成注入 ───\n if (trace.length === 0) {\n // 结构说明:\n // 1) 用户目标\n // 2) 当前 remaining\n // 3) URL(可选)\n // 4) 快照 + 行为约束(禁 page_info、禁误触 Agent UI、要求 REMAINING 输出)\n const parts: string[] = [\n userMessage,\n \"\",\n \"## Progressive execution state\",\n \"Current remaining instruction to execute this round:\",\n activeInstruction,\n ];\n if (currentUrl) {\n parts.push(\"\", `URL: ${currentUrl}`);\n }\n if (latestSnapshot) {\n parts.push(\n \"\",\n \"## Current page snapshot\",\n \"Apply task-reduction model directly from this snapshot. Do NOT restate the task.\",\n \"Use hash IDs (e.g. #a1b2c) from the snapshot as selector params.\",\n \"Do NOT call page_info (get_url/get_title/query_all/snapshot).\",\n \"Batch independent visible actions in one round.\",\n \"Build the minimal action array from current snapshot to finish this remaining instruction in one round whenever possible.\",\n \"For deterministic increase/decrease controls, compute delta from current visible value and issue exactly that many clicks in one round (e.g., +2 => two increase clicks). Do not overshoot then undo.\",\n \"If action changes DOM (open modal/navigate), stop that batch and continue next round.\",\n \"For dropdown/select fields, use dom with action=select_option (or fill on a select).\",\n \"If a needed list shows `... (N children omitted)` under a specific container, output `SNAPSHOT_HINT: EXPAND_CHILDREN #<containerRef>` and wait for next round snapshot.\",\n \"Stop rule: once requested state is reached, stop tool calls. If verification is needed, verify once and then output REMAINING: DONE.\",\n allowAgentUiInteraction\n ? \"User explicitly asked to operate AutoPilot UI. You may interact with chat input/send/dock only as requested.\"\n : \"Do NOT interact with any AI chat UI elements (chat input, send button, dock). Only operate on the actual page content.\",\n \"Output one line: REMAINING: <new remaining task after this round> or REMAINING: DONE\",\n wrapSnapshot(latestSnapshot),\n );\n }\n if (protocolViolationHint) {\n parts.push(\"\", protocolViolationHint);\n }\n messages.push({ role: \"user\", content: parts.join(\"\\n\") });\n return messages;\n }\n\n // ─── Round 1+:注入“已完成步骤 + 执行上下文 + 最新快照” ───\n // 不再重复原始 userMessage,避免模型每轮回到起点重做。\n\n // 第 1 条 assistant 消息:已完成步骤摘要(从 trace 重建)\n const traceParts: string[] = [];\n for (let i = 0; i < trace.length; i++) {\n const entry = trace[i];\n const isError = hasToolError(entry.result);\n const brief = formatToolResultBrief(entry.result);\n const status = isError ? \"❌\" : \"✅\";\n const marker = entry.marker ? ` ${entry.marker}` : \"\";\n traceParts.push(\n `${status} ${i + 1}. ${entry.name}${formatToolInputBrief(entry.input)} → ${brief}${marker}`,\n );\n }\n messages.push({\n role: \"assistant\",\n content: `Done steps (do NOT repeat):\\n${traceParts.join(\"\\n\")}`,\n });\n\n // 第 2 条 user 消息:执行上下文 + 协议约束 + 最新快照\n const hasErrors = trace.some(e => hasToolError(e.result));\n const contextParts: string[] = [\n // 执行上下文语义:\n // - 当前 remaining 是唯一待消费目标\n // - 已完成步骤不重复\n // - 必须基于最新快照决策,不猜测未来 DOM\n // - 要求模型继续输出 REMAINING 协议\n \"## Execution context\",\n \"Current remaining instruction:\",\n activeInstruction,\n \"\",\n \"Task-reduction model:\",\n \"Input: current remaining instruction + previous round executed actions + this-round actions.\",\n \"Output: new remaining instruction after removing this-round actions.\",\n \"Start from visible page state directly. Do NOT restate task. Do NOT output planning text.\",\n \"Execute all independent visible sub-tasks in one round.\",\n \"Do NOT act on elements not present in this snapshot yet.\",\n \"If action changes DOM (open modal/navigate), stop after that batch and continue next round.\",\n \"Do NOT call page_info (get_url/get_title/query_all/snapshot).\",\n \"For dropdown/select fields, use dom with action=select_option (or fill on a select).\",\n \"If a needed list shows `... (N children omitted)` under a specific container, output `SNAPSHOT_HINT: EXPAND_CHILDREN #<containerRef>` and wait for next round snapshot.\",\n \"Build the minimal action array from current snapshot to finish this remaining instruction in one round whenever possible.\",\n \"For deterministic increase/decrease controls, compute delta from current visible value and issue exactly that many clicks in one round (e.g., +2 => two increase clicks). Do not overshoot then undo.\",\n \"Stop rule: once requested state is reached, stop tool calls. If verification is needed, verify once and then output REMAINING: DONE.\",\n allowAgentUiInteraction\n ? \"User explicitly asked to operate AutoPilot UI. You may interact with chat input/send/dock only as requested.\"\n : \"Do NOT interact with any AI chat UI elements (chat input, send button, dock). Only operate on the actual page content.\",\n ];\n\n if (hasErrors) {\n contextParts.push(\n \"\",\n \"The last step failed. Retry with a different approach, or skip and continue with other visible targets.\",\n );\n } else {\n contextParts.push(\n \"\",\n \"If the goal is fully done, reply with a short summary (no tool calls).\",\n );\n }\n\n if (previousRoundTasks && previousRoundTasks.length > 0) {\n contextParts.push(\n \"\",\n \"Previous round planned task array (already executed):\",\n ...previousRoundTasks.map((task, index) => `${index + 1}. ${task}`),\n );\n\n // ─── 效果验证指令 ───\n // 要求 AI 在规划新操作前,先对比上轮操作与当前快照,判断是否生效。\n // 如果未生效,要求从同一区域找其他可操作目标,而非重复相同操作。\n contextParts.push(\n \"\",\n \"## Effect verification (MANDATORY before planning new actions)\",\n \"Compare the above executed actions against the CURRENT snapshot below:\",\n \"- Did each action produce the expected result? (e.g., click opened a dialog/navigated; fill changed the input value; select_option updated the selected item)\",\n \"- If an action had NO visible effect (page unchanged near the target area), do NOT repeat the same target.\",\n \" Instead, look for a different actionable element nearby (sibling/ancestor in the same row/card/group) that has stronger interaction signals (click listeners, button/link semantics).\",\n \"- Only after confirming which previous actions succeeded, plan this round's new actions on the remaining unfinished parts.\",\n );\n }\n\n if (previousRoundPlannedTasks && previousRoundPlannedTasks.length > 0) {\n contextParts.push(\n \"\",\n \"Previous round model planned task array (before execution):\",\n ...previousRoundPlannedTasks.map((task, index) => `${index + 1}. ${task}`),\n );\n }\n\n if (previousRoundModelOutput) {\n contextParts.push(\n \"\",\n \"Previous round model output (normalized, for task reduction input):\",\n previousRoundModelOutput,\n );\n }\n\n contextParts.push(\n // 协议约束:要求模型显式返回下一轮 remaining\n \"\",\n \"After this round, include one plain text line:\",\n \"REMAINING: <new remaining instruction after this-round actions>\",\n \"or REMAINING: DONE\",\n );\n\n // 最近失败摘要:若最近一步报错,把错误摘要附在上下文尾部\n const lastEntry = trace[trace.length - 1];\n if (hasToolError(lastEntry.result)) {\n const detail = toContentString(lastEntry.result.content);\n const stripped = detail.replace(SNAPSHOT_REGEX, \"\").trim();\n if (stripped && stripped.length < 300) {\n contextParts.push(\"\", \"Last error: \" + stripped);\n }\n }\n\n if (currentUrl) {\n contextParts.push(\"\", `URL: ${currentUrl}`);\n }\n\n if (protocolViolationHint) {\n contextParts.push(\"\", protocolViolationHint);\n }\n\n if (latestSnapshot) {\n // 始终注入“最新快照”,并强调无需再调用 page_info 获取页面信息。\n contextParts.push(\n \"\",\n \"## Latest DOM snapshot\",\n \"Use hash IDs from this snapshot. Do NOT call page_info — this is already the latest.\",\n wrapSnapshot(latestSnapshot),\n );\n }\n\n messages.push({ role: \"user\", content: contextParts.join(\"\\n\") });\n\n return messages;\n}\n","/**\n * 保护与恢复机制。\n *\n * 这个文件负责给 Agent Loop 提供“防失败、防空转、防重复”的保护链。\n * 目标是:即使某一步失败,也尽量让循环继续推进,而不是直接崩掉。\n *\n * 主要能力:\n * 1) 冗余拦截:拦住无意义的 `page_info.*` 调用\n * 2) 快照防抖:连续 snapshot 触发时给出警告并限制空转\n * 3) 找不到元素恢复:自动等待 + 刷新快照 + 重试上限\n * 4) 导航后刷新:导航成功后立刻更新快照上下文\n * 5) 空转检测:连续只读轮次触发停机信号\n *\n * 一句话:这里是主循环的“保险丝层”。\n */\nimport type { ToolCallResult } from \"../tool-registry.js\";\nimport type { AgentLoopCallbacks } from \"./types.js\";\nimport { DEFAULT_ACTION_RECOVERY_ROUNDS } from \"./constants.js\";\nimport { readPageSnapshot } from \"./snapshot.js\";\nimport {\n getToolAction,\n hasToolError,\n isElementNotFoundResult,\n resolveRecoveryWaitMs,\n buildToolCallKey,\n sleep,\n toContentString,\n} from \"./helpers.js\";\nimport { ToolRegistry } from \"../tool-registry.js\";\nimport type { PageContextState } from \"./types.js\";\n\n// ─── 冗余 page_info 拦截 ───\n\n/** 冗余 page_info 动作集合。 */\nconst REDUNDANT_PAGE_INFO_ACTIONS = new Set([\"snapshot\", \"query_all\", \"get_url\", \"get_title\", \"get_viewport\"]);\n\n/**\n * 冗余 page_info 检查。\n *\n * 场景:模型在 loop 中频繁请求 page_info,导致“只看不做”。\n * 处理:命中白名单动作时直接返回拦截结果,不真正执行工具。\n *\n * 示例:\n * - 输入:`page_info.snapshot`\n * - 输出:`REDUNDANT_PAGE_INFO_SKIPPED`\n */\nexport function checkRedundantSnapshot(\n toolName: string,\n toolInput: unknown,\n _latestSnapshot: string | undefined,\n round: number,\n): ToolCallResult | null {\n if (toolName !== \"page_info\") return null;\n\n const action = getToolAction(toolInput);\n if (action && REDUNDANT_PAGE_INFO_ACTIONS.has(action)) {\n return {\n content:\n `page_info.${action} is blocked in loop execution. A snapshot is provided by the framework; continue with actionable tools directly.`,\n details: {\n code: \"REDUNDANT_PAGE_INFO_SKIPPED\",\n action,\n round,\n },\n };\n }\n return null;\n}\n\n/**\n * 快照防抖。\n *\n * 规则:连续触发 `page_info.snapshot` 时,第 2 次起标记为冗余,\n * 返回 `REDUNDANT_SNAPSHOT`,提醒模型直接使用已有快照继续执行。\n *\n * 返回值:\n * - `result`:可能被替换成防抖后的结果\n * - `consecutiveCount`:更新后的连续 snapshot 计数\n */\nexport function applySnapshotDebounce(\n toolName: string,\n toolInput: unknown,\n result: ToolCallResult,\n consecutiveCount: number,\n): { result: ToolCallResult; consecutiveCount: number } {\n if (toolName === \"page_info\" && getToolAction(toolInput) === \"snapshot\") {\n const newCount = consecutiveCount + 1;\n if (newCount >= 2) {\n return {\n consecutiveCount: newCount,\n result: {\n content: [\n toContentString(result.content),\n \"Redundant snapshot detected. Continue with remaining actionable steps using the latest snapshot; avoid additional snapshot unless navigation or uncertainty changes.\",\n ].join(\"\\n\"),\n details: {\n error: true,\n code: \"REDUNDANT_SNAPSHOT\",\n consecutiveSnapshotCalls: newCount,\n },\n },\n };\n }\n return { result, consecutiveCount: newCount };\n }\n // 非 snapshot 调用,重置计数\n return { result, consecutiveCount: 0 };\n}\n\n// ─── 元素未找到自动恢复 ───\n\n/**\n * 元素未找到恢复。\n *\n * 触发条件:\n * - 工具是 `dom`\n * - 结果被识别为“元素未找到”\n *\n * 处理流程:\n * 1) 按调用键统计恢复次数(同 name + input 视为同一调用)\n * 2) 在上限内:等待 -> 刷新快照 -> 返回 `ELEMENT_NOT_FOUND_RECOVERY`\n * 3) 超过上限:返回 `ELEMENT_NOT_FOUND_MAX_RECOVERY_REACHED`\n *\n * 说明:函数只返回“恢复后的结果描述”,是否继续下一轮由主循环决定。\n */\nexport async function handleElementRecovery(\n toolName: string,\n toolInput: unknown,\n result: ToolCallResult,\n recoveryAttempts: Map<string, number>,\n registry: ToolRegistry,\n pageContext: PageContextState,\n callbacks?: AgentLoopCallbacks,\n): Promise<ToolCallResult | null> {\n if (toolName !== \"dom\" || !isElementNotFoundResult(result)) {\n return null;\n }\n\n const key = buildToolCallKey(toolName, toolInput);\n const attempts = (recoveryAttempts.get(key) ?? 0) + 1;\n recoveryAttempts.set(key, attempts);\n const recoveryWaitMs = resolveRecoveryWaitMs(toolInput);\n\n if (attempts <= DEFAULT_ACTION_RECOVERY_ROUNDS) {\n await sleep(recoveryWaitMs);\n callbacks?.onBeforeRecoverySnapshot?.();\n pageContext.latestSnapshot = await readPageSnapshot(registry);\n\n return {\n content: [\n toContentString(result.content),\n `Recovery ${attempts}/${DEFAULT_ACTION_RECOVERY_ROUNDS}: snapshot refreshed, re-locate target.`,\n ].join(\"\\n\"),\n details: {\n error: true,\n code: \"ELEMENT_NOT_FOUND_RECOVERY\",\n recoveryAttempt: attempts,\n recoveryMaxRounds: DEFAULT_ACTION_RECOVERY_ROUNDS,\n },\n };\n }\n\n return {\n content: [\n toContentString(result.content),\n `Max recovery attempts (${DEFAULT_ACTION_RECOVERY_ROUNDS}) reached. Try a different target.`,\n ].join(\"\\n\"),\n details: {\n error: true,\n code: \"ELEMENT_NOT_FOUND_MAX_RECOVERY_REACHED\",\n recoveryAttempt: attempts,\n recoveryMaxRounds: DEFAULT_ACTION_RECOVERY_ROUNDS,\n },\n };\n}\n\n// ─── 导航后 URL 变化检测 ───\n\n/**\n * 导航后快照刷新。\n *\n * 当 `navigate.goto/back/forward/reload` 成功后,立即刷新快照,\n * 防止后续动作还在旧页面上下文里决策。\n */\nexport async function handleNavigationUrlChange(\n toolName: string,\n toolInput: unknown,\n result: ToolCallResult,\n registry: ToolRegistry,\n pageContext: PageContextState,\n callbacks?: AgentLoopCallbacks,\n): Promise<void> {\n if (toolName !== \"navigate\") return;\n\n const action = getToolAction(toolInput);\n if (\n (action === \"goto\" || action === \"back\" || action === \"forward\" || action === \"reload\") &&\n !hasToolError(result)\n ) {\n callbacks?.onBeforeRecoverySnapshot?.();\n pageContext.latestSnapshot = await readPageSnapshot(registry);\n }\n}\n\n// ─── 空转检测 ───\n\n/** 只读工具集合。 */\nconst READ_ONLY_TOOLS = new Set([\"page_info\"]);\n\n/** DOM 只读动作集合。 */\nconst READ_ONLY_DOM_ACTIONS = new Set([\"get_text\", \"get_attr\"]);\n\n/**\n * 空转检测:识别连续只读轮次并终止。\n *\n * 判定口径:\n * - `page_info.*` 视为只读\n * - `dom.get_text/get_attr` 视为只读\n *\n * 返回值语义:\n * - `-1`:触发停机(连续 2 轮纯只读)\n * - `0`:本轮有实质操作,计数清零\n * - `>0`:当前连续只读轮次\n */\nexport function detectIdleLoop(\n toolCalls: Array<{ name: string; input: unknown }>,\n consecutiveReadOnlyRounds: number,\n): number {\n const allReadOnly = toolCalls.length > 0 && toolCalls.every(({ name, input }) => {\n if (READ_ONLY_TOOLS.has(name)) return true;\n if (name !== \"dom\") return false;\n const action = getToolAction(input);\n return Boolean(action && READ_ONLY_DOM_ACTIONS.has(action));\n });\n if (allReadOnly) {\n const newCount = consecutiveReadOnlyRounds + 1;\n // 连续 2 轮纯只读 → 返回 -1 表示强制终止\n return newCount >= 2 ? -1 : newCount;\n }\n return 0; // 有实际操作,重置\n}\n","/**\n * Agent Loop 主流程(口语版)\n *\n * 流程图(文本):\n *\n * 轮次开始\n * │\n * ├─ 先看有没有最新快照\n * │ └─ 没有就先拍一张(可带 expandChildrenRefs)\n * │\n * ├─ 组装本轮上下文消息\n * │ └─ remaining + 上轮任务 + 最新快照 +(必要时)重试提示\n * │\n * ├─ 调用模型拿决策\n * │ └─ 同时解析 `REMAINING` 和 `SNAPSHOT_HINT`\n * │\n * ├─ 有 toolCalls 吗?\n * │ ├─ 没有:走收敛/协议修复判断(必要时等待后重试)\n * │ └─ 有:逐个执行工具\n * │ ├─ 冗余拦截(例如 page_info 空转)\n * │ ├─ 失败恢复(元素未找到重试)\n * │ ├─ 导航后更新快照\n * │ └─ 命中断轮条件则提前结束本轮\n * │\n * ├─ 更新 remaining(优先协议,缺失时启发式剔除)\n * │\n * ├─ 防空转 / 防自转检查\n * │ └─ 连续只读或重复批次会触发停机\n * │\n * ├─ 刷新快照\n * ▼\n * 下一轮或停机\n *\n * 停机条件(任一命中):\n * - `REMAINING: DONE`(或 remaining 为空)\n * - 协议修复后仍无推进\n * - 连续只读(空转)\n * - 重复批次(自转)\n * - 达到 maxRounds\n */\nimport {\n DEFAULT_MAX_ROUNDS,\n DEFAULT_NOT_FOUND_RETRY_ROUNDS,\n DEFAULT_NOT_FOUND_RETRY_WAIT_MS,\n DEFAULT_ROUND_STABILITY_WAIT_LOADING_SELECTORS,\n DEFAULT_ROUND_STABILITY_WAIT_QUIET_MS,\n DEFAULT_ROUND_STABILITY_WAIT_TIMEOUT_MS,\n} from \"./constants.js\";\nimport {\n buildTaskArray,\n collectMissingTask,\n deriveNextInstruction,\n extractHashSelectorRef,\n getToolAction,\n normalizeModelOutput,\n parseSnapshotExpandHints,\n reduceRemainingHeuristically,\n shouldForceRoundBreak,\n isPotentialDomMutation,\n sleep,\n toContentString,\n hasToolError,\n} from \"./helpers.js\";\nimport { readPageSnapshot, stripSnapshotFromPrompt } from \"./snapshot.js\";\nimport { buildCompactMessages } from \"./messages.js\";\nimport {\n checkRedundantSnapshot,\n applySnapshotDebounce,\n handleElementRecovery,\n handleNavigationUrlChange,\n detectIdleLoop,\n} from \"./recovery.js\";\nimport type {\n AgentLoopParams,\n AgentLoopResult,\n AgentLoopMetrics,\n PageContextState,\n ToolTraceEntry,\n} from \"./types.js\";\nimport type { AIMessage } from \"../types.js\";\n\n/**\n * 执行 Agent 循环。\n *\n * 你可以把这个函数理解成“任务执行调度器”:\n * - 输入:用户任务、系统提示词、工具注册表、历史消息、初始快照\n * - 过程:按轮次持续执行“看页面 -> 让模型决策 -> 跑工具 -> 更新上下文”\n * - 输出:最终回复、完整工具调用记录、可复用消息、结构化指标\n *\n * 每轮主流程(固定顺序):\n * 1) Ensure Snapshot:确保当前有最新快照(必要时读取)\n * 2) Build Messages:构建紧凑上下文(remaining + 上轮轨迹 + 最新快照)\n * 3) Call AI:请求模型并解析协议字段(`REMAINING` / `SNAPSHOT_HINT`)\n * 4) Execute Tools:执行工具调用并应用保护机制(冗余拦截、恢复、导航刷新)\n * 5) Reduce Remaining:推进剩余任务(优先协议,缺失时启发式剔除)\n * 6) Guard & Refresh:防空转/防自转判定,并刷新快照进入下一轮\n *\n * 核心状态语义:\n * - `remainingInstruction`:当前轮还未消费完的任务文本\n * - `previousRoundTasks`:上一轮已执行动作,防止模型原样重复\n * - `previousRoundPlannedTasks`:上一轮模型计划,用于重复批次检测\n * - `protocolViolationHint`:协议修复提示(remaining 未完成却无工具调用时注入)\n *\n * 停机条件(命中任意一条即结束):\n * - 模型无工具调用且 remaining 已收敛(`REMAINING: DONE` 或空)\n * - 协议修复后仍无推进\n * - 连续只读轮次(防空转)\n * - 连续重复计划批次(防自转)\n * - 达到 `maxRounds`\n */\nexport async function executeAgentLoop(\n params: AgentLoopParams,\n): Promise<AgentLoopResult> {\n const {\n client,\n registry,\n systemPrompt,\n message,\n initialSnapshot,\n history,\n dryRun = false,\n maxRounds = DEFAULT_MAX_ROUNDS,\n roundStabilityWait,\n callbacks,\n } = params;\n\n // 固定依赖与运行态容器\n const tools = registry.getDefinitions();\n const allToolCalls: AgentLoopResult[\"toolCalls\"] = [];\n const fullToolTrace: ToolTraceEntry[] = [];\n const actionRecoveryAttempts = new Map<string, number>();\n const pageContext: PageContextState = {\n latestSnapshot: initialSnapshot,\n };\n\n // 最终输出\n let finalReply = \"\";\n\n // 循环控制状态\n let consecutiveSnapshotCalls = 0;\n let consecutiveReadOnlyRounds = 0;\n let usedRounds = 0;\n\n // token 统计\n let inputTokens = 0;\n let outputTokens = 0;\n // 渐进式任务状态\n // remainingInstruction: 当前轮次要继续消费的剩余文本。\n // previousRoundTasks: 上一轮已经执行过的任务数组,用于提醒 AI 不要原样重复。\n // lastPlannedBatchKey + consecutiveSamePlannedBatch: 防止 AI 连续给出相同任务批次导致自转。\n // lastRoundHadError: 如果上一轮有错误,不触发“重复批次即停机”,避免误停。\n let remainingInstruction = message.trim();\n let previousRoundTasks: string[] = [];\n let previousRoundPlannedTasks: string[] = [];\n let previousRoundModelOutput = \"\";\n let lastPlannedBatchKey = \"\";\n let consecutiveSamePlannedBatch = 0;\n let lastRoundHadError = false;\n let protocolViolationHint: string | undefined;\n const snapshotExpandRefIds = new Set<string>();\n const effectiveRoundStabilityWait = {\n enabled: roundStabilityWait?.enabled ?? true,\n timeoutMs: Math.max(200, Math.floor(roundStabilityWait?.timeoutMs ?? DEFAULT_ROUND_STABILITY_WAIT_TIMEOUT_MS)),\n quietMs: Math.max(50, Math.floor(roundStabilityWait?.quietMs ?? DEFAULT_ROUND_STABILITY_WAIT_QUIET_MS)),\n loadingSelectors: [\n ...new Set(\n [\n ...DEFAULT_ROUND_STABILITY_WAIT_LOADING_SELECTORS,\n ...(roundStabilityWait?.loadingSelectors ?? []),\n ]\n .map(selector => selector.trim())\n .filter(Boolean),\n ),\n ],\n };\n // 恢复与拦截统计\n let recoveryCount = 0;\n let redundantInterceptCount = 0;\n\n type MissingToolTask = {\n name: string;\n input: unknown;\n reason: string;\n };\n\n let pendingNotFoundRetry:\n | {\n attempt: number;\n tasks: MissingToolTask[];\n }\n | undefined;\n\n // 快照体积统计\n let snapshotReadCount = 0;\n let snapshotSizeTotal = 0;\n let snapshotSizeMax = 0;\n\n /**\n * 记录快照统计。\n *\n * 用于输出可观测指标:读取次数、平均长度、最大长度。\n * Used for observability metrics: read count, avg size, max size.\n */\n const recordSnapshotStats = (snapshot: string | undefined): void => {\n if (typeof snapshot !== \"string\") return;\n snapshotReadCount += 1;\n snapshotSizeTotal += snapshot.length;\n if (snapshot.length > snapshotSizeMax) snapshotSizeMax = snapshot.length;\n };\n\n /**\n * 刷新页面快照。\n *\n * 只做两件事:读取最新快照 + 更新快照统计。\n * Does exactly two things: read latest snapshot + update metrics.\n */\n const refreshSnapshot = async (): Promise<void> => {\n pageContext.latestSnapshot = await readPageSnapshot(\n registry,\n snapshotExpandRefIds.size > 0\n ? { expandChildrenRefs: Array.from(snapshotExpandRefIds), expandedChildrenLimit: 120 }\n : undefined,\n );\n recordSnapshotStats(pageContext.latestSnapshot);\n };\n\n /**\n * 轮次后稳定等待(双重等待)。\n *\n * 顺序固定为:\n * 1) 等待 loading 指示器隐藏\n * 2) 等待 DOM quiet window\n */\n const runRoundStabilityBarrier = async (): Promise<void> => {\n if (!effectiveRoundStabilityWait.enabled) return;\n if (!registry.has(\"wait\")) return;\n\n const timeout = effectiveRoundStabilityWait.timeoutMs;\n const loadingSelector = effectiveRoundStabilityWait.loadingSelectors.join(\", \");\n\n if (loadingSelector) {\n await registry.dispatch(\"wait\", {\n action: \"wait_for_selector\",\n selector: loadingSelector,\n state: \"hidden\",\n timeout,\n });\n }\n\n await registry.dispatch(\"wait\", {\n action: \"wait_for_stable\",\n timeout,\n quietMs: effectiveRoundStabilityWait.quietMs,\n });\n };\n\n\n if (pageContext.latestSnapshot) {\n recordSnapshotStats(pageContext.latestSnapshot);\n }\n\n /**\n * 追加工具轨迹。\n *\n * 同时写入:\n * - allToolCalls:对外返回结果\n * - fullToolTrace:下一轮消息上下文\n */\n const appendToolTrace = (\n round: number,\n name: string,\n input: unknown,\n result: AgentLoopResult[\"toolCalls\"][number][\"result\"],\n ): void => {\n allToolCalls.push({ name, input, result });\n fullToolTrace.push({ round, name, input, result });\n };\n\n // 主循环\n for (let round = 0; round < maxRounds; round++) {\n callbacks?.onRound?.(round);\n usedRounds = round + 1;\n\n // ═══ 阶段 1:确保快照 ═══\n if (!pageContext.latestSnapshot) {\n await refreshSnapshot();\n }\n\n // ═══ 阶段 2:构建紧凑消息 ═══\n // 每轮消息都自带快照(buildCompactMessages 注入),因此始终剥离\n // system prompt 中的旧快照,避免重复。\n const effectivePrompt = stripSnapshotFromPrompt(systemPrompt);\n\n const chatMessages = buildCompactMessages(\n message,\n fullToolTrace,\n pageContext.latestSnapshot,\n pageContext.currentUrl,\n history,\n remainingInstruction,\n previousRoundTasks,\n previousRoundModelOutput,\n previousRoundPlannedTasks,\n protocolViolationHint,\n );\n\n if (pendingNotFoundRetry && pendingNotFoundRetry.tasks.length > 0) {\n chatMessages.push({\n role: \"user\",\n content: [\n \"## Not-found retry context\",\n `Retry attempt: ${pendingNotFoundRetry.attempt}/${DEFAULT_NOT_FOUND_RETRY_ROUNDS}`,\n \"These tool targets were not found in previous execution:\",\n ...pendingNotFoundRetry.tasks.map((task, i) =>\n `${i + 1}. ${task.name}(${JSON.stringify(task.input)}) -> ${task.reason}`,\n ),\n \"Only retry unresolved targets that are now visible in the latest snapshot.\",\n \"If still not found, return no tool calls and include REMAINING with the unresolved part.\",\n ].join(\"\\n\"),\n });\n }\n\n // ═══ 阶段 3:调用 AI ═══\n const response = await client.chat({\n systemPrompt: effectivePrompt,\n messages: chatMessages,\n tools,\n });\n\n // 计费/观测数据累计\n inputTokens += response.usage?.inputTokens ?? 0;\n outputTokens += response.usage?.outputTokens ?? 0;\n\n // 先解析协议,最终推进在本轮执行后统一决定。\n const parsedInstructionState = deriveNextInstruction(response.text, remainingInstruction);\n const snapshotHintRefs = parseSnapshotExpandHints(response.text);\n for (const ref of snapshotHintRefs.slice(0, 8)) {\n snapshotExpandRefIds.add(ref);\n }\n\n // 没有工具调用:若处于找不到重试流程,先等待再重试;否则正常结束\n if (!response.toolCalls || response.toolCalls.length === 0) {\n if (pendingNotFoundRetry) {\n const unresolvedHint = response.text?.toLowerCase() ?? \"\";\n const stillUnresolved =\n unresolvedHint.includes(\"找不到\") ||\n unresolvedHint.includes(\"未找到\") ||\n unresolvedHint.includes(\"not found\") ||\n unresolvedHint.includes(\"cannot find\") ||\n unresolvedHint.includes(\"unable to locate\");\n\n if (stillUnresolved && pendingNotFoundRetry.attempt < DEFAULT_NOT_FOUND_RETRY_ROUNDS) {\n pendingNotFoundRetry = {\n ...pendingNotFoundRetry,\n attempt: pendingNotFoundRetry.attempt + 1,\n };\n callbacks?.onText?.(\n `未命中目标,准备第 ${pendingNotFoundRetry.attempt} 次重试(等待 ${DEFAULT_NOT_FOUND_RETRY_WAIT_MS}ms)...`,\n );\n await sleep(DEFAULT_NOT_FOUND_RETRY_WAIT_MS);\n await refreshSnapshot();\n continue;\n }\n pendingNotFoundRetry = undefined;\n }\n\n if (parsedInstructionState.hasRemainingProtocol) {\n remainingInstruction = parsedInstructionState.nextInstruction;\n }\n\n const unresolvedRemaining = remainingInstruction.trim().length > 0;\n if (unresolvedRemaining && round < maxRounds - 1) {\n protocolViolationHint = [\n \"Protocol violation in previous round:\",\n \"- Remaining task is not DONE, but no tool calls were returned.\",\n \"This round MUST do one of:\",\n \"1) Return actionable tool calls for visible targets; or\",\n \"2) If truly complete, return a short summary and EXACTLY `REMAINING: DONE`.\",\n \"Do NOT output planning/explaining text.\",\n ].join(\"\\n\");\n lastRoundHadError = true;\n await refreshSnapshot();\n continue;\n }\n\n finalReply = response.text ?? \"\";\n if (finalReply) callbacks?.onText?.(finalReply);\n break;\n }\n\n protocolViolationHint = undefined;\n const plannedTasksCurrentRound = buildTaskArray(\n response.toolCalls.map(tc => ({ name: tc.name, input: tc.input })),\n );\n\n const plannedBatchKey = JSON.stringify(\n response.toolCalls.map(tc => ({ name: tc.name, input: tc.input })),\n );\n // 比较“本轮计划”与“上一轮计划”是否完全一致。\n if (plannedBatchKey === lastPlannedBatchKey) {\n consecutiveSamePlannedBatch += 1;\n } else {\n consecutiveSamePlannedBatch = 1;\n lastPlannedBatchKey = plannedBatchKey;\n }\n\n // 防自转:连续两轮给出相同计划且上一轮无错误,判定任务已完成或模型卡住,直接结束。\n if (consecutiveSamePlannedBatch >= 2 && !lastRoundHadError) {\n finalReply = response.text?.trim() || \"任务已完成。\";\n if (finalReply) callbacks?.onText?.(finalReply);\n break;\n }\n\n // ─── Dry-run 模式 ───\n if (dryRun) {\n finalReply = response.text ? response.text + \"\\n\\n\" : \"\";\n finalReply += \"🔧 AI 请求调用以下工具(dry-run 模式,未执行):\\n\";\n for (const tc of response.toolCalls) {\n callbacks?.onToolCall?.(tc.name, tc.input);\n finalReply += `\\n┌─ 工具: ${tc.name}\\n`;\n finalReply += `│ ID: ${tc.id}\\n`;\n finalReply += `│ 参数:\\n`;\n const inputStr = JSON.stringify(tc.input, null, 2);\n for (const line of inputStr.split(\"\\n\")) {\n finalReply += `│ ${line}\\n`;\n }\n finalReply += `└────────────────────\\n`;\n }\n break;\n }\n\n // ═══ 阶段 4:执行工具调用(带保护机制)═══\n\n // 批量执行所有工具调用\n // roundHasError 用于控制“重复批次停机”:上一轮有错误时,不应武断终止。\n let roundHasError = false;\n let roundHasPotentialDomMutation = false;\n const executedTaskCalls: Array<{ name: string; input: unknown }> = [];\n const roundMissingTasks: MissingToolTask[] = [];\n for (const tc of response.toolCalls) {\n\n // 自动策略:当 AI 对 hash 列表执行 scroll 时,默认下一轮对该节点放宽 children 截断。\n // 这样即使模型未显式输出 SNAPSHOT_HINT,也能尽快拿到完整列表选项。\n if (tc.name === \"dom\" && getToolAction(tc.input) === \"scroll\") {\n const ref = extractHashSelectorRef(tc.input);\n if (ref) snapshotExpandRefIds.add(ref);\n }\n\n // 保护 1:冗余快照拦截\n const redundant = checkRedundantSnapshot(\n tc.name, tc.input, pageContext.latestSnapshot, round,\n );\n if (redundant) {\n appendToolTrace(round, tc.name, tc.input, redundant);\n redundantInterceptCount += 1;\n callbacks?.onToolResult?.(tc.name, redundant);\n continue;\n }\n\n callbacks?.onToolCall?.(tc.name, tc.input);\n\n // 执行工具\n let result = await registry.dispatch(tc.name, tc.input);\n\n // 保护 2:连续快照防抖\n const debounced = applySnapshotDebounce(\n tc.name, tc.input, result, consecutiveSnapshotCalls,\n );\n result = debounced.result;\n consecutiveSnapshotCalls = debounced.consecutiveCount;\n\n // 保护 3:元素未找到自动恢复\n const recovered = await handleElementRecovery(\n tc.name, tc.input, result,\n actionRecoveryAttempts, registry, pageContext, callbacks,\n );\n if (recovered) result = recovered;\n if (\n recovered?.details &&\n typeof recovered.details === \"object\" &&\n (recovered.details as { code?: unknown }).code === \"ELEMENT_NOT_FOUND_RECOVERY\"\n ) {\n recoveryCount += 1;\n }\n\n appendToolTrace(round, tc.name, tc.input, result);\n executedTaskCalls.push({ name: tc.name, input: tc.input });\n\n const missingTask = collectMissingTask(tc.name, tc.input, result);\n if (missingTask) {\n roundMissingTasks.push(missingTask);\n }\n\n if (result.details && typeof result.details === \"object\") {\n roundHasError = roundHasError || Boolean((result.details as { error?: unknown }).error);\n }\n if (!hasToolError(result) && isPotentialDomMutation(tc.name, tc.input)) {\n roundHasPotentialDomMutation = true;\n }\n\n // 捕获显式 snapshot 结果\n if (tc.name === \"page_info\" && getToolAction(tc.input) === \"snapshot\") {\n pageContext.latestSnapshot = toContentString(result.content);\n recordSnapshotStats(pageContext.latestSnapshot);\n }\n\n // 保护 4:导航后 URL 变化检测\n await handleNavigationUrlChange(\n tc.name, tc.input, result, registry, pageContext, callbacks,\n );\n\n callbacks?.onToolResult?.(tc.name, result);\n\n if (shouldForceRoundBreak(tc.name, tc.input)) {\n break;\n }\n }\n\n if (roundMissingTasks.length > 0) {\n pendingNotFoundRetry = {\n attempt: 1,\n tasks: roundMissingTasks,\n };\n } else {\n pendingNotFoundRetry = undefined;\n }\n\n // 将本轮执行状态传给下一轮上下文。\n if (parsedInstructionState.hasRemainingProtocol) {\n remainingInstruction = parsedInstructionState.nextInstruction;\n } else {\n const nextByHeuristic = reduceRemainingHeuristically(remainingInstruction, executedTaskCalls.length);\n if (nextByHeuristic !== remainingInstruction) {\n remainingInstruction = nextByHeuristic;\n } else {\n roundHasError = true;\n }\n }\n\n previousRoundModelOutput = parsedInstructionState.hasRemainingProtocol\n ? normalizeModelOutput(response.text)\n : `REMAINING: ${remainingInstruction || \"DONE\"}`;\n\n lastRoundHadError = roundHasError;\n previousRoundTasks = buildTaskArray(executedTaskCalls);\n previousRoundPlannedTasks = plannedTasksCurrentRound;\n\n // 协议显式 DONE 且本轮已完成执行且无错误:直接收敛,避免后续重复动作。\n if (\n parsedInstructionState.hasRemainingProtocol &&\n remainingInstruction.trim().length === 0 &&\n !roundHasError\n ) {\n finalReply = response.text?.trim() || \"任务已完成。\";\n if (finalReply) callbacks?.onText?.(finalReply);\n break;\n }\n\n // 保护 5:空转检测\n const attemptedTaskCalls = response.toolCalls.map(tc => ({ name: tc.name, input: tc.input }));\n const idleResult = detectIdleLoop(attemptedTaskCalls, consecutiveReadOnlyRounds);\n if (idleResult === -1) {\n finalReply = response.text?.trim() || \"任务已完成。\";\n if (finalReply) callbacks?.onText?.(finalReply);\n break;\n }\n consecutiveReadOnlyRounds = idleResult;\n\n if (roundHasPotentialDomMutation) {\n await runRoundStabilityBarrier();\n }\n\n // ═══ 阶段 5:刷新快照(供下一轮使用)═══\n await refreshSnapshot();\n }\n\n // 构建紧凑的 result.messages 供多轮记忆使用\n // Build compact result.messages for optional multi-turn memory reuse.\n const resultMessages: AIMessage[] = [...(history ?? []), { role: \"user\", content: message }];\n if (finalReply) {\n resultMessages.push({ role: \"assistant\", content: finalReply });\n }\n\n // 结果统计\n const successfulToolCalls = allToolCalls.filter(tc => {\n const details = tc.result.details;\n return !(details && typeof details === \"object\" && Boolean((details as { error?: unknown }).error));\n }).length;\n const failedToolCalls = allToolCalls.length - successfulToolCalls;\n\n const metrics: AgentLoopMetrics = {\n roundCount: usedRounds,\n totalToolCalls: allToolCalls.length,\n successfulToolCalls,\n failedToolCalls,\n toolSuccessRate: allToolCalls.length > 0\n ? Number((successfulToolCalls / allToolCalls.length).toFixed(4))\n : 1,\n recoveryCount,\n redundantInterceptCount,\n snapshotReadCount,\n latestSnapshotSize: pageContext.latestSnapshot?.length ?? 0,\n avgSnapshotSize: snapshotReadCount > 0 ? Math.round(snapshotSizeTotal / snapshotReadCount) : 0,\n maxSnapshotSize: snapshotSizeMax,\n inputTokens,\n outputTokens,\n };\n\n // 统一发出指标回调\n callbacks?.onMetrics?.(metrics);\n\n return { reply: finalReply, toolCalls: allToolCalls, messages: resultMessages, metrics };\n}\n\n// ─── Re-exports(维持外部 API 不变)───\nexport { wrapSnapshot } from \"./snapshot.js\";\nexport type {\n AgentLoopParams,\n AgentLoopResult,\n AgentLoopCallbacks,\n AgentLoopMetrics,\n RoundStabilityWaitOptions,\n} from \"./types.js\";\n","/**\n * AI Client 共享常量(中)/ Shared constants and helpers for AI clients (EN).\n */\nimport type { AIClientConfig } from \"./index.js\";\n\n// ─── Provider 端点映射 ───\n\n/** 默认端点映射(中)/ Default API endpoints by provider (EN). */\nexport const PROVIDER_ENDPOINTS: Record<string, string> = {\n openai: \"https://api.openai.com/v1\",\n copilot: \"https://models.inference.ai.azure.com\",\n anthropic: \"https://api.anthropic.com\",\n deepseek: \"https://api.deepseek.com\",\n doubao: \"https://ark.cn-beijing.volces.com/api/v3\",\n qwen: \"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n};\n\n// ─── 共享工具函数 ───\n\n/** 校验 provider(中)/ Validate provider support (EN). */\nexport function validateProvider(provider: string): void {\n if (!PROVIDER_ENDPOINTS[provider]) {\n const supported = Object.keys(PROVIDER_ENDPOINTS).join(\", \");\n throw new Error(\n `Unknown AI provider: ${provider}. Supported: ${supported}`,\n );\n }\n}\n\n/** 解析 baseURL(中)/ Resolve API base URL (EN). */\nexport function resolveBaseURL(config: AIClientConfig): string {\n return config.baseURL ?? PROVIDER_ENDPOINTS[config.provider] ?? \"\";\n}\n\n/**\n * 清理 schema(中)/ Clean non-serializable fields from schema (EN).\n */\nexport function cleanSchema(schema: unknown): unknown {\n return JSON.parse(JSON.stringify(schema));\n}\n","/** SSE 事件处理器(中)/ SSE JSON event handler (EN). Return false to stop early. */\nexport type SSEJSONHandler = (\n event: Record<string, unknown>,\n meta: { event?: string; rawData: string },\n) => void | boolean | Promise<void | boolean>;\n\n/** SSE 配置(中)/ SSE consume options (EN). */\nexport type SSEConsumeOptions = {\n /** 单次读取超时(毫秒)。不传则不超时。 */\n readTimeoutMs?: number;\n /** 是否在遇到 [DONE] 时提前结束(默认 true)。 */\n stopOnDone?: boolean;\n};\n\n/**\n * 通用 SSE(JSON) 消费器(中)/ Generic SSE(JSON) consumer (EN).\n *\n * 读取 response.body,按 SSE 规则拼装并分发 JSON data 事件。\n * Reads response body, assembles SSE frames, and dispatches JSON data events.\n */\nexport async function consumeSSEJSON(\n response: Response,\n onEvent: SSEJSONHandler,\n options: SSEConsumeOptions = {},\n): Promise<void> {\n if (!response.body) return;\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n const stopOnDone = options.stopOnDone ?? true;\n\n let buffer = \"\";\n let currentEvent: string | undefined;\n let dataLines: string[] = [];\n let stoppedByDone = false;\n\n async function readChunk() {\n const readTimeoutMs = options.readTimeoutMs;\n if (!readTimeoutMs || readTimeoutMs <= 0) {\n return reader.read();\n }\n\n return new Promise<ReadableStreamReadResult<Uint8Array>>((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(new Error(`SSE read timeout (${readTimeoutMs}ms)`));\n }, readTimeoutMs);\n\n reader.read().then(\n (value) => {\n clearTimeout(timer);\n resolve(value);\n },\n (error) => {\n clearTimeout(timer);\n reject(error);\n },\n );\n });\n }\n\n async function flushEvent(): Promise<boolean> {\n if (dataLines.length === 0) {\n currentEvent = undefined;\n return true;\n }\n\n const rawData = dataLines.join(\"\\n\").trim();\n const event = currentEvent;\n dataLines = [];\n currentEvent = undefined;\n\n if (!rawData) return true;\n if (stopOnDone && rawData === \"[DONE]\") {\n stoppedByDone = true;\n return false;\n }\n\n try {\n const parsed = JSON.parse(rawData) as Record<string, unknown>;\n const shouldContinue = await onEvent(parsed, { event, rawData });\n if (shouldContinue === false) return false;\n } catch {\n // 非 JSON data 事件忽略\n }\n\n return true;\n }\n\n while (true) {\n const { done, value } = await readChunk();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() ?? \"\";\n\n for (const rawLine of lines) {\n const line = rawLine.endsWith(\"\\r\") ? rawLine.slice(0, -1) : rawLine;\n const trimmed = line.trim();\n\n if (!trimmed) {\n const shouldContinue = await flushEvent();\n if (!shouldContinue) break;\n continue;\n }\n if (trimmed.startsWith(\":\")) continue;\n if (trimmed.startsWith(\"event:\")) {\n currentEvent = trimmed.slice(6).trim() || undefined;\n continue;\n }\n if (trimmed.startsWith(\"data:\")) {\n dataLines.push(trimmed.slice(5).trimStart());\n }\n }\n\n if (stoppedByDone) break;\n }\n\n if (!stoppedByDone) {\n await flushEvent();\n } else {\n await reader.cancel().catch(() => undefined);\n }\n}\n","/**\n * 可继承 AI 客户端基类(中)/ Extensible base AI client class (EN).\n *\n * 支持注入 chatHandler 或子类覆写 chat。\n * Supports injected chatHandler or subclass override.\n */\nimport type { AIChatResponse, AIClient, AIMessage } from \"../types.js\";\nimport type { ToolDefinition } from \"../tool-registry.js\";\nimport {\n consumeSSEJSON,\n type SSEConsumeOptions,\n type SSEJSONHandler,\n} from \"./sse.js\";\n\n// ─── 类型定义 ───\n\n/** chat 入参(中)/ Chat handler params aligned with AIClient.chat (EN). */\nexport type ChatHandlerParams = {\n /** 系统提示词 */\n systemPrompt: string;\n /** 对话消息列表 */\n messages: AIMessage[];\n /** 可用工具定义列表 */\n tools?: ToolDefinition[];\n};\n\n/** BaseAIClient 选项(中)/ BaseAIClient constructor options (EN). */\nexport type BaseAIClientOptions = {\n /** 对话处理函数 — 接收 ChatHandlerParams,返回 AIChatResponse */\n chatHandler: (params: ChatHandlerParams) => Promise<AIChatResponse>;\n};\n\nexport {\n consumeSSEJSON,\n type SSEConsumeOptions,\n type SSEJSONHandler,\n} from \"./sse.js\";\n\n// ─── BaseAIClient 类 ───\n\n/**\n * BaseAIClient 实现(中)/ BaseAIClient implementation of AIClient (EN).\n */\nexport class BaseAIClient implements AIClient {\n /** 用户提供的对话处理函数 */\n protected chatHandler: (params: ChatHandlerParams) => Promise<AIChatResponse>;\n\n constructor(options: BaseAIClientOptions) {\n this.chatHandler = options.chatHandler;\n }\n\n /**\n * 发送对话请求(中)/ Dispatch chat request via handler (EN).\n */\n async chat(params: ChatHandlerParams): Promise<AIChatResponse> {\n return this.chatHandler(params);\n }\n\n /** SSE 消费复用入口(中)/ Reusable SSE(JSON) consumer for subclasses (EN). */\n protected async consumeSSEJSON(\n response: Response,\n onEvent: SSEJSONHandler,\n options?: SSEConsumeOptions,\n ): Promise<void> {\n return consumeSSEJSON(response, onEvent, options);\n }\n}\n","/**\n * OpenAI/Copilot 客户端(中)/ OpenAI-compatible client implementation (EN).\n */\nimport type { AIChatResponse, AIMessage, AIToolCall } from \"../types.js\";\nimport type { AIClientConfig, ChatParams, ChatRequestInit } from \"./index.js\";\nimport { BaseAIClient } from \"./custom.js\";\nimport type { ChatHandlerParams } from \"./custom.js\";\nimport { consumeSSEJSON } from \"./sse.js\";\nimport { resolveBaseURL, cleanSchema } from \"./constants.js\";\n\n// ─── OpenAI 原始 API 响应类型 ───\n\n/** OpenAI 工具调用原始类型(中)/ Raw OpenAI tool_call shape (EN). */\ntype OpenAIRawToolCall = {\n id: string;\n type: \"function\";\n function: { name: string; arguments: string };\n};\n\n/** OpenAI 原始响应类型(中)/ Raw OpenAI chat completion response (EN). */\ntype OpenAIRawResponse = {\n choices?: Array<{\n message: {\n content: string | null;\n tool_calls?: OpenAIRawToolCall[];\n };\n }>;\n usage?: {\n prompt_tokens?: number;\n completion_tokens?: number;\n };\n};\n\n// ─── OpenAIClient 类 ───\n\n/**\n * OpenAIClient 类(中)/ OpenAIClient class for OpenAI & Copilot (EN).\n */\nexport class OpenAIClient extends BaseAIClient {\n /** AI 客户端配置(provider / model / apiKey / baseURL) */\n protected config: AIClientConfig;\n\n constructor(config: AIClientConfig) {\n // 注入 chatHandler — 根据 config.stream 选择流式或 JSON(默认流式)\n super({\n chatHandler: async (params: ChatHandlerParams): Promise<AIChatResponse> => {\n const req = buildOpenAIRequest(this.config, params);\n const useStream = this.config.stream ?? true;\n\n if (!useStream) {\n const res = await fetch(req.url, {\n method: req.method,\n headers: req.headers,\n body: req.body,\n });\n\n if (!res.ok) {\n const errText = await res.text();\n throw new Error(`AI API ${res.status}: ${errText.slice(0, 500)}`);\n }\n\n const data = await res.json();\n return parseOpenAIResponse(data);\n }\n\n // 流式模式:请求体已在 buildOpenAIRequest 中包含 stream 字段\n const streamRes = await fetch(req.url, {\n method: req.method,\n headers: req.headers,\n body: req.body,\n });\n\n if (!streamRes.ok) {\n const errText = await streamRes.text();\n throw new Error(`AI API ${streamRes.status}: ${errText.slice(0, 500)}`);\n }\n\n const contentType = streamRes.headers.get(\"content-type\") ?? \"\";\n if (contentType.includes(\"application/json\")) {\n const data = await streamRes.json();\n return parseOpenAIResponse(data);\n }\n\n return parseOpenAIStream(streamRes, 20000);\n },\n });\n this.config = config;\n }\n}\n\n// ─── 底层 API:请求构建 ───\n\n/**\n * 构建 OpenAI 请求(中)/ Build OpenAI chat request payload (EN).\n */\nexport function buildOpenAIRequest(\n config: AIClientConfig,\n params: ChatParams,\n): ChatRequestInit {\n const baseURL = resolveBaseURL(config);\n const { systemPrompt, messages, tools } = params;\n\n // 转换工具定义为 OpenAI function calling 格式\n const openaiTools = tools?.map((t) => ({\n type: \"function\" as const,\n function: {\n name: t.name,\n description: t.description,\n parameters: cleanSchema(t.schema),\n },\n }));\n\n // 转换消息为 OpenAI 格式\n const openaiMessages = convertMessages(systemPrompt, messages);\n\n // 构建请求体\n const body: Record<string, unknown> = {\n model: config.model,\n messages: openaiMessages,\n temperature: 0.3,\n max_tokens: 4096,\n };\n\n if (config.stream ?? true) {\n body.stream = true;\n body.stream_options = { include_usage: true };\n }\n\n if (openaiTools && openaiTools.length > 0) {\n body.tools = openaiTools;\n body.tool_choice = \"auto\";\n body.parallel_tool_calls = true;\n }\n\n return {\n url: `${baseURL}/chat/completions`,\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${config.apiKey}`,\n },\n body: JSON.stringify(body),\n };\n}\n\n// ─── 响应解析 ───\n\n/**\n * 解析 OpenAI 响应(中)/ Parse raw OpenAI response into AIChatResponse (EN).\n */\nexport function parseOpenAIResponse(data: unknown): AIChatResponse {\n const d = data as OpenAIRawResponse;\n const choice = d.choices?.[0];\n if (!choice) throw new Error(\"AI 未返回有效响应\");\n\n const msg = choice.message;\n\n // 解析工具调用:arguments 是 JSON 字符串,需要 parse 为对象\n const toolCalls: AIToolCall[] | undefined = msg.tool_calls?.map((tc) => ({\n id: tc.id,\n name: tc.function.name,\n input: JSON.parse(tc.function.arguments),\n }));\n\n return {\n text: msg.content || undefined,\n toolCalls: toolCalls?.length ? toolCalls : undefined,\n usage: d.usage\n ? {\n inputTokens: d.usage.prompt_tokens ?? 0,\n outputTokens: d.usage.completion_tokens ?? 0,\n }\n : undefined,\n };\n}\n\n// ─── 内部辅助函数 ───\n\n/**\n * 消息转换(中)/ Convert unified messages to OpenAI format (EN).\n */\nfunction convertMessages(\n systemPrompt: string,\n messages: AIMessage[],\n): Record<string, unknown>[] {\n const result: Record<string, unknown>[] = [\n { role: \"system\", content: systemPrompt },\n ];\n\n for (const m of messages) {\n if (m.role === \"tool\" && Array.isArray(m.content)) {\n // 工具结果 → 每个结果单独一条 tool 消息(OpenAI 要求按 tool_call_id 对应)\n for (const tc of m.content) {\n result.push({\n role: \"tool\",\n content: tc.result,\n tool_call_id: tc.toolCallId,\n });\n }\n } else if (m.role === \"assistant\" && m.toolCalls?.length) {\n // AI 回复含工具调用 → 带 tool_calls 字段\n result.push({\n role: \"assistant\",\n content: typeof m.content === \"string\" ? m.content : null,\n tool_calls: m.toolCalls.map((tc) => ({\n id: tc.id,\n type: \"function\",\n function: {\n name: tc.name,\n arguments: JSON.stringify(tc.input),\n },\n })),\n });\n } else {\n // 普通消息(user / assistant 纯文本)\n result.push({\n role: m.role,\n content:\n typeof m.content === \"string\"\n ? m.content\n : JSON.stringify(m.content),\n });\n }\n }\n\n return result;\n}\n\n// ─── 流式响应解析 ───\n\n/** 流式 tool_call 增量类型(中)/ Tool-call delta type in SSE stream (EN). */\ntype OpenAIStreamToolCallDelta = {\n index: number;\n id?: string;\n function?: { name?: string; arguments?: string };\n};\n\n/** 流式 chunk 类型(中)/ SSE chunk type (EN). */\ntype OpenAIStreamChunk = {\n choices?: Array<{\n delta: {\n content?: string;\n tool_calls?: OpenAIStreamToolCallDelta[];\n };\n }>;\n usage?: { prompt_tokens?: number; completion_tokens?: number };\n};\n\n/**\n * 解析 OpenAI SSE(中)/ Parse OpenAI SSE stream into unified response (EN).\n */\nexport async function parseOpenAIStream(\n response: Response,\n readTimeoutMs = 20000,\n): Promise<AIChatResponse> {\n // 回退:无 ReadableStream 支持\n if (!response.body) {\n const data = await response.json();\n return parseOpenAIResponse(data);\n }\n\n let text = \"\";\n const toolCallMap = new Map<number, { id: string; name: string; arguments: string }>();\n let usage: AIChatResponse[\"usage\"];\n await consumeSSEJSON(\n response,\n (event) => {\n const chunk = event as OpenAIStreamChunk;\n const delta = chunk.choices?.[0]?.delta;\n\n if (delta?.content) text += delta.content;\n\n if (delta?.tool_calls) {\n for (const tc of delta.tool_calls) {\n const idx = tc.index ?? 0;\n const existing = toolCallMap.get(idx);\n if (existing) {\n if (tc.function?.arguments) existing.arguments += tc.function.arguments;\n } else {\n toolCallMap.set(idx, {\n id: tc.id ?? \"\",\n name: tc.function?.name ?? \"\",\n arguments: tc.function?.arguments ?? \"\",\n });\n }\n }\n }\n\n if (chunk.usage) {\n usage = {\n inputTokens: chunk.usage.prompt_tokens ?? 0,\n outputTokens: chunk.usage.completion_tokens ?? 0,\n };\n }\n },\n { readTimeoutMs, stopOnDone: true },\n );\n\n // 组装工具调用\n const toolCalls: AIToolCall[] = [];\n for (const [, tc] of [...toolCallMap.entries()].sort((a, b) => a[0] - b[0])) {\n try {\n toolCalls.push({ id: tc.id, name: tc.name, input: JSON.parse(tc.arguments) });\n } catch {\n // 工具参数 JSON 解析失败,跳过\n }\n }\n\n return {\n text: text || undefined,\n toolCalls: toolCalls.length > 0 ? toolCalls : undefined,\n usage,\n };\n}\n","/**\n * Anthropic 客户端实现(中)/ Anthropic Messages API client implementation (EN).\n */\nimport type { AIChatResponse, AIMessage, AIToolCall } from \"../types.js\";\nimport type { AIClientConfig, ChatParams, ChatRequestInit } from \"./index.js\";\nimport { BaseAIClient } from \"./custom.js\";\nimport type { ChatHandlerParams } from \"./custom.js\";\nimport { consumeSSEJSON } from \"./sse.js\";\nimport { resolveBaseURL, cleanSchema } from \"./constants.js\";\n\n// ─── Anthropic 原始 API 响应类型 ───\n\n/** Anthropic 文本块(中)/ Anthropic text block (EN). */\ntype AnthropicTextBlock = {\n type: \"text\";\n text: string;\n};\n\n/** Anthropic 工具调用块(中)/ Anthropic tool_use block (EN). */\ntype AnthropicToolUseBlock = {\n type: \"tool_use\";\n id: string;\n name: string;\n input: unknown;\n};\n\n/** Anthropic 内容块联合类型(中)/ Anthropic content block union (EN). */\ntype AnthropicContentBlock = AnthropicTextBlock | AnthropicToolUseBlock;\n\n/** Anthropic 原始响应类型(中)/ Raw Anthropic response type (EN). */\ntype AnthropicRawResponse = {\n content?: AnthropicContentBlock[];\n usage?: {\n input_tokens: number;\n output_tokens: number;\n };\n};\n\n// ─── AnthropicClient 类 ───\n\n/**\n * AnthropicClient 类(中)/ AnthropicClient class (EN).\n */\nexport class AnthropicClient extends BaseAIClient {\n /** AI 客户端配置(provider / model / apiKey / baseURL) */\n protected config: AIClientConfig;\n\n constructor(config: AIClientConfig) {\n // 注入 chatHandler — 根据 config.stream 选择流式或 JSON(默认流式)\n super({\n chatHandler: async (params: ChatHandlerParams): Promise<AIChatResponse> => {\n const req = buildAnthropicRequest(this.config, params);\n const useStream = this.config.stream ?? true;\n\n if (!useStream) {\n const res = await fetch(req.url, {\n method: req.method,\n headers: req.headers,\n body: req.body,\n });\n\n if (!res.ok) {\n const errText = await res.text();\n throw new Error(`AI API ${res.status}: ${errText.slice(0, 500)}`);\n }\n\n const data = await res.json();\n return parseAnthropicResponse(data);\n }\n\n // 流式模式:请求体已在 buildAnthropicRequest 中包含 stream 字段\n const res = await fetch(req.url, {\n method: req.method,\n headers: req.headers,\n body: req.body,\n });\n\n if (!res.ok) {\n const errText = await res.text();\n throw new Error(`AI API ${res.status}: ${errText.slice(0, 500)}`);\n }\n\n const contentType = res.headers.get(\"content-type\") ?? \"\";\n if (contentType.includes(\"application/json\")) {\n const data = await res.json();\n return parseAnthropicResponse(data);\n }\n\n return parseAnthropicStream(res);\n },\n });\n this.config = config;\n }\n}\n\n// ─── 底层 API:请求构建 ───\n\n/**\n * 构建 Anthropic 请求(中)/ Build Anthropic Messages API request (EN).\n */\nexport function buildAnthropicRequest(\n config: AIClientConfig,\n params: ChatParams,\n): ChatRequestInit {\n const baseURL = resolveBaseURL(config);\n const { systemPrompt, messages, tools } = params;\n\n // 转换工具定义为 Anthropic 格式(input_schema 而非 parameters)\n const anthropicTools = tools?.map((t) => ({\n name: t.name,\n description: t.description,\n input_schema: cleanSchema(t.schema),\n }));\n\n // 转换消息为 Anthropic 格式(过滤掉 system 角色消息)\n const anthropicMessages = convertMessages(messages);\n\n // 构建请求体 — system 作为顶层字段\n const body: Record<string, unknown> = {\n model: config.model,\n max_tokens: config.model.includes(\"opus\") ? 16384 : 8192,\n system: systemPrompt,\n messages: anthropicMessages,\n };\n\n if (config.stream ?? true) {\n body.stream = true;\n }\n\n if (anthropicTools && anthropicTools.length > 0) {\n body.tools = anthropicTools;\n }\n\n return {\n url: `${baseURL}/v1/messages`,\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": config.apiKey,\n \"anthropic-version\": \"2023-06-01\",\n },\n body: JSON.stringify(body),\n };\n}\n\n// ─── 响应解析 ───\n\n/**\n * 解析 Anthropic 响应(中)/ Parse raw Anthropic response (EN).\n */\nexport function parseAnthropicResponse(data: unknown): AIChatResponse {\n const d = data as AnthropicRawResponse;\n\n // 提取所有文本块,合并为单个字符串\n const text = d.content\n ?.filter((b): b is AnthropicTextBlock => b.type === \"text\")\n .map((b) => b.text)\n .join(\"\");\n\n // 提取所有工具调用块\n const toolCalls: AIToolCall[] | undefined = d.content\n ?.filter((b): b is AnthropicToolUseBlock => b.type === \"tool_use\")\n .map((b) => ({\n id: b.id,\n name: b.name,\n input: b.input,\n }));\n\n return {\n text: text || undefined,\n toolCalls: toolCalls?.length ? toolCalls : undefined,\n usage: d.usage\n ? {\n inputTokens: d.usage.input_tokens,\n outputTokens: d.usage.output_tokens,\n }\n : undefined,\n };\n}\n\n// ─── 内部辅助函数 ───\n\n/**\n * 消息格式转换(中)/ Convert unified messages to Anthropic format (EN).\n */\nfunction convertMessages(\n messages: AIMessage[],\n): Record<string, unknown>[] {\n return messages\n .filter((m) => m.role !== \"system\")\n .map((m) => {\n if (m.role === \"tool\" && Array.isArray(m.content)) {\n // 工具结果 → Anthropic 用 user 角色 + tool_result content block\n return {\n role: \"user\" as const,\n content: m.content.map((tc) => ({\n type: \"tool_result\" as const,\n tool_use_id: tc.toolCallId,\n content: tc.result,\n })),\n };\n }\n if (m.role === \"assistant\" && m.toolCalls?.length) {\n // AI 回复含工具调用 → text block + tool_use blocks\n const content: Record<string, unknown>[] = [];\n if (m.content && typeof m.content === \"string\") {\n content.push({ type: \"text\", text: m.content });\n }\n for (const tc of m.toolCalls) {\n content.push({\n type: \"tool_use\",\n id: tc.id,\n name: tc.name,\n input: tc.input,\n });\n }\n return { role: \"assistant\" as const, content };\n }\n // 普通消息(user / assistant 纯文本)\n return {\n role: m.role as \"user\" | \"assistant\",\n content:\n typeof m.content === \"string\"\n ? m.content\n : JSON.stringify(m.content),\n };\n });\n}\n\n// ─── 流式响应解析 ───\n\n/**\n * 解析 Anthropic SSE(中)/ Parse Anthropic SSE stream (EN).\n */\nexport async function parseAnthropicStream(response: Response): Promise<AIChatResponse> {\n // 回退:无 ReadableStream 支持\n if (!response.body) {\n const data = await response.json();\n return parseAnthropicResponse(data);\n }\n\n let text = \"\";\n const toolCalls: AIToolCall[] = [];\n let currentToolUse: { id: string; name: string; inputJson: string } | null = null;\n let inputTokens = 0;\n let outputTokens = 0;\n await consumeSSEJSON(\n response,\n (event) => {\n switch (event.type) {\n case \"message_start\": {\n const msg = event.message as { usage?: { input_tokens?: number } } | undefined;\n inputTokens = msg?.usage?.input_tokens ?? 0;\n break;\n }\n\n case \"content_block_start\": {\n const block = event.content_block as { type: string; id?: string; name?: string } | undefined;\n if (block?.type === \"tool_use\") {\n currentToolUse = { id: block.id ?? \"\", name: block.name ?? \"\", inputJson: \"\" };\n }\n break;\n }\n\n case \"content_block_delta\": {\n const delta = event.delta as { type: string; text?: string; partial_json?: string } | undefined;\n if (delta?.type === \"text_delta\") {\n text += delta.text ?? \"\";\n } else if (delta?.type === \"input_json_delta\" && currentToolUse) {\n currentToolUse.inputJson += delta.partial_json ?? \"\";\n }\n break;\n }\n\n case \"content_block_stop\":\n if (currentToolUse) {\n try {\n toolCalls.push({\n id: currentToolUse.id,\n name: currentToolUse.name,\n input: JSON.parse(currentToolUse.inputJson || \"{}\"),\n });\n } catch {\n // 工具参数 JSON 解析失败,跳过\n }\n currentToolUse = null;\n }\n break;\n\n case \"message_delta\": {\n const deltaUsage = (event as { usage?: { output_tokens?: number } }).usage;\n outputTokens = deltaUsage?.output_tokens ?? 0;\n break;\n }\n }\n },\n { stopOnDone: false },\n );\n\n return {\n text: text || undefined,\n toolCalls: toolCalls.length > 0 ? toolCalls : undefined,\n usage: inputTokens > 0 || outputTokens > 0 ? { inputTokens, outputTokens } : undefined,\n };\n}\n","/**\n * DeepSeek 客户端封装(中)/ DeepSeek client wrapper (EN).\n *\n * DeepSeek 与 OpenAI Chat Completions 兼容,直接复用 OpenAIClient。\n * DeepSeek is OpenAI-compatible, so it reuses OpenAIClient behavior.\n */\nimport { OpenAIClient } from \"./openai.js\";\n\n/**\n * DeepSeek 客户端类(中)/ DeepSeek client class extending OpenAIClient (EN).\n */\nexport class DeepSeekClient extends OpenAIClient {}\n","/**\n * Doubao 客户端封装(中)/ Doubao client wrapper (EN).\n *\n * Doubao(火山引擎 Ark)与 OpenAI Chat Completions 兼容,直接复用 OpenAIClient。\n * Doubao (Volcengine Ark) is OpenAI-compatible, so it reuses OpenAIClient behavior.\n */\nimport { OpenAIClient } from \"./openai.js\";\n\n/**\n * Doubao 客户端类(中)/ Doubao client class extending OpenAIClient (EN).\n */\nexport class DoubaoClient extends OpenAIClient {}\n","/**\n * Qwen 客户端封装(中)/ Qwen client wrapper (EN).\n *\n * Qwen(阿里云百炼兼容模式)与 OpenAI Chat Completions 兼容,直接复用 OpenAIClient。\n * Qwen (DashScope compatible mode) is OpenAI-compatible, so it reuses OpenAIClient behavior.\n */\nimport { OpenAIClient } from \"./openai.js\";\n\n/**\n * Qwen 客户端类(中)/ Qwen client class extending OpenAIClient (EN).\n */\nexport class QwenClient extends OpenAIClient {}\n","/**\n * AI 客户端主入口(中)/ AI client entrypoint based on fetch (EN).\n *\n * 提供 provider 路由与统一类型导出。\n * Provides provider routing and unified type exports.\n */\nimport type { AIClient, AIChatResponse, AIMessage } from \"../types.js\";\nimport type { ToolDefinition } from \"../tool-registry.js\";\nimport { validateProvider } from \"./constants.js\";\nimport { OpenAIClient } from \"./openai.js\";\nimport { AnthropicClient } from \"./anthropic.js\";\nimport { DeepSeekClient } from \"./deepseek.js\";\nimport { DoubaoClient } from \"./doubao.js\";\nimport { QwenClient } from \"./qwen.js\";\n\n// Re-export 类型,方便外部统一从 ai-client 导入\nexport type { AIClient, AIChatResponse, AIMessage, AIToolCall } from \"../types.js\";\n\n// Re-export 客户端类(基类 + OpenAI + Anthropic)\nexport { BaseAIClient, type BaseAIClientOptions, type ChatHandlerParams } from \"./custom.js\";\nexport { OpenAIClient, parseOpenAIStream } from \"./openai.js\";\nexport { AnthropicClient, parseAnthropicStream } from \"./anthropic.js\";\nexport { DeepSeekClient } from \"./deepseek.js\";\nexport { DoubaoClient } from \"./doubao.js\";\nexport { QwenClient } from \"./qwen.js\";\n\n// ─── 公共类型定义 ───\n\n/** AI 客户端配置(中)/ AI client configuration (EN). */\nexport type AIClientConfig = {\n /** AI 提供商: \"openai\" | \"copilot\" | \"anthropic\" | \"deepseek\" | \"doubao\" | \"qwen\" */\n provider: string;\n /** 模型名称,如 \"gpt-4o\"、\"claude-sonnet-4-20250514\" */\n model: string;\n /** API Key / Token */\n apiKey: string;\n /** 自定义 API 基础 URL(可选,如本地 Ollama: http://localhost:11434/v1) */\n baseURL?: string;\n /** 是否启用流式输出(SSE)。默认 true;传 false 时使用 JSON 非流式响应。 */\n stream?: boolean;\n};\n\n/** 统一 chat 入参(中)/ Unified chat parameters (EN). */\nexport type ChatParams = {\n /** 系统提示词 */\n systemPrompt: string;\n /** 对话消息列表 */\n messages: AIMessage[];\n /** 可用工具定义列表 */\n tools?: ToolDefinition[];\n};\n\n/**\n * HTTP 请求对象(中)/ Built HTTP request init payload (EN).\n */\nexport type ChatRequestInit = {\n /** 请求 URL */\n url: string;\n /** HTTP 方法 */\n method: \"POST\";\n /** 请求头 */\n headers: Record<string, string>;\n /** 请求体(JSON 字符串) */\n body: string;\n};\n\n// ─── 高层 API ───\n\n/**\n * 创建 AI 客户端(中)/ Create AI client by provider (EN).\n */\nexport function createAIClient(config: AIClientConfig): AIClient {\n validateProvider(config.provider);\n\n switch (config.provider) {\n case \"openai\":\n case \"copilot\":\n return new OpenAIClient(config);\n case \"doubao\":\n return new DoubaoClient(config);\n case \"qwen\":\n return new QwenClient(config);\n case \"anthropic\":\n return new AnthropicClient(config);\n case \"deepseek\":\n return new DeepSeekClient(config);\n default:\n throw new Error(\n `Unknown AI provider: ${config.provider}. Supported: openai, copilot, anthropic, deepseek, doubao, qwen`,\n );\n }\n}\n","/**\n * Tool Registry — 工具注册表,负责工具的注册、查询和分发。\n *\n * 实例化设计 — 每个 Agent 创建独立的 ToolRegistry,避免全局状态污染:\n *\n * // Node 端\n * const registry = new ToolRegistry();\n * registerBuiltinTools(registry); // 注册 exec, file, browser...\n * await executeAgentLoop({ registry, ... });\n *\n * // Web 端\n * const registry = new ToolRegistry();\n * registerWebTools(registry); // 注册 dom, navigate...\n * await executeAgentLoop({ registry, ... });\n *\n * 优点:\n * - 多实例安全:Node Agent 和 Web Agent 可并行运行,工具列表互不干扰\n * - 测试隔离:每个 test case 创建独立 registry,无需清理全局状态\n * - 可组合:可按需注册不同工具子集\n */\nimport type { TObject } from \"@sinclair/typebox\";\nexport { jsonResult, readNumberParam, readStringParam } from \"./tool-params.js\";\n\n/**\n * 工具执行结果 — 每个工具的 execute() 必须返回此类型。\n */\nexport type ToolCallResult = {\n /** 返回内容(字符串文本或结构化对象,最终会序列化后发给 AI) */\n content: string | Record<string, unknown>;\n /** 可选的额外细节(用于日志记录、调试等,不直接发给 AI) */\n details?: Record<string, unknown>;\n};\n\n/**\n * 工具定义 — 注册工具时需要提供的完整描述。\n *\n * 这四个字段分别告诉 AI「叫什么名字」「能做什么」「需要什么参数」「怎么执行」:\n * - name + description → AI 根据用户意图选择合适的工具\n * - schema → AI 生成符合格式的参数 JSON\n * - execute → 实际执行逻辑\n */\nexport type ToolDefinition = {\n /** 工具名称(AI 通过此名称调用,如 \"exec\"、\"file_read\") */\n name: string;\n /** 工具描述(AI 据此判断何时使用这个工具) */\n description: string;\n /** 参数的 JSON Schema(TypeBox 定义,描述工具接受哪些参数及其类型) */\n schema: TObject;\n /** 执行函数 — 接收 AI 传入的参数,返回执行结果 */\n execute: (params: Record<string, unknown>) => Promise<ToolCallResult>;\n};\n\n/**\n * 工具注册表实例 — 管理一组工具的注册、查询和分发。\n *\n * 每个 Agent 拥有独立的 ToolRegistry 实例,从而:\n * - Node Agent 的 exec/file 工具不会泄漏到 Web Agent\n * - Web Agent 的 dom/navigate 工具不会泄漏到 Node Agent\n * - 测试中不同 case 互不影响\n */\nexport class ToolRegistry {\n private tools = new Map<string, ToolDefinition>();\n\n /** 注册一个工具 */\n register(tool: ToolDefinition): void {\n this.tools.set(tool.name, tool);\n }\n\n /** 获取所有已注册的工具定义列表(发给 AI,告知可用工具) */\n getDefinitions(): ToolDefinition[] {\n return Array.from(this.tools.values());\n }\n\n /** 按名称检查工具是否已注册。 */\n has(name: string): boolean {\n return this.tools.has(name);\n }\n\n /** 按名称注销工具,返回是否删除成功。 */\n unregister(name: string): boolean {\n return this.tools.delete(name);\n }\n\n /**\n * 根据工具名分发并执行工具调用。\n * - 找到工具 → 执行 execute() → 返回结果\n * - 找不到 → 返回错误信息(不抛异常,让 AI 知道工具不存在)\n * - 执行出错 → 捕获异常,返回错误信息(不中断 Agent 循环)\n */\n async dispatch(name: string, input: unknown): Promise<ToolCallResult> {\n const tool = this.tools.get(name);\n if (!tool) {\n return {\n content: `Unknown tool: ${name}`,\n details: { error: true, toolName: name },\n };\n }\n\n try {\n const params = (input ?? {}) as Record<string, unknown>;\n return await tool.execute(params);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return {\n content: `Tool \"${name}\" failed: ${message}`,\n details: { error: true, toolName: name, message },\n };\n }\n }\n}\n","/**\n * 极简系统提示词构建器。\n *\n * 纯函数,不依赖运行时环境;调用方只需传入工具定义和可选扩展指令。\n *\n * 职责:\n * - 组装发送给 AI 的 system prompt(英文正文)\n * - 包含核心规则、工具列表、事件简写表、输出协议\n * - 支持额外自定义指令注入\n *\n * 约束(来自 AGENTS.md §11):\n * - 发送给模型的 prompt 正文统一英文\n * - 中文仅用于源码注释\n *\n * 调用方:\n * - `agent-loop/index.ts` 在循环启动时调用 `buildSystemPrompt()` 构建系统消息\n * - `web/index.ts` 的 WebAgent 通过 systemPrompt 配置传入额外指令\n */\nimport type { ToolDefinition } from \"./tool-registry.js\";\n\n/**\n * 系统提示词构建参数。\n *\n * 所有字段可选:\n * - tools:当前注册的工具列表,用于生成 \"## Available Tools\" 章节\n * - thinkingLevel:AI 思考深度标签(如 \"high\"/\"medium\"),影响推理行为\n * - extraInstructions:额外英文指令,追加到 \"## Extra Instructions\" 章节\n */\nexport type SystemPromptParams = {\n /** 已注册工具列表。 */\n tools?: ToolDefinition[];\n /** AI 思考深度标签。 */\n thinkingLevel?: string;\n /** 额外英文指令(字符串或字符串数组)。 */\n extraInstructions?: string | string[];\n};\n\n/**\n * 规范化额外指令:统一转为非空字符串数组。\n *\n * - 单字符串 → 单元素数组\n * - 字符串数组 → 过滤空值\n * - undefined → 空数组\n */\nfunction normalizeExtraInstructions(input?: string | string[]): string[] {\n if (!input) return [];\n const rawList = Array.isArray(input) ? input : [input];\n return rawList.map(s => s.trim()).filter(Boolean);\n}\n\n/**\n * 构建系统提示词。\n *\n * 输出结构(按章节顺序):\n * 1. **Core Rules** — Agent 核心行为规则\n * - 快照驱动决策:仅基于当前快照 + 剩余任务工作\n * - 增量消费模型:每轮执行后输出 REMAINING 推进任务\n * - hash ID 定位:仅交互元素携带 #hashID,非交互元素为上下文\n * - 事件信号:listeners=\"...\" 标注运行时事件绑定\n * - 批量执行:同轮完成所有独立可见操作\n * - 输入顺序:fill/type 前必须先 focus/click 同一目标\n * - DOM 变化断轮:会改变 DOM 的动作执行后等待下一轮新快照\n * - 停机规则:任务完成后输出 REMAINING: DONE\n *\n * 2. **Listener Abbrevs** — 事件简写对照表\n * - 快照中 listeners=\"clk,inp,chg\" 的简写含义\n * - 与 page-info-tool.ts 的 EVENT_ABBREV 映射一致\n *\n * 3. **Output Contract** — 输出协议\n * - 每轮返回工具调用 + REMAINING 文本行\n *\n * 4. **Available Tools**(可选) — 当前注册的工具及描述\n *\n * 5. **Reasoning Profile**(可选) — 思考深度配置\n *\n * 6. **Extra Instructions**(可选) — 用户自定义额外指令\n *\n * @param params - 构建参数(工具列表、思考深度、额外指令)\n * @returns 完整的系统提示词字符串(英文)\n */\nexport function buildSystemPrompt(params: SystemPromptParams = {}): string {\n const sections: string[] = [];\n\n // ─── 章节 1:角色定义 + 核心规则 ───\n // 这是 prompt 最核心的部分,定义了 Agent 的行为模式和约束。\n // 规则按重要性排列,每条规则对应一个具体的行为约束。\n sections.push(\n [\n \"You are AutoPilot, an AI agent controlling the current web page via tools.\",\n \"\",\n \"## Core Rules\",\n\n // ── 快照驱动决策:不回顾历史,只看当前快照 + 当前剩余任务 ──\n \"- Work from CURRENT snapshot + CURRENT remaining task directly. Do not restate the request.\",\n\n // ── 增量消费模型:每轮输入 = (剩余任务, 上轮已执行, 本轮执行),输出 = 新的剩余任务 ──\n \"- Treat each round as task reduction:\",\n \" Input: (1) current remaining task, (2) previous round executed actions, (3) actions you execute this round.\",\n \" Output: new remaining task after removing this-round actions.\",\n\n // ── hash ID 定位:仅交互元素有 #hashID,非交互元素(标题/标签/文本)无 ID ──\n \"- Use only visible targets from snapshot. Use #hashID as selector. Do not guess CSS selectors.\",\n \"- Only interactive elements (with events, inputs, buttons, links, etc.) carry #hashID. Elements without #hashID are context-only (labels, headings, text) and cannot be targeted.\",\n\n // ── 角色优先标签:[combobox] 表示 role=\"combobox\" 的元素,标签已反映交互模式 ──\n \"- Snapshot tag in brackets may show ARIA role instead of HTML tag when it better describes the interaction pattern (e.g. [combobox] for input with role=\\\"combobox\\\", [slider] for div with role=\\\"slider\\\"). Treat the bracket tag as the primary interaction hint.\",\n\n // ── 事件信号:listeners=\"clk,inp\" 标注运行时事件绑定,辅助 AI 选择操作目标 ──\n \"- listeners=\\\"...\\\" on snapshot indicates bound event handlers (see Listener Abbrevs below). Prefer targets with relevant listeners when multiple candidates look similar.\",\n \"- Click targeting rule (MANDATORY): for click/navigation actions, prioritize elements with explicit click signals (listeners containing clk/pdn/mdn, onclick, native link/button semantics, or role=button/link).\",\n \"- Do NOT click focus/hover-only nodes for navigation (e.g. listeners only like fcs/blr/men/mlv without click-related signals). Treat those as context labels unless no better actionable target exists.\",\n \"- Correlation fallback: if a click produced no progress, in the next round choose the nearest actionable sibling/ancestor within the same semantic group (same row/card/form), such as adjacent repo path/link/button, instead of repeating the same ineffective target.\",\n\n // ── 批量执行:同轮完成所有独立可见操作,减少轮次消耗 ──\n \"- Batch independent visible actions in one round. Do not split one form into many rounds unnecessarily.\",\n\n // ── 输入顺序(强制):fill/type/select_option 前必须先 focus/click 同一目标 ──\n \"- Strict input order (MANDATORY): before every fill/type/select_option, click or focus the SAME target immediately in the SAME round.\",\n \"- Multi-field rule (MANDATORY): execute alternating pairs in one batch: focus/click field A -> fill/type A -> focus/click field B -> fill/type B.\",\n \"- Build the minimal action array from CURRENT snapshot to satisfy the target in one round whenever possible.\",\n \"- Do NOT run focus-only batches (e.g., focus A -> focus B). Each focused input/select target must be followed by its input/select action right away.\",\n \"- Fixed sequence examples: dom.focus(#name) -> dom.fill(#name, \\\"new-name\\\") -> dom.focus(#desc) -> dom.fill(#desc, \\\"new-desc\\\"); dom.click(#select) -> dom.select_option(#select, ...).\",\n\n // ── 步进器规则:计算目标差值,精确点击 |delta| 次 ──\n \"- Deterministic delta rule: for increase/decrease steppers, compute target delta from visible current value and emit exactly |delta| clicks in one round (e.g., +2 => click increase twice). Never overshoot then undo.\",\n\n // ── checkbox/radio:必须瞄准真实 input 控件,不要点 label/容器 ──\n \"- For check/uncheck, target the real input control (checkbox/radio), not nearby text/container nodes.\",\n\n // ── 表单批量规则:一个表单的所有独立字段应在同一轮填完 ──\n \"- Form batch rule: for one visible form, complete all independent fields in one round; do not fill one field then verify repeatedly.\",\n\n // ── DOM 变化断轮:会改变 DOM 的动作(弹窗/导航)执行后停止,等下一轮新快照 ──\n \"- If an action will change DOM (open modal, navigate), stop after that action batch and continue next round with new snapshot.\",\n\n // ── 禁止冗余快照调用:每轮已自动注入快照,不需要手动调用 page_info ──\n \"- Do NOT call page_info (snapshot/query/get_url/get_title). Snapshot is already provided every round.\",\n\n // ── 下拉选择:使用 dom.select_option 或 fill ──\n \"- For dropdown/select, use dom action=select_option (or fill on select).\",\n\n // ── children omitted 定向展开:输出 SNAPSHOT_HINT 请求展开被截断的子节点 ──\n \"- If a required list shows `... (N children omitted)` under a specific container, request focused expansion by outputting `SNAPSHOT_HINT: EXPAND_CHILDREN #<containerRef>`.\",\n \"- After outputting snapshot expansion hint, wait for the next refreshed snapshot before further scrolling/clicking on that list.\",\n\n // ── 验证白名单:除非用户明确要求,否则不验证 input/select 值 ──\n \"- Verification whitelist: do NOT use get_text/get_attr to verify input/select values unless the user explicitly asks for verification.\",\n\n // ── 停机规则:任务完成后立即输出 REMAINING: DONE,不做多余操作 ──\n \"- Stop rule: when the requested state is achieved, stop calling tools. If verification is requested, verify once and then return REMAINING: DONE (no repeated get_text/get_attr on the same target).\",\n\n // ── 自我隔离:不操作 AutoPilot 自身 UI ──\n \"- Do NOT interact with AutoPilot UI unless user explicitly asks.\",\n \"\",\n\n // ─── 章节 2:事件简写对照表 ───\n // 与 page-info-tool.ts 的 EVENT_ABBREV 映射保持一致。\n // AI 通过此表理解快照中 listeners=\"clk,inp,fcs\" 的含义。\n \"## Listener Abbrevs\",\n \"clk=click dbl=dblclick mdn=mousedown mup=mouseup mmv=mousemove mov=mouseover mot=mouseout men=mouseenter mlv=mouseleave pdn=pointerdown pup=pointerup pmv=pointermove tst=touchstart ted=touchend kdn=keydown kup=keyup inp=input chg=change sub=submit fcs=focus blr=blur scl=scroll whl=wheel drg=drag drs=dragstart dre=dragend drp=drop ctx=contextmenu\",\n \"\",\n\n // ─── 章节 3:输出协议 ───\n // 与 agent-loop/messages.ts 的 REMAINING 协议一致:\n // - 有剩余任务 → REMAINING: <剩余任务文本>\n // - 全部完成 → REMAINING: DONE\n \"## Output Contract\",\n \"- Return tool calls for this round.\",\n \"- Also include one plain text line:\",\n \" REMAINING: <new remaining task after this round>\",\n \" or REMAINING: DONE\",\n \"\",\n\n // ─── 章节 4:最小示例 ───\n // 帮助模型理解增量消费模型的具体执行方式。\n \"## Minimal Example\",\n \"Task: click button -> type \\\"abc\\\" in input -> send\",\n \"Round1 execute: click button\",\n \"Remaining: type \\\"abc\\\" in input -> send\",\n \"Round2 execute: type \\\"abc\\\" in input\",\n \"Remaining: send\",\n \"Round3 execute: send\",\n \"Remaining: DONE\",\n ].join(\"\\n\"),\n );\n\n // ─── 章节 5(可选):工具列表 ───\n // 列出当前注册的所有工具及其描述,供 AI 选择使用。\n const tools = params.tools ?? [];\n if (tools.length > 0) {\n const toolLines = tools.map(t => `- **${t.name}**: ${t.description}`);\n sections.push(\n \"## Available Tools\\n\\n\" +\n toolLines.join(\"\\n\") + \"\\n\\n\" +\n \"Use tools when needed to complete the user's request.\"\n );\n }\n\n // ─── 章节 6(可选):思考深度配置 ───\n // 影响模型的推理深度(如 \"high\" 表示复杂任务需深度思考)。\n if (params.thinkingLevel) {\n sections.push(\n [\n \"## Reasoning Profile\",\n `- Thinking level: ${params.thinkingLevel}`,\n ].join(\"\\n\"),\n );\n }\n\n // ─── 章节 7(可选):额外自定义指令 ───\n // 由 WebAgent 使用方通过 extraInstructions 配置传入。\n // 典型用途:业务特定规则、UI 框架提示、测试场景约束等。\n const extraInstructions = normalizeExtraInstructions(params.extraInstructions);\n if (extraInstructions.length > 0) {\n sections.push(\n [\n \"## Extra Instructions\",\n ...extraInstructions.map(line => `- ${line}`),\n ].join(\"\\n\"),\n );\n }\n\n return sections.join(\"\\n\\n\");\n}\n","/**\n * 全局事件监听追踪器(浏览器端)。\n *\n * 设计目标:\n * 1. 从 EventTarget.prototype 统一拦截 add/removeEventListener\n * 2. 仅记录 Element 实例,避免污染 document/window 等\n * 3. 不改变原调用语义:先执行原方法,再做追踪记录\n * 4. 追踪失败时静默兜底,不影响业务代码执行\n */\n\ntype AddEventListenerFn = EventTarget[\"addEventListener\"];\ntype RemoveEventListenerFn = EventTarget[\"removeEventListener\"];\n\nconst elementEventMap = new WeakMap<Element, Set<string>>();\n\nlet installed = false;\nlet originalAddEventListener: AddEventListenerFn | undefined;\nlet originalRemoveEventListener: RemoveEventListenerFn | undefined;\n\nfunction normalizeEventType(type: unknown): string | null {\n if (typeof type !== \"string\") return null;\n const normalized = type.trim().toLowerCase();\n return normalized || null;\n}\n\nfunction canTrackElementTarget(target: EventTarget): target is Element {\n if (typeof Element === \"undefined\") return false;\n return target instanceof Element;\n}\n\nfunction trackElementEvent(target: EventTarget, type: string): void {\n if (!canTrackElementTarget(target)) return;\n const prev = elementEventMap.get(target);\n if (prev) {\n prev.add(type);\n return;\n }\n elementEventMap.set(target, new Set([type]));\n}\n\nfunction untrackElementEvent(target: EventTarget, type: string): void {\n if (!canTrackElementTarget(target)) return;\n const prev = elementEventMap.get(target);\n if (!prev) return;\n prev.delete(type);\n if (prev.size === 0) {\n elementEventMap.delete(target);\n }\n}\n\n/**\n * 安装全局监听追踪补丁(幂等)。\n */\nexport function installEventListenerTracking(): void {\n if (installed) return;\n if (typeof EventTarget === \"undefined\") return;\n\n const proto = EventTarget.prototype;\n const nativeAdd = proto.addEventListener;\n const nativeRemove = proto.removeEventListener;\n\n if (typeof nativeAdd !== \"function\" || typeof nativeRemove !== \"function\") return;\n\n originalAddEventListener = nativeAdd;\n originalRemoveEventListener = nativeRemove;\n\n proto.addEventListener = function patchedAddEventListener(\n this: EventTarget,\n type: string,\n listener: EventListenerOrEventListenerObject | null,\n options?: boolean | AddEventListenerOptions,\n ): void {\n originalAddEventListener?.call(this, type, listener, options);\n try {\n const normalizedType = normalizeEventType(type);\n if (!normalizedType || listener == null) return;\n trackElementEvent(this, normalizedType);\n } catch {\n // 追踪失败不应影响业务逻辑\n }\n };\n\n proto.removeEventListener = function patchedRemoveEventListener(\n this: EventTarget,\n type: string,\n listener: EventListenerOrEventListenerObject | null,\n options?: boolean | EventListenerOptions,\n ): void {\n originalRemoveEventListener?.call(this, type, listener, options);\n try {\n const normalizedType = normalizeEventType(type);\n if (!normalizedType || listener == null) return;\n untrackElementEvent(this, normalizedType);\n } catch {\n // 追踪失败不应影响业务逻辑\n }\n };\n\n installed = true;\n}\n\n/**\n * 读取元素已记录的事件名(排序后返回,便于稳定输出)。\n */\nexport function getTrackedElementEvents(el: Element): string[] {\n const set = elementEventMap.get(el);\n if (!set || set.size === 0) return [];\n return Array.from(set).sort();\n}\n\n/**\n * 判断元素是否存在至少一个被追踪到的事件绑定。\n */\nexport function hasTrackedElementEvents(el: Element): boolean {\n return (elementEventMap.get(el)?.size ?? 0) > 0;\n}\n","/**\n * DOM Tool — 浏览器 DOM 操作工具(结合 Playwright 核心交互模式增强)。\n *\n * 关键改进(参考 Playwright):\n * 1. retarget — 点击时自动重定向到 button/link/label.control\n * 2. scrollIntoView 多策略 — 4 种 block 对齐轮换,解决 sticky 遮挡\n * 3. stable 检查 — rAF 逐帧检测元素位置稳定后再操作\n * 4. hit-target 验证 — elementsFromPoint 检查是否被遮挡\n * 5. 完整点击事件链 — pointermove→pointerdown→mousedown→pointerup→mouseup→click\n * 6. check/uncheck 通过 click — 先检查→click 切换→验证状态\n * 7. press 组合键 — 支持 Control+a, Shift+Enter 等修饰键\n * 8. fill 分类型 — date/color/range 走 setValue,text 类走 selectAll+原生写入\n * 9. 自定义下拉增强 — 更广泛的 option 选择器 + 等待弹出\n * 10. ARIA disabled — 检查祖先链 aria-disabled\n *\n * 运行环境:浏览器 Content Script(直接访问 DOM,无 CDP)。\n */\nimport { Type } from \"@sinclair/typebox\";\nimport type { ToolDefinition, ToolCallResult } from \"../../core/tool-registry.js\";\nimport type { RefStore } from \"../ref-store.js\";\nimport { getTrackedElementEvents } from \"../event-listener-tracker.js\";\n\n// ─── 常量 ───\n\nconst DEFAULT_WAIT_MS = 1200;\n\n/** scrollIntoView 轮换策略(参考 Playwright dom.ts) */\nconst SCROLL_OPTIONS: (ScrollIntoViewOptions | undefined)[] = [\n undefined,\n { block: \"end\", inline: \"end\" },\n { block: \"center\", inline: \"center\" },\n { block: \"start\", inline: \"start\" },\n];\n\n/** fill 时直接 setValue 的 input 类型(参考 Playwright kInputTypesToSetValue) */\nconst INPUT_SET_VALUE_TYPES = new Set([\n \"color\", \"date\", \"time\", \"datetime-local\", \"month\", \"range\", \"week\",\n]);\n\n/** fill 时走 selectText+写入 的 input 类型 */\nconst INPUT_TYPE_INTO_TYPES = new Set([\n \"\", \"email\", \"number\", \"password\", \"search\", \"tel\", \"text\", \"url\",\n]);\n\n/** 不可 fill 的 input 类型 */\nconst INPUT_BLOCKED_TYPES = new Set([\n \"checkbox\", \"radio\", \"file\", \"button\", \"submit\", \"reset\", \"image\",\n]);\n\n/** 修饰键集合 */\nconst MODIFIER_KEYS = new Set([\"Control\", \"Shift\", \"Alt\", \"Meta\"]);\n\n/** 键名→code 映射 */\nconst KEY_CODE_MAP: Record<string, string> = {\n Enter: \"Enter\", Escape: \"Escape\", Esc: \"Escape\",\n Tab: \"Tab\", Space: \"Space\", \" \": \"Space\",\n Backspace: \"Backspace\", Delete: \"Delete\",\n ArrowUp: \"ArrowUp\", ArrowDown: \"ArrowDown\",\n ArrowLeft: \"ArrowLeft\", ArrowRight: \"ArrowRight\",\n Home: \"Home\", End: \"End\", PageUp: \"PageUp\", PageDown: \"PageDown\",\n Control: \"ControlLeft\", Shift: \"ShiftLeft\", Alt: \"AltLeft\", Meta: \"MetaLeft\",\n};\n\nconst FILL_RELEVANT_EVENTS = new Set([\n \"input\", \"change\", \"focus\", \"blur\", \"keydown\",\n \"click\", \"mousedown\", \"pointerdown\",\n]);\n\n// ─── 模块状态 ───\n\nlet activeRefStore: RefStore | undefined;\n\nexport function setActiveRefStore(store: RefStore | undefined): void {\n activeRefStore = store;\n}\n\nexport function getActiveRefStore(): RefStore | undefined {\n return activeRefStore;\n}\n\n// ─── 基础工具 ───\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n\n/** 查询元素:优先 RefStore hash,回退 CSS 选择器 */\nfunction queryElement(selector: string): Element | string {\n try {\n if (selector.startsWith(\"#\") && activeRefStore) {\n const id = selector.slice(1);\n if (activeRefStore.has(id)) {\n const el = activeRefStore.get(id);\n if (!el) return `未找到 ref \"${selector}\" 对应的元素(可能已被移除或快照已过期)`;\n return el;\n }\n }\n const el = document.querySelector(selector);\n if (!el) return `未找到匹配 \"${selector}\" 的元素`;\n return el;\n } catch {\n return `选择器语法错误: ${selector}`;\n }\n}\n\n/** 轮询等待元素出现 */\nasync function waitForElement(selector: string, timeoutMs: number): Promise<Element | string | null> {\n const start = Date.now();\n while (Date.now() - start <= timeoutMs) {\n const r = queryElement(selector);\n if (typeof r !== \"string\") return r;\n if (r.startsWith(\"选择器语法错误\")) return r;\n await sleep(100);\n }\n return null;\n}\n\nfunction resolveWaitMs(params: Record<string, unknown>): number {\n const waitMs = params.waitMs;\n if (typeof waitMs === \"number\" && Number.isFinite(waitMs)) return Math.max(0, Math.floor(waitMs));\n const waitSeconds = params.waitSeconds;\n if (typeof waitSeconds === \"number\" && Number.isFinite(waitSeconds)) return Math.max(0, Math.floor(waitSeconds * 1000));\n return DEFAULT_WAIT_MS;\n}\n\n// ─── 可见性判定(参考 Playwright domUtils.ts) ───\n\n/** 检查元素样式可见性(处理 checkVisibility / details 折叠 / visibility) */\nfunction isStyleVisible(el: Element, style?: CSSStyleDeclaration): boolean {\n style = style ?? window.getComputedStyle(el);\n if (typeof el.checkVisibility === \"function\") {\n if (!el.checkVisibility()) return false;\n } else {\n const det = el.closest(\"details,summary\");\n if (det !== el && det?.nodeName === \"DETAILS\" && !(det as HTMLDetailsElement).open) return false;\n }\n return style.visibility === \"visible\";\n}\n\n/**\n * 元素可见性检查(参考 Playwright isElementVisible+computeBox)。\n * 处理 display:contents / display:none / visibility / opacity / 尺寸为 0。\n */\nfunction isElementVisible(el: Element): boolean {\n if (!(el instanceof HTMLElement || el instanceof SVGElement)) return false;\n if (!el.isConnected) return false;\n const style = window.getComputedStyle(el);\n\n if (style.display === \"contents\") {\n for (let child = el.firstChild; child; child = child.nextSibling) {\n if (child.nodeType === Node.ELEMENT_NODE && isElementVisible(child as Element)) return true;\n if (child.nodeType === Node.TEXT_NODE) {\n const range = document.createRange();\n range.selectNodeContents(child);\n const rects = range.getClientRects();\n for (let i = 0; i < rects.length; i++) {\n if (rects[i].width > 0 && rects[i].height > 0) return true;\n }\n }\n }\n return false;\n }\n if (style.display === \"none\") return false;\n if (!isStyleVisible(el, style)) return false;\n if (style.opacity === \"0\") return false;\n const rect = el.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n}\n\n// ─── disabled / editable 检查(参考 Playwright) ───\n\n/** ARIA disabled:检查元素自身 + 祖先链 aria-disabled(参考 Playwright getAriaDisabled) */\nfunction isElementDisabled(el: Element): boolean {\n if (el instanceof HTMLButtonElement || el instanceof HTMLInputElement ||\n el instanceof HTMLSelectElement || el instanceof HTMLTextAreaElement) {\n if ((el as HTMLButtonElement).disabled) return true;\n }\n let cursor: Element | null = el;\n while (cursor) {\n if (cursor.getAttribute(\"aria-disabled\") === \"true\") return true;\n cursor = cursor.parentElement;\n }\n return false;\n}\n\nfunction isEditableElement(el: Element): boolean {\n if (el instanceof HTMLTextAreaElement) return !el.readOnly;\n if (el instanceof HTMLInputElement) {\n return !INPUT_BLOCKED_TYPES.has(el.type) && !el.readOnly;\n }\n if (el instanceof HTMLSelectElement) return true;\n return el instanceof HTMLElement && el.isContentEditable;\n}\n\n// ─── 稳定性检查(参考 Playwright _checkElementIsStable) ───\n\n/** rAF 逐帧检查元素位置是否连续 3 帧不变 */\nfunction checkElementStable(el: Element, timeoutMs = 800): Promise<boolean> {\n return new Promise((resolve) => {\n let lastRect: DOMRect | undefined;\n let stableCount = 0;\n const start = performance.now();\n function check() {\n if (performance.now() - start > timeoutMs || !el.isConnected) { resolve(false); return; }\n const rect = el.getBoundingClientRect();\n if (lastRect) {\n const same = rect.x === lastRect.x && rect.y === lastRect.y &&\n rect.width === lastRect.width && rect.height === lastRect.height;\n if (!same) { stableCount = 0; } else if (++stableCount >= 3) { resolve(true); return; }\n }\n lastRect = rect;\n requestAnimationFrame(check);\n }\n requestAnimationFrame(check);\n });\n}\n\n// ─── retarget(参考 Playwright injectedScript.retarget) ───\n\ntype RetargetMode = \"none\" | \"follow-label\" | \"button-link\";\n\n/**\n * 将目标重定向到关联的交互控件。\n * - button-link:非交互元素→最近 button/[role=button]/a/[role=link]\n * - follow-label:label→control + 非交互→button/[role=button]/[role=checkbox]/[role=radio]\n */\nfunction retarget(el: Element, mode: RetargetMode): Element {\n if (mode === \"none\") return el;\n if (!el.matches(\"input, textarea, select\") && !(el as HTMLElement).isContentEditable) {\n if (mode === \"button-link\") {\n el = el.closest(\"button, [role=button], a, [role=link]\") || el;\n } else {\n el = el.closest(\"button, [role=button], [role=checkbox], [role=radio]\") || el;\n }\n }\n if (mode === \"follow-label\") {\n if (!el.matches(\"a, input, textarea, button, select, [role=link], [role=button], [role=checkbox], [role=radio]\") &&\n !(el as HTMLElement).isContentEditable) {\n const label = el.closest(\"label\") as HTMLLabelElement | null;\n if (label?.control) el = label.control;\n }\n }\n return el;\n}\n\n// ─── scrollIntoView(参考 Playwright 4 种策略轮换) ───\n\nfunction scrollIntoViewIfNeeded(el: Element, retry = 0): void {\n if (retry === 0 && \"scrollIntoViewIfNeeded\" in el) {\n (el as HTMLElement & { scrollIntoViewIfNeeded: (c?: boolean) => void }).scrollIntoViewIfNeeded(true);\n return;\n }\n const opts = SCROLL_OPTIONS[retry % SCROLL_OPTIONS.length];\n el.scrollIntoView(opts ?? { block: \"center\", inline: \"nearest\" });\n}\n\n// ─── hit-target 检查 ───\n\n/** 检查元素中心点是否被遮挡,返回遮挡元素描述或 null */\nfunction checkHitTarget(el: Element): string | null {\n const rect = el.getBoundingClientRect();\n const x = rect.left + rect.width / 2;\n const y = rect.top + rect.height / 2;\n const topEl = document.elementFromPoint(x, y);\n if (!topEl) return null;\n if (topEl === el || el.contains(topEl) || topEl.contains(el)) return null;\n const sharedLabel = topEl.closest(\"label\");\n if (sharedLabel && sharedLabel.contains(el)) return null;\n return describeElement(topEl);\n}\n\n// ─── actionability 综合检查 ───\n\nfunction ensureActionable(el: Element, action: string, selector: string, force: boolean): ToolCallResult | null {\n if (force) return null;\n if (!el.isConnected) {\n return { content: `\"${selector}\" 元素已脱离文档,无法执行 ${action}`, details: { error: true, code: \"ELEMENT_DETACHED\", action, selector } };\n }\n const readOnlyActions = new Set([\"get_text\", \"get_attr\"]);\n if (!readOnlyActions.has(action) && !isElementVisible(el)) {\n return { content: `\"${selector}\" 元素不可见,无法执行 ${action}`, details: { error: true, code: \"ELEMENT_NOT_VISIBLE\", action, selector } };\n }\n const mutationActions = new Set([\"click\", \"fill\", \"type\", \"press\", \"select_option\", \"clear\", \"check\", \"uncheck\"]);\n if (mutationActions.has(action) && isElementDisabled(el)) {\n return { content: `\"${selector}\" 元素已禁用(disabled/aria-disabled),无法执行 ${action}`, details: { error: true, code: \"ELEMENT_DISABLED\", action, selector } };\n }\n if ([\"fill\", \"type\", \"clear\"].includes(action) && !isEditableElement(el)) {\n // 允许 fill 作用于 role=slider(后续在 fill 分支做专门处理)\n if (action === \"fill\" && el.getAttribute(\"role\") === \"slider\") {\n return null;\n }\n return { content: `\"${selector}\" 不是可编辑元素,无法执行 ${action}`, details: { error: true, code: \"UNSUPPORTED_FILL_TARGET\", action, selector } };\n }\n return null;\n}\n\n/**\n * 为 role=slider 查找关联的数值输入框。\n * 典型场景:Element Plus slider + input-number 同属一个 form-item。\n */\nfunction findAssociatedSliderInput(slider: Element): HTMLInputElement | null {\n const candidates: Element[] = [];\n\n const formItem = slider.closest(\".el-form-item\");\n if (formItem) candidates.push(formItem);\n\n let cursor: Element | null = slider.parentElement;\n for (let depth = 0; cursor && depth < 4; depth++, cursor = cursor.parentElement) {\n candidates.push(cursor);\n }\n\n for (const scope of candidates) {\n const input = scope.querySelector(\n 'input[type=\"number\"], input[role=\"spinbutton\"], .el-input-number input:not([type=\"hidden\"])',\n );\n if (input instanceof HTMLInputElement && isEditableElement(input) && isElementVisible(input)) {\n return input;\n }\n }\n return null;\n}\n\n// ─── 事件派发(参考 Playwright input.ts 事件链) ───\n\nfunction getClickPoint(el: Element): { x: number; y: number } {\n const r = el.getBoundingClientRect();\n return { x: r.left + r.width / 2, y: r.top + r.height / 2 };\n}\n\n/**\n * 完整点击事件链(参考 Playwright Mouse.click):\n * pointermove → mousemove → (per clickCount) pointerdown → mousedown → focus → pointerup → mouseup → click\n */\nfunction dispatchClickEvents(el: HTMLElement, clickCount = 1): void {\n const { x, y } = getClickPoint(el);\n const base: MouseEventInit = { bubbles: true, cancelable: true, view: window, clientX: x, clientY: y, button: 0 };\n\n el.dispatchEvent(new PointerEvent(\"pointermove\", { ...base, pointerId: 1 }));\n el.dispatchEvent(new MouseEvent(\"mousemove\", base));\n\n for (let cc = 1; cc <= clickCount; cc++) {\n el.dispatchEvent(new PointerEvent(\"pointerdown\", { ...base, detail: cc, buttons: 1, pointerId: 1 }));\n el.dispatchEvent(new MouseEvent(\"mousedown\", { ...base, detail: cc, buttons: 1 }));\n if (cc === 1 && el !== document.activeElement) el.focus({ preventScroll: true });\n el.dispatchEvent(new PointerEvent(\"pointerup\", { ...base, detail: cc, pointerId: 1 }));\n el.dispatchEvent(new MouseEvent(\"mouseup\", { ...base, detail: cc }));\n el.dispatchEvent(new MouseEvent(\"click\", { ...base, detail: cc }));\n }\n}\n\n/** hover 事件链 */\nfunction dispatchHoverEvents(el: HTMLElement): void {\n const { x, y } = getClickPoint(el);\n const base: MouseEventInit = { bubbles: true, cancelable: true, view: window, clientX: x, clientY: y };\n el.dispatchEvent(new PointerEvent(\"pointerenter\", { ...base, bubbles: false }));\n el.dispatchEvent(new MouseEvent(\"mouseenter\", { ...base, bubbles: false }));\n el.dispatchEvent(new PointerEvent(\"pointermove\", { ...base, pointerId: 1 }));\n el.dispatchEvent(new MouseEvent(\"mousemove\", base));\n el.dispatchEvent(new MouseEvent(\"mouseover\", base));\n}\n\n/** 派发 input + change 事件(兼容 React/Vue 受控组件) */\nfunction dispatchInputEvents(el: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement): void {\n el.dispatchEvent(new Event(\"input\", { bubbles: true, composed: true }));\n el.dispatchEvent(new Event(\"change\", { bubbles: true }));\n}\n\n/** 原生 setter 写入表单值(绕过 React/Vue getter/setter 拦截) */\nfunction setNativeValue(el: HTMLInputElement | HTMLTextAreaElement, value: string): void {\n const proto = el instanceof HTMLInputElement ? HTMLInputElement.prototype : HTMLTextAreaElement.prototype;\n const desc = Object.getOwnPropertyDescriptor(proto, \"value\");\n if (desc?.set) desc.set.call(el, value);\n else el.value = value;\n}\n\nfunction getFillEventSupportScore(el: Element): number {\n let score = 0;\n\n if (el.hasAttribute(\"oninput\") || el.hasAttribute(\"onchange\")) score += 80;\n if (el.hasAttribute(\"onfocus\") || el.hasAttribute(\"onblur\")) score += 60;\n if (el.hasAttribute(\"onclick\")) score += 40;\n\n const tracked = getTrackedElementEvents(el);\n for (const eventName of tracked) {\n if (!FILL_RELEVANT_EVENTS.has(eventName)) continue;\n if (eventName === \"input\") score += 40;\n else if (eventName === \"change\") score += 35;\n else if (eventName === \"focus\" || eventName === \"blur\") score += 28;\n else if (eventName === \"keydown\") score += 24;\n else score += 14;\n }\n\n return score;\n}\n\nfunction isCandidateFillTarget(el: Element): boolean {\n if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) {\n return !isElementDisabled(el);\n }\n if (el instanceof HTMLElement && el.isContentEditable) return true;\n return false;\n}\n\nfunction executeFillOnResolvedTarget(\n target: Element,\n value: string,\n selector: string,\n action: string,\n sourceHint?: string,\n): ToolCallResult | null {\n if (target instanceof HTMLInputElement) {\n const type = target.type.toLowerCase();\n if (INPUT_BLOCKED_TYPES.has(type)) {\n return { content: `\"${selector}\" 为 input[type=${type}],不支持 fill;请使用 click/check 等动作。`, details: { error: true, code: \"UNSUPPORTED_FILL_TARGET\", action, selector } };\n }\n if (INPUT_SET_VALUE_TYPES.has(type)) {\n const finalVal = type === \"color\" ? value.toLowerCase().trim() : value.trim();\n target.focus();\n target.value = finalVal;\n if (target.value !== finalVal) {\n return { content: `\"${selector}\" 填写格式不匹配(type=${type})`, details: { error: true, code: \"MALFORMED_VALUE\", action, selector } };\n }\n dispatchInputEvents(target);\n const suffix = sourceHint ? `(${sourceHint})` : \"\";\n return { content: `已填写 ${describeElement(target)}: \"${finalVal}\"${suffix}` };\n }\n if (type === \"number\" && Number.isNaN(Number(value.trim()))) {\n return { content: `\"${selector}\" 为 input[type=number],无法填写非数字 \"${value}\"`, details: { error: true, code: \"INVALID_NUMBER\", action, selector } };\n }\n scrollIntoViewIfNeeded(target);\n target.focus();\n selectText(target);\n setNativeValue(target, value);\n dispatchInputEvents(target);\n if (target.value !== value) {\n return { content: `\"${selector}\" 填写后值不一致:期望 \"${value}\",实际 \"${target.value}\"`, details: { error: true, code: \"FILL_NOT_APPLIED\", action, selector } };\n }\n const suffix = sourceHint ? `(${sourceHint})` : \"\";\n return { content: `已填写 ${describeElement(target)}: \"${value}\"${suffix}` };\n }\n\n if (target instanceof HTMLTextAreaElement) {\n scrollIntoViewIfNeeded(target);\n target.focus();\n selectText(target);\n setNativeValue(target, value);\n dispatchInputEvents(target);\n const suffix = sourceHint ? `(${sourceHint})` : \"\";\n return { content: `已填写 ${describeElement(target)}: \"${value}\"${suffix}` };\n }\n\n if (target instanceof HTMLSelectElement) {\n target.focus();\n const options = Array.from(target.options);\n let matched = options.find(o => o.value === value);\n if (!matched) {\n const normalized = value.trim().toLowerCase();\n matched = options.find(o => o.text.trim().toLowerCase() === normalized);\n }\n if (!matched) return { content: `\"${selector}\" 下拉框中不存在选项 \"${value}\"` };\n target.value = matched.value;\n dispatchInputEvents(target);\n const suffix = sourceHint ? `(${sourceHint})` : \"\";\n return { content: `已填写 ${describeElement(target)}: \"${value}\"${suffix}` };\n }\n\n if (target instanceof HTMLElement && target.isContentEditable) {\n target.focus();\n selectText(target);\n if (value) document.execCommand(\"insertText\", false, value);\n else document.execCommand(\"delete\", false, undefined);\n const suffix = sourceHint ? `(${sourceHint})` : \"\";\n return { content: `已填写 ${describeElement(target)}: \"${value}\"${suffix}` };\n }\n\n return null;\n}\n\nfunction guessNearbyFillTarget(anchor: Element, value: string): Element | null {\n const preferNumeric = Number.isFinite(Number(value));\n const scopeEntries: Array<{ scope: Element; level: number }> = [];\n\n const formItem = anchor.closest(\".el-form-item\");\n if (formItem) scopeEntries.push({ scope: formItem, level: 0 });\n\n let cursor: Element | null = anchor.parentElement;\n for (let level = 1; cursor && level <= 4; level++, cursor = cursor.parentElement) {\n scopeEntries.push({ scope: cursor, level });\n }\n\n const visited = new Set<Element>();\n let best: { el: Element; score: number } | null = null;\n\n for (const { scope, level } of scopeEntries) {\n const candidates = Array.from(scope.querySelectorAll(\n 'input:not([type=\"hidden\"]), textarea, select, [contenteditable=\"true\"], [role=\"spinbutton\"]',\n ));\n\n for (const candidate of candidates) {\n if (!(candidate instanceof Element)) continue;\n if (visited.has(candidate)) continue;\n visited.add(candidate);\n\n if (!isCandidateFillTarget(candidate)) continue;\n if (!isElementVisible(candidate)) continue;\n\n let score = 100 - level * 18;\n score += getFillEventSupportScore(candidate);\n\n if (candidate instanceof HTMLInputElement) {\n const type = candidate.type.toLowerCase();\n if (preferNumeric && (type === \"number\" || candidate.getAttribute(\"role\") === \"spinbutton\")) score += 80;\n if (!preferNumeric && [\"text\", \"\", \"search\", \"email\", \"tel\", \"url\", \"password\"].includes(type)) score += 36;\n }\n\n if (candidate.getAttribute(\"placeholder\")) score += 8;\n if (candidate.getAttribute(\"aria-label\")) score += 8;\n\n if (!best || score > best.score) {\n best = { el: candidate, score };\n }\n }\n }\n\n return best?.el ?? null;\n}\n\n// ─── selectText(参考 Playwright:input/textarea/contenteditable 三种策略) ───\n\nfunction selectText(el: Element): void {\n if (el instanceof HTMLInputElement) { el.select(); el.focus(); return; }\n if (el instanceof HTMLTextAreaElement) { el.selectionStart = 0; el.selectionEnd = el.value.length; el.focus(); return; }\n const range = document.createRange();\n range.selectNodeContents(el);\n const sel = window.getSelection();\n if (sel) { sel.removeAllRanges(); sel.addRange(range); }\n if (el instanceof HTMLElement) el.focus();\n}\n\n// ─── 键盘:组合键支持(参考 Playwright Keyboard.press) ───\n\nfunction splitKeyCombo(key: string): string[] {\n const tokens = key.split(\"+\");\n for (let i = 0; i < tokens.length; i++) {\n if (tokens[i] === \"\" && i + 1 < tokens.length) { tokens[i + 1] = \"+\" + tokens[i + 1]; tokens.splice(i, 1); }\n }\n return tokens.filter(Boolean);\n}\n\nfunction resolveKeyCode(key: string): string {\n return KEY_CODE_MAP[key] ?? (key.length === 1 ? `Key${key.toUpperCase()}` : key);\n}\n\n/**\n * 执行 press:修饰键按正序 down → 主键 down/up → 修饰键逆序 up(参考 Playwright)。\n * 修饰键按下时抑制文本输入(只发 keydown/keyup,不发 keypress)。\n */\nfunction executePress(el: Element, key: string): void {\n const tokens = splitKeyCombo(key);\n const mainKey = tokens[tokens.length - 1];\n const mods = tokens.slice(0, -1);\n const modState = {\n ctrlKey: mods.includes(\"Control\"),\n shiftKey: mods.includes(\"Shift\"),\n altKey: mods.includes(\"Alt\"),\n metaKey: mods.includes(\"Meta\"),\n };\n const hasNonShiftMod = modState.ctrlKey || modState.altKey || modState.metaKey;\n\n for (const m of mods) {\n el.dispatchEvent(new KeyboardEvent(\"keydown\", { key: m, code: resolveKeyCode(m), bubbles: true, cancelable: true, ...modState }));\n }\n const allowed = el.dispatchEvent(new KeyboardEvent(\"keydown\", { key: mainKey, code: resolveKeyCode(mainKey), bubbles: true, cancelable: true, ...modState }));\n // 只有无非 Shift 修饰键且是单字符时才发 keypress(参考 Playwright 文本抑制逻辑)\n if (allowed && mainKey.length === 1 && !hasNonShiftMod) {\n el.dispatchEvent(new KeyboardEvent(\"keypress\", { key: mainKey, code: resolveKeyCode(mainKey), bubbles: true, cancelable: true, ...modState }));\n }\n el.dispatchEvent(new KeyboardEvent(\"keyup\", { key: mainKey, code: resolveKeyCode(mainKey), bubbles: true, cancelable: true, ...modState }));\n for (let i = mods.length - 1; i >= 0; i--) {\n el.dispatchEvent(new KeyboardEvent(\"keyup\", { key: mods[i], code: resolveKeyCode(mods[i]), bubbles: true, cancelable: true, ...modState }));\n }\n}\n\n// ─── 元素描述 ───\n\nfunction describeElement(el: Element): string {\n const tag = el.tagName.toLowerCase();\n const id = el.id ? `#${el.id}` : \"\";\n const cls = el.className && typeof el.className === \"string\"\n ? el.className.trim().split(/\\s+/).filter(Boolean).slice(0, 3).map(c => `.${c}`).join(\"\") : \"\";\n const text = el instanceof HTMLSelectElement\n ? el.selectedOptions[0]?.textContent?.trim().slice(0, 40) ?? \"\"\n : el.textContent?.trim().slice(0, 40) ?? \"\";\n const textHint = text ? ` \"${text}\"` : \"\";\n const hints: string[] = [];\n for (const attr of [\"type\", \"name\", \"placeholder\", \"href\", \"role\"]) {\n const v = el.getAttribute(attr);\n if (v) hints.push(`${attr}=${v}`);\n }\n if (el instanceof HTMLSelectElement && el.value) hints.push(`val=${el.value}`);\n const attrHint = hints.length > 0 ? ` [${hints.join(\", \")}]` : \"\";\n return `<${tag}${id}${cls}>${textHint}${attrHint}`;\n}\n\n// ─── checkable 目标归一化 ───\n\nfunction isCheckableInput(el: Element | null): el is HTMLInputElement {\n return el instanceof HTMLInputElement && (el.type === \"checkbox\" || el.type === \"radio\");\n}\n\nfunction getChecked(el: Element): boolean | \"error\" {\n if (el instanceof HTMLInputElement && (el.type === \"checkbox\" || el.type === \"radio\")) return el.checked;\n const role = el.getAttribute(\"role\");\n if (role === \"checkbox\" || role === \"radio\" || role === \"switch\") return el.getAttribute(\"aria-checked\") === \"true\";\n return \"error\";\n}\n\n/**\n * 归一化 check/uncheck 目标:允许命中文本容器/label/div,回溯到关联 checkbox/radio。\n */\nfunction resolveCheckableTarget(el: Element): Element {\n if (getChecked(el) !== \"error\") return el;\n if (el instanceof HTMLLabelElement && el.control && getChecked(el.control) !== \"error\") return el.control;\n const ownerLabel = el.closest(\"label\") as HTMLLabelElement | null;\n if (ownerLabel?.control && getChecked(ownerLabel.control) !== \"error\") return ownerLabel.control;\n const inner = el.querySelector('input[type=\"checkbox\"], input[type=\"radio\"], [role=\"checkbox\"], [role=\"radio\"], [role=\"switch\"]');\n if (inner && getChecked(inner) !== \"error\") return inner;\n const prev = el.previousElementSibling;\n if (prev && getChecked(prev) !== \"error\") return prev;\n const next = el.nextElementSibling;\n if (next && getChecked(next) !== \"error\") return next;\n const parent = el.parentElement;\n if (parent) {\n const inP = parent.querySelector('input[type=\"checkbox\"], input[type=\"radio\"], [role=\"checkbox\"], [role=\"radio\"], [role=\"switch\"]');\n if (inP && getChecked(inP) !== \"error\") return inP;\n }\n return el;\n}\n\n/**\n * 为 pointer 类动作(click/check/uncheck)解析可点击代理目标:\n * 当命中隐藏的原生 checkbox/radio/switch input 时,优先改点其可见 label/容器。\n */\nfunction resolvePointerActionTarget(el: Element): Element {\n if (!(el instanceof HTMLInputElement)) return el;\n\n const inputType = el.type?.toLowerCase() ?? \"\";\n const isCheckable = inputType === \"checkbox\" || inputType === \"radio\";\n if (!isCheckable && el.getAttribute(\"role\") !== \"switch\") return el;\n if (isElementVisible(el)) return el;\n\n const label = el.labels?.[0] ?? (el.closest(\"label\") as HTMLLabelElement | null);\n if (label && isElementVisible(label)) return label;\n\n const proxy = el.closest(\".el-switch, .el-checkbox, .el-radio, [role='switch'], [role='checkbox'], [role='radio']\");\n if (proxy && isElementVisible(proxy)) return proxy;\n\n const siblingProxy = el.parentElement?.querySelector(\n \".el-switch__core, .el-checkbox__inner, .el-radio__inner, [role='switch'], [role='checkbox'], [role='radio']\",\n );\n if (siblingProxy && isElementVisible(siblingProxy)) return siblingProxy;\n\n return el;\n}\n\n/**\n * 当命中表单项说明 label(如 Element Plus el-form-item__label)时,\n * 自动重定向到同一表单项中的首个可交互控件。\n */\nfunction resolveFormItemControlTarget(el: Element): Element {\n if (!(el instanceof HTMLElement)) return el;\n const isLabelLike = el.tagName === \"LABEL\" || el.classList.contains(\"el-form-item__label\");\n if (!isLabelLike) return el;\n\n const htmlLabel = el as HTMLLabelElement;\n if (htmlLabel.control && isElementVisible(htmlLabel.control)) return htmlLabel.control;\n\n const formItem = el.closest(\".el-form-item\");\n if (!formItem) return el;\n const content = formItem.querySelector(\".el-form-item__content\") ?? formItem;\n const control = content.querySelector(\n \"input:not([type='hidden']), textarea, select, button, [role='switch'], [role='checkbox'], [role='radio'], [role='button'], .el-switch, .el-checkbox, .el-radio, [tabindex]:not([tabindex='-1'])\",\n );\n if (control && isElementVisible(control)) return control;\n return el;\n}\n\n// ─── 自定义下拉增强 ───\n\nfunction findVisibleOptionByText(text: string): HTMLElement | null {\n const target = text.trim().toLowerCase();\n if (!target) return null;\n const selectors = [\n '[role=\"option\"]', '[role=\"listbox\"] li',\n \".el-select-dropdown__item\", \".el-option\", // Element Plus\n \".ant-select-item-option\", // Ant Design\n \".el-cascader-node\", \".el-dropdown-menu__item\",\n '[class*=\"option\"]', \"li[data-value]\", \"option\",\n ].join(\", \");\n const nodes = Array.from(document.querySelectorAll(selectors));\n const visible = nodes.filter(n => n instanceof HTMLElement && isElementVisible(n));\n for (const n of visible) { if (n.textContent?.trim().toLowerCase() === target) return n as HTMLElement; }\n for (const n of visible) { if (n.textContent?.trim().toLowerCase().includes(target)) return n as HTMLElement; }\n return null;\n}\n\nasync function waitForDropdownPopup(maxWait = 500): Promise<void> {\n const start = Date.now();\n while (Date.now() - start < maxWait) {\n const popup = document.querySelector('[role=\"listbox\"], .el-select-dropdown, .el-popper, .ant-select-dropdown, [class*=\"dropdown\"]');\n if (popup && isElementVisible(popup)) return;\n await sleep(50);\n }\n}\n\n// ─── 工具定义 ───\n\nexport function createDomTool(): ToolDefinition {\n return {\n name: \"dom\",\n description: [\n \"Perform DOM operations on the current page.\",\n \"Actions: click, fill, select_option, clear, check, uncheck, type, focus, hover, scroll, press, get_text, get_attr, set_attr, add_class, remove_class.\",\n \"Input/Select rule: before each fill/type/select_option, click or focus the same target immediately in the same round.\",\n \"For multiple fields, use alternating pairs in one batch: focus/click A -> fill/type A -> focus/click B -> fill/type B.\",\n \"Use the hash ID from DOM snapshot (e.g. #a1b2c) as selector.\",\n \"press supports combo keys like 'Control+a', 'Shift+Enter'.\",\n \"check/uncheck is done via click — state change is verified after action.\",\n \"Ordinal/index rule: treat visual order as 1-based when the instruction says 'the Nth item' (e.g. 4th star = 4th visible icon from left to right), and avoid off-by-one mistakes.\",\n \"Disambiguation rule: distinguish descriptive text/labels from actionable options. Do not click nearby label/help text; click the actual interactive option/control item (icon/button/option) that changes state.\",\n \"Unknown/complex components: if a container element (e.g. role=slider, rating, custom widget) has multiple child icons/items in the snapshot but you don't know how to operate it directly, try clicking the appropriate child element instead. For example, a rating component with 5 star icon children — click the 4th icon child to set 4 stars. A slider with a runway — clicking the runway at the right position may work. Always prefer interacting with visible children when the parent container doesn't respond to fill/click as expected.\",\n \"fill supports role=slider elements: use fill with a numeric value on a role=slider container (rating/slider) to set its value programmatically.\",\n \"For wheel/virtualized pickers where target option is not visible yet, use scroll on the picker column first, then click/select the newly visible option. scroll supports steps for repeated scrolling in one call.\",\n ].join(\" \"),\n\n schema: Type.Object({\n action: Type.String({\n description: \"DOM action: click | fill | select_option | clear | check | uncheck | type | focus | hover | scroll | press | get_text | get_attr | set_attr | add_class | remove_class.\",\n }),\n selector: Type.String({ description: \"Element ref ID from snapshot (e.g. #r0, #r5) or CSS selector\" }),\n value: Type.Optional(Type.String({ description: \"Value for fill/type/set_attr actions.\" })),\n key: Type.Optional(Type.String({ description: \"Key for press action. Supports combo: 'Enter', 'Control+a', 'Shift+Enter', 'Meta+c'\" })),\n label: Type.Optional(Type.String({ description: \"Label text for select_option action.\" })),\n index: Type.Optional(Type.Number({ description: \"0-based option index for select_option action\" })),\n attribute: Type.Optional(Type.String({ description: \"Attribute name for get_attr/set_attr\" })),\n className: Type.Optional(Type.String({ description: \"CSS class name for add_class/remove_class\" })),\n clickCount: Type.Optional(Type.Number({ description: \"Click count (default 1). 2 = double-click, 3 = triple-click.\" })),\n deltaY: Type.Optional(Type.Number({ description: \"Vertical scroll delta for scroll action. Positive = down, negative = up.\" })),\n deltaX: Type.Optional(Type.Number({ description: \"Horizontal scroll delta for scroll action.\" })),\n steps: Type.Optional(Type.Number({ description: \"Repeat count for scroll action (default 1, max 20).\" })),\n waitMs: Type.Optional(Type.Number({ description: \"Wait timeout in ms before action (default: 1200).\" })),\n waitSeconds: Type.Optional(Type.Number({ description: \"Wait timeout in seconds (fallback for waitMs).\" })),\n force: Type.Optional(Type.Boolean({ description: \"Skip actionability checks (default false).\" })),\n }),\n\n execute: async (params): Promise<ToolCallResult> => {\n const action = params.action as string;\n const selector = params.selector as string;\n const waitMs = resolveWaitMs(params);\n const force = params.force === true;\n\n if (!selector) return { content: \"缺少 selector 参数\" };\n\n // ── 元素查找 ──\n let el: Element;\n if (waitMs > 0) {\n const found = await waitForElement(selector, waitMs);\n if (typeof found === \"string\") return { content: found, details: { error: true, code: \"INVALID_SELECTOR\", action, selector } };\n if (!found) return { content: `未找到匹配 \"${selector}\" 的元素`, details: { error: true, code: \"ELEMENT_NOT_FOUND\", action, selector, waitMs } };\n el = found;\n } else {\n const r = queryElement(selector);\n if (typeof r === \"string\") return { content: r, details: { error: true, code: r.startsWith(\"未找到\") ? \"ELEMENT_NOT_FOUND\" : \"INVALID_SELECTOR\", action, selector, waitMs } };\n el = r;\n }\n\n // check/uncheck 归一化\n if (action === \"check\" || action === \"uncheck\") {\n el = resolveCheckableTarget(el);\n }\n\n const actionabilityTarget =\n action === \"click\" || action === \"check\" || action === \"uncheck\"\n ? resolvePointerActionTarget(resolveFormItemControlTarget(el))\n : el;\n\n try {\n // actionability(skip for force / read-only actions)\n const checkResult = ensureActionable(actionabilityTarget, action, selector, force);\n if (checkResult) return checkResult;\n\n switch (action) {\n // ─── click ───\n case \"click\": {\n const target = resolvePointerActionTarget(resolveFormItemControlTarget(retarget(el, force ? \"none\" : \"button-link\")));\n const clickCount = typeof params.clickCount === \"number\" ? params.clickCount : 1;\n\n // option 元素自动写回 select\n if (target instanceof HTMLOptionElement) {\n const parent = target.parentElement;\n if (parent instanceof HTMLSelectElement) {\n parent.focus(); parent.value = target.value;\n dispatchInputEvents(parent);\n return { content: `已选择 ${describeElement(parent)} 的选项 \"${target.value}\"` };\n }\n }\n\n if (target instanceof HTMLElement) {\n scrollIntoViewIfNeeded(target);\n // stable 检查(参考 Playwright)\n if (!force) await checkElementStable(target, 500);\n // hit-target 检查\n if (!force) {\n const blocker = checkHitTarget(target);\n if (blocker) {\n scrollIntoViewIfNeeded(target, 1);\n await sleep(100);\n // 第二次检查仍被遮挡时 warn 但不阻断\n }\n }\n dispatchClickEvents(target, clickCount);\n } else {\n target.dispatchEvent(new MouseEvent(\"click\", { bubbles: true }));\n }\n return { content: `已点击 ${describeElement(target)}` };\n }\n\n // ─── fill(参考 Playwright 分类型策略) ───\n case \"fill\": {\n const value = params.value as string;\n if (value === undefined) return { content: \"缺少 value 参数\" };\n const target = retarget(el, \"follow-label\");\n\n // role=slider 特化:优先写关联数字输入框,其次点击离散子项(评分星级)\n if (target instanceof HTMLElement && target.getAttribute(\"role\") === \"slider\") {\n const numericValue = Number(value);\n if (!Number.isFinite(numericValue)) {\n const guessed = guessNearbyFillTarget(target, value);\n if (guessed) {\n const guessedResult = executeFillOnResolvedTarget(guessed, value, selector, action, \"heuristic-nearby-target\");\n if (guessedResult) return guessedResult;\n }\n return { content: `\"${selector}\" 为 role=slider,未找到可推断填写目标`, details: { error: true, code: \"UNSUPPORTED_FILL_TARGET\", action, selector } };\n }\n\n const linkedInput = findAssociatedSliderInput(target);\n if (linkedInput) {\n const filled = executeFillOnResolvedTarget(linkedInput, String(numericValue), selector, action, `from ${describeElement(target)}`);\n if (filled) return filled;\n }\n\n const min = Number(target.getAttribute(\"aria-valuemin\") ?? \"1\");\n const max = Number(target.getAttribute(\"aria-valuemax\") ?? String(target.children.length || 5));\n const discreteCount = Number.isFinite(max - min + 1) ? Math.max(1, Math.round(max - min + 1)) : target.children.length;\n const desiredIndex = Math.round(numericValue - min);\n const children = Array.from(target.children).filter((node): node is HTMLElement => node instanceof HTMLElement);\n\n if (children.length >= discreteCount && desiredIndex >= 0 && desiredIndex < children.length) {\n const item = children[desiredIndex];\n scrollIntoViewIfNeeded(item);\n dispatchClickEvents(item);\n return { content: `已点击 ${describeElement(item)},设置 ${describeElement(target)} 值为 ${numericValue}` };\n }\n\n const guessed = guessNearbyFillTarget(target, String(numericValue));\n if (guessed) {\n const guessedResult = executeFillOnResolvedTarget(guessed, String(numericValue), selector, action, \"heuristic-nearby-target\");\n if (guessedResult) return guessedResult;\n }\n\n return { content: `\"${selector}\" 为 role=slider,但未找到可写入输入框或可点击离散子项`, details: { error: true, code: \"UNSUPPORTED_FILL_TARGET\", action, selector } };\n }\n\n const directFilled = executeFillOnResolvedTarget(target, value, selector, action);\n if (directFilled) return directFilled;\n\n const guessed = guessNearbyFillTarget(target, value);\n if (guessed) {\n const guessedResult = executeFillOnResolvedTarget(guessed, value, selector, action, \"heuristic-nearby-target\");\n if (guessedResult) return guessedResult;\n }\n\n return { content: `\"${selector}\" 不是可编辑元素,且未在附近找到可推断填写目标`, details: { error: true, code: \"UNSUPPORTED_FILL_TARGET\", action, selector } };\n }\n\n // ─── select_option(参考 Playwright selectOptions 多策略匹配) ───\n case \"select_option\": {\n const value = params.value as string | undefined;\n const label = params.label as string | undefined;\n const index = typeof params.index === \"number\" ? Math.floor(params.index) : undefined;\n if (value === undefined && label === undefined && index === undefined) {\n return { content: \"缺少可选参数:value 或 label 或 index\" };\n }\n\n const target = retarget(el, \"follow-label\");\n\n // 非原生 <select>:自定义下拉\n if (!(target instanceof HTMLSelectElement)) {\n if (!(target instanceof HTMLElement)) return { content: `\"${selector}\" 不是下拉框元素` };\n scrollIntoViewIfNeeded(target);\n const wanted = (label ?? value ?? \"\").trim();\n if (!wanted) return { content: `\"${selector}\" 为自定义下拉时,需提供 value 或 label` };\n dispatchClickEvents(target); // 点击触发器打开\n await waitForDropdownPopup(800);\n const option = findVisibleOptionByText(wanted);\n if (!option) return { content: `未找到与 \"${wanted}\" 匹配的可见下拉选项(自定义下拉)`, details: { error: true, code: \"OPTION_NOT_FOUND\", action, selector, wanted } };\n dispatchClickEvents(option);\n return { content: `已在自定义下拉中选择 \"${wanted}\"` };\n }\n\n // 原生 <select>\n target.focus();\n const options = Array.from(target.options);\n let selected: HTMLOptionElement | undefined;\n if (value !== undefined) selected = options.find(o => o.value === value);\n if (!selected && label !== undefined) { const nl = label.trim().toLowerCase(); selected = options.find(o => o.text.trim().toLowerCase() === nl); }\n if (!selected && value !== undefined) { const nv = value.trim().toLowerCase(); selected = options.find(o => o.text.trim().toLowerCase() === nv); }\n if (!selected && index !== undefined) {\n if (index < 0 || index >= options.length) return { content: `\"${selector}\" 下拉框不存在 index=${index} 的选项` };\n selected = options[index];\n }\n if (!selected) return { content: `\"${selector}\" 下拉框中不存在选项 \"${value ?? label ?? `index=${index}`}\"` };\n // option disabled 检查(参考 Playwright)\n if (selected.disabled) return { content: `\"${selector}\" 目标选项已禁用:${selected.value}`, details: { error: true, code: \"OPTION_DISABLED\", action, selector } };\n if (!target.multiple) { for (const o of options) o.selected = false; }\n selected.selected = true;\n target.value = selected.value;\n dispatchInputEvents(target);\n return { content: `已选择 ${describeElement(target)}: value=\"${selected.value}\", label=\"${selected.text.trim()}\"` };\n }\n\n // ─── clear ───\n case \"clear\": {\n const target = retarget(el, \"follow-label\");\n if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {\n scrollIntoViewIfNeeded(target);\n target.focus(); selectText(target);\n setNativeValue(target as HTMLInputElement, \"\");\n dispatchInputEvents(target);\n return { content: `已清空 ${describeElement(target)}` };\n }\n if (target instanceof HTMLSelectElement) {\n target.focus(); target.value = \"\";\n dispatchInputEvents(target);\n return { content: `已清空 ${describeElement(target)}` };\n }\n if (target instanceof HTMLElement && target.isContentEditable) {\n target.focus(); selectText(target);\n document.execCommand(\"delete\", false, undefined);\n return { content: `已清空 ${describeElement(target)}` };\n }\n return { content: `\"${selector}\" 不是可清空元素` };\n }\n\n // ─── check / uncheck(参考 Playwright:通过 click 切换 + 验证状态) ───\n case \"check\":\n case \"uncheck\": {\n const wantChecked = action === \"check\";\n const current = getChecked(el);\n if (current === \"error\") {\n return { content: `\"${selector}\" 不是 checkbox/radio/[role=checkbox]/[role=radio],无法 ${action}`, details: { error: true, code: \"NOT_CHECKABLE\", action, selector } };\n }\n // 已是目标状态(幂等,参考 Playwright)\n if (current === wantChecked) return { content: `${describeElement(el)} 已经是${wantChecked ? \"选中\" : \"未选中\"}状态` };\n // radio 不能 uncheck\n if (!wantChecked && el instanceof HTMLInputElement && el.type === \"radio\") {\n return { content: `无法取消 radio 按钮的选中状态`, details: { error: true, code: \"CANNOT_UNCHECK_RADIO\", action, selector } };\n }\n // 通过 click 切换(参考 Playwright _setChecked)\n const pointerTarget = resolvePointerActionTarget(el);\n scrollIntoViewIfNeeded(pointerTarget);\n if (pointerTarget instanceof HTMLElement) dispatchClickEvents(pointerTarget);\n else pointerTarget.dispatchEvent(new MouseEvent(\"click\", { bubbles: true }));\n // 验证状态变更\n await sleep(50);\n const finalState = getChecked(el);\n if (finalState !== wantChecked && el instanceof HTMLInputElement) {\n el.checked = wantChecked;\n dispatchInputEvents(el);\n }\n return { content: `已${wantChecked ? \"勾选\" : \"取消勾选\"} ${describeElement(el)}` };\n }\n\n // ─── type(逐字符键入) ───\n case \"type\": {\n const value = params.value as string;\n if (value === undefined) return { content: \"缺少 value 参数\" };\n const target = retarget(el, \"follow-label\");\n scrollIntoViewIfNeeded(target);\n if (target instanceof HTMLElement) target.focus();\n\n for (const char of value) {\n const init: KeyboardEventInit = { key: char, code: resolveKeyCode(char), bubbles: true, cancelable: true };\n target.dispatchEvent(new KeyboardEvent(\"keydown\", init));\n target.dispatchEvent(new KeyboardEvent(\"keypress\", init));\n if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {\n const proto = target instanceof HTMLInputElement ? HTMLInputElement.prototype : HTMLTextAreaElement.prototype;\n const nativeSet = Object.getOwnPropertyDescriptor(proto, \"value\")?.set;\n if (nativeSet) nativeSet.call(target, target.value + char); else target.value += char;\n } else if (target instanceof HTMLElement && target.isContentEditable) {\n document.execCommand(\"insertText\", false, char);\n }\n target.dispatchEvent(new Event(\"input\", { bubbles: true, composed: true }));\n target.dispatchEvent(new KeyboardEvent(\"keyup\", init));\n }\n if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {\n target.dispatchEvent(new Event(\"change\", { bubbles: true }));\n }\n return { content: `已逐字输入到 ${describeElement(target)}: \"${value}\"` };\n }\n\n // ─── focus(参考 Playwright:双次 focus) ───\n case \"focus\": {\n const target = retarget(el, \"follow-label\");\n if (target instanceof HTMLElement || target instanceof SVGElement) {\n target.focus(); target.focus(); // Playwright workaround: 双次 focus\n }\n return { content: `已聚焦 ${describeElement(target)}` };\n }\n\n // ─── hover ───\n case \"hover\": {\n const target = retarget(el, \"none\");\n scrollIntoViewIfNeeded(target);\n if (!force) await checkElementStable(target, 500);\n if (target instanceof HTMLElement) dispatchHoverEvents(target);\n return { content: `已悬停 ${describeElement(target)}` };\n }\n\n // ─── scroll(组件内滚动,适配时间滚轮/虚拟列表) ───\n case \"scroll\": {\n const target = retarget(el, \"none\");\n const deltaY = typeof params.deltaY === \"number\"\n ? params.deltaY\n : (typeof params.value === \"string\" && !Number.isNaN(Number(params.value)) ? Number(params.value) : 180);\n const deltaX = typeof params.deltaX === \"number\" ? params.deltaX : 0;\n const rawSteps = typeof params.steps === \"number\" ? Math.floor(params.steps) : 1;\n const steps = Math.min(20, Math.max(1, rawSteps));\n\n if (target instanceof HTMLElement) {\n scrollIntoViewIfNeeded(target);\n for (let i = 0; i < steps; i++) {\n target.scrollBy({ top: deltaY, left: deltaX, behavior: \"auto\" });\n target.dispatchEvent(new WheelEvent(\"wheel\", {\n bubbles: true,\n cancelable: true,\n deltaY,\n deltaX,\n }));\n }\n return { content: `已滚动 ${describeElement(target)}: deltaY=${deltaY}, deltaX=${deltaX}, steps=${steps}` };\n }\n\n for (let i = 0; i < steps; i++) {\n target.dispatchEvent(new WheelEvent(\"wheel\", {\n bubbles: true,\n cancelable: true,\n deltaY,\n deltaX,\n }));\n }\n return { content: `已滚动 ${describeElement(target)}: deltaY=${deltaY}, deltaX=${deltaX}, steps=${steps}` };\n }\n\n // ─── press(支持组合键) ───\n case \"press\": {\n const key = (params.key as string) || (params.value as string);\n if (!key) return { content: \"缺少 key 参数(如 Enter, Escape, Tab, Control+a)\" };\n const target = retarget(el, \"none\");\n scrollIntoViewIfNeeded(target);\n if (target instanceof HTMLElement) target.focus();\n executePress(target, key);\n // Enter 特殊:触发 form submit\n const mainKey = splitKeyCombo(key).pop();\n if (mainKey === \"Enter\") {\n const form = (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) ? (target.form ?? target.closest(\"form\")) : target.closest(\"form\");\n form?.dispatchEvent(new Event(\"submit\", { bubbles: true, cancelable: true }));\n }\n return { content: `已在 ${describeElement(target)} 上按下 ${key}` };\n }\n\n // ─── 读取类 ───\n case \"get_text\": {\n const text = el.textContent?.trim() ?? \"\";\n return { content: `${describeElement(el)} 的文本内容:${text || \"(空)\"}` };\n }\n case \"get_attr\": {\n const attribute = params.attribute as string;\n if (!attribute) return { content: \"缺少 attribute 参数\" };\n const attrName = attribute.toLowerCase();\n if (attrName === \"checked\") {\n if (el instanceof HTMLInputElement) return { content: `${describeElement(el)} 的 checked = ${String(el.checked)}` };\n return { content: `${describeElement(el)} 的 checked = ${el.getAttribute(\"aria-checked\") ?? \"(不存在)\"}` };\n }\n if (attrName === \"selected\") {\n if (el instanceof HTMLOptionElement) return { content: `${describeElement(el)} 的 selected = ${String(el.selected)}` };\n return { content: `${describeElement(el)} 的 selected = ${el.getAttribute(\"aria-selected\") ?? \"(不存在)\"}` };\n }\n if (attrName === \"disabled\") {\n if (el instanceof HTMLButtonElement || el instanceof HTMLInputElement || el instanceof HTMLSelectElement || el instanceof HTMLTextAreaElement) {\n return { content: `${describeElement(el)} 的 disabled = ${String(el.disabled)}` };\n }\n }\n if (attrName === \"readonly\" && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {\n return { content: `${describeElement(el)} 的 readonly = ${String(el.readOnly)}` };\n }\n if (attrName === \"value\" && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement)) {\n return { content: `${describeElement(el)} 的 value = ${el.value || \"(空)\"}` };\n }\n return { content: `${describeElement(el)} 的 ${attribute} = ${el.getAttribute(attribute) ?? \"(不存在)\"}` };\n }\n\n // ─── 修改类 ───\n case \"set_attr\": {\n const attribute = params.attribute as string;\n const value = params.value as string;\n if (!attribute || value === undefined) return { content: \"缺少 attribute 或 value 参数\" };\n el.setAttribute(attribute, value);\n return { content: `已设置 ${describeElement(el)} 的 ${attribute}=\"${value}\"` };\n }\n case \"add_class\": {\n const className = params.className as string;\n if (!className) return { content: \"缺少 className 参数\" };\n el.classList.add(className);\n return { content: `已添加 class \"${className}\" 到 ${describeElement(el)}` };\n }\n case \"remove_class\": {\n const className = params.className as string;\n if (!className) return { content: \"缺少 className 参数\" };\n el.classList.remove(className);\n return { content: `已移除 ${describeElement(el)} 的 class \"${className}\"` };\n }\n\n default:\n return { content: `未知的 DOM 动作: ${action}` };\n }\n } catch (err) {\n return { content: `DOM 操作 \"${action}\" 失败: ${err instanceof Error ? err.message : String(err)}`, details: { error: true, action, selector } };\n }\n },\n };\n}","/**\n * Page Info Tool — 基于 Web API 的页面信息获取工具。\n *\n * 替代 Playwright 的 getTitle/getUrl/snapshot 等。\n * 运行环境:浏览器 Content Script。\n *\n * 支持 6 种动作:\n * get_url — 获取当前页面 URL\n * get_title — 获取页面标题\n * get_selection — 获取用户选中的文本\n * get_viewport — 获取视口尺寸和滚动位置\n * snapshot — 获取页面 DOM 结构快照(AI 可读的文本描述)\n * query_all — 查询所有匹配选择器的元素,返回摘要信息\n */\nimport { Type } from \"@sinclair/typebox\";\nimport type { ToolDefinition, ToolCallResult } from \"../../core/tool-registry.js\";\nimport type { RefStore } from \"../ref-store.js\";\nimport { getTrackedElementEvents, hasTrackedElementEvents } from \"../event-listener-tracker.js\";\nimport { getActiveRefStore } from \"./dom-tool.js\";\n\n/** 快照配置选项 */\nexport type SnapshotOptions = {\n /** 最大遍历深度(默认 12) */\n maxDepth?: number;\n /**\n * 视口裁剪:只保留与视口相交的元素(默认 true)。\n * 开启后,完全在视口外的元素会被跳过,大幅减少 token 消耗。\n * 注意:祖先容器即使自身不在视口内,只要有子元素在视口内就会保留。\n */\n viewportOnly?: boolean;\n /**\n * 智能剪枝:折叠无意义的纯布局容器(默认 true)。\n * 开启后,没有文本、没有 id、没有交互属性的纯布局元素(div/span/section 等)\n * 如果自身无意义,会被折叠——子元素直接提升到父级输出,减少嵌套噪音。\n */\n pruneLayout?: boolean;\n /**\n * hash ID 映射表(可选)。\n * 传入 RefStore 实例后,每个元素使用确定性 hash ID 替代完整 XPath,\n * 大幅减少 token 消耗。dom-tool 通过 RefStore.get(id) 解析回 DOM 元素。\n */\n refStore?: RefStore;\n /** 最大输出节点数(默认 220),超过后停止继续遍历。 */\n maxNodes?: number;\n /** 每个父节点最多输出的子元素数(默认 25),超出部分会折叠。 */\n maxChildren?: number;\n /** 文本截断长度(默认 40)。 */\n maxTextLength?: number;\n /**\n * 是否对“选项列表”容器放宽子节点截断(默认 false)。\n * 典型场景:时间选择器/下拉选项列表,避免关键选项被 `...children omitted` 折叠。\n */\n expandOptionLists?: boolean;\n /**\n * 仅对指定 hash ref 节点放宽子节点截断(优先级高于默认 maxChildren)。\n * 例如:[#abc123, #def456],用于 AI 在看到 children omitted 后定向请求放宽。\n */\n expandChildrenRefs?: string[];\n /** 对 expandChildrenRefs 节点生效的子节点上限(默认 120)。 */\n expandedChildrenLimit?: number;\n};\n\n/** 快照属性值最大保留长度(超出截断)。 */\nconst MAX_SNAPSHOT_ATTR_VALUE_LENGTH = 120;\n/** 选项列表放宽时的子节点上限(仍保留硬上限,避免快照无限膨胀)。 */\nconst MAX_EXPANDED_LIST_CHILDREN = 120;\n/** 定向放宽 children 的硬上限。 */\nconst MAX_EXPANDED_CHILDREN_LIMIT = 300;\n\n/** 事件名 → 快照简写映射(压缩 token)。 */\nconst EVENT_ABBREV: Record<string, string> = {\n click: \"clk\", dblclick: \"dbl\",\n mousedown: \"mdn\", mouseup: \"mup\", mousemove: \"mmv\",\n mouseover: \"mov\", mouseout: \"mot\", mouseenter: \"men\", mouseleave: \"mlv\",\n pointerdown: \"pdn\", pointerup: \"pup\", pointermove: \"pmv\",\n touchstart: \"tst\", touchend: \"ted\",\n keydown: \"kdn\", keyup: \"kup\",\n input: \"inp\", change: \"chg\", submit: \"sub\",\n focus: \"fcs\", blur: \"blr\",\n scroll: \"scl\", wheel: \"whl\",\n drag: \"drg\", dragstart: \"drs\", dragend: \"dre\", drop: \"drp\",\n contextmenu: \"ctx\",\n};\n\nfunction abbrevEvent(name: string): string {\n return EVENT_ABBREV[name] ?? name.slice(0, 3);\n}\n\n/**\n * 规整快照属性值,避免把长 base64/data URL 原样注入快照。\n */\nfunction sanitizeSnapshotAttrValue(value: string): string {\n const trimmed = value.trim();\n if (!trimmed) return \"\";\n\n const dataUrlMatch = trimmed.match(/^data:([^,]*?),(.*)$/i);\n if (dataUrlMatch) {\n const meta = dataUrlMatch[1] || \"\";\n const payload = dataUrlMatch[2] || \"\";\n const isBase64 = /;base64/i.test(meta);\n const payloadLength = payload.length;\n const previewMeta = meta.slice(0, 48);\n if (isBase64 || payloadLength > 64) {\n return `data:${previewMeta},<omitted:${payloadLength}>`;\n }\n }\n\n const base64ChunkMatch = trimmed.match(/^[A-Za-z0-9+/]{80,}={0,2}$/);\n if (base64ChunkMatch) {\n return `<base64:${trimmed.length}>`;\n }\n\n if (trimmed.length > MAX_SNAPSHOT_ATTR_VALUE_LENGTH) {\n return `${trimmed.slice(0, MAX_SNAPSHOT_ATTR_VALUE_LENGTH)}...`;\n }\n return trimmed;\n}\n\n/**\n * 生成页面 DOM 快照 — 将 DOM 树转为 AI 可理解的文本描述。\n *\n * 基于 Web API 实现,只遍历可见元素,跳过 script/style/svg 等无意义节点。\n * 传入 RefStore 时,每个元素生成确定性 hash ID(如 #a1b2c),\n * AI 通过 hash ID 精确定位元素,无需猜测 CSS 选择器。\n *\n * 输出格式示例:\n * [header] #k9f2a\n * [nav] #m3d7e\n * [a] \"首页\" href=\"/\" #p1c4b\n * [a] \"关于\" href=\"/about\" #q8e5f\n * [main] #r2a6d\n * [h1] \"欢迎\" #s7g3h\n * [input] type=\"text\" placeholder=\"搜索...\" #t4j8k\n * [button] \"搜索\" id=\"search-btn\" onclick #u5n2m\n *\n * @param root - 快照根元素(默认 document.body)\n * @param options - 快照选项对象,或传入数字作为 maxDepth(向后兼容)\n */\nexport function generateSnapshot(\n root: Element = document.body,\n options: SnapshotOptions | number = {},\n): string {\n // 向后兼容:数字参数视为 maxDepth\n const opts: SnapshotOptions = typeof options === \"number\"\n ? { maxDepth: options }\n : options;\n\n const maxDepth = opts.maxDepth ?? 12;\n const viewportOnly = opts.viewportOnly ?? true;\n const pruneLayout = opts.pruneLayout ?? true;\n const maxNodes = opts.maxNodes ?? 220;\n const maxChildren = opts.maxChildren ?? 25;\n const maxTextLength = opts.maxTextLength ?? 40;\n const expandOptionLists = opts.expandOptionLists ?? false;\n const expandedChildrenLimit = Math.min(\n MAX_EXPANDED_CHILDREN_LIMIT,\n Math.max(1, opts.expandedChildrenLimit ?? MAX_EXPANDED_LIST_CHILDREN),\n );\n const expandChildrenRefSet = new Set(\n (opts.expandChildrenRefs ?? [])\n .map(ref => ref.trim().replace(/^#/, \"\"))\n .filter(Boolean),\n );\n\n let emittedNodes = 0;\n let truncatedByNodeBudget = false;\n\n const refStore = opts.refStore;\n\n const SKIP_TAGS = new Set([\n \"SCRIPT\", \"STYLE\", \"SVG\", \"NOSCRIPT\", \"LINK\", \"META\", \"BR\", \"HR\",\n ]);\n\n /** 纯布局容器标签 — 智能剪枝时可能被折叠 */\n const LAYOUT_TAGS = new Set([\n \"DIV\", \"SPAN\", \"SECTION\", \"ARTICLE\", \"ASIDE\", \"MAIN\",\n \"HEADER\", \"FOOTER\", \"NAV\", \"FIGURE\", \"FIGCAPTION\",\n ]);\n\n /** 视口尺寸(viewportOnly 开启时使用) */\n const vpWidth = viewportOnly ? window.innerWidth : 0;\n const vpHeight = viewportOnly ? window.innerHeight : 0;\n\n const INTERACTIVE_ATTRS = [\n \"href\", \"type\", \"placeholder\", \"value\", \"name\", \"role\", \"aria-label\",\n \"src\", \"alt\", \"title\", \"for\", \"action\", \"method\",\n ];\n\n const INTERACTIVE_TAGS = new Set([\n \"A\", \"BUTTON\", \"INPUT\", \"TEXTAREA\", \"SELECT\", \"OPTION\", \"LABEL\", \"SUMMARY\",\n ]);\n\n const INTERACTIVE_EVENTS = new Set([\n \"click\", \"dblclick\", \"mousedown\", \"mouseup\", \"pointerdown\", \"pointerup\",\n \"touchstart\", \"touchend\", \"input\", \"change\", \"keydown\", \"keyup\",\n \"submit\", \"focus\", \"blur\",\n ]);\n\n /** 交互性 ARIA role — 需要分配 hash ID 的角色集合 */\n const INTERACTIVE_ROLES = new Set([\n \"button\", \"link\", \"tab\", \"switch\", \"slider\", \"checkbox\", \"radio\",\n \"combobox\", \"listbox\", \"option\", \"menuitem\", \"textbox\", \"spinbutton\",\n \"searchbox\", \"treeitem\", \"gridcell\", \"scrollbar\",\n ]);\n\n /**\n * 事件优先级(值越大越优先):\n * 输入链路(input/change/focus/blur) > 点击链路(click/pointer) > 其他事件。\n */\n const EVENT_PRIORITY: Record<string, number> = {\n input: 140,\n change: 130,\n focus: 120,\n blur: 110,\n keydown: 100,\n keyup: 90,\n click: 80,\n dblclick: 70,\n pointerdown: 60,\n pointerup: 55,\n mousedown: 50,\n mouseup: 45,\n touchstart: 40,\n touchend: 35,\n submit: 30,\n };\n\n /** 布尔状态属性 — 只在存在时输出(无值),如 disabled、checked */\n const BOOLEAN_ATTRS = [\n \"disabled\", \"checked\", \"readonly\", \"required\", \"selected\",\n \"hidden\",\n ];\n\n /**\n * 计算元素在父节点中同标签兄弟里的序号(1-based,XPath 规范)。\n * 如果同标签兄弟只有一个,返回空字符串(无需索引消歧)。\n */\n function getSiblingIndex(el: Element): string {\n const parent = el.parentElement;\n if (!parent) return \"\";\n const tag = el.tagName;\n const siblings = Array.from(parent.children).filter((c) => c.tagName === tag);\n if (siblings.length <= 1) return \"\";\n return `[${siblings.indexOf(el) + 1}]`;\n }\n\n /**\n * 判断元素是否与视口相交(部分可见也算)。\n * 对根级容器(depth <= 1)始终返回 true,确保不丢失顶层结构。\n */\n function isInViewport(el: Element, depth: number): boolean {\n if (!viewportOnly) return true;\n // 根级容器始终保留(body/html 等),否则整棵树会被跳过\n if (depth <= 1) return true;\n const rect = el.getBoundingClientRect();\n // 元素完全在视口外则跳过\n if (rect.bottom < 0 || rect.top > vpHeight) return false;\n if (rect.right < 0 || rect.left > vpWidth) return false;\n // 零尺寸元素(如隐藏的 position:absolute 元素)也跳过\n if (rect.width === 0 && rect.height === 0) return false;\n return true;\n }\n\n /**\n * 判断元素是否为「无意义布局容器」(智能剪枝候选)。\n * 满足所有条件时返回 true:\n * 1. 标签是常见布局容器(div/span/section 等)\n * 2. 没有 id\n * 3. 没有交互属性(href/role/aria-label/onclick 等)\n * 4. 没有直接文本内容\n */\n function isEmptyLayoutContainer(el: Element, directText: string): boolean {\n if (!pruneLayout) return false;\n if (!LAYOUT_TAGS.has(el.tagName)) return false;\n // 有 id 的元素可能是重要锚点\n if (el.getAttribute(\"id\")) return false;\n // 有 role/aria-label 的元素有语义\n if (el.getAttribute(\"role\") || el.getAttribute(\"aria-label\")) return false;\n // 有内联事件(onclick 等)的元素有交互\n for (const attr of Array.from(el.attributes)) {\n if (attr.name.startsWith(\"on\")) return false;\n }\n // 运行时追踪到事件绑定的元素有交互语义\n if (hasTrackedElementEvents(el)) return false;\n // 有直接文本内容的元素有意义\n if (directText) return false;\n return true;\n }\n\n function hasInteractiveTrackedEvents(el: Element): boolean {\n const tracked = getTrackedElementEvents(el);\n if (tracked.length === 0) return false;\n return tracked.some(name => INTERACTIVE_EVENTS.has(name));\n }\n\n function getTrackedEventPriorityScore(el: Element): number {\n const tracked = getTrackedElementEvents(el);\n if (tracked.length === 0) return 0;\n\n let score = 0;\n for (const name of tracked) {\n score += EVENT_PRIORITY[name] ?? 8;\n }\n return score;\n }\n\n /**\n * 元素优先级:\n * 1) 输入控件/按钮等语义控件\n * 2) 事件追踪优先级(输入、点击、失焦等)\n * 3) inline 事件与可聚焦能力补充加分\n */\n function getElementPriorityScore(el: Element): number {\n let score = 0;\n\n if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) {\n score += 200;\n } else if (el instanceof HTMLButtonElement || el instanceof HTMLAnchorElement) {\n score += 180;\n } else if (el.getAttribute(\"role\") === \"button\" || el.getAttribute(\"role\") === \"switch\" || el.getAttribute(\"role\") === \"slider\") {\n score += 160;\n }\n\n score += getTrackedEventPriorityScore(el);\n\n if (el.hasAttribute(\"onclick\")) score += 60;\n if (el.hasAttribute(\"oninput\") || el.hasAttribute(\"onchange\")) score += 80;\n if (el.hasAttribute(\"onfocus\") || el.hasAttribute(\"onblur\")) score += 70;\n if (el.hasAttribute(\"tabindex\")) score += 20;\n\n return score;\n }\n\n function orderChildrenByPriority(children: Element[]): Element[] {\n return children\n .map((child, index) => ({\n child,\n index,\n interactive: isInteractiveElement(child),\n score: getElementPriorityScore(child),\n }))\n .sort((a, b) => {\n if (a.interactive !== b.interactive) return a.interactive ? -1 : 1;\n if (b.score !== a.score) return b.score - a.score;\n return a.index - b.index;\n })\n .map(entry => entry.child);\n }\n\n function isInteractiveElement(el: Element): boolean {\n if (INTERACTIVE_TAGS.has(el.tagName)) return true;\n if (el.hasAttribute(\"onclick\")) return true;\n if (el.hasAttribute(\"role\")) return true;\n if (el.hasAttribute(\"tabindex\")) return true;\n if (el.hasAttribute(\"aria-label\")) return true;\n if (hasInteractiveTrackedEvents(el)) return true;\n return false;\n }\n\n /**\n * 判断元素是否需要分配 hash ID(仅交互节点分配,节省 token)。\n *\n * 核心依据:元素是否绑定了交互事件(INTERACTIVE_EVENTS 集合)。\n * 辅助依据:语义交互标签、内联事件、ARIA role、tabindex 等兜底。\n */\n function needsHashId(el: Element): boolean {\n // 核心判定:有追踪到的交互事件(click/input/change/focus/blur 等)\n if (hasInteractiveTrackedEvents(el)) return true;\n // 内联事件处理器(onclick/oninput 等)\n for (const attr of Array.from(el.attributes)) {\n if (attr.name.startsWith(\"on\")) return true;\n }\n // 语义交互标签兜底(即使没有事件追踪也应可操作)\n if (INTERACTIVE_TAGS.has(el.tagName)) return true;\n // 交互性 ARIA role 兜底\n const role = el.getAttribute(\"role\");\n if (role && INTERACTIVE_ROLES.has(role)) return true;\n // tabindex → 可聚焦\n if (el.hasAttribute(\"tabindex\")) return true;\n // contenteditable → 可编辑\n if ((el as HTMLElement).isContentEditable && el.getAttribute(\"contenteditable\") !== \"inherit\") return true;\n return false;\n }\n\n /** 判断是否为“选项列表”容器(时间/下拉/listbox 等)。 */\n function isOptionListContainer(el: Element): boolean {\n if (el.getAttribute(\"role\") === \"listbox\") return true;\n const cls = (el.getAttribute(\"class\") || \"\").toLowerCase();\n if (\n cls.includes(\"time-spinner__list\") ||\n cls.includes(\"select-dropdown\") ||\n cls.includes(\"virtual-list\") ||\n cls.includes(\"option\")\n ) {\n return true;\n }\n\n if (el.tagName === \"UL\") {\n const children = Array.from(el.children);\n if (children.length >= 20) {\n const liCount = children.filter(child => child.tagName === \"LI\").length;\n if (liCount / children.length >= 0.8) return true;\n }\n }\n return false;\n }\n\n /** 针对子节点截断计算动态上限。 */\n function resolveChildLimit(el: Element, defaultLimit: number, hashId?: string): number {\n let nextLimit = defaultLimit;\n if (expandOptionLists && isOptionListContainer(el)) {\n nextLimit = Math.max(nextLimit, MAX_EXPANDED_LIST_CHILDREN);\n }\n if (hashId && expandChildrenRefSet.has(hashId)) {\n nextLimit = Math.max(nextLimit, expandedChildrenLimit);\n }\n return nextLimit;\n }\n\n function walk(el: Element, depth: number, parentPath: string): string {\n if (emittedNodes >= maxNodes) {\n truncatedByNodeBudget = true;\n return \"\";\n }\n\n if (depth > maxDepth) return \"\";\n if (SKIP_TAGS.has(el.tagName)) return \"\";\n\n // 跳过标记为 autopilot 内部 UI 的元素(避免 AI 操作自身界面)\n if (el.hasAttribute(\"data-autopilot-ignore\")) return \"\";\n\n // 跳过不可见元素\n const style = window.getComputedStyle(el);\n if (style.display === \"none\" || style.visibility === \"hidden\") return \"\";\n\n // ─── 视口裁剪 ───\n // 检查元素是否在视口内(viewportOnly 关闭时始终通过)\n if (!isInViewport(el, depth)) return \"\";\n\n const indent = \" \".repeat(depth);\n const tag = el.tagName.toLowerCase();\n\n // ─── 角色优先标签 ───\n // 当元素有 INTERACTIVE_ROLES 内的 role 且与 tag 不等价时,\n // 用 role 替代 tag 作为显示标签(如 [combobox] 替代 [input] role=\"combobox\")。\n // 这让 AI 直接从标签判断交互模式,同时省去冗余的 role=\"...\" 属性。\n const rawRole = el.getAttribute(\"role\");\n const useRoleAsTag = !!(rawRole && INTERACTIVE_ROLES.has(rawRole) && rawRole !== tag);\n const displayTag = useRoleAsTag ? rawRole : tag;\n\n // 构建当前元素的内部路径(始终计算,子元素路径依赖它)\n // 注意:路径使用原始 tag 而非 displayTag,保证 hash 计算一致性\n const index = getSiblingIndex(el);\n const currentPath = `${parentPath}/${tag}${index}`;\n // 仅交互节点分配 hash ID 并注册到 RefStore,非交互节点不占 token\n const shouldAssignHash = refStore && needsHashId(el);\n const hashId = shouldAssignHash ? refStore.set(el, currentPath) : undefined;\n\n // 收集有意义的属性(精简版:只保留对 AI 操作有用的信息)\n const attrs: string[] = [];\n\n // 1. id — 最重要的标识信息\n const elId = el.getAttribute(\"id\");\n if (elId) attrs.push(`id=\"${elId}\"`);\n\n // 2. class — 只保留第 1 个有语义的类名(大幅减少 token)\n const className = el.getAttribute(\"class\")?.trim();\n if (className) {\n const cls = className.split(/\\s+/)\n .find(c => c && !c.startsWith(\"data-v-\") && c.length < 25 && !/^[a-z]{1,2}\\d|^_|^css-/.test(c));\n if (cls) attrs.push(`class=\"${cls}\"`);\n }\n\n // 3. 交互属性(href, type, placeholder 等)\n for (const attr of INTERACTIVE_ATTRS) {\n // 若 role 已提升为显示标签,跳过 role 属性避免重复输出\n if (attr === \"role\" && useRoleAsTag) continue;\n const val = el.getAttribute(attr);\n if (val) {\n const safeVal = sanitizeSnapshotAttrValue(val);\n if (safeVal) attrs.push(`${attr}=\"${safeVal}\"`);\n }\n }\n\n // 4. 布尔状态属性(disabled, checked 等)\n for (const attr of BOOLEAN_ATTRS) {\n if (el.hasAttribute(attr)) attrs.push(attr);\n }\n\n // 4.1 运行时布尔状态(property 级别),避免仅靠 attribute 导致状态丢失\n if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement || el instanceof HTMLButtonElement) {\n if (el.disabled && !attrs.includes(\"disabled\")) attrs.push(\"disabled\");\n }\n if ((el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) && el.readOnly) {\n if (!attrs.includes(\"readonly\")) attrs.push(\"readonly\");\n }\n\n // 5. 事件绑定 — 只标记有 onclick(最重要的交互信号)\n if (el.hasAttribute(\"onclick\")) attrs.push(\"onclick\");\n\n // 5.1 运行时事件绑定(addEventListener 追踪)\n const trackedEvents = getTrackedElementEvents(el);\n if (trackedEvents.length > 0) {\n const preview = trackedEvents.slice(0, 6).map(abbrevEvent).join(\",\");\n const suffix = trackedEvents.length > 6 ? \",...\" : \"\";\n attrs.push(`listeners=\"${preview}${suffix}\"`);\n }\n\n // 6. data-* 属性 — 只保留 data-testid(自动化测试定位用)\n const testId = el.getAttribute(\"data-testid\") || el.getAttribute(\"data-test-id\");\n if (testId) {\n const safeTestId = sanitizeSnapshotAttrValue(testId).slice(0, 25);\n if (safeTestId) attrs.push(`data-testid=\"${safeTestId}\"`);\n }\n\n // 7. 对于 input/textarea,补充当前实际 value(截短到 40 字符)\n if ((el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) && el.value) {\n const currentVal = sanitizeSnapshotAttrValue(el.value).slice(0, 40);\n const attrVal = el.getAttribute(\"value\");\n if (attrVal !== currentVal) {\n attrs.push(`val=\"${currentVal}\"`);\n }\n }\n\n // 7.1 对于 checkbox/radio,补充运行时 checked 状态(property 级别)\n if (el instanceof HTMLInputElement && (el.type === \"checkbox\" || el.type === \"radio\") && el.checked) {\n if (!attrs.includes(\"checked\")) attrs.push(\"checked\");\n }\n\n // 8. 对于 select,补充当前选中 value;对于 option,按运行时 selected 状态输出\n if (el instanceof HTMLSelectElement && el.value) {\n attrs.push(`val=\"${sanitizeSnapshotAttrValue(el.value).slice(0, 40)}\"`);\n }\n if (el instanceof HTMLOptionElement && el.selected) {\n if (!attrs.includes(\"selected\")) attrs.push(\"selected\");\n }\n\n // 获取直接文本(不含子元素文本)\n let directText = \"\";\n for (let i = 0; i < el.childNodes.length; i++) {\n const node = el.childNodes[i];\n if (node.nodeType === Node.TEXT_NODE) {\n const t = node.textContent?.trim();\n if (t) directText += t + \" \";\n }\n }\n directText = directText.trim();\n\n // ─── 智能剪枝 ───\n // 无意义布局容器:默认不输出自身行,直接将子元素提升到当前层级。\n // 若提升后同层出现多个孩子(如 a 与 b(c) 折叠成 a 与 c),\n // 则输出括号分组块,显式保留这些节点的关联来源。\n if (isEmptyLayoutContainer(el, directText)) {\n const allChildren = Array.from(el.children);\n const orderedChildren = orderChildrenByPriority(allChildren);\n const childLimit = resolveChildLimit(el, maxChildren, hashId);\n const selectedChildren = orderedChildren.slice(0, childLimit);\n const omittedChildren = orderedChildren.length - selectedChildren.length;\n\n const childBlocks: string[] = [];\n for (let i = 0; i < selectedChildren.length; i++) {\n // 子元素继承当前路径(保证 hash 计算正确),但不增加缩进\n const childResult = walk(selectedChildren[i], depth, currentPath);\n if (childResult) childBlocks.push(childResult);\n }\n\n // 如果子树也全部为空,整个容器就被剪掉\n if (childBlocks.length === 0 && omittedChildren <= 0) {\n return \"\";\n }\n\n const shouldGroupCollapsedChildren = childBlocks.length >= 2 || omittedChildren > 0;\n if (!shouldGroupCollapsedChildren) {\n return childBlocks.join(\"\\n\");\n }\n\n const groupLines: string[] = [\n `${\" \".repeat(depth)}([${displayTag}] collapsed-group`,\n ];\n for (const block of childBlocks) {\n groupLines.push(indentMultiline(block, 1));\n }\n\n if (omittedChildren > 0) {\n groupLines.push(`${\" \".repeat(depth + 1)}... (${omittedChildren} children omitted)`);\n }\n\n groupLines.push(`${\" \".repeat(depth)})`);\n return groupLines.join(\"\\n\");\n }\n\n // 构建当前元素描述:[显示标签] \"文本\" 属性 #hashID(仅交互节点)\n // displayTag 在有交互性 role 时为 role 值,否则为原始 HTML tag\n let line = `${indent}[${displayTag}]`;\n if (directText) line += ` \"${directText.slice(0, maxTextLength)}\"`;\n if (attrs.length) line += ` ${attrs.join(\" \")}`;\n // 仅交互节点输出 hash ID,非交互节点不附加标识(省 token)\n if (hashId) {\n line += ` #${hashId}`;\n }\n\n const lines: string[] = [line];\n emittedNodes++;\n\n // 递归子元素(优先保留可交互元素,再保留普通元素)\n const allChildren = Array.from(el.children);\n const orderedChildren = orderChildrenByPriority(allChildren);\n const childLimit = resolveChildLimit(el, maxChildren, hashId);\n const selectedChildren = orderedChildren.slice(0, childLimit);\n const omittedChildren = orderedChildren.length - selectedChildren.length;\n\n for (let i = 0; i < selectedChildren.length; i++) {\n const childResult = walk(selectedChildren[i], depth + 1, currentPath);\n if (childResult) lines.push(childResult);\n }\n\n if (omittedChildren > 0) {\n lines.push(`${indent} ... (${omittedChildren} children omitted)`);\n }\n\n return lines.join(\"\\n\");\n }\n\n // 根元素自身的标签作为路径起点,walk 内部不再重复追加\n // 例如 root=body 时,parentPath=\"\",walk 中 currentPath=\"/body\"\n const output = walk(root, 0, \"\") || \"(空页面)\";\n if (!truncatedByNodeBudget) return output;\n return `${output}\\n... (snapshot truncated: maxNodes=${maxNodes})`;\n}\n\n/**\n * 查询所有匹配元素并返回摘要信息(标签、文本、关键属性)。\n */\nfunction queryAllElements(selector: string, limit = 20): string {\n try {\n const elements = document.querySelectorAll(selector);\n if (elements.length === 0) return `未找到匹配 \"${selector}\" 的元素`;\n\n const results: string[] = [`找到 ${elements.length} 个元素:`];\n const count = Math.min(elements.length, limit);\n\n for (let i = 0; i < count; i++) {\n const el = elements[i];\n const tag = el.tagName.toLowerCase();\n const text = el.textContent?.trim().slice(0, 60) ?? \"\";\n const id = el.id ? `#${el.id}` : \"\";\n const cls = el.className && typeof el.className === \"string\"\n ? `.${el.className.split(\" \").filter(Boolean).join(\".\")}`\n : \"\";\n results.push(` ${i + 1}. <${tag}${id}${cls}> \"${text}\"`);\n }\n\n if (elements.length > limit) {\n results.push(` ...还有 ${elements.length - limit} 个元素`);\n }\n\n return results.join(\"\\n\");\n } catch {\n return `选择器语法错误: ${selector}`;\n }\n}\n\n/**\n * 多行文本块缩进(中)/ Indent each line of a multiline block (EN).\n */\nfunction indentMultiline(block: string, indentLevel: number): string {\n const prefix = \" \".repeat(indentLevel);\n return block\n .split(\"\\n\")\n .map(line => `${prefix}${line}`)\n .join(\"\\n\");\n}\n\nexport function createPageInfoTool(): ToolDefinition {\n return {\n name: \"page_info\",\n description: [\n \"Get information about the current page.\",\n \"Actions: get_url, get_title, get_selection (selected text),\",\n \"get_viewport (size & scroll), snapshot (DOM structure), query_all (find all matching elements).\",\n ].join(\" \"),\n\n schema: Type.Object({\n action: Type.String({\n description:\n \"Info action: get_url | get_title | get_selection | get_viewport | snapshot | query_all\",\n }),\n selector: Type.Optional(\n Type.String({ description: \"CSS selector for query_all action\" }),\n ),\n maxDepth: Type.Optional(\n Type.Number({ description: \"Max depth for snapshot (default: 12)\" }),\n ),\n viewportOnly: Type.Optional(\n Type.Boolean({ description: \"Only snapshot elements visible in viewport (default: true)\" }),\n ),\n pruneLayout: Type.Optional(\n Type.Boolean({ description: \"Collapse empty layout containers like div/span (default: true)\" }),\n ),\n maxNodes: Type.Optional(\n Type.Number({ description: \"Maximum nodes to include in snapshot (default: 220)\" }),\n ),\n maxChildren: Type.Optional(\n Type.Number({ description: \"Maximum children per element (default: 25)\" }),\n ),\n maxTextLength: Type.Optional(\n Type.Number({ description: \"Maximum text length per node (default: 40)\" }),\n ),\n expandOptionLists: Type.Optional(\n Type.Boolean({ description: \"Expand option-list containers to avoid child truncation (default: false)\" }),\n ),\n expandChildrenRefs: Type.Optional(\n Type.Array(Type.String({ description: \"Hash refs to expand child truncation for (e.g. #abc123)\" })),\n ),\n expandedChildrenLimit: Type.Optional(\n Type.Number({ description: \"Child limit for expandChildrenRefs nodes (default: 120, max: 300)\" }),\n ),\n }),\n\n execute: async (params): Promise<ToolCallResult> => {\n const action = params.action as string;\n\n try {\n switch (action) {\n case \"get_url\":\n return { content: window.location.href };\n\n case \"get_title\":\n return { content: document.title || \"(无标题)\" };\n\n case \"get_selection\": {\n // 获取用户当前选中的文本\n const selection = window.getSelection();\n const text = selection?.toString().trim() ?? \"\";\n return { content: text || \"(未选中任何文本)\" };\n }\n\n case \"get_viewport\": {\n // 获取视口和滚动信息\n const info = {\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n scrollX: window.scrollX,\n scrollY: window.scrollY,\n pageWidth: document.documentElement.scrollWidth,\n pageHeight: document.documentElement.scrollHeight,\n };\n return { content: JSON.stringify(info, null, 2) };\n }\n\n case \"snapshot\": {\n // 生成 DOM 快照 — AI 理解当前页面结构的主要方式\n const maxDepth = (params.maxDepth as number) ?? 12;\n const viewportOnly = (params.viewportOnly as boolean) ?? true;\n const pruneLayout = (params.pruneLayout as boolean) ?? true;\n const maxNodes = (params.maxNodes as number) ?? 220;\n const maxChildren = (params.maxChildren as number) ?? 25;\n const maxTextLength = (params.maxTextLength as number) ?? 40;\n const expandOptionLists = (params.expandOptionLists as boolean) ?? false;\n const expandChildrenRefs = Array.isArray(params.expandChildrenRefs)\n ? (params.expandChildrenRefs as unknown[]).filter((ref): ref is string => typeof ref === \"string\")\n : undefined;\n const expandedChildrenLimit = typeof params.expandedChildrenLimit === \"number\"\n ? params.expandedChildrenLimit as number\n : undefined;\n const snapshot = generateSnapshot(document.body, {\n maxDepth,\n viewportOnly,\n pruneLayout,\n maxNodes,\n maxChildren,\n maxTextLength,\n expandOptionLists,\n expandChildrenRefs,\n expandedChildrenLimit,\n refStore: getActiveRefStore(),\n });\n return { content: snapshot };\n }\n\n case \"query_all\": {\n // 查询所有匹配元素\n const selector = params.selector as string;\n if (!selector) return { content: \"缺少 selector 参数\" };\n return { content: queryAllElements(selector) };\n }\n\n default:\n return { content: `未知的页面信息动作: ${action}` };\n }\n } catch (err) {\n return {\n content: `页面信息操作 \"${action}\" 失败: ${err instanceof Error ? err.message : String(err)}`,\n details: { error: true, action },\n };\n }\n },\n };\n}\n","/**\n * Navigate Tool — 页面导航工具(增强版)。\n *\n * 支持 5 种动作:\n * goto — 跳转到指定 URL\n * back — 浏览器后退\n * forward — 浏览器前进\n * reload — 刷新当前页面\n * scroll — 滚动页面到指定位置或元素(支持 RefStore hash ID + 多策略对齐)\n */\nimport { Type } from \"@sinclair/typebox\";\nimport type { ToolDefinition, ToolCallResult } from \"../../core/tool-registry.js\";\nimport { getActiveRefStore } from \"./dom-tool.js\";\n\n/** 解析 selector(支持 RefStore hash ID 和 CSS 选择器) */\nfunction resolveElement(selector: string): Element | null {\n if (selector.startsWith(\"#\")) {\n const store = getActiveRefStore();\n if (store) {\n const id = selector.slice(1);\n if (store.has(id)) return store.get(id) ?? null;\n }\n }\n try { return document.querySelector(selector); } catch { return null; }\n}\n\nexport function createNavigateTool(): ToolDefinition {\n return {\n name: \"navigate\",\n description: [\n \"Navigate the current page.\",\n \"Actions: goto (open URL), back, forward, reload, scroll (to position or element).\",\n \"scroll supports hash ID from snapshot (e.g. #r0) or CSS selector.\",\n ].join(\" \"),\n\n schema: Type.Object({\n action: Type.String({\n description: \"Navigation action: goto | back | forward | reload | scroll\",\n }),\n url: Type.Optional(Type.String({ description: \"URL for goto action\" })),\n selector: Type.Optional(\n Type.String({ description: \"Element ref ID from snapshot (e.g. #r0) or CSS selector for scroll action\" }),\n ),\n x: Type.Optional(Type.Number({ description: \"Horizontal scroll position (pixels)\" })),\n y: Type.Optional(Type.Number({ description: \"Vertical scroll position (pixels)\" })),\n }),\n\n execute: async (params): Promise<ToolCallResult> => {\n const action = params.action as string;\n\n try {\n switch (action) {\n case \"goto\": {\n const url = params.url as string;\n if (!url) return { content: \"缺少 url 参数\" };\n window.location.href = url;\n return { content: `正在导航到 ${url}` };\n }\n\n case \"back\": {\n window.history.back();\n return { content: \"已后退\" };\n }\n\n case \"forward\": {\n window.history.forward();\n return { content: \"已前进\" };\n }\n\n case \"reload\": {\n window.location.reload();\n return { content: \"正在刷新页面\" };\n }\n\n case \"scroll\": {\n const selector = params.selector as string | undefined;\n\n if (selector) {\n const el = resolveElement(selector);\n if (!el) return { content: `未找到元素 \"${selector}\"` };\n // 尝试 scrollIntoViewIfNeeded(Chrome),回退 scrollIntoView center\n if (\"scrollIntoViewIfNeeded\" in el) {\n (el as HTMLElement & { scrollIntoViewIfNeeded: (c?: boolean) => void }).scrollIntoViewIfNeeded(true);\n } else {\n el.scrollIntoView({ behavior: \"smooth\", block: \"center\" });\n }\n return { content: `已滚动到元素 \"${selector}\"` };\n }\n\n const x = (params.x as number) ?? 0;\n const y = (params.y as number) ?? 0;\n window.scrollTo({ left: x, top: y, behavior: \"smooth\" });\n return { content: `已滚动到 (${x}, ${y})` };\n }\n\n default:\n return { content: `未知的导航动作: ${action}` };\n }\n } catch (err) {\n return {\n content: `导航操作 \"${action}\" 失败: ${err instanceof Error ? err.message : String(err)}`,\n details: { error: true, action },\n };\n }\n },\n };\n}\n","/**\n * Wait Tool 等待工具 / Wait utility for DOM conditions.\n *\n * 支持动作 / Supported actions:\n * - wait_for_selector: 等待选择器达到状态 / wait selector state\n * - wait_for_hidden: 等待元素隐藏或移除 / wait element hidden or detached\n * - wait_for_text: 等待页面出现文本 / wait text appears in page\n * - wait_for_stable: 等待 DOM 进入静默窗口 / wait DOM quiet window\n *\n * 说明 / Notes:\n * - hash selector(如 #abc123)优先通过 RefStore 解析。\n * - 可见性语义与 dom-tool 保持一致(参考 Playwright 风格)。\n */\nimport { Type } from \"@sinclair/typebox\";\nimport type { ToolDefinition, ToolCallResult } from \"../../core/tool-registry.js\";\nimport { getActiveRefStore } from \"./dom-tool.js\";\n\nconst DEFAULT_TIMEOUT = 6_000;\nconst POLL_INTERVAL_MS = 80;\nconst STABLE_TICK_MS = 50;\nconst OBSERVER_OPTIONS: MutationObserverInit = {\n childList: true,\n subtree: true,\n attributes: true,\n characterData: true,\n};\nconst TEXT_OBSERVER_OPTIONS: MutationObserverInit = {\n childList: true,\n subtree: true,\n characterData: true,\n};\n\ntype SelectorState = \"attached\" | \"visible\" | \"hidden\" | \"detached\";\n\n/**\n * 可见性判定 / Visibility check.\n *\n * 与 dom-tool 保持一致,处理 display:contents、visibility、opacity、零尺寸等场景。\n */\nfunction isVisible(el: Element): boolean {\n if (!(el instanceof HTMLElement || el instanceof SVGElement)) return false;\n if (!el.isConnected) return false;\n const style = window.getComputedStyle(el);\n\n if (style.display === \"contents\") {\n for (let child = el.firstChild; child; child = child.nextSibling) {\n if (child.nodeType === Node.ELEMENT_NODE && isVisible(child as Element)) return true;\n if (child.nodeType === Node.TEXT_NODE) {\n const range = document.createRange();\n range.selectNodeContents(child);\n const rects = range.getClientRects();\n for (let i = 0; i < rects.length; i++) {\n if (rects[i].width > 0 && rects[i].height > 0) return true;\n }\n }\n }\n return false;\n }\n if (style.display === \"none\") return false;\n if (typeof el.checkVisibility === \"function\") {\n if (!el.checkVisibility()) return false;\n }\n if (style.visibility !== \"visible\") return false;\n if (style.opacity === \"0\") return false;\n const rect = el.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n}\n\n/**\n * 解析选择器 / Resolve selector.\n *\n * 先尝试 RefStore hash,再回退到 document.querySelector。\n */\nfunction resolveSelector(selector: string): Element | null {\n if (selector.startsWith(\"#\")) {\n const store = getActiveRefStore();\n if (store) {\n const id = selector.slice(1);\n if (store.has(id)) return store.get(id) ?? null;\n }\n }\n try { return document.querySelector(selector); } catch { return null; }\n}\n\n/**\n * 计算选择器状态 / Evaluate selector state.\n *\n * @returns matched 表示是否达到目标状态;element 为当前命中的元素(如果存在)。\n */\nfunction evaluateSelectorState(selector: string, state: SelectorState): { matched: boolean; element?: Element } {\n const el = resolveSelector(selector) ?? undefined;\n switch (state) {\n case \"attached\":\n return { matched: Boolean(el), element: el };\n case \"visible\":\n return { matched: Boolean(el && isVisible(el)), element: el };\n case \"hidden\":\n return { matched: !el || !isVisible(el), element: el };\n case \"detached\":\n return { matched: !el, element: el };\n default:\n return { matched: false };\n }\n}\n\n/**\n * 等待选择器达到指定状态 / Wait selector reaches state.\n *\n * 策略:轮询 + MutationObserver 双通道,既保证及时性也降低漏检概率。\n */\nfunction waitForSelectorState(\n selector: string,\n state: SelectorState,\n timeoutMs: number,\n): Promise<{ element?: Element }> {\n return new Promise((resolve, reject) => {\n let finished = false;\n\n const finish = (handler: () => void): void => {\n if (finished) return;\n finished = true;\n clearTimeout(timer);\n clearInterval(interval);\n observer.disconnect();\n handler();\n };\n\n const check = (): void => {\n let result: { matched: boolean; element?: Element };\n try {\n result = evaluateSelectorState(selector, state);\n } catch {\n finish(() => reject(new Error(`选择器语法错误: ${selector}`)));\n return;\n }\n if (result.matched) {\n finish(() => resolve({ element: result.element }));\n }\n };\n\n const timer = setTimeout(() => {\n finish(() => reject(new Error(`等待 \"${selector}\" 达到状态 \"${state}\" 超时 (${timeoutMs}ms)`)));\n }, timeoutMs);\n\n const interval = setInterval(check, POLL_INTERVAL_MS);\n const observer = new MutationObserver(check);\n observer.observe(document.body, OBSERVER_OPTIONS);\n\n check();\n });\n}\n\n/**\n * 等待文本出现 / Wait text appears.\n *\n * 先做一次即时检查,再监听 DOM 变化。\n */\nfunction waitForText(text: string, timeoutMs: number): Promise<void> {\n return new Promise((resolve, reject) => {\n // 先检查是否已包含\n if (document.body.textContent?.includes(text)) {\n resolve();\n return;\n }\n\n const timer = setTimeout(() => {\n observer.disconnect();\n reject(new Error(`等待文本 \"${text}\" 出现超时 (${timeoutMs}ms)`));\n }, timeoutMs);\n\n const observer = new MutationObserver(() => {\n if (document.body.textContent?.includes(text)) {\n clearTimeout(timer);\n observer.disconnect();\n resolve();\n }\n });\n\n observer.observe(document.body, TEXT_OBSERVER_OPTIONS);\n });\n}\n\n/**\n * 等待 DOM 稳定 / Wait DOM stable.\n *\n * 定义:quietMs 窗口内没有任何 MutationObserver 事件。\n */\nfunction waitForDomStable(timeoutMs: number, quietMs: number): Promise<void> {\n return new Promise((resolve, reject) => {\n const startedAt = Date.now();\n let lastMutationAt = Date.now();\n\n const finish = (ok: boolean, err?: Error): void => {\n clearInterval(tick);\n observer.disconnect();\n if (ok) resolve();\n else reject(err ?? new Error(\"等待页面稳定失败\"));\n };\n\n const observer = new MutationObserver(() => {\n lastMutationAt = Date.now();\n });\n\n observer.observe(document.body, OBSERVER_OPTIONS);\n\n const tick = setInterval(() => {\n const now = Date.now();\n if (now - startedAt > timeoutMs) {\n finish(false, new Error(`等待页面稳定超时 (${timeoutMs}ms)`));\n return;\n }\n if (now - lastMutationAt >= quietMs) {\n finish(true);\n }\n }, STABLE_TICK_MS);\n });\n}\n\nexport function createWaitTool(): ToolDefinition {\n return {\n name: \"wait\",\n description: [\n \"Wait for DOM changes on the current page.\",\n \"Actions: wait_for_selector (element appears), wait_for_hidden (element disappears),\",\n \"wait_for_text (specific text appears in page), wait_for_stable (DOM stops changing).\",\n ].join(\" \"),\n\n schema: Type.Object({\n action: Type.String({\n description: \"Wait action: wait_for_selector | wait_for_hidden | wait_for_text | wait_for_stable\",\n }),\n selector: Type.Optional(\n Type.String({ description: \"CSS selector for wait_for_selector/wait_for_hidden\" }),\n ),\n state: Type.Optional(\n Type.String({ description: \"Selector state for wait_for_selector: attached | visible | hidden | detached (default: attached)\" }),\n ),\n text: Type.Optional(\n Type.String({ description: \"Text to wait for in wait_for_text\" }),\n ),\n timeout: Type.Optional(\n Type.Number({ description: \"Timeout in milliseconds (default: 6000)\" }),\n ),\n quietMs: Type.Optional(\n Type.Number({ description: \"Quiet window for wait_for_stable in milliseconds (default: 300)\" }),\n ),\n }),\n\n execute: async (params): Promise<ToolCallResult> => {\n const action = params.action as string;\n const timeoutMs = (params.timeout as number) ?? DEFAULT_TIMEOUT;\n\n try {\n switch (action) {\n case \"wait_for_selector\": {\n const selector = params.selector as string;\n if (!selector) return { content: \"缺少 selector 参数\" };\n const state = (params.state as SelectorState | undefined) ?? \"attached\";\n if (![\"attached\", \"visible\", \"hidden\", \"detached\"].includes(state)) {\n return { content: `无效 state: ${state}` };\n }\n const result = await waitForSelectorState(selector, state, timeoutMs);\n if (state === \"attached\" || state === \"visible\") {\n const tag = result.element?.tagName?.toLowerCase();\n return { content: `元素 \"${selector}\" 已达到状态 \"${state}\"${tag ? ` (${tag})` : \"\"}` };\n }\n return { content: `元素 \"${selector}\" 已达到状态 \"${state}\"` };\n }\n\n case \"wait_for_hidden\": {\n const selector = params.selector as string;\n if (!selector) return { content: \"缺少 selector 参数\" };\n await waitForSelectorState(selector, \"hidden\", timeoutMs);\n return { content: `元素 \"${selector}\" 已隐藏或消失` };\n }\n\n case \"wait_for_text\": {\n const text = params.text as string;\n if (!text) return { content: \"缺少 text 参数\" };\n await waitForText(text, timeoutMs);\n return { content: `文本 \"${text}\" 已出现` };\n }\n\n case \"wait_for_stable\": {\n const quietMs = Math.max(50, Math.floor((params.quietMs as number) ?? 300));\n await waitForDomStable(timeoutMs, quietMs);\n return { content: `页面已稳定(静默窗口 ${quietMs}ms)` };\n }\n\n default:\n return { content: `未知的等待动作: ${action}` };\n }\n } catch (err) {\n return {\n content: `等待操作 \"${action}\" 失败: ${err instanceof Error ? err.message : String(err)}`,\n details: { error: true, action },\n };\n }\n },\n };\n}\n","/**\n * Evaluate Tool — 在页面上下文中执行任意 JavaScript 表达式。\n *\n * 替代 Playwright 的 page.evaluate()。\n * 运行环境:浏览器 Content Script。\n *\n * 这是最灵活的工具 — 当其他 tools 无法满足需求时,\n * AI 可以直接编写 JS 代码来操作页面。\n *\n * 支持 2 种动作:\n * evaluate — 执行 JS 表达式并返回结果\n * evaluate_handle — 执行 JS 并返回序列化的 DOM 信息\n */\nimport { Type } from \"@sinclair/typebox\";\nimport type { ToolDefinition, ToolCallResult } from \"../../core/tool-registry.js\";\n\n/**\n * 安全执行 JS 表达式,捕获错误并序列化结果。\n */\nfunction safeEvaluate(expression: string): { result?: unknown; error?: string } {\n try {\n // 使用 Function 构造器代替 eval,避免污染当前作用域\n const fn = new Function(`\"use strict\"; return (${expression});`);\n const result = fn();\n return { result };\n } catch {\n // 如果作为表达式失败,尝试作为语句块执行\n try {\n const fn = new Function(`\"use strict\"; ${expression}`);\n const result = fn();\n return { result };\n } catch (err2) {\n return { error: err2 instanceof Error ? err2.message : String(err2) };\n }\n }\n}\n\n/**\n * 将执行结果序列化为字符串(处理 DOM 元素、循环引用等)。\n */\nfunction serializeResult(value: unknown): string {\n if (value === undefined) return \"undefined\";\n if (value === null) return \"null\";\n\n // DOM 元素 → 返回 outerHTML 片段\n if (value instanceof Element) {\n const tag = value.tagName.toLowerCase();\n const id = value.id ? `#${value.id}` : \"\";\n const text = value.textContent?.trim().slice(0, 100) ?? \"\";\n return `<${tag}${id}> \"${text}\"`;\n }\n\n // NodeList / HTMLCollection → 逐个序列化\n if (value instanceof NodeList || value instanceof HTMLCollection) {\n const items = Array.from(value).map((el, i) => ` ${i}: ${serializeResult(el)}`);\n return `[${value.length} elements]\\n${items.join(\"\\n\")}`;\n }\n\n // 普通值 → JSON 序列化\n try {\n return JSON.stringify(value, null, 2);\n } catch {\n return String(value);\n }\n}\n\nexport function createEvaluateTool(): ToolDefinition {\n return {\n name: \"evaluate\",\n description: [\n \"Execute JavaScript code in the current page context.\",\n \"Use this when other tools cannot accomplish the task.\",\n \"Can access document, window, and all page APIs.\",\n ].join(\" \"),\n\n schema: Type.Object({\n expression: Type.String({\n description:\n \"JavaScript expression or code block to execute. Has access to document, window, etc.\",\n }),\n }),\n\n execute: async (params): Promise<ToolCallResult> => {\n const expression = params.expression as string;\n if (!expression) return { content: \"缺少 expression 参数\" };\n\n const { result, error } = safeEvaluate(expression);\n\n if (error) {\n return {\n content: `JS 执行错误: ${error}`,\n details: { error: true, expression },\n };\n }\n\n return { content: serializeResult(result) };\n },\n };\n}\n","/**\n * RefStore — 快照 hash ID 与 DOM 元素的映射表。\n *\n * 快照生成时,根据元素的 DOM 路径 + 页面 URL 生成确定性 hash ID,\n * 同时保存 ID → Element 的映射。AI 使用 hash ID 作为 selector 定位元素,\n * 免去超长 XPath 路径,大幅减少 token 消耗。\n *\n * 优势:\n * - **确定性**:同一元素无论快照顺序,始终得到相同 ID\n * - **并发安全**:多次快照不会产生 ID 冲突\n * - **跨页面隔离**:URL hash 作为命名空间,不同页面元素 ID 互不碰撞\n *\n * 生命周期:每次 WebAgent.chat() 调用时创建,对话结束后清空。\n *\n * 使用方:\n * page-info-tool.ts — generateSnapshot() 写入映射\n * dom-tool.ts — queryElement() 读取映射\n * index.ts — WebAgent 持有实例,管理生命周期\n */\n\n/**\n * FNV-1a 32-bit hash — 简单高效的字符串散列。\n * 分布均匀,碰撞率低,适合生成短 ID。\n */\nfunction fnv1a(str: string): number {\n let h = 0x811c9dc5; // FNV offset basis\n for (let i = 0; i < str.length; i++) {\n h ^= str.charCodeAt(i);\n h = Math.imul(h, 0x01000193); // FNV prime\n }\n return h >>> 0; // 转为无符号 32-bit\n}\n\n/**\n * hash ID → DOM 元素的映射存储。\n *\n * - `set(el, path)` 由快照生成时调用,返回确定性 hash ID\n * - `get(id)` 由 dom-tool 查询时调用,根据 hash ID 取回元素\n * - `has(id)` 检查 ID 是否存在(用于 selector 类型判断)\n * - `clear()` 每次对话结束后清空\n */\nexport class RefStore {\n private map = new Map<string, Element>();\n /** 页面 URL 的 hash 前缀,用于跨页面命名空间隔离 */\n private urlKey: string;\n\n /**\n * @param url 当前页面 URL(可选)。传入后作为 hash 命名空间,\n * 使不同页面的相同 DOM 路径产生不同 ID。\n */\n constructor(url?: string) {\n this.urlKey = url ?? \"\";\n }\n\n /**\n * 注册一个元素,返回确定性 hash ID。\n * 相同 URL + path 始终产生相同 ID(并发安全)。\n *\n * @param el DOM 元素引用\n * @param path 元素的 XPath-like 路径(如 \"/body/div[1]/main/button\")\n */\n set(el: Element, path: string): string {\n const baseId = fnv1a(this.urlKey + path).toString(36);\n let id = baseId;\n // 极小概率碰撞处理:不同 path 映射到相同 hash 时追加后缀\n let suffix = 2;\n while (this.map.has(id) && this.map.get(id) !== el) {\n id = baseId + suffix++;\n }\n this.map.set(id, el);\n return id;\n }\n\n /**\n * 根据 hash ID 获取 DOM 元素。\n * 返回 Element 或 undefined(ID 不存在或元素已被移除)。\n */\n get(id: string): Element | undefined {\n return this.map.get(id);\n }\n\n /** 检查 hash ID 是否存在 */\n has(id: string): boolean {\n return this.map.has(id);\n }\n\n /** 清空所有映射 */\n clear(): void {\n this.map.clear();\n }\n\n /**\n * 重置映射表:清空所有映射,并可选更新 URL 命名空间。\n *\n * 用于页面导航后刷新 RefStore:旧的 hash ID → Element 映射已失效,\n * 需要用新 URL 重新生成确定性 hash。\n *\n * @param url 新的页面 URL(不传则保持原 URL 命名空间)\n */\n reset(url?: string): void {\n this.map.clear();\n if (url !== undefined) {\n this.urlKey = url;\n }\n }\n\n /** 当前映射数量 */\n get size(): number {\n return this.map.size;\n }\n}\n","/**\n * Web Tools 消息通信桥接层。\n *\n * 解决 Chrome Extension 的作用域隔离问题:\n *\n * Service Worker (后台) Content Script (页面)\n * ┌──────────────────┐ ┌──────────────────────┐\n * │ agent-core │ │ document / window │\n * │ tool-registry │ chrome.tabs │ │\n * │ │ .sendMessage() │ DOM 操作实际执行 │\n * │ tool.execute() │ ─────────────────► │ handleToolMessage() │\n * │ ↓ │ │ ↓ │\n * │ sendToContent() │ ◄───────────────── │ 返回执行结果 │\n * └──────────────────┘ response └──────────────────────┘\n *\n * 使用方式:\n * Service Worker 端:\n * import { createProxyExecutor } from \"./messaging.js\";\n * const execute = createProxyExecutor();\n * // execute 会把调用转发到 content script\n *\n * Content Script 端:\n * import { registerToolHandler } from \"./messaging.js\";\n * registerToolHandler(actualExecutors);\n * // 监听来自 service worker 的工具调用请求\n */\n\n// ─── 消息类型定义 ───\n\n/** Service Worker → Content Script 的工具调用请求 */\nexport type ToolCallMessage = {\n type: \"AUTOPILOT_TOOL_CALL\";\n toolName: string;\n params: Record<string, unknown>;\n callId: string;\n};\n\n/** Content Script → Service Worker 的工具调用结果 */\nexport type ToolCallResponse = {\n type: \"AUTOPILOT_TOOL_RESULT\";\n callId: string;\n result: {\n content: string | Record<string, unknown>;\n details?: Record<string, unknown>;\n };\n};\n\n// ─── Service Worker 端(发送方) ───\n\n/**\n * 创建代理执行器 — 在 Service Worker 端使用。\n *\n * 它不直接执行 DOM 操作,而是通过 chrome.tabs.sendMessage\n * 把调用请求发给当前活动 tab 的 content script 执行。\n *\n * @returns execute 函数,签名与 ToolDefinition.execute 相同\n */\nexport function createProxyExecutor() {\n return async (\n toolName: string,\n params: Record<string, unknown>,\n ): Promise<{ content: string | Record<string, unknown>; details?: Record<string, unknown> }> => {\n const callId = `${toolName}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;\n\n // 获取当前活动 tab\n const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });\n if (!tab?.id) {\n return { content: \"错误:没有活动的浏览器标签页\" };\n }\n\n // 发送消息到 content script 并等待结果\n const message: ToolCallMessage = {\n type: \"AUTOPILOT_TOOL_CALL\",\n toolName,\n params,\n callId,\n };\n\n try {\n const response = await chrome.tabs.sendMessage(tab.id, message) as ToolCallResponse;\n return response.result;\n } catch (err) {\n return {\n content: `工具调用失败(content script 可能未加载): ${err instanceof Error ? err.message : String(err)}`,\n details: { error: true, toolName },\n };\n }\n };\n}\n\n// ─── Content Script 端(接收方) ───\n\n/** 工具执行器映射:toolName → execute 函数 */\nexport type ToolExecutorMap = Map<\n string,\n (params: Record<string, unknown>) => Promise<{\n content: string | Record<string, unknown>;\n details?: Record<string, unknown>;\n }>\n>;\n\n/**\n * 在 Content Script 端注册工具执行处理器。\n *\n * 监听来自 Service Worker 的 AUTOPILOT_TOOL_CALL 消息,\n * 根据 toolName 找到对应的执行函数,执行后返回结果。\n *\n * @param executors 工具名称 → 执行函数的映射\n */\nexport function registerToolHandler(executors: ToolExecutorMap): void {\n chrome.runtime.onMessage.addListener(\n (message: unknown, _sender: chrome.runtime.MessageSender, sendResponse: (response: ToolCallResponse) => void) => {\n // 只处理我们的消息类型\n const msg = message as ToolCallMessage;\n if (msg?.type !== \"AUTOPILOT_TOOL_CALL\") return false;\n\n const executor = executors.get(msg.toolName);\n if (!executor) {\n sendResponse({\n type: \"AUTOPILOT_TOOL_RESULT\",\n callId: msg.callId,\n result: { content: `未知工具: ${msg.toolName}` },\n });\n return true; // 同步返回 true 表示我们会异步 sendResponse\n }\n\n // 异步执行工具并返回结果\n executor(msg.params)\n .then((result) => {\n sendResponse({\n type: \"AUTOPILOT_TOOL_RESULT\",\n callId: msg.callId,\n result,\n });\n })\n .catch((err) => {\n sendResponse({\n type: \"AUTOPILOT_TOOL_RESULT\",\n callId: msg.callId,\n result: {\n content: `工具 ${msg.toolName} 执行异常: ${err instanceof Error ? err.message : String(err)}`,\n details: { error: true },\n },\n });\n });\n\n return true; // 告诉 Chrome 我们会异步调用 sendResponse\n },\n );\n}\n","/**\n * WebAgent — 浏览器端 AI Agent 类。\n *\n * 封装了完整的 Agent 能力,可在浏览器中独立运行:\n * - 对话(chat) → 发消息、获取 AI 回复\n * - 工具注册 → 注册内置 Web 工具或自定义工具\n * - 决策循环 → 复用 core/agent-loop.ts 的通用逻辑\n * - AI 连接 → 复用 core/ai-client.ts(基于 fetch,跨平台)\n *\n * 使用示例:\n * ```ts\n * const agent = new WebAgent({ token: \"ghp_xxx\", provider: \"copilot\" });\n * agent.registerTools(); // 注册内置 Web 工具\n * agent.callbacks.onText = (text) => console.log(text);\n *\n * const result = await agent.chat(\"获取页面标题\");\n * console.log(result.reply);\n * ```\n *\n * 架构位置:\n * ┌──────────────────────────────────────────────────┐\n * │ WebAgent(浏览器端入口) │\n * │ ┌──────────┐ ┌────────────┐ ┌──────────────┐ │\n * │ │ core/ │ │ core/ │ │ web/ │ │\n * │ │ ai-client│ │ agent-loop │ │ (DOM/导航等)│ │\n * │ │ (fetch) │ │ (通用循环) │ │ │ │\n * │ └──────────┘ └────────────┘ └──────────────┘ │\n * └──────────────────────────────────────────────────┘\n */\nimport {\n executeAgentLoop,\n type AgentLoopCallbacks,\n type AgentLoopResult,\n type RoundStabilityWaitOptions,\n wrapSnapshot,\n} from \"../core/agent-loop/index.js\";\nimport type { AIMessage } from \"../core/types.js\";\nimport { createAIClient } from \"../core/ai-client/index.js\";\nimport type { AIClient } from \"../core/types.js\";\nimport { ToolRegistry, type ToolDefinition } from \"../core/tool-registry.js\";\nimport { buildSystemPrompt } from \"../core/system-prompt.js\";\nimport { generateSnapshot, type SnapshotOptions } from \"./tools/page-info-tool.js\";\nimport { createDomTool, setActiveRefStore } from \"./tools/dom-tool.js\";\nimport { createNavigateTool } from \"./tools/navigate-tool.js\";\nimport { createPageInfoTool } from \"./tools/page-info-tool.js\";\nimport { createWaitTool } from \"./tools/wait-tool.js\";\nimport { createEvaluateTool } from \"./tools/evaluate-tool.js\";\nimport { RefStore } from \"./ref-store.js\";\nimport { installEventListenerTracking } from \"./event-listener-tracker.js\";\n\n// 默认安装全局事件监听追踪(幂等),用于快照输出 listeners 信号。\ninstallEventListenerTracking();\n\n// ─── 回调类型 ───\n\n/** WebAgent 事件回调(扩展 AgentLoopCallbacks,增加快照事件) */\nexport type WebAgentCallbacks = AgentLoopCallbacks & {\n /** 自动快照生成完成时触发 */\n onSnapshot?: (snapshot: string) => void;\n};\n\n// ─── 配置 ───\n\nexport type WebAgentOptions = {\n /**\n * 自定义 AI 客户端实例(可选)。\n *\n * 传入后将直接使用该实例进行对话,忽略 token / provider / model / baseURL。\n * 支持 BaseAIClient 或任何实现 AIClient 接口的对象。\n *\n * ```ts\n * const client = new BaseAIClient({ chatHandler: async (params) => { ... } });\n * const agent = new WebAgent({ client });\n * ```\n */\n client?: AIClient;\n /** API 认证 Token (GitHub PAT / OpenAI key / Anthropic key) */\n token?: string;\n /** AI 提供商: \"copilot\" | \"openai\" | \"anthropic\" | \"deepseek\" | \"doubao\" | \"qwen\"(默认 \"copilot\") */\n provider?: string;\n /** 模型名称(默认 \"gpt-4o\") */\n model?: string;\n /** 自定义 API 基础 URL(可选,覆盖 provider 默认值) */\n baseURL?: string;\n /** 是否启用流式输出(SSE)。默认 true;false 时使用 JSON 非流式响应。 */\n stream?: boolean;\n /** 是否启用干运行模式 */\n dryRun?: boolean;\n /**\n * 系统提示词注册项。\n * - string:按默认 key 注册一条\n * - Record<string, string>:按 key 批量注册多条\n */\n systemPrompt?: string | Record<string, string>;\n /** 最大工具调用轮次(默认 10) */\n maxRounds?: number;\n /** 是否启用多轮对话记忆(默认 false) */\n memory?: boolean;\n /** 是否在每次对话前自动生成页面快照(默认 true) */\n autoSnapshot?: boolean;\n /** 快照选项(视口裁剪、智能剪枝等,autoSnapshot 开启时生效) */\n snapshotOptions?: SnapshotOptions;\n /** 轮次后稳定等待(加载态 + DOM 静默)配置 */\n roundStabilityWait?: RoundStabilityWaitOptions;\n};\n\n// ─── WebAgent 类 ───\n\nexport class WebAgent {\n /** 默认系统提示词 key(兼容旧版 setSystemPrompt(prompt))。 */\n private static readonly DEFAULT_SYSTEM_PROMPT_KEY = \"default\";\n /** 默认内置工具名(注册后受保护,不允许删除)。 */\n private static readonly DEFAULT_TOOL_NAMES = [\"dom\", \"navigate\", \"page_info\", \"wait\", \"evaluate\"] as const;\n\n /** 用户传入的自定义 AI 客户端实例(优先级高于 token/provider) */\n private client?: AIClient;\n private token: string;\n private provider: string;\n private model: string;\n private baseURL?: string;\n private stream: boolean;\n private dryRun: boolean;\n private maxRounds: number;\n /** system prompt 注册表(key -> prompt 文本)。 */\n private systemPromptRegistry = new Map<string, string>();\n /** 受保护工具集合(默认工具)。 */\n private protectedToolNames = new Set<string>();\n\n /** 多轮对话记忆开关 */\n private memory: boolean;\n /** 对话历史(memory 开启时自动累积) */\n private history: AIMessage[] = [];\n /** 自动快照开关 */\n private autoSnapshot: boolean;\n /** 快照选项 */\n private snapshotOptions: SnapshotOptions;\n /** 轮次后稳定等待配置 */\n private roundStabilityWait?: RoundStabilityWaitOptions;\n\n /** 工具注册表实例 — 每个 WebAgent 拥有独立的工具集 */\n private registry = new ToolRegistry();\n\n /** 事件回调 — 绑定后可实时获取 Agent 进度,用于 UI 展示 */\n callbacks: WebAgentCallbacks = {};\n\n constructor(options: WebAgentOptions) {\n this.client = options.client;\n this.token = options.token || \"\";\n this.provider = options.provider ?? \"copilot\";\n this.model = options.model ?? \"gpt-4o\";\n this.baseURL = options.baseURL;\n this.stream = options.stream ?? true;\n this.dryRun = options.dryRun ?? false;\n this.maxRounds = options.maxRounds ?? 40;\n this.memory = options.memory ?? false;\n this.autoSnapshot = options.autoSnapshot ?? true;\n this.snapshotOptions = options.snapshotOptions ?? {};\n this.roundStabilityWait = options.roundStabilityWait;\n\n if (typeof options.systemPrompt === \"string\") {\n this.setSystemPrompt(options.systemPrompt);\n } else if (options.systemPrompt && typeof options.systemPrompt === \"object\") {\n this.setSystemPrompts(options.systemPrompt);\n }\n }\n\n // ─── 工具管理 ───\n\n /** 注册所有内置 Web 工具(dom, navigate, page_info, wait, evaluate) */\n registerTools(): void {\n this.registry.register(createDomTool());\n this.registry.register(createNavigateTool());\n this.registry.register(createPageInfoTool());\n this.registry.register(createWaitTool());\n this.registry.register(createEvaluateTool());\n\n for (const name of WebAgent.DEFAULT_TOOL_NAMES) {\n this.protectedToolNames.add(name);\n }\n }\n\n /** 注册一个自定义工具 */\n registerTool(tool: ToolDefinition): void {\n this.registry.register(tool);\n }\n\n /**\n * 删除一个已注册工具。\n * - 默认内置工具(registerTools 注册)不允许删除\n * - 返回 true 表示删除成功,false 表示不存在或受保护\n */\n removeTool(name: string): boolean {\n if (this.protectedToolNames.has(name)) return false;\n return this.registry.unregister(name);\n }\n\n /** 检查工具是否已注册。 */\n hasTool(name: string): boolean {\n return this.registry.has(name);\n }\n\n /** 获取当前所有已注册工具名。 */\n getToolNames(): string[] {\n return this.registry.getDefinitions().map(tool => tool.name);\n }\n\n /**\n * 删除所有“非默认”工具。\n * 返回值为本次被删除的工具名数组。\n */\n clearCustomTools(): string[] {\n const removed: string[] = [];\n for (const tool of this.registry.getDefinitions()) {\n if (this.protectedToolNames.has(tool.name)) continue;\n if (this.registry.unregister(tool.name)) {\n removed.push(tool.name);\n }\n }\n return removed;\n }\n\n /** 获取所有已注册的工具定义列表 */\n getTools(): ToolDefinition[] {\n return this.registry.getDefinitions();\n }\n\n // ─── 配置修改 ───\n\n /** 设置 API Token */\n setToken(token: string): void {\n this.token = token;\n }\n\n /**\n * 设置自定义 AI 客户端实例。\n *\n * 传入后将优先使用该实例进行对话,忽略 token / provider / model / baseURL。\n * 传入 undefined 可恢复使用内置客户端。\n */\n setClient(client: AIClient | undefined): void {\n this.client = client;\n }\n\n /** 设置 AI 提供商 */\n setProvider(provider: string): void {\n this.provider = provider;\n }\n\n /** 设置模型 */\n setModel(model: string): void {\n this.model = model;\n }\n\n /** 设置是否启用流式输出(SSE) */\n setStream(enabled: boolean): void {\n this.stream = enabled;\n }\n\n /** 获取当前流式输出开关状态 */\n getStream(): boolean {\n return this.stream;\n }\n\n /** 切换干运行模式 */\n setDryRun(enabled: boolean): void {\n this.dryRun = enabled;\n }\n\n /**\n * 注册系统提示词。\n * - setSystemPrompt(prompt):使用默认 key 注册\n * - setSystemPrompt(key, prompt):按指定 key 注册\n */\n setSystemPrompt(prompt: string): void;\n setSystemPrompt(key: string, prompt: string): void;\n setSystemPrompt(keyOrPrompt: string, maybePrompt?: string): void {\n const key = maybePrompt === undefined\n ? WebAgent.DEFAULT_SYSTEM_PROMPT_KEY\n : keyOrPrompt.trim();\n const prompt = maybePrompt === undefined ? keyOrPrompt : maybePrompt;\n\n if (!key) throw new Error(\"system prompt 的 key 不能为空\");\n const value = prompt.trim();\n if (!value) throw new Error(\"system prompt 不能为空\");\n\n this.systemPromptRegistry.set(key, value);\n }\n\n /** 批量注册系统提示词(key -> prompt)。 */\n setSystemPrompts(prompts: Record<string, string>): void {\n for (const [key, prompt] of Object.entries(prompts)) {\n this.setSystemPrompt(key, prompt);\n }\n }\n\n /** 注销指定 key 的系统提示词。 */\n removeSystemPrompt(key: string): boolean {\n return this.systemPromptRegistry.delete(key);\n }\n\n /** 只保留指定 key 的系统提示词,其余全部删除。 */\n keepOnlySystemPrompt(key: string): boolean {\n if (!this.systemPromptRegistry.has(key)) return false;\n const value = this.systemPromptRegistry.get(key)!;\n this.systemPromptRegistry.clear();\n this.systemPromptRegistry.set(key, value);\n return true;\n }\n\n /** 获取当前已注册的全部系统提示词(浅拷贝)。 */\n getSystemPrompts(): Record<string, string> {\n return Object.fromEntries(this.systemPromptRegistry.entries());\n }\n\n /** 删除全部系统提示词。 */\n clearSystemPrompts(): void {\n this.systemPromptRegistry.clear();\n }\n\n /** 开启或关闭多轮对话记忆 */\n setMemory(enabled: boolean): void {\n this.memory = enabled;\n if (!enabled) this.history = [];\n }\n\n /** 获取当前记忆开关状态 */\n getMemory(): boolean {\n return this.memory;\n }\n\n /** 开启或关闭自动快照 */\n setAutoSnapshot(enabled: boolean): void {\n this.autoSnapshot = enabled;\n }\n\n /** 获取当前自动快照开关状态 */\n getAutoSnapshot(): boolean {\n return this.autoSnapshot;\n }\n\n /** 设置快照选项(视口裁剪、智能剪枝等) */\n setSnapshotOptions(options: SnapshotOptions): void {\n this.snapshotOptions = options;\n }\n\n /** 获取当前快照选项 */\n getSnapshotOptions(): SnapshotOptions {\n return { ...this.snapshotOptions };\n }\n\n /** 清空对话历史(不影响记忆开关) */\n clearHistory(): void {\n this.history = [];\n }\n\n // ─── 核心能力 ───\n\n /**\n * 发送消息并获取 AI 回复(含完整工具调用循环)。\n *\n * 内部流程(全部复用 core):\n * 1. createAIClient() → 创建 fetch AI 客户端\n * 2. buildSystemPrompt() → 构建系统提示词\n * 3. executeAgentLoop() → 执行决策循环\n * 4. callbacks → 实时通知 UI\n */\n async chat(message: string): Promise<AgentLoopResult> {\n // 优先使用自定义 client,否则使用内置 createAIClient\n const client = this.client ?? this.createBuiltinClient();\n\n // 先构建基础系统提示词,再追加已注册的 system prompt 扩展。\n let systemPrompt = buildSystemPrompt({ tools: this.registry.getDefinitions() });\n if (this.systemPromptRegistry.size > 0) {\n const extensionText = Array.from(this.systemPromptRegistry.entries())\n .map(([key, prompt]) => `- [${key}]\\n${prompt}`)\n .join(\"\\n\\n\");\n systemPrompt += `\\n\\n## Registered System Prompt Extensions\\n${extensionText}`;\n }\n\n // ─── 自动快照:注入 system prompt,不污染对话历史 ───\n // 创建本次对话的 RefStore,快照结束后保持活跃,对话结束后清空\n const refStore = new RefStore(globalThis.location?.href);\n setActiveRefStore(refStore);\n let initialSnapshot: string | undefined;\n\n try {\n const snapshot = generateSnapshot(document.body, {\n maxDepth: 12,\n viewportOnly: false,\n maxNodes: 500,\n maxChildren: 30,\n ...this.snapshotOptions,\n refStore,\n });\n initialSnapshot = snapshot;\n if (this.autoSnapshot) {\n this.callbacks.onSnapshot?.(snapshot);\n }\n\n systemPrompt += wrapSnapshot(\n `\\n\\n## DOM Snapshot\\n\\`\\`\\`\\n${snapshot}\\n\\`\\`\\``,\n );\n } catch {\n // 快照失败不阻塞正常流程\n }\n\n // 包装回调:在恢复快照前重置 RefStore,确保新快照的 hash ID 有效\n const wrappedCallbacks: WebAgentCallbacks = {\n ...this.callbacks,\n onBeforeRecoverySnapshot: (newUrl?: string) => {\n // URL 变化 → 清空映射 + 更新 URL 命名空间\n // 元素定位失败 → 仅清空可能失效的映射(URL 不变)\n if (newUrl !== undefined) {\n refStore.reset(newUrl);\n } else {\n refStore.clear();\n }\n // 转发到用户回调(如有设置)\n this.callbacks.onBeforeRecoverySnapshot?.(newUrl);\n },\n };\n\n // 复用 core/agent-loop — 同一份决策循环\n const result = await executeAgentLoop({\n client,\n registry: this.registry,\n systemPrompt,\n message,\n initialSnapshot,\n history: this.memory ? this.history : undefined,\n dryRun: this.dryRun,\n maxRounds: this.maxRounds,\n roundStabilityWait: this.roundStabilityWait,\n callbacks: wrappedCallbacks,\n });\n\n // 记忆模式:累积对话历史供下次 chat() 使用\n if (this.memory) {\n this.history = result.messages;\n }\n\n // 对话结束,清空 RefStore\n refStore.clear();\n setActiveRefStore(undefined);\n\n return result;\n }\n\n // ─── 内部方法 ───\n\n /**\n * 创建内置 AI 客户端(基于 token / provider / model 配置)。\n *\n * @throws 未设置 token 时抛出 Error\n */\n private createBuiltinClient(): AIClient {\n if (!this.token) {\n throw new Error(\"未设置 Token,请先调用 setToken() 或传入自定义 client\");\n }\n return createAIClient({\n provider: this.provider,\n model: this.model,\n apiKey: this.token,\n baseURL: this.baseURL,\n stream: this.stream,\n });\n }\n}\n\n// ─── Re-exports ───\n// 从入口文件统一导出所有公共 API,消费方只需 import from \"agentpage\"\n\nexport {\n generateSnapshot,\n type SnapshotOptions,\n} from \"./tools/page-info-tool.js\";\nexport { createDomTool } from \"./tools/dom-tool.js\";\nexport { createNavigateTool } from \"./tools/navigate-tool.js\";\nexport { createPageInfoTool } from \"./tools/page-info-tool.js\";\nexport { createWaitTool } from \"./tools/wait-tool.js\";\nexport { createEvaluateTool } from \"./tools/evaluate-tool.js\";\nexport {\n createProxyExecutor,\n registerToolHandler,\n type ToolCallMessage,\n type ToolCallResponse,\n type ToolExecutorMap,\n} from \"./messaging.js\";\n"],"mappings":"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"}