page-action-cache 2.0.9 → 2026.2.1
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/dist/index.d.ts +21 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +222 -372
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +6 -19
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -2
- package/openclaw.plugin.json +23 -14
- package/package.json +24 -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 -129
- package/dist/cache-invalidation.d.ts.map +0 -1
- package/dist/cache-invalidation.js +0 -266
- 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/multi-level-cache.d.ts +0 -127
- package/dist/multi-level-cache.d.ts.map +0 -1
- package/dist/multi-level-cache.js +0 -364
- 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/variable-extractor.d.ts +0 -56
- package/dist/variable-extractor.d.ts.map +0 -1
- package/dist/variable-extractor.js +0 -161
- package/dist/variable-extractor.js.map +0 -1
- package/src/browser-action-executor.ts +0 -337
- package/src/cache-invalidation.ts +0 -342
- package/src/cache-manager.ts +0 -211
- package/src/index.ts +0 -468
- package/src/multi-level-cache.ts +0 -480
- 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 -206
package/dist/index.js
CHANGED
|
@@ -1,408 +1,258 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Page Action Cache - OpenClaw Extension
|
|
3
|
-
* 页面操作缓存扩展
|
|
3
|
+
* 页面操作缓存扩展
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
// 内存缓存(页面会话级别)
|
|
6
|
+
const memoryCache = new Map();
|
|
7
|
+
const MAX_CACHE_SIZE = 50;
|
|
8
|
+
const CACHE_TTL_MS = 5 * 60 * 1000; // 5 分钟
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* 生成缓存键
|
|
11
11
|
*/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const enabled = pageActionCacheConfig.enabled !== false;
|
|
29
|
-
console.log('[PageActionCache] Enabled status:', enabled);
|
|
30
|
-
if (!enabled) {
|
|
31
|
-
console.log('[PageActionCache] Extension disabled by configuration');
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
console.log('[PageActionCache] Extension enabled, proceeding with tool registration...');
|
|
35
|
-
// 应用配置
|
|
36
|
-
if (pageActionCacheConfig.cacheLevelStrategy) {
|
|
37
|
-
multiLevelCache.setStrategy(pageActionCacheConfig.cacheLevelStrategy);
|
|
38
|
-
console.log('[PageActionCache] Set cache level strategy:', pageActionCacheConfig.cacheLevelStrategy);
|
|
39
|
-
}
|
|
40
|
-
if (pageActionCacheConfig.llmClassificationThreshold !== undefined) {
|
|
41
|
-
multiLevelCache.setLLMThreshold(pageActionCacheConfig.llmClassificationThreshold);
|
|
42
|
-
console.log('[PageActionCache] Set LLM classification threshold:', pageActionCacheConfig.llmClassificationThreshold);
|
|
43
|
-
}
|
|
44
|
-
if (pageActionCacheConfig.invalidationStrategy) {
|
|
45
|
-
cacheInvalidation.setStrategy(pageActionCacheConfig.invalidationStrategy);
|
|
46
|
-
console.log('[PageActionCache] Set invalidation strategy:', pageActionCacheConfig.invalidationStrategy);
|
|
12
|
+
function getCacheKey(url, viewport) {
|
|
13
|
+
if (!url)
|
|
14
|
+
return '';
|
|
15
|
+
return viewport ? `${url}:${viewport}` : url;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 清理过期缓存
|
|
19
|
+
*/
|
|
20
|
+
function cleanupExpiredCache() {
|
|
21
|
+
const now = Date.now();
|
|
22
|
+
let cleaned = 0;
|
|
23
|
+
for (const [key, entry] of memoryCache.entries()) {
|
|
24
|
+
if (now - entry.timestamp > CACHE_TTL_MS) {
|
|
25
|
+
memoryCache.delete(key);
|
|
26
|
+
cleaned++;
|
|
27
|
+
}
|
|
47
28
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
29
|
+
// LRU 清理
|
|
30
|
+
while (memoryCache.size > MAX_CACHE_SIZE) {
|
|
31
|
+
const oldestKey = memoryCache.keys().next().value;
|
|
32
|
+
if (oldestKey) {
|
|
33
|
+
memoryCache.delete(oldestKey);
|
|
34
|
+
cleaned++;
|
|
35
|
+
}
|
|
51
36
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
37
|
+
return cleaned;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 主扩展入口
|
|
41
|
+
*/
|
|
42
|
+
export default function register(api) {
|
|
43
|
+
console.log('[PageActionCache] Initializing extension...');
|
|
44
|
+
try {
|
|
45
|
+
// ✅ 方法 1:通过 getToolContext 获取配置
|
|
46
|
+
let toolContext = undefined;
|
|
47
|
+
if (typeof api.getToolContext === 'function') {
|
|
48
|
+
toolContext = api.getToolContext();
|
|
49
|
+
console.log('[PageActionCache] Method 1: config via getToolContext:', toolContext ? 'available' : 'not available');
|
|
50
|
+
}
|
|
51
|
+
// ✅ 方法 2:直接从 api 对象读取(某些版本)
|
|
52
|
+
if (!toolContext || typeof toolContext.config === 'undefined') {
|
|
57
53
|
try {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
console.log('[PageActionCache] Context params:', ctx.params ? JSON.stringify(ctx.params, null, 2) : 'null');
|
|
62
|
-
// 从插件配置中读取 enabled 状态
|
|
63
|
-
const pluginConfig = ctx.config?.plugins?.entries?.['page-action-cache'] ?? {};
|
|
64
|
-
const enabled = pluginConfig.enabled !== false;
|
|
65
|
-
if (!enabled) {
|
|
66
|
-
console.log('[PageActionCache] Extension disabled by context config');
|
|
67
|
-
return {
|
|
68
|
-
success: false,
|
|
69
|
-
message: 'Extension is disabled',
|
|
70
|
-
executedActions: 0,
|
|
71
|
-
skippedActions: 0,
|
|
72
|
-
failedActions: 0,
|
|
73
|
-
savedTime: 0
|
|
74
|
-
};
|
|
54
|
+
if (typeof api.config === 'object' && api.config) {
|
|
55
|
+
toolContext = { config: api.config };
|
|
56
|
+
console.log('[PageActionCache] Method 2: config via api.config:', toolContext);
|
|
75
57
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
// 识别场景
|
|
96
|
-
let recognizedScenario = scenario || scenarioRecognizer.recognize(url);
|
|
97
|
-
console.log('[PageActionCache] Recognized scenario:', recognizedScenario);
|
|
98
|
-
let cachedEntry = null;
|
|
99
|
-
let isCacheHit = false;
|
|
100
|
-
let executedActions = 0;
|
|
101
|
-
let skippedActions = 0;
|
|
102
|
-
let failedActions = 0;
|
|
103
|
-
let savedTime = 0;
|
|
104
|
-
let cacheId = '';
|
|
105
|
-
// 尝试从缓存中查找
|
|
106
|
-
if (useCache && !forceRefresh) {
|
|
107
|
-
console.log('[PageActionCache] Checking cache...');
|
|
108
|
-
cachedEntry = multiLevelCache.queryCache(url, viewport);
|
|
109
|
-
if (cachedEntry) {
|
|
110
|
-
console.log('[PageActionCache] Cache hit found:', cachedEntry.id);
|
|
111
|
-
isCacheHit = true;
|
|
112
|
-
cacheId = cachedEntry.id;
|
|
113
|
-
// 检查页面是否发生变化(如果提供了 HTML)
|
|
114
|
-
if (pageActionCacheConfig.pageChangeDetectionEnabled && params.html) {
|
|
115
|
-
const shouldInvalidate = cacheInvalidation.shouldInvalidate(url, viewport, params.html);
|
|
116
|
-
if (shouldInvalidate) {
|
|
117
|
-
console.log('[PageActionCache] Page changed, cache invalidated');
|
|
118
|
-
multiLevelCache.deleteCache(url, viewport);
|
|
119
|
-
cachedEntry = null;
|
|
120
|
-
isCacheHit = false;
|
|
121
|
-
}
|
|
122
|
-
else {
|
|
123
|
-
console.log('[PageActionCache] Page unchanged, cache valid');
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
console.log('[PageActionCache] Cache miss');
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
// 如果没有缓存或需要强制刷新,执行新的操作
|
|
132
|
-
if (!cachedEntry) {
|
|
133
|
-
console.log('[PageActionCache] Executing new actions...');
|
|
134
|
-
if (!actions || actions.length === 0) {
|
|
135
|
-
return {
|
|
136
|
-
success: false,
|
|
137
|
-
message: 'No actions provided and no cache found',
|
|
138
|
-
executedActions: 0,
|
|
139
|
-
skippedActions: 0,
|
|
140
|
-
failedActions: 0,
|
|
141
|
-
savedTime: 0
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
// 执行操作
|
|
145
|
-
const actionResults = [];
|
|
146
|
-
for (let i = 0; i < actions.length; i++) {
|
|
147
|
-
const action = actions[i];
|
|
148
|
-
console.log('[PageActionCache] Executing action', i + 1, ':', action.type);
|
|
149
|
-
try {
|
|
150
|
-
const startTime = Date.now();
|
|
151
|
-
let result;
|
|
152
|
-
switch (action.type) {
|
|
153
|
-
case 'navigate':
|
|
154
|
-
if (!action.params?.url) {
|
|
155
|
-
result = { success: false, message: 'Missing URL' };
|
|
156
|
-
}
|
|
157
|
-
else {
|
|
158
|
-
// 模拟导航操作
|
|
159
|
-
console.log('[PageActionCache] Navigating to:', action.params.url);
|
|
160
|
-
result = { success: true, message: `Navigated to ${action.params.url}` };
|
|
161
|
-
}
|
|
162
|
-
break;
|
|
163
|
-
case 'click':
|
|
164
|
-
if (!action.params?.selector) {
|
|
165
|
-
result = { success: false, message: 'Missing selector' };
|
|
166
|
-
}
|
|
167
|
-
else {
|
|
168
|
-
console.log('[PageActionCache] Clicking:', action.params.selector);
|
|
169
|
-
result = { success: true, message: `Clicked ${action.params.selector}` };
|
|
170
|
-
}
|
|
171
|
-
break;
|
|
172
|
-
case 'type':
|
|
173
|
-
if (!action.params?.selector || action.params?.text === undefined) {
|
|
174
|
-
result = { success: false, message: 'Missing selector or text' };
|
|
175
|
-
}
|
|
176
|
-
else {
|
|
177
|
-
console.log('[PageActionCache] Typing:', action.params.text);
|
|
178
|
-
result = { success: true, message: `Typed text` };
|
|
179
|
-
}
|
|
180
|
-
break;
|
|
181
|
-
case 'screenshot':
|
|
182
|
-
console.log('[PageActionCache] Taking screenshot');
|
|
183
|
-
result = { success: true, message: 'Screenshot taken' };
|
|
184
|
-
break;
|
|
185
|
-
case 'script':
|
|
186
|
-
if (!action.params?.script) {
|
|
187
|
-
result = { success: false, message: 'Missing script' };
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
console.log('[PageActionCache] Executing script');
|
|
191
|
-
result = { success: true, message: 'Script executed' };
|
|
192
|
-
}
|
|
193
|
-
break;
|
|
194
|
-
default:
|
|
195
|
-
result = { success: false, message: `Unknown action type: ${action.type}` };
|
|
196
|
-
}
|
|
197
|
-
const executionTime = Date.now() - startTime;
|
|
198
|
-
result.executionTime = executionTime;
|
|
199
|
-
actionResults.push(result);
|
|
200
|
-
if (result.success) {
|
|
201
|
-
executedActions++;
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
console.log('[PageActionCache] Method 2 failed, api.config not available');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// ✅ 方法 3:从 config 对象获取(通过其他方式)
|
|
64
|
+
if (!toolContext) {
|
|
65
|
+
try {
|
|
66
|
+
const configMethod = api.getConfig;
|
|
67
|
+
if (typeof configMethod === 'function') {
|
|
68
|
+
const rawConfig = configMethod();
|
|
69
|
+
console.log('[PageActionCache] Method 3: config via getConfig:', rawConfig);
|
|
70
|
+
if (rawConfig && typeof rawConfig === 'object') {
|
|
71
|
+
const pluginsConfig = rawConfig.plugins;
|
|
72
|
+
if (pluginsConfig && typeof pluginsConfig === 'object') {
|
|
73
|
+
const extensionConfig = pluginsConfig.entries?.['page-action-cache'];
|
|
74
|
+
if (extensionConfig && typeof extensionConfig === 'object') {
|
|
75
|
+
toolContext = { config: { 'page-action-cache': extensionConfig } };
|
|
76
|
+
console.log('[PageActionCache] Method 3 success: config loaded');
|
|
202
77
|
}
|
|
203
78
|
else {
|
|
204
|
-
|
|
79
|
+
console.log('[PageActionCache] Method 3: page-action-cache entry not found');
|
|
205
80
|
}
|
|
206
81
|
}
|
|
207
|
-
|
|
208
|
-
console.
|
|
209
|
-
failedActions++;
|
|
210
|
-
actionResults.push({
|
|
211
|
-
success: false,
|
|
212
|
-
message: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
213
|
-
});
|
|
82
|
+
else {
|
|
83
|
+
console.log('[PageActionCache] Method 3: plugins entries not found');
|
|
214
84
|
}
|
|
215
85
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
type: action.type,
|
|
219
|
-
params: action.params,
|
|
220
|
-
successCount: actionResults[index]?.success ? 1 : 0,
|
|
221
|
-
failCount: actionResults[index]?.success ? 0 : 1
|
|
222
|
-
}));
|
|
223
|
-
cacheId = multiLevelCache.addCache(url, viewport, processedActions, recognizedScenario, llmClassificationScore);
|
|
224
|
-
// 保存页面快照(如果提供了 HTML)
|
|
225
|
-
if (params.html) {
|
|
226
|
-
cacheInvalidation.saveSnapshot(url, viewport, params.html);
|
|
86
|
+
else {
|
|
87
|
+
console.log('[PageActionCache] Method 3: raw config is not an object');
|
|
227
88
|
}
|
|
228
|
-
console.log('[PageActionCache] Saved to cache with ID:', cacheId);
|
|
229
|
-
savedTime = 0; // 首次执行不节省时间
|
|
230
|
-
}
|
|
231
|
-
else {
|
|
232
|
-
// 使用缓存执行
|
|
233
|
-
console.log('[PageActionCache] Using cached entry...');
|
|
234
|
-
const result = await browserActionExecutor.executeCacheEntry(cachedEntry);
|
|
235
|
-
executedActions = result.executedActions;
|
|
236
|
-
skippedActions = result.skippedActions;
|
|
237
|
-
failedActions = result.failedActions;
|
|
238
|
-
savedTime = result.savedTime;
|
|
239
|
-
console.log('[PageActionCache] Cache execution result:', result);
|
|
240
89
|
}
|
|
241
|
-
// 生成返回消息
|
|
242
|
-
let message = isCacheHit ? 'Cached Execution' : 'New Execution';
|
|
243
|
-
message += '\n\n';
|
|
244
|
-
message += `Execution completed ${isCacheHit ? 'from cache' : 'successfully'}\n\n`;
|
|
245
|
-
message += `- Executed Actions: ${executedActions}\n`;
|
|
246
|
-
message += `- Skipped Actions: ${skippedActions}\n`;
|
|
247
|
-
message += `- Failed Actions: ${failedActions}\n`;
|
|
248
|
-
message += `- Saved Time: ${savedTime}ms\n\n`;
|
|
249
|
-
message += `Cache ID: ${cacheId}\n`;
|
|
250
|
-
message += `URL: ${url}\n`;
|
|
251
|
-
message += `Scenario: ${recognizedScenario}\n`;
|
|
252
|
-
if (isCacheHit && cachedEntry) {
|
|
253
|
-
message += `Cache Level: ${cachedEntry.level}\n`;
|
|
254
|
-
message += `Access Count: ${cachedEntry.accessCount}\n`;
|
|
255
|
-
}
|
|
256
|
-
console.log('[PageActionCache] Returning result:', message);
|
|
257
|
-
return {
|
|
258
|
-
success: failedActions === 0,
|
|
259
|
-
message,
|
|
260
|
-
executedActions,
|
|
261
|
-
skippedActions,
|
|
262
|
-
failedActions,
|
|
263
|
-
savedTime,
|
|
264
|
-
cacheId,
|
|
265
|
-
url,
|
|
266
|
-
scenario: recognizedScenario,
|
|
267
|
-
isCacheHit,
|
|
268
|
-
cacheLevel: cachedEntry?.level
|
|
269
|
-
};
|
|
270
90
|
}
|
|
271
|
-
catch (
|
|
272
|
-
console.
|
|
273
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
274
|
-
return {
|
|
275
|
-
success: false,
|
|
276
|
-
message: `Error: ${errorMessage}`,
|
|
277
|
-
executedActions: 0,
|
|
278
|
-
skippedActions: 0,
|
|
279
|
-
failedActions: 0,
|
|
280
|
-
savedTime: 0
|
|
281
|
-
};
|
|
91
|
+
catch (e) {
|
|
92
|
+
console.log('[PageActionCache] Method 3 failed:', e);
|
|
282
93
|
}
|
|
283
94
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
95
|
+
// 使用默认配置
|
|
96
|
+
const pluginConfig = {
|
|
97
|
+
enabled: toolContext?.config?.['page-action-cache']?.enabled !== false,
|
|
98
|
+
autoUseCache: toolContext?.config?.['page-action-cache']?.autoUseCache !== false,
|
|
99
|
+
};
|
|
100
|
+
console.log('[PageActionCache] Final config:', JSON.stringify(pluginConfig));
|
|
101
|
+
if (!pluginConfig.enabled) {
|
|
102
|
+
console.log('[PageActionCache] Extension disabled by configuration');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
console.log('[PageActionCache] Extension enabled, registering tools...');
|
|
106
|
+
// 注册页面快照缓存工具
|
|
107
|
+
api.registerTool({
|
|
108
|
+
name: 'page_snapshot_cache',
|
|
109
|
+
description: 'Cache and retrieve page snapshots to speed up repeated operations',
|
|
110
|
+
inputSchema: {
|
|
111
|
+
type: 'object',
|
|
112
|
+
properties: {
|
|
113
|
+
url: {
|
|
114
|
+
type: 'string',
|
|
115
|
+
description: 'Page URL'
|
|
116
|
+
},
|
|
117
|
+
snapshot: {
|
|
118
|
+
type: 'string',
|
|
119
|
+
description: 'Page snapshot to store'
|
|
120
|
+
},
|
|
121
|
+
refs: {
|
|
122
|
+
type: 'object',
|
|
123
|
+
description: 'Role references map'
|
|
124
|
+
},
|
|
125
|
+
invalidate: {
|
|
126
|
+
type: 'boolean',
|
|
127
|
+
description: 'Invalidate cache for this URL',
|
|
128
|
+
default: false
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
required: ['url']
|
|
132
|
+
},
|
|
133
|
+
async execute(params) {
|
|
134
|
+
console.log('[PageActionCache] page_snapshot_cache called:', JSON.stringify(params));
|
|
135
|
+
const { url, snapshot, refs, invalidate = false } = params;
|
|
136
|
+
// 处理失效请求
|
|
137
|
+
if (invalidate) {
|
|
138
|
+
const key = getCacheKey(url);
|
|
139
|
+
memoryCache.delete(key);
|
|
140
|
+
console.log(`[PageActionCache] Cache invalidated for: ${url}`);
|
|
295
141
|
return {
|
|
296
|
-
|
|
297
|
-
|
|
142
|
+
text: `Cache invalidated for ${url}`,
|
|
143
|
+
invalidated: true
|
|
298
144
|
};
|
|
299
145
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
146
|
+
// 处理存储请求
|
|
147
|
+
if (snapshot && refs) {
|
|
148
|
+
const key = getCacheKey(url);
|
|
149
|
+
const entry = {
|
|
150
|
+
url,
|
|
151
|
+
snapshot,
|
|
152
|
+
refs,
|
|
153
|
+
timestamp: Date.now(),
|
|
154
|
+
hitCount: 0
|
|
155
|
+
};
|
|
156
|
+
memoryCache.set(key, entry);
|
|
157
|
+
// 清理过期缓存
|
|
158
|
+
cleanupExpiredCache();
|
|
159
|
+
console.log(`[PageActionCache] Cache stored for: ${url}`);
|
|
160
|
+
return {
|
|
161
|
+
text: `Snapshot cached for ${url}`,
|
|
162
|
+
cached: true
|
|
163
|
+
};
|
|
315
164
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
165
|
+
// 处理查询请求
|
|
166
|
+
const key = getCacheKey(url);
|
|
167
|
+
const cached = memoryCache.get(key);
|
|
168
|
+
if (cached && pluginConfig.autoUseCache) {
|
|
169
|
+
// 更新访问统计
|
|
170
|
+
cached.hitCount++;
|
|
171
|
+
cached.timestamp = Date.now();
|
|
172
|
+
console.log(`[PageActionCache] Cache HIT for: ${url} (hit #${cached.hitCount})`);
|
|
173
|
+
return {
|
|
174
|
+
text: `Cache hit for ${url}`,
|
|
175
|
+
snapshot: cached.snapshot,
|
|
176
|
+
refs: cached.refs,
|
|
177
|
+
hitCount: cached.hitCount,
|
|
178
|
+
cached: true
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
console.log(`[PageActionCache] Cache MISS for: ${url}`);
|
|
320
182
|
return {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
stats,
|
|
324
|
-
invalidationStats
|
|
183
|
+
text: `Cache miss for ${url}`,
|
|
184
|
+
cached: false
|
|
325
185
|
};
|
|
326
186
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
187
|
+
});
|
|
188
|
+
// 注册缓存统计工具
|
|
189
|
+
api.registerTool({
|
|
190
|
+
name: 'page_cache_stats',
|
|
191
|
+
description: 'View page snapshot cache statistics',
|
|
192
|
+
inputSchema: {
|
|
193
|
+
type: 'object',
|
|
194
|
+
properties: {},
|
|
195
|
+
required: []
|
|
196
|
+
},
|
|
197
|
+
async execute() {
|
|
198
|
+
console.log('[PageActionCache] page_cache_stats called');
|
|
199
|
+
// 清理过期缓存
|
|
200
|
+
const cleaned = cleanupExpiredCache();
|
|
201
|
+
const stats = {
|
|
202
|
+
totalEntries: memoryCache.size,
|
|
203
|
+
hits: Array.from(memoryCache.values()).reduce((sum, e) => sum + e.hitCount, 0),
|
|
204
|
+
avgHitCount: memoryCache.size > 0
|
|
205
|
+
? Array.from(memoryCache.values()).reduce((sum, e) => sum + e.hitCount, 0) / memoryCache.size
|
|
206
|
+
: 0,
|
|
207
|
+
oldestEntry: memoryCache.size > 0
|
|
208
|
+
? Math.min(...Array.from(memoryCache.values()).map(e => e.timestamp))
|
|
209
|
+
: null,
|
|
210
|
+
newestEntry: memoryCache.size > 0
|
|
211
|
+
? Math.max(...Array.from(memoryCache.values()).map(e => e.timestamp))
|
|
212
|
+
: null
|
|
332
213
|
};
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
api.registerTool({
|
|
338
|
-
name: 'browser_cache_clear',
|
|
339
|
-
description: '清空页面操作缓存',
|
|
340
|
-
async execute(ctx) {
|
|
341
|
-
try {
|
|
342
|
-
console.log('[PageActionCache] browser_cache_clear called');
|
|
343
|
-
console.log('[PageActionCache] Context params:', ctx.params ? JSON.stringify(ctx.params, null, 2) : 'null');
|
|
344
|
-
const pluginConfig = ctx.config?.plugins?.entries?.['page-action-cache'] ?? {};
|
|
345
|
-
const enabled = pluginConfig.enabled !== false;
|
|
346
|
-
if (!enabled) {
|
|
347
|
-
return {
|
|
348
|
-
success: false,
|
|
349
|
-
message: 'Extension is disabled'
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
const params = ctx.params || {};
|
|
353
|
-
const level = params.level || 'all'; // 'all', 'L3', 'L2', 'L1'
|
|
354
|
-
const url = params.url;
|
|
355
|
-
const viewport = params.viewport;
|
|
356
|
-
console.log('[PageActionCache] Clear params:', { level, url, viewport });
|
|
357
|
-
let cleared = 0;
|
|
358
|
-
if (level === 'all') {
|
|
359
|
-
if (url) {
|
|
360
|
-
// 清空特定 URL 的所有缓存
|
|
361
|
-
multiLevelCache.deleteCacheByUrl(url);
|
|
362
|
-
cacheInvalidation.invalidateByUrl(url);
|
|
363
|
-
cleared = 1;
|
|
364
|
-
}
|
|
365
|
-
else {
|
|
366
|
-
// 清空所有缓存
|
|
367
|
-
multiLevelCache.clearAll();
|
|
368
|
-
cacheInvalidation.reset();
|
|
369
|
-
cleared = 1;
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
else if (level === 'L3' || level === 'L2' || level === 'L1') {
|
|
373
|
-
if (url && viewport) {
|
|
374
|
-
multiLevelCache.deleteCache(url, viewport);
|
|
375
|
-
cacheInvalidation.invalidate(url, viewport);
|
|
376
|
-
cleared = 1;
|
|
377
|
-
}
|
|
378
|
-
else {
|
|
379
|
-
multiLevelCache.clearLevel(level);
|
|
380
|
-
cleared = 1;
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
console.log('[PageActionCache] Cleared cache entries');
|
|
384
|
-
const levelText = level === 'all' ? 'all caches' : `${level} cache`;
|
|
385
|
-
const urlText = url ? ` for ${url}` : '';
|
|
386
|
-
const message = `Cleared ${levelText}${urlText}`;
|
|
214
|
+
console.log('[PageActionCache] Stats:', JSON.stringify(stats));
|
|
215
|
+
const hitRate = stats.totalEntries > 0
|
|
216
|
+
? (stats.hits / stats.totalEntries * 100).toFixed(2)
|
|
217
|
+
: '0.00';
|
|
387
218
|
return {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
219
|
+
text: `## Page Snapshot Cache Statistics\n\n` +
|
|
220
|
+
`- Total Entries: ${stats.totalEntries}\n` +
|
|
221
|
+
`- Total Hits: ${stats.hits}\n` +
|
|
222
|
+
`- Hit Rate: ${hitRate}%\n` +
|
|
223
|
+
`- Avg Hit Count: ${stats.avgHitCount.toFixed(1)}\n` +
|
|
224
|
+
`- Cleaned Entries: ${cleaned}\n` +
|
|
225
|
+
`- Oldest Entry: ${stats.oldestEntry ? new Date(stats.oldestEntry).toISOString() : 'N/A'}\n` +
|
|
226
|
+
`- Newest Entry: ${stats.newestEntry ? new Date(stats.newestEntry).toISOString() : 'N/A'}`,
|
|
227
|
+
stats
|
|
393
228
|
};
|
|
394
229
|
}
|
|
395
|
-
|
|
396
|
-
|
|
230
|
+
});
|
|
231
|
+
// 注册缓存清空工具
|
|
232
|
+
api.registerTool({
|
|
233
|
+
name: 'page_cache_clear',
|
|
234
|
+
description: 'Clear page snapshot cache',
|
|
235
|
+
inputSchema: {
|
|
236
|
+
type: 'object',
|
|
237
|
+
properties: {},
|
|
238
|
+
required: []
|
|
239
|
+
},
|
|
240
|
+
async execute() {
|
|
241
|
+
console.log('[PageActionCache] page_cache_clear called');
|
|
242
|
+
const count = memoryCache.size;
|
|
243
|
+
memoryCache.clear();
|
|
244
|
+
console.log(`[PageActionCache] Cleared ${count} cache entries`);
|
|
397
245
|
return {
|
|
398
|
-
|
|
399
|
-
|
|
246
|
+
text: `Cleared ${count} cache entries`,
|
|
247
|
+
cleared: count
|
|
400
248
|
};
|
|
401
249
|
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
|
|
250
|
+
});
|
|
251
|
+
console.log('[PageActionCache] All tools registered successfully');
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
console.error('[PageActionCache] Fatal error during plugin registration:', error);
|
|
255
|
+
// 即使发生错误,也尝试注册基本工具以确保插件可用
|
|
256
|
+
}
|
|
405
257
|
}
|
|
406
|
-
// Export register as registerTools for backward compatibility with tests
|
|
407
|
-
export const registerTools = register;
|
|
408
258
|
//# sourceMappingURL=index.js.map
|