page-action-cache 2.0.8 → 2026.2.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.
- package/README.md +100 -306
- package/openclaw.plugin.json +23 -14
- package/package.json +28 -21
- package/dist/browser-action-executor.d.ts +0 -87
- package/dist/browser-action-executor.d.ts.map +0 -1
- package/dist/browser-action-executor.js +0 -283
- package/dist/browser-action-executor.js.map +0 -1
- package/dist/cache-invalidation.d.ts +0 -128
- package/dist/cache-invalidation.d.ts.map +0 -1
- package/dist/cache-invalidation.js +0 -262
- package/dist/cache-invalidation.js.map +0 -1
- package/dist/cache-manager.d.ts +0 -83
- package/dist/cache-manager.d.ts.map +0 -1
- package/dist/cache-manager.js +0 -184
- package/dist/cache-manager.js.map +0 -1
- package/dist/index.d.ts +0 -10
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -48
- package/dist/index.js.map +0 -1
- package/dist/multi-level-cache.d.ts +0 -127
- package/dist/multi-level-cache.d.ts.map +0 -1
- package/dist/multi-level-cache.js +0 -362
- package/dist/multi-level-cache.js.map +0 -1
- package/dist/scenario-recognizer.d.ts +0 -35
- package/dist/scenario-recognizer.d.ts.map +0 -1
- package/dist/scenario-recognizer.js +0 -93
- package/dist/scenario-recognizer.js.map +0 -1
- package/dist/types.d.ts +0 -62
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -6
- package/dist/types.js.map +0 -1
- package/dist/variable-extractor.d.ts +0 -56
- package/dist/variable-extractor.d.ts.map +0 -1
- package/dist/variable-extractor.js +0 -159
- package/dist/variable-extractor.js.map +0 -1
- package/src/browser-action-executor.ts +0 -337
- package/src/cache-invalidation.ts +0 -338
- package/src/cache-manager.ts +0 -211
- package/src/index.ts +0 -58
- package/src/multi-level-cache.ts +0 -478
- package/src/scenario-recognizer.ts +0 -121
- package/src/types-mock.d.ts +0 -18
- package/src/types.ts +0 -66
- package/src/variable-extractor.ts +0 -204
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cache Invalidation - Page Action Cache
|
|
3
|
-
* 缓存失效管理器 - 页面变化检测和缓存失效
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* 页面变化类型
|
|
8
|
-
*/
|
|
9
|
-
type ChangeType = 'major' | 'minor' | 'none';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* 页面快照
|
|
13
|
-
*/
|
|
14
|
-
interface PageSnapshot {
|
|
15
|
-
url: string;
|
|
16
|
-
viewport: string;
|
|
17
|
-
timestamp: number;
|
|
18
|
-
htmlHash?: string;
|
|
19
|
-
structureHash?: string;
|
|
20
|
-
contentHash?: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* 缓存失效策略
|
|
25
|
-
*/
|
|
26
|
-
type InvalidationStrategy = 'soft' | 'hard';
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* 页面变化检测结果
|
|
30
|
-
*/
|
|
31
|
-
interface ChangeDetectionResult {
|
|
32
|
-
changed: boolean;
|
|
33
|
-
changeType: ChangeType;
|
|
34
|
-
changeScore: number;
|
|
35
|
-
confidence: number;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* 缓存失效管理器
|
|
40
|
-
*/
|
|
41
|
-
export class CacheInvalidation {
|
|
42
|
-
private snapshots: Map<string, PageSnapshot> = new Map();
|
|
43
|
-
private changeHistory: Map<string, number[]> = new Map();
|
|
44
|
-
private strategy: InvalidationStrategy = 'soft';
|
|
45
|
-
private changeThreshold: number = 0.5;
|
|
46
|
-
|
|
47
|
-
constructor(strategy: InvalidationStrategy = 'soft', changeThreshold: number = 0.5) {
|
|
48
|
-
this.strategy = strategy;
|
|
49
|
-
this.changeThreshold = changeThreshold;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* 设置失效策略
|
|
54
|
-
*/
|
|
55
|
-
setStrategy(strategy: InvalidationStrategy): void {
|
|
56
|
-
this.strategy = strategy;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* 设置变化阈值
|
|
61
|
-
*/
|
|
62
|
-
setChangeThreshold(threshold: number): void {
|
|
63
|
-
this.changeThreshold = Math.max(0, Math.min(1, threshold));
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* 创建页面快照
|
|
68
|
-
*/
|
|
69
|
-
createSnapshot(url: string, viewport: string, html?: string): PageSnapshot {
|
|
70
|
-
const snapshot: PageSnapshot = {
|
|
71
|
-
url,
|
|
72
|
-
viewport,
|
|
73
|
-
timestamp: Date.now()
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
if (html) {
|
|
77
|
-
snapshot.htmlHash = this.generateHash(html);
|
|
78
|
-
snapshot.structureHash = this.generateStructureHash(html);
|
|
79
|
-
snapshot.contentHash = this.generateContentHash(html);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return snapshot;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* 保存页面快照
|
|
87
|
-
*/
|
|
88
|
-
saveSnapshot(url: string, viewport: string, html?: string): void {
|
|
89
|
-
const key = this.getSnapshotKey(url, viewport);
|
|
90
|
-
const snapshot = this.createSnapshot(url, viewport, html);
|
|
91
|
-
this.snapshots.set(key, snapshot);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* 检测页面变化
|
|
96
|
-
*/
|
|
97
|
-
detectChanges(url: string, viewport: string, currentHtml: string): ChangeDetectionResult {
|
|
98
|
-
const key = this.getSnapshotKey(url, viewport);
|
|
99
|
-
const previousSnapshot = this.snapshots.get(key);
|
|
100
|
-
|
|
101
|
-
if (!previousSnapshot) {
|
|
102
|
-
return {
|
|
103
|
-
changed: true,
|
|
104
|
-
changeType: 'major',
|
|
105
|
-
changeScore: 1.0,
|
|
106
|
-
confidence: 0.8
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const currentSnapshot = this.createSnapshot(url, viewport, currentHtml);
|
|
111
|
-
const changeScore = this.calculateChangeScore(previousSnapshot, currentSnapshot);
|
|
112
|
-
const changeType = this.determineChangeType(changeScore);
|
|
113
|
-
|
|
114
|
-
// 记录变化历史
|
|
115
|
-
this.recordChangeHistory(key, changeScore);
|
|
116
|
-
|
|
117
|
-
return {
|
|
118
|
-
changed: changeScore > this.changeThreshold,
|
|
119
|
-
changeType,
|
|
120
|
-
changeScore,
|
|
121
|
-
confidence: this.calculateConfidence(previousSnapshot, currentSnapshot)
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* 计算变化分数
|
|
127
|
-
*/
|
|
128
|
-
private calculateChangeScore(previous: PageSnapshot, current: PageSnapshot): number {
|
|
129
|
-
let totalScore = 0;
|
|
130
|
-
let weightSum = 0;
|
|
131
|
-
|
|
132
|
-
// HTML 变化 (权重 0.4)
|
|
133
|
-
if (previous.htmlHash && current.htmlHash) {
|
|
134
|
-
const htmlChanged = previous.htmlHash !== current.htmlHash ? 1 : 0;
|
|
135
|
-
totalScore += htmlChanged * 0.4;
|
|
136
|
-
weightSum += 0.4;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// 结构变化 (权重 0.35)
|
|
140
|
-
if (previous.structureHash && current.structureHash) {
|
|
141
|
-
const structureChanged = previous.structureHash !== current.structureHash ? 1 : 0;
|
|
142
|
-
totalScore += structureChanged * 0.35;
|
|
143
|
-
weightSum += 0.35;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// 内容变化 (权重 0.25)
|
|
147
|
-
if (previous.contentHash && current.contentHash) {
|
|
148
|
-
const contentChanged = previous.contentHash !== current.contentHash ? 1 : 0;
|
|
149
|
-
totalScore += contentChanged * 0.25;
|
|
150
|
-
weightSum += 0.25;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return weightSum > 0 ? totalScore / weightSum : 0;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* 确定变化类型
|
|
158
|
-
*/
|
|
159
|
-
private determineChangeType(changeScore: number): ChangeType {
|
|
160
|
-
if (changeScore >= 0.7) return 'major';
|
|
161
|
-
if (changeScore >= 0.3) return 'minor';
|
|
162
|
-
return 'none';
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* 计算置信度
|
|
167
|
-
*/
|
|
168
|
-
private calculateConfidence(previous: PageSnapshot, current: PageSnapshot): number {
|
|
169
|
-
// 时间越近,置信度越高
|
|
170
|
-
const timeDiff = current.timestamp - previous.timestamp;
|
|
171
|
-
const timeFactor = Math.max(0, 1 - timeDiff / (24 * 60 * 60 * 1000)); // 24小时内
|
|
172
|
-
|
|
173
|
-
return timeFactor * 0.8 + 0.2; // 基础置信度 0.2
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* 记录变化历史
|
|
178
|
-
*/
|
|
179
|
-
private recordChangeHistory(key: string, changeScore: number): void {
|
|
180
|
-
if (!this.changeHistory.has(key)) {
|
|
181
|
-
this.changeHistory.set(key, []);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const history = this.changeHistory.get(key)!;
|
|
185
|
-
history.push(changeScore);
|
|
186
|
-
|
|
187
|
-
// 只保留最近 10 次记录
|
|
188
|
-
if (history.length > 10) {
|
|
189
|
-
history.shift();
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* 判断是否应该失效缓存
|
|
195
|
-
*/
|
|
196
|
-
shouldInvalidate(url: string, viewport: string, currentHtml: string): boolean {
|
|
197
|
-
const detection = this.detectChanges(url, viewport, currentHtml);
|
|
198
|
-
|
|
199
|
-
if (this.strategy === 'hard') {
|
|
200
|
-
// 硬失效:任何变化都失效
|
|
201
|
-
return detection.changed;
|
|
202
|
-
} else {
|
|
203
|
-
// 软失效:根据变化类型和阈值判断
|
|
204
|
-
const key = this.getSnapshotKey(url, viewport);
|
|
205
|
-
const history = this.changeHistory.get(key) || [];
|
|
206
|
-
|
|
207
|
-
// 如果是重大变化,立即失效
|
|
208
|
-
if (detection.changeType === 'major') {
|
|
209
|
-
return true;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// 如果连续 3 次检测到变化,失效
|
|
213
|
-
const recentChanges = history.slice(-3);
|
|
214
|
-
if (recentChanges.length === 3 && recentChanges.every(s => s > this.changeThreshold)) {
|
|
215
|
-
return true;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return detection.changed && detection.confidence > 0.7;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* 失效缓存条目
|
|
224
|
-
*/
|
|
225
|
-
invalidate(url: string, viewport: string): void {
|
|
226
|
-
const key = this.getSnapshotKey(url, viewport);
|
|
227
|
-
this.snapshots.delete(key);
|
|
228
|
-
this.changeHistory.delete(key);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* 批量失效 URL 相关的所有缓存
|
|
233
|
-
*/
|
|
234
|
-
invalidateByUrl(url: string): void {
|
|
235
|
-
for (const key of this.snapshots.keys()) {
|
|
236
|
-
if (key.startsWith(`${url}:`)) {
|
|
237
|
-
this.snapshots.delete(key);
|
|
238
|
-
this.changeHistory.delete(key);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* 获取失效统计
|
|
245
|
-
*/
|
|
246
|
-
getInvalidationStats(): {
|
|
247
|
-
totalSnapshots: number;
|
|
248
|
-
activeSnapshots: number;
|
|
249
|
-
invalidationRate: number;
|
|
250
|
-
} {
|
|
251
|
-
const totalSnapshots = this.changeHistory.size;
|
|
252
|
-
let activeInvalidations = 0;
|
|
253
|
-
|
|
254
|
-
for (const history of this.changeHistory.values()) {
|
|
255
|
-
if (history.length > 0 && history[history.length - 1] > this.changeThreshold) {
|
|
256
|
-
activeInvalidations++;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
return {
|
|
261
|
-
totalSnapshots,
|
|
262
|
-
activeSnapshots: this.snapshots.size,
|
|
263
|
-
invalidationRate: totalSnapshots > 0 ? activeInvalidations / totalSnapshots : 0
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* 清理过期快照
|
|
269
|
-
*/
|
|
270
|
-
cleanup(maxAge: number = 7 * 24 * 60 * 60 * 1000): void { // 默认 7 天
|
|
271
|
-
const now = Date.now();
|
|
272
|
-
const expiredKeys: string[] = [];
|
|
273
|
-
|
|
274
|
-
for (const [key, snapshot] of this.snapshots.entries()) {
|
|
275
|
-
if (now - snapshot.timestamp > maxAge) {
|
|
276
|
-
expiredKeys.push(key);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
for (const key of expiredKeys) {
|
|
281
|
-
this.snapshots.delete(key);
|
|
282
|
-
this.changeHistory.delete(key);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
console.log(`[CacheInvalidation] Cleaned up ${expiredKeys.length} expired snapshots`);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* 生成哈希
|
|
290
|
-
*/
|
|
291
|
-
private generateHash(str: string): string {
|
|
292
|
-
let hash = 0;
|
|
293
|
-
for (let i = 0; i < str.length; i++) {
|
|
294
|
-
const char = str.charCodeAt(i);
|
|
295
|
-
hash = ((hash << 5) - hash) + char;
|
|
296
|
-
hash = hash & hash; // Convert to 32bit integer
|
|
297
|
-
}
|
|
298
|
-
return Math.abs(hash).toString(16);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* 生成结构哈希
|
|
303
|
-
*/
|
|
304
|
-
private generateStructureHash(html: string): string {
|
|
305
|
-
// 移除文本内容,只保留标签结构
|
|
306
|
-
const structure = html.replace(/>[^<]*</g, '><').replace(/\s+/g, '');
|
|
307
|
-
return this.generateHash(structure);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* 生成内容哈希
|
|
312
|
-
*/
|
|
313
|
-
private generateContentHash(html: string): string {
|
|
314
|
-
// 移除 HTML 标签,只保留文本内容
|
|
315
|
-
const content = html.replace(/<[^>]*>/g, '').replace(/\s+/g, ' ').trim();
|
|
316
|
-
return this.generateHash(content);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* 生成快照键
|
|
321
|
-
*/
|
|
322
|
-
private getSnapshotKey(url: string, viewport: string): string {
|
|
323
|
-
return `${url}:${viewport}`;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* 重置
|
|
328
|
-
*/
|
|
329
|
-
reset(): void {
|
|
330
|
-
this.snapshots.clear();
|
|
331
|
-
this.changeHistory.clear();
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* 单例导出
|
|
337
|
-
*/
|
|
338
|
-
export const cacheInvalidation = new CacheInvalidation();
|
package/src/cache-manager.ts
DELETED
|
@@ -1,211 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
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
|
-
export function register(api: OpenClawPluginApi): void {
|
|
16
|
-
console.log('[PageActionCache] Initializing extension...');
|
|
17
|
-
|
|
18
|
-
// 获取插件配置 - 详细日志
|
|
19
|
-
const rawConfig = api.getConfig?.();
|
|
20
|
-
console.log('[PageActionCache] Raw config object:', JSON.stringify(rawConfig, null, 2));
|
|
21
|
-
|
|
22
|
-
// 检查配置对象是否有效
|
|
23
|
-
if (!rawConfig || typeof rawConfig !== 'object') {
|
|
24
|
-
console.log('[PageActionCache] Invalid config object type:', typeof rawConfig);
|
|
25
|
-
// 配置无效,使用空对象继续
|
|
26
|
-
rawConfig = {};
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const pluginConfig = rawConfig?.plugins?.entries?.['page-action-cache'] ?? {};
|
|
30
|
-
console.log('[PageActionCache] Plugin config for page-action-cache:', JSON.stringify(pluginConfig, null, 2));
|
|
31
|
-
|
|
32
|
-
const pageActionCacheConfig = pluginConfig ?? {};
|
|
33
|
-
console.log('[PageActionCache] Parsed pageActionCacheConfig:', JSON.stringify(pageActionCacheConfig, null, 2));
|
|
34
|
-
|
|
35
|
-
const enabled = pageActionCacheConfig.enabled !== false;
|
|
36
|
-
console.log('[PageActionCache] Enabled status:', enabled);
|
|
37
|
-
|
|
38
|
-
if (!enabled) {
|
|
39
|
-
console.log('[PageActionCache] Extension disabled by configuration');
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
console.log('[PageActionCache] Extension enabled, proceeding with tool registration...');
|
|
44
|
-
|
|
45
|
-
// 注册缓存执行工具
|
|
46
|
-
api.registerTool({
|
|
47
|
-
name: 'browser_cache_execute',
|
|
48
|
-
description: '执行缓存的页面操作序列,支持自动缓存匹配、场景识别和变量提取',
|
|
49
|
-
async execute(ctx: any) {
|
|
50
|
-
try {
|
|
51
|
-
console.log('[PageActionCache] browser_cache_execute called');
|
|
52
|
-
console.log('[PageActionCache] Context type:', typeof ctx);
|
|
53
|
-
console.log('[PageActionCache] Context config:', ctx.config ? JSON.stringify(ctx.config, null, 2) : 'null');
|
|
54
|
-
console.log('[PageActionCache] Context params:', ctx.params ? JSON.stringify(ctx.params, null, 2) : 'null');
|
|
55
|
-
|
|
56
|
-
// 从插件配置中读取 enabled 状态
|
|
57
|
-
const pluginConfig = ctx.config?.plugins?.entries?.['page-action-cache'] ?? {};
|
|
58
|
-
const enabled = pluginConfig.enabled !== false;
|