agentpage 0.0.17 → 0.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1368,9 +1368,13 @@ function buildSystemPrompt(params = {}) {
1368
1368
  " Output: new remaining task after removing this-round actions.",
1369
1369
  "- Use only visible targets from snapshot. Use #hashID as selector. Do not guess CSS selectors.",
1370
1370
  "- Batch independent visible actions in one round. Do not split one form into many rounds unnecessarily.",
1371
+ "- Strict input order: before every fill/type/select_option, first click or focus the same target in the SAME round.",
1372
+ "- Fixed sequence examples: dom.click(#field) -> dom.fill(#field, \"text\"); dom.click(#select) -> dom.select_option(#select, ...).",
1373
+ "- Form batch rule: for one visible form, complete all independent fields in one round; do not fill one field then verify repeatedly.",
1371
1374
  "- If an action will change DOM (open modal, navigate), stop after that action batch and continue next round with new snapshot.",
1372
1375
  "- Do NOT call page_info (snapshot/query/get_url/get_title). Snapshot is already provided every round.",
1373
1376
  "- For dropdown/select, use dom action=select_option (or fill on select).",
1377
+ "- Verification whitelist: do NOT use get_text/get_attr to verify input/select values unless the user explicitly asks for verification.",
1374
1378
  "- Do NOT interact with AutoPilot UI unless user explicitly asks.",
1375
1379
  "",
1376
1380
  "## Output Contract",
@@ -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/index.ts","../src/core/tool-registry.ts","../src/core/system-prompt.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 = 2000;\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 辅助函数(中)/ Agent loop helpers (EN).\n *\n * 仅包含纯函数与无副作用工具。\n * Pure utilities only (no side effects).\n */\nimport type { ToolCallResult } from \"../tool-registry.js\";\nimport { DEFAULT_RECOVERY_WAIT_MS } from \"./constants.js\";\n\n/** 异步睡眠(中)/ Async sleep utility (EN). */\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/** 统一内容为字符串(中)/ Normalize tool content to string (EN). */\nexport function toContentString(content: ToolCallResult[\"content\"]): string {\n return typeof content === \"string\" ? content : JSON.stringify(content, null, 2);\n}\n\n/** 元素不存在判定(中)/ Detect element-not-found failure (EN). */\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/** 生成稳定调用键(中)/ Build stable key for a tool call (EN). */\nexport function buildToolCallKey(name: string, input: unknown): string {\n return `${name}:${JSON.stringify(input)}`;\n}\n\n/**\n * 解析恢复等待时长(中)/ Resolve recovery wait duration (EN).\n * 优先级:waitMs > waitSeconds > 默认值。\n * Priority: waitMs > waitSeconds > default value.\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/** 读取工具 action(中)/ Read tool action from input (EN). */\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/** 判定错误标记(中)/ Check whether result is marked as error (EN). */\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 快照生命周期管理(中)/ DOM snapshot lifecycle management (EN).\n *\n * 负责读取、包裹、去重、剥离。\n * Handles read, wrap, deduplicate, and strip operations.\n *\n * 快照读取主流程(中)/ Snapshot read pipeline (EN):\n * 1) 组装快照参数(默认偏完整性)/ Build snapshot params with completeness-oriented defaults.\n * 2) 调用 page_info.snapshot / Dispatch `page_info.snapshot` via ToolRegistry.\n * 3) 将 provider/tool 的 content 统一转成字符串 / Normalize tool content to plain string.\n * 4) 将快照交给消息层包裹并注入 / Pass snapshot to message layer for wrapping/injection.\n * 5) 在多轮对话中去重旧快照 / Deduplicate outdated snapshots across rounds.\n *\n * 调用链(中)/ Call chain (EN):\n * - `agent-loop/index.ts` 在“无快照、每轮结束、导航后、恢复后”触发读取。\n * - `messages.ts` 负责把最新快照注入到本轮上下文。\n * - 本文件只处理快照文本本身,不负责业务决策与停机判定。\n *\n * 压缩/剪枝实现位置(中)/ Where compression & pruning are implemented (EN):\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(中)/ Read current page URL via page_info (EN).\n *\n * 步骤(中)/ Steps (EN):\n * 1) 通过 registry 分发 `page_info.get_url`。\n * 2) 若 content 为字符串则直接返回。\n * 3) 否则返回 undefined,交由上层容错。\n *\n * 输入/输出(中)/ I/O contract (EN):\n * - In: `ToolRegistry`\n * - Out: `string | undefined`\n * - Side effects: 无(仅发起一次工具调用)/ none (single tool dispatch only)\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 * 读取页面快照(中)/ Read current page snapshot (EN).\n *\n * 默认关闭 viewportOnly,优先完整性。\n * viewportOnly defaults to false to prioritize completeness.\n *\n * 步骤(中)/ Steps (EN):\n * 1) 合并调用方 options 与默认值(深度/裁剪/剪枝/节点上限等)。\n * 2) 分发 `page_info.snapshot` 获取当前 DOM 文本快照。\n * 3) 使用 `toContentString` 归一化输出,避免 provider 差异导致结构不一致。\n * 4) 返回稳定字符串给 loop,供后续注入消息与统计。\n *\n * 默认参数意图(中)/ Default parameter rationale (EN):\n * - `maxDepth=8`: 保留足够层级,减少关键控件被截断。\n * - `viewportOnly=false`: 优先完整性,避免误判“元素不存在”。\n * - `pruneLayout=true`: 抑制纯布局噪声,降低 token 压力。\n * - `maxNodes=500` / `maxChildren=30`: 控制体积上限,兼顾可读性。\n * - `maxTextLength=40`: 防止长文本淹没结构信息。\n *\n * 压缩/剪枝是怎么做的(中)/ How compression & pruning works in practice (EN):\n * - `viewportOnly=true` 时:仅保留与视口相交元素(根层容器保留),完全视口外元素跳过。\n * - `pruneLayout=true` 时:无 id/无语义/无交互/无直接文本的布局容器会被“折叠”,\n * 子节点直接提升输出,减少无意义层级。\n * - `maxNodes`:全局节点预算,超限后停止继续遍历并追加 truncation 提示。\n * - `maxChildren`:每个父节点只保留前 N 个子元素,其余用 `... (n children omitted)` 汇总。\n * - `maxTextLength`:节点文本按长度截断,避免长段文案占满上下文。\n * - 交互优先排序:优先输出按钮/输入框/链接等交互元素,再输出普通元素。\n * - 属性压缩:仅保留关键属性(如 id、关键 class、交互属性、布尔状态、val),减少冗余 token。\n *\n * 输入/输出(中)/ I/O contract (EN):\n * - In: `ToolRegistry` + 可选快照参数\n * - Out: 归一化后的快照字符串(始终 string)\n * - Side effects: 无本地状态写入;仅依赖工具调用结果\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 },\n): Promise<string> {\n const result = await registry.dispatch(\"page_info\", {\n action: \"snapshot\",\n maxDepth: options?.maxDepth ?? 8,\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 });\n return toContentString(result.content);\n}\n\n// ─── 快照标记 ───\n\n/**\n * 包裹快照(中)/ Wrap snapshot with boundary markers (EN).\n *\n * 作用(中)/ Purpose (EN):\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/** 转义正则字符(中)/ Escape regex special chars (EN). */\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/** 快照块匹配正则(中)/ Regex for snapshot blocks (EN). */\nconst SNAPSHOT_REGEX = new RegExp(\n `${escapeRegex(SNAPSHOT_START)}[\\\\s\\\\S]*?${escapeRegex(SNAPSHOT_END)}`,\n \"g\",\n);\n\n/** 是否包含快照标记(中)/ Check whether text includes snapshot markers (EN). */\nfunction containsSnapshot(text: string): boolean {\n return text.includes(SNAPSHOT_START);\n}\n\n/**\n * 去重消息快照(中)/ Deduplicate snapshots in messages (EN).\n * 仅保留最后一份快照,旧快照替换为过期提示。\n * Keep only the latest snapshot and mark older ones as outdated.\n *\n * 步骤(中)/ Steps (EN):\n * 1) 扫描 tool 消息中的快照块引用。\n * 2) 保留最后一次快照,视为当前事实来源。\n * 3) 将更早快照替换为 `SNAPSHOT_OUTDATED`,避免模型引用旧状态。\n *\n * 返回语义(中)/ Return semantics (EN):\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 * 剥离旧快照(中)/ Strip outdated snapshot blocks from system prompt (EN).\n *\n * 说明(中)/ Notes (EN):\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/** 导出快照正则(中)/ Export snapshot regex for message helpers (EN). */\nexport { SNAPSHOT_REGEX };\n","/**\n * 紧凑消息构建(中)/ Compact message construction (EN).\n *\n * 目标:让 AI 每轮只基于“主目标 + 已完成步骤 + 最新快照”做增量决策。\n * Goal: enforce incremental decisions from master goal, done steps, and latest snapshot.\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 意图判定(中)/ Detect explicit intent to operate AutoPilot UI (EN).\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/** 输入摘要(中)/ Build brief text for tool input (EN). */\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 * 结果摘要(中)/ Build one-line summary for tool result (EN).\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 * 轨迹格式化(中)/ Format full tool trace to readable text (EN).\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 * 构建紧凑消息数组(中)/ Build compact AI message array (EN).\n *\n * Round 0: task + snapshot.\n * Round 1+: master goal + done steps + execution context + latest snapshot.\n *\n * 新增渐进式语义(中)/ Progressive semantics (EN):\n * - `remainingInstruction`:当前轮次仍待执行的文本。\n * - `previousRoundTasks`:上一轮已执行的任务数组,避免重复计划。\n * - 消息中要求模型输出 `REMAINING: ...` 或 `REMAINING: DONE`,供下一轮继续消费。\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:任务描述 + 快照,一条消息搞定 ───\n if (trace.length === 0) {\n // 中文释义:Round 0 发送给模型的信息结构\n // 1) 当前用户目标\n // 2) 当前轮次剩余任务文本\n // 3) 当前 URL(如有)\n // 4) 最新快照 + 执行约束(禁 page_info、禁误触 AI UI、下拉框用 select_option/fill)\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 \"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 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+:已完成步骤 + 执行上下文与快照(不再重复原始 userMessage) ───\n\n // 第 1 条:已完成步骤摘要(从 fullToolTrace 重建)\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 条:执行上下文 + 最新快照\n const hasErrors = trace.some(e => hasToolError(e.result));\n const contextParts: string[] = [\n // 中文释义:Round 1+ 执行上下文\n // - Master goal: 原始总目标,不变\n // - Current remaining instruction: 当前尚未完成的子任务文本\n // - Completed steps: 已完成步骤不重复\n // - Snapshot constraints: 只基于最新快照执行;不跨 DOM 变化链式操作\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 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 // 中文释义:要求模型显式返回剩余任务协议\n // - REMAINING: <text> 还有未完成\n // - REMAINING: DONE 当前任务文本已消费完\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 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 * 保护与恢复机制(中)/ Protection and recovery mechanisms (EN).\n *\n * 确保单次失败不打断主循环。\n * Keeps the main loop resilient to single-step failures.\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 动作(中)/ Redundant page_info actions to intercept (EN). */\nconst REDUNDANT_PAGE_INFO_ACTIONS = new Set([\"snapshot\", \"query_all\", \"get_url\", \"get_title\", \"get_viewport\"]);\n\n/**\n * 冗余 page_info 检查(中)/ Check whether page_info call is redundant (EN).\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 * 快照防抖(中)/ Debounce repeated snapshot calls (EN).\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 * 元素未找到恢复(中)/ Recover from element-not-found failures (EN).\n *\n * 前两次自动恢复,超过上限后返回终止提示。\n * Auto-recovers for initial attempts, then returns max-recovery signal.\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/** 导航后快照刷新(中)/ Refresh snapshot after navigation actions (EN). */\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/** 只读工具集合(中)/ Read-only tool set (EN). */\nconst READ_ONLY_TOOLS = new Set([\"page_info\"]);\n\n/**\n * 空转检测(中)/ Detect idle loops dominated by read-only actions (EN).\n * 返回 -1 表示应终止循环。\n * Returns -1 when loop should terminate.\n */\nexport function detectIdleLoop(\n toolCallNames: string[],\n consecutiveReadOnlyRounds: number,\n): number {\n const allReadOnly = toolCallNames.every(name => READ_ONLY_TOOLS.has(name));\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 主流程(中)/ Core environment-agnostic agent loop (EN).\n *\n * 负责消息构建、AI 决策、工具执行、恢复保护与指标汇总。\n * Orchestrates message build, AI decisions, tool execution, recovery, and metrics.\n *\n * 流程图(文本):\n *\n * 轮次开始\n * │\n * ├─ 确保快照可用\n * ├─ 构建紧凑消息(目标 + 剩余任务 + 执行轨迹 + 快照)\n * ├─ 调用模型\n * ├─ 无 toolCalls ? 结束 : 执行工具\n * ├─ 应用保护机制(冗余拦截/恢复/导航检测/空转/防自转)\n * ├─ 刷新快照\n * ▼\n * 下一轮或停机\n */\nimport {\n DEFAULT_MAX_ROUNDS,\n DEFAULT_NOT_FOUND_RETRY_ROUNDS,\n DEFAULT_NOT_FOUND_RETRY_WAIT_MS,\n} from \"./constants.js\";\nimport {\n getToolAction,\n isElementNotFoundResult,\n sleep,\n toContentString,\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 循环(中)/ Execute the agent loop (EN).\n *\n * 每轮:确保快照 → 构建消息 → 调用 AI → 执行工具 → 保护处理 → 刷新快照。\n * Per round: ensure snapshot -> build messages -> call AI -> execute tools -> apply protections -> refresh snapshot.\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 callbacks,\n } = params;\n\n // 固定依赖与运行态容器(中)/ Static dependencies and runtime containers (EN).\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 // 最终输出(中)/ Final output state (EN).\n let finalReply = \"\";\n\n // 循环控制状态(中)/ Loop control state (EN).\n let consecutiveSnapshotCalls = 0;\n let consecutiveReadOnlyRounds = 0;\n let usedRounds = 0;\n\n // token 统计(中)/ Token accounting (EN).\n let inputTokens = 0;\n let outputTokens = 0;\n // 渐进式任务状态(中)/ Progressive task state (EN).\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 // 恢复与拦截统计(中)/ Recovery/interception counters (EN).\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 // 快照体积统计(中)/ Snapshot size metrics (EN).\n let snapshotReadCount = 0;\n let snapshotSizeTotal = 0;\n let snapshotSizeMax = 0;\n\n /**\n * 记录快照统计(中)/ Record snapshot metrics (EN).\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 * 刷新页面快照(中)/ Refresh page snapshot (EN).\n *\n * 只做两件事:读取最新快照 + 更新快照统计。\n * Does exactly two things: read latest snapshot + update metrics.\n */\n const refreshSnapshot = async (): Promise<void> => {\n pageContext.latestSnapshot = await readPageSnapshot(registry);\n recordSnapshotStats(pageContext.latestSnapshot);\n };\n\n if (pageContext.latestSnapshot) {\n recordSnapshotStats(pageContext.latestSnapshot);\n }\n\n /**\n * 追加工具轨迹(中)/ Append tool trace entry (EN).\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 * 生成任务数组(中)/ Build normalized task array (EN).\n *\n * 将本轮 toolCalls 归一化成稳定字符串数组,便于:\n * - 回传到下一轮消息上下文(提醒已执行计划)\n * - 进行“是否与上一轮完全相同”的比较\n */\n const buildTaskArray = (toolCalls: Array<{ name: string; input: unknown }>): string[] =>\n toolCalls.map(tc => {\n const inputText = JSON.stringify(tc.input);\n return `${tc.name}:${inputText}`;\n });\n\n /**\n * 规范化模型文本输出(中)/ Normalize model text for next-round input (EN).\n *\n * 优先保留 REMAINING 行;否则截断首段文本,避免长篇规划污染下一轮输入。\n * Prefer REMAINING line; otherwise keep a short excerpt to avoid long planning spillover.\n */\n const 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) {\n return `REMAINING: ${remainingMatch[1].trim()}`;\n }\n const firstBlock = trimmed.split(/\\n\\s*\\n/)[0]?.trim() ?? trimmed;\n return firstBlock.slice(0, 220);\n };\n\n /**\n * 判定动作是否会触发 DOM 结构变化(中)/ Whether action may cause DOM-shape change (EN).\n *\n * 触发后应强制断轮,等待下一轮新快照继续。\n * Force round break after such action and continue with refreshed snapshot next round.\n */\n const shouldForceRoundBreak = (toolName: string, toolInput: unknown): boolean => {\n const action = getToolAction(toolInput);\n if (toolName === \"navigate\") {\n return action === \"goto\" || action === \"back\" || action === \"forward\" || action === \"reload\";\n }\n if (toolName === \"dom\") {\n return action === \"click\" || action === \"press\";\n }\n if (toolName === \"evaluate\") {\n return true;\n }\n return false;\n };\n\n /**\n * 将“找不到元素”的失败任务整理成可重试清单(中)/ Build retry task list for not-found failures (EN).\n */\n const collectMissingTask = (\n name: string,\n input: unknown,\n result: AgentLoopResult[\"toolCalls\"][number][\"result\"],\n ): MissingToolTask | 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 * 解析 REMAINING 协议(中)/ Parse REMAINING protocol from model text (EN).\n *\n * 支持:\n * - `REMAINING: <text>` → 继续下一轮消费该剩余文本\n * - `REMAINING: DONE` → 剩余任务为空\n * 返回 null 表示本轮没有提供 REMAINING 标记。\n */\n const 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 * 推进下一轮描述(中)/ Derive next-round instruction from model text (EN).\n *\n * 优先 REMAINING 协议;若未提供,则保持当前 remaining 不变。\n * Priority: REMAINING protocol first; otherwise keep current remaining instruction unchanged.\n */\n const 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 // 协议缺失时按规则回退:保持当前剩余任务不变。\n // Fallback when protocol is missing: keep current remaining instruction unchanged.\n return { nextInstruction: currentInstruction, hasRemainingProtocol: false };\n };\n\n /**\n * 启发式任务剔除(中)/ Heuristic remaining reduction for linear instructions (EN).\n *\n * 在 REMAINING 缺失但本轮有执行动作时,按“线性片段”剔除已执行步数,避免下一轮继续携带整段原任务。\n * When REMAINING is missing but actions were executed, drop executed step count from a linearized instruction.\n */\n const reduceRemainingHeuristically = (\n currentInstruction: string,\n executedCount: number,\n ): string => {\n if (!currentInstruction.trim() || executedCount <= 0) return currentInstruction;\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 // 主循环(中)/ Main round loop (EN).\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 // 计费/观测数据累计(中)/ Aggregate usage for observability (EN).\n inputTokens += response.usage?.inputTokens ?? 0;\n outputTokens += response.usage?.outputTokens ?? 0;\n\n // 先解析协议,最终推进在本轮执行后统一决定。\n // Parse protocol first; final remaining update is decided after execution.\n const parsedInstructionState = deriveNextInstruction(response.text, remainingInstruction);\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 // Compare whether current planned batch is identical to the previous one.\n if (plannedBatchKey === lastPlannedBatchKey) {\n consecutiveSamePlannedBatch += 1;\n } else {\n consecutiveSamePlannedBatch = 1;\n lastPlannedBatchKey = plannedBatchKey;\n }\n\n // 防自转:连续两轮给出相同计划且上一轮无错误,判定任务已完成或模型卡住,直接结束。\n // Anti-spin: if same planned batch appears twice and previous round had no error, stop the request.\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 // roundHasError guards anti-spin stop: do not hard-stop if previous round had errors.\n let roundHasError = false;\n const executedTaskCalls: Array<{ name: string; input: unknown }> = [];\n const roundMissingTasks: MissingToolTask[] = [];\n for (const tc of response.toolCalls) {\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\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 // Carry current execution state into next round context.\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 // 保护 5:空转检测\n const toolCallNames = executedTaskCalls.map(tc => tc.name);\n const idleResult = detectIdleLoop(toolCallNames, consecutiveReadOnlyRounds);\n if (idleResult === -1) {\n finalReply = response.text || \"任务已完成。\";\n if (finalReply) callbacks?.onText?.(finalReply);\n break;\n }\n consecutiveReadOnlyRounds = idleResult;\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 // 结果统计(中)/ Compute success/failure metrics (EN).\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 // 统一发出指标回调(中)/ Emit metrics callback once per chat (EN).\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 { AgentLoopParams, AgentLoopResult, AgentLoopCallbacks, AgentLoopMetrics } 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};\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 * 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\";\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\";\n\n// ─── 公共类型定义 ───\n\n/** AI 客户端配置(中)/ AI client configuration (EN). */\nexport type AIClientConfig = {\n /** AI 提供商: \"openai\" | \"copilot\" | \"anthropic\" */\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 \"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`,\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 * 根据工具名分发并执行工具调用。\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 * 极简系统提示词构建器(中)/ Minimal system prompt builder (EN).\n *\n * 纯函数,不依赖运行时环境;调用方只需传入工具定义和可选扩展指令。\n * Pure function with no runtime coupling; callers pass tools and optional extra instructions.\n */\nimport type { ToolDefinition } from \"./tool-registry.js\";\n\nexport type SystemPromptParams = {\n /** 已注册工具列表(中)/ Registered tool definitions (EN). */\n tools?: ToolDefinition[];\n /** AI 思考深度标签(中)/ Optional thinking-level label (EN). */\n thinkingLevel?: string;\n /** 额外英文指令(中)/ Additional English instructions (EN). */\n extraInstructions?: string | string[];\n};\n\n/**\n * 规范化额外指令(中)/ Normalize additional instructions (EN).\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 * 构建系统提示词(中)/ Build system prompt (EN).\n *\n * 约束:\n * - 输出给模型的提示词正文统一为英文。\n * - 中文仅用于代码注释,便于团队维护。\n *\n * Constraints:\n * - Prompt text sent to model stays English-only.\n * - Chinese content is used in code comments only for maintainability.\n */\nexport function buildSystemPrompt(params: SystemPromptParams = {}): string {\n const sections: string[] = [];\n sections.push(\n [\n \"You are AutoPilot, an AI agent controlling the current web page via tools.\",\n \"\",\n \"## Core Rules\",\n \"- Work from CURRENT snapshot + CURRENT remaining task directly. Do not restate the request.\",\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 \"- Use only visible targets from snapshot. Use #hashID as selector. Do not guess CSS selectors.\",\n \"- Batch independent visible actions in one round. Do not split one form into many rounds unnecessarily.\",\n \"- If an action will change DOM (open modal, navigate), stop after that action batch and continue next round with new snapshot.\",\n \"- Do NOT call page_info (snapshot/query/get_url/get_title). Snapshot is already provided every round.\",\n \"- For dropdown/select, use dom action=select_option (or fill on select).\",\n \"- Do NOT interact with AutoPilot UI unless user explicitly asks.\",\n \"\",\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 \"## 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 // 工具列表(中)/ Available tool list (EN).\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 // 思考深度(中)/ Thinking-level hint (EN).\n if (params.thinkingLevel) {\n sections.push(\n [\n \"## Reasoning Profile\",\n `- Thinking level: ${params.thinkingLevel}`,\n ].join(\"\\n\"),\n );\n }\n\n // 额外指令(中)/ Additional custom instructions (EN).\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 * DOM Tool — 基于 Web API 的 DOM 操作工具。\n *\n * 替代 Playwright 的 click/fill/type 等操作,直接在页面上下文中执行。\n * 运行环境:浏览器 Content Script。\n *\n * 支持 15 种动作:\n * click — 点击元素\n * fill — 填写可编辑控件(input/textarea/select/contenteditable)\n * select_option — 选择下拉框选项(value/label)\n * clear — 清空输入控件\n * check — 勾选 checkbox/radio\n * uncheck — 取消勾选 checkbox\n * type — 逐字符模拟键入\n * focus — 聚焦元素\n * hover — 鼠标悬停(触发 mouseenter/mouseover)\n * press — 按下键盘按键(Enter/Escape/Tab/ArrowDown 等)\n * get_text — 获取元素文本内容\n * get_attr — 获取元素属性值\n * set_attr — 设置元素属性\n * add_class — 添加 CSS 类名\n * remove_class — 移除 CSS 类名\n */\nimport { Type } from \"@sinclair/typebox\";\nimport type { ToolDefinition, ToolCallResult } from \"../../core/tool-registry.js\";\nimport type { RefStore } from \"../ref-store.js\";\n\nconst DEFAULT_WAIT_MS = 1000;\n\n/** 当前活跃的 RefStore 实例(由 WebAgent 在 chat() 时设置) */\nlet activeRefStore: RefStore | undefined;\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * 安全地查询 DOM 元素。\n *\n * 支持两种定位方式(优先级从高到低):\n * - hash ID(以 \"#\" 开头且在 RefStore 中存在):确定性 hash 查找(最高效)\n * - CSS 选择器(其他):传统 querySelector\n */\nfunction queryElement(selector: string): Element | string {\n try {\n // #hashId — 优先从 RefStore 查找\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 // 不在 RefStore 中 → 回退到 CSS 选择器(可能是 #some-id)\n }\n\n // CSS 选择器\n const el = document.querySelector(selector);\n if (!el) return `未找到匹配 \"${selector}\" 的元素`;\n return el;\n } catch {\n return `选择器语法错误: ${selector}`;\n }\n}\n\n/**\n * 设置当前活跃的 RefStore(由 WebAgent 在 chat 开始时调用)。\n */\nexport function setActiveRefStore(store: RefStore | undefined): void {\n activeRefStore = store;\n}\n\n/** 获取当前活跃的 RefStore(供其他工具复用) */\nexport function getActiveRefStore(): RefStore | undefined {\n return activeRefStore;\n}\n\n/**\n * 在给定超时时间内轮询查找元素。\n * - 返回 Element:找到元素\n * - 返回 string:选择器语法错误\n * - 返回 null:超时未找到\n */\nasync function waitForElement(\n selector: string,\n timeoutMs: number,\n): Promise<Element | string | null> {\n const start = Date.now();\n\n while (Date.now() - start <= timeoutMs) {\n const elOrError = queryElement(selector);\n if (typeof elOrError !== \"string\") return elOrError;\n\n if (elOrError.startsWith(\"选择器语法错误\")) return elOrError;\n await sleep(100);\n }\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)) {\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_WAIT_MS;\n}\n\n/**\n * 模拟真实用户输入:触发 input、change 事件,兼容 React/Vue 等框架。\n */\nfunction dispatchInputEvents(el: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement): void {\n try {\n el.dispatchEvent(new InputEvent(\"input\", {\n bubbles: true,\n cancelable: true,\n inputType: \"insertText\",\n data: null,\n }));\n } catch {\n el.dispatchEvent(new Event(\"input\", { bubbles: true, cancelable: true }));\n }\n el.dispatchEvent(new Event(\"change\", { bubbles: true, cancelable: true }));\n}\n\n/**\n * 使用原生 setter 写入表单值,提升对受控组件(React/Vue 等)的兼容性。\n */\nfunction setNativeEditableValue(\n el: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n value: string,\n): void {\n const proto =\n el instanceof HTMLInputElement\n ? HTMLInputElement.prototype\n : el instanceof HTMLTextAreaElement\n ? HTMLTextAreaElement.prototype\n : HTMLSelectElement.prototype;\n const descriptor = Object.getOwnPropertyDescriptor(proto, \"value\");\n if (descriptor?.set) {\n descriptor.set.call(el, value);\n return;\n }\n el.value = value;\n}\n\n/**\n * 读取可编辑元素当前值。\n */\nfunction getEditableValue(\n el: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n): string {\n return el.value ?? \"\";\n}\n\n/**\n * 将常见 key 映射为更接近浏览器语义的 KeyboardEvent.code。\n */\nfunction resolveKeyboardCode(key: string): string {\n const map: Record<string, string> = {\n Enter: \"Enter\",\n Escape: \"Escape\",\n Esc: \"Escape\",\n Tab: \"Tab\",\n Space: \"Space\",\n \" \": \"Space\",\n Backspace: \"Backspace\",\n Delete: \"Delete\",\n ArrowUp: \"ArrowUp\",\n ArrowDown: \"ArrowDown\",\n ArrowLeft: \"ArrowLeft\",\n ArrowRight: \"ArrowRight\",\n };\n return map[key] ?? key;\n}\n\n/**\n * 生成元素的可读描述,用于在操作结果中展示实际命中的 DOM 节点。\n * 格式:<tag#id.class> \"文本\" [attr=val, ...]\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 : \"\";\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\n // 关键属性\n const hints: string[] = [];\n for (const attr of [\"type\", \"name\", \"placeholder\", \"href\", \"role\"]) {\n const val = el.getAttribute(attr);\n if (val) hints.push(`${attr}=${val}`);\n }\n if (el instanceof HTMLSelectElement && el.value) {\n hints.push(`val=${el.value}`);\n }\n const attrHint = hints.length > 0 ? ` [${hints.join(\", \")}]` : \"\";\n\n return `<${tag}${id}${cls}>${textHint}${attrHint}`;\n}\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 if (style.display === \"none\" || style.visibility === \"hidden\") return false;\n if (style.opacity === \"0\") return false;\n const rect = el.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n}\n\nfunction isElementDisabled(el: Element): boolean {\n if (!(el instanceof HTMLElement)) return false;\n if (el.hasAttribute(\"disabled\")) return true;\n if (el.getAttribute(\"aria-disabled\") === \"true\") return true;\n if (\"disabled\" in el && typeof (el as { disabled?: unknown }).disabled === \"boolean\") {\n return Boolean((el as { disabled?: boolean }).disabled);\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 const blockedTypes = new Set([\"checkbox\", \"radio\", \"file\", \"button\", \"submit\", \"reset\"]);\n return !blockedTypes.has(el.type) && !el.readOnly;\n }\n if (el instanceof HTMLSelectElement) return true;\n return el instanceof HTMLElement && el.isContentEditable;\n}\n\nfunction ensureActionable(\n el: Element,\n action: string,\n selector: string,\n): ToolCallResult | null {\n if (!el.isConnected) {\n return {\n content: `\"${selector}\" 元素已脱离文档,无法执行 ${action}`,\n details: { error: true, code: \"ELEMENT_DETACHED\", action, selector },\n };\n }\n\n const readOnlyActions = new Set([\"get_text\", \"get_attr\"]);\n if (!readOnlyActions.has(action) && !isElementVisible(el)) {\n return {\n content: `\"${selector}\" 元素不可见,无法执行 ${action}`,\n details: { error: true, code: \"ELEMENT_NOT_VISIBLE\", action, selector },\n };\n }\n\n const mutationActions = new Set([\n \"click\", \"fill\", \"type\", \"press\", \"select_option\", \"clear\", \"check\", \"uncheck\",\n ]);\n if (mutationActions.has(action) && isElementDisabled(el)) {\n return {\n content: `\"${selector}\" 元素已禁用,无法执行 ${action}`,\n details: { error: true, code: \"ELEMENT_DISABLED\", action, selector },\n };\n }\n\n if ([\"fill\", \"type\", \"clear\"].includes(action) && !isEditableElement(el)) {\n return {\n content: `\"${selector}\" 不是可编辑元素,无法执行 ${action}`,\n details: { error: true, code: \"UNSUPPORTED_FILL_TARGET\", action, selector },\n };\n }\n\n return null;\n}\n\nfunction isOptionCandidateVisible(el: Element): boolean {\n if (!(el instanceof HTMLElement)) return false;\n if (!isElementVisible(el)) return false;\n const text = el.textContent?.trim() ?? \"\";\n return text.length > 0;\n}\n\nfunction findVisibleOptionByText(text: string): HTMLElement | null {\n const target = text.trim().toLowerCase();\n if (!target) return null;\n const nodes = Array.from(document.querySelectorAll(\n '[role=\"option\"], .bk-select-option, .bk-option, [data-option], li, option',\n ));\n\n for (const node of nodes) {\n if (!isOptionCandidateVisible(node)) continue;\n const content = node.textContent?.trim().toLowerCase() ?? \"\";\n if (content === target) return node as HTMLElement;\n }\n for (const node of nodes) {\n if (!isOptionCandidateVisible(node)) continue;\n const content = node.textContent?.trim().toLowerCase() ?? \"\";\n if (content.includes(target)) return node as HTMLElement;\n }\n return null;\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, press, get_text, get_attr, set_attr, add_class, remove_class.\",\n \"Use the hash ID from DOM snapshot (e.g. #a1b2c) as selector.\",\n ].join(\" \"),\n\n schema: Type.Object({\n action: Type.String({\n description:\n \"DOM action: click | fill | select_option | clear | check | uncheck | type | focus | hover | 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(\n Type.String({ description: \"Value for fill/type/set_attr actions\" }),\n ),\n key: Type.Optional(\n Type.String({ description: \"Key name for press action (e.g. Enter, Escape, Tab, ArrowDown, ArrowUp, Backspace, Delete, Space)\" }),\n ),\n label: Type.Optional(\n Type.String({ description: \"Label text for select_option action (fallback when value is not provided)\" }),\n ),\n index: Type.Optional(\n Type.Number({ description: \"0-based option index for select_option action\" }),\n ),\n attribute: Type.Optional(\n Type.String({ description: \"Attribute name for get_attr/set_attr actions\" }),\n ),\n className: Type.Optional(\n Type.String({ description: \"CSS class name for add_class/remove_class\" }),\n ),\n waitMs: Type.Optional(\n Type.Number({\n description:\n \"Optional wait timeout in ms before action (default: 1000). Use 0 to disable waiting.\",\n }),\n ),\n waitSeconds: Type.Optional(\n Type.Number({\n description:\n \"Optional wait timeout in seconds before action. Used when waitMs is not provided.\",\n }),\n ),\n force: Type.Optional(\n Type.Boolean({ description: \"Skip actionability checks for interaction actions (default false).\" }),\n ),\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 let el: Element;\n if (waitMs > 0) {\n const found = await waitForElement(selector, waitMs);\n\n if (typeof found === \"string\") {\n return {\n content: found,\n details: { error: true, code: \"INVALID_SELECTOR\", action, selector },\n };\n }\n\n if (!found) {\n return {\n content: `未找到匹配 \"${selector}\" 的元素`,\n details: {\n error: true,\n code: \"ELEMENT_NOT_FOUND\",\n action,\n selector,\n waitMs,\n },\n };\n }\n\n el = found;\n } else {\n const elOrError = queryElement(selector);\n if (typeof elOrError === \"string\") {\n const code = elOrError.startsWith(\"未找到\")\n ? \"ELEMENT_NOT_FOUND\"\n : \"INVALID_SELECTOR\";\n return {\n content: elOrError,\n details: { error: true, code, action, selector, waitMs },\n };\n }\n el = elOrError;\n }\n\n try {\n if (!force) {\n const checkResult = ensureActionable(el, action, selector);\n if (checkResult) return checkResult;\n }\n\n switch (action) {\n // ─── 交互类 ───\n\n case \"click\": {\n // Playwright-like 兼容:若点中 option,自动写回父 select 并触发 change。\n if (el instanceof HTMLOptionElement) {\n const parent = el.parentElement;\n if (parent instanceof HTMLSelectElement) {\n parent.focus();\n parent.value = el.value;\n dispatchInputEvents(parent);\n return { content: `已选择 ${describeElement(parent)} 的选项 \"${el.value}\"` };\n }\n }\n\n // 模拟点击:先 focus 再 click,触发完整事件链\n if (el instanceof HTMLElement) {\n el.focus();\n el.dispatchEvent(new PointerEvent(\"pointerdown\", { bubbles: true, cancelable: true }));\n el.dispatchEvent(new MouseEvent(\"mousedown\", { bubbles: true, cancelable: true }));\n el.dispatchEvent(new PointerEvent(\"pointerup\", { bubbles: true, cancelable: true }));\n el.dispatchEvent(new MouseEvent(\"mouseup\", { bubbles: true, cancelable: true }));\n el.click();\n } else {\n el.dispatchEvent(new MouseEvent(\"click\", { bubbles: true }));\n }\n return { content: `已点击 ${describeElement(el)}` };\n }\n\n case \"focus\": {\n // 聚焦元素\n if (el instanceof HTMLElement) {\n el.focus();\n } else {\n el.dispatchEvent(new FocusEvent(\"focus\", { bubbles: true }));\n }\n return { content: `已聚焦 ${describeElement(el)}` };\n }\n\n case \"hover\": {\n // 鼠标悬停:触发 mouseenter → mouseover 事件链\n el.dispatchEvent(new MouseEvent(\"mouseenter\", { bubbles: false, cancelable: true }));\n el.dispatchEvent(new MouseEvent(\"mouseover\", { bubbles: true, cancelable: true }));\n el.dispatchEvent(new MouseEvent(\"mousemove\", { bubbles: true, cancelable: true }));\n return { content: `已悬停 ${describeElement(el)}` };\n }\n\n case \"press\": {\n // 按下指定键:先聚焦元素,再触发 keydown → keypress → keyup 完整事件链\n const key = (params.key as string) || (params.value as string);\n if (!key) return { content: \"缺少 key 参数(如 Enter, Escape, Tab)\" };\n\n if (el instanceof HTMLElement) el.focus();\n\n const eventInit: KeyboardEventInit = {\n key,\n code: resolveKeyboardCode(key),\n bubbles: true,\n cancelable: true,\n };\n const keydownAllowed = el.dispatchEvent(new KeyboardEvent(\"keydown\", eventInit));\n el.dispatchEvent(new KeyboardEvent(\"keypress\", eventInit));\n el.dispatchEvent(new KeyboardEvent(\"keyup\", eventInit));\n\n if (keydownAllowed && key === \"Enter\") {\n if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {\n const form = el.form ?? el.closest(\"form\");\n form?.dispatchEvent(new Event(\"submit\", { bubbles: true, cancelable: true }));\n }\n }\n return { content: `已在 ${describeElement(el)} 上按下 ${key}` };\n }\n\n case \"fill\": {\n // 填写可编辑控件:支持 input / textarea / select / contenteditable\n const value = params.value as string;\n if (value === undefined) return { content: \"缺少 value 参数\" };\n\n if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {\n if (el instanceof HTMLInputElement) {\n const blockedTypes = new Set([\"checkbox\", \"radio\", \"file\", \"button\", \"submit\", \"reset\"]);\n if (blockedTypes.has(el.type)) {\n return {\n content: `\"${selector}\" 为 input[type=${el.type}],不支持 fill;请使用 click/press/select_option 等动作。`,\n details: { error: true, code: \"UNSUPPORTED_FILL_TARGET\", action, selector },\n };\n }\n }\n el.focus();\n setNativeEditableValue(el, value);\n dispatchInputEvents(el);\n\n const actualValue = getEditableValue(el);\n if (actualValue !== value) {\n return {\n content: `\"${selector}\" 填写后值不一致:期望 \"${value}\",实际 \"${actualValue}\"`,\n details: {\n error: true,\n code: \"FILL_NOT_APPLIED\",\n action,\n selector,\n expected: value,\n actual: actualValue,\n },\n };\n }\n } else if (el instanceof HTMLSelectElement) {\n el.focus();\n\n // 1) 先按 option.value 精确匹配\n let matched = false;\n for (const option of Array.from(el.options)) {\n if (option.value === value) {\n el.value = option.value;\n matched = true;\n break;\n }\n }\n\n // 2) 再按展示文本匹配(忽略大小写与首尾空格)\n if (!matched) {\n const normalized = value.trim().toLowerCase();\n for (const option of Array.from(el.options)) {\n if (option.text.trim().toLowerCase() === normalized) {\n el.value = option.value;\n matched = true;\n break;\n }\n }\n }\n\n if (!matched) {\n return { content: `\"${selector}\" 下拉框中不存在选项 \"${value}\"` };\n }\n\n dispatchInputEvents(el);\n\n const actualValue = getEditableValue(el);\n if (actualValue !== el.value) {\n return {\n content: `\"${selector}\" 下拉框状态异常,未确认写入`,\n details: {\n error: true,\n code: \"FILL_NOT_APPLIED\",\n action,\n selector,\n expected: value,\n actual: actualValue,\n },\n };\n }\n } else if (el instanceof HTMLElement && el.isContentEditable) {\n el.focus();\n el.textContent = value;\n el.dispatchEvent(new Event(\"input\", { bubbles: true }));\n } else {\n return { content: `\"${selector}\" 不是可编辑元素` };\n }\n return { content: `已填写 ${describeElement(el)}: \"${value}\"` };\n }\n\n case \"select_option\": {\n // Playwright-like selectOption:通过 value 或 label 精确选择下拉项\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 if (!(el instanceof HTMLSelectElement)) {\n if (!(el instanceof HTMLElement)) {\n return { content: `\"${selector}\" 不是下拉框元素` };\n }\n\n el.focus();\n el.click();\n const wanted = (label ?? value ?? \"\").trim();\n if (!wanted) {\n return { content: `\"${selector}\" 为自定义下拉时,需提供 value 或 label` };\n }\n const option = findVisibleOptionByText(wanted);\n if (!option) {\n return {\n content: `未找到与 \"${wanted}\" 匹配的可见下拉选项(自定义下拉)`,\n details: {\n error: true,\n code: \"OPTION_NOT_FOUND\",\n action,\n selector,\n wanted,\n },\n };\n }\n option.click();\n return { content: `已在自定义下拉中选择 \"${wanted}\"` };\n }\n\n el.focus();\n\n const options = Array.from(el.options);\n let selectedOption: HTMLOptionElement | undefined;\n\n if (value !== undefined) {\n selectedOption = options.find(option => option.value === value);\n }\n\n if (!selectedOption && label !== undefined) {\n const normalizedLabel = label.trim().toLowerCase();\n selectedOption = options.find(option => option.text.trim().toLowerCase() === normalizedLabel);\n }\n\n if (!selectedOption && value !== undefined) {\n const normalizedValueAsLabel = value.trim().toLowerCase();\n selectedOption = options.find(option => option.text.trim().toLowerCase() === normalizedValueAsLabel);\n }\n\n if (!selectedOption && index !== undefined) {\n if (index < 0 || index >= options.length) {\n return { content: `\"${selector}\" 下拉框不存在 index=${index} 的选项` };\n }\n selectedOption = options[index];\n }\n\n if (!selectedOption) {\n const wanted = value ?? label ?? `index=${index}`;\n return { content: `\"${selector}\" 下拉框中不存在选项 \"${wanted}\"` };\n }\n\n if (selectedOption.disabled) {\n return { content: `\"${selector}\" 目标选项已禁用:${selectedOption.value}` };\n }\n\n if (!el.multiple) {\n for (const option of options) {\n option.selected = false;\n }\n }\n selectedOption.selected = true;\n el.value = selectedOption.value;\n\n dispatchInputEvents(el);\n return {\n content: `已选择 ${describeElement(el)}: value=\"${selectedOption.value}\", label=\"${selectedOption.text.trim()}\"`,\n };\n }\n\n case \"clear\": {\n if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) {\n el.focus();\n setNativeEditableValue(el, \"\");\n dispatchInputEvents(el);\n return { content: `已清空 ${describeElement(el)}` };\n }\n if (el instanceof HTMLElement && el.isContentEditable) {\n el.focus();\n el.textContent = \"\";\n el.dispatchEvent(new Event(\"input\", { bubbles: true }));\n return { content: `已清空 ${describeElement(el)}` };\n }\n return { content: `\"${selector}\" 不是可清空元素` };\n }\n\n case \"check\": {\n if (!(el instanceof HTMLInputElement) || (el.type !== \"checkbox\" && el.type !== \"radio\")) {\n return { content: `\"${selector}\" 不是 checkbox/radio` };\n }\n el.focus();\n if (!el.checked) {\n el.checked = true;\n dispatchInputEvents(el);\n }\n return { content: `已勾选 ${describeElement(el)}` };\n }\n\n case \"uncheck\": {\n if (!(el instanceof HTMLInputElement) || el.type !== \"checkbox\") {\n return { content: `\"${selector}\" 不是 checkbox` };\n }\n el.focus();\n if (el.checked) {\n el.checked = false;\n dispatchInputEvents(el);\n }\n return { content: `已取消勾选 ${describeElement(el)}` };\n }\n\n case \"type\": {\n // 逐字符键入:每个字符触发 keydown → keypress → input → keyup\n // 适用于有实时监听键盘事件的输入框(如搜索自动补全)\n const value = params.value as string;\n if (value === undefined) return { content: \"缺少 value 参数\" };\n\n if (el instanceof HTMLElement) el.focus();\n\n for (const char of value) {\n el.dispatchEvent(\n new KeyboardEvent(\"keydown\", { key: char, bubbles: true }),\n );\n el.dispatchEvent(\n new KeyboardEvent(\"keypress\", { key: char, bubbles: true }),\n );\n if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {\n el.value += char;\n }\n el.dispatchEvent(new Event(\"input\", { bubbles: true }));\n el.dispatchEvent(\n new KeyboardEvent(\"keyup\", { key: char, bubbles: true }),\n );\n }\n return { content: `已逐字输入到 ${describeElement(el)}: \"${value}\"` };\n }\n\n // ─── 读取类 ───\n\n case \"get_text\": {\n // 获取元素的文本内容(包括子元素)\n const text = el.textContent?.trim() ?? \"\";\n return { content: `${describeElement(el)} 的文本内容:${text || \"(空)\"}` };\n }\n\n case \"get_attr\": {\n // 获取元素的指定属性值\n const attribute = params.attribute as string;\n if (!attribute) return { content: \"缺少 attribute 参数\" };\n const attrValue = el.getAttribute(attribute);\n return { content: `${describeElement(el)} 的 ${attribute} = ${attrValue ?? \"(不存在)\"}` };\n }\n\n // ─── 修改类 ───\n\n case \"set_attr\": {\n // 设置元素的属性值\n const attribute = params.attribute as string;\n const value = params.value as string;\n if (!attribute || value === undefined)\n return { content: \"缺少 attribute 或 value 参数\" };\n el.setAttribute(attribute, value);\n return { content: `已设置 ${describeElement(el)} 的 ${attribute}=\"${value}\"` };\n }\n\n case \"add_class\": {\n // 给元素添加 CSS 类名\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\n case \"remove_class\": {\n // 移除元素的 CSS 类名\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 {\n content: `DOM 操作 \"${action}\" 失败: ${err instanceof Error ? err.message : String(err)}`,\n details: { error: true, action, selector },\n };\n }\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 { getActiveRefStore } from \"./dom-tool.js\";\n\n/** 快照配置选项 */\nexport type SnapshotOptions = {\n /** 最大遍历深度(默认 6) */\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\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 ?? 6;\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\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 /** 布尔状态属性 — 只在存在时输出(无值),如 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 (directText) return false;\n return true;\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 return false;\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 // 构建当前元素的内部路径(用于 hash 计算,不输出到快照)\n const index = getSiblingIndex(el);\n const currentPath = `${parentPath}/${tag}${index}`;\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 const val = el.getAttribute(attr);\n if (val) attrs.push(`${attr}=\"${val}\"`);\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 // 6. data-* 属性 — 只保留 data-testid(自动化测试定位用)\n const testId = el.getAttribute(\"data-testid\") || el.getAttribute(\"data-test-id\");\n if (testId) attrs.push(`data-testid=\"${testId.slice(0, 25)}\"`);\n\n // 7. 对于 input/textarea,补充当前实际 value(截短到 40 字符)\n if ((el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) && el.value) {\n const currentVal = 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=\"${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 if (isEmptyLayoutContainer(el, directText)) {\n const allChildren = Array.from(el.children);\n const interactiveChildren = allChildren.filter(isInteractiveElement);\n const nonInteractiveChildren = allChildren.filter((child) => !isInteractiveElement(child));\n const orderedChildren = [...interactiveChildren, ...nonInteractiveChildren];\n const selectedChildren = orderedChildren.slice(0, maxChildren);\n const omittedChildren = orderedChildren.length - selectedChildren.length;\n\n const childLines: string[] = [];\n for (let i = 0; i < selectedChildren.length; i++) {\n // 子元素继承当前路径(保证 hash 计算正确),但不增加缩进\n const childResult = walk(selectedChildren[i], depth, currentPath);\n if (childResult) childLines.push(childResult);\n }\n\n if (omittedChildren > 0) {\n childLines.push(`${\" \".repeat(depth)}... (${omittedChildren} children omitted)`);\n }\n\n // 如果子树也全部为空,整个容器就被剪掉\n return childLines.join(\"\\n\");\n }\n\n // 构建当前元素描述:[标签] \"文本\" 属性 #ID\n let line = `${indent}[${tag}]`;\n if (directText) line += ` \"${directText.slice(0, maxTextLength)}\"`;\n if (attrs.length) line += ` ${attrs.join(\" \")}`;\n // 使用 hash ID(如 #a1b2c)或回退到完整 XPath\n if (refStore) {\n const hashId = refStore.set(el, currentPath);\n line += ` #${hashId}`;\n } else {\n line += ` ref=\"${currentPath}\"`;\n }\n\n const lines: string[] = [line];\n emittedNodes++;\n\n // 递归子元素(优先保留可交互元素,再保留普通元素)\n const allChildren = Array.from(el.children);\n const interactiveChildren = allChildren.filter(isInteractiveElement);\n const nonInteractiveChildren = allChildren.filter((child) => !isInteractiveElement(child));\n const orderedChildren = [...interactiveChildren, ...nonInteractiveChildren];\n const selectedChildren = orderedChildren.slice(0, maxChildren);\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\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: 6)\" }),\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 }),\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) ?? 6;\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 snapshot = generateSnapshot(document.body, {\n maxDepth,\n viewportOnly,\n pruneLayout,\n maxNodes,\n maxChildren,\n maxTextLength,\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 — 基于 Web API 的页面导航工具。\n *\n * 替代 Playwright 的 goto/goBack/goForward/reload。\n * 运行环境:浏览器 Content Script。\n *\n * 支持 5 种动作:\n * goto — 跳转到指定 URL\n * back — 浏览器后退\n * forward — 浏览器前进\n * reload — 刷新当前页面\n * scroll — 滚动页面到指定位置或元素\n */\nimport { Type } from \"@sinclair/typebox\";\nimport type { ToolDefinition, ToolCallResult } from \"../../core/tool-registry.js\";\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 ].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: \"CSS selector for scroll action (scrolls element into view)\" }),\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 // 跳转到指定 URL\n const url = params.url as string;\n if (!url) return { content: \"缺少 url 参数\" };\n\n // 支持相对路径和绝对路径\n window.location.href = url;\n return { content: `正在导航到 ${url}` };\n }\n\n case \"back\": {\n // 浏览器后退(等同于点击后退按钮)\n window.history.back();\n return { content: \"已后退\" };\n }\n\n case \"forward\": {\n // 浏览器前进\n window.history.forward();\n return { content: \"已前进\" };\n }\n\n case \"reload\": {\n // 刷新当前页面\n window.location.reload();\n return { content: \"正在刷新页面\" };\n }\n\n case \"scroll\": {\n // 滚动页面:优先滚动到元素,否则滚动到坐标\n const selector = params.selector as string | undefined;\n\n if (selector) {\n const el = document.querySelector(selector);\n if (!el) return { content: `未找到元素 \"${selector}\"` };\n el.scrollIntoView({ behavior: \"smooth\", block: \"center\" });\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 — 基于 MutationObserver 的元素等待工具。\n *\n * 替代 Playwright 的 waitForSelector/waitForNavigation。\n * 运行环境:浏览器 Content Script。\n *\n * 支持 4 种动作:\n * wait_for_selector — 等待匹配选择器的元素出现\n * wait_for_hidden — 等待元素消失或隐藏\n * wait_for_text — 等待页面中出现指定文本\n * wait_for_stable — 等待 DOM 在一段时间内无变化\n */\nimport { Type } from \"@sinclair/typebox\";\nimport type { ToolDefinition, ToolCallResult } from \"../../core/tool-registry.js\";\n\n/** 默认超时时间(毫秒) */\nconst DEFAULT_TIMEOUT = 10_000;\n\ntype SelectorState = \"attached\" | \"visible\" | \"hidden\" | \"detached\";\n\n/**\n * Playwright 风格可见性判定(近似)。\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 if (style.display === \"none\" || style.visibility === \"hidden\") 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 * 读取 selector 当前状态。\n */\nfunction evaluateSelectorState(selector: string, state: SelectorState): { matched: boolean; element?: Element } {\n const el = document.querySelector(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 * 等待 selector 达到指定状态(近似 Playwright state 语义)。\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, 80);\n const observer = new MutationObserver(check);\n observer.observe(document.body, {\n childList: true,\n subtree: true,\n attributes: true,\n characterData: true,\n });\n\n check();\n });\n}\n\n/**\n * 等待页面中出现指定文本。\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, {\n childList: true,\n subtree: true,\n characterData: true,\n });\n });\n}\n\n/**\n * 等待页面进入稳定状态:在 quietMs 时间窗口内没有 DOM 变化。\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, {\n childList: true,\n subtree: true,\n attributes: true,\n characterData: true,\n });\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 }, 50);\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: 10000)\" }),\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 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\";\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\"(默认 \"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 /** 自定义系统提示词(不传则使用默认 Web 提示词) */\n systemPrompt?: string;\n /** 最大工具调用轮次(默认 10) */\n maxRounds?: number;\n /** 是否启用多轮对话记忆(默认 false) */\n memory?: boolean;\n /** 是否在每次对话前自动生成页面快照(默认 true) */\n autoSnapshot?: boolean;\n /** 快照选项(视口裁剪、智能剪枝等,autoSnapshot 开启时生效) */\n snapshotOptions?: SnapshotOptions;\n};\n\n// ─── WebAgent 类 ───\n\nexport class WebAgent {\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 private customSystemPrompt?: 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 /** 工具注册表实例 — 每个 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.customSystemPrompt = options.systemPrompt;\n this.memory = options.memory ?? false;\n this.autoSnapshot = options.autoSnapshot ?? true;\n this.snapshotOptions = options.snapshotOptions ?? {};\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\n /** 注册一个自定义工具 */\n registerTool(tool: ToolDefinition): void {\n this.registry.register(tool);\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 setSystemPrompt(prompt: string): void {\n this.customSystemPrompt = prompt;\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 // 复用 core/system-prompt 或使用自定义\n let systemPrompt =\n this.customSystemPrompt ??\n buildSystemPrompt({ tools: this.registry.getDefinitions() });\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: 8,\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 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/index.ts","../src/core/tool-registry.ts","../src/core/system-prompt.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 = 2000;\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 辅助函数(中)/ Agent loop helpers (EN).\n *\n * 仅包含纯函数与无副作用工具。\n * Pure utilities only (no side effects).\n */\nimport type { ToolCallResult } from \"../tool-registry.js\";\nimport { DEFAULT_RECOVERY_WAIT_MS } from \"./constants.js\";\n\n/** 异步睡眠(中)/ Async sleep utility (EN). */\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/** 统一内容为字符串(中)/ Normalize tool content to string (EN). */\nexport function toContentString(content: ToolCallResult[\"content\"]): string {\n return typeof content === \"string\" ? content : JSON.stringify(content, null, 2);\n}\n\n/** 元素不存在判定(中)/ Detect element-not-found failure (EN). */\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/** 生成稳定调用键(中)/ Build stable key for a tool call (EN). */\nexport function buildToolCallKey(name: string, input: unknown): string {\n return `${name}:${JSON.stringify(input)}`;\n}\n\n/**\n * 解析恢复等待时长(中)/ Resolve recovery wait duration (EN).\n * 优先级:waitMs > waitSeconds > 默认值。\n * Priority: waitMs > waitSeconds > default value.\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/** 读取工具 action(中)/ Read tool action from input (EN). */\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/** 判定错误标记(中)/ Check whether result is marked as error (EN). */\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 快照生命周期管理(中)/ DOM snapshot lifecycle management (EN).\n *\n * 负责读取、包裹、去重、剥离。\n * Handles read, wrap, deduplicate, and strip operations.\n *\n * 快照读取主流程(中)/ Snapshot read pipeline (EN):\n * 1) 组装快照参数(默认偏完整性)/ Build snapshot params with completeness-oriented defaults.\n * 2) 调用 page_info.snapshot / Dispatch `page_info.snapshot` via ToolRegistry.\n * 3) 将 provider/tool 的 content 统一转成字符串 / Normalize tool content to plain string.\n * 4) 将快照交给消息层包裹并注入 / Pass snapshot to message layer for wrapping/injection.\n * 5) 在多轮对话中去重旧快照 / Deduplicate outdated snapshots across rounds.\n *\n * 调用链(中)/ Call chain (EN):\n * - `agent-loop/index.ts` 在“无快照、每轮结束、导航后、恢复后”触发读取。\n * - `messages.ts` 负责把最新快照注入到本轮上下文。\n * - 本文件只处理快照文本本身,不负责业务决策与停机判定。\n *\n * 压缩/剪枝实现位置(中)/ Where compression & pruning are implemented (EN):\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(中)/ Read current page URL via page_info (EN).\n *\n * 步骤(中)/ Steps (EN):\n * 1) 通过 registry 分发 `page_info.get_url`。\n * 2) 若 content 为字符串则直接返回。\n * 3) 否则返回 undefined,交由上层容错。\n *\n * 输入/输出(中)/ I/O contract (EN):\n * - In: `ToolRegistry`\n * - Out: `string | undefined`\n * - Side effects: 无(仅发起一次工具调用)/ none (single tool dispatch only)\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 * 读取页面快照(中)/ Read current page snapshot (EN).\n *\n * 默认关闭 viewportOnly,优先完整性。\n * viewportOnly defaults to false to prioritize completeness.\n *\n * 步骤(中)/ Steps (EN):\n * 1) 合并调用方 options 与默认值(深度/裁剪/剪枝/节点上限等)。\n * 2) 分发 `page_info.snapshot` 获取当前 DOM 文本快照。\n * 3) 使用 `toContentString` 归一化输出,避免 provider 差异导致结构不一致。\n * 4) 返回稳定字符串给 loop,供后续注入消息与统计。\n *\n * 默认参数意图(中)/ Default parameter rationale (EN):\n * - `maxDepth=8`: 保留足够层级,减少关键控件被截断。\n * - `viewportOnly=false`: 优先完整性,避免误判“元素不存在”。\n * - `pruneLayout=true`: 抑制纯布局噪声,降低 token 压力。\n * - `maxNodes=500` / `maxChildren=30`: 控制体积上限,兼顾可读性。\n * - `maxTextLength=40`: 防止长文本淹没结构信息。\n *\n * 压缩/剪枝是怎么做的(中)/ How compression & pruning works in practice (EN):\n * - `viewportOnly=true` 时:仅保留与视口相交元素(根层容器保留),完全视口外元素跳过。\n * - `pruneLayout=true` 时:无 id/无语义/无交互/无直接文本的布局容器会被“折叠”,\n * 子节点直接提升输出,减少无意义层级。\n * - `maxNodes`:全局节点预算,超限后停止继续遍历并追加 truncation 提示。\n * - `maxChildren`:每个父节点只保留前 N 个子元素,其余用 `... (n children omitted)` 汇总。\n * - `maxTextLength`:节点文本按长度截断,避免长段文案占满上下文。\n * - 交互优先排序:优先输出按钮/输入框/链接等交互元素,再输出普通元素。\n * - 属性压缩:仅保留关键属性(如 id、关键 class、交互属性、布尔状态、val),减少冗余 token。\n *\n * 输入/输出(中)/ I/O contract (EN):\n * - In: `ToolRegistry` + 可选快照参数\n * - Out: 归一化后的快照字符串(始终 string)\n * - Side effects: 无本地状态写入;仅依赖工具调用结果\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 },\n): Promise<string> {\n const result = await registry.dispatch(\"page_info\", {\n action: \"snapshot\",\n maxDepth: options?.maxDepth ?? 8,\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 });\n return toContentString(result.content);\n}\n\n// ─── 快照标记 ───\n\n/**\n * 包裹快照(中)/ Wrap snapshot with boundary markers (EN).\n *\n * 作用(中)/ Purpose (EN):\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/** 转义正则字符(中)/ Escape regex special chars (EN). */\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/** 快照块匹配正则(中)/ Regex for snapshot blocks (EN). */\nconst SNAPSHOT_REGEX = new RegExp(\n `${escapeRegex(SNAPSHOT_START)}[\\\\s\\\\S]*?${escapeRegex(SNAPSHOT_END)}`,\n \"g\",\n);\n\n/** 是否包含快照标记(中)/ Check whether text includes snapshot markers (EN). */\nfunction containsSnapshot(text: string): boolean {\n return text.includes(SNAPSHOT_START);\n}\n\n/**\n * 去重消息快照(中)/ Deduplicate snapshots in messages (EN).\n * 仅保留最后一份快照,旧快照替换为过期提示。\n * Keep only the latest snapshot and mark older ones as outdated.\n *\n * 步骤(中)/ Steps (EN):\n * 1) 扫描 tool 消息中的快照块引用。\n * 2) 保留最后一次快照,视为当前事实来源。\n * 3) 将更早快照替换为 `SNAPSHOT_OUTDATED`,避免模型引用旧状态。\n *\n * 返回语义(中)/ Return semantics (EN):\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 * 剥离旧快照(中)/ Strip outdated snapshot blocks from system prompt (EN).\n *\n * 说明(中)/ Notes (EN):\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/** 导出快照正则(中)/ Export snapshot regex for message helpers (EN). */\nexport { SNAPSHOT_REGEX };\n","/**\n * 紧凑消息构建(中)/ Compact message construction (EN).\n *\n * 目标:让 AI 每轮只基于“主目标 + 已完成步骤 + 最新快照”做增量决策。\n * Goal: enforce incremental decisions from master goal, done steps, and latest snapshot.\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 意图判定(中)/ Detect explicit intent to operate AutoPilot UI (EN).\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/** 输入摘要(中)/ Build brief text for tool input (EN). */\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 * 结果摘要(中)/ Build one-line summary for tool result (EN).\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 * 轨迹格式化(中)/ Format full tool trace to readable text (EN).\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 * 构建紧凑消息数组(中)/ Build compact AI message array (EN).\n *\n * Round 0: task + snapshot.\n * Round 1+: master goal + done steps + execution context + latest snapshot.\n *\n * 新增渐进式语义(中)/ Progressive semantics (EN):\n * - `remainingInstruction`:当前轮次仍待执行的文本。\n * - `previousRoundTasks`:上一轮已执行的任务数组,避免重复计划。\n * - 消息中要求模型输出 `REMAINING: ...` 或 `REMAINING: DONE`,供下一轮继续消费。\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:任务描述 + 快照,一条消息搞定 ───\n if (trace.length === 0) {\n // 中文释义:Round 0 发送给模型的信息结构\n // 1) 当前用户目标\n // 2) 当前轮次剩余任务文本\n // 3) 当前 URL(如有)\n // 4) 最新快照 + 执行约束(禁 page_info、禁误触 AI UI、下拉框用 select_option/fill)\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 \"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 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+:已完成步骤 + 执行上下文与快照(不再重复原始 userMessage) ───\n\n // 第 1 条:已完成步骤摘要(从 fullToolTrace 重建)\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 条:执行上下文 + 最新快照\n const hasErrors = trace.some(e => hasToolError(e.result));\n const contextParts: string[] = [\n // 中文释义:Round 1+ 执行上下文\n // - Master goal: 原始总目标,不变\n // - Current remaining instruction: 当前尚未完成的子任务文本\n // - Completed steps: 已完成步骤不重复\n // - Snapshot constraints: 只基于最新快照执行;不跨 DOM 变化链式操作\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 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 // 中文释义:要求模型显式返回剩余任务协议\n // - REMAINING: <text> 还有未完成\n // - REMAINING: DONE 当前任务文本已消费完\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 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 * 保护与恢复机制(中)/ Protection and recovery mechanisms (EN).\n *\n * 确保单次失败不打断主循环。\n * Keeps the main loop resilient to single-step failures.\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 动作(中)/ Redundant page_info actions to intercept (EN). */\nconst REDUNDANT_PAGE_INFO_ACTIONS = new Set([\"snapshot\", \"query_all\", \"get_url\", \"get_title\", \"get_viewport\"]);\n\n/**\n * 冗余 page_info 检查(中)/ Check whether page_info call is redundant (EN).\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 * 快照防抖(中)/ Debounce repeated snapshot calls (EN).\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 * 元素未找到恢复(中)/ Recover from element-not-found failures (EN).\n *\n * 前两次自动恢复,超过上限后返回终止提示。\n * Auto-recovers for initial attempts, then returns max-recovery signal.\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/** 导航后快照刷新(中)/ Refresh snapshot after navigation actions (EN). */\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/** 只读工具集合(中)/ Read-only tool set (EN). */\nconst READ_ONLY_TOOLS = new Set([\"page_info\"]);\n\n/**\n * 空转检测(中)/ Detect idle loops dominated by read-only actions (EN).\n * 返回 -1 表示应终止循环。\n * Returns -1 when loop should terminate.\n */\nexport function detectIdleLoop(\n toolCallNames: string[],\n consecutiveReadOnlyRounds: number,\n): number {\n const allReadOnly = toolCallNames.every(name => READ_ONLY_TOOLS.has(name));\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 主流程(中)/ Core environment-agnostic agent loop (EN).\n *\n * 负责消息构建、AI 决策、工具执行、恢复保护与指标汇总。\n * Orchestrates message build, AI decisions, tool execution, recovery, and metrics.\n *\n * 流程图(文本):\n *\n * 轮次开始\n * │\n * ├─ 确保快照可用\n * ├─ 构建紧凑消息(目标 + 剩余任务 + 执行轨迹 + 快照)\n * ├─ 调用模型\n * ├─ 无 toolCalls ? 结束 : 执行工具\n * ├─ 应用保护机制(冗余拦截/恢复/导航检测/空转/防自转)\n * ├─ 刷新快照\n * ▼\n * 下一轮或停机\n */\nimport {\n DEFAULT_MAX_ROUNDS,\n DEFAULT_NOT_FOUND_RETRY_ROUNDS,\n DEFAULT_NOT_FOUND_RETRY_WAIT_MS,\n} from \"./constants.js\";\nimport {\n getToolAction,\n isElementNotFoundResult,\n sleep,\n toContentString,\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 循环(中)/ Execute the agent loop (EN).\n *\n * 每轮:确保快照 → 构建消息 → 调用 AI → 执行工具 → 保护处理 → 刷新快照。\n * Per round: ensure snapshot -> build messages -> call AI -> execute tools -> apply protections -> refresh snapshot.\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 callbacks,\n } = params;\n\n // 固定依赖与运行态容器(中)/ Static dependencies and runtime containers (EN).\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 // 最终输出(中)/ Final output state (EN).\n let finalReply = \"\";\n\n // 循环控制状态(中)/ Loop control state (EN).\n let consecutiveSnapshotCalls = 0;\n let consecutiveReadOnlyRounds = 0;\n let usedRounds = 0;\n\n // token 统计(中)/ Token accounting (EN).\n let inputTokens = 0;\n let outputTokens = 0;\n // 渐进式任务状态(中)/ Progressive task state (EN).\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 // 恢复与拦截统计(中)/ Recovery/interception counters (EN).\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 // 快照体积统计(中)/ Snapshot size metrics (EN).\n let snapshotReadCount = 0;\n let snapshotSizeTotal = 0;\n let snapshotSizeMax = 0;\n\n /**\n * 记录快照统计(中)/ Record snapshot metrics (EN).\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 * 刷新页面快照(中)/ Refresh page snapshot (EN).\n *\n * 只做两件事:读取最新快照 + 更新快照统计。\n * Does exactly two things: read latest snapshot + update metrics.\n */\n const refreshSnapshot = async (): Promise<void> => {\n pageContext.latestSnapshot = await readPageSnapshot(registry);\n recordSnapshotStats(pageContext.latestSnapshot);\n };\n\n if (pageContext.latestSnapshot) {\n recordSnapshotStats(pageContext.latestSnapshot);\n }\n\n /**\n * 追加工具轨迹(中)/ Append tool trace entry (EN).\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 * 生成任务数组(中)/ Build normalized task array (EN).\n *\n * 将本轮 toolCalls 归一化成稳定字符串数组,便于:\n * - 回传到下一轮消息上下文(提醒已执行计划)\n * - 进行“是否与上一轮完全相同”的比较\n */\n const buildTaskArray = (toolCalls: Array<{ name: string; input: unknown }>): string[] =>\n toolCalls.map(tc => {\n const inputText = JSON.stringify(tc.input);\n return `${tc.name}:${inputText}`;\n });\n\n /**\n * 规范化模型文本输出(中)/ Normalize model text for next-round input (EN).\n *\n * 优先保留 REMAINING 行;否则截断首段文本,避免长篇规划污染下一轮输入。\n * Prefer REMAINING line; otherwise keep a short excerpt to avoid long planning spillover.\n */\n const 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) {\n return `REMAINING: ${remainingMatch[1].trim()}`;\n }\n const firstBlock = trimmed.split(/\\n\\s*\\n/)[0]?.trim() ?? trimmed;\n return firstBlock.slice(0, 220);\n };\n\n /**\n * 判定动作是否会触发 DOM 结构变化(中)/ Whether action may cause DOM-shape change (EN).\n *\n * 触发后应强制断轮,等待下一轮新快照继续。\n * Force round break after such action and continue with refreshed snapshot next round.\n */\n const shouldForceRoundBreak = (toolName: string, toolInput: unknown): boolean => {\n const action = getToolAction(toolInput);\n if (toolName === \"navigate\") {\n return action === \"goto\" || action === \"back\" || action === \"forward\" || action === \"reload\";\n }\n if (toolName === \"dom\") {\n return action === \"click\" || action === \"press\";\n }\n if (toolName === \"evaluate\") {\n return true;\n }\n return false;\n };\n\n /**\n * 将“找不到元素”的失败任务整理成可重试清单(中)/ Build retry task list for not-found failures (EN).\n */\n const collectMissingTask = (\n name: string,\n input: unknown,\n result: AgentLoopResult[\"toolCalls\"][number][\"result\"],\n ): MissingToolTask | 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 * 解析 REMAINING 协议(中)/ Parse REMAINING protocol from model text (EN).\n *\n * 支持:\n * - `REMAINING: <text>` → 继续下一轮消费该剩余文本\n * - `REMAINING: DONE` → 剩余任务为空\n * 返回 null 表示本轮没有提供 REMAINING 标记。\n */\n const 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 * 推进下一轮描述(中)/ Derive next-round instruction from model text (EN).\n *\n * 优先 REMAINING 协议;若未提供,则保持当前 remaining 不变。\n * Priority: REMAINING protocol first; otherwise keep current remaining instruction unchanged.\n */\n const 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 // 协议缺失时按规则回退:保持当前剩余任务不变。\n // Fallback when protocol is missing: keep current remaining instruction unchanged.\n return { nextInstruction: currentInstruction, hasRemainingProtocol: false };\n };\n\n /**\n * 启发式任务剔除(中)/ Heuristic remaining reduction for linear instructions (EN).\n *\n * 在 REMAINING 缺失但本轮有执行动作时,按“线性片段”剔除已执行步数,避免下一轮继续携带整段原任务。\n * When REMAINING is missing but actions were executed, drop executed step count from a linearized instruction.\n */\n const reduceRemainingHeuristically = (\n currentInstruction: string,\n executedCount: number,\n ): string => {\n if (!currentInstruction.trim() || executedCount <= 0) return currentInstruction;\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 // 主循环(中)/ Main round loop (EN).\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 // 计费/观测数据累计(中)/ Aggregate usage for observability (EN).\n inputTokens += response.usage?.inputTokens ?? 0;\n outputTokens += response.usage?.outputTokens ?? 0;\n\n // 先解析协议,最终推进在本轮执行后统一决定。\n // Parse protocol first; final remaining update is decided after execution.\n const parsedInstructionState = deriveNextInstruction(response.text, remainingInstruction);\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 // Compare whether current planned batch is identical to the previous one.\n if (plannedBatchKey === lastPlannedBatchKey) {\n consecutiveSamePlannedBatch += 1;\n } else {\n consecutiveSamePlannedBatch = 1;\n lastPlannedBatchKey = plannedBatchKey;\n }\n\n // 防自转:连续两轮给出相同计划且上一轮无错误,判定任务已完成或模型卡住,直接结束。\n // Anti-spin: if same planned batch appears twice and previous round had no error, stop the request.\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 // roundHasError guards anti-spin stop: do not hard-stop if previous round had errors.\n let roundHasError = false;\n const executedTaskCalls: Array<{ name: string; input: unknown }> = [];\n const roundMissingTasks: MissingToolTask[] = [];\n for (const tc of response.toolCalls) {\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\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 // Carry current execution state into next round context.\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 // 保护 5:空转检测\n const toolCallNames = executedTaskCalls.map(tc => tc.name);\n const idleResult = detectIdleLoop(toolCallNames, consecutiveReadOnlyRounds);\n if (idleResult === -1) {\n finalReply = response.text || \"任务已完成。\";\n if (finalReply) callbacks?.onText?.(finalReply);\n break;\n }\n consecutiveReadOnlyRounds = idleResult;\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 // 结果统计(中)/ Compute success/failure metrics (EN).\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 // 统一发出指标回调(中)/ Emit metrics callback once per chat (EN).\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 { AgentLoopParams, AgentLoopResult, AgentLoopCallbacks, AgentLoopMetrics } 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};\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 * 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\";\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\";\n\n// ─── 公共类型定义 ───\n\n/** AI 客户端配置(中)/ AI client configuration (EN). */\nexport type AIClientConfig = {\n /** AI 提供商: \"openai\" | \"copilot\" | \"anthropic\" */\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 \"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`,\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 * 根据工具名分发并执行工具调用。\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 * 极简系统提示词构建器(中)/ Minimal system prompt builder (EN).\n *\n * 纯函数,不依赖运行时环境;调用方只需传入工具定义和可选扩展指令。\n * Pure function with no runtime coupling; callers pass tools and optional extra instructions.\n */\nimport type { ToolDefinition } from \"./tool-registry.js\";\n\nexport type SystemPromptParams = {\n /** 已注册工具列表(中)/ Registered tool definitions (EN). */\n tools?: ToolDefinition[];\n /** AI 思考深度标签(中)/ Optional thinking-level label (EN). */\n thinkingLevel?: string;\n /** 额外英文指令(中)/ Additional English instructions (EN). */\n extraInstructions?: string | string[];\n};\n\n/**\n * 规范化额外指令(中)/ Normalize additional instructions (EN).\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 * 构建系统提示词(中)/ Build system prompt (EN).\n *\n * 约束:\n * - 输出给模型的提示词正文统一为英文。\n * - 中文仅用于代码注释,便于团队维护。\n *\n * Constraints:\n * - Prompt text sent to model stays English-only.\n * - Chinese content is used in code comments only for maintainability.\n */\nexport function buildSystemPrompt(params: SystemPromptParams = {}): string {\n const sections: string[] = [];\n sections.push(\n [\n \"You are AutoPilot, an AI agent controlling the current web page via tools.\",\n \"\",\n \"## Core Rules\",\n \"- Work from CURRENT snapshot + CURRENT remaining task directly. Do not restate the request.\",\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 \"- Use only visible targets from snapshot. Use #hashID as selector. Do not guess CSS selectors.\",\n \"- Batch independent visible actions in one round. Do not split one form into many rounds unnecessarily.\",\n \"- Strict input order: before every fill/type/select_option, first click or focus the same target in the SAME round.\",\n \"- Fixed sequence examples: dom.click(#field) -> dom.fill(#field, \\\"text\\\"); dom.click(#select) -> dom.select_option(#select, ...).\",\n \"- Form batch rule: for one visible form, complete all independent fields in one round; do not fill one field then verify repeatedly.\",\n \"- If an action will change DOM (open modal, navigate), stop after that action batch and continue next round with new snapshot.\",\n \"- Do NOT call page_info (snapshot/query/get_url/get_title). Snapshot is already provided every round.\",\n \"- For dropdown/select, use dom action=select_option (or fill on select).\",\n \"- Verification whitelist: do NOT use get_text/get_attr to verify input/select values unless the user explicitly asks for verification.\",\n \"- Do NOT interact with AutoPilot UI unless user explicitly asks.\",\n \"\",\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 \"## 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 // 工具列表(中)/ Available tool list (EN).\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 // 思考深度(中)/ Thinking-level hint (EN).\n if (params.thinkingLevel) {\n sections.push(\n [\n \"## Reasoning Profile\",\n `- Thinking level: ${params.thinkingLevel}`,\n ].join(\"\\n\"),\n );\n }\n\n // 额外指令(中)/ Additional custom instructions (EN).\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 * DOM Tool — 基于 Web API 的 DOM 操作工具。\n *\n * 替代 Playwright 的 click/fill/type 等操作,直接在页面上下文中执行。\n * 运行环境:浏览器 Content Script。\n *\n * 支持 15 种动作:\n * click — 点击元素\n * fill — 填写可编辑控件(input/textarea/select/contenteditable)\n * select_option — 选择下拉框选项(value/label)\n * clear — 清空输入控件\n * check — 勾选 checkbox/radio\n * uncheck — 取消勾选 checkbox\n * type — 逐字符模拟键入\n * focus — 聚焦元素\n * hover — 鼠标悬停(触发 mouseenter/mouseover)\n * press — 按下键盘按键(Enter/Escape/Tab/ArrowDown 等)\n * get_text — 获取元素文本内容\n * get_attr — 获取元素属性值\n * set_attr — 设置元素属性\n * add_class — 添加 CSS 类名\n * remove_class — 移除 CSS 类名\n */\nimport { Type } from \"@sinclair/typebox\";\nimport type { ToolDefinition, ToolCallResult } from \"../../core/tool-registry.js\";\nimport type { RefStore } from \"../ref-store.js\";\n\nconst DEFAULT_WAIT_MS = 1000;\n\n/** 当前活跃的 RefStore 实例(由 WebAgent 在 chat() 时设置) */\nlet activeRefStore: RefStore | undefined;\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * 安全地查询 DOM 元素。\n *\n * 支持两种定位方式(优先级从高到低):\n * - hash ID(以 \"#\" 开头且在 RefStore 中存在):确定性 hash 查找(最高效)\n * - CSS 选择器(其他):传统 querySelector\n */\nfunction queryElement(selector: string): Element | string {\n try {\n // #hashId — 优先从 RefStore 查找\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 // 不在 RefStore 中 → 回退到 CSS 选择器(可能是 #some-id)\n }\n\n // CSS 选择器\n const el = document.querySelector(selector);\n if (!el) return `未找到匹配 \"${selector}\" 的元素`;\n return el;\n } catch {\n return `选择器语法错误: ${selector}`;\n }\n}\n\n/**\n * 设置当前活跃的 RefStore(由 WebAgent 在 chat 开始时调用)。\n */\nexport function setActiveRefStore(store: RefStore | undefined): void {\n activeRefStore = store;\n}\n\n/** 获取当前活跃的 RefStore(供其他工具复用) */\nexport function getActiveRefStore(): RefStore | undefined {\n return activeRefStore;\n}\n\n/**\n * 在给定超时时间内轮询查找元素。\n * - 返回 Element:找到元素\n * - 返回 string:选择器语法错误\n * - 返回 null:超时未找到\n */\nasync function waitForElement(\n selector: string,\n timeoutMs: number,\n): Promise<Element | string | null> {\n const start = Date.now();\n\n while (Date.now() - start <= timeoutMs) {\n const elOrError = queryElement(selector);\n if (typeof elOrError !== \"string\") return elOrError;\n\n if (elOrError.startsWith(\"选择器语法错误\")) return elOrError;\n await sleep(100);\n }\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)) {\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_WAIT_MS;\n}\n\n/**\n * 模拟真实用户输入:触发 input、change 事件,兼容 React/Vue 等框架。\n */\nfunction dispatchInputEvents(el: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement): void {\n try {\n el.dispatchEvent(new InputEvent(\"input\", {\n bubbles: true,\n cancelable: true,\n inputType: \"insertText\",\n data: null,\n }));\n } catch {\n el.dispatchEvent(new Event(\"input\", { bubbles: true, cancelable: true }));\n }\n el.dispatchEvent(new Event(\"change\", { bubbles: true, cancelable: true }));\n}\n\n/**\n * 使用原生 setter 写入表单值,提升对受控组件(React/Vue 等)的兼容性。\n */\nfunction setNativeEditableValue(\n el: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n value: string,\n): void {\n const proto =\n el instanceof HTMLInputElement\n ? HTMLInputElement.prototype\n : el instanceof HTMLTextAreaElement\n ? HTMLTextAreaElement.prototype\n : HTMLSelectElement.prototype;\n const descriptor = Object.getOwnPropertyDescriptor(proto, \"value\");\n if (descriptor?.set) {\n descriptor.set.call(el, value);\n return;\n }\n el.value = value;\n}\n\n/**\n * 读取可编辑元素当前值。\n */\nfunction getEditableValue(\n el: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n): string {\n return el.value ?? \"\";\n}\n\n/**\n * 将常见 key 映射为更接近浏览器语义的 KeyboardEvent.code。\n */\nfunction resolveKeyboardCode(key: string): string {\n const map: Record<string, string> = {\n Enter: \"Enter\",\n Escape: \"Escape\",\n Esc: \"Escape\",\n Tab: \"Tab\",\n Space: \"Space\",\n \" \": \"Space\",\n Backspace: \"Backspace\",\n Delete: \"Delete\",\n ArrowUp: \"ArrowUp\",\n ArrowDown: \"ArrowDown\",\n ArrowLeft: \"ArrowLeft\",\n ArrowRight: \"ArrowRight\",\n };\n return map[key] ?? key;\n}\n\n/**\n * 生成元素的可读描述,用于在操作结果中展示实际命中的 DOM 节点。\n * 格式:<tag#id.class> \"文本\" [attr=val, ...]\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 : \"\";\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\n // 关键属性\n const hints: string[] = [];\n for (const attr of [\"type\", \"name\", \"placeholder\", \"href\", \"role\"]) {\n const val = el.getAttribute(attr);\n if (val) hints.push(`${attr}=${val}`);\n }\n if (el instanceof HTMLSelectElement && el.value) {\n hints.push(`val=${el.value}`);\n }\n const attrHint = hints.length > 0 ? ` [${hints.join(\", \")}]` : \"\";\n\n return `<${tag}${id}${cls}>${textHint}${attrHint}`;\n}\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 if (style.display === \"none\" || style.visibility === \"hidden\") return false;\n if (style.opacity === \"0\") return false;\n const rect = el.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n}\n\nfunction isElementDisabled(el: Element): boolean {\n if (!(el instanceof HTMLElement)) return false;\n if (el.hasAttribute(\"disabled\")) return true;\n if (el.getAttribute(\"aria-disabled\") === \"true\") return true;\n if (\"disabled\" in el && typeof (el as { disabled?: unknown }).disabled === \"boolean\") {\n return Boolean((el as { disabled?: boolean }).disabled);\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 const blockedTypes = new Set([\"checkbox\", \"radio\", \"file\", \"button\", \"submit\", \"reset\"]);\n return !blockedTypes.has(el.type) && !el.readOnly;\n }\n if (el instanceof HTMLSelectElement) return true;\n return el instanceof HTMLElement && el.isContentEditable;\n}\n\nfunction ensureActionable(\n el: Element,\n action: string,\n selector: string,\n): ToolCallResult | null {\n if (!el.isConnected) {\n return {\n content: `\"${selector}\" 元素已脱离文档,无法执行 ${action}`,\n details: { error: true, code: \"ELEMENT_DETACHED\", action, selector },\n };\n }\n\n const readOnlyActions = new Set([\"get_text\", \"get_attr\"]);\n if (!readOnlyActions.has(action) && !isElementVisible(el)) {\n return {\n content: `\"${selector}\" 元素不可见,无法执行 ${action}`,\n details: { error: true, code: \"ELEMENT_NOT_VISIBLE\", action, selector },\n };\n }\n\n const mutationActions = new Set([\n \"click\", \"fill\", \"type\", \"press\", \"select_option\", \"clear\", \"check\", \"uncheck\",\n ]);\n if (mutationActions.has(action) && isElementDisabled(el)) {\n return {\n content: `\"${selector}\" 元素已禁用,无法执行 ${action}`,\n details: { error: true, code: \"ELEMENT_DISABLED\", action, selector },\n };\n }\n\n if ([\"fill\", \"type\", \"clear\"].includes(action) && !isEditableElement(el)) {\n return {\n content: `\"${selector}\" 不是可编辑元素,无法执行 ${action}`,\n details: { error: true, code: \"UNSUPPORTED_FILL_TARGET\", action, selector },\n };\n }\n\n return null;\n}\n\nfunction isOptionCandidateVisible(el: Element): boolean {\n if (!(el instanceof HTMLElement)) return false;\n if (!isElementVisible(el)) return false;\n const text = el.textContent?.trim() ?? \"\";\n return text.length > 0;\n}\n\nfunction findVisibleOptionByText(text: string): HTMLElement | null {\n const target = text.trim().toLowerCase();\n if (!target) return null;\n const nodes = Array.from(document.querySelectorAll(\n '[role=\"option\"], .bk-select-option, .bk-option, [data-option], li, option',\n ));\n\n for (const node of nodes) {\n if (!isOptionCandidateVisible(node)) continue;\n const content = node.textContent?.trim().toLowerCase() ?? \"\";\n if (content === target) return node as HTMLElement;\n }\n for (const node of nodes) {\n if (!isOptionCandidateVisible(node)) continue;\n const content = node.textContent?.trim().toLowerCase() ?? \"\";\n if (content.includes(target)) return node as HTMLElement;\n }\n return null;\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, press, get_text, get_attr, set_attr, add_class, remove_class.\",\n \"Use the hash ID from DOM snapshot (e.g. #a1b2c) as selector.\",\n ].join(\" \"),\n\n schema: Type.Object({\n action: Type.String({\n description:\n \"DOM action: click | fill | select_option | clear | check | uncheck | type | focus | hover | 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(\n Type.String({ description: \"Value for fill/type/set_attr actions\" }),\n ),\n key: Type.Optional(\n Type.String({ description: \"Key name for press action (e.g. Enter, Escape, Tab, ArrowDown, ArrowUp, Backspace, Delete, Space)\" }),\n ),\n label: Type.Optional(\n Type.String({ description: \"Label text for select_option action (fallback when value is not provided)\" }),\n ),\n index: Type.Optional(\n Type.Number({ description: \"0-based option index for select_option action\" }),\n ),\n attribute: Type.Optional(\n Type.String({ description: \"Attribute name for get_attr/set_attr actions\" }),\n ),\n className: Type.Optional(\n Type.String({ description: \"CSS class name for add_class/remove_class\" }),\n ),\n waitMs: Type.Optional(\n Type.Number({\n description:\n \"Optional wait timeout in ms before action (default: 1000). Use 0 to disable waiting.\",\n }),\n ),\n waitSeconds: Type.Optional(\n Type.Number({\n description:\n \"Optional wait timeout in seconds before action. Used when waitMs is not provided.\",\n }),\n ),\n force: Type.Optional(\n Type.Boolean({ description: \"Skip actionability checks for interaction actions (default false).\" }),\n ),\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 let el: Element;\n if (waitMs > 0) {\n const found = await waitForElement(selector, waitMs);\n\n if (typeof found === \"string\") {\n return {\n content: found,\n details: { error: true, code: \"INVALID_SELECTOR\", action, selector },\n };\n }\n\n if (!found) {\n return {\n content: `未找到匹配 \"${selector}\" 的元素`,\n details: {\n error: true,\n code: \"ELEMENT_NOT_FOUND\",\n action,\n selector,\n waitMs,\n },\n };\n }\n\n el = found;\n } else {\n const elOrError = queryElement(selector);\n if (typeof elOrError === \"string\") {\n const code = elOrError.startsWith(\"未找到\")\n ? \"ELEMENT_NOT_FOUND\"\n : \"INVALID_SELECTOR\";\n return {\n content: elOrError,\n details: { error: true, code, action, selector, waitMs },\n };\n }\n el = elOrError;\n }\n\n try {\n if (!force) {\n const checkResult = ensureActionable(el, action, selector);\n if (checkResult) return checkResult;\n }\n\n switch (action) {\n // ─── 交互类 ───\n\n case \"click\": {\n // Playwright-like 兼容:若点中 option,自动写回父 select 并触发 change。\n if (el instanceof HTMLOptionElement) {\n const parent = el.parentElement;\n if (parent instanceof HTMLSelectElement) {\n parent.focus();\n parent.value = el.value;\n dispatchInputEvents(parent);\n return { content: `已选择 ${describeElement(parent)} 的选项 \"${el.value}\"` };\n }\n }\n\n // 模拟点击:先 focus 再 click,触发完整事件链\n if (el instanceof HTMLElement) {\n el.focus();\n el.dispatchEvent(new PointerEvent(\"pointerdown\", { bubbles: true, cancelable: true }));\n el.dispatchEvent(new MouseEvent(\"mousedown\", { bubbles: true, cancelable: true }));\n el.dispatchEvent(new PointerEvent(\"pointerup\", { bubbles: true, cancelable: true }));\n el.dispatchEvent(new MouseEvent(\"mouseup\", { bubbles: true, cancelable: true }));\n el.click();\n } else {\n el.dispatchEvent(new MouseEvent(\"click\", { bubbles: true }));\n }\n return { content: `已点击 ${describeElement(el)}` };\n }\n\n case \"focus\": {\n // 聚焦元素\n if (el instanceof HTMLElement) {\n el.focus();\n } else {\n el.dispatchEvent(new FocusEvent(\"focus\", { bubbles: true }));\n }\n return { content: `已聚焦 ${describeElement(el)}` };\n }\n\n case \"hover\": {\n // 鼠标悬停:触发 mouseenter → mouseover 事件链\n el.dispatchEvent(new MouseEvent(\"mouseenter\", { bubbles: false, cancelable: true }));\n el.dispatchEvent(new MouseEvent(\"mouseover\", { bubbles: true, cancelable: true }));\n el.dispatchEvent(new MouseEvent(\"mousemove\", { bubbles: true, cancelable: true }));\n return { content: `已悬停 ${describeElement(el)}` };\n }\n\n case \"press\": {\n // 按下指定键:先聚焦元素,再触发 keydown → keypress → keyup 完整事件链\n const key = (params.key as string) || (params.value as string);\n if (!key) return { content: \"缺少 key 参数(如 Enter, Escape, Tab)\" };\n\n if (el instanceof HTMLElement) el.focus();\n\n const eventInit: KeyboardEventInit = {\n key,\n code: resolveKeyboardCode(key),\n bubbles: true,\n cancelable: true,\n };\n const keydownAllowed = el.dispatchEvent(new KeyboardEvent(\"keydown\", eventInit));\n el.dispatchEvent(new KeyboardEvent(\"keypress\", eventInit));\n el.dispatchEvent(new KeyboardEvent(\"keyup\", eventInit));\n\n if (keydownAllowed && key === \"Enter\") {\n if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {\n const form = el.form ?? el.closest(\"form\");\n form?.dispatchEvent(new Event(\"submit\", { bubbles: true, cancelable: true }));\n }\n }\n return { content: `已在 ${describeElement(el)} 上按下 ${key}` };\n }\n\n case \"fill\": {\n // 填写可编辑控件:支持 input / textarea / select / contenteditable\n const value = params.value as string;\n if (value === undefined) return { content: \"缺少 value 参数\" };\n\n if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {\n if (el instanceof HTMLInputElement) {\n const blockedTypes = new Set([\"checkbox\", \"radio\", \"file\", \"button\", \"submit\", \"reset\"]);\n if (blockedTypes.has(el.type)) {\n return {\n content: `\"${selector}\" 为 input[type=${el.type}],不支持 fill;请使用 click/press/select_option 等动作。`,\n details: { error: true, code: \"UNSUPPORTED_FILL_TARGET\", action, selector },\n };\n }\n }\n el.focus();\n setNativeEditableValue(el, value);\n dispatchInputEvents(el);\n\n const actualValue = getEditableValue(el);\n if (actualValue !== value) {\n return {\n content: `\"${selector}\" 填写后值不一致:期望 \"${value}\",实际 \"${actualValue}\"`,\n details: {\n error: true,\n code: \"FILL_NOT_APPLIED\",\n action,\n selector,\n expected: value,\n actual: actualValue,\n },\n };\n }\n } else if (el instanceof HTMLSelectElement) {\n el.focus();\n\n // 1) 先按 option.value 精确匹配\n let matched = false;\n for (const option of Array.from(el.options)) {\n if (option.value === value) {\n el.value = option.value;\n matched = true;\n break;\n }\n }\n\n // 2) 再按展示文本匹配(忽略大小写与首尾空格)\n if (!matched) {\n const normalized = value.trim().toLowerCase();\n for (const option of Array.from(el.options)) {\n if (option.text.trim().toLowerCase() === normalized) {\n el.value = option.value;\n matched = true;\n break;\n }\n }\n }\n\n if (!matched) {\n return { content: `\"${selector}\" 下拉框中不存在选项 \"${value}\"` };\n }\n\n dispatchInputEvents(el);\n\n const actualValue = getEditableValue(el);\n if (actualValue !== el.value) {\n return {\n content: `\"${selector}\" 下拉框状态异常,未确认写入`,\n details: {\n error: true,\n code: \"FILL_NOT_APPLIED\",\n action,\n selector,\n expected: value,\n actual: actualValue,\n },\n };\n }\n } else if (el instanceof HTMLElement && el.isContentEditable) {\n el.focus();\n el.textContent = value;\n el.dispatchEvent(new Event(\"input\", { bubbles: true }));\n } else {\n return { content: `\"${selector}\" 不是可编辑元素` };\n }\n return { content: `已填写 ${describeElement(el)}: \"${value}\"` };\n }\n\n case \"select_option\": {\n // Playwright-like selectOption:通过 value 或 label 精确选择下拉项\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 if (!(el instanceof HTMLSelectElement)) {\n if (!(el instanceof HTMLElement)) {\n return { content: `\"${selector}\" 不是下拉框元素` };\n }\n\n el.focus();\n el.click();\n const wanted = (label ?? value ?? \"\").trim();\n if (!wanted) {\n return { content: `\"${selector}\" 为自定义下拉时,需提供 value 或 label` };\n }\n const option = findVisibleOptionByText(wanted);\n if (!option) {\n return {\n content: `未找到与 \"${wanted}\" 匹配的可见下拉选项(自定义下拉)`,\n details: {\n error: true,\n code: \"OPTION_NOT_FOUND\",\n action,\n selector,\n wanted,\n },\n };\n }\n option.click();\n return { content: `已在自定义下拉中选择 \"${wanted}\"` };\n }\n\n el.focus();\n\n const options = Array.from(el.options);\n let selectedOption: HTMLOptionElement | undefined;\n\n if (value !== undefined) {\n selectedOption = options.find(option => option.value === value);\n }\n\n if (!selectedOption && label !== undefined) {\n const normalizedLabel = label.trim().toLowerCase();\n selectedOption = options.find(option => option.text.trim().toLowerCase() === normalizedLabel);\n }\n\n if (!selectedOption && value !== undefined) {\n const normalizedValueAsLabel = value.trim().toLowerCase();\n selectedOption = options.find(option => option.text.trim().toLowerCase() === normalizedValueAsLabel);\n }\n\n if (!selectedOption && index !== undefined) {\n if (index < 0 || index >= options.length) {\n return { content: `\"${selector}\" 下拉框不存在 index=${index} 的选项` };\n }\n selectedOption = options[index];\n }\n\n if (!selectedOption) {\n const wanted = value ?? label ?? `index=${index}`;\n return { content: `\"${selector}\" 下拉框中不存在选项 \"${wanted}\"` };\n }\n\n if (selectedOption.disabled) {\n return { content: `\"${selector}\" 目标选项已禁用:${selectedOption.value}` };\n }\n\n if (!el.multiple) {\n for (const option of options) {\n option.selected = false;\n }\n }\n selectedOption.selected = true;\n el.value = selectedOption.value;\n\n dispatchInputEvents(el);\n return {\n content: `已选择 ${describeElement(el)}: value=\"${selectedOption.value}\", label=\"${selectedOption.text.trim()}\"`,\n };\n }\n\n case \"clear\": {\n if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) {\n el.focus();\n setNativeEditableValue(el, \"\");\n dispatchInputEvents(el);\n return { content: `已清空 ${describeElement(el)}` };\n }\n if (el instanceof HTMLElement && el.isContentEditable) {\n el.focus();\n el.textContent = \"\";\n el.dispatchEvent(new Event(\"input\", { bubbles: true }));\n return { content: `已清空 ${describeElement(el)}` };\n }\n return { content: `\"${selector}\" 不是可清空元素` };\n }\n\n case \"check\": {\n if (!(el instanceof HTMLInputElement) || (el.type !== \"checkbox\" && el.type !== \"radio\")) {\n return { content: `\"${selector}\" 不是 checkbox/radio` };\n }\n el.focus();\n if (!el.checked) {\n el.checked = true;\n dispatchInputEvents(el);\n }\n return { content: `已勾选 ${describeElement(el)}` };\n }\n\n case \"uncheck\": {\n if (!(el instanceof HTMLInputElement) || el.type !== \"checkbox\") {\n return { content: `\"${selector}\" 不是 checkbox` };\n }\n el.focus();\n if (el.checked) {\n el.checked = false;\n dispatchInputEvents(el);\n }\n return { content: `已取消勾选 ${describeElement(el)}` };\n }\n\n case \"type\": {\n // 逐字符键入:每个字符触发 keydown → keypress → input → keyup\n // 适用于有实时监听键盘事件的输入框(如搜索自动补全)\n const value = params.value as string;\n if (value === undefined) return { content: \"缺少 value 参数\" };\n\n if (el instanceof HTMLElement) el.focus();\n\n for (const char of value) {\n el.dispatchEvent(\n new KeyboardEvent(\"keydown\", { key: char, bubbles: true }),\n );\n el.dispatchEvent(\n new KeyboardEvent(\"keypress\", { key: char, bubbles: true }),\n );\n if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {\n el.value += char;\n }\n el.dispatchEvent(new Event(\"input\", { bubbles: true }));\n el.dispatchEvent(\n new KeyboardEvent(\"keyup\", { key: char, bubbles: true }),\n );\n }\n return { content: `已逐字输入到 ${describeElement(el)}: \"${value}\"` };\n }\n\n // ─── 读取类 ───\n\n case \"get_text\": {\n // 获取元素的文本内容(包括子元素)\n const text = el.textContent?.trim() ?? \"\";\n return { content: `${describeElement(el)} 的文本内容:${text || \"(空)\"}` };\n }\n\n case \"get_attr\": {\n // 获取元素的指定属性值\n const attribute = params.attribute as string;\n if (!attribute) return { content: \"缺少 attribute 参数\" };\n const attrValue = el.getAttribute(attribute);\n return { content: `${describeElement(el)} 的 ${attribute} = ${attrValue ?? \"(不存在)\"}` };\n }\n\n // ─── 修改类 ───\n\n case \"set_attr\": {\n // 设置元素的属性值\n const attribute = params.attribute as string;\n const value = params.value as string;\n if (!attribute || value === undefined)\n return { content: \"缺少 attribute 或 value 参数\" };\n el.setAttribute(attribute, value);\n return { content: `已设置 ${describeElement(el)} 的 ${attribute}=\"${value}\"` };\n }\n\n case \"add_class\": {\n // 给元素添加 CSS 类名\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\n case \"remove_class\": {\n // 移除元素的 CSS 类名\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 {\n content: `DOM 操作 \"${action}\" 失败: ${err instanceof Error ? err.message : String(err)}`,\n details: { error: true, action, selector },\n };\n }\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 { getActiveRefStore } from \"./dom-tool.js\";\n\n/** 快照配置选项 */\nexport type SnapshotOptions = {\n /** 最大遍历深度(默认 6) */\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\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 ?? 6;\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\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 /** 布尔状态属性 — 只在存在时输出(无值),如 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 (directText) return false;\n return true;\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 return false;\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 // 构建当前元素的内部路径(用于 hash 计算,不输出到快照)\n const index = getSiblingIndex(el);\n const currentPath = `${parentPath}/${tag}${index}`;\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 const val = el.getAttribute(attr);\n if (val) attrs.push(`${attr}=\"${val}\"`);\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 // 6. data-* 属性 — 只保留 data-testid(自动化测试定位用)\n const testId = el.getAttribute(\"data-testid\") || el.getAttribute(\"data-test-id\");\n if (testId) attrs.push(`data-testid=\"${testId.slice(0, 25)}\"`);\n\n // 7. 对于 input/textarea,补充当前实际 value(截短到 40 字符)\n if ((el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) && el.value) {\n const currentVal = 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=\"${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 if (isEmptyLayoutContainer(el, directText)) {\n const allChildren = Array.from(el.children);\n const interactiveChildren = allChildren.filter(isInteractiveElement);\n const nonInteractiveChildren = allChildren.filter((child) => !isInteractiveElement(child));\n const orderedChildren = [...interactiveChildren, ...nonInteractiveChildren];\n const selectedChildren = orderedChildren.slice(0, maxChildren);\n const omittedChildren = orderedChildren.length - selectedChildren.length;\n\n const childLines: string[] = [];\n for (let i = 0; i < selectedChildren.length; i++) {\n // 子元素继承当前路径(保证 hash 计算正确),但不增加缩进\n const childResult = walk(selectedChildren[i], depth, currentPath);\n if (childResult) childLines.push(childResult);\n }\n\n if (omittedChildren > 0) {\n childLines.push(`${\" \".repeat(depth)}... (${omittedChildren} children omitted)`);\n }\n\n // 如果子树也全部为空,整个容器就被剪掉\n return childLines.join(\"\\n\");\n }\n\n // 构建当前元素描述:[标签] \"文本\" 属性 #ID\n let line = `${indent}[${tag}]`;\n if (directText) line += ` \"${directText.slice(0, maxTextLength)}\"`;\n if (attrs.length) line += ` ${attrs.join(\" \")}`;\n // 使用 hash ID(如 #a1b2c)或回退到完整 XPath\n if (refStore) {\n const hashId = refStore.set(el, currentPath);\n line += ` #${hashId}`;\n } else {\n line += ` ref=\"${currentPath}\"`;\n }\n\n const lines: string[] = [line];\n emittedNodes++;\n\n // 递归子元素(优先保留可交互元素,再保留普通元素)\n const allChildren = Array.from(el.children);\n const interactiveChildren = allChildren.filter(isInteractiveElement);\n const nonInteractiveChildren = allChildren.filter((child) => !isInteractiveElement(child));\n const orderedChildren = [...interactiveChildren, ...nonInteractiveChildren];\n const selectedChildren = orderedChildren.slice(0, maxChildren);\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\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: 6)\" }),\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 }),\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) ?? 6;\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 snapshot = generateSnapshot(document.body, {\n maxDepth,\n viewportOnly,\n pruneLayout,\n maxNodes,\n maxChildren,\n maxTextLength,\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 — 基于 Web API 的页面导航工具。\n *\n * 替代 Playwright 的 goto/goBack/goForward/reload。\n * 运行环境:浏览器 Content Script。\n *\n * 支持 5 种动作:\n * goto — 跳转到指定 URL\n * back — 浏览器后退\n * forward — 浏览器前进\n * reload — 刷新当前页面\n * scroll — 滚动页面到指定位置或元素\n */\nimport { Type } from \"@sinclair/typebox\";\nimport type { ToolDefinition, ToolCallResult } from \"../../core/tool-registry.js\";\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 ].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: \"CSS selector for scroll action (scrolls element into view)\" }),\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 // 跳转到指定 URL\n const url = params.url as string;\n if (!url) return { content: \"缺少 url 参数\" };\n\n // 支持相对路径和绝对路径\n window.location.href = url;\n return { content: `正在导航到 ${url}` };\n }\n\n case \"back\": {\n // 浏览器后退(等同于点击后退按钮)\n window.history.back();\n return { content: \"已后退\" };\n }\n\n case \"forward\": {\n // 浏览器前进\n window.history.forward();\n return { content: \"已前进\" };\n }\n\n case \"reload\": {\n // 刷新当前页面\n window.location.reload();\n return { content: \"正在刷新页面\" };\n }\n\n case \"scroll\": {\n // 滚动页面:优先滚动到元素,否则滚动到坐标\n const selector = params.selector as string | undefined;\n\n if (selector) {\n const el = document.querySelector(selector);\n if (!el) return { content: `未找到元素 \"${selector}\"` };\n el.scrollIntoView({ behavior: \"smooth\", block: \"center\" });\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 — 基于 MutationObserver 的元素等待工具。\n *\n * 替代 Playwright 的 waitForSelector/waitForNavigation。\n * 运行环境:浏览器 Content Script。\n *\n * 支持 4 种动作:\n * wait_for_selector — 等待匹配选择器的元素出现\n * wait_for_hidden — 等待元素消失或隐藏\n * wait_for_text — 等待页面中出现指定文本\n * wait_for_stable — 等待 DOM 在一段时间内无变化\n */\nimport { Type } from \"@sinclair/typebox\";\nimport type { ToolDefinition, ToolCallResult } from \"../../core/tool-registry.js\";\n\n/** 默认超时时间(毫秒) */\nconst DEFAULT_TIMEOUT = 10_000;\n\ntype SelectorState = \"attached\" | \"visible\" | \"hidden\" | \"detached\";\n\n/**\n * Playwright 风格可见性判定(近似)。\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 if (style.display === \"none\" || style.visibility === \"hidden\") 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 * 读取 selector 当前状态。\n */\nfunction evaluateSelectorState(selector: string, state: SelectorState): { matched: boolean; element?: Element } {\n const el = document.querySelector(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 * 等待 selector 达到指定状态(近似 Playwright state 语义)。\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, 80);\n const observer = new MutationObserver(check);\n observer.observe(document.body, {\n childList: true,\n subtree: true,\n attributes: true,\n characterData: true,\n });\n\n check();\n });\n}\n\n/**\n * 等待页面中出现指定文本。\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, {\n childList: true,\n subtree: true,\n characterData: true,\n });\n });\n}\n\n/**\n * 等待页面进入稳定状态:在 quietMs 时间窗口内没有 DOM 变化。\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, {\n childList: true,\n subtree: true,\n attributes: true,\n characterData: true,\n });\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 }, 50);\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: 10000)\" }),\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 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\";\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\"(默认 \"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 /** 自定义系统提示词(不传则使用默认 Web 提示词) */\n systemPrompt?: string;\n /** 最大工具调用轮次(默认 10) */\n maxRounds?: number;\n /** 是否启用多轮对话记忆(默认 false) */\n memory?: boolean;\n /** 是否在每次对话前自动生成页面快照(默认 true) */\n autoSnapshot?: boolean;\n /** 快照选项(视口裁剪、智能剪枝等,autoSnapshot 开启时生效) */\n snapshotOptions?: SnapshotOptions;\n};\n\n// ─── WebAgent 类 ───\n\nexport class WebAgent {\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 private customSystemPrompt?: 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 /** 工具注册表实例 — 每个 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.customSystemPrompt = options.systemPrompt;\n this.memory = options.memory ?? false;\n this.autoSnapshot = options.autoSnapshot ?? true;\n this.snapshotOptions = options.snapshotOptions ?? {};\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\n /** 注册一个自定义工具 */\n registerTool(tool: ToolDefinition): void {\n this.registry.register(tool);\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 setSystemPrompt(prompt: string): void {\n this.customSystemPrompt = prompt;\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 // 复用 core/system-prompt 或使用自定义\n let systemPrompt =\n this.customSystemPrompt ??\n buildSystemPrompt({ tools: this.registry.getDefinitions() });\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: 8,\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 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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentpage",
3
- "version": "0.0.17",
3
+ "version": "0.0.20",
4
4
  "description": "Embeddable AI Agent SDK for browsers — let AI operate your web pages via tool-calling",
5
5
  "keywords": [
6
6
  "ai",