openclaw-sc 5.38.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/.env.example +13 -0
- package/INSTALL.md +13 -0
- package/LICENSE +233 -0
- package/README.md +123 -0
- package/SECURITY.md +27 -0
- package/index.js +3479 -0
- package/lib/code-review-shared.js +164 -0
- package/lib/config.js +164 -0
- package/lib/constants.js +438 -0
- package/lib/decomposer.js +896 -0
- package/lib/dialog-recall.js +389 -0
- package/lib/env.js +59 -0
- package/lib/level-rules.js +72 -0
- package/lib/log-manager.js +258 -0
- package/lib/preemption.js +278 -0
- package/lib/priority-calc.js +134 -0
- package/lib/prompt-injection.js +748 -0
- package/lib/route-evidence.js +701 -0
- package/lib/shared-fs.js +244 -0
- package/lib/steward-rules.js +648 -0
- package/lib/task-center.js +602 -0
- package/lib/task-chain.js +713 -0
- package/lib/task-checker.js +317 -0
- package/lib/task-profiles.js +255 -0
- package/lib/tcell.js +411 -0
- package/lib/tool-auto-discover.js +522 -0
- package/lib/tool-enrich-subagent.js +375 -0
- package/lib/tool-handlers.js +178 -0
- package/lib/tool-selector.js +459 -0
- package/openclaw.plugin.json +55 -0
- package/package.json +63 -0
- package/security.js +141 -0
- package/tools/bridge.js +3288 -0
- package/tools/dashboard/tk-dashboard.py +262 -0
- package/tools/hippocampus-multi-search.js +1038 -0
- package/tools/hippocampus-sqlite.js +738 -0
- package/tools/hippocampus-store.js +262 -0
- package/tools/mcp-tools.config.json +653 -0
- package/tools/pipeline-engine.js +667 -0
- package/tools/sidecar/_check_tools.py +34 -0
- package/tools/sidecar/sidecar-server.cjs +1360 -0
- package/tools/sidecar/subagent-runner.cjs +1471 -0
- package/tools/system-tools.js +619 -0
- package/vector/README.md +100 -0
- package/vector/usearch-bridge.js +639 -0
- package/vector/usearch-http.js +74 -0
- package/vector/usearch-serve.js +156 -0
- package/workers/worker.js +1419 -0
package/lib/tcell.js
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🛡️ 高频任务缓存 — 高频任务s级缓存重放系统
|
|
3
|
+
*
|
|
4
|
+
* 核心逻辑:
|
|
5
|
+
* 1. 每次成功任务记录模式签名 hash(任务类型 + 关键词)
|
|
6
|
+
* 2. 缓存命中直接跳转到对应工具,绕过完整路由
|
|
7
|
+
* 3. LRU + 成功率驱逐(低于60%自动淘汰)
|
|
8
|
+
*
|
|
9
|
+
* v1.0 — 2026-05-31
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { join, dirname } from "path";
|
|
13
|
+
import { homedir } from "os";
|
|
14
|
+
import { readFile, writeFile, mkdir, readdir, unlink, stat, rename } from "fs/promises";
|
|
15
|
+
import crypto from "crypto";
|
|
16
|
+
|
|
17
|
+
// ====== 常量 ======
|
|
18
|
+
const TCELL_DIR = join(homedir(), '.openclaw', 'workspace', 'memory', 'shared', 'tcell');
|
|
19
|
+
const CACHE_FILE = join(TCELL_DIR, 'cache.json');
|
|
20
|
+
// 🧠 设计决策:MAX_CACHE_ENTRIES=500。缓存目上限——管理员从200调到500。
|
|
21
|
+
// 40+工具每个约12种参数组合≈480,500刚好够覆盖。
|
|
22
|
+
// 过大(1000+)则persistI/O变慢,过小(50)则命中率不够。
|
|
23
|
+
const MAX_CACHE_ENTRIES = 500; // 最大缓存目(管理员从200调到500,40+工具够用)
|
|
24
|
+
const MIN_SUCCESS_RATE = 0.6; // 最低成功率(低于此值自动驱逐)
|
|
25
|
+
const EVICTION_SCAN_INTERVAL = 20; // 每20次操作扫描一次驱逐
|
|
26
|
+
const LRU_HALF_LIFE_MS = 3600000; // LRU时间衰减半周期1小时
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 计算模式签名
|
|
30
|
+
* @param {string} taskType - 任务类型 (如 cpu_search, cpu_orchestrate)
|
|
31
|
+
* @param {string} keywords - 关键词摘要 (从任务描述中提取的前80字)
|
|
32
|
+
* @returns {string} SHA256 hex 前16位
|
|
33
|
+
*/
|
|
34
|
+
export function computeSignature(taskType, keywords) {
|
|
35
|
+
const source = `${taskType}::${(keywords || '').trim().toLowerCase()}`;
|
|
36
|
+
return crypto.createHash('sha256').update(source, 'utf-8').digest('hex').substring(0, 16);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 从任务描述中提取关键词(去停用词,取前80字符)
|
|
41
|
+
* @param {string} taskDesc - 原始任务描述
|
|
42
|
+
* @returns {string} 精简关键词
|
|
43
|
+
*/
|
|
44
|
+
export function extractKeywords(taskDesc) {
|
|
45
|
+
if (!taskDesc || typeof taskDesc !== 'string') return '';
|
|
46
|
+
// 取前120字符,去除非内容行
|
|
47
|
+
const lines = taskDesc.split('\n')
|
|
48
|
+
.map(l => l.trim())
|
|
49
|
+
.filter(l => l && !l.startsWith('⚡') && !l.startsWith('🏃') && !l.startsWith('思考') && !l.startsWith('---') && !l.startsWith('```'))
|
|
50
|
+
.slice(0, 3);
|
|
51
|
+
return lines.join(' ').substring(0, 80);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 高频任务快速路径缓存
|
|
56
|
+
*/
|
|
57
|
+
class FastPathCache {
|
|
58
|
+
constructor() {
|
|
59
|
+
this.cache = new Map(); // signature → { tool, taskType, keywords, successCount, failCount, lastAccess, createdAt }
|
|
60
|
+
this.hitCount = 0;
|
|
61
|
+
this.missCount = 0;
|
|
62
|
+
this.evictionCount = 0;
|
|
63
|
+
this.initialized = false;
|
|
64
|
+
this._opCount = 0;
|
|
65
|
+
// 🧠 实例级 MIN_SUCCESS_RATE,默认使用模块常量。
|
|
66
|
+
// createEnrichTcell 传入独立阈值 0.75 覆盖此值(enrichment layer需求需独立阈值)。
|
|
67
|
+
// 设计原因:不同 Tcell 实例需要不同的成功率阈值,
|
|
68
|
+
// 但原 lookup()/recordResult()/_evictStale() 直接引用模块级常量,
|
|
69
|
+
// 导致实例级覆盖(如 createEnrichTcell 设 tcell.MIN_SUCCESS_RATE=0.75)不生效。
|
|
70
|
+
// 改用 this.MIN_SUCCESS_RATE 让每个实例自己决定淘汰线。
|
|
71
|
+
this.MIN_SUCCESS_RATE = MIN_SUCCESS_RATE;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 初始化:从磁盘加载缓存
|
|
76
|
+
*/
|
|
77
|
+
async init() {
|
|
78
|
+
await mkdir(TCELL_DIR, { recursive: true }).catch((err) => {
|
|
79
|
+
console.warn(`[sc] ⚠️ mkdir TCELL_DIR失败: ${err?.message || '未知错误'}`);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// 支持实例级缓存文件路径(enrichment layer使用独立文件防污染)
|
|
83
|
+
// 当 this.CACHE_FILE 未设置时,回退到模块级 CACHE_FILE
|
|
84
|
+
const cacheFile = this.CACHE_FILE || CACHE_FILE;
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const raw = await readFile(cacheFile, 'utf-8');
|
|
88
|
+
const data = JSON.parse(raw);
|
|
89
|
+
if (Array.isArray(data)) {
|
|
90
|
+
for (const entry of data) {
|
|
91
|
+
if (entry.signature && entry.tool) {
|
|
92
|
+
this.cache.set(entry.signature, {
|
|
93
|
+
tool: entry.tool,
|
|
94
|
+
taskType: entry.taskType || '',
|
|
95
|
+
keywords: entry.keywords || '',
|
|
96
|
+
successCount: entry.successCount || 0,
|
|
97
|
+
failCount: entry.failCount || 0,
|
|
98
|
+
lastAccess: entry.lastAccess || 0,
|
|
99
|
+
createdAt: entry.createdAt || 0,
|
|
100
|
+
params: entry.params || {},
|
|
101
|
+
avgDurationMs: entry.avgDurationMs || 0,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// TODO: 移除调试日志 console.log(`[sc] 🛡️ fast-path cache已加载: ${this.cache.size} 个缓存目`);
|
|
106
|
+
}
|
|
107
|
+
} catch {
|
|
108
|
+
// TODO: 移除调试日志 console.log('[sc] 🛡️ fast-path cache无缓存数据,从头开始');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
this.initialized = true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 从任务描述生成签名
|
|
116
|
+
* @param {string} taskType - 任务类型
|
|
117
|
+
* @param {string} taskDesc - 完整任务描述
|
|
118
|
+
* @returns {string} 签名
|
|
119
|
+
*/
|
|
120
|
+
makeSignature(taskType, taskDesc) {
|
|
121
|
+
return computeSignature(taskType, extractKeywords(taskDesc));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 查询缓存(命中即返回缓存结果,更新LRU)
|
|
126
|
+
* @param {string} signature - 模式签名
|
|
127
|
+
* @returns {{ hit: boolean, entry: object|null, bypassRoute: boolean }}
|
|
128
|
+
*/
|
|
129
|
+
lookup(signature) {
|
|
130
|
+
if (!this.initialized || !signature) {
|
|
131
|
+
this.missCount++;
|
|
132
|
+
return { hit: false, entry: null, bypassRoute: false };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const entry = this.cache.get(signature);
|
|
136
|
+
if (!entry) {
|
|
137
|
+
this.missCount++;
|
|
138
|
+
return { hit: false, entry: null, bypassRoute: false };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 检查成功率
|
|
142
|
+
const totalCalls = entry.successCount + entry.failCount;
|
|
143
|
+
const successRate = totalCalls > 0 ? entry.successCount / totalCalls : 0;
|
|
144
|
+
|
|
145
|
+
if (successRate < this.MIN_SUCCESS_RATE) {
|
|
146
|
+
// 成功率不足,自动驱逐
|
|
147
|
+
this.cache.delete(signature);
|
|
148
|
+
this.evictionCount++;
|
|
149
|
+
this.missCount++;
|
|
150
|
+
this._persist();
|
|
151
|
+
return { hit: false, entry: null, bypassRoute: false, evicted: true, reason: `成功率 ${(successRate*100).toFixed(1)}% < ${(this.MIN_SUCCESS_RATE*100)}%` };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// LRU更新
|
|
155
|
+
entry.lastAccess = Date.now();
|
|
156
|
+
this.hitCount++;
|
|
157
|
+
|
|
158
|
+
// 缓存命中 → 跳过路由
|
|
159
|
+
return {
|
|
160
|
+
hit: true,
|
|
161
|
+
entry: {
|
|
162
|
+
tool: entry.tool,
|
|
163
|
+
taskType: entry.taskType,
|
|
164
|
+
keywords: entry.keywords,
|
|
165
|
+
successRate,
|
|
166
|
+
totalCalls,
|
|
167
|
+
avgDurationMs: entry.avgDurationMs,
|
|
168
|
+
params: entry.params || {},
|
|
169
|
+
},
|
|
170
|
+
bypassRoute: true,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* 记录缓存命中后的执行结果
|
|
176
|
+
* @param {string} signature - 模式签名
|
|
177
|
+
* @param {boolean} success - 是否成功
|
|
178
|
+
* @param {number} durationMs - 耗时
|
|
179
|
+
*/
|
|
180
|
+
recordResult(signature, success, durationMs) {
|
|
181
|
+
if (!signature) return;
|
|
182
|
+
|
|
183
|
+
const entry = this.cache.get(signature);
|
|
184
|
+
if (!entry) return;
|
|
185
|
+
|
|
186
|
+
if (success) {
|
|
187
|
+
entry.successCount++;
|
|
188
|
+
// 滚动平均耗时
|
|
189
|
+
if (entry.avgDurationMs > 0) {
|
|
190
|
+
entry.avgDurationMs = Math.round((entry.avgDurationMs * 0.7) + (durationMs || 0) * 0.3);
|
|
191
|
+
} else {
|
|
192
|
+
entry.avgDurationMs = durationMs || 0;
|
|
193
|
+
}
|
|
194
|
+
} else {
|
|
195
|
+
entry.failCount++;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
entry.lastAccess = Date.now();
|
|
199
|
+
|
|
200
|
+
// 检查是否需要驱逐
|
|
201
|
+
const total = entry.successCount + entry.failCount;
|
|
202
|
+
if (total >= 3) {
|
|
203
|
+
const rate = entry.successCount / total;
|
|
204
|
+
if (rate < this.MIN_SUCCESS_RATE) {
|
|
205
|
+
this.cache.delete(signature);
|
|
206
|
+
this.evictionCount++;
|
|
207
|
+
// TODO: 移除调试日志 console.log(`[sc] 🛡️ fast-path cache驱逐: ${signature} (成功率 ${(rate*100).toFixed(1)}% < ${(this.MIN_SUCCESS_RATE * 100).toFixed(0)}%)`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
this._opCount++;
|
|
212
|
+
if (this._opCount % EVICTION_SCAN_INTERVAL === 0) {
|
|
213
|
+
this._evictStale();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
this._persist();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* 记录新的缓存目
|
|
221
|
+
* @param {string} signature - 模式签名
|
|
222
|
+
* @param {string} tool - 路由到的工具
|
|
223
|
+
* @param {string} taskType - 任务类型
|
|
224
|
+
* @param {string} taskDesc - 任务描述
|
|
225
|
+
* @param {object} params - 相关参数
|
|
226
|
+
*/
|
|
227
|
+
add(signature, tool, taskType, taskDesc, params = {}) {
|
|
228
|
+
if (!this.initialized || !signature || !tool) return;
|
|
229
|
+
if (this.cache.has(signature)) return; // 已存在不覆盖
|
|
230
|
+
|
|
231
|
+
const keywords = extractKeywords(taskDesc);
|
|
232
|
+
const entry = {
|
|
233
|
+
tool,
|
|
234
|
+
taskType: taskType || '',
|
|
235
|
+
keywords,
|
|
236
|
+
successCount: 1, // 首次记录视为成功
|
|
237
|
+
failCount: 0,
|
|
238
|
+
lastAccess: Date.now(),
|
|
239
|
+
createdAt: Date.now(),
|
|
240
|
+
params,
|
|
241
|
+
avgDurationMs: 0,
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
this.cache.set(signature, entry);
|
|
245
|
+
|
|
246
|
+
// 超过上限时驱逐最久未访问的
|
|
247
|
+
if (this.cache.size > MAX_CACHE_ENTRIES) {
|
|
248
|
+
this._evictLRU();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
this._persist();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* 获取缓存stats摘要
|
|
256
|
+
*/
|
|
257
|
+
getStats() {
|
|
258
|
+
const entries = [];
|
|
259
|
+
for (const [sig, entry] of this.cache) {
|
|
260
|
+
const total = entry.successCount + entry.failCount;
|
|
261
|
+
entries.push({
|
|
262
|
+
signature: sig.substring(0, 12) + '...',
|
|
263
|
+
tool: entry.tool,
|
|
264
|
+
taskType: entry.taskType,
|
|
265
|
+
totalCalls: total,
|
|
266
|
+
successRate: total > 0 ? (entry.successCount / total * 100).toFixed(1) + '%' : 'N/A',
|
|
267
|
+
lastAccess: entry.lastAccess ? Math.round((Date.now() - entry.lastAccess) / 60000) + 'm ago' : 'never',
|
|
268
|
+
avgDurationMs: entry.avgDurationMs,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// 按上次访问时间降序
|
|
273
|
+
entries.sort((a, b) => {
|
|
274
|
+
const aTime = a.lastAccess === 'never' ? 0 : parseInt(a.lastAccess);
|
|
275
|
+
const bTime = b.lastAccess === 'never' ? 0 : parseInt(b.lastAccess);
|
|
276
|
+
return aTime - bTime;
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
totalEntries: this.cache.size,
|
|
281
|
+
maxEntries: MAX_CACHE_ENTRIES,
|
|
282
|
+
hitCount: this.hitCount,
|
|
283
|
+
missCount: this.missCount,
|
|
284
|
+
hitRate: (this.hitCount + this.missCount) > 0
|
|
285
|
+
? (this.hitCount / (this.hitCount + this.missCount) * 100).toFixed(1) + '%'
|
|
286
|
+
: '0.0%',
|
|
287
|
+
evictionCount: this.evictionCount,
|
|
288
|
+
recentEntries: entries.slice(0, 20),
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* 检查特定任务是否在缓存中(用于before路由判断)
|
|
294
|
+
*/
|
|
295
|
+
check(taskType, taskDesc) {
|
|
296
|
+
const signature = this.makeSignature(taskType, taskDesc);
|
|
297
|
+
return this.lookup(signature);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* LRU驱逐:淘汰最久未访问的
|
|
302
|
+
*/
|
|
303
|
+
_evictLRU() {
|
|
304
|
+
let oldestSig = null;
|
|
305
|
+
let oldestTime = Infinity;
|
|
306
|
+
for (const [sig, entry] of this.cache) {
|
|
307
|
+
if (entry.lastAccess < oldestTime) {
|
|
308
|
+
oldestTime = entry.lastAccess;
|
|
309
|
+
oldestSig = sig;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (oldestSig) {
|
|
313
|
+
this.cache.delete(oldestSig);
|
|
314
|
+
this.evictionCount++;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* 扫描并驱逐过期目(低成功率 + 7天未访问)
|
|
320
|
+
*/
|
|
321
|
+
_evictStale() {
|
|
322
|
+
const now = Date.now();
|
|
323
|
+
const sevenDays = 7 * 24 * 3600 * 1000;
|
|
324
|
+
let evicted = 0;
|
|
325
|
+
|
|
326
|
+
for (const [sig, entry] of this.cache) {
|
|
327
|
+
// 7天未访问
|
|
328
|
+
if (now - entry.lastAccess > sevenDays) {
|
|
329
|
+
this.cache.delete(sig);
|
|
330
|
+
evicted++;
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// 低于成功率
|
|
335
|
+
const total = entry.successCount + entry.failCount;
|
|
336
|
+
if (total >= 3) {
|
|
337
|
+
const rate = entry.successCount / total;
|
|
338
|
+
if (rate < this.MIN_SUCCESS_RATE) {
|
|
339
|
+
this.cache.delete(sig);
|
|
340
|
+
evicted++;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (evicted > 0) {
|
|
346
|
+
this.evictionCount += evicted;
|
|
347
|
+
// TODO: 移除调试日志 console.log(`[sc] 🛡️ fast-path cache扫描驱逐: ${evicted} 个过期目`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* persist到磁盘
|
|
353
|
+
*/
|
|
354
|
+
async _persist() {
|
|
355
|
+
// 🧠 串行化写文件:后发写入排队等前一次完成,避免并发 writeFile 竞争。
|
|
356
|
+
// 设计原因:多个连续 _persist() 调用时,后写的完成可能比先写的早,
|
|
357
|
+
// 最后磁盘上存的是旧数据(写文件竞争)。
|
|
358
|
+
// 用 Promise 链保证每次写入严格顺序执行。
|
|
359
|
+
if (!this._persistLock) {
|
|
360
|
+
this._persistLock = Promise.resolve();
|
|
361
|
+
}
|
|
362
|
+
this._persistLock = this._persistLock.then(async () => {
|
|
363
|
+
try {
|
|
364
|
+
const data = [];
|
|
365
|
+
for (const [signature, entry] of this.cache) {
|
|
366
|
+
data.push({ signature, ...entry });
|
|
367
|
+
}
|
|
368
|
+
await mkdir(TCELL_DIR, { recursive: true }).catch((err) => {
|
|
369
|
+
console.warn(`[sc] ⚠️ mkdir TCELL_DIR失败(persist): ${err?.message || '未知错误'}`);
|
|
370
|
+
});
|
|
371
|
+
// 支持实例级缓存文件路径(enrichment layer使用独立文件防污染)
|
|
372
|
+
// 当 this.CACHE_FILE 未设置时,回退到模块级 CACHE_FILE
|
|
373
|
+
const cacheFile = this.CACHE_FILE || CACHE_FILE;
|
|
374
|
+
await writeFile(cacheFile, JSON.stringify(data, null, 2), 'utf-8');
|
|
375
|
+
} catch (err) {
|
|
376
|
+
console.warn(`[sc] ⚠️ fast-path cachepersist失败: ${err.message}`);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
return this._persistLock;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// 单例
|
|
384
|
+
// ====== enrichment layer专用 Tcell 实例(独立阈值 0.75 + 独立persist文件) ======
|
|
385
|
+
// 与主 tcell 完全隔离,不共享签名空间。
|
|
386
|
+
// 设计原因:原 tcell 服务于 core_routeTask 的高频任务缓存,命中后绕过路由整个流程。
|
|
387
|
+
// enrichment layer tcell 服务于 enrichSubagentTask 的签名缓存,命中后跳过 enrich 逻辑直接返回推荐。
|
|
388
|
+
// 两者任务类型完全不同,混用会导致签名空间污染。
|
|
389
|
+
// 原 tcell 阈值为 0.6(成功率淘汰线),enrichment layer需要 0.75(置信度触发线)。
|
|
390
|
+
const ENRICH_THRESHOLD = 0.75;
|
|
391
|
+
const ENRICH_CACHE_FILE = join(TCELL_DIR, 'enrich-cache.json');
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* 创建enrichment layer专用 Tcell 实例
|
|
395
|
+
* - 独立阈值:0.75(原 tcell 为 0.6)
|
|
396
|
+
* - 独立persist文件:enrich-cache.json(不污染主 cache.json)
|
|
397
|
+
* - 实例完全隔离,后续可独立调参
|
|
398
|
+
*
|
|
399
|
+
* @returns {Promise<FastPathCache>} 配置好的 enrichTcell 实例
|
|
400
|
+
*/
|
|
401
|
+
export async function createEnrichTcell() {
|
|
402
|
+
const tcell = new FastPathCache();
|
|
403
|
+
tcell.MIN_SUCCESS_RATE = ENRICH_THRESHOLD; // 覆盖阈值为 0.75
|
|
404
|
+
tcell.CACHE_FILE = ENRICH_CACHE_FILE; // 独立缓存文件防签名空间污染
|
|
405
|
+
await tcell.init(); // 从磁盘加载
|
|
406
|
+
return tcell;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
export const tcell = new FastPathCache();
|
|
410
|
+
|
|
411
|
+
export default tcell;
|