deeper-cli 1.2.4 → 1.3.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/dist/cli/index.js CHANGED
@@ -1131,44 +1131,100 @@ function uid() {
1131
1131
  function ensureDir() {
1132
1132
  if (!existsSync5(MEM_DIR)) mkdirSync4(MEM_DIR, { recursive: true });
1133
1133
  if (!existsSync5(STATS_FILE)) {
1134
- writeFileSync3(STATS_FILE, JSON.stringify({ totalEntries: 0, byType: {}, totalTokens: 0, lastCleanup: Date.now() }), "utf-8");
1134
+ writeFileSync3(STATS_FILE, JSON.stringify({ totalEntries: 0, byType: {}, totalTokens: 0, lastCleanup: Date.now(), lastConsolidate: Date.now() }, null, 2), "utf-8");
1135
1135
  }
1136
1136
  }
1137
1137
  function loadStats() {
1138
1138
  try {
1139
1139
  return JSON.parse(readFileSync4(STATS_FILE, "utf-8"));
1140
1140
  } catch {
1141
- return { totalEntries: 0, byType: {}, totalTokens: 0, lastCleanup: 0 };
1141
+ return { totalEntries: 0, byType: {}, totalTokens: 0, lastCleanup: 0, lastConsolidate: 0 };
1142
1142
  }
1143
1143
  }
1144
1144
  function saveStats(s) {
1145
1145
  writeFileSync3(STATS_FILE, JSON.stringify(s, null, 2), "utf-8");
1146
1146
  }
1147
- var MEM_DIR, STATS_FILE, MAX_WORKING_MEM, MAX_TOTAL_MEM, CLEANUP_THRESHOLD, currentSessionId, XMemory, xmemory;
1147
+ function tokenize(text) {
1148
+ const lower = text.toLowerCase();
1149
+ const words = lower.split(/[\s\p{P}\p{S}]+/).filter((w) => w.length >= 2);
1150
+ const bigrams = [];
1151
+ for (let i = 0; i < words.length - 1; i++) bigrams.push(words[i] + "_" + words[i + 1]);
1152
+ return [.../* @__PURE__ */ new Set([...words, ...bigrams])];
1153
+ }
1154
+ function similarity(a, b2) {
1155
+ if (a === b2) return 1;
1156
+ if (!a || !b2) return 0;
1157
+ const ta = tokenize(a);
1158
+ const tb = tokenize(b2);
1159
+ if (ta.length === 0 || tb.length === 0) return 0;
1160
+ const sa = new Set(ta);
1161
+ const sb = new Set(tb);
1162
+ let intersection = 0;
1163
+ for (const t of sa) if (sb.has(t)) intersection++;
1164
+ const union = Math.max(sa.size, sb.size);
1165
+ return union > 0 ? intersection / union : 0;
1166
+ }
1167
+ var MEM_DIR, STATS_FILE, MAX_WORKING_MEM, MAX_TOTAL_MEM, CLEANUP_THRESHOLD, DEDUP_SIMILARITY_THRESHOLD, CONSOLIDATE_THRESHOLD, SAVE_DEBOUNCE_MS, currentSessionId, TFIDFIndexer, XMemory, xmemory;
1148
1168
  var init_xmemory = __esm({
1149
1169
  "src/memory/xmemory.ts"() {
1150
1170
  "use strict";
1151
1171
  init_constants();
1152
1172
  MEM_DIR = join4(DEEPER_HOME, "xmemory");
1153
1173
  STATS_FILE = join4(MEM_DIR, "stats.json");
1154
- MAX_WORKING_MEM = 50;
1155
- MAX_TOTAL_MEM = 2e3;
1156
- CLEANUP_THRESHOLD = 1500;
1174
+ MAX_WORKING_MEM = 100;
1175
+ MAX_TOTAL_MEM = 3e3;
1176
+ CLEANUP_THRESHOLD = 2e3;
1177
+ DEDUP_SIMILARITY_THRESHOLD = 0.85;
1178
+ CONSOLIDATE_THRESHOLD = 5;
1179
+ SAVE_DEBOUNCE_MS = 3e3;
1157
1180
  currentSessionId = "";
1181
+ TFIDFIndexer = class {
1182
+ docFreq = /* @__PURE__ */ new Map();
1183
+ numDocs = 0;
1184
+ build(entries) {
1185
+ this.docFreq.clear();
1186
+ this.numDocs = 0;
1187
+ for (const entry of entries) {
1188
+ this.numDocs++;
1189
+ const tokens = new Set(tokenize(entry.content));
1190
+ for (const t of tokens) this.docFreq.set(t, (this.docFreq.get(t) || 0) + 1);
1191
+ }
1192
+ }
1193
+ idf(term) {
1194
+ const df = this.docFreq.get(term) || 0;
1195
+ if (df === 0 || df >= this.numDocs) return 0;
1196
+ return Math.log((this.numDocs + 0.5) / (df + 0.5)) + 1;
1197
+ }
1198
+ tfidfScore(content, queryTerms) {
1199
+ const tokens = tokenize(content);
1200
+ const tfMap = /* @__PURE__ */ new Map();
1201
+ for (const t of tokens) tfMap.set(t, (tfMap.get(t) || 0) + 1);
1202
+ const maxTf = Math.max(...tfMap.values(), 1);
1203
+ let score = 0;
1204
+ for (const qt of queryTerms) {
1205
+ const tf = (tfMap.get(qt) || 0) / maxTf * (1 + Math.log(tokens.length));
1206
+ score += tf * this.idf(qt);
1207
+ }
1208
+ return score;
1209
+ }
1210
+ };
1158
1211
  XMemory = class {
1159
1212
  working = [];
1160
1213
  index = /* @__PURE__ */ new Map();
1161
1214
  dirty = false;
1215
+ saveTimer = null;
1216
+ tfidf = new TFIDFIndexer();
1162
1217
  constructor() {
1163
1218
  ensureDir();
1164
1219
  }
1165
- // ============ 写入 ============
1166
1220
  store(type, content, tags = [], importance = 5, accuracy = 7, source = "agent", references = []) {
1221
+ const deduped = this.deduplicate(type, content, importance);
1222
+ if (deduped !== null) return deduped;
1167
1223
  const id = uid();
1168
1224
  const entry = {
1169
1225
  id,
1170
1226
  type,
1171
- content: content.slice(0, 2e3),
1227
+ content: content.slice(0, 4e3),
1172
1228
  tags,
1173
1229
  importance: Math.min(10, Math.max(0, importance)),
1174
1230
  accuracy: Math.min(10, Math.max(0, accuracy)),
@@ -1181,12 +1237,11 @@ var init_xmemory = __esm({
1181
1237
  };
1182
1238
  if (type === "working") {
1183
1239
  this.working.push(entry);
1184
- if (this.working.length > MAX_WORKING_MEM) {
1185
- this.working.shift();
1186
- }
1240
+ this.evictWorking();
1187
1241
  }
1188
1242
  this.index.set(id, entry);
1189
1243
  this.dirty = true;
1244
+ this.scheduleSave();
1190
1245
  if (this.index.size > CLEANUP_THRESHOLD) {
1191
1246
  this.autoCleanup();
1192
1247
  }
@@ -1204,31 +1259,41 @@ var init_xmemory = __esm({
1204
1259
  storeWorking(content, tags = []) {
1205
1260
  return this.store("working", content, tags, 3, 5, "agent");
1206
1261
  }
1207
- // ============ 检索 ============
1208
1262
  recall(query, limit = 5, minImportance = 0) {
1209
- const keywords = query.toLowerCase().split(/[\s,,。]+/).filter((w) => w.length > 1);
1263
+ const keywords = tokenize(query).filter((w) => w.length >= 2);
1210
1264
  if (keywords.length === 0) return [];
1211
- const scored = [];
1265
+ this.tfidf.build(this.index.values());
1212
1266
  const now = Date.now();
1267
+ const scored = [];
1213
1268
  for (const entry of this.index.values()) {
1214
1269
  if (entry.importance < minImportance) continue;
1215
- let score = 0;
1216
- const c2 = (entry.content || "").toLowerCase();
1270
+ const c2 = entry.content.toLowerCase();
1217
1271
  const t = entry.tags.join(" ").toLowerCase();
1272
+ let keywordScore = 0;
1273
+ let exactMatchBonus = 0;
1218
1274
  for (const kw of keywords) {
1219
- if (c2.includes(kw)) score += 3;
1220
- if (t.includes(kw)) score += 2;
1221
- if (entry.type === "procedural" && c2.includes(kw)) score += 1;
1222
- }
1223
- const age = (now - entry.accessedAt) / (1e3 * 60 * 60);
1224
- score += entry.importance * 0.5;
1225
- score -= Math.min(age / 24, 5);
1226
- if (score > 0) scored.push({ entry, score });
1275
+ if (c2.includes(kw)) {
1276
+ keywordScore += 3;
1277
+ exactMatchBonus += 1;
1278
+ }
1279
+ if (t.includes(kw)) keywordScore += 2;
1280
+ if (entry.type === "procedural" && c2.includes(kw)) keywordScore += 1;
1281
+ if (entry.type === "semantic" && c2.includes(kw)) keywordScore += 1.5;
1282
+ }
1283
+ const tfidfScore = this.tfidf.tfidfScore(entry.content, keywords);
1284
+ const recencyBoost = Math.max(0, 3 - (now - entry.accessedAt) / (1e3 * 60 * 30));
1285
+ const frequencyBoost = Math.min(entry.accessCount * 0.3, 4);
1286
+ const importanceWeight = entry.importance * 0.6;
1287
+ const ageHours = (now - entry.createdAt) / (1e3 * 60 * 60);
1288
+ const ageDecay = Math.min(ageHours / 48, 6);
1289
+ const totalScore = keywordScore * 1 + tfidfScore * 2 + importanceWeight + recencyBoost + frequencyBoost - ageDecay + (exactMatchBonus >= 3 ? 3 : 0);
1290
+ if (totalScore > 0) scored.push({ entry, score: totalScore });
1227
1291
  }
1228
1292
  scored.sort((a, b2) => b2.score - a.score);
1229
1293
  return scored.slice(0, limit).map((s) => {
1230
1294
  s.entry.accessedAt = now;
1231
1295
  s.entry.accessCount++;
1296
+ s.entry.importance = Math.min(10, s.entry.importance + 0.1);
1232
1297
  return s.entry;
1233
1298
  });
1234
1299
  }
@@ -1237,18 +1302,21 @@ var init_xmemory = __esm({
1237
1302
  for (const entry of this.index.values()) {
1238
1303
  if (entry.type === type) res.push(entry);
1239
1304
  }
1240
- res.sort((a, b2) => b2.createdAt - a.createdAt);
1305
+ res.sort((a, b2) => b2.importance * 2 + b2.accessCount - (a.importance * 2 + a.accessCount));
1241
1306
  return res.slice(0, limit);
1242
1307
  }
1243
1308
  getWorking() {
1244
- return this.working;
1309
+ return this.working.sort(
1310
+ (a, b2) => b2.importance * 2 + b2.accessCount - (a.importance * 2 + a.accessCount)
1311
+ );
1245
1312
  }
1246
- getWorkingContext(maxTokens = 2e3) {
1313
+ getWorkingContext(maxTokens = 4e3) {
1247
1314
  if (this.working.length === 0) return "";
1248
- const lines = this.working.map((e) => `[\u5DE5\u4F5C\u8BB0\u5FC6] ${e.content.slice(0, 300)}`);
1315
+ const sorted = this.getWorking();
1316
+ const lines = sorted.map((e) => `[${e.source}] ${e.content.slice(0, 500)}`);
1249
1317
  let result = "";
1250
1318
  for (const line of lines) {
1251
- if ((result + line).length > maxTokens * 4) break;
1319
+ if ((result + line).length > maxTokens * 3) break;
1252
1320
  result += line + "\n";
1253
1321
  }
1254
1322
  return result;
@@ -1257,19 +1325,34 @@ var init_xmemory = __esm({
1257
1325
  const recalled = this.recall(task, limit, 3);
1258
1326
  const proc = recalled.filter((r2) => r2.type === "procedural" || r2.type === "semantic");
1259
1327
  if (proc.length === 0) return "";
1260
- return "[\u8BB0\u5FC6\u63D0\u793A]\n" + proc.map((p) => `- ${p.content.slice(0, 250)}`).join("\n");
1328
+ return "[\u8BB0\u5FC6\u63D0\u793A]\n" + proc.map((p) => `- [${p.type === "procedural" ? "\u6280\u80FD" : "\u77E5\u8BC6"}] ${p.content.slice(0, 350)}`).join("\n");
1261
1329
  }
1262
1330
  getSessionSummary() {
1263
1331
  const entries = [...this.index.values()].filter((e) => e.sessionId === currentSessionId);
1264
1332
  if (entries.length === 0) return "";
1265
- const key = entries.filter((e) => e.importance >= 5).sort((a, b2) => b2.importance - a.importance).slice(0, 8);
1266
- return `[XMemory\xB7\u672C\u4F1A\u8BDD]
1267
- ` + key.map((e) => {
1268
- const t = e.type === "semantic" ? "\u77E5\u8BC6" : e.type === "procedural" ? "\u6280\u80FD" : e.type === "episodic" ? "\u7ECF\u5386" : "\u5DE5\u4F5C";
1269
- return `[${t}] ${e.content.slice(0, 200)}`;
1270
- }).join("\n");
1271
- }
1272
- // ============ 持久化 ============
1333
+ const byType = { semantic: [], procedural: [], episodic: [], working: [] };
1334
+ for (const e of entries) {
1335
+ if (byType[e.type]) byType[e.type].push(e);
1336
+ }
1337
+ const parts = [];
1338
+ const keySemantic = byType.semantic.sort((a, b2) => b2.importance - a.importance).slice(0, 5);
1339
+ if (keySemantic.length > 0) {
1340
+ parts.push(`[\u77E5\u8BC6\u8BB0\u5FC6\xB7${keySemantic.length}\u6761]
1341
+ ` + keySemantic.map((e) => `\u2022 ${e.content.slice(0, 250)}`).join("\n"));
1342
+ }
1343
+ const keyProcedural = byType.procedural.sort((a, b2) => b2.importance - a.importance).slice(0, 5);
1344
+ if (keyProcedural.length > 0) {
1345
+ parts.push(`[\u6280\u80FD\u8BB0\u5FC6\xB7${keyProcedural.length}\u6761]
1346
+ ` + keyProcedural.map((e) => `\u2022 ${e.content.slice(0, 250)}`).join("\n"));
1347
+ }
1348
+ const keyEpisodic = byType.episodic.sort((a, b2) => b2.importance - a.importance).slice(0, 3);
1349
+ if (keyEpisodic.length > 0) {
1350
+ parts.push(`[\u7ECF\u5386\u8BB0\u5FC6\xB7${keyEpisodic.length}\u6761]
1351
+ ` + keyEpisodic.map((e) => `\u2192 ${e.content.slice(0, 150)}`).join("\n"));
1352
+ }
1353
+ return `[XMemory\xB7\u672C\u4F1A\u8BDD ${entries.length}\u6761]
1354
+ ${parts.join("\n")}`;
1355
+ }
1273
1356
  async save() {
1274
1357
  if (!this.dirty) return;
1275
1358
  ensureDir();
@@ -1280,7 +1363,7 @@ var init_xmemory = __esm({
1280
1363
  }
1281
1364
  for (const [type, entries] of Object.entries(byType)) {
1282
1365
  const file = join4(MEM_DIR, `${type}.json`);
1283
- writeFileSync3(file, JSON.stringify(entries.slice(-500)), "utf-8");
1366
+ writeFileSync3(file, JSON.stringify(entries.slice(-800)), "utf-8");
1284
1367
  }
1285
1368
  const stats = loadStats();
1286
1369
  stats.totalEntries = this.index.size;
@@ -1312,16 +1395,112 @@ var init_xmemory = __esm({
1312
1395
  if (this.working.length > MAX_WORKING_MEM) {
1313
1396
  this.working = this.working.slice(-MAX_WORKING_MEM);
1314
1397
  }
1398
+ this.consolidateIfNeeded();
1399
+ }
1400
+ scheduleSave() {
1401
+ if (this.saveTimer) return;
1402
+ this.saveTimer = setTimeout(() => {
1403
+ this.saveTimer = null;
1404
+ this.save().catch(() => {
1405
+ });
1406
+ }, SAVE_DEBOUNCE_MS);
1407
+ }
1408
+ deduplicate(type, content, importance) {
1409
+ const threshold = type === "working" ? 0.95 : type === "episodic" ? 0.85 : DEDUP_SIMILARITY_THRESHOLD;
1410
+ const shortContent = content.slice(0, 500);
1411
+ for (const entry of this.index.values()) {
1412
+ if (entry.type !== type) continue;
1413
+ const sim = similarity(shortContent, entry.content.slice(0, 500));
1414
+ if (sim >= threshold) {
1415
+ entry.accessedAt = Date.now();
1416
+ entry.accessCount++;
1417
+ entry.importance = Math.min(10, Math.max(entry.importance, importance));
1418
+ if (content.length > entry.content.length && content.length <= 4e3) {
1419
+ entry.content = content;
1420
+ this.dirty = true;
1421
+ }
1422
+ return entry.id;
1423
+ }
1424
+ }
1425
+ return null;
1426
+ }
1427
+ evictWorking() {
1428
+ while (this.working.length > MAX_WORKING_MEM) {
1429
+ let worstIdx = 0;
1430
+ let worstScore = Infinity;
1431
+ for (let i = 0; i < this.working.length; i++) {
1432
+ const e = this.working[i];
1433
+ const score = e.importance + e.accessCount * 0.3 - (Date.now() - e.accessedAt) / (1e3 * 60 * 15);
1434
+ if (score < worstScore) {
1435
+ worstScore = score;
1436
+ worstIdx = i;
1437
+ }
1438
+ }
1439
+ this.working.splice(worstIdx, 1);
1440
+ }
1441
+ }
1442
+ consolidateIfNeeded() {
1443
+ const stats = loadStats();
1444
+ const now = Date.now();
1445
+ if (now - stats.lastConsolidate < 24 * 60 * 60 * 1e3) return;
1446
+ const candidates = /* @__PURE__ */ new Map();
1447
+ for (const entry of this.index.values()) {
1448
+ if (entry.type === "episodic" && entry.accessCount >= CONSOLIDATE_THRESHOLD) {
1449
+ const key = entry.tags.slice(0, 3).sort().join("|") || "_default_";
1450
+ if (!candidates.has(key)) candidates.set(key, []);
1451
+ candidates.get(key).push(entry);
1452
+ }
1453
+ }
1454
+ let consolidated = 0;
1455
+ for (const [, group] of candidates) {
1456
+ if (group.length < 2) continue;
1457
+ group.sort((a, b2) => b2.accessCount - a.accessCount);
1458
+ const best = group[0];
1459
+ const allContent = group.map((g2) => g2.content).join("\n");
1460
+ const summary = `[ consolidated from ${group.length} memories ]
1461
+ ${allContent.slice(0, 2e3)}`;
1462
+ const newType = best.accessCount >= 10 ? "procedural" : "semantic";
1463
+ const existing = this.findSimilar(newType, best.content);
1464
+ if (existing) {
1465
+ existing.content = summary.slice(0, 4e3);
1466
+ existing.accessCount += Math.floor(group.reduce((s, g2) => s + g2.accessCount, 0) / group.length);
1467
+ existing.importance = Math.min(10, existing.importance + 1);
1468
+ } else {
1469
+ this.store(newType, summary, best.tags, Math.min(10, best.importance + 2), 9, "system");
1470
+ consolidated++;
1471
+ }
1472
+ for (const old of group) {
1473
+ if (old.id !== best.id) this.index.delete(old.id);
1474
+ }
1475
+ }
1476
+ if (consolidated > 0 || candidates.size > 0) {
1477
+ stats.lastConsolidate = now;
1478
+ saveStats(stats);
1479
+ this.dirty = true;
1480
+ }
1481
+ }
1482
+ findSimilar(type, content) {
1483
+ const threshold = 0.75;
1484
+ let best = null;
1485
+ let bestSim = 0;
1486
+ for (const entry of this.index.values()) {
1487
+ if (entry.type !== type) continue;
1488
+ const sim = similarity(content.slice(0, 300), entry.content.slice(0, 300));
1489
+ if (sim > bestSim) {
1490
+ bestSim = sim;
1491
+ best = entry;
1492
+ }
1493
+ }
1494
+ return bestSim >= threshold ? best : null;
1315
1495
  }
1316
- // ============ 维护 ============
1317
1496
  autoCleanup() {
1318
1497
  const entries = [...this.index.values()];
1319
1498
  entries.sort((a, b2) => {
1320
- const scoreA = a.importance * 2 + a.accessCount - (Date.now() - a.accessedAt) / (1e3 * 60 * 60 * 24);
1321
- const scoreB = b2.importance * 2 + b2.accessCount - (Date.now() - b2.accessedAt) / (1e3 * 60 * 60 * 24);
1499
+ const scoreA = a.importance * 2 + a.accessCount + (a.type === "semantic" ? 3 : a.type === "procedural" ? 2 : 0) - (Date.now() - a.accessedAt) / (1e3 * 60 * 60 * 24);
1500
+ const scoreB = b2.importance * 2 + b2.accessCount + (b2.type === "semantic" ? 3 : b2.type === "procedural" ? 2 : 0) - (Date.now() - b2.accessedAt) / (1e3 * 60 * 60 * 24);
1322
1501
  return scoreA - scoreB;
1323
1502
  });
1324
- const toKeep = entries.slice(-500);
1503
+ const toKeep = entries.slice(-800);
1325
1504
  this.index.clear();
1326
1505
  this.working = [];
1327
1506
  for (const entry of toKeep) {
@@ -3843,6 +4022,11 @@ var init_DeepSeekClient = __esm({
3843
4022
  const rc = m.reasoning_content || m.thinking;
3844
4023
  if (rc) msg.reasoning_content = rc;
3845
4024
  return msg;
4025
+ }).map((msg) => {
4026
+ if (msg.role === "tool" && !msg.name) {
4027
+ msg.name = "tool";
4028
+ }
4029
+ return msg;
3846
4030
  }),
3847
4031
  temperature: config.temperature,
3848
4032
  max_tokens: config.maxTokens,
@@ -12450,7 +12634,10 @@ ${globalRules}` });
12450
12634
  }
12451
12635
  r2.push(e);
12452
12636
  }
12453
- return r2;
12637
+ return r2.map((m) => {
12638
+ if (m.role === "tool" && !m.name) m.name = "tool";
12639
+ return m;
12640
+ });
12454
12641
  }
12455
12642
  function trimHistory(h, max) {
12456
12643
  while (h.length > max) h.shift();