page-action-cache 1.0.1 → 1.0.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "page-action-cache",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Page action caching extension for OpenClaw - caches LLM browser operations to avoid re-analyzing the same DOM",
5
5
  "keywords": [
6
6
  "automation",
@@ -17,24 +17,22 @@
17
17
  "directory": "extensions/page-action-cache"
18
18
  },
19
19
  "files": [
20
- "index.ts",
21
20
  "dist/",
22
- "src/",
23
21
  "skills/",
24
22
  "docs/",
25
23
  "package.json",
26
24
  "openclaw.plugin.json"
27
25
  ],
28
26
  "type": "module",
29
- "main": "src/hooks-entry.js",
27
+ "main": "dist/hooks-entry.js",
30
28
  "exports": {
31
29
  ".": {
32
30
  "types": "./src/index.d.ts",
33
- "default": "./src/index.js"
31
+ "default": "./dist/index.js"
34
32
  },
35
33
  "./hooks-entry": {
36
- "types": "./src/hooks-entry.d.ts",
37
- "default": "./src/hooks-entry.js"
34
+ "types": "./dist/hooks-entry.d.ts",
35
+ "default": "./dist/hooks-entry.js"
38
36
  }
39
37
  },
40
38
  "scripts": {
@@ -70,7 +68,7 @@
70
68
  },
71
69
  "openclaw": {
72
70
  "extensions": [
73
- "./src/hooks-entry.js"
71
+ "./dist/hooks-entry.js"
74
72
  ]
75
73
  }
76
- }
74
+ }
package/index.ts DELETED
@@ -1,118 +0,0 @@
1
- /**
2
- * Page Action Cache Extension
3
- * 页面操作缓存扩展入口
4
- *
5
- * 功能:
6
- * - 缓存 LLM 生成的浏览器操作序列
7
- * - 避免重复分析相同页面
8
- * - 大幅降低 token 消耗和操作延迟
9
- *
10
- * 使用方法:
11
- * 1. 安装扩展:npm install @openclaw/page-action-cache
12
- * 2. 在配置中启用扩展
13
- * 3. LLM 会自动使用缓存的操作序列
14
- */
15
-
16
- import type { CacheConfig } from "./src/types.js";
17
- import { registerPageActionCacheHooks } from "./src/hooks.js";
18
- import { getToolDefinitions } from "./src/tools.js";
19
-
20
- // ============================================================================
21
- // 扩展元数据
22
- // ============================================================================
23
-
24
- export const metadata = {
25
- name: "Page Action Cache",
26
- version: "1.0.0",
27
- description:
28
- "缓存 LLM 浏览器操作序列,避免重复分析相同页面,大幅降低 token 消耗",
29
- author: "OpenClaw",
30
- license: "MIT",
31
- };
32
-
33
- // ============================================================================
34
- // 默认配置
35
- // ============================================================================
36
-
37
- const DEFAULT_CONFIG: CacheConfig = {
38
- enabled: true,
39
- autoUseCache: true,
40
- scenarioRecognitionEnabled: true,
41
- llmClassificationThreshold: 70,
42
- cacheLevelStrategy: "auto",
43
- defaultCacheLevel: "L3",
44
- pageChangeDetectionEnabled: true,
45
- changeInvalidationThreshold: 80,
46
- invalidationStrategy: "soft",
47
- maxVersionsPerEntry: 3,
48
- variableExtractionEnabled: true,
49
- allowUserConfirmVariables: false,
50
- encryptSensitiveCache: false,
51
- accessControlEnabled: false,
52
- allowedUserIds: [],
53
- logSanitizationEnabled: true,
54
- showCacheStatusToUser: true,
55
- enableUserCacheConfirmation: false,
56
- enableUserForcedRefresh: true,
57
- enableUserCacheErrorReport: true,
58
- trackExecutionStats: true,
59
- statsUpdateInterval: 60,
60
- };
61
-
62
- // ============================================================================
63
- // 扩展初始化
64
- // ============================================================================
65
-
66
- /**
67
- * 扩展初始化函数
68
- */
69
- export function activate(api: any, config?: Partial<CacheConfig>): void {
70
- console.log(`[PageActionCache] Initializing v${metadata.version}...`);
71
-
72
- const finalConfig = { ...DEFAULT_CONFIG, ...config };
73
-
74
- // 注册 Hooks
75
- registerPageActionCacheHooks(api, finalConfig);
76
-
77
- // 注册自定义工具
78
- const toolDefinitions = getToolDefinitions();
79
- for (const toolDef of toolDefinitions) {
80
- api.registerTool?.(toolDef);
81
- console.log(`[PageActionCache] Registered tool: ${toolDef.name}`);
82
- }
83
-
84
- console.log("[PageActionCache] Extension activated successfully");
85
- }
86
-
87
- /**
88
- * 扩展停用函数
89
- */
90
- export function deactivate(): void {
91
- console.log("[PageActionCache] Deactivating...");
92
- // 清理资源
93
- console.log("[PageActionCache] Extension deactivated");
94
- }
95
-
96
- // ============================================================================
97
- // 导出(用于测试和调试)
98
- // ============================================================================
99
-
100
- export * from "./src/types.js";
101
- export { getCacheStore } from "./src/cache-store.js";
102
- export { getScenarioRecognizer } from "./src/scenario-recognizer.js";
103
- export { getVariableResolver } from "./src/variable-resolver.js";
104
- export { getCacheInvalidator } from "./src/cache-invalidator.js";
105
- export { getSecurityPolicy } from "./src/security-policy.js";
106
- export { getUXEnhancer } from "./src/ux-enhancer.js";
107
- export { getActionsExecutor } from "./src/actions-executor.js";
108
- export { getCacheStrategyFactory } from "./src/cache-strategy.js";
109
-
110
- // ============================================================================
111
- // 默认导出(OpenClaw 扩展系统使用)
112
- // ============================================================================
113
-
114
- export default {
115
- metadata,
116
- activate,
117
- deactivate,
118
- };
@@ -1,441 +0,0 @@
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
- }