page-action-cache 1.0.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.
Files changed (73) hide show
  1. package/dist/actions-executor.d.ts +62 -0
  2. package/dist/actions-executor.d.ts.map +1 -0
  3. package/dist/actions-executor.js +339 -0
  4. package/dist/actions-executor.js.map +1 -0
  5. package/dist/cache-invalidator.d.ts +70 -0
  6. package/dist/cache-invalidator.d.ts.map +1 -0
  7. package/dist/cache-invalidator.js +212 -0
  8. package/dist/cache-invalidator.js.map +1 -0
  9. package/dist/cache-store.d.ts +80 -0
  10. package/dist/cache-store.d.ts.map +1 -0
  11. package/dist/cache-store.js +361 -0
  12. package/dist/cache-store.js.map +1 -0
  13. package/dist/cache-strategy.d.ts +65 -0
  14. package/dist/cache-strategy.d.ts.map +1 -0
  15. package/dist/cache-strategy.js +237 -0
  16. package/dist/cache-strategy.js.map +1 -0
  17. package/dist/hooks-entry.d.ts +18 -0
  18. package/dist/hooks-entry.d.ts.map +1 -0
  19. package/dist/hooks-entry.js +27 -0
  20. package/dist/hooks-entry.js.map +1 -0
  21. package/dist/hooks.d.ts +10 -0
  22. package/dist/hooks.d.ts.map +1 -0
  23. package/dist/hooks.js +277 -0
  24. package/dist/hooks.js.map +1 -0
  25. package/dist/index.d.ts +24 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +34 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/scenario-recognizer.d.ts +45 -0
  30. package/dist/scenario-recognizer.d.ts.map +1 -0
  31. package/dist/scenario-recognizer.js +213 -0
  32. package/dist/scenario-recognizer.js.map +1 -0
  33. package/dist/security-policy.d.ts +62 -0
  34. package/dist/security-policy.d.ts.map +1 -0
  35. package/dist/security-policy.js +219 -0
  36. package/dist/security-policy.js.map +1 -0
  37. package/dist/tools.d.ts +209 -0
  38. package/dist/tools.d.ts.map +1 -0
  39. package/dist/tools.js +383 -0
  40. package/dist/tools.js.map +1 -0
  41. package/dist/types.d.ts +336 -0
  42. package/dist/types.d.ts.map +1 -0
  43. package/dist/types.js +8 -0
  44. package/dist/types.js.map +1 -0
  45. package/dist/ux-enhancer.d.ts +60 -0
  46. package/dist/ux-enhancer.d.ts.map +1 -0
  47. package/dist/ux-enhancer.js +218 -0
  48. package/dist/ux-enhancer.js.map +1 -0
  49. package/dist/variable-resolver.d.ts +28 -0
  50. package/dist/variable-resolver.d.ts.map +1 -0
  51. package/dist/variable-resolver.js +201 -0
  52. package/dist/variable-resolver.js.map +1 -0
  53. package/docs/API.md +555 -0
  54. package/docs/IMPLEMENTATION.md +1792 -0
  55. package/docs/INTEGRATION.md +387 -0
  56. package/docs/README.md +183 -0
  57. package/index.ts +118 -0
  58. package/openclaw.plugin.json +208 -0
  59. package/package.json +76 -0
  60. package/skills/page-action-cache/SKILL.md +216 -0
  61. package/src/actions-executor.ts +441 -0
  62. package/src/cache-invalidator.ts +271 -0
  63. package/src/cache-store.ts +457 -0
  64. package/src/cache-strategy.ts +327 -0
  65. package/src/hooks-entry.ts +114 -0
  66. package/src/hooks.ts +332 -0
  67. package/src/index.ts +104 -0
  68. package/src/scenario-recognizer.ts +259 -0
  69. package/src/security-policy.ts +268 -0
  70. package/src/tools.ts +437 -0
  71. package/src/types.ts +482 -0
  72. package/src/ux-enhancer.ts +266 -0
  73. package/src/variable-resolver.ts +258 -0
@@ -0,0 +1,266 @@
1
+ /**
2
+ * UX Enhancer
3
+ * 用户体验增强 - 缓存状态展示、用户确认、错误反馈
4
+ */
5
+
6
+ import type { PageActionCacheEntry, ExecutionResult } from "./types.js";
7
+ import { getCacheStore } from "./cache-store.js";
8
+
9
+ // ============================================================================
10
+ // UX Enhancer 类
11
+ // ============================================================================
12
+
13
+ /**
14
+ * 用户体验增强器
15
+ */
16
+ export class UXEnhancer {
17
+ private cacheStore = getCacheStore();
18
+
19
+ /**
20
+ * 展示缓存状态
21
+ */
22
+ showCacheStatus(entry: PageActionCacheEntry, match?: {
23
+ confidence: number;
24
+ method: string;
25
+ }): string {
26
+ const stats = this.cacheStore.getStats();
27
+
28
+ let message = `
29
+ 【缓存命中信息】
30
+
31
+ `;
32
+ message += `场景:${entry.scenario}\n`;
33
+ message += `描述:${entry.description}\n`;
34
+ message += `缓存层级:${entry.cacheLevel}\n`;
35
+ message += `URL:${entry.url}\n`;
36
+
37
+ if (match) {
38
+ message += `匹配置信度:${match.confidence}%\n`;
39
+ message += `匹配方式:${match.method}\n`;
40
+ }
41
+
42
+ message += `访问次数:${entry.accessCount}\n`;
43
+ message += `成功率:${this.calculateSuccessRate(entry)}%\n`;
44
+ message += `平均执行时间:${entry.avgExecutionTime}ms\n`;
45
+ message += `创建时间:${new Date(entry.createdAt).toLocaleString()}\n`;
46
+
47
+ message += `\n【缓存统计】\n`;
48
+ message += `总条目:${stats.totalEntries}\n`;
49
+ message += `总命中:${stats.totalHits}\n`;
50
+ message += `命中率:${stats.hitRate.toFixed(1)}%\n`;
51
+ message += `节省 Token:${stats.savedTokens}\n`;
52
+ message += `节省时间:${(stats.savedTime / 1000).toFixed(1)}s\n`;
53
+
54
+ message += `\n【操作序列】\n`;
55
+ message += this.formatActionsBrief(entry.actions);
56
+
57
+ return message;
58
+ }
59
+
60
+ /**
61
+ * 格式化操作列表(简要版)
62
+ */
63
+ private formatActionsBrief(actions: any[]): string {
64
+ return actions
65
+ .map((action, i) => {
66
+ switch (action.type) {
67
+ case "navigate":
68
+ return ` ${i + 1}. 导航到 ${action.url}`;
69
+ case "click":
70
+ return ` ${i + 1}. 点击 ${action.ref}`;
71
+ case "type":
72
+ return ` ${i + 1}. 输入 ${action.ref}`;
73
+ case "press":
74
+ return ` ${i + 1}. 按键 ${action.key}`;
75
+ default:
76
+ return ` ${i + 1}. ${action.type}`;
77
+ }
78
+ })
79
+ .join("\n");
80
+ }
81
+
82
+ /**
83
+ * 生成缓存使用确认提示
84
+ */
85
+ generateCacheConfirmationPrompt(entry: PageActionCacheEntry): string {
86
+ return `
87
+ 检测到缓存的操作序列(${entry.cacheLevel} 级):
88
+
89
+ 场景:${entry.scenario}
90
+ 操作数:${entry.actions.length}
91
+ 命中率:${this.cacheStore.getStats().hitRate.toFixed(1)}%
92
+
93
+ 是否使用缓存的操作序列?(这将跳过 LLM 分析,大幅降低 token 消耗)
94
+ `;
95
+ }
96
+
97
+ /**
98
+ * 生成试运行结果
99
+ */
100
+ generateDryRunResult(actions: any[]): string {
101
+ return `
102
+ 【试运行模式】将执行 ${actions.length} 个操作:
103
+
104
+ ${this.formatActionsBrief(actions)}
105
+
106
+ 实际执行时不会执行这些操作。
107
+ `;
108
+ }
109
+
110
+ /**
111
+ * 生成执行结果报告
112
+ */
113
+ generateExecutionReport(
114
+ results: ExecutionResult[],
115
+ force: boolean = false
116
+ ): string {
117
+ const successCount = results.filter((r) => r.success).length;
118
+ const failCount = results.length - successCount;
119
+ const totalTime = results.reduce((sum, r) => sum + r.duration, 0);
120
+
121
+ let message = "\n【执行完成】\n";
122
+ message += `成功:${successCount}/${results.length} 个操作\n`;
123
+ message += `失败:${failCount} 个操作\n`;
124
+ message += `总耗时:${totalTime}ms\n`;
125
+ message += `平均耗时:${(totalTime / results.length).toFixed(0)}ms\n`;
126
+
127
+ if (failCount > 0) {
128
+ message += `\n【失败的操作】\n`;
129
+ results
130
+ .filter((r) => !r.success)
131
+ .forEach((r, i) => {
132
+ message += `${i + 1}. ${r.action}: ${r.error}\n`;
133
+ });
134
+ }
135
+
136
+ message += "\n【详细结果】\n";
137
+ results.forEach((r, i) => {
138
+ const icon = r.success ? "✅" : "❌";
139
+ const time = r.duration ? `(${r.duration}ms)` : "";
140
+ message += `${icon} 操作 ${i + 1}: ${r.action} ${time}\n`;
141
+ });
142
+
143
+ if (force) {
144
+ message += "\n⚠️ 注意:这是强制执行模式,可能执行了过期的缓存操作。\n";
145
+ }
146
+
147
+ return message;
148
+ }
149
+
150
+ /**
151
+ * 生成缓存失效通知
152
+ */
153
+ generateInvalidationNotification(
154
+ changeType: "structure" | "content",
155
+ confidence: number
156
+ ): string {
157
+ const typeText = changeType === "structure" ? "结构变化" : "内容变化";
158
+
159
+ return `
160
+ 【缓存已失效】
161
+
162
+ 检测到页面${typeText}(置信度:${confidence}%)
163
+ 缓存已自动失效,下次操作将重新分析页面。
164
+
165
+ 如需立即执行,可使用强制刷新选项。
166
+ `;
167
+ }
168
+
169
+ /**
170
+ * 生成统计报告
171
+ */
172
+ generateStatsReport(): string {
173
+ const stats = this.cacheStore.getStats();
174
+ const entries = this.cacheStore.listEntries();
175
+
176
+ let message = "\n【缓存统计报告】\n\n";
177
+
178
+ message += "=== 基本统计 ===\n";
179
+ message += `总条目数:${stats.totalEntries}\n`;
180
+ message += `总命中:${stats.totalHits}\n`;
181
+ message += `总未命中:${stats.totalMisses}\n`;
182
+ message += `命中率:${stats.hitRate.toFixed(1)}%\n\n`;
183
+
184
+ message += "=== 分层统计 ===\n";
185
+ message += `L3(场景级)命中:${stats.l3Hits}\n`;
186
+ message += `L2(流程级)命中:${stats.l2Hits}\n`;
187
+ message += `L1(原子级)命中:${stats.l1Hits}\n\n`;
188
+
189
+ message += "=== 效果统计 ===\n";
190
+ message += `节省 Token:${stats.savedTokens}\n`;
191
+ message += `节省时间:${(stats.savedTime / 1000).toFixed(1)}s\n`;
192
+ message += `平均执行时间:${stats.avgExecutionTime}ms\n\n`;
193
+
194
+ message += "=== 场景识别 ===\n";
195
+ message += `场景匹配次数:${stats.scenarioMatches}\n`;
196
+ message += `LLM 分类次数:${stats.llmClassifications}\n`;
197
+ message += `历史学习次数:${stats.learnedAssociations}\n\n`;
198
+
199
+ message += "=== 用户反馈 ===\n";
200
+ message += `用户确认次数:${stats.userConfirmations}\n`;
201
+ message += `用户强制刷新次数:${stats.userForcedRefreshes}\n`;
202
+ message += `缓存执行错误:${stats.cacheErrors}\n\n`;
203
+
204
+ if (entries.length > 0) {
205
+ message += "=== 热门场景 ===\n";
206
+ message += this.cacheStore.getTopScenarios(5) + "\n\n";
207
+
208
+ message += "=== 最近访问 ===\n";
209
+ const recent = entries.slice(0, 10);
210
+ recent.forEach((entry, i) => {
211
+ message += `${i + 1}. ${entry.scenario} (${entry.cacheLevel}) - ${new Date(entry.lastAccessTime).toLocaleString()}\n`;
212
+ });
213
+ }
214
+
215
+ return message;
216
+ }
217
+
218
+ /**
219
+ * 计算成功率
220
+ */
221
+ private calculateSuccessRate(entry: PageActionCacheEntry): number {
222
+ const total = entry.successCount + entry.failCount;
223
+ return total > 0 ? (entry.successCount / total) * 100 : 100;
224
+ }
225
+
226
+ // -------------------------------------------------------------------------
227
+ // 用户反馈记录
228
+ // -------------------------------------------------------------------------
229
+
230
+ /**
231
+ * 记录用户确认
232
+ */
233
+ recordUserConfirmation(): void {
234
+ const stats = this.cacheStore.getStats();
235
+ stats.userConfirmations++;
236
+ }
237
+
238
+ /**
239
+ * 记录用户强制刷新
240
+ */
241
+ recordUserForcedRefresh(): void {
242
+ const stats = this.cacheStore.getStats();
243
+ stats.userForcedRefreshes++;
244
+ }
245
+
246
+ /**
247
+ * 记录缓存错误
248
+ */
249
+ recordCacheError(): void {
250
+ const stats = this.cacheStore.getStats();
251
+ stats.cacheErrors++;
252
+ }
253
+ }
254
+
255
+ // ============================================================================
256
+ // 单例
257
+ // ============================================================================
258
+
259
+ let uxEnhancerInstance: UXEnhancer | null = null;
260
+
261
+ export function getUXEnhancer(): UXEnhancer {
262
+ if (!uxEnhancerInstance) {
263
+ uxEnhancerInstance = new UXEnhancer();
264
+ }
265
+ return uxEnhancerInstance;
266
+ }
@@ -0,0 +1,258 @@
1
+ /**
2
+ * Variable Resolver
3
+ * 变量解析器 - 从用户输入提取变量
4
+ */
5
+
6
+ import type { VariableMap } from "./types.js";
7
+
8
+ // ============================================================================
9
+ // 基础变量解析器
10
+ // ============================================================================
11
+
12
+ /**
13
+ * 变量解析器接口
14
+ */
15
+ interface VariableResolverInterface {
16
+ resolve(userInput: string): VariableMap;
17
+ }
18
+
19
+ // ============================================================================
20
+ // 登录变量解析器
21
+ // ============================================================================
22
+
23
+ /**
24
+ * 登录变量解析器
25
+ */
26
+ class LoginVariableResolver implements VariableResolverInterface {
27
+ resolve(userInput: string): VariableMap {
28
+ const result: VariableMap = {};
29
+
30
+ // 解析 "用户名 xxx 密码 xxx"
31
+ const usernameMatch =
32
+ /(?:用户名|账号|username|user|用户)[::\s]*([\u4e00-\u9fa5a-zA-Z0-9@._+-]+)/gi.exec(
33
+ userInput
34
+ );
35
+ if (usernameMatch) {
36
+ result.username = usernameMatch[1].trim();
37
+ }
38
+
39
+ const passwordMatch =
40
+ /(?:密码|password|pwd|pass)[::\s]*(.+)/gi.exec(userInput);
41
+ if (passwordMatch) {
42
+ result.password = passwordMatch[1].trim();
43
+ }
44
+
45
+ // 尝试提取邮箱格式
46
+ const emailMatch = /[\w.-]+@[\w.-]+\.\w+/.exec(userInput);
47
+ if (emailMatch && !result.username) {
48
+ result.username = emailMatch[0];
49
+ }
50
+
51
+ return result;
52
+ }
53
+ }
54
+
55
+ // ============================================================================
56
+ // 支付变量解析器
57
+ // ============================================================================
58
+
59
+ /**
60
+ * 支付变量解析器
61
+ */
62
+ class CheckoutVariableResolver implements VariableResolverInterface {
63
+ resolve(userInput: string): VariableMap {
64
+ const result: VariableMap = {};
65
+
66
+ // 解析支付方式
67
+ const paymentMatch = /(?:支付方式|payment method)[::\s]*(.+)/gi.exec(
68
+ userInput
69
+ );
70
+ if (paymentMatch) {
71
+ const custom = result.custom as any || {};
72
+ result.custom = { ...custom, paymentMethod: paymentMatch[1].trim() } as any;
73
+ }
74
+
75
+ // 解析金额
76
+ const amountMatch = /(?:金额|amount)[::\s]*([\d.]+)/gi.exec(userInput);
77
+ if (amountMatch) {
78
+ const custom = result.custom as any || {};
79
+ result.custom = { ...custom, amount: amountMatch[1] } as any;
80
+ }
81
+
82
+ return result;
83
+ }
84
+ }
85
+
86
+ // ============================================================================
87
+ // 搜索变量解析器
88
+ // ============================================================================
89
+
90
+ /**
91
+ * 搜索变量解析器
92
+ */
93
+ class SearchVariableResolver implements VariableResolverInterface {
94
+ resolve(userInput: string): VariableMap {
95
+ const result: VariableMap = {};
96
+
97
+ // 解析搜索关键词(提取"搜索"或"找"后面的内容)
98
+ const searchMatch = /(?:搜索|查找|找|search)[::\s]*(.+)/gi.exec(
99
+ userInput
100
+ );
101
+ if (searchMatch) {
102
+ const custom = result.custom as any || {};
103
+ result.custom = { ...custom, query: searchMatch[1].trim() } as any;
104
+ }
105
+
106
+ return result;
107
+ }
108
+ }
109
+
110
+ // ============================================================================
111
+ // 设置变量解析器
112
+ // ============================================================================
113
+
114
+ /**
115
+ * 设置变量解析器
116
+ */
117
+ class SettingsVariableResolver implements VariableResolverInterface {
118
+ resolve(userInput: string): VariableMap {
119
+ const result: VariableMap = {};
120
+
121
+ // 解析键值对
122
+ const kvMatches = [...userInput.matchAll(/(\w+)[::]\s*([^\s]+)/g)];
123
+ for (const match of kvMatches) {
124
+ const custom = result.custom as any || {};
125
+ result.custom = {
126
+ ...custom,
127
+ [match[1]]: match[2].trim(),
128
+ } as any;
129
+ }
130
+
131
+ return result;
132
+ }
133
+ }
134
+
135
+ // ============================================================================
136
+ // 通用变量解析器
137
+ // ============================================================================
138
+
139
+ /**
140
+ * 通用变量解析器
141
+ */
142
+ class GenericVariableResolver implements VariableResolverInterface {
143
+ resolve(userInput: string): VariableMap {
144
+ const result: VariableMap = {};
145
+
146
+ // 尝试提取常见变量
147
+ const patterns = [
148
+ { key: "username", regex: /(?:user|username)[::]\s*(.+)/gi },
149
+ { key: "email", regex: /(?:email|邮箱)[::]\s*(.+)/gi },
150
+ { key: "phone", regex: /(?:phone|电话|手机)[::]\s*(.+)/gi },
151
+ { key: "code", regex: /(?:code|验证码|码)[::]\s*(.+)/gi },
152
+ ];
153
+
154
+ for (const pattern of patterns) {
155
+ const match = pattern.regex.exec(userInput);
156
+ if (match) {
157
+ result[pattern.key] = match[1].trim();
158
+ }
159
+ }
160
+
161
+ // 提取所有键值对
162
+ const kvMatches = [...userInput.matchAll(/(\w+)[::]\s*([^\s]+)/g)];
163
+ for (const match of kvMatches) {
164
+ if (!result[match[1]]) {
165
+ const custom = result.custom as any || {};
166
+ result.custom = { ...custom, [match[1]]: match[2].trim() } as any;
167
+ }
168
+ }
169
+
170
+ return result;
171
+ }
172
+ }
173
+
174
+ // ============================================================================
175
+ // Variable Resolver 类
176
+ // ============================================================================
177
+
178
+ /**
179
+ * 变量解析器
180
+ */
181
+ export class VariableResolver {
182
+ /**
183
+ * 从用户输入提取变量
184
+ */
185
+ resolveVariables(userInput: string, scenario: string): VariableMap {
186
+ const resolver = this.getResolver(scenario);
187
+ return resolver.resolve(userInput);
188
+ }
189
+
190
+ /**
191
+ * 根据场景获取对应的解析器
192
+ */
193
+ private getResolver(scenario: string): VariableResolverInterface {
194
+ switch (scenario) {
195
+ case "login":
196
+ case "login_with_vars":
197
+ return new LoginVariableResolver();
198
+ case "checkout":
199
+ case "payment":
200
+ return new CheckoutVariableResolver();
201
+ case "search":
202
+ return new SearchVariableResolver();
203
+ case "settings":
204
+ return new SettingsVariableResolver();
205
+ default:
206
+ return new GenericVariableResolver();
207
+ }
208
+ }
209
+
210
+ // -------------------------------------------------------------------------
211
+ // 辅助方法
212
+ // -------------------------------------------------------------------------
213
+
214
+ /**
215
+ * 验证变量
216
+ */
217
+ validateVariables(variables: VariableMap, scenario: string): boolean {
218
+ switch (scenario) {
219
+ case "login":
220
+ return !!(variables.username || variables.email);
221
+ case "checkout":
222
+ return true; // 支付场景可能有可选变量
223
+ default:
224
+ return true;
225
+ }
226
+ }
227
+
228
+ /**
229
+ * 合并变量(后覆盖前)
230
+ */
231
+ mergeVariables(
232
+ base: VariableMap,
233
+ override: VariableMap
234
+ ): VariableMap {
235
+ const result: any = {
236
+ ...base,
237
+ ...override,
238
+ };
239
+ // 合并 custom 字段
240
+ const baseCustom = base.custom as any || {};
241
+ const overrideCustom = override.custom as any || {};
242
+ result.custom = { ...baseCustom, ...overrideCustom } as any;
243
+ return result;
244
+ }
245
+ }
246
+
247
+ // ============================================================================
248
+ // 单例
249
+ // ============================================================================
250
+
251
+ let variableResolverInstance: VariableResolver | null = null;
252
+
253
+ export function getVariableResolver(): VariableResolver {
254
+ if (!variableResolverInstance) {
255
+ variableResolverInstance = new VariableResolver();
256
+ }
257
+ return variableResolverInstance;
258
+ }