agentpage 0.0.38 → 0.0.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,6 +9,7 @@
9
9
  > 核心主张:通过 **Prompt + Tools + 路由**,快速为网站实现 AI 赋能,并构建**前端运行时 AI Skill**。AutoPilot 本质上是一个运行在前端浏览器中的 AI Agent。
10
10
 
11
11
  [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
12
+ <a href="https://www.npmjs.com/package/agentpage"><img src="https://img.shields.io/npm/v/agentpage" alt="npm" /></a>
12
13
 
13
14
  AutoPilot 的目标不是生成文本,而是在浏览器中完成真实任务:点击、填写、导航、等待、执行脚本,并在每一轮根据最新页面状态持续推进。
14
15
 
@@ -18,14 +19,11 @@ AutoPilot 的目标不是生成文本,而是在浏览器中完成真实任务
18
19
  2. **易集成、低侵入**:可以快速接入各类前端工程,通过少量配置就能落地可执行 Agent。
19
20
  3. **可编排、可扩展**:通过**用户可自定义的 Prompt 规则** + Tools 注册 + 路由上下文,为网站渐进式构建 AI 能力(即前端运行时 AI Skill)。
20
21
 
21
- AutoPilot 的定位是“**Web 端原生 Agent 补充层**”:在当前行业里,大多数 Agent 仍以“后端服务编排 + API 调用”为主,而真正长期驻留在前端浏览器、直接理解并操作真实页面状态的 Agent 仍然稀缺。AutoPilot 关注的正是这块空白能力。
22
+ AutoPilot 的定位是"**Web 端原生 Agent 补充层**":在当前行业里,大多数 Agent 仍以"后端服务编排 + API 调用"为主,而真正长期驻留在前端浏览器、直接理解并操作真实页面状态的 Agent 仍然稀缺。AutoPilot 关注的正是这块空白能力。
22
23
 
23
- ## README 导航
24
-
25
- - 产品定位与价值:为什么是前端原生 AI Agent
26
- - 快速开始:5 分钟集成可运行的 Web Agent
27
- - 路由化落地:通过 Prompt + Tools + 路由构建前端运行时 AI Skill
28
- - 执行机制权威说明:`权威执行文档(v2)`
24
+ ```bash
25
+ npm install agentpage
26
+ ```
29
27
 
30
28
  ---
31
29
 
@@ -34,8 +32,8 @@ AutoPilot 的定位是“**Web 端原生 Agent 补充层**”:在当前行业
34
32
  - 角色定位:作为后端 Agent 的补充,而非替代
35
33
  - 运行形态:完全运行在浏览器上下文(可扩展到 Chrome Extension)
36
34
  - 核心机制:快照驱动 + 工具调用 + 增量消费
37
- - 场景目标:让 AI 理解“当前路由能做什么”,并在该上下文内可靠执行
38
- - 产品形态:可作为前端系统的“AI 插件层”,按项目逐步接入、按路由逐步增强
35
+ - 场景目标:让 AI 理解"当前路由能做什么",并在该上下文内可靠执行
36
+ - 产品形态:可作为前端系统的"AI 插件层",按项目逐步接入、按路由逐步增强
39
37
  - 架构分层:
40
38
  - `core`:环境无关引擎(Agent Loop、AI Client、Tool Registry)
41
39
  - `web`:浏览器能力实现(DOM/导航/快照/等待/执行)
@@ -54,69 +52,40 @@ AutoPilot 的定位是“**Web 端原生 Agent 补充层**”:在当前行业
54
52
 
55
53
  为什么这个模型对复杂业务有效:
56
54
 
57
- - Prompt 解决“怎么做才安全可靠”。
58
- - Tools 解决“能做什么动作”。
59
- - Route 解决“现在应该做什么”。
55
+ - Prompt 解决"怎么做才安全可靠"。
56
+ - Tools 解决"能做什么动作"。
57
+ - Route 解决"现在应该做什么"。
60
58
 
61
59
  因此它非常适合 DevOps/ERP 等高复杂前端系统:可按路由渐进式接入,不需要一次性重构全站。
62
60
 
63
- ## 机制亮点:前端运行时 AI Skill
64
-
65
- - 核心公式:**Prompt + Tools + 路由 = 前端运行时 AI Skill**。
66
- - 快速赋能路径:先定义路由能力,再配置**用户自定义 Prompt 约束**,最后注册项目级/路由级 Tools。
67
-
68
- - 运行完全依赖前端:Agent 在页面运行时直接感知当前 DOM、状态和路由。
69
- - 路由是能力边界:每个路由都可以定义“当前页面可做什么、不能做什么”。
70
- - Prompt 是行为策略:你可以自定义项目级/路由级 Prompt 约束,控制执行风格、风险动作与权限边界。
71
- - Tools 是执行能力:可以注册项目级通用工具,也可以注册路由级专用工具。
72
- - 渐进式赋能:先从高价值路由开始,逐步扩展到全站,形成可维护的 AI Skill 网络。
73
-
74
- 一句话理解:
75
-
76
- - 后端 Agent 负责“全局任务编排”,AutoPilot 负责“前端最后一公里执行”,两者组合可形成完整闭环。
77
-
78
61
  ## 为什么是前端原生 Agent
79
62
 
80
63
  - 传统后端 Agent 更擅长:流程编排、跨系统调用、数据聚合。
81
64
  - 前端原生 Agent 更擅长:理解当前页面真实状态、直接操作复杂 UI 组件、处理路由内交互细节。
82
65
  - 两者组合后可形成闭环:
83
- - 后端负责“全局计划与系统级动作”
84
- - 前端负责“页面级执行与交互落地”
66
+ - 后端负责"全局计划与系统级动作"
67
+ - 前端负责"页面级执行与交互落地"
85
68
 
86
69
  对 DevOps / ERP 这类复杂系统尤其关键:
87
70
 
88
71
  - 页面状态复杂(列表、筛选、弹窗、步骤流、权限态)且变化快。
89
- - 纯后端视角很难精确知道“此刻页面上到底可点什么”。
72
+ - 纯后端视角很难精确知道"此刻页面上到底可点什么"。
90
73
  - 前端 Agent 可以基于快照和路由上下文做增量消费,显著减少误操作与空转。
91
74
 
92
- ## 优势
75
+ ## 核心优势
93
76
 
94
- - **强调机制价值**:通过 Prompt + Tools + 路由三层组合,可以快速把“可执行 AI 能力”植入现有前端系统。
77
+ - **Prompt + Tools + 路由三层解耦**:可以快速把"可执行 AI 能力"植入现有前端系统,按路由渐进式接入,支持"项目级工具 + 路由级工具"组合。
78
+ - **增量任务消费协议(REMAINING)**:任务不是一次性执行,而是逐轮消费收敛。每轮只做当前快照可执行的动作,通过 `REMAINING` 协议跟踪进度,支持协议修复和启发式回退,确保复杂多步任务稳定收敛。
79
+ - **8 层保护机制**:冗余拦截、快照防抖、元素恢复、Not-found 重试对话流、导航刷新、空转检测、重复批次防自转、协议修复 —— 目标是**稳定收敛**,而不是偶然成功。
80
+ - **Playwright 级别交互语义**:完整 pointer/mouse 事件链、4 种 scrollIntoView 策略轮换、actionability 五重检查(可见/稳定/可用/可编辑/遮挡)、智能重定向 retarget、隐藏控件代理点击(ElementPlus/AntD)、`select_option` value/label/index 三策略。
81
+ - **运行时事件信号追踪**:通过 `EventTarget.prototype` 补丁全局追踪事件绑定,快照中输出 `listeners="clk,inp,chg"` 信号,帮助 AI 精准识别真实可交互元素,而非猜测。
82
+ - **效果验证机制(Effect Check)**:每轮行动前自动检查上轮操作是否在当前快照中生效,未生效则尝试邻近元素,避免重复点击无效目标。
83
+ - **结构化可观测指标**:每次 chat 输出 `AgentLoopMetrics`(轮次、成功率、恢复次数、快照体积、Token 消耗),可直接接入监控系统。
84
+ - **core/web 分层架构**:`core` 零 DOM 依赖,可在 Worker/Extension/Node 复用;`web` 封装浏览器能力。
95
85
 
96
- - 基于项目路由落地前端级 Agent:
97
- - Agent 直接运行在真实业务页面,可感知当前路由上下文(列表页、详情页、弹窗页等)
98
- - 同一套执行循环可在不同路由中持续推进任务,不依赖后端额外编排
99
- - 基于路由配置渐进式讲解页面能力:
100
- - 可按路由注入“页面能力说明”(可查询、可编辑、可提交流程、风险动作等)
101
- - AI 随路由切换逐步理解系统,不需要一次性学习全站复杂规则
102
- - 前端注册 Tools,快速接入复杂工程:
103
- - 通过 `registerTools()` 和 `registerTool()` 把 DOM 操作、导航、等待、业务动作统一抽象为可调用能力
104
- - 支持“项目级工具 + 路由级工具”组合:共性能力复用,特殊页面能力按需挂载
105
- - 复杂页面中的“表单 + 下拉 + 弹窗 + 列表 + 路由跳转”可在同一 Agent 流程内组合执行
106
- - 对既有前端工程侵入低:
107
- - 保持分层边界(`core` 环境无关、`web` 负责浏览器实现),便于在现有项目按需接入
108
- - 通过快照驱动与工具调用机制,优先复用现有页面结构与交互逻辑
86
+ ## 企业落地实践
109
87
 
110
- ## 前景与演进方向
111
-
112
- - 从“单页面自动操作”升级到“路由网络级执行助手”:在多个路由间持续消费任务。
113
- - 从“通用工具调用”升级到“业务语义工具层”:把企业关键动作封装成稳定能力。
114
- - 从“单次执行”升级到“可观测闭环”:持续记录成功率、恢复率、收敛轮次,反哺策略优化。
115
- - 与后端 Agent 协同:后端给出全局任务,前端 Agent 负责最后一公里落地执行。
116
-
117
- ## 企业落地实践(深度说明)
118
-
119
- 对于企业前端系统(尤其是 DevOps / ERP),真正决定成败的不是“有没有 Agent”,而是是否能在真实路由中稳定收敛。AutoPilot 的价值在于它把落地拆成可持续演进的能力模型,而不是一次性大改造。
88
+ 对于企业前端系统(尤其是 DevOps / ERP),真正决定成败的不是"有没有 Agent",而是是否能在真实路由中稳定收敛。AutoPilot 的价值在于它把落地拆成可持续演进的能力模型,而不是一次性大改造。
120
89
 
121
90
  在工程实践中,建议围绕三条主线理解和建设:
122
91
 
@@ -128,337 +97,300 @@ AutoPilot 的定位是“**Web 端原生 Agent 补充层**”:在当前行业
128
97
 
129
98
  - 它天然适配渐进式改造:先做高价值路由,再扩展到全站,不阻断原有业务。
130
99
  - 它天然具备可观测性:每轮工具调用、恢复次数、快照体积、收敛轮次都能被记录和优化。
131
- - 它天然支持协同:后端 Agent 负责全局流程编排,前端 AutoPilot 负责页面级“最后一公里”执行。
100
+ - 它天然支持协同:后端 Agent 负责全局流程编排,前端 AutoPilot 负责页面级"最后一公里"执行。
132
101
 
133
- 最终形态不是“在网页里放一个会聊天的助手”,而是“在前端运行时形成一套可配置、可执行、可演进的 AI Skill 网络”。
102
+ 最终形态不是"在网页里放一个会聊天的助手",而是"在前端运行时形成一套可配置、可执行、可演进的 AI Skill 网络"。
134
103
 
135
104
  ---
136
105
 
137
106
  ## 快速开始
138
107
 
139
- ### 安装
140
-
141
- ```bash
142
- pnpm install
143
- ```
144
-
145
- ### 最小可运行示例
146
-
147
108
  ```ts
148
109
  import { WebAgent } from "agentpage";
149
110
 
150
111
  const agent = new WebAgent({
151
112
  token: "your-api-key",
152
- provider: "doubao", // openai | copilot | anthropic | deepseek | doubao | qwen
153
- model: "doubao-1.5-pro-32k",
154
- // 用户可自定义 Prompt 规则(项目级/路由级)
155
- systemPrompt: "You are an assistant for this route. Follow route safety constraints.",
156
- memory: true,
157
- autoSnapshot: true,
158
- stream: true,
113
+ provider: "openai", // openai | copilot | anthropic | deepseek | doubao | qwen
114
+ model: "gpt-4o",
159
115
  });
160
116
 
161
- agent.registerTools();
117
+ agent.registerTools(); // 注册内置 Web 工具
162
118
 
163
119
  agent.callbacks = {
164
- onRound: (round) => console.log("round", round + 1),
165
- onToolCall: (name, input) => console.log("tool", name, input),
166
- onToolResult: (name, result) => console.log("result", name, result.content),
167
- onText: (text) => console.log("assistant", text),
120
+ onRound: (round) => console.log(`第 ${round + 1} 轮`),
121
+ onToolCall: (name, input) => console.log("调用:", name, input),
122
+ onToolResult: (name, result) => console.log("结果:", name, result.content),
123
+ onText: (text) => console.log("回复:", text),
168
124
  };
169
125
 
170
126
  const result = await agent.chat("打开任务弹窗,填写标题和优先级,然后提交");
171
127
  console.log(result.reply);
172
- ```
173
-
174
- ### 启动 Demo
175
-
176
- ```bash
177
- pnpm demo
128
+ console.log(result.metrics); // { roundCount, toolSuccessRate, inputTokens, ... }
178
129
  ```
179
130
 
180
131
  ### 按路由构建 AI Skill(推荐范式)
181
132
 
182
133
  ```ts
183
- import { WebAgent } from "agentpage";
184
-
185
- const agent = new WebAgent({
186
- token: "your-api-key",
187
- provider: "deepseek",
188
- model: "deepseek-chat",
189
- });
190
-
191
- agent.registerTools(); // 注册通用工具
192
-
193
- type RouteSkillConfig = {
194
- prompt: string;
195
- registerRouteTools?: () => void;
196
- };
197
-
198
- const routeSkills: Record<string, RouteSkillConfig> = {
134
+ const routeSkills: Record<string, { prompt: string; tools?: () => void }> = {
199
135
  "/tickets": {
200
- prompt: "You are an assistant for the tickets page. Prioritize filtering, status updates, and safe submit.",
136
+ prompt: "You are on tickets page. Prioritize filtering and status updates.",
201
137
  },
202
138
  "/deploy": {
203
- prompt: "You are an assistant for deployment page. Confirm risky actions before triggering release.",
204
- registerRouteTools: () => {
205
- // agent.registerTool(createDeployTool())
206
- },
139
+ prompt: "You are on deploy page. Confirm risky actions before release.",
140
+ tools: () => agent.registerTool(createDeployTool()),
207
141
  },
208
142
  };
209
143
 
210
- function applyRouteSkill(pathname: string) {
211
- const skill = routeSkills[pathname];
144
+ function applyRouteSkill(path: string) {
145
+ agent.clearCustomTools();
146
+ agent.clearSystemPrompts();
147
+ const skill = routeSkills[path];
212
148
  if (!skill) return;
213
- agent.setSystemPrompt(skill.prompt); // 用户自定义 Prompt
214
- skill.registerRouteTools?.(); // 路由级 Tools
149
+ agent.setSystemPrompt(skill.prompt);
150
+ skill.tools?.();
215
151
  }
216
152
 
217
153
  applyRouteSkill(location.pathname);
218
154
  ```
219
155
 
220
- 这套模式的价值是:
221
-
222
- - 同一个 Agent 内核,在不同路由动态切换“策略 + 能力”。
223
- - 项目可先接入高价值路由,再逐步扩展,形成可维护的 AI Skill 网络。
224
-
225
156
  ---
226
157
 
227
- ## 配置参数(WebAgentOptions)
228
-
229
- 创建 `WebAgent` 时可用参数如下:
158
+ ## 架构
230
159
 
231
- | 参数 | 类型 | 默认值 | 说明 |
232
- | --- | --- | --- | --- |
233
- | `client` | `AIClient` | - | 自定义 AI 客户端;传入后优先使用该实例,忽略 token/provider/model/baseURL |
234
- | `token` | `string` | `""` | API Token(GitHub PAT / OpenAI API Key / Anthropic Key / DeepSeek Key / Doubao Ark Key / DashScope Key) |
235
- | `provider` | `string` | `"copilot"` | AI 服务商:`copilot` / `openai` / `anthropic` / `deepseek` / `doubao` / `qwen` |
236
- | `model` | `string` | `"gpt-4o"` | 模型名称(需与 provider 匹配,如 `doubao-1.5-pro-32k`、`qwen-plus`、`deepseek-chat`) |
237
- | `baseURL` | `string` | - | 自定义 API 基础地址(用于代理/私有部署,覆盖 provider 默认端点) |
238
- | `stream` | `boolean` | `true` | 是否启用流式返回(SSE);关闭后使用 JSON 非流式响应 |
239
- | `dryRun` | `boolean` | `false` | 干运行模式:仅输出 AI 计划调用的工具列表,不执行真实操作 |
240
- | `systemPrompt` | `string \| Record<string, string>` | 内置 prompt | 系统提示词注册项:支持单条或 key-value 多条注册(会追加到内置 prompt) |
241
- | `maxRounds` | `number` | `40` | 单次 chat 最大循环轮次,超过后强制终止 |
242
- | `memory` | `boolean` | `false` | 是否开启多轮对话记忆(跨 chat 调用保留历史消息) |
243
- | `autoSnapshot` | `boolean` | `true` | chat 前是否自动生成首轮页面快照并注入 system prompt |
244
- | `snapshotOptions` | `SnapshotOptions` | `{}` | 快照生成参数覆盖(深度、裁剪、剪枝、节点上限等) |
245
- | `roundStabilityWait` | `RoundStabilityWaitOptions` | `{ enabled: true }` | 轮次后稳定等待配置(loading hidden + DOM stable);`loadingSelectors` 为“与默认值合并去重”,不会覆盖默认列表 |
246
-
247
- ### 参数详细说明
248
-
249
- #### `client`(自定义 AI 客户端)
250
-
251
- 当你有自己的 AI 后端或需要自定义请求逻辑时,可以传入实现了 `AIClient` 接口的实例:
252
-
253
- ```ts
254
- import { BaseAIClient } from "agentpage/core";
255
-
256
- const customClient = new BaseAIClient({
257
- chatHandler: async (params) => {
258
- // 自定义请求逻辑:转发到内部网关、添加鉴权等
259
- const res = await fetch("/api/ai-proxy", {
260
- method: "POST",
261
- body: JSON.stringify(params),
262
- });
263
- return res.json();
264
- },
265
- });
266
-
267
- const agent = new WebAgent({ client: customClient });
268
160
  ```
269
-
270
- `AIClient` 接口只需实现一个 `chat` 方法:
271
-
272
- ```ts
273
- type AIClient = {
274
- chat(params: {
275
- systemPrompt: string;
276
- messages: AIMessage[];
277
- tools?: ToolDefinition[];
278
- }): Promise<AIChatResponse>;
279
- };
161
+ ┌──────────────────────────────────────────────────────┐
162
+ │ WebAgent (web) │
163
+ │ ┌──────────┐ ┌──────────────┐ ┌────────────────┐ │
164
+ │ │ AI Client│ │ Agent Loop │ │ Web Tools │ │
165
+ │ │ (fetch) │ │ (core 循环) │ │ (DOM/导航/等待)│ │
166
+ └──────────┘ └──────────────┘ └────────────────┘ │
167
+ └──────────────────────────────────────────────────────┘
280
168
  ```
281
169
 
282
- #### `provider` `model` 组合
170
+ | 层级 | 职责 | 约束 |
171
+ | --- | --- | --- |
172
+ | `core` | AI 客户端适配、Agent Loop 编排、工具注册分发、系统提示词 | 不依赖 DOM API,可在任意 JS 运行时复用 |
173
+ | `web` | WebAgent 入口、5 个内置工具、RefStore 映射、事件追踪、UI 面板 | 依赖浏览器 API,不反向污染 core |
283
174
 
284
- | Provider | 默认端点 | 推荐模型 | 说明 |
285
- | --- | --- | --- | --- |
286
- | `copilot` | GitHub Copilot API | `gpt-4o` | 需要 GitHub PAT |
287
- | `openai` | `https://api.openai.com/v1` | `gpt-4o` / `gpt-4o-mini` | 标准 OpenAI 接口 |
288
- | `anthropic` | `https://api.anthropic.com` | `claude-sonnet-4-20250514` | Anthropic 原生接口 |
289
- | `deepseek` | `https://api.deepseek.com` | `deepseek-chat` | DeepSeek 接口 |
290
- | `doubao` | `https://ark.cn-beijing.volces.com/api/v3` | `doubao-1.5-pro-32k` | 火山引擎 Ark(OpenAI 兼容) |
291
- | `qwen` | `https://dashscope.aliyuncs.com/compatible-mode/v1` | `qwen-plus` | 阿里云百炼兼容模式(OpenAI 兼容) |
175
+ ---
292
176
 
293
- #### `systemPrompt`(Prompt 注册与维护)
177
+ ## 核心机制
294
178
 
295
- 推荐使用“key-value 注册表”维护 Prompt:
179
+ ### 1. 快照驱动决策
296
180
 
297
- ```ts
298
- // 方式 1:初始化时注册单条(默认 key = default)
299
- const agent = new WebAgent({
300
- systemPrompt: "You are a deployment assistant. Only operate deploy-related UI.",
301
- // ...
302
- });
181
+ AI 每轮不是"凭记忆猜页面",而是基于最新 DOM 快照选择可执行动作:
303
182
 
304
- // 方式 2:初始化时按 key 批量注册
305
- const routeAgent = new WebAgent({
306
- systemPrompt: {
307
- tickets: "You are on tickets page. Prioritize filtering and status updates.",
308
- deploy: "You are on deploy page. Confirm risky actions before release.",
309
- },
310
- });
311
-
312
- // 方式 3:运行时维护(新增/覆盖/删除/只保留)
313
- agent.setSystemPrompt("tickets", "You are on tickets page. Only operate ticket UI.");
314
- agent.setSystemPrompt("global prompt fallback"); // 写入默认 key: default
315
- agent.removeSystemPrompt("tickets");
316
- agent.keepOnlySystemPrompt("default");
317
- agent.clearSystemPrompts();
318
- console.log(agent.getSystemPrompts());
183
+ ```
184
+ 输入:当前快照 S + 当前任务 R → 输出:可执行任务批次 T
319
185
  ```
320
186
 
321
- 内置 prompt 的核心规则包括:快照优先决策、任务增量消费(REMAINING 协议)、批量执行、禁止 page_info 空转等。并且在点击场景中要求优先选择具备明确点击信号(如 `clk/pdn/mdn`、`onclick`、链接/按钮语义)的目标;若点击无效,下一轮应优先尝试同语义组中邻近的可操作元素,避免重复点击无效节点。注册表中的 Prompt 会作为扩展段追加到内置 prompt 之后。
187
+ 快照包含:
188
+ - 元素标签与 ARIA role(`[combobox]`/`[slider]` 替代冗余 tag+role)
189
+ - hash 选择器(`#a1b2c`)— 仅交互元素携带,非交互元素作为上下文
190
+ - 运行态属性(`checked`/`disabled`/`readonly`/`val`/`selected`)
191
+ - 事件信号(`listeners="clk,inp,chg"`)— 基于运行时真实绑定
192
+ - 智能剪枝:布局容器折叠、`collapsed-group` 标记、视口裁剪、节点预算控制
322
193
 
323
- #### `dryRun`(干运行模式)
194
+ ### 2. 增量任务消费(REMAINING 协议)
324
195
 
325
- 开启后,AI 会正常返回工具调用计划,但不会真正执行任何工具。适用于:
326
- - 调试 prompt 效果
327
- - 验证 AI 是否正确理解任务
328
- - 演示 Agent 执行流程
196
+ 用户任务被逐轮"吃掉",不一次性硬做完:
329
197
 
330
- ```ts
331
- const agent = new WebAgent({ dryRun: true, /* ... */ });
332
- const result = await agent.chat("填写表单并提交");
333
- // result.reply 中包含 AI 计划调用的工具列表,但不会真正点击/填写
334
198
  ```
199
+ 总任务: A → B → C
335
200
 
336
- #### `memory`(多轮对话记忆)
201
+ Round 1: 执行 A → REMAINING: B → C
202
+ Round 2: 执行 B → REMAINING: C
203
+ Round 3: 执行 C → REMAINING: DONE
204
+ ```
337
205
 
338
- 开启后,每次 `chat()` 调用结束会将本轮消息追加到内部历史中,下次 `chat()` 时自动携带:
206
+ 每轮消息携带:`当前剩余任务` + `上轮已执行动作` + `效果检查提示` + `最新快照`。
339
207
 
340
- ```ts
341
- const agent = new WebAgent({ memory: true, /* ... */ });
208
+ - Round 0 使用原始任务;Round 1+ 不再注入原始消息,避免"回头重做"
209
+ - 模型返回 `REMAINING: <text>` 表示还有剩余,`REMAINING: DONE` 表示完成
210
+ - REMAINING 缺失时自动启发式剔除已完成任务,或保持不推进
211
+ - "remaining 未完成 + 无工具调用" 触发协议修复回合
342
212
 
343
- await agent.chat("打开设置页面"); // 1 轮:AI 可看到空历史
344
- await agent.chat("把语言切换成英文"); // 第 2 轮:AI 可看到第 1 轮的上下文
345
- await agent.chat("然后保存设置"); // 第 3 轮:AI 看到前 2 轮的完整上下文
213
+ ### 3. 批量执行但不跨 DOM 变化
346
214
 
347
- agent.clearHistory(); // 手动清空历史
348
- ```
349
-
350
- 注意:多轮记忆会增加每次请求的 token 消耗,建议在真正需要跨 chat 上下文时开启。
215
+ - **可同轮**:同时填写多个已可见输入框(`focus→fill→focus→fill`)
216
+ - **不可同轮**:点击"打开弹窗"后立即填写弹窗内容(应等下一轮新快照)
217
+ - `click` 始终是本轮最后一个动作,执行后强制断轮
218
+ - 轮次结束后自动执行"loading 隐藏 + DOM 静默"双重等待
351
219
 
352
- #### `maxRounds`(最大轮次)
220
+ ### 4. 多层保护机制
353
221
 
354
- 控制单次 `chat()` 的最大循环次数。每一轮包含"构建消息 调用 AI → 执行工具 → 刷新快照"完整流水线。
222
+ | 机制 | 触发条件 | 效果 |
223
+ | --- | --- | --- |
224
+ | 冗余 page_info 拦截 | AI 调用 `page_info.*` | 直接返回拦截结果,避免 Token 浪费 |
225
+ | 元素未找到恢复 | `dom` 操作命中失败 | 等待 100ms → 刷新快照 → 返回恢复标记 |
226
+ | Not-found 重试对话流 | 本轮存在未找到元素 | 注入失败上下文 + `attempt x/y`,最多 2 轮 |
227
+ | 导航后上下文刷新 | `navigate` 成功 | 重置 RefStore + 刷新快照 |
228
+ | 空转检测 | 连续只读无推进 | 自动终止循环 |
229
+ | 重复批次防自转 | 连续两轮相同任务批次 | 直接终止 |
230
+ | 协议修复回合 | remaining 未完成却无工具调用 | 注入强约束提示 |
231
+ | 轮次稳定等待 | 本轮有 DOM 变化动作 | loading hidden + DOM quiet(200ms/4s) |
232
+
233
+ ### 5. 停机条件
234
+
235
+ - 模型返回 `REMAINING: DONE` 并输出总结
236
+ - 无工具调用且 remaining 已收敛
237
+ - 重复批次防自转 / 协议修复后仍无推进
238
+ - 达到 `maxRounds` 上限
355
239
 
356
- - 简单任务(填写一个表单):通常 3-8 轮
357
- - 中等任务(多步骤流程):通常 8-20 轮
358
- - 复杂任务(跨页面操作):可能需要 20-40 轮
240
+ ---
359
241
 
360
- 建议:先用默认值 40,观察 `metrics.roundCount` 确定实际轮次分布后再调整。
242
+ ## 配置参数
361
243
 
362
- ### 快照参数(snapshotOptions)
244
+ ### WebAgentOptions
363
245
 
364
246
  | 参数 | 类型 | 默认值 | 说明 |
365
247
  | --- | --- | --- | --- |
366
- | `maxDepth` | `number` | `12`(chat 首轮)/ `12`(page_info.snapshot) | DOM 最大遍历深度,超过该深度的子树不会输出 |
367
- | `viewportOnly` | `boolean` | `false`(chat 首轮)/ `true`(page_info.snapshot) | 是否仅保留与视口相交的元素 |
368
- | `pruneLayout` | `boolean` | `true` | 是否折叠无意义纯布局容器(div/span/section 等) |
369
- | `maxNodes` | `number` | `500`(chat 首轮)/ `220`(page_info.snapshot) | 快照最大节点输出数量,超出后停止遍历并追加截断提示 |
370
- | `maxChildren` | `number` | `30`(chat 首轮)/ `25`(page_info.snapshot) | 每个父节点最多保留的子元素数量 |
371
- | `maxTextLength` | `number` | `40` | 单节点文本截断长度(字符数) |
372
- | `refStore` | `RefStore` | 自动创建 | hash ID 映射表(一般无需手动传入,WebAgent 自动管理) |
248
+ | `client` | `AIClient` | - | 自定义 AI 客户端实例;传入后忽略 token/provider/model/baseURL |
249
+ | `token` | `string` | `""` | API Token(GitHub PAT / OpenAI Key / Anthropic Key 等) |
250
+ | `provider` | `string` | `"copilot"` | AI 服务商(见下方 Provider 表) |
251
+ | `model` | `string` | `"gpt-4o"` | 模型名称(需与 provider 匹配) |
252
+ | `baseURL` | `string` | - | 自定义 API 端点(代理/私有部署) |
253
+ | `stream` | `boolean` | `true` | 是否启用 SSE 流式输出 |
254
+ | `requestTimeoutMs` | `number` | `45000` | 单次 AI 请求超时(毫秒) |
255
+ | `dryRun` | `boolean` | `false` | 干运行模式:输出工具计划但不执行 |
256
+ | `systemPrompt` | `string \| Record<string, string>` | 内置 | 自定义 Prompt;支持单条或 key-value 多条注册 |
257
+ | `maxRounds` | `number` | `40` | 单次 chat 最大循环轮次 |
258
+ | `memory` | `boolean` | `false` | 是否开启多轮对话记忆(跨 chat 保留历史) |
259
+ | `autoSnapshot` | `boolean` | `true` | chat 前自动生成首轮快照 |
260
+ | `snapshotOptions` | `SnapshotOptions` | `{}` | 快照参数(深度、裁剪、节点上限等) |
261
+ | `roundStabilityWait` | `RoundStabilityWaitOptions` | `{ enabled: true }` | 轮次后稳定等待配置 |
262
+ | `panel` | `boolean \| PanelOptions` | - | 内置 UI 面板配置 |
263
+
264
+ ### Provider 支持矩阵
265
+
266
+ | Provider | 默认端点 | 推荐模型 |
267
+ | --- | --- | --- |
268
+ | `copilot` | GitHub Copilot API | `gpt-4o` |
269
+ | `openai` | `https://api.openai.com/v1` | `gpt-4o` / `gpt-4o-mini` |
270
+ | `anthropic` | `https://api.anthropic.com` | `claude-sonnet-4-20250514` |
271
+ | `deepseek` | `https://api.deepseek.com` | `deepseek-chat` |
272
+ | `doubao` | `https://ark.cn-beijing.volces.com/api/v3` | `doubao-1.5-pro-32k` |
273
+ | `qwen` | `https://dashscope.aliyuncs.com/compatible-mode/v1` | `qwen-plus` |
373
274
 
374
- ### 快照参数详细说明
275
+ ### 快照参数(SnapshotOptions)
375
276
 
376
- #### `maxDepth`(遍历深度)
277
+ | 参数 | 类型 | 默认值 | 说明 |
278
+ | --- | --- | --- | --- |
279
+ | `maxDepth` | `number` | `12` | DOM 最大遍历深度 |
280
+ | `viewportOnly` | `boolean` | `false` | 仅保留视口内元素 |
281
+ | `pruneLayout` | `boolean` | `true` | 折叠无意义布局容器,子节点提升 |
282
+ | `maxNodes` | `number` | `500` | 快照最大节点数 |
283
+ | `maxChildren` | `number` | `30` | 每个父节点最大子元素数 |
284
+ | `maxTextLength` | `number` | `40` | 文本截断长度 |
285
+ | `listenerEvents` | `string[]` | 9 种常用事件 | 快照输出的 listener 事件白名单 |
377
286
 
378
- 决定 DOM 树遍历的最大层级。从根元素(`document.body`)到目标元素的嵌套层数。
287
+ ### 轮次稳定等待(RoundStabilityWaitOptions)
379
288
 
380
- - 设置过低(如 3-4):可能遗漏深层嵌套的表单控件或弹窗内容
381
- - 设置过高(如 12+):快照体积膨胀,浪费 token 且可能包含大量无用结构层
382
- - 推荐值:`10-12`,覆盖大多数组件库深层嵌套场景
289
+ | 参数 | 类型 | 默认值 | 说明 |
290
+ | --- | --- | --- | --- |
291
+ | `enabled` | `boolean` | `true` | 是否启用 |
292
+ | `timeoutMs` | `number` | `4000` | 总超时 |
293
+ | `quietMs` | `number` | `200` | DOM 静默窗口 |
294
+ | `loadingSelectors` | `string[]` | AntD/ElementPlus/BK/TDesign 等 | 追加合并,不覆盖默认 |
383
295
 
384
- #### `viewportOnly`(视口裁剪)
296
+ ---
385
297
 
386
- - `true`:只保留当前可视区域内的元素,完全在视口外的元素跳过
387
- - `false`:保留整个页面所有可见元素,不论是否在视口内
298
+ ## 内置工具
388
299
 
389
- 使用建议:
390
- - 首轮快照建议 `false`(避免"元素不存在"误判,完整了解页面结构)
391
- - 后续恢复快照可以用 `true`(减少 token 消耗)
392
- - 长列表页面建议 `true`(避免快照过大)
300
+ 5 个内置工具通过 `agent.registerTools()` 一次注册。
393
301
 
394
- #### `pruneLayout`(布局折叠)
302
+ ### dom — DOM 交互
395
303
 
396
- 核心优化策略,可显著减少快照体积:
304
+ | 动作 | 说明 | 关键参数 |
305
+ | --- | --- | --- |
306
+ | `click` | 点击(完整 pointer/mouse 事件链) | `selector`, `clickCount` |
307
+ | `fill` | 清空后填写 | `selector`, `value` |
308
+ | `type` | 逐字追加输入 | `selector`, `value` |
309
+ | `press` | 按键(支持 `Control+a` 组合键) | `selector`, `key` |
310
+ | `select_option` | 选择下拉选项(value/label/index 三策略) | `selector`, `value`/`label`/`index` |
311
+ | `check` / `uncheck` | 勾选/取消 checkbox | `selector` |
312
+ | `clear` | 清空输入框 | `selector` |
313
+ | `focus` / `hover` | 聚焦/悬停 | `selector` |
314
+ | `get_text` / `get_attr` | 读取文本/属性 | `selector` |
315
+ | `set_attr` / `add_class` / `remove_class` | 修改属性/类名 | `selector`, `value` |
316
+
317
+ **Playwright 风格增强**:
318
+ - **Actionability 五重检查**:可见性、帧稳定性(rAF 连续 3 帧)、可用性(disabled + aria-disabled 祖先链)、可编辑性、遮挡检测(elementFromPoint)
319
+ - **智能重定向(retarget)**:非交互元素自动查找最近 button/link/label.control
320
+ - **scrollIntoView 4 策略轮换**:`ifNeeded → end → center → start`,解决 sticky 遮挡
321
+ - **隐藏控件代理点击**:ElementPlus/AntD 的 switch/checkbox/radio 自动重定向到可见代理元素
322
+ - **fill 分类型**:date/color/range 走 `value` setter,text 类走 `selectAll` + 原生写入
323
+ - **完整事件链**:`pointermove→pointerdown→mousedown→focus→pointerup→mouseup→click`
324
+
325
+ ### navigate — 页面导航
326
+
327
+ | 动作 | 说明 |
328
+ | --- | --- |
329
+ | `goto` | 跳转到指定 URL |
330
+ | `back` / `forward` | 浏览器后退/前进 |
331
+ | `reload` | 刷新页面 |
332
+ | `scroll` | 滚动到指定元素或坐标 |
397
333
 
398
- ```
399
- 开启前(pruneLayout=false): 开启后(pruneLayout=true):
400
- [div] [button] "提交" #abc
401
- [div] [input] type="text" #def
402
- [div]
403
- [button] "提交" #abc
404
- [input] type="text" #def
405
- ```
334
+ ### wait — 条件等待
406
335
 
407
- 折叠规则:
408
- - 没有 `id`、没有语义标签(如 `main`/`nav`)、没有交互属性、没有直接文本的纯布局div/span
409
- - 子节点直接**提升**输出到父级位置
410
- - 当同一折叠容器提升出多个相邻节点时,用 `collapsed-group` 括号块标记它们的来源关联
336
+ | 动作 | 说明 | state |
337
+ | --- | --- | --- |
338
+ | `wait_for_selector` | 等待选择器达到状态 | `attached`/`visible`/`hidden`/`detached` |
339
+ | `wait_for_hidden` | 等待元素隐藏 | - |
340
+ | `wait_for_text` | 等待页面出现文本 | - |
341
+ | `wait_for_stable` | 等待 DOM 静默 | - |
411
342
 
412
- #### `maxNodes`(节点预算)
343
+ 双通道检测:轮询(80ms)+ MutationObserver,确保快速响应又不遗漏。
413
344
 
414
- 全局节点输出预算。当已输出节点数达到该值时,停止继续遍历并追加截断提示 `... (truncated, N nodes emitted)`。
345
+ ### page_info 页面信息
415
346
 
416
- - 大型表单页面:建议 300-500
417
- - 简单页面:100-200 即可
418
- - 长列表页面:建议配合 `viewportOnly=true` 使用
347
+ `get_url` / `get_title` / `get_selection` / `get_viewport` / `snapshot` / `query_all`
419
348
 
420
- #### `maxChildren`(子节点上限)
349
+ > Agent Loop 中 `page_info` 大多被拦截为冗余,框架每轮自动提供最新快照。
421
350
 
422
- 每个父节点最多输出的子元素数量。超过部分用 `... (N children omitted)` 汇总。
351
+ ### evaluate JS 执行
423
352
 
424
- 适用于超长列表(如表格的 `tbody` 有数百行),避免快照被重复结构淹没。
353
+ 执行页面上下文 JavaScript 表达式或语句块。兜底工具,适用于其他工具无法覆盖的场景。
425
354
 
426
- #### 首轮快照与后续快照的参数差异
355
+ ---
427
356
 
428
- | 场景 | maxDepth | viewportOnly | maxNodes | maxChildren | 原因 |
429
- | --- | --- | --- | --- | --- | --- |
430
- | `chat()` 首轮 | 12 | false | 500 | 30 | 优先完整性,提供充足的页面上下文 |
431
- | `page_info.snapshot`(loop 内) | 12 | false | 500 | 30 | 与首轮对齐,保证一致性 |
432
- | `page_info.snapshot`(手动调用) | 12 | true | 220 | 25 | 较小体积,适合局部更新 |
357
+ ## 自定义 Prompt
433
358
 
434
- ---
359
+ ```ts
360
+ // 方式 1:初始化单条
361
+ const agent = new WebAgent({
362
+ systemPrompt: "Only operate deploy-related UI. Confirm before release.",
363
+ });
435
364
 
436
- ## 运行时 API(完整参考)
365
+ // 方式 2:key-value 多条
366
+ const agent = new WebAgent({
367
+ systemPrompt: {
368
+ safety: "Never delete data without confirmation.",
369
+ deploy: "Confirm risky actions before triggering release.",
370
+ },
371
+ });
437
372
 
438
- ### 工具管理
373
+ // 方式 3:运行时维护
374
+ agent.setSystemPrompt("tickets", "Prioritize filtering and status updates.");
375
+ agent.removeSystemPrompt("tickets");
376
+ agent.keepOnlySystemPrompt("deploy");
377
+ agent.clearSystemPrompts();
378
+ ```
439
379
 
440
- | 方法 | 签名 | 说明 |
441
- | --- | --- | --- |
442
- | `registerTools()` | `(): void` | 注册全部 5 个内置工具(`dom/navigate/page_info/wait/evaluate`) |
443
- | `registerTool(tool)` | `(tool: ToolDefinition): void` | 注册单个自定义工具 |
444
- | `removeTool(name)` | `(name: string): boolean` | 删除工具;若是默认内置工具则返回 `false` |
445
- | `hasTool(name)` | `(name: string): boolean` | 检查工具是否已注册 |
446
- | `getToolNames()` | `(): string[]` | 获取当前已注册的工具名列表 |
447
- | `clearCustomTools()` | `(): string[]` | 删除全部自定义工具并返回被删除工具名;默认内置工具保留 |
448
- | `getTools()` | `(): ToolDefinition[]` | 获取当前已注册的工具定义列表 |
380
+ 已注册 Prompt 作为扩展段追加到内置系统提示词之后。内置 Prompt 包含:快照优先决策、REMAINING 协议、批量执行规则、Effect check、事件信号优先级、禁止 page_info 空转等核心约束。
449
381
 
450
- > 默认内置工具保护:通过 `registerTools()` 注册的 `dom/navigate/page_info/wait/evaluate` 不允许删除。
382
+ ---
383
+
384
+ ## 自定义工具
451
385
 
452
386
  ```ts
453
- // 注册所有内置工具
454
- agent.registerTools();
387
+ import { Type } from "@sinclair/typebox";
455
388
 
456
- // 注册自定义业务工具
457
389
  agent.registerTool({
458
390
  name: "create_ticket",
459
391
  description: "Create a new ticket in the system",
460
392
  schema: Type.Object({
461
- title: Type.String(),
393
+ title: Type.String({ description: "Ticket title" }),
462
394
  priority: Type.String({ enum: ["high", "medium", "low"] }),
463
395
  }),
464
396
  async execute(params) {
@@ -466,1413 +398,154 @@ agent.registerTool({
466
398
  return { content: `Ticket created: ${params.title}` };
467
399
  },
468
400
  });
469
-
470
- // 维护工具
471
- console.log(agent.hasTool("create_ticket"));
472
- console.log(agent.getToolNames());
473
- agent.removeTool("create_ticket");
474
- agent.clearCustomTools();
475
401
  ```
476
402
 
477
- ### 模型与执行配置
478
-
479
- | 方法 | 签名 | 说明 |
480
- | --- | --- | --- |
481
- | `setToken(token)` | `(token: string): void` | 设置 API Token |
482
- | `setClient(client)` | `(client: AIClient \| undefined): void` | 设置自定义客户端(传 undefined 恢复内置) |
483
- | `setProvider(provider)` | `(provider: string): void` | 切换 AI 服务商 |
484
- | `setModel(model)` | `(model: string): void` | 切换模型 |
485
- | `setStream(enabled)` | `(enabled: boolean): void` | 开关流式输出 |
486
- | `getStream()` | `(): boolean` | 获取流式状态 |
487
- | `setDryRun(enabled)` | `(enabled: boolean): void` | 开关干运行模式 |
488
- | `setSystemPrompt(prompt)` | `(prompt: string): void` | 以默认 key(`default`) 注册/覆盖一条 Prompt |
489
- | `setSystemPrompt(key, prompt)` | `(key: string, prompt: string): void` | 按 key 注册/覆盖一条 Prompt |
490
- | `setSystemPrompts(prompts)` | `(prompts: Record<string, string>): void` | 批量注册 Prompt |
491
- | `removeSystemPrompt(key)` | `(key: string): boolean` | 删除指定 key 的 Prompt |
492
- | `keepOnlySystemPrompt(key)` | `(key: string): boolean` | 仅保留指定 key 的 Prompt |
493
- | `getSystemPrompts()` | `(): Record<string, string>` | 获取全部已注册 Prompt(浅拷贝) |
494
- | `clearSystemPrompts()` | `(): void` | 清空全部已注册 Prompt |
495
- | `setMemory(enabled)` | `(enabled: boolean): void` | 开关多轮记忆(关闭时自动清空历史) |
496
- | `getMemory()` | `(): boolean` | 获取记忆状态 |
497
- | `clearHistory()` | `(): void` | 清空对话历史(不影响记忆开关) |
498
- | `setAutoSnapshot(enabled)` | `(enabled: boolean): void` | 开关自动快照 |
499
- | `getAutoSnapshot()` | `(): boolean` | 获取自动快照状态 |
500
- | `setSnapshotOptions(opts)` | `(opts: SnapshotOptions): void` | 设置快照参数 |
501
- | `getSnapshotOptions()` | `(): SnapshotOptions` | 获取当前快照参数(返回浅拷贝) |
502
-
503
- ### 执行入口
504
-
505
- | 方法 | 签名 | 说明 |
506
- | --- | --- | --- |
507
- | `chat(message)` | `(message: string): Promise<AgentLoopResult>` | 执行完整 Agent Loop,返回结果 |
508
-
509
- `chat()` 内部完整流程:
403
+ 工具管理:`registerTool()` / `removeTool()` / `hasTool()` / `getToolNames()` / `clearCustomTools()` / `getTools()`
510
404
 
511
- 1. 创建或复用 AI 客户端实例
512
- 2. 构建系统提示词(内置或自定义)
513
- 3. 创建本次会话 `RefStore`(hash ID → Element 映射)
514
- 4. 生成首轮页面快照并注入到 system prompt
515
- 5. 进入 `executeAgentLoop` 循环(消息构建 → AI 调用 → 工具执行 → 快照刷新)
516
- 6. 循环结束后返回 `AgentLoopResult`
517
- 7. 若开启 memory,将本轮消息追加至历史
518
- 8. 清理 RefStore 释放映射
405
+ 内置工具(`dom/navigate/page_info/wait/evaluate`)受保护,不允许删除。
519
406
 
520
407
  ---
521
408
 
522
- ## 回调与可观测性
523
-
524
- 可通过 `agent.callbacks` 订阅执行过程中的每一个关键事件:
525
-
526
- | 回调 | 签名 | 触发时机 | 用途 |
527
- | --- | --- | --- | --- |
528
- | `onRound(round)` | `(round: number) => void` | 每轮循环开始 | 展示当前轮次,round 从 0 开始 |
529
- | `onText(text)` | `(text: string) => void` | 模型返回文本输出 | 展示 AI 实时文本(流式/非流式) |
530
- | `onToolCall(name, input)` | `(name: string, input: unknown) => void` | 工具执行前 | 展示 AI 计划调用的工具和参数 |
531
- | `onToolResult(name, result)` | `(name: string, result: ToolCallResult) => void` | 工具执行完成后 | 展示执行结果(成功/失败/恢复) |
532
- | `onSnapshot(snapshot)` | `(snapshot: string) => void` | 自动快照生成后(仅首轮) | 调试快照质量和体积 |
533
- | `onBeforeRecoverySnapshot(newUrl?)` | `(newUrl?: string) => void` | 恢复快照生成前 | 重置路由态/RefStore 映射 |
534
- | `onMetrics(metrics)` | `(metrics: AgentLoopMetrics) => void` | chat() 完整结束后 | 记录 KPI 指标汇总 |
535
-
536
- ### 回调使用示例
409
+ ## 自定义 AI Client
537
410
 
538
411
  ```ts
539
- agent.callbacks = {
540
- onRound: (round) => {
541
- console.log(`── 第 ${round + 1} 轮开始 ──`);
542
- },
543
-
544
- onText: (text) => {
545
- // 流式展示 AI 输出
546
- chatPanel.appendText(text);
547
- },
548
-
549
- onToolCall: (name, input) => {
550
- // 展示"正在执行..."
551
- chatPanel.showToolLoading(name, input);
552
- },
553
-
554
- onToolResult: (name, result) => {
555
- // 展示执行结果
556
- const isError = result.details?.error;
557
- chatPanel.showToolResult(name, result.content, isError);
558
- },
412
+ import { BaseAIClient } from "agentpage";
559
413
 
560
- onSnapshot: (snapshot) => {
561
- // 调试:检查快照体积
562
- console.log(`首轮快照体积: ${snapshot.length} 字符`);
563
- },
564
-
565
- onBeforeRecoverySnapshot: (newUrl) => {
566
- if (newUrl) {
567
- console.log(`页面导航到: ${newUrl},正在重建快照映射...`);
568
- } else {
569
- console.log("元素定位失败,正在刷新快照...");
570
- }
571
- },
572
-
573
- onMetrics: (metrics) => {
574
- // 上报到监控系统
575
- analytics.track("agent_chat_complete", {
576
- rounds: metrics.roundCount,
577
- toolCalls: metrics.totalToolCalls,
578
- successRate: metrics.toolSuccessRate,
579
- recoveries: metrics.recoveryCount,
580
- tokens: metrics.inputTokens + metrics.outputTokens,
581
- });
582
- },
583
- };
414
+ const agent = new WebAgent({
415
+ client: new BaseAIClient({
416
+ chatHandler: async ({ url, method, headers, body }) => {
417
+ const res = await fetch("/api/ai-proxy", { method: "POST", body });
418
+ return res;
419
+ },
420
+ }),
421
+ });
584
422
  ```
585
423
 
586
- ### `onMetrics` 输出字段详解
587
-
588
- | 字段 | 类型 | 说明 |
589
- | --- | --- | --- |
590
- | `roundCount` | `number` | 本次 chat 实际执行的轮次数 |
591
- | `totalToolCalls` | `number` | 工具调用总次数(包含被拦截的) |
592
- | `successfulToolCalls` | `number` | 成功执行的工具调用数 |
593
- | `failedToolCalls` | `number` | 失败的工具调用数(包含恢复中的) |
594
- | `toolSuccessRate` | `number` | 工具成功率(0-1,保留 4 位小数) |
595
- | `recoveryCount` | `number` | 元素未找到自动恢复触发次数 |
596
- | `redundantInterceptCount` | `number` | 冗余 page_info 拦截次数(越高说明 prompt 效果越差) |
597
- | `snapshotReadCount` | `number` | 快照读取总次数(含首轮 + 恢复 + 每轮刷新) |
598
- | `latestSnapshotSize` | `number` | 最后一次快照的字符长度 |
599
- | `avgSnapshotSize` | `number` | 平均快照字符长度 |
600
- | `maxSnapshotSize` | `number` | 最大快照字符长度 |
601
- | `inputTokens` | `number` | 累计输入 token 数(所有轮次求和) |
602
- | `outputTokens` | `number` | 累计输出 token 数(所有轮次求和) |
603
-
604
- ### 健康指标参考
605
-
606
- | 指标 | 健康范围 | 警告范围 | 异常范围 | 含义 |
607
- | --- | --- | --- | --- | --- |
608
- | `toolSuccessRate` | > 0.85 | 0.6-0.85 | < 0.6 | 工具执行成功率 |
609
- | `recoveryCount` | 0-2 | 3-5 | > 5 | 元素定位失败恢复次数 |
610
- | `roundCount` | 1-10 | 10-25 | > 25 | 单次任务消耗轮次 |
611
- | `avgSnapshotSize` | 1k-5k | 5k-10k | > 10k | 快照体积(字符数) |
612
- | `redundantInterceptCount` | 0 | 1-3 | > 3 | 无效 page_info 调用被拦截次数 |
613
-
614
- ---
615
-
616
- ## 返回结构(AgentLoopResult)
617
-
618
- `chat()` 返回一个完整的执行结果对象:
619
-
620
- | 字段 | 类型 | 说明 |
621
- | --- | --- | --- |
622
- | `reply` | `string` | AI 最终文本回复(任务完成总结或错误说明) |
623
- | `toolCalls` | `Array<{ name, input, result }>` | 本次所有工具调用的完整轨迹(按执行顺序) |
624
- | `messages` | `AIMessage[]` | 完整对话消息数组(可用于 memory 持续或外部存储) |
625
- | `metrics` | `AgentLoopMetrics` | 本次执行的量化指标 |
626
-
627
- ### 各字段使用示例
424
+ 也可以实现 `AIClient` 接口直接传入:
628
425
 
629
426
  ```ts
630
- const result = await agent.chat("填写用户名为 admin 并提交登录");
631
-
632
- // 1. 展示最终回复
633
- console.log(result.reply);
634
- // 输出类似:"已填写用户名 admin 并点击登录按钮。"
635
-
636
- // 2. 审查工具调用轨迹
637
- for (const tc of result.toolCalls) {
638
- console.log(`${tc.name}(${JSON.stringify(tc.input)}) → ${tc.result.content}`);
639
- }
640
- // 输出类似:
641
- // dom({"action":"fill","selector":"#a1b2c","value":"admin"}) → ✓ 已填写
642
- // dom({"action":"click","selector":"#d3e4f"}) → ✓ 已点击
643
-
644
- // 3. 检查执行指标
645
- console.log(`总轮次: ${result.metrics.roundCount}`);
646
- console.log(`工具调用: ${result.metrics.totalToolCalls}`);
647
- console.log(`成功率: ${result.metrics.toolSuccessRate}`);
648
- console.log(`Token 消耗: ${result.metrics.inputTokens + result.metrics.outputTokens}`);
649
-
650
- // 4. 存储消息用于后续分析
651
- localStorage.setItem("last_chat_messages", JSON.stringify(result.messages));
427
+ type AIClient = {
428
+ chat(params: {
429
+ systemPrompt: string;
430
+ messages: AIMessage[];
431
+ tools?: ToolDefinition[];
432
+ }): Promise<{ text?: string; toolCalls?: AIToolCall[]; usage?: { inputTokens: number; outputTokens: number } }>;
433
+ };
652
434
  ```
653
435
 
654
- ### ToolCallResult 结构
436
+ ---
655
437
 
656
- 每个工具调用返回标准化结构:
438
+ ## 回调与可观测性
657
439
 
658
440
  ```ts
659
- type ToolCallResult = {
660
- content: string | Record<string, unknown>; // 返回内容(发给 AI)
661
- details?: Record<string, unknown>; // 额外细节(日志/调试用)
441
+ agent.callbacks = {
442
+ onRound: (round) => console.log(`── Round ${round + 1} ──`),
443
+ onText: (text) => ui.appendText(text),
444
+ onToolCall: (name, input) => ui.showLoading(name),
445
+ onToolResult: (name, result) => ui.showResult(name, result.content),
446
+ onSnapshot: (snapshot) => console.log(`快照: ${snapshot.length} chars`),
447
+ onMetrics: (metrics) => analytics.track("chat_complete", metrics),
662
448
  };
663
449
  ```
664
450
 
665
- `details` 中的关键字段:
451
+ ### AgentLoopMetrics
666
452
 
667
- | 字段 | 出现条件 | 含义 |
668
- | --- | --- | --- |
669
- | `error: true` | 工具执行失败时 | 标记这是一个错误结果 |
670
- | `code` | 有特定错误类型时 | 错误码(见下方错误码表) |
671
- | `action` | DOM/导航等工具 | 执行的具体动作 |
672
- | `selector` | DOM 工具 | 目标元素选择器 |
673
- | `recoveryAttempt` | 恢复机制触发时 | 当前恢复次数 |
453
+ | 字段 | 说明 |
454
+ | --- | --- |
455
+ | `roundCount` | 实际执行轮次 |
456
+ | `totalToolCalls` / `successfulToolCalls` / `failedToolCalls` | 工具调用计数 |
457
+ | `toolSuccessRate` | 成功率(0-1) |
458
+ | `recoveryCount` | 元素恢复触发次数 |
459
+ | `redundantInterceptCount` | 冗余拦截次数 |
460
+ | `snapshotReadCount` / `latestSnapshotSize` / `avgSnapshotSize` / `maxSnapshotSize` | 快照统计 |
461
+ | `inputTokens` / `outputTokens` | Token 消耗 |
674
462
 
675
- ### 错误码速查
463
+ ### AgentLoopResult
676
464
 
677
- | 错误码 | 触发来源 | 含义 |
465
+ | 字段 | 类型 | 说明 |
678
466
  | --- | --- | --- |
679
- | `ELEMENT_NOT_FOUND` | dom-tool | hash ID / CSS 选择器未命中元素 |
680
- | `ELEMENT_NOT_VISIBLE` | dom-tool | 元素存在但不可见 |
681
- | `ELEMENT_DISABLED` | dom-tool | 元素被禁用(disabled / aria-disabled) |
682
- | `ELEMENT_DETACHED` | dom-tool | 元素已脱离 DOM 树 |
683
- | `UNSUPPORTED_FILL_TARGET` | dom-tool | 目标不可编辑(如 div 或 checkbox) |
684
- | `ELEMENT_NOT_FOUND_RECOVERY` | recovery | 恢复中:已刷新快照,等待重试 |
685
- | `ELEMENT_NOT_FOUND_MAX_RECOVERY_REACHED` | recovery | 恢复次数用尽 |
686
- | `REDUNDANT_PAGE_INFO_SKIPPED` | recovery | page_info 调用被拦截为冗余 |
687
- | `REDUNDANT_SNAPSHOT` | recovery | 连续重复快照被标记 |
467
+ | `reply` | `string` | AI 最终回复 |
468
+ | `toolCalls` | `Array<{ name, input, result }>` | 完整工具调用轨迹 |
469
+ | `messages` | `AIMessage[]` | 完整对话消息(可用于 memory) |
470
+ | `metrics` | `AgentLoopMetrics` | 运行指标 |
688
471
 
689
472
  ---
690
473
 
691
- ## 内置工具详细参考
474
+ ## Agent Loop 执行流程
692
475
 
693
- AutoPilot 内置 5 个工具,覆盖浏览器交互的核心能力。所有工具通过 `ToolRegistry` 统一注册和分发。
694
-
695
- ### `dom`(DOM 交互工具)
696
-
697
- 最核心的工具,负责所有页面元素交互操作。
698
-
699
- **参数说明:**
700
-
701
- | 参数 | 类型 | 必需 | 说明 |
702
- | --- | --- | --- | --- |
703
- | `action` | `string` | ✅ | 操作类型(见下方动作表) |
704
- | `selector` | `string` | ✅ | 元素选择器(hash ID 如 `#a1b2c` 或 CSS 选择器) |
705
- | `value` | `string` | 部分动作 | 填写值(fill/type/set_attr/add_class/remove_class) |
706
- | `key` | `string` | press | 按键名称(如 `Enter`、`Control+a`) |
707
- | `label` | `string` | select_option | 选项显示文本 |
708
- | `index` | `number` | select_option | 选项索引 |
709
- | `attribute` | `string` | get_attr/set_attr | 属性名称 |
710
- | `className` | `string` | add_class/remove_class | CSS 类名(旧参数名,已被 `value` 兼容) |
711
- | `clickCount` | `number` | click | 点击次数(默认 1,双击传 2,三击传 3) |
712
- | `waitMs` | `number` | 所有动作 | 等待元素出现的超时时间(毫秒,默认 1200) |
713
- | `waitSeconds` | `number` | 所有动作 | 等待超时(秒,`waitMs` 优先级更高) |
714
- | `force` | `boolean` | 所有动作 | 跳过 actionability 检查(默认 false) |
715
-
716
- **动作详解:**
717
-
718
- | 动作 | 说明 | 示例参数 |
719
- | --- | --- | --- |
720
- | `click` | 点击元素(完整事件链) | `{ action: "click", selector: "#btn" }` |
721
- | `fill` | 填写输入框(清空后写入) | `{ action: "fill", selector: "#name", value: "张三" }` |
722
- | `type` | 逐字输入(不清空已有内容) | `{ action: "type", selector: "#search", value: "keyword" }` |
723
- | `press` | 按键(支持组合键) | `{ action: "press", selector: "#input", key: "Enter" }` |
724
- | `select_option` | 选择下拉选项 | `{ action: "select_option", selector: "#sel", value: "opt1" }` |
725
- | `check` | 勾选 checkbox | `{ action: "check", selector: "#agree" }` |
726
- | `uncheck` | 取消勾选 | `{ action: "uncheck", selector: "#agree" }` |
727
- | `clear` | 清空输入框 | `{ action: "clear", selector: "#name" }` |
728
- | `focus` | 聚焦元素 | `{ action: "focus", selector: "#input" }` |
729
- | `hover` | 鼠标悬停 | `{ action: "hover", selector: "#menu" }` |
730
- | `get_text` | 获取元素文本 | `{ action: "get_text", selector: "#title" }` |
731
- | `get_attr` | 获取属性值 | `{ action: "get_attr", selector: "#el", attribute: "href" }` |
732
- | `set_attr` | 设置属性值 | `{ action: "set_attr", selector: "#el", attribute: "data-id", value: "1" }` |
733
- | `add_class` | 添加 CSS 类 | `{ action: "add_class", selector: "#el", value: "active" }` |
734
- | `remove_class` | 移除 CSS 类 | `{ action: "remove_class", selector: "#el", value: "active" }` |
735
-
736
- **关键行为机制(Playwright 风格):**
737
-
738
- 1. **Actionability 检查**(默认开启,`force=true` 可跳过):
739
- - 可见性:元素 `display` 不为 `none`、`visibility` 为 `visible`、`opacity` 不为 `0`、宽高大于 0
740
- - 稳定性:通过 `requestAnimationFrame` 连续 3 帧检测元素位置不变
741
- - 可用性:检查 `disabled` 属性和祖先链 `aria-disabled`
742
- - 可编辑性:`fill`/`type`/`clear` 需要目标是可编辑元素
743
- - 遮挡检测:`elementFromPoint` 检查元素中心是否被其他元素覆盖
744
-
745
- 2. **点击事件链**(完整 Pointer/Mouse 序列):
746
- ```
747
- pointermove → mousemove → pointerdown → mousedown → focus → pointerup → mouseup → click
748
- ```
749
- - 每个事件都带有正确的坐标(元素中心点)和 button/buttons 属性
750
- - 支持多次点击(clickCount=2 双击、clickCount=3 三击)
751
-
752
- 3. **智能重定向(retarget)**:
753
- - `click`:非交互元素自动查找最近的 `button/[role=button]/a/[role=link]`
754
- - `follow-label`:`<label>` 元素自动重定向到 `label.control`
755
- - 隐藏控件代理:Element Plus/AntD 等组件库的隐藏 checkbox/radio/switch,自动重定向到可见的代理元素(`.el-switch`、`.el-checkbox` 等)
756
- - 表单项 label 映射:点击 `.el-form-item__label` 自动定位到同一 form-item 内的实际控件
757
-
758
- 4. **fill 分类型处理**:
759
- - `date/color/range/time/month/week`:通过原生 `value` setter 直接写入
760
- - `text/email/password/search/url/tel/number`:`selectAll` + 原生写入 + 事件派发
761
- - `textarea`/`contenteditable`:同上
762
- - **禁止类型**:`checkbox/radio/file/button/submit/reset/image`
763
-
764
- 5. **select_option 三策略**:
765
- - 优先 `value` 匹配 → 其次 `label` 文本匹配 → 最后 `index` 索引
766
- - 原生 `<select>` 和自定义下拉组件(Element Plus/AntD)都有增强处理
767
- - 返回明确的 `selected value + label`
768
-
769
- 6. **press 组合键**:
770
- - 支持 `Control+a`、`Shift+Enter`、`Meta+c` 等
771
- - 修饰键通过 `keydown → keyup` 事件链实现
772
-
773
- 7. **scrollIntoView 多策略轮换**:
774
- - 策略 0:`scrollIntoViewIfNeeded(true)`(Chrome 私有 API)
775
- - 策略 1:`scrollIntoView({ block: "end", inline: "end" })`
776
- - 策略 2:`scrollIntoView({ block: "center", inline: "center" })`
777
- - 策略 3:`scrollIntoView({ block: "start", inline: "start" })`
778
- - 当 hit-target 检测到遮挡时,自动轮换策略重试
779
-
780
- 8. **get_attr 运行态增强**:
781
- - `checked`:读取 DOM property 而非 HTML attribute(适配动态状态)
782
- - `selected`:读取 `<option>` 的 `selected` property
783
- - `disabled`/`readonly`:读取 DOM property
784
- - `value`:读取当前输入值(property)而非初始 HTML attribute
785
-
786
- ### `navigate`(页面导航工具)
787
-
788
- **参数说明:**
789
-
790
- | 参数 | 类型 | 必需 | 说明 |
791
- | --- | --- | --- | --- |
792
- | `action` | `string` | ✅ | 导航动作类型 |
793
- | `url` | `string` | goto | 目标 URL(`goto` 动作必需) |
794
- | `selector` | `string` | scroll | 滚动目标(hash ID 或 CSS 选择器) |
795
- | `x` | `number` | scroll | 水平滚动位置(像素) |
796
- | `y` | `number` | scroll | 垂直滚动位置(像素) |
797
-
798
- **动作详解:**
799
-
800
- | 动作 | 说明 | 行为 |
801
- | --- | --- | --- |
802
- | `goto` | 跳转到指定 URL | `window.location.href = url`,触发导航后自动刷新快照 |
803
- | `back` | 浏览器后退 | `window.history.back()`,触发后自动刷新快照 |
804
- | `forward` | 浏览器前进 | `window.history.forward()`,触发后自动刷新快照 |
805
- | `reload` | 刷新页面 | `window.location.reload()`,触发后自动刷新快照 |
806
- | `scroll` | 滚动到指定位置/元素 | 优先 `scrollIntoViewIfNeeded`,回退 `scrollIntoView(center)` |
807
-
808
- **注意事项:**
809
- - `goto/back/forward/reload` 执行后会触发**强制断轮**,等待下一轮新快照
810
- - `scroll.selector` 支持 RefStore hash ID 和 CSS 选择器
811
-
812
- ### `wait`(等待工具)
813
-
814
- **参数说明:**
815
-
816
- | 参数 | 类型 | 必需 | 说明 |
817
- | --- | --- | --- | --- |
818
- | `action` | `string` | ✅ | 等待动作类型 |
819
- | `selector` | `string` | wait_for_selector/hidden | 目标元素选择器 |
820
- | `state` | `string` | wait_for_selector | 目标状态:`attached`/`visible`/`hidden`/`detached` |
821
- | `text` | `string` | wait_for_text | 要等待出现的文本内容 |
822
- | `timeout` | `number` | 所有动作 | 超时时间(毫秒,默认 6000) |
823
- | `quietMs` | `number` | wait_for_stable | DOM 静默窗口时长(毫秒,默认 300) |
824
-
825
- **动作详解:**
826
-
827
- | 动作 | 说明 | 实现策略 |
828
- | --- | --- | --- |
829
- | `wait_for_selector` | 等待选择器达到指定状态 | 轮询(80ms) + MutationObserver 双通道 |
830
- | `wait_for_hidden` | 等待元素隐藏或移除 | 等同 `wait_for_selector(state=hidden)` |
831
- | `wait_for_text` | 等待页面出现指定文本 | 轮询(80ms) + MutationObserver(characterData) |
832
- | `wait_for_stable` | 等待 DOM 进入静默窗口 | MutationObserver 监听变化,无变化持续 quietMs 则完成 |
833
-
834
- **状态语义(wait_for_selector):**
835
-
836
- | state | 含义 | 完成条件 |
837
- | --- | --- | --- |
838
- | `attached`(默认) | 元素存在于 DOM 树 | `querySelector` 命中 |
839
- | `visible` | 元素可见 | 存在且 `display/visibility/opacity/size` 均正常 |
840
- | `hidden` | 元素不可见或不存在 | 不存在或存在但不可见 |
841
- | `detached` | 元素从 DOM 树移除 | `querySelector` 未命中 |
842
-
843
- **双通道检测策略:**
844
- - **轮询通道**:每 80ms 主动检查一次目标状态
845
- - **MutationObserver 通道**:监听 DOM 变化事件,变化发生时立即检查
846
- - 双通道确保"快速响应变化 + 不遗漏非 DOM 触发的状态变化"
847
-
848
- ### `page_info`(页面信息工具)
849
-
850
- **参数说明:**
851
-
852
- | 参数 | 类型 | 必需 | 说明 |
853
- | --- | --- | --- | --- |
854
- | `action` | `string` | ✅ | 信息获取动作类型 |
855
- | `selector` | `string` | query_all | CSS 选择器(用于 `query_all` 动作) |
856
- | `maxDepth` | `number` | snapshot | 最大遍历深度(默认 12) |
857
- | `viewportOnly` | `boolean` | snapshot | 是否仅保留视口内元素(默认 true) |
858
- | `pruneLayout` | `boolean` | snapshot | 是否折叠布局容器(默认 true) |
859
- | `maxNodes` | `number` | snapshot | 最大节点数(默认 220) |
860
- | `maxChildren` | `number` | snapshot | 每个父节点最大子节点数(默认 25) |
861
- | `maxTextLength` | `number` | snapshot | 文本截断长度(默认 40) |
862
-
863
- **动作详解:**
864
-
865
- | 动作 | 返回内容 |
866
- | --- | --- |
867
- | `get_url` | 当前页面 URL 字符串 |
868
- | `get_title` | 当前页面标题 |
869
- | `get_selection` | 用户当前选中的文本 |
870
- | `get_viewport` | 视口宽高和滚动位置(JSON) |
871
- | `snapshot` | DOM 结构快照(AI 可读文本格式) |
872
- | `query_all` | 匹配选择器的所有元素摘要 |
873
-
874
- **重要:** 在 Agent Loop 内,`page_info` 的大多数动作会被 recovery 模块拦截为冗余(因为框架每轮已自动提供最新快照)。AI 不需要也不应该主动调用这些动作。
875
-
876
- ### `evaluate`(JavaScript 执行工具)
877
-
878
- **参数说明:**
879
-
880
- | 参数 | 类型 | 必需 | 说明 |
881
- | --- | --- | --- | --- |
882
- | `expression` | `string` | ✅ | JavaScript 表达式或语句块 |
883
-
884
- **行为细节:**
885
-
886
- - 使用 `new Function("use strict"; ...)` 执行(不污染当前作用域)
887
- - 优先作为表达式求值(`return (expression)`)
888
- - 若表达式失败,回退为语句块执行(`expression`)
889
- - 结果序列化:
890
- - DOM 元素 → `<tag#id> "textContent"`
891
- - NodeList/HTMLCollection → 逐个序列化
892
- - 普通值 → `JSON.stringify`
893
- - 错误 → 返回错误信息
894
-
895
- **使用建议:**
896
-
897
- ```ts
898
- // ✅ 适合:其他工具无法覆盖的场景
899
- { expression: "document.querySelector('.modal').style.display = 'none'" }
900
- { expression: "window.scrollTo(0, document.body.scrollHeight)" }
901
-
902
- // ❌ 避免:有专用工具的场景
903
- // 用 dom.click 代替手动派发 click 事件
904
- // 用 navigate.goto 代替 window.location.href = ...
905
476
  ```
906
-
907
- **安全注意:** `evaluate` 是最强大也是最危险的工具。建议在 Prompt 中明确约束使用条件。
908
-
909
- ---
910
-
911
- ## 设计思想(Design Philosophy)
912
-
913
- AutoPilot 的设计不是“做一个会聊天的前端插件”,而是建立一个可执行、可约束、可演进的前端 Agent 运行时。
914
-
915
- ### 1) 快照优先,不猜页面
916
-
917
- - 每轮只基于最新快照做决策,避免“记忆幻觉”。
918
- - 快照本身是事实边界,工具调用是行为边界。
919
-
920
- ### 2) 渐进式任务消费,不一次性硬做完
921
-
922
- - 通过 `REMAINING` 协议持续收敛任务。
923
- - 每轮完成一批当前可执行动作,逐轮推进直至 `DONE`。
924
-
925
- ### 3) Prompt + Tools + 路由 三层解耦
926
-
927
- - Prompt 决定策略
928
- - Tools 决定动作
929
- - 路由决定上下文
930
-
931
- 这三层解耦让企业系统能够低风险增量接入,而不是一次性重构。
932
-
933
- ### 4) 保护机制优先于“盲目追求一步到位”
934
-
935
- - 冗余拦截、找不到元素恢复、重试对话流、空转终止、防自转停机。
936
- - 目标是稳定收敛,而不是偶然成功。
937
-
938
- ### 5) 对后端 Agent 互补,而非替代
939
-
940
- - 后端 Agent 擅长跨系统编排。
941
- - 前端 AutoPilot 擅长页面级执行。
942
- - 两者组合形成完整企业 AI 执行闭环。
943
-
944
- ---
945
-
946
- ## 当前目录结构(权威)
947
-
948
- ```text
949
- src/
950
- ├── core/
951
- │ ├── index.ts
952
- │ ├── types.ts
953
- │ ├── tool-params.ts
954
- │ ├── tool-registry.ts
955
- │ ├── system-prompt.ts
956
- │ ├── agent-loop/
957
- │ │ ├── index.ts
958
- │ │ ├── types.ts
959
- │ │ ├── constants.ts
960
- │ │ ├── helpers.ts
961
- │ │ ├── snapshot.ts
962
- │ │ ├── messages.ts
963
- │ │ ├── recovery.ts
964
- │ │ └── LOOP_MECHANISM.md
965
- │ └── ai-client/
966
- │ ├── index.ts
967
- │ ├── constants.ts
968
- │ ├── custom.ts
969
- │ ├── openai.ts
970
- │ ├── anthropic.ts
971
- │ ├── deepseek.ts
972
- │ ├── doubao.ts
973
- │ ├── qwen.ts
974
- │ └── sse.ts
975
- └── web/
976
- ├── index.ts
977
- ├── dom-tool.ts # 兼容转发层(re-export)
978
- ├── navigate-tool.ts # 兼容转发层(re-export)
979
- ├── page-info-tool.ts # 兼容转发层(re-export)
980
- ├── wait-tool.ts # 兼容转发层(re-export)
981
- ├── evaluate-tool.ts # 兼容转发层(re-export)
982
- ├── event-listener-tracker.ts # 全局事件监听追踪器
983
- ├── ref-store.ts
984
- ├── messaging.ts
985
- └── tools/
986
- ├── dom-tool.ts
987
- ├── navigate-tool.ts
988
- ├── page-info-tool.ts
989
- ├── wait-tool.ts
990
- └── evaluate-tool.ts
477
+ chat(task) 触发
478
+
479
+ ├─ 创建 RefStore,生成首轮快照 S₀
480
+
481
+ └─ 进入 executeAgentLoop 循环
482
+
483
+ ├─ 1. Ensure Snapshot — 确保有最新快照
484
+ ├─ 2. Build Messages — remaining + 上轮轨迹 + 效果检查 + 快照
485
+ ├─ 3. Call AI — 拿到 text + toolCalls + REMAINING
486
+ ├─ 4. Execute Tools — 逐个分发,应用保护机制
487
+ ├─ 5. Reduce Remaining — 推进任务(协议优先,启发式回退)
488
+ ├─ 6. Guard — 防空转/防自转/协议修复判定
489
+ └─ 7. Refresh Snapshot → 下一轮
490
+
491
+ └─ 停机 → 返回 AgentLoopResult
991
492
  ```
992
493
 
993
494
  ---
994
495
 
995
- ## 核心原理
996
-
997
- ### 1) 快照驱动决策
998
-
999
- AI 每一轮不是“凭记忆猜页面”,而是基于最新快照选择可执行动作。
1000
-
1001
- 可把本轮决策写成一个最小公式:
1002
-
1003
- - 输入:`当前快照 S` + `当前任务描述 R`
1004
- - 输出:`当前可执行任务批次 T`(仅包含快照里可见且可操作的目标)
1005
-
1006
- 也就是:`S + R -> T`
496
+ ## 目录结构
1007
497
 
1008
- 快照包含:
1009
- - 元素标签与关键信息
1010
- - hash selector(如 `#a1b2c`)
1011
- - 结构化层级关系
1012
- - 布局折叠关联标记:当剪枝把同一容器链路中的多个子节点提升到同层时,会用括号分组(`collapsed-group`)标记它们的来源关联
1013
-
1014
- ### 2) 任务增量消费
1015
-
1016
- 用户任务会被分解成子任务,按轮次逐步“吃掉”:
1017
-
1018
- - 轮次 N:在快照 `S_n` 上执行当前可做任务 `T_n`
1019
- - 执行后默认先视为成功,并从当前任务 `R_n` 中剔除 `T_n`
1020
- - 得到下一轮任务 `R_(n+1)`,再配合新快照进入下一轮
1021
- - 全部剔除完成后结束
1022
-
1023
- 任务推进可写成:
1024
-
1025
- - 输入:`当前任务 R_n` + `本轮执行 T_n` + `已执行历史 H_n`
1026
- - 输出:`下一轮任务 R_(n+1)`
1027
-
1028
- 也就是:`R_n + T_n + H_n -> R_(n+1)`
1029
-
1030
- 其中 `R_(n+1)` 就是下一次发给模型的核心输入之一。
1031
-
1032
- 新增(渐进式协议):
1033
- - 每轮都会显式携带 `Current remaining instruction`(当前剩余文本)
1034
- - 每轮都会携带 `Previous round planned task array`(上一轮执行计划)
1035
- - 模型可在文本中返回:
1036
- - `REMAINING: <剩余内容>`:表示还有任务要继续
1037
- - `REMAINING: DONE`:表示剩余任务已空
1038
- - 注意:模型在 `tool_calls` 轮可能返回空 `content`;这不代表任务结束。
1039
-
1040
- ### 3) 批量但不跨变更链式执行
1041
-
1042
- 允许同轮批量执行多个“当前可见目标”的动作;
1043
- 不允许把“会导致新 DOM 出现”的后续动作强行塞进同轮。
1044
-
1045
- 例子:
1046
- - 可同轮:同时填写两个已可见输入框
1047
- - 不可同轮:点击“打开弹窗”后立即填写弹窗字段(应等下一轮新快照)
1048
- - 当前实现:若本轮出现潜在 DOM 变化动作,轮次结束会自动执行双重等待(先 loading hidden,再 DOM quiet window),默认 `quietMs=200`、`timeoutMs=4000`。
1049
- - `loadingSelectors` 默认内置 AntD / Element Plus / BK / TDesign(TD)及通用加载态选择器;用户自定义会在默认列表基础上追加并去重,不会覆盖默认值。
1050
-
1051
- ---
1052
-
1053
- ## 完整对话流程(执行版)
1054
-
1055
- > 目标:每轮都基于“当前快照 + 当前任务”推进,避免 `page_info` 空转。
1056
-
1057
- ### 0) chat 触发(前端)
1058
-
1059
- 当调用 `WebAgent.chat()` 时,前端会先做一件事:
1060
-
1061
- 1. 立即生成首轮快照 `S0`(浏览器端自动完成,不依赖 AI 请求)
1062
- 2. 将 `S0` 注入到 system prompt
1063
- 3. 将 `S0` 作为 `initialSnapshot` 传入 loop
1064
-
1065
- 这保证了首轮就是“有快照可执行”的状态。
1066
-
1067
- ### 1) 每轮输入(给模型)
1068
-
1069
- 每轮构建消息时,核心输入固定为:
1070
-
1071
- - `Current remaining instruction`(当前剩余任务)
1072
- - `Previous round planned task array`(上一轮已执行任务)
1073
- - `Previous round model output (normalized)`(上一轮模型输出归一化摘要)
1074
- - `Latest DOM snapshot`(当前快照)
1075
-
1076
- 说明:
1077
- - Round 0 会携带原始任务文本作为起点;
1078
- - Round 1+ 不再重复注入原始 userMessage,避免模型“回头重做”。
1079
-
1080
- ### 2) 每轮输出(模型返回)
1081
-
1082
- 模型应返回:
1083
-
1084
- - 本轮工具调用批次(能批量就批量)
1085
- - 一行剩余任务协议:
1086
- - `REMAINING: <new remaining instruction>`
1087
- - 或 `REMAINING: DONE`
1088
-
1089
- 实现细节:
1090
- - 若该轮返回 `tool_calls` 且 `content` 为空,loop 仍以“工具执行结果”推进状态,不把空文本当完成信号。
1091
-
1092
- ### 3) 每轮执行与状态推进
1093
-
1094
- loop 对本轮返回做以下处理:
1095
-
1096
- 1. 执行工具调用批次
1097
- 2. 拦截 `page_info.*`(在 loop 内视为冗余,不让其成为主流程)
1098
- 3. 处理恢复(元素找不到时自动刷新快照)
1099
- 4. 若本轮存在潜在 DOM 变化动作:执行轮次后稳定等待(loading hidden + DOM stable)
1100
- 5. 刷新快照进入下一轮
1101
- 6. 更新下一轮任务文本:
1102
- - 优先使用 `REMAINING`
1103
- - 若缺失 `REMAINING` 且本轮有执行动作:按线性任务剔除做启发式推进(避免整段原任务重复)
1104
- - 若缺失 `REMAINING` 且本轮无执行进展:保持当前任务不推进(按协议回退)
1105
- 7. 若“remaining 未完成 + 无工具调用”:
1106
- - 不直接结束
1107
- - 下一轮注入 `Protocol violation` 强约束提示,要求“要么给可执行工具调用,要么严格 `REMAINING: DONE`”
1108
-
1109
- ### 3.1) 找不到元素重试流(Not-found Retry Dialogue)
1110
-
1111
- 当执行工具返回“元素未找到”时,不会直接空转看页面,而是进入重试对话流:
1112
-
1113
- 1. 收集失败工具调用(name/input)及失败原因
1114
- 2. 将“失败工具集合 + 最新快照 + 当前任务”一起发给模型重试
1115
- 3. 在消息中标注重试次数:`attempt x/y`
1116
- 4. 若仍未命中,默认 `await 1000ms` 后刷新快照再重试
1117
- 5. 超过最大尝试次数后退出重试流,交由模型给出剩余任务或结束
1118
-
1119
- 默认参数:
1120
- - `DEFAULT_NOT_FOUND_RETRY_ROUNDS = 2`
1121
- - `DEFAULT_NOT_FOUND_RETRY_WAIT_MS = 1000`
1122
-
1123
- ### 4) 停机条件
1124
-
1125
- - 无工具调用且 remaining 已完成(或明确 `REMAINING: DONE`)
1126
- - `REMAINING: DONE` 后自然收敛
1127
- - 重复批次防自转触发
1128
- - 达到 `maxRounds`
1129
-
1130
- ### 5) 线性任务剔除示例(标准范式)
1131
-
1132
- 总任务:`点击按钮 -> 输入框输入 "abc" -> 发送`
1133
-
1134
- - 第 1 轮
1135
- - 当前任务:`点击按钮 -> 输入框输入 "abc" -> 发送`
1136
- - 上一轮已执行:空
1137
- - 本轮执行:`点击按钮`
1138
- - 下一轮任务:`输入框输入 "abc" -> 发送`
1139
-
1140
- - 第 2 轮
1141
- - 当前任务:`输入框输入 "abc" -> 发送`
1142
- - 上一轮已执行:`点击按钮`
1143
- - 本轮执行:`输入框输入 "abc"`
1144
- - 下一轮任务:`发送`
1145
-
1146
- - 第 3 轮
1147
- - 当前任务:`发送`
1148
- - 上一轮已执行:`点击按钮 -> 输入框输入 "abc"`
1149
- - 本轮执行:`发送`
1150
- - 下一轮任务:`DONE`
1151
-
1152
- 核心思想:每轮默认“本轮执行成功”,从当前任务中剔除本轮执行项,得到下一轮任务。
1153
-
1154
- ---
1155
-
1156
- ## Prompt 设计架构(执行版)
1157
-
1158
- ### A. System Prompt(全局规则层)
1159
-
1160
- 由 `src/core/system-prompt.ts` 生成,职责是定义不可变执行约束:
1161
-
1162
- - 从当前快照直接执行,不复述任务
1163
- - 任务按“剔除模型”推进(current + previous + this-round -> new remaining)
1164
- - 禁止 `page_info` 作为规划手段
1165
- - 可见目标尽量同轮批量执行
1166
- - DOM 会变化的动作执行后在下一轮继续
1167
- - 统一输出 `REMAINING` 协议
1168
-
1169
- ### B. Round Messages(轮次状态层)
1170
-
1171
- 由 `src/core/agent-loop/messages.ts` 构建,职责是把运行时状态传给模型:
1172
-
1173
- - `Current remaining instruction`
1174
- - `Done steps (do NOT repeat)`
1175
- - `Previous round planned task array`
1176
- - `Effect verification` — 要求 AI 对比上轮操作与当前快照,确认每个操作是否生效;未生效时从同区域找其他目标而非重复
1177
- - `Previous round model output (normalized)`
1178
- - `Latest DOM snapshot`
1179
-
1180
- 这层是“每轮变化”的动态上下文。
1181
-
1182
- ### C. Loop Control(执行控制层)
1183
-
1184
- 由 `src/core/agent-loop/index.ts` 负责:
1185
-
1186
- - 首轮使用前端注入的 `initialSnapshot`
1187
- - 每轮执行后刷新快照
1188
- - 推进 `remainingInstruction`
1189
- - `REMAINING` 缺失且本轮有执行动作时:按线性任务剔除做启发式推进
1190
- - `REMAINING` 缺失且本轮无执行进展时:保持当前 remaining
1191
- - 防空转、防重复、防无限循环
1192
- - DOM 变更动作触发强制断轮(等待下一轮新快照)
1193
-
1194
- ### D. Recovery & Guard(保护层)
1195
-
1196
- 由 `src/core/agent-loop/recovery.ts` 提供:
1197
-
1198
- - 拦截冗余 `page_info` 调用
1199
- - 元素未命中自动恢复并刷新快照
1200
- - 导航后刷新快照
1201
- - 空转检测
1202
-
1203
- 由 `src/core/agent-loop/index.ts` 补充:
1204
- - not-found 重试对话流(失败工具聚合 + 尝试次数 + 等待重试)
1205
-
1206
- ---
1207
-
1208
- ## 完整架构流程图(含链路)
1209
-
1210
- ### A. 端到端主流程
1211
-
1212
- ```mermaid
1213
- flowchart TD
1214
- U[用户输入任务] --> W[WebAgent.chat]
1215
- W --> P[构建 system prompt + 准备工具定义]
1216
- P --> L[executeAgentLoop]
1217
- L --> S[读取/刷新 latest snapshot]
1218
- S --> M[buildCompactMessages]
1219
- M --> C[AIClient.chat]
1220
- C --> D{是否返回 toolCalls}
1221
- D -->|是| X[ToolRegistry.dispatch 执行工具]
1222
- X --> T[写入 trace 与结果]
1223
- T --> S
1224
- D -->|否| F[输出最终总结并结束]
1225
- ```
1226
-
1227
- ### B. 分层模块关系
1228
-
1229
- ```mermaid
1230
- flowchart LR
1231
- subgraph Web[web 层]
1232
- WI[web/index.ts\nWebAgent]
1233
- WT[web tools]
1234
- ELT[event-listener-tracker.ts]
1235
- RS[ref-store.ts]
1236
- MG[messaging.ts]
1237
- end
1238
-
1239
- subgraph Core[core 层]
1240
- AL[agent-loop/*]
1241
- TR[tool-registry.ts]
1242
- SP[system-prompt.ts]
1243
- AI[ai-client/*]
1244
- end
1245
-
1246
- WI --> AL
1247
- WI --> TR
1248
- WI --> SP
1249
- WI --> WT
1250
- WT --> RS
1251
- WT --> ELT
1252
- WI --> MG
1253
-
1254
- AL --> AI
1255
- AL --> TR
1256
498
  ```
1257
-
1258
- ### C. Agent Loop 轮次时序
1259
-
1260
- ```mermaid
1261
- sequenceDiagram
1262
- participant User as User
1263
- participant Agent as WebAgent
1264
- participant Loop as AgentLoop
1265
- participant AI as AIClient
1266
- participant Tool as ToolRegistry
1267
-
1268
- User->>Agent: chat(task)
1269
- Agent->>Loop: executeAgentLoop(...)
1270
- loop round 0..max
1271
- Loop->>Loop: read snapshot/context
1272
- Loop->>AI: compact messages + tools
1273
- AI-->>Loop: text/toolCalls
1274
- alt has toolCalls
1275
- Loop->>Tool: dispatch tool calls
1276
- Tool-->>Loop: results
1277
- Loop->>Loop: recovery + refresh snapshot
1278
- else final text
1279
- Loop-->>Agent: final reply
1280
- end
1281
- end
1282
- ```
1283
-
1284
- ---
1285
-
1286
- ## Agent Loop 细节
1287
-
1288
- 主流程位于 `src/core/agent-loop/index.ts`:
1289
-
1290
- 1. 确保当前快照可用
1291
- 2. 构建紧凑消息(remaining + 执行历史 + 上轮模型输出 + 最新快照)
1292
- 3. 调用 AI
1293
- 4. 执行工具调用并记录 trace
1294
- 5. 运行保护机制
1295
- 6. 刷新快照并进入下一轮
1296
-
1297
- ### 渐进式执行状态(新增)
1298
-
1299
- `src/core/agent-loop/index.ts` 内部维护 5 个关键状态:
1300
- - `remainingInstruction`:当前轮次待消费文本(初始值为用户原始输入)
1301
- - `previousRoundTasks`:上一轮执行任务数组
1302
- - `previousRoundPlannedTasks`:上一轮模型给出的计划批次(执行前)
1303
- - `previousRoundModelOutput`:上一轮模型输出归一化摘要(执行后供下轮输入)
1304
- - `lastPlannedBatchKey`:用于识别是否连续两轮给出完全相同的任务批次
1305
-
1306
- 停机规则:
1307
- - 若模型返回无工具调用且 remaining 未完成 → 不直接结束,进入协议修复轮
1308
- - 若模型返回无工具调用且 remaining 已完成(或 `REMAINING: DONE`)→ 结束
1309
- - 若连续两轮规划出相同任务批次,且上一轮无错误 → 自动终止,防止自转
1310
- - 若模型文本包含 `REMAINING: DONE`,通常下一轮会自然进入“无工具调用总结”并结束
1311
-
1312
- ### 紧凑消息结构
1313
-
1314
- 由 `messages.ts` 构建,核心语义:
1315
- - Round 0:用户原始任务 + 首轮快照
1316
- - Round 1+:剩余任务 + done steps + 上轮计划批次 + 上轮模型输出归一化 + 最新快照
1317
- - Done steps:已完成动作(避免重复)
1318
- - Execution context + latest snapshot:当前可执行范围
1319
-
1320
- ### 快照生命周期
1321
-
1322
- 由 `snapshot.ts` 管理:
1323
- - 读取快照
1324
- - 包裹快照边界
1325
- - 去重历史快照
1326
- - 剥离旧 prompt 快照
1327
-
1328
- ### 快照读取流程图
1329
-
1330
- ```mermaid
1331
- flowchart TD
1332
- A[chat 开始 / 新一轮开始] --> B{是否已有 latestSnapshot}
1333
- B -->|有| C[直接复用当前快照]
1334
- B -->|无| D[readPageSnapshot via page_info.snapshot]
1335
- D --> E[recordSnapshotStats 更新统计]
1336
- E --> F[wrapSnapshot 添加 SNAPSHOT_START/END]
1337
- C --> G[buildCompactMessages 注入 Latest DOM snapshot]
1338
- F --> G
1339
- G --> H[stripSnapshotFromPrompt 剥离 system prompt 旧快照]
1340
- H --> I[发送给模型并执行工具]
1341
- I --> J{是否触发恢复重拍}
1342
- J -->|导航成功/元素未找到恢复/每轮结束刷新| K[readPageSnapshot 刷新 latestSnapshot]
1343
- J -->|否| L[进入下一轮]
1344
- K --> E
1345
- L --> B
1346
- ```
1347
-
1348
- ---
1349
-
1350
- ## 保护机制
1351
-
1352
- 由 `recovery.ts` 提供:
1353
-
1354
- - 冗余 `page_info` 拦截:减少无意义工具调用
1355
- - 元素找不到恢复:自动等待并刷新快照
1356
- - 导航 URL 变化检测:更新上下文,防止旧定位污染
1357
- - 空转检测:避免循环无进展
1358
- - 重复批次防自转:连续返回同一批任务时自动停止
1359
-
1360
- 这些机制直接决定“调用次数、成功率、稳定性”。
1361
-
1362
- ---
1363
-
1364
- ## AI Client 设计
1365
-
1366
- `src/core/ai-client/` 提供统一接口,内部按 provider 适配:
1367
-
1368
- - `openai.ts`:OpenAI / Copilot 协议
1369
- - `anthropic.ts`:Anthropic 协议
1370
- - `deepseek.ts`:DeepSeek 协议
1371
- - `sse.ts`:流式事件解析
1372
-
1373
- 统一输出为 `AIChatResponse`,上层 loop 无需关心 provider 差异。
1374
-
1375
- ---
1376
-
1377
- ## Web 工具体系
1378
-
1379
- 内置 5 个工具(`src/web/*.ts`):
1380
-
1381
- 1. `dom`:点击、填写、输入等交互
1382
- 2. `navigate`:跳转、前进后退、滚动
1383
- 3. `page_info`:URL/标题/快照/查询
1384
- 4. `wait`:等待元素或文本条件
1385
- 5. `evaluate`:执行页面上下文 JS
1386
-
1387
- 通过 `ToolRegistry` 统一暴露给模型,执行结果标准化返回。
1388
-
1389
- ### Playwright 对齐说明(当前实现)
1390
-
1391
- - `dom.click`:采用更完整的点击事件链(`pointerdown/mousedown/pointerup/mouseup/click`)。
1392
- - `dom.select_option`:支持 `value/label/index`;结果返回显式 `value + label`。
1393
- - `dom.fill`:不允许用于 `checkbox/radio/file/button/submit/reset` 等不兼容输入类型。
1394
- - `wait.wait_for_selector`:支持 `state=attached|visible|hidden|detached`(默认 `attached`)。
1395
- - 快照运行态增强:可见 `select val`、`option selected`、`checked`、`disabled`、`readonly`,减少重复操作。
1396
-
1397
- ---
1398
-
1399
- ## 扩展与自定义
1400
-
1401
- ### 注册自定义工具
1402
-
1403
- ```ts
1404
- import { Type } from "@sinclair/typebox";
1405
-
1406
- agent.registerTool({
1407
- name: "my_tool",
1408
- description: "业务工具说明",
1409
- schema: Type.Object({
1410
- value: Type.String(),
1411
- }),
1412
- async execute(params) {
1413
- return { content: `ok: ${params.value}` };
1414
- },
1415
- });
499
+ src/
500
+ ├── core/ # 环境无关引擎
501
+ │ ├── index.ts # Core 入口
502
+ │ ├── types.ts # 共享类型
503
+ │ ├── system-prompt.ts # 系统提示词构建
504
+ │ ├── tool-registry.ts # 工具注册表
505
+ │ ├── tool-params.ts # 参数辅助
506
+ │ ├── agent-loop/ # Agent 循环
507
+ │ │ ├── index.ts # 主流程编排
508
+ │ │ ├── messages.ts # 紧凑消息构建
509
+ │ │ ├── snapshot.ts # 快照读取/包裹/去重
510
+ │ │ ├── recovery.ts # 恢复与拦截
511
+ │ │ ├── helpers.ts # 纯函数工具
512
+ │ │ ├── constants.ts # 默认常量
513
+ │ │ ├── types.ts # 循环类型
514
+ │ │ └── LOOP_MECHANISM.md # 机制权威说明
515
+ │ └── ai-client/ # AI 客户端
516
+ │ ├── index.ts # Provider 路由
517
+ │ ├── openai.ts # OpenAI/Copilot
518
+ │ ├── anthropic.ts # Anthropic
519
+ │ ├── deepseek.ts # DeepSeek
520
+ │ ├── doubao.ts # 豆包 (Ark)
521
+ │ ├── qwen.ts # 通义千问
522
+ │ ├── custom.ts # BaseAIClient 基类
523
+ │ ├── sse.ts # SSE 解析器
524
+ │ └── constants.ts # 端点与校验
525
+ └── web/ # 浏览器实现
526
+ ├── index.ts # WebAgent 入口
527
+ ├── ref-store.ts # #hashID → Element 映射
528
+ ├── event-listener-tracker.ts # 全局事件追踪
529
+ ├── messaging.ts # Extension 消息桥
530
+ ├── ui/ # 内置 UI 面板
531
+ └── tools/ # 工具实现
532
+ ├── dom-tool.ts
533
+ ├── navigate-tool.ts
534
+ ├── page-info-tool.ts
535
+ ├── wait-tool.ts
536
+ └── evaluate-tool.ts
1416
537
  ```
1417
538
 
1418
- ### Chrome Extension 模式
1419
-
1420
- `web/messaging.ts` 提供消息桥:
1421
- - Service Worker 发起工具调用
1422
- - Content Script 执行 DOM 工具
1423
- - 回传结果给后台 Agent
1424
-
1425
539
  ---
1426
540
 
1427
- ## 设计约束(必须遵守)
1428
-
1429
- - `web` 只依赖 `core`,`core` 不依赖 DOM API
1430
- - ToolRegistry 必须实例化,禁止全局单例污染
1431
- - 工具失败应返回可消费结果,不应直接中断主循环
1432
- - 消息策略与系统提示必须一致(否则会增加无效 completion)
1433
-
1434
- ---
1435
-
1436
- ## 权威执行文档(v2,建议以本节为准)
1437
-
1438
- > 本节对应当前代码实现(`src/core/agent-loop/*`、`src/core/system-prompt.ts`、`src/web/tools/*`)。
1439
- > 目标:把执行闭环、恢复策略、快照剪枝、工具能力、Prompt 协议一次性讲透。
1440
-
1441
- ### 1. 端到端执行架构(Execution Architecture)
1442
-
1443
- #### 1.1 分层职责
1444
-
1445
- - `core`(环境无关):
1446
- - `agent-loop`:轮次编排、停机判定、恢复/重试、指标汇总
1447
- - `system-prompt`:系统规则模板
1448
- - `tool-registry`:工具注册/分发/错误兜底
1449
- - `ai-client`:多 provider 协议适配(OpenAI/Copilot/Anthropic/DeepSeek/Doubao/Qwen)
1450
- - `web`(浏览器实现):
1451
- - `WebAgent`:入口编排、记忆、autoSnapshot、callbacks
1452
- - `tools`:DOM/导航/页面信息/等待/evaluate
1453
- - `event-listener-tracker`:全局事件监听追踪(`EventTarget.prototype` 补丁)
1454
- - `RefStore`:`#hashID -> Element` 映射
1455
-
1456
- #### 1.1.1 事件监听追踪器(event-listener-tracker)
1457
-
1458
- `src/web/event-listener-tracker.ts` 是快照交互性判定的基础设施:
1459
-
1460
- **原理:**
1461
- - 在 `web/index.ts` 模块加载时默认调用 `installEventListenerTracking()`(幂等),一次性补丁 `EventTarget.prototype.addEventListener/removeEventListener`
1462
- - 所有后续的 `addEventListener` 调用都会被拦截并记录,`removeEventListener` 同理反向清除
1463
- - 仅记录 Element 实例(跳过 `document`/`window` 等非元素目标)
1464
- - 内部用 `WeakMap<Element, Set<string>>` 存储,元素被 GC 后自动释放
1465
-
1466
- **导出 API:**
1467
-
1468
- | 函数 | 说明 |
1469
- | --- | --- |
1470
- | `installEventListenerTracking()` | 安装全局补丁(幂等) |
1471
- | `getTrackedElementEvents(el)` | 返回元素已记录的事件名数组(排序后) |
1472
- | `hasTrackedElementEvents(el)` | 判断元素是否存在至少一个被追踪的事件绑定 |
1473
-
1474
- **下游消费方(page-info-tool.ts):**
1475
- - `hasInteractiveTrackedEvents(el)` — 判断元素是否绑定了 INTERACTIVE_EVENTS 集合中的事件(click/input/change/focus 等 15 种),作为 hash ID 分配和优先级排序的首要判据
1476
- - `getTrackedElementEvents(el)` — 输出快照中的 `listeners="clk,inp,..."` 事件简写标注
1477
- - `getElementPriorityScore(el)` — 基于事件类型计算优先级分数,决定同层子元素输出顺序
1478
-
1479
- **安全约束:**
1480
- - 补丁不改变原调用语义(先执行原生方法,再做记录)
1481
- - 追踪失败时静默兜底,不影响业务代码执行
1482
- - 不记录 listener 函数引用,避免内存泄漏
1483
-
1484
- #### 1.2 Chat 生命周期(从 `WebAgent.chat()` 到收敛)
1485
-
1486
- 1. 创建本次会话 `RefStore`,并激活到 DOM 工具层。
1487
- 2. 前端生成 `initialSnapshot`(`generateSnapshot`),并注入 system prompt(`wrapSnapshot`)。
1488
- 3. 进入 `executeAgentLoop`:
1489
- - 构建紧凑消息(remaining + trace + latest snapshot)
1490
- - 调用 AI
1491
- - 执行工具调用
1492
- - 应用保护机制(拦截/恢复/空转/防自转)
1493
- - 刷新快照,进入下一轮
1494
- 4. 终止后返回 `AgentLoopResult`(`reply/toolCalls/messages/metrics`)。
1495
- 5. 清理 `RefStore`,释放本轮 hash 映射。
1496
-
1497
- #### 1.3 单轮执行流水线(Round Pipeline)
1498
-
1499
- 每一轮固定 5 个阶段:
1500
-
1501
- 1. **Ensure Snapshot**:无快照则读取快照。
1502
- 2. **Build Messages**:注入当前 remaining、历史执行轨迹、最新快照。
1503
- 3. **Call Model**:拿到 `text + toolCalls`。
1504
- 4. **Execute Tools**:逐个分发并执行,期间应用恢复机制。
1505
- 5. **Refresh Snapshot**:为下一轮准备最新页面状态。
1506
-
1507
- 核心思想:
1508
-
1509
- - 决策输入不是“用户原文”,而是“当前剩余任务 + 当前快照 + 已执行轨迹”。
1510
- - 每轮只消费当前可见可操作目标,不跨 DOM 变化链路强行连续操作。
1511
-
1512
- ---
1513
-
1514
- ### 2. 渐进式任务消费协议(Remaining Protocol)
1515
-
1516
- #### 2.1 状态变量
1517
-
1518
- `executeAgentLoop` 内部维护:
1519
-
1520
- - `remainingInstruction`:当前待消费任务文本(初始为用户输入)
1521
- - `previousRoundTasks`:上一轮已执行任务数组
1522
- - `previousRoundPlannedTasks`:上一轮模型规划数组(执行前)
1523
- - `previousRoundModelOutput`:上一轮模型输出归一化结果
1524
- - `lastPlannedBatchKey` + `consecutiveSamePlannedBatch`:重复批次防自转
1525
- - `lastRoundHadError`:上轮有错时不触发“重复批次即停机”
1526
-
1527
- #### 2.2 协议文本
1528
-
1529
- 模型每轮应返回:
1530
-
1531
- - `REMAINING: <text>`(还有剩余)
1532
- - 或 `REMAINING: DONE`(当前任务已消费完)
1533
-
1534
- #### 2.3 缺失协议时的回退策略
1535
-
1536
- - 若本轮有执行动作:启发式线性剔除(`reduceRemainingHeuristically`)
1537
- - 若本轮无推进:保持 remaining 不变,并标记协议违约风险
1538
-
1539
- #### 2.4 协议修复回合(Protocol Violation Round)
1540
-
1541
- 当出现“remaining 未完成 + 无工具调用”:
1542
-
1543
- - 不直接结束
1544
- - 下一轮注入强约束提示:
1545
- - 要么返回可执行工具调用
1546
- - 要么严格返回 `REMAINING: DONE`
1547
-
1548
- ---
1549
-
1550
- ### 3. 停机条件与防自转
1551
-
1552
- 触发结束的典型条件:
1553
-
1554
- 1. 无工具调用且 remaining 已完成。
1555
- 2. 模型明确给出 `REMAINING: DONE` 并进入总结收敛。
1556
- 3. 连续两轮完全相同 planned batch 且上一轮无错误(防自转)。
1557
- 4. 达到 `maxRounds`。
1558
-
1559
- 另外:纯只读空转(例如连续只做 `page_info`)会被 `detectIdleLoop` 终止。
1560
-
1561
- ---
1562
-
1563
- ### 4. 完整重试与恢复机制(Recovery & Retry)
1564
-
1565
- #### 4.1 机制总览
1566
-
1567
- | 机制 | 触发条件 | 默认参数 | 动作 | 代码位置 |
1568
- | --- | --- | --- | --- | --- |
1569
- | 冗余 page_info 拦截 | `page_info.snapshot/query_all/get_url/get_title/get_viewport` | - | 直接返回拦截结果,不执行真实调用 | `recovery.ts#checkRedundantSnapshot` |
1570
- | 连续 snapshot 防抖 | 连续 page_info.snapshot | 阈值=2 | 标记 `REDUNDANT_SNAPSHOT` | `recovery.ts#applySnapshotDebounce` |
1571
- | 元素未找到自动恢复 | `dom` 且结果为 element not found | `DEFAULT_ACTION_RECOVERY_ROUNDS=2`,`DEFAULT_RECOVERY_WAIT_MS=100` | 等待 -> 刷新快照 -> 返回 recovery 结果 | `recovery.ts#handleElementRecovery` |
1572
- | Not-found 重试对话流 | 本轮有 not-found 失败任务 | `DEFAULT_NOT_FOUND_RETRY_ROUNDS=2`,`DEFAULT_NOT_FOUND_RETRY_WAIT_MS=1000` | 注入失败任务上下文 + attempt x/y,必要时等待后重试 | `index.ts` 主循环 |
1573
- | 导航后上下文刷新 | `navigate` 成功且动作为 goto/back/forward/reload | - | 立即刷新快照 | `recovery.ts#handleNavigationUrlChange` |
1574
- | 空转检测 | 连续只读轮次 | 连续 2 轮 | 返回 -1 终止 | `recovery.ts#detectIdleLoop` |
1575
-
1576
- #### 4.2 Not-found 重试对话流详细步骤
1577
-
1578
- 1. 本轮执行时收集失败任务(name/input/reason)。
1579
- 2. 下一轮在消息中附加 `## Not-found retry context`:
1580
- - `Retry attempt: x/y`
1581
- - 失败工具列表
1582
- - 仅重试当前快照可见目标
1583
- 3. 若模型仍说明“找不到”且未超上限:
1584
- - 等待 `DEFAULT_NOT_FOUND_RETRY_WAIT_MS`
1585
- - 刷新快照
1586
- - 继续下一轮
1587
- 4. 超过最大重试轮次后退出该流,交由 remaining 协议收敛。
1588
-
1589
- ---
1590
-
1591
- ### 5. 快照系统(Snapshot System)
1592
-
1593
- #### 5.1 快照生命周期
1594
-
1595
- - 读取:`readPageSnapshot(registry, options)`
1596
- - 包裹:`wrapSnapshot()` 注入 `SNAPSHOT_START/END`
1597
- - 去重:`deduplicateSnapshots()` 保留最新快照,旧快照替换为 `SNAPSHOT_OUTDATED`
1598
- - 剥离:`stripSnapshotFromPrompt()` 清理 system prompt 中历史快照
1599
-
1600
- #### 5.2 Core 与 Web 的参数默认值差异(很重要)
1601
-
1602
- - `core/agent-loop/snapshot.ts` 默认读取:
1603
- - `maxDepth=12`
1604
- - `viewportOnly=false`(优先完整性)
1605
- - `pruneLayout=true`
1606
- - `maxNodes=500`
1607
- - `maxChildren=30`
1608
- - `maxTextLength=40`
1609
- - `web/tools/page-info-tool.ts` 的 `snapshot` action 默认值:
1610
- - `maxDepth=12`
1611
- - `viewportOnly=true`
1612
- - `pruneLayout=true`
1613
- - `maxNodes=220`
1614
- - `maxChildren=25`
1615
- - `maxTextLength=40`
1616
-
1617
- #### 5.3 剪枝与压缩策略
1618
-
1619
- `generateSnapshot()` 的核心策略:
1620
-
1621
- 1. 跳过无意义节点:`script/style/svg/meta/...`
1622
- 2. 可见性过滤:`display:none/visibility:hidden/零尺寸` 排除
1623
- 3. 视口裁剪(可选):仅保留与视口相交元素
1624
- 4. 智能折叠布局容器(`pruneLayout=true`):
1625
- - 空布局容器不输出自身
1626
- - 子节点提升输出
1627
- - 多子节点提升时输出 `collapsed-group` 括号块
1628
- 5. 交互优先:同层先输出 interactive children
1629
- 6. 仅交互节点分配 hash ID:通过 `hasInteractiveTrackedEvents()` + 语义标签 + ARIA role 判定交互性,非交互节点不输出 `#hashId`
1630
- 7. 角色优先标签:当元素有 `INTERACTIVE_ROLES` 中的 ARIA role 且与 tag 不等价时,用 role 作为显示标签(`[combobox]` 替代 `[input] role="combobox"`),同时去除冗余 `role` 属性
1631
- 8. 预算控制:`maxNodes` + `maxChildren` 截断
1632
- 9. 文字压缩:`maxTextLength`
1633
- 10. 属性压缩:仅保留关键属性、运行时状态(`checked/disabled/readonly/selected/val`)
1634
- 11. 事件信号压缩:输出 `listeners="clk,inp,chg,..."` 简写,帮助模型识别真实交互链路
1635
-
1636
- #### 5.4 RefStore 与 hash selector
1637
-
1638
- - 快照中仅交互元素携带 `#hashId`(而非长 CSS/XPath),非交互元素(标题/标签/纯文本)无标识
1639
- - 交互性判定:运行时事件绑定(`hasInteractiveTrackedEvents`)> 语义标签(`input/button/a`)> ARIA role(`INTERACTIVE_ROLES` 集合)> `tabindex`/`contenteditable`
1640
- - 工具执行时优先按 hash 命中真实 Element
1641
- - 页面变化/恢复快照时会重建映射,避免旧引用污染
1642
-
1643
- ---
1644
-
1645
- ### 6. Tools 能力总表(详细)
1646
-
1647
- #### 6.1 `dom` 工具(`src/web/tools/dom-tool.ts`)
1648
-
1649
- 支持动作:
1650
-
1651
- - `click`
1652
- - `fill`
1653
- - `select_option`
1654
- - `clear`
1655
- - `check` / `uncheck`
1656
- - `type`
1657
- - `focus`
1658
- - `hover`
1659
- - `press`
1660
- - `get_text`
1661
- - `get_attr`
1662
- - `set_attr`
1663
- - `add_class`
1664
- - `remove_class`
1665
-
1666
- 关键语义增强(Playwright 风格):
1667
-
1668
- 1. `retarget`:自动重定向到可交互目标
1669
- 2. `scrollIntoView` 多策略轮换
1670
- 3. 元素稳定性检查(rAF)
1671
- 4. hit-target 覆盖检测
1672
- 5. 完整 pointer/mouse 点击事件链
1673
- 6. `check/uncheck` 通过 click + 状态校验
1674
- 7. 组合键 `press`(如 `Control+a`)
1675
- 8. `fill` 按输入类型分流(setValue vs text 输入)
1676
- 9. 自定义下拉增强(Element Plus/AntD)
1677
- 10. ARIA disabled 祖先链检查
1678
- 11. 隐藏原生控件代理点击(switch/checkbox/radio)
1679
- 12. 表单项说明 label 自动映射到真实控件
1680
-
1681
- 动作边界:
1682
-
1683
- - `fill` 不支持 `checkbox/radio/file/button/submit/reset`。
1684
- - `uncheck` 对 `radio` 返回错误(语义不可逆)。
1685
- - 默认做 actionability 检查;`force=true` 可跳过。
1686
-
1687
- #### 6.2 `navigate` 工具(`src/web/tools/navigate-tool.ts`)
1688
-
1689
- 支持动作:
1690
-
1691
- - `goto`
1692
- - `back`
1693
- - `forward`
1694
- - `reload`
1695
- - `scroll`(按 `selector` 或 `x/y`)
1696
-
1697
- 细节:
1698
-
1699
- - `scroll.selector` 支持 hash selector + CSS selector。
1700
- - 优先 `scrollIntoViewIfNeeded`,回退 `scrollIntoView(center)`。
1701
-
1702
- #### 6.3 `page_info` 工具(`src/web/tools/page-info-tool.ts`)
1703
-
1704
- 支持动作:
1705
-
1706
- - `get_url`
1707
- - `get_title`
1708
- - `get_selection`
1709
- - `get_viewport`
1710
- - `snapshot`
1711
- - `query_all`
1712
-
1713
- 注意:
1714
-
1715
- - 在 loop 中多数 `page_info.*` 会被拦截为冗余,避免空转。
1716
- - `snapshot` 是框架层自动能力,不建议模型主动频繁调用。
1717
-
1718
- #### 6.4 `wait` 工具(`src/web/tools/wait-tool.ts`)
1719
-
1720
- 支持动作:
1721
-
1722
- - `wait_for_selector`
1723
- - `wait_for_hidden`
1724
- - `wait_for_text`
1725
- - `wait_for_stable`
1726
-
1727
- 状态语义:
1728
-
1729
- - `attached` / `visible` / `hidden` / `detached`
1730
-
1731
- 实现策略:
1732
-
1733
- - 轮询 + MutationObserver 双通道
1734
- - 可见性判定与 `dom-tool` 对齐(避免“wait visible 与 click visible 不一致”)
1735
-
1736
- #### 6.5 `evaluate` 工具(`src/web/tools/evaluate-tool.ts`)
1737
-
1738
- 能力:
1739
-
1740
- - 执行页面上下文 JS(表达式或语句块)
1741
- - 序列化返回普通值、DOM 元素、NodeList/HTMLCollection
1742
-
1743
- 定位:
1744
-
1745
- - 兜底工具(其他工具难以表达时使用)
1746
- - 同时也是高风险工具,建议少量、明确、可回滚地使用
1747
-
1748
- ---
1749
-
1750
- ### 7. Prompt 设计(完整)
1751
-
1752
- #### 7.1 Prompt 分层
1753
-
1754
- 1. **System Prompt(稳定规则层)**
1755
- - 由 `buildSystemPrompt()` 生成
1756
- - 永久规则:快照优先、批处理、输入顺序、禁止 page_info、REMAINING 协议
1757
- 2. **Round Message(动态状态层)**
1758
- - 由 `buildCompactMessages()` 生成
1759
- - 包含当前 remaining、done steps、上轮计划、上轮输出归一化、最新快照
1760
- 3. **Recovery Context(修复层)**
1761
- - Not-found retry context
1762
- - Protocol violation hint
1763
-
1764
- #### 7.2 System Prompt 的关键规则(当前实现)
1765
-
1766
- `buildSystemPrompt()` 中的核心约束(英文原文语义):
1767
-
1768
- - 从“当前快照 + 当前 remaining”直接执行,不复述任务
1769
- - 每轮按 task reduction 推进
1770
- - 仅交互元素(有事件/输入框/按钮/链接等)携带 `#hashID`,无 `#hashID` 的元素为上下文
1771
- - 角色优先标签:方括号标签可能是 ARIA role 而非 HTML tag(如 `[combobox]`/`[slider]`),表示交互模式
1772
- - 使用 hash selector,不猜 CSS
1773
- - 可见且互不依赖的动作同轮批量执行
1774
- - 输入顺序强约束:`focus/click -> fill/type/select_option`
1775
- - 多字段交替对执行(防止 focus-only 空轮)
1776
- - DOM 结构变化动作后断轮
1777
- - 禁用 `page_info` 作为规划手段
1778
- - 输出 `REMAINING: ...` 或 `REMAINING: DONE`
1779
-
1780
- #### 7.3 Round 0 与 Round 1+ 差异
1781
-
1782
- - Round 0:原始任务 + 首轮快照
1783
- - Round 1+:不再重复原始用户输入,改为“remaining 驱动”
1784
-
1785
- 这能显著降低“回头重做”的概率。
1786
-
1787
- #### 7.4 显式 UI 意图判定
1788
-
1789
- `messages.ts` 有 `isExplicitAgentUiRequest()`:
1790
-
1791
- - 若用户明确要求操作 AutoPilot 聊天 UI,允许相关操作
1792
- - 否则默认禁止触碰聊天输入框/发送按钮/dock
1793
-
1794
- ---
1795
-
1796
- ### 8. 可观测性与指标
1797
-
1798
- 每次 `chat` 结束输出 `AgentLoopMetrics`:
1799
-
1800
- - `roundCount`
1801
- - `totalToolCalls`
1802
- - `successfulToolCalls`
1803
- - `failedToolCalls`
1804
- - `toolSuccessRate`
1805
- - `recoveryCount`
1806
- - `redundantInterceptCount`
1807
- - `snapshotReadCount`
1808
- - `latestSnapshotSize`
1809
- - `avgSnapshotSize`
1810
- - `maxSnapshotSize`
1811
- - `inputTokens`
1812
- - `outputTokens`
1813
-
1814
- 建议重点观测四个健康指标:
1815
-
1816
- 1. `toolSuccessRate`
1817
- 2. `recoveryCount`
1818
- 3. `roundCount`
1819
- 4. `avgSnapshotSize`
1820
-
1821
- ---
1822
-
1823
- ### 9. 常量与默认参数速查
1824
-
1825
- `src/core/agent-loop/constants.ts`:
1826
-
1827
- - `DEFAULT_MAX_ROUNDS = 40`
1828
- - `DEFAULT_RECOVERY_WAIT_MS = 100`
1829
- - `DEFAULT_ACTION_RECOVERY_ROUNDS = 2`
1830
- - `DEFAULT_NOT_FOUND_RETRY_ROUNDS = 2`
1831
- - `DEFAULT_NOT_FOUND_RETRY_WAIT_MS = 1000`
1832
-
1833
- `src/web/tools/wait-tool.ts`:
1834
-
1835
- - `DEFAULT_TIMEOUT = 6000`
1836
-
1837
- ---
1838
-
1839
- ### 10. 文档与实现一致性清单(维护者必看)
1840
-
1841
- Agent Loop 机制权威文档:`src/core/agent-loop/LOOP_MECHANISM.md`。
1842
-
1843
- 任何涉及“渐进式任务消费”的改动,至少同步以下文件:
1844
-
1845
- 1. `src/core/agent-loop/messages.ts`(输入语义)
1846
- 2. `src/core/agent-loop/index.ts`(停机判定与推进逻辑)
1847
- 3. `src/core/agent-loop/LOOP_MECHANISM.md`(机制权威说明)
1848
- 4. `README.md`(机制说明)
1849
-
1850
- 任何涉及“找不到元素重试流”的改动,至少同步:
1851
-
1852
- 1. `src/core/agent-loop/index.ts`
1853
- 2. `src/core/agent-loop/recovery.ts`
1854
- 3. `src/core/agent-loop/LOOP_MECHANISM.md`
1855
- 4. `README.md`
1856
-
1857
- 任何涉及 provider 新增/调整的改动,至少同步:
1858
-
1859
- 1. `src/core/ai-client/index.ts`(provider 路由)
1860
- 2. `src/core/ai-client/constants.ts`(默认端点)
1861
- 3. `src/web/index.ts`(WebAgentOptions 注释/提示)
1862
- 4. `README.md`(配置示例与支持矩阵)
1863
-
1864
- 这样才能保证“实现、提示词、文档”三者一致,不出现行为漂移。
1865
-
1866
- ---
1867
-
1868
- ## 开发命令
541
+ ## 开发
1869
542
 
1870
543
  ```bash
1871
- pnpm install
1872
- pnpm check
1873
- pnpm test
1874
- pnpm demo
1875
- pnpm build
544
+ pnpm install # 安装依赖
545
+ pnpm check # 类型检查 + Lint
546
+ pnpm test # 运行测试
547
+ pnpm demo # 启动 Demo
548
+ pnpm build # 构建
1876
549
  ```
1877
550
 
1878
551
  ---
@@ -1880,35 +553,3 @@ pnpm build
1880
553
  ## License
1881
554
 
1882
555
  MIT
1883
-
1884
- ## 文档待优化项(TODO)
1885
-
1886
- 以下是当前 README 中尚需扩展或补充的内容,按优先级排列:
1887
-
1888
- ### 需要深化的现有章节
1889
-
1890
- | 章节 | 当前状态 | 待优化内容 |
1891
- | --- | --- | --- |
1892
- | 设计思想 | 只有结论,缺少推导 | 每条原则补充"为什么这么做"+ 实现方式 + 完整保护机制清单表格 |
1893
- | 快照格式 | 未独立说明 | 新增章节:快照文本结构示例、标记说明表(`#hash`/`checked`/`val`/`collapsed-group` 等)、生成管线流程图 |
1894
- | RefStore 机制 | 仅在架构图中提及 | 新增章节:为什么不用 CSS 选择器、FNV-1a hash 算法说明、生命周期图、核心 API 表 |
1895
-
1896
- ### 需要新增的章节
1897
-
1898
- | 章节 | 说明 |
1899
- | --- | --- |
1900
- | AI Client 适配详解 | Provider 路由架构图、差异处理表(工具格式/流式协议/Token字段/系统提示)、统一响应格式 `AIChatResponse` 类型定义、自定义 Client 代码示例、SSE 流式解析说明 |
1901
- | 多轮对话记忆机制 | `memory=true` 时的 history 累积原理、消息格式 `AIMessage` 类型、记忆策略表(累积/注入/快照处理/清空/关闭)、Token 消耗与上下文窗口注意事项 |
1902
- | 错误处理与安全机制 | "错误不中断循环"原则说明、五层恢复链路详解(工具层→恢复层→对话层→协议层→循环层)、安全约束清单表(Agent UI 禁操作/fill 类型限制/evaluate 风险/ARIA disabled 检查等) |
1903
- | 性能优化建议 | 三维度优化表:减少 Token 消耗(pruneLayout/maxNodes/viewportOnly)、减少执行轮次(精确 Prompt/批量执行/业务工具封装)、提高成功率(waitMs/retry 工具/组件适配) |
1904
- | 常见问题 FAQ | 与 Puppeteer/Playwright 的区别、支持的 AI 模型、为什么用工具调用而非代码生成、登录态处理、快照过大处理、调试方法、iframe 支持、Token 消耗参考值、操作权限限制方式 |
1905
-
1906
- ### 具体补充清单
1907
-
1908
- 1. **设计思想 - 保护机制表格**:将现有简述扩展为 9 项完整清单(冗余拦截 / 快照防抖 / 元素恢复 / Not-found 重试 / 导航刷新 / 空转检测 / 重复批次防自转 / 协议修复 / 强制断轮)
1909
- 2. **设计思想 - 后端协作模式**:补充前后端 Agent 协作的完整交互示例代码块
1910
- 3. **RefStore - hash 生成算法**:`FNV-1a(URL + XPath) → 36进制 → 碰撞检测` 流程说明
1911
- 4. **RefStore - 生命周期图**:从 `chat()` 创建到 `clear()` 释放的完整流程
1912
- 5. **AI Client - 自定义接入**:`BaseAIClient` 和纯对象两种方式的代码示例
1913
- 6. **快照格式 - 生成管线**:从 `document.body` 到最终文本的 10 步管线流程
1914
- 7. **错误处理 - 恢复常量**:`DEFAULT_ACTION_RECOVERY_ROUNDS=2` / `DEFAULT_NOT_FOUND_RETRY_ROUNDS=2` / `DEFAULT_NOT_FOUND_RETRY_WAIT_MS=1000` 等关键参数说明