openclaw-multi-auto 1.4.5 → 1.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/dist/build-info.json +3 -3
  2. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  3. package/dist/plugin-sdk/mattermost.js +3 -3
  4. package/dist/plugin-sdk/signal.js +2 -2
  5. package/docs/browser-architecture.md +602 -0
  6. package/extensions/googlechat/node_modules/.bin/openclaw +2 -2
  7. package/extensions/memory-core/node_modules/.bin/openclaw +2 -2
  8. package/extensions/memory-lancedb/node_modules/.bin/openai +2 -2
  9. package/extensions/page-action-cache/dist/actions-executor.d.ts +62 -0
  10. package/extensions/page-action-cache/dist/actions-executor.d.ts.map +1 -0
  11. package/extensions/page-action-cache/dist/actions-executor.js +339 -0
  12. package/extensions/page-action-cache/dist/actions-executor.js.map +1 -0
  13. package/extensions/page-action-cache/dist/cache-invalidator.d.ts +70 -0
  14. package/extensions/page-action-cache/dist/cache-invalidator.d.ts.map +1 -0
  15. package/extensions/page-action-cache/dist/cache-invalidator.js +212 -0
  16. package/extensions/page-action-cache/dist/cache-invalidator.js.map +1 -0
  17. package/extensions/page-action-cache/dist/cache-store.d.ts +80 -0
  18. package/extensions/page-action-cache/dist/cache-store.d.ts.map +1 -0
  19. package/extensions/page-action-cache/dist/cache-store.js +361 -0
  20. package/extensions/page-action-cache/dist/cache-store.js.map +1 -0
  21. package/extensions/page-action-cache/dist/cache-strategy.d.ts +65 -0
  22. package/extensions/page-action-cache/dist/cache-strategy.d.ts.map +1 -0
  23. package/extensions/page-action-cache/dist/cache-strategy.js +237 -0
  24. package/extensions/page-action-cache/dist/cache-strategy.js.map +1 -0
  25. package/extensions/page-action-cache/dist/hooks-entry.d.ts +18 -0
  26. package/extensions/page-action-cache/dist/hooks-entry.d.ts.map +1 -0
  27. package/extensions/page-action-cache/dist/hooks-entry.js +27 -0
  28. package/extensions/page-action-cache/dist/hooks-entry.js.map +1 -0
  29. package/extensions/page-action-cache/dist/hooks.d.ts +10 -0
  30. package/extensions/page-action-cache/dist/hooks.d.ts.map +1 -0
  31. package/extensions/page-action-cache/dist/hooks.js +277 -0
  32. package/extensions/page-action-cache/dist/hooks.js.map +1 -0
  33. package/extensions/page-action-cache/dist/index.d.ts +24 -0
  34. package/extensions/page-action-cache/dist/index.d.ts.map +1 -0
  35. package/extensions/page-action-cache/dist/index.js +34 -0
  36. package/extensions/page-action-cache/dist/index.js.map +1 -0
  37. package/extensions/page-action-cache/dist/scenario-recognizer.d.ts +45 -0
  38. package/extensions/page-action-cache/dist/scenario-recognizer.d.ts.map +1 -0
  39. package/extensions/page-action-cache/dist/scenario-recognizer.js +213 -0
  40. package/extensions/page-action-cache/dist/scenario-recognizer.js.map +1 -0
  41. package/extensions/page-action-cache/dist/security-policy.d.ts +62 -0
  42. package/extensions/page-action-cache/dist/security-policy.d.ts.map +1 -0
  43. package/extensions/page-action-cache/dist/security-policy.js +219 -0
  44. package/extensions/page-action-cache/dist/security-policy.js.map +1 -0
  45. package/extensions/page-action-cache/dist/tools.d.ts +209 -0
  46. package/extensions/page-action-cache/dist/tools.d.ts.map +1 -0
  47. package/extensions/page-action-cache/dist/tools.js +383 -0
  48. package/extensions/page-action-cache/dist/tools.js.map +1 -0
  49. package/extensions/page-action-cache/dist/types.d.ts +336 -0
  50. package/extensions/page-action-cache/dist/types.d.ts.map +1 -0
  51. package/extensions/page-action-cache/dist/types.js +8 -0
  52. package/extensions/page-action-cache/dist/types.js.map +1 -0
  53. package/extensions/page-action-cache/dist/ux-enhancer.d.ts +60 -0
  54. package/extensions/page-action-cache/dist/ux-enhancer.d.ts.map +1 -0
  55. package/extensions/page-action-cache/dist/ux-enhancer.js +218 -0
  56. package/extensions/page-action-cache/dist/ux-enhancer.js.map +1 -0
  57. package/extensions/page-action-cache/dist/variable-resolver.d.ts +28 -0
  58. package/extensions/page-action-cache/dist/variable-resolver.d.ts.map +1 -0
  59. package/extensions/page-action-cache/dist/variable-resolver.js +201 -0
  60. package/extensions/page-action-cache/dist/variable-resolver.js.map +1 -0
  61. package/extensions/page-action-cache/docs/API.md +555 -0
  62. package/extensions/page-action-cache/docs/IMPLEMENTATION.md +1792 -0
  63. package/extensions/page-action-cache/docs/INTEGRATION.md +387 -0
  64. package/extensions/page-action-cache/docs/README.md +183 -0
  65. package/extensions/page-action-cache/index.ts +118 -0
  66. package/extensions/page-action-cache/node_modules/.bin/nlc +21 -0
  67. package/extensions/page-action-cache/node_modules/.bin/node-llama-cpp +21 -0
  68. package/extensions/page-action-cache/node_modules/.bin/openclaw +21 -0
  69. package/extensions/page-action-cache/node_modules/.bin/tsc +21 -0
  70. package/extensions/page-action-cache/node_modules/.bin/tsserver +21 -0
  71. package/extensions/page-action-cache/node_modules/.bin/vitest +21 -0
  72. package/extensions/page-action-cache/openclaw.plugin.json +208 -0
  73. package/extensions/page-action-cache/package.json +76 -0
  74. package/extensions/page-action-cache/scripts/npm_publish.sh +80 -0
  75. package/extensions/page-action-cache/skills/page-action-cache/SKILL.md +216 -0
  76. package/extensions/page-action-cache/src/actions-executor.ts +441 -0
  77. package/extensions/page-action-cache/src/cache-invalidator.ts +271 -0
  78. package/extensions/page-action-cache/src/cache-store.ts +457 -0
  79. package/extensions/page-action-cache/src/cache-strategy.ts +327 -0
  80. package/extensions/page-action-cache/src/hooks-entry.ts +114 -0
  81. package/extensions/page-action-cache/src/hooks.ts +332 -0
  82. package/extensions/page-action-cache/src/index.ts +104 -0
  83. package/extensions/page-action-cache/src/scenario-recognizer.ts +259 -0
  84. package/extensions/page-action-cache/src/security-policy.ts +268 -0
  85. package/extensions/page-action-cache/src/tools.ts +437 -0
  86. package/extensions/page-action-cache/src/types.ts +482 -0
  87. package/extensions/page-action-cache/src/ux-enhancer.ts +266 -0
  88. package/extensions/page-action-cache/src/variable-resolver.ts +258 -0
  89. package/extensions/page-action-cache/tests/actions-executor.test.ts +424 -0
  90. package/extensions/page-action-cache/tests/cache-store.test.ts +267 -0
  91. package/extensions/page-action-cache/tests/integration-test.ts +62 -0
  92. package/extensions/page-action-cache/tests/scenario-recognizer.test.ts +140 -0
  93. package/extensions/page-action-cache/tests/variable-resolver.test.ts +187 -0
  94. package/extensions/page-action-cache/tsconfig.json +39 -0
  95. package/package.json +1 -1
  96. package/scripts/create-instance.sh +26 -8
  97. package/scripts/npm_publish.sh +59 -1
  98. package/scripts/publish-extension.sh +343 -0
  99. package/ui/node_modules/.bin/vite +2 -2
  100. package/ui/node_modules/.bin/vitest +2 -2
@@ -0,0 +1,216 @@
1
+ # 页面操作缓存扩展 - 使用指南
2
+
3
+ **版本**: 1.0.0
4
+ **更新日期**: 2026-03-13
5
+
6
+ ## 概述
7
+
8
+ ## 概述
9
+
10
+ 本技能提供了页面操作缓存功能,可以缓存 LLM 分析浏览器页面后生成的操作序列。当再次访问相同页面时,直接使用缓存的操作,无需重新分析 DOM,大幅降低 token 消耗和操作延迟。
11
+
12
+ ## 核心优势
13
+
14
+ - **节省 Token**: 避免每次操作都发送 DOM 给 LLM 分析
15
+ - **加速执行**: 跳过 LLM 分析,直接执行缓存的操作
16
+ - **智能匹配**: 多层场景识别(规则+LLM+历史)
17
+ - **安全可靠**: 自动检测页面变化,缓存失效
18
+
19
+ ## 可用工具
20
+
21
+ ### 1. execute_cached - 执行缓存操作
22
+
23
+ **用途**: 执行缓存的页面操作序列
24
+
25
+ **参数**:
26
+ - `cacheKey` (必需): 缓存键(从 cache_info 中获取)
27
+ - `fromIndex` (可选): 从第几个操作开始执行(默认 0)
28
+ - `toIndex` (可选): 执行到第几个操作(不指定则执行全部)
29
+ - `dryRun` (可选): 试运行模式,不实际执行
30
+ - `force` (可选): 强制执行(忽略页面变化检测)
31
+
32
+ **示例**:
33
+ ```
34
+ 使用缓存登录 example.com,cacheKey: abc123
35
+ ```
36
+
37
+ ### 2. cache_stats - 查看缓存统计
38
+
39
+ **用途**: 查看缓存命中统计、节省的 token 等
40
+
41
+ **参数**: 无
42
+
43
+ **示例**:
44
+ ```
45
+ 查看缓存统计信息
46
+ ```
47
+
48
+ ### 3. cache_list - 列出所有缓存
49
+
50
+ **用途**: 列出所有缓存条目及其详情
51
+
52
+ **参数**:
53
+ - `limit` (可选): 最多返回的条目数(默认 20)
54
+ - `filterScenario` (可选): 按场景过滤(如 login, checkout)
55
+
56
+ **示例**:
57
+ ```
58
+ 列出所有登录相关的缓存
59
+ ```
60
+
61
+ ### 4. cache_clear - 清空缓存
62
+
63
+ **用途**: 清空缓存条目
64
+
65
+ **参数**:
66
+ - `scenario` (可选): 只清空指定场景的缓存
67
+
68
+ **示例**:
69
+ ```
70
+ 清空所有缓存
71
+ 清空登录场景的缓存
72
+ ```
73
+
74
+ ### 5. scenario_list - 列出所有场景
75
+
76
+ **用途**: 列出所有支持的缓存场景及其匹配规则
77
+
78
+ **参数**: 无
79
+
80
+ **示例**:
81
+ ```
82
+ 列出所有支持的缓存场景
83
+ ```
84
+
85
+ ### 6. force_refresh - 强制刷新缓存
86
+
87
+ **用途**: 强制刷新指定页面的缓存
88
+
89
+ **参数**:
90
+ - `url` (必需): 页面 URL
91
+ - `viewport` (可选): 视口尺寸
92
+
93
+ **示例**:
94
+ ```
95
+ 强制刷新 example.com/login 的缓存
96
+ ```
97
+
98
+ ## 使用流程
99
+
100
+ ### 场景 1: 首次访问页面
101
+
102
+ ```
103
+ 用户: "登录 example.com"
104
+
105
+ LLM: 分析页面 DOM,生成操作序列
106
+
107
+ 系统: 自动保存到缓存(L3 级)
108
+ ```
109
+
110
+ ### 场景 2: 再次访问相同页面
111
+
112
+ ```
113
+ 用户: "登录 example.com"
114
+
115
+ 系统: 检测到缓存匹配(置信度 95%)
116
+
117
+ LLM: 收到 cacheInfo,决定使用缓存
118
+
119
+ 系统: 使用 execute_cached 工具执行缓存操作
120
+
121
+ 结果: 节省 5000 tokens,加速 2 秒
122
+ ```
123
+
124
+ ### 场景 3: 页面发生变化
125
+
126
+ ```
127
+ 用户: "登录 example.com"
128
+
129
+ 系统: 检测页面结构变化
130
+
131
+ 系统: 自动失效缓存
132
+
133
+ LLM: 重新分析页面,生成新操作
134
+
135
+ 系统: 保存新的缓存
136
+ ```
137
+
138
+ ## 支持的场景
139
+
140
+ | 场景 | 关键词 | 缓存层级 |
141
+ |------|---------|---------|
142
+ | login | 登录、登陆、注册、sign in | L3 |
143
+ | logout | 退出、登出、注销、logout | L3 |
144
+ | search | 搜索、查找、search | L3 |
145
+ | checkout | 结账、结算、支付、checkout | L3 |
146
+ | settings | 设置、配置、修改 | L3 |
147
+ | form_fill | 填写、填表单、提交 | L2 |
148
+ | navigate | 打开、进入、跳转 | L2 |
149
+ | screenshot | 截图、screenshot | L1 |
150
+
151
+ ## 缓存层级
152
+
153
+ - **L3: 场景级** - 包含变量模板(如 username, password),高置信度时使用
154
+ - **L2: 流程级** - 固定操作序列,中等置信度时使用
155
+ - **L1: 原子级** - 单个操作,低置信度时使用
156
+
157
+ ## 注意事项
158
+
159
+ 1. **页面变化检测**: 系统会自动检测页面结构变化,如果检测到变化会自动失效缓存
160
+ 2. **变量替换**: 支持变量模板(如 ${username}),执行时自动从用户输入提取
161
+ 3. **敏感数据**: 密码等敏感数据会自动脱敏存储
162
+ 4. **试运行**: 使用 dryRun 参数可以预览操作而不执行
163
+ 5. **强制执行**: 使用 force 参数可以跳过页面变化检测(谨慎使用)
164
+
165
+ ## 最佳实践
166
+
167
+ 1. **优先使用缓存**: 当工具返回 cacheInfo 时,优先使用 execute_cached 工具
168
+ 2. **检查命中统计**: 定期查看 cache_stats,了解缓存效果
169
+ 3. **及时清理**: 使用 cache_clear 清理不再需要的缓存
170
+ 4. **测试新功能**: 使用 dryRun 模式测试缓存操作
171
+ 5. **处理变化**: 当页面变化导致操作失败时,使用 force_refresh 刷新缓存
172
+
173
+ ## 故障排查
174
+
175
+ ### 问题 1: 缓存未命中
176
+
177
+ **原因**:
178
+ - 首次访问该页面
179
+ - 缓存已过期
180
+ - 页面结构发生变化
181
+
182
+ **解决**:
183
+ - 使用 scenario_list 查看支持的场景
184
+ - 使用 cache_list 查看现有缓存
185
+ - 使用 force_refresh 强制刷新
186
+
187
+ ### 问题 2: 缓存执行失败
188
+
189
+ **原因**:
190
+ - 页面结构发生变化
191
+ - 元素选择器失效
192
+ - 操作顺序错误
193
+
194
+ **解决**:
195
+ - 查看执行结果中的错误信息
196
+ - 使用 force_refresh 刷新缓存
197
+ - 调整操作序列
198
+
199
+ ### 问题 3: 变量未替换
200
+
201
+ **原因**:
202
+ - 用户输入中不包含变量值
203
+ - 变量名不匹配
204
+
205
+ **解决**:
206
+ - 确保用户输入包含变量值(如 "用户名 alice")
207
+ - 使用变量提取测试验证
208
+
209
+ ## 统计信息含义
210
+
211
+ - **总条目数**: 当前缓存的页面操作条目数量
212
+ - **总命中**: 缓存命中的总次数
213
+ - **命中率**: 缓存命中占总请求的百分比
214
+ - **L3/L2/L1 命中**: 各层级缓存命中的次数
215
+ - **节省 Token**: 估算节省的 token 数量
216
+ - **节省时间**: 估算节省的时间(毫秒)
@@ -0,0 +1,441 @@
1
+ /**
2
+ * Actions Executor
3
+ * 操作执行器 - 执行缓存的原子化操作
4
+ */
5
+
6
+ import type {
7
+ AtomicAction,
8
+ ExecutionResult,
9
+ PwAi,
10
+ VariableMap,
11
+ NavigateAction,
12
+ ClickAction,
13
+ TypeAction,
14
+ PressAction,
15
+ HoverAction,
16
+ ScreenshotAction,
17
+ EvaluateAction,
18
+ } from "./types.js";
19
+
20
+ // ============================================================================
21
+ // Actions Executor 类
22
+ // ============================================================================
23
+
24
+ /**
25
+ * 操作执行器
26
+ */
27
+ export class ActionsExecutor {
28
+ private pw: PwAi | null = null;
29
+ private cdpUrl: string = "";
30
+
31
+ constructor(cdpUrl?: string) {
32
+ if (cdpUrl) {
33
+ this.cdpUrl = cdpUrl;
34
+ }
35
+ }
36
+
37
+ /**
38
+ * 设置 Playwright 客户端
39
+ */
40
+ setPlaywrightClient(pw: PwAi): void {
41
+ this.pw = pw;
42
+ }
43
+
44
+ /**
45
+ * 设置 CDP URL
46
+ */
47
+ setCdpUrl(cdpUrl: string): void {
48
+ this.cdpUrl = cdpUrl;
49
+ }
50
+
51
+ /**
52
+ * 执行单个操作
53
+ */
54
+ async execute(
55
+ action: AtomicAction,
56
+ variables?: VariableMap
57
+ ): Promise<ExecutionResult> {
58
+ const startTime = Date.now();
59
+
60
+ try {
61
+ // 应用变量
62
+ const resolvedAction = this.applyVariables(action, variables);
63
+
64
+ // 根据操作类型执行
65
+ switch (resolvedAction.type) {
66
+ case "navigate":
67
+ await this.executeNavigate(resolvedAction as NavigateAction);
68
+ break;
69
+
70
+ case "click":
71
+ await this.executeClick(resolvedAction as ClickAction);
72
+ break;
73
+
74
+ case "type":
75
+ await this.executeType(resolvedAction as TypeAction);
76
+ break;
77
+
78
+ case "press":
79
+ await this.executePress(resolvedAction as PressAction);
80
+ break;
81
+
82
+ case "hover":
83
+ await this.executeHover(resolvedAction as HoverAction);
84
+ break;
85
+
86
+ case "screenshot":
87
+ await this.executeScreenshot(resolvedAction as ScreenshotAction);
88
+ break;
89
+
90
+ case "scroll":
91
+ case "wait":
92
+ case "select":
93
+ case "focus":
94
+ case "drag":
95
+ case "upload":
96
+ await this.executeEvaluate(resolvedAction as EvaluateAction);
97
+ break;
98
+
99
+ case "composite":
100
+ // 复合操作暂不实现
101
+ throw new Error("Composite actions not yet implemented");
102
+
103
+ default:
104
+ throw new Error(`Unsupported action type: ${action.type}`);
105
+ }
106
+
107
+ return {
108
+ action: action.type,
109
+ success: true,
110
+ duration: Date.now() - startTime,
111
+ };
112
+ } catch (error) {
113
+ return {
114
+ action: action.type,
115
+ success: false,
116
+ error: error instanceof Error ? error.message : String(error),
117
+ duration: Date.now() - startTime,
118
+ };
119
+ }
120
+ }
121
+
122
+ /**
123
+ * 批量执行操作
124
+ */
125
+ async executeBatch(
126
+ actions: AtomicAction[],
127
+ variables?: VariableMap,
128
+ options?: {
129
+ fromIndex?: number;
130
+ toIndex?: number;
131
+ atomic?: boolean; // 是否原子执行(全部成功或全部失败)
132
+ }
133
+ ): Promise<ExecutionResult[]> {
134
+ const from = options?.fromIndex ?? 0;
135
+ const to =
136
+ options?.toIndex !== undefined ? options.toIndex + 1 : actions.length;
137
+ const atomic = options?.atomic ?? false;
138
+
139
+ const results: ExecutionResult[] = [];
140
+ const actionsToExecute = actions.slice(from, to);
141
+
142
+ for (const action of actionsToExecute) {
143
+ const result = await this.execute(action, variables);
144
+ results.push(result);
145
+
146
+ // 原子执行:失败则停止
147
+ if (atomic && !result.success) {
148
+ break;
149
+ }
150
+ }
151
+
152
+ return results;
153
+ }
154
+
155
+ // -------------------------------------------------------------------------
156
+ // 具体操作实现
157
+ // -------------------------------------------------------------------------
158
+
159
+ private async executeNavigate(action: NavigateAction): Promise<void> {
160
+ if (!this.pw) {
161
+ throw new Error("Playwright client not set");
162
+ }
163
+
164
+ await this.pw.navigateViaPlaywright({
165
+ cdpUrl: action.cdpUrl || this.cdpUrl,
166
+ targetId: action.targetId,
167
+ url: action.url,
168
+ waitUntil: action.navigationPolicy?.waitUntil || "domcontentloaded",
169
+ timeout: action.navigationPolicy?.timeout,
170
+ });
171
+ }
172
+
173
+ private async executeClick(action: ClickAction): Promise<void> {
174
+ if (!this.pw) {
175
+ throw new Error("Playwright client not set");
176
+ }
177
+
178
+ await this.pw.clickViaPlaywright({
179
+ cdpUrl: action.cdpUrl || this.cdpUrl,
180
+ targetId: action.targetId,
181
+ ref: action.ref,
182
+ doubleClick: action.doubleClick,
183
+ button: action.button || "left",
184
+ modifiers: action.modifiers,
185
+ timeoutMs: action.timeoutMs,
186
+ });
187
+ }
188
+
189
+ private async executeType(action: TypeAction): Promise<void> {
190
+ if (!this.pw) {
191
+ throw new Error("Playwright client not set");
192
+ }
193
+
194
+ await this.pw.typeViaPlaywright({
195
+ cdpUrl: action.cdpUrl || this.cdpUrl,
196
+ targetId: action.targetId,
197
+ ref: action.ref,
198
+ text: action.text,
199
+ submit: action.submit,
200
+ slowly: action.slowly,
201
+ timeoutMs: action.timeoutMs,
202
+ });
203
+ }
204
+
205
+ private async executePress(action: PressAction): Promise<void> {
206
+ if (!this.pw) {
207
+ throw new Error("Playwright client not set");
208
+ }
209
+
210
+ await this.pw.pressKeyViaPlaywright({
211
+ cdpUrl: action.cdpUrl || this.cdpUrl,
212
+ targetId: action.targetId,
213
+ key: action.key,
214
+ delayMs: action.delayMs,
215
+ });
216
+ }
217
+
218
+ private async executeHover(action: HoverAction): Promise<void> {
219
+ if (!this.pw) {
220
+ throw new Error("Playwright client not set");
221
+ }
222
+
223
+ await this.pw.hoverViaPlaywright({
224
+ cdpUrl: action.cdpUrl || this.cdpUrl,
225
+ targetId: action.targetId,
226
+ ref: action.ref,
227
+ timeoutMs: action.timeoutMs,
228
+ });
229
+ }
230
+
231
+ private async executeScreenshot(action: ScreenshotAction): Promise<string> {
232
+ if (!this.pw) {
233
+ throw new Error("Playwright client not set");
234
+ }
235
+
236
+ return this.pw.screenshotViaPlaywright({
237
+ cdpUrl: action.cdpUrl || this.cdpUrl,
238
+ targetId: action.targetId,
239
+ });
240
+ }
241
+
242
+ private async executeEvaluate(action: EvaluateAction): Promise<any> {
243
+ if (!this.pw) {
244
+ throw new Error("Playwright client not set");
245
+ }
246
+
247
+ return this.pw.evaluateViaPlaywright({
248
+ cdpUrl: action.cdpUrl || this.cdpUrl,
249
+ targetId: action.targetId,
250
+ code: action.evaluate.code,
251
+ args: action.evaluate.args,
252
+ });
253
+ }
254
+
255
+ // -------------------------------------------------------------------------
256
+ // 变量替换
257
+ // -------------------------------------------------------------------------
258
+
259
+ /**
260
+ * 应用变量到操作
261
+ */
262
+ private applyVariables(
263
+ action: AtomicAction,
264
+ variables?: VariableMap
265
+ ): AtomicAction {
266
+ if (!variables) {
267
+ return action;
268
+ }
269
+
270
+ // 检查是否需要变量替换
271
+ if (!action.variable) {
272
+ return action;
273
+ }
274
+
275
+ // 获取变量值
276
+ const value = variables[action.variable];
277
+ if (value === undefined) {
278
+ // 变量未定义,返回原操作
279
+ return action;
280
+ }
281
+
282
+ // 根据操作类型设置正确的参数
283
+ const actionType = action.type;
284
+ if (actionType === "type") {
285
+ return { ...action, text: value as string };
286
+ } else if (actionType === "press") {
287
+ return { ...action, key: value as string };
288
+ } else if (actionType === "navigate") {
289
+ return { ...action, url: value as string };
290
+ } else if (actionType === "scroll" || actionType === "wait" ||
291
+ actionType === "select" || actionType === "focus" ||
292
+ actionType === "drag" || actionType === "upload") {
293
+ // evaluate 操作替换模板变量
294
+ return this.replaceTemplateVariables(action, variables);
295
+ } else {
296
+ // 其他操作替换所有字符串参数中的模板变量
297
+ return this.replaceTemplateVariables(action, variables);
298
+ }
299
+ }
300
+
301
+ /**
302
+ * 替换操作中的模板变量
303
+ */
304
+ private replaceTemplateVariables(
305
+ action: AtomicAction,
306
+ variables: VariableMap
307
+ ): AtomicAction {
308
+ const result: any = { ...action };
309
+
310
+ // 替换字符串类型参数中的 ${variable}
311
+ for (const key of Object.keys(action)) {
312
+ const value = (action as any)[key];
313
+ if (typeof value === "string") {
314
+ result[key] = value.replace(/\$\{([^}]+)\}/g, (match, varName) => {
315
+ const replacement = variables[varName];
316
+ return replacement !== undefined ? replacement : match;
317
+ });
318
+ }
319
+ }
320
+
321
+ // 特殊处理 evaluate.code(如果存在)
322
+ if (result.evaluate?.code && typeof result.evaluate.code === "string") {
323
+ result.evaluate.code = result.evaluate.code.replace(
324
+ /\$\{([^}]+)\}/g,
325
+ (match: string, varName: string) => {
326
+ const replacement = variables[varName];
327
+ return replacement !== undefined ? replacement : match;
328
+ }
329
+ );
330
+ }
331
+
332
+ return result;
333
+ }
334
+
335
+ // -------------------------------------------------------------------------
336
+ // 格式化输出
337
+ // -------------------------------------------------------------------------
338
+
339
+ /**
340
+ * 格式化操作列表
341
+ */
342
+ formatActions(actions: AtomicAction[]): string {
343
+ return actions
344
+ .map((action, i) => this.formatAction(action, i + 1))
345
+ .join("\n");
346
+ }
347
+
348
+ /**
349
+ * 格式化单个操作
350
+ */
351
+ private formatAction(action: AtomicAction, index: number): string {
352
+ switch (action.type) {
353
+ case "navigate":
354
+ return `${index}. 导航到:${(action as NavigateAction).url}`;
355
+
356
+ case "screenshot":
357
+ return `${index}. 截图`;
358
+
359
+ case "click":
360
+ const btn = (action as ClickAction).button || "left";
361
+ const dbl = (action as ClickAction).doubleClick ? "双击" : "";
362
+ const mod = (action as ClickAction).modifiers?.length
363
+ ? `+${(action as ClickAction).modifiers!.join("+")}`
364
+ : "";
365
+ return `${index}. ${dbl}点击(${btn}${mod}):${action.ref}`;
366
+
367
+ case "type":
368
+ const txt = (action as TypeAction).text
369
+ ? `="${(action as TypeAction).text}"`
370
+ : "";
371
+ const sub = (action as TypeAction).submit ? " [提交]" : "";
372
+ const slow = (action as TypeAction).slowly ? " [慢速]" : "";
373
+ return `${index}. 输入:${action.ref}${txt}${sub}${slow}`;
374
+
375
+ case "press":
376
+ const delay = (action as PressAction).delayMs
377
+ ? `(${(action as PressAction).delayMs}ms)`
378
+ : "";
379
+ return `${index}. 按键:${(action as PressAction).key}${delay}${
380
+ action.ref ? ` @ ${action.ref}` : ""
381
+ }`;
382
+
383
+ case "hover":
384
+ return `${index}. 悬停:${action.ref}`;
385
+
386
+ case "scroll":
387
+ return `${index}. 滚动:${
388
+ (action as EvaluateAction).evaluate?.code || "向下滚动"
389
+ }`;
390
+
391
+ case "wait":
392
+ const waitDelay = action.delay ? `(${action.delay}ms)` : "";
393
+ return `${index}. 等待:${
394
+ (action as EvaluateAction).evaluate?.code || "默认等待"
395
+ }${waitDelay}`;
396
+
397
+ case "select":
398
+ return `${index}. 选择:${action.ref}`;
399
+
400
+ case "focus":
401
+ return `${index}. 聚焦:${action.ref}`;
402
+
403
+ case "drag":
404
+ return `${index}. 拖拽:${action.ref}`;
405
+
406
+ case "upload":
407
+ return `${index}. 上传:${action.ref}`;
408
+
409
+ default:
410
+ return `${index}. ${action.type}:${action.ref || ""}`;
411
+ }
412
+ }
413
+
414
+ /**
415
+ * 格式化执行结果
416
+ */
417
+ formatExecutionResults(results: ExecutionResult[]): string {
418
+ return results
419
+ .map((r, i) => {
420
+ const icon = r.success ? "✅" : "❌";
421
+ const time = r.duration ? `(${r.duration}ms)` : "";
422
+ return `${icon} 操作 ${i + 1}: ${r.action} ${time}${
423
+ r.error ? ` - ${r.error}` : ""
424
+ }`;
425
+ })
426
+ .join("\n");
427
+ }
428
+ }
429
+
430
+ // ============================================================================
431
+ // 单例
432
+ // ============================================================================
433
+
434
+ let actionsExecutorInstance: ActionsExecutor | null = null;
435
+
436
+ export function getActionsExecutor(cdpUrl?: string): ActionsExecutor {
437
+ if (!actionsExecutorInstance) {
438
+ actionsExecutorInstance = new ActionsExecutor(cdpUrl);
439
+ }
440
+ return actionsExecutorInstance;
441
+ }