page-action-cache 1.0.4 → 2.0.0

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 (89) hide show
  1. package/README.md +399 -0
  2. package/dist/browser-action-executor.d.ts +87 -0
  3. package/dist/browser-action-executor.d.ts.map +1 -0
  4. package/dist/browser-action-executor.js +283 -0
  5. package/dist/browser-action-executor.js.map +1 -0
  6. package/dist/cache-invalidation.d.ts +128 -0
  7. package/dist/cache-invalidation.d.ts.map +1 -0
  8. package/dist/cache-invalidation.js +262 -0
  9. package/dist/cache-invalidation.js.map +1 -0
  10. package/dist/cache-manager.d.ts +83 -0
  11. package/dist/cache-manager.d.ts.map +1 -0
  12. package/dist/cache-manager.js +184 -0
  13. package/dist/cache-manager.js.map +1 -0
  14. package/dist/index.d.ts +7 -21
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +249 -31
  17. package/dist/index.js.map +1 -1
  18. package/dist/multi-level-cache.d.ts +127 -0
  19. package/dist/multi-level-cache.d.ts.map +1 -0
  20. package/dist/multi-level-cache.js +362 -0
  21. package/dist/multi-level-cache.js.map +1 -0
  22. package/dist/scenario-recognizer.d.ts +17 -27
  23. package/dist/scenario-recognizer.d.ts.map +1 -1
  24. package/dist/scenario-recognizer.js +63 -183
  25. package/dist/scenario-recognizer.js.map +1 -1
  26. package/dist/types.d.ts +38 -312
  27. package/dist/types.d.ts.map +1 -1
  28. package/dist/types.js +2 -4
  29. package/dist/types.js.map +1 -1
  30. package/dist/variable-extractor.d.ts +56 -0
  31. package/dist/variable-extractor.d.ts.map +1 -0
  32. package/dist/variable-extractor.js +159 -0
  33. package/dist/variable-extractor.js.map +1 -0
  34. package/openclaw.plugin.json +12 -190
  35. package/package.json +29 -45
  36. package/src/browser-action-executor.ts +337 -0
  37. package/src/cache-invalidation.ts +338 -0
  38. package/src/cache-manager.ts +211 -0
  39. package/src/index.ts +306 -0
  40. package/src/multi-level-cache.ts +478 -0
  41. package/src/scenario-recognizer.ts +121 -0
  42. package/src/types-mock.d.ts +18 -0
  43. package/src/types.ts +66 -0
  44. package/src/variable-extractor.ts +204 -0
  45. package/dist/actions-executor.d.ts +0 -62
  46. package/dist/actions-executor.d.ts.map +0 -1
  47. package/dist/actions-executor.js +0 -339
  48. package/dist/actions-executor.js.map +0 -1
  49. package/dist/cache-invalidator.d.ts +0 -70
  50. package/dist/cache-invalidator.d.ts.map +0 -1
  51. package/dist/cache-invalidator.js +0 -212
  52. package/dist/cache-invalidator.js.map +0 -1
  53. package/dist/cache-store.d.ts +0 -80
  54. package/dist/cache-store.d.ts.map +0 -1
  55. package/dist/cache-store.js +0 -361
  56. package/dist/cache-store.js.map +0 -1
  57. package/dist/cache-strategy.d.ts +0 -65
  58. package/dist/cache-strategy.d.ts.map +0 -1
  59. package/dist/cache-strategy.js +0 -237
  60. package/dist/cache-strategy.js.map +0 -1
  61. package/dist/hooks-entry.d.ts +0 -29
  62. package/dist/hooks-entry.d.ts.map +0 -1
  63. package/dist/hooks-entry.js +0 -83
  64. package/dist/hooks-entry.js.map +0 -1
  65. package/dist/hooks.d.ts +0 -10
  66. package/dist/hooks.d.ts.map +0 -1
  67. package/dist/hooks.js +0 -277
  68. package/dist/hooks.js.map +0 -1
  69. package/dist/security-policy.d.ts +0 -62
  70. package/dist/security-policy.d.ts.map +0 -1
  71. package/dist/security-policy.js +0 -219
  72. package/dist/security-policy.js.map +0 -1
  73. package/dist/tools.d.ts +0 -209
  74. package/dist/tools.d.ts.map +0 -1
  75. package/dist/tools.js +0 -383
  76. package/dist/tools.js.map +0 -1
  77. package/dist/ux-enhancer.d.ts +0 -60
  78. package/dist/ux-enhancer.d.ts.map +0 -1
  79. package/dist/ux-enhancer.js +0 -218
  80. package/dist/ux-enhancer.js.map +0 -1
  81. package/dist/variable-resolver.d.ts +0 -28
  82. package/dist/variable-resolver.d.ts.map +0 -1
  83. package/dist/variable-resolver.js +0 -201
  84. package/dist/variable-resolver.js.map +0 -1
  85. package/docs/API.md +0 -555
  86. package/docs/IMPLEMENTATION.md +0 -1792
  87. package/docs/INTEGRATION.md +0 -387
  88. package/docs/README.md +0 -183
  89. package/skills/page-action-cache/SKILL.md +0 -216
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Cache Manager - Page Action Cache Core
3
+ * 缓存管理器 - 页面操作缓存管理器
4
+ */
5
+
6
+ import type { CacheEntry, CacheStats, ScenarioType } from './types.js';
7
+ import { clearInterval, setInterval } from 'node:timers';
8
+
9
+ /**
10
+ * 缓存管理器
11
+ */
12
+ class CacheManager {
13
+ private cache: Map<string, CacheEntry> = new Map();
14
+ private stats: CacheStats = {
15
+ totalEntries: 0,
16
+ totalActions: 0,
17
+ hitRate: 0,
18
+ savedActions: 0,
19
+ savedTime: 0
20
+ };
21
+ private autoSaveInterval: NodeJS.Timeout | null = null;
22
+ private totalQueries: number = 0;
23
+ private totalHits: number = 0;
24
+
25
+ constructor() {
26
+ this.loadCache();
27
+ this.startAutoSave();
28
+ }
29
+
30
+ /**
31
+ * 加载缓存
32
+ */
33
+ private loadCache(): void {
34
+ try {
35
+ const cacheFile = process.env.PAGE_ACTION_CACHE_FILE || '/tmp/page-action-cache.json';
36
+ const fs = require('fs');
37
+ if (fs.existsSync(cacheFile)) {
38
+ const data = fs.readFileSync(cacheFile, 'utf8');
39
+ this.cache = new Map(JSON.parse(data));
40
+ }
41
+ console.log(`[CacheManager] Loaded ${this.cache.size} cache entries`);
42
+ } catch (error: any) {
43
+ console.error(`[CacheManager] Failed to load cache: ${error.message}`);
44
+ }
45
+ }
46
+
47
+ /**
48
+ * 保存缓存
49
+ */
50
+ private saveCache(): void {
51
+ try {
52
+ const cacheFile = process.env.PAGE_ACTION_CACHE_FILE || '/tmp/page-action-cache.json';
53
+ const fs = require('fs');
54
+ const data = Array.from(this.cache.entries());
55
+ fs.writeFileSync(cacheFile, JSON.stringify(data, null, 2));
56
+ console.log(`[CacheManager] Saved ${data.length} cache entries`);
57
+ } catch (error: any) {
58
+ console.error(`[CacheManager] Failed to save cache: ${error.message}`);
59
+ }
60
+ }
61
+
62
+ /**
63
+ * 自动保存
64
+ */
65
+ private startAutoSave(): void {
66
+ setInterval(() => this.saveCache(), 60000); // 每分钟保存一次
67
+ }
68
+
69
+ /**
70
+ * 查询缓存
71
+ */
72
+ query(url: string, viewport?: string): CacheEntry | null {
73
+ const key = this.getCacheKey(url, viewport);
74
+
75
+ if (!key) {
76
+ return null;
77
+ }
78
+
79
+ this.totalQueries++;
80
+ const entry = this.cache.get(key);
81
+ if (entry) {
82
+ this.totalHits++;
83
+ return entry;
84
+ }
85
+ return null;
86
+ }
87
+
88
+ /**
89
+ * 更新缓存状态
90
+ */
91
+ updateCacheStatus(id: string, success: boolean): void {
92
+ const entry = this.cache.get(id);
93
+ if (!entry) {
94
+ return;
95
+ }
96
+
97
+ // CacheEntry doesn't have successCount/failCount, so we skip this
98
+ // This is a compatibility method for older API
99
+ console.log(`[CacheManager] Cache status update requested for ${id}: ${success}`);
100
+ }
101
+
102
+ /**
103
+ * 生成缓存键
104
+ */
105
+ private getCacheKey(url: string, viewport?: string | { width?: number; height?: number }): string {
106
+ let viewportStr = 'default';
107
+ if (viewport) {
108
+ if (typeof viewport === 'string') {
109
+ viewportStr = viewport;
110
+ } else if (viewport.width && viewport.height) {
111
+ viewportStr = `${viewport.width}x${viewport.height}`;
112
+ }
113
+ }
114
+ return `${url}:${viewportStr}`;
115
+ }
116
+
117
+ /**
118
+ * 添加缓存条目
119
+ */
120
+ addCache(params: {
121
+ url: string;
122
+ actions: Array<{ type: 'navigate' | 'click' | 'screenshot' | 'type' | 'script'; params?: any; successCount?: number; failCount?: number }>;
123
+ scenario?: ScenarioType;
124
+ viewport?: string | { width?: number; height?: number };
125
+ }): string {
126
+ const key = this.getCacheKey(params.url, params.viewport);
127
+ const entry: CacheEntry = {
128
+ id: Date.now().toString(),
129
+ url: params.url,
130
+ viewport: typeof params.viewport === 'string' ? params.viewport : (params.viewport ? `${params.viewport.width}x${params.viewport.height}` : '1920x1080'),
131
+ actions: params.actions.map(action => ({
132
+ ...action,
133
+ successCount: action.successCount || 0,
134
+ failCount: action.failCount || 0
135
+ })),
136
+ scenario: params.scenario || 'unknown',
137
+ timestamp: Date.now(),
138
+ hitRate: 0,
139
+ savedActions: 0,
140
+ savedTime: 0
141
+ };
142
+
143
+ this.cache.set(key, entry);
144
+ this.stats.totalEntries = this.cache.size;
145
+ this.stats.totalActions += params.actions.length;
146
+
147
+ return entry.id;
148
+ }
149
+
150
+ /**
151
+ * 获取缓存
152
+ */
153
+ getCache(id: string): CacheEntry | null {
154
+ return this.cache.get(id) || null;
155
+ }
156
+
157
+ /**
158
+ * 清空缓存
159
+ */
160
+ clearCache(): void {
161
+ this.cache.clear();
162
+ this.stats = {
163
+ totalEntries: 0,
164
+ totalActions: 0,
165
+ hitRate: 0,
166
+ savedActions: 0,
167
+ savedTime: 0
168
+ };
169
+ this.totalQueries = 0;
170
+ this.totalHits = 0;
171
+ console.log('[CacheManager] Cache cleared');
172
+ }
173
+
174
+ /**
175
+ * 获取统计
176
+ */
177
+ getStats(): CacheStats & {
178
+ totalQueries: number;
179
+ totalHits: number;
180
+ savedTimeFormatted: string;
181
+ } {
182
+ const hitRate = this.totalQueries > 0 ? (this.totalHits / this.totalQueries) * 100 : 0;
183
+ const savedTimePerMin = this.stats.savedTime > 0 ? this.stats.savedTime / 60000 : 0; // 转换为分钟
184
+
185
+ return {
186
+ ...this.stats,
187
+ hitRate: Math.round(hitRate * 100) / 100,
188
+ totalQueries: this.totalQueries,
189
+ totalHits: this.totalHits,
190
+ savedTimeFormatted: savedTimePerMin > 0
191
+ ? `${savedTimePerMin > 60 ? Math.floor(savedTimePerMin) : savedTimePerMin.toFixed(2)} 分钟`
192
+ : '0 分钟'
193
+ };
194
+ }
195
+
196
+ /**
197
+ * 销毁
198
+ */
199
+ destroy(): void {
200
+ this.cache.clear();
201
+ if (this.autoSaveInterval) {
202
+ clearInterval(this.autoSaveInterval);
203
+ this.autoSaveInterval = null;
204
+ }
205
+ }
206
+ }
207
+
208
+ /**
209
+ * 单例导出
210
+ */
211
+ export const cacheManager = new CacheManager();
package/src/index.ts ADDED
@@ -0,0 +1,306 @@
1
+ /**
2
+ * Page Action Cache - OpenClaw Extension
3
+ * 页面操作缓存扩展 - OpenClaw 扩展
4
+ */
5
+
6
+ import type { OpenClawPluginApi } from './types-mock.js';
7
+ import { scenarioRecognizer } from './scenario-recognizer.js';
8
+ import { multiLevelCache } from './multi-level-cache.js';
9
+ import { cacheInvalidation } from './cache-invalidation.js';
10
+ import { browserActionExecutor } from './browser-action-executor.js';
11
+
12
+ /**
13
+ * 配置接口
14
+ */
15
+ interface PageActionCacheConfig {
16
+ enabled: boolean;
17
+ autoUseCache?: boolean;
18
+ scenarioRecognitionEnabled?: boolean;
19
+ llmClassificationThreshold?: number;
20
+ cacheLevelStrategy?: 'auto' | 'l3-only' | 'l2-only' | 'l1-only';
21
+ defaultCacheLevel?: 'L3' | 'L2' | 'L1';
22
+ pageChangeDetectionEnabled?: boolean;
23
+ changeInvalidationThreshold?: number;
24
+ invalidationStrategy?: 'soft' | 'hard';
25
+ variableExtractionEnabled?: boolean;
26
+ allowUserConfirmVariables?: boolean;
27
+ allowUserForcedRefresh?: boolean;
28
+ enableUserCacheErrorReport?: boolean;
29
+ trackExecutionStats?: boolean;
30
+ statsUpdateInterval?: number;
31
+ }
32
+
33
+ /**
34
+ * 主扩展入口
35
+ */
36
+ export function register(api: OpenClawPluginApi): void {
37
+ console.log('[PageActionCache] Initializing extension...');
38
+
39
+ // 获取插件配置
40
+ const config = (api.getConfig?.() || {}) as PageActionCacheConfig;
41
+ const enabled = config.enabled !== false;
42
+
43
+ if (!enabled) {
44
+ console.log('[PageActionCache] Extension disabled by configuration');
45
+ return;
46
+ }
47
+
48
+ // 配置扩展
49
+ if (config.scenarioRecognitionEnabled !== false) {
50
+ console.log('[PageActionCache] Scenario recognition enabled');
51
+ }
52
+
53
+ if (config.pageChangeDetectionEnabled !== false) {
54
+ cacheInvalidation.setStrategy(config.invalidationStrategy || 'soft');
55
+ cacheInvalidation.setChangeThreshold(config.changeInvalidationThreshold || 0.5);
56
+ console.log('[PageActionCache] Cache invalidation enabled');
57
+ }
58
+
59
+ if (config.cacheLevelStrategy) {
60
+ multiLevelCache.setStrategy(config.cacheLevelStrategy);
61
+ console.log(`[PageActionCache] Cache level strategy: ${config.cacheLevelStrategy}`);
62
+ }
63
+
64
+ if (config.llmClassificationThreshold) {
65
+ multiLevelCache.setLLMThreshold(config.llmClassificationThreshold);
66
+ }
67
+
68
+ // 注册缓存执行工具
69
+ api.registerTool({
70
+ name: 'browser_cache_execute',
71
+ description: '执行缓存的页面操作序列,支持自动缓存匹配、场景识别和变量提取',
72
+ async execute(ctx: any) {
73
+ try {
74
+ console.log('[PageActionCache] browser_cache_execute called');
75
+
76
+ const enabled = ctx.config?.plugins?.entries?.['page-action-cache']?.config?.enabled ?? true;
77
+
78
+ if (!enabled) {
79
+ return {
80
+ type: 'text',
81
+ content: 'Page Action Cache 功能已禁用'
82
+ };
83
+ }
84
+
85
+ const request = ctx.params as any;
86
+ const url = request?.url;
87
+
88
+ if (!url) {
89
+ return {
90
+ type: 'text',
91
+ content: 'Missing required parameter: url'
92
+ };
93
+ }
94
+
95
+ // 尝试从缓存中查找
96
+ const cachedEntry = multiLevelCache.queryCache(url, request.viewport || '1920x1080');
97
+
98
+ if (cachedEntry && !request.forceRefresh) {
99
+ console.log('[PageActionCache] Cache hit found');
100
+ return await executeFromCache(cachedEntry);
101
+ }
102
+
103
+ // 缓存未命中或强制刷新,创建新缓存并执行
104
+ console.log('[PageActionCache] Cache miss or forced refresh, creating new entry');
105
+
106
+ // 识别场景
107
+ let scenario = request.scenario;
108
+ if (!scenario) {
109
+ scenario = scenarioRecognizer.recognizeByUrl(url);
110
+ }
111
+
112
+ // 创建缓存条目
113
+ const actions = request.actions || [];
114
+ const cacheActions = actions.map((action: any) => ({
115
+ type: action.type,
116
+ params: action.params,
117
+ successCount: 0,
118
+ failCount: 0
119
+ }));
120
+
121
+ const cacheId = multiLevelCache.addCache(
122
+ url,
123
+ request.viewport || '1920x1080',
124
+ cacheActions,
125
+ scenario,
126
+ request.llmClassificationScore
127
+ );
128
+
129
+ // 执行操作
130
+ const entry = {
131
+ id: cacheId,
132
+ url,
133
+ viewport: request.viewport || '1920x1080',
134
+ actions: cacheActions,
135
+ scenario,
136
+ timestamp: Date.now(),
137
+ hitRate: 0,
138
+ savedActions: 0,
139
+ savedTime: 0,
140
+ level: 'L1' as const,
141
+ createdAt: Date.now(),
142
+ lastAccessed: Date.now(),
143
+ accessCount: 0,
144
+ expiresAt: Date.now() + 86400000 // 24小时
145
+ };
146
+
147
+ const result = await browserActionExecutor.executeCacheEntry(entry);
148
+
149
+ return {
150
+ type: 'text',
151
+ content: `## Cache Execution Result
152
+
153
+ ${result.message}
154
+
155
+ - Executed Actions: ${result.executedActions}
156
+ - Skipped Actions: ${result.skippedActions}
157
+ - Failed Actions: ${result.failedActions}
158
+ - Saved Time: ${result.savedTime}ms
159
+
160
+ Cache ID: ${cacheId}
161
+ URL: ${url}
162
+ Scenario: ${scenario}
163
+ LLM Classification Score: ${request.llmClassificationScore || 'N/A'}`
164
+ };
165
+ } catch (error: any) {
166
+ console.error('[PageActionCache] Error:', error);
167
+ return {
168
+ type: 'text',
169
+ content: `执行失败: ${error.message}`
170
+ };
171
+ }
172
+ }
173
+ });
174
+
175
+ // 注册缓存统计工具
176
+ api.registerTool({
177
+ name: 'browser_cache_stats',
178
+ description: '查看页面操作缓存的统计信息',
179
+ async execute(_ctx: any) {
180
+ try {
181
+ const stats = multiLevelCache.getStats();
182
+ const invalidationStats = cacheInvalidation.getInvalidationStats();
183
+
184
+ return {
185
+ type: 'text',
186
+ content: `## Page Action Cache Statistics
187
+
188
+ ### Overall Performance
189
+ - Total Cache Entries: ${stats.totalEntries}
190
+ - Total Cache Hits: ${stats.totalHits}
191
+ - Cache Hit Rate: ${stats.hitRate.toFixed(2)}%
192
+
193
+ ### Multi-Level Cache Distribution
194
+ ${Object.entries(stats.levelStats).map(([level, data]) => `
195
+ #### ${level} Cache
196
+ - Size: ${data.size}/${data.maxCapacity} (${(data.usageRate * 100).toFixed(1)}% full)
197
+ - Average Access Count: ${data.avgAccessCount.toFixed(1)}
198
+ `).join('')}
199
+
200
+ ### Cache Invalidation
201
+ - Total Snapshots: ${invalidationStats.totalSnapshots}
202
+ - Active Snapshots: ${invalidationStats.activeSnapshots}
203
+ - Invalidation Rate: ${(invalidationStats.invalidationRate * 100).toFixed(2)}%`
204
+ };
205
+ } catch (error: any) {
206
+ return {
207
+ type: 'text',
208
+ content: `统计功能错误: ${error.message}`
209
+ };
210
+ }
211
+ }
212
+ });
213
+
214
+ // 注册缓存清除工具
215
+ api.registerTool({
216
+ name: 'browser_cache_clear',
217
+ description: '清空页面操作缓存',
218
+ async execute(ctx: any) {
219
+ try {
220
+ const request = ctx.params as any;
221
+
222
+ if (request.url && request.viewport) {
223
+ // 删除特定缓存条目
224
+ const deleted = multiLevelCache.deleteCache(request.url, request.viewport || '1920x1080');
225
+ cacheInvalidation.invalidate(request.url, request.viewport || '1920x1080');
226
+
227
+ return {
228
+ type: 'text',
229
+ content: deleted
230
+ ? `Cache entry deleted for ${request.url}:${request.viewport || '1920x1080'}`
231
+ : `No cache entry found for ${request.url}:${request.viewport || '1920x1080'}`
232
+ };
233
+ } else if (request.url) {
234
+ // 删除 URL 相关的所有缓存
235
+ multiLevelCache.deleteCacheByUrl(request.url);
236
+ cacheInvalidation.invalidateByUrl(request.url);
237
+
238
+ return {
239
+ type: 'text',
240
+ content: `All cache entries for ${request.url} have been deleted`
241
+ };
242
+ } else if (request.level) {
243
+ // 按级别删除
244
+ if (request.level === 'all') {
245
+ multiLevelCache.clearAll();
246
+ cacheInvalidation.reset();
247
+ } else {
248
+ multiLevelCache.clearLevel(request.level);
249
+ }
250
+
251
+ return {
252
+ type: 'text',
253
+ content: `Cache cleared for level: ${request.level}`
254
+ };
255
+ } else {
256
+ // 默认清空所有缓存
257
+ multiLevelCache.clearAll();
258
+ cacheInvalidation.reset();
259
+
260
+ return {
261
+ type: 'text',
262
+ content: 'All page action cache has been cleared'
263
+ };
264
+ }
265
+ } catch (error: any) {
266
+ return {
267
+ type: 'text',
268
+ content: `清除功能错误: ${error.message}`
269
+ };
270
+ }
271
+ }
272
+ });
273
+
274
+ console.log('[PageActionCache] Tools registered successfully');
275
+
276
+ // 定期清理过期缓存
277
+ setInterval(() => {
278
+ multiLevelCache.cleanupExpired();
279
+ cacheInvalidation.cleanup();
280
+ }, 60 * 60 * 1000); // 每小时清理一次
281
+ }
282
+
283
+ /**
284
+ * 从缓存执行
285
+ */
286
+ async function executeFromCache(cachedEntry: any) {
287
+ const result = await browserActionExecutor.executeCacheEntry(cachedEntry);
288
+
289
+ return {
290
+ type: 'text',
291
+ content: `## Cached Execution
292
+
293
+ ${result.message}
294
+
295
+ - Executed Actions: ${result.executedActions}
296
+ - Skipped Actions: ${result.skippedActions}
297
+ - Failed Actions: ${result.failedActions}
298
+ - Saved Time: ${result.savedTime}ms
299
+
300
+ Cache ID: ${cachedEntry.id}
301
+ URL: ${cachedEntry.url}
302
+ Scenario: ${cachedEntry.scenario}
303
+ Cache Level: ${cachedEntry.level}
304
+ Access Count: ${cachedEntry.accessCount || 0}`
305
+ };
306
+ }