@joshuaswarren/openclaw-engram 8.3.12 → 8.3.14
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/index.js +563 -53
- package/dist/index.js.map +1 -1
- package/openclaw.plugin.json +68 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -120,6 +120,9 @@ function parseConfig(raw) {
|
|
|
120
120
|
qmdEnabled: cfg.qmdEnabled !== false,
|
|
121
121
|
qmdCollection: typeof cfg.qmdCollection === "string" ? cfg.qmdCollection : "openclaw-engram",
|
|
122
122
|
qmdMaxResults: typeof cfg.qmdMaxResults === "number" ? cfg.qmdMaxResults : 8,
|
|
123
|
+
qmdColdTierEnabled: cfg.qmdColdTierEnabled === true,
|
|
124
|
+
qmdColdCollection: typeof cfg.qmdColdCollection === "string" && cfg.qmdColdCollection.length > 0 ? cfg.qmdColdCollection : "openclaw-engram-cold",
|
|
125
|
+
qmdColdMaxResults: typeof cfg.qmdColdMaxResults === "number" ? cfg.qmdColdMaxResults : 8,
|
|
123
126
|
embeddingFallbackEnabled: cfg.embeddingFallbackEnabled !== false,
|
|
124
127
|
embeddingFallbackProvider: cfg.embeddingFallbackProvider === "openai" ? "openai" : cfg.embeddingFallbackProvider === "local" ? "local" : "auto",
|
|
125
128
|
qmdPath: typeof cfg.qmdPath === "string" && cfg.qmdPath.length > 0 ? cfg.qmdPath : void 0,
|
|
@@ -257,6 +260,7 @@ function parseConfig(raw) {
|
|
|
257
260
|
qmdAutoEmbedEnabled: cfg.qmdAutoEmbedEnabled === true,
|
|
258
261
|
qmdEmbedMinIntervalMs: typeof cfg.qmdEmbedMinIntervalMs === "number" ? cfg.qmdEmbedMinIntervalMs : 60 * 6e4,
|
|
259
262
|
qmdUpdateTimeoutMs: typeof cfg.qmdUpdateTimeoutMs === "number" ? cfg.qmdUpdateTimeoutMs : 9e4,
|
|
263
|
+
qmdUpdateMinIntervalMs: typeof cfg.qmdUpdateMinIntervalMs === "number" ? cfg.qmdUpdateMinIntervalMs : 15 * 6e4,
|
|
260
264
|
// Local LLM resilience
|
|
261
265
|
localLlmRetry5xxCount: typeof cfg.localLlmRetry5xxCount === "number" ? cfg.localLlmRetry5xxCount : 1,
|
|
262
266
|
localLlmRetryBackoffMs: typeof cfg.localLlmRetryBackoffMs === "number" ? cfg.localLlmRetryBackoffMs : 400,
|
|
@@ -277,6 +281,12 @@ function parseConfig(raw) {
|
|
|
277
281
|
includeInRecallByDefault: p?.includeInRecallByDefault === true
|
|
278
282
|
})).filter((p) => p.name.length > 0) : [],
|
|
279
283
|
defaultRecallNamespaces: Array.isArray(cfg.defaultRecallNamespaces) ? ["self", "shared"].filter((x) => cfg.defaultRecallNamespaces.includes(x)) : ["self", "shared"],
|
|
284
|
+
cronRecallMode: cfg.cronRecallMode === "none" ? "none" : cfg.cronRecallMode === "allowlist" ? "allowlist" : "all",
|
|
285
|
+
cronRecallAllowlist: Array.isArray(cfg.cronRecallAllowlist) ? cfg.cronRecallAllowlist.filter((v) => typeof v === "string" && v.length > 0) : [],
|
|
286
|
+
cronRecallPolicyEnabled: cfg.cronRecallPolicyEnabled !== false,
|
|
287
|
+
cronRecallNormalizedQueryMaxChars: typeof cfg.cronRecallNormalizedQueryMaxChars === "number" ? cfg.cronRecallNormalizedQueryMaxChars : 480,
|
|
288
|
+
cronRecallInstructionHeavyTokenCap: typeof cfg.cronRecallInstructionHeavyTokenCap === "number" ? cfg.cronRecallInstructionHeavyTokenCap : 36,
|
|
289
|
+
cronConversationRecallMode: cfg.cronConversationRecallMode === "always" ? "always" : cfg.cronConversationRecallMode === "never" ? "never" : "auto",
|
|
280
290
|
autoPromoteToSharedEnabled: cfg.autoPromoteToSharedEnabled === true,
|
|
281
291
|
autoPromoteToSharedCategories: Array.isArray(cfg.autoPromoteToSharedCategories) ? cfg.autoPromoteToSharedCategories.filter((c) => c === "correction" || c === "decision" || c === "preference") : ["correction", "decision", "preference"],
|
|
282
292
|
autoPromoteMinConfidenceTier: cfg.autoPromoteMinConfidenceTier === "explicit" ? "explicit" : cfg.autoPromoteMinConfidenceTier === "implied" ? "implied" : "explicit",
|
|
@@ -3490,6 +3500,7 @@ import os2 from "os";
|
|
|
3490
3500
|
import path2 from "path";
|
|
3491
3501
|
var QMD_TIMEOUT_MS = 3e4;
|
|
3492
3502
|
var QMD_DAEMON_TIMEOUT_MS = 6e4;
|
|
3503
|
+
var QMD_PROBE_TIMEOUT_MS = 8e3;
|
|
3493
3504
|
var QMD_UPDATE_BACKOFF_MS = 15 * 60 * 1e3;
|
|
3494
3505
|
var QMD_EMBED_BACKOFF_MS = 60 * 60 * 1e3;
|
|
3495
3506
|
var QMD_FALLBACK_PATHS = [
|
|
@@ -3702,6 +3713,7 @@ var QmdClient = class {
|
|
|
3702
3713
|
this.maxResults = maxResults;
|
|
3703
3714
|
this.slowLog = opts?.slowLog;
|
|
3704
3715
|
this.updateTimeoutMs = opts?.updateTimeoutMs ?? 12e4;
|
|
3716
|
+
this.updateMinIntervalMs = Math.max(0, opts?.updateMinIntervalMs ?? 15 * 6e4);
|
|
3705
3717
|
this.configuredQmdPath = opts?.qmdPath?.trim() ? opts.qmdPath.trim() : void 0;
|
|
3706
3718
|
this.daemonRecheckIntervalMs = opts?.daemonRecheckIntervalMs ?? 6e4;
|
|
3707
3719
|
if (opts?.daemonUrl) {
|
|
@@ -3711,7 +3723,10 @@ var QmdClient = class {
|
|
|
3711
3723
|
available = null;
|
|
3712
3724
|
lastUpdateFailAtMs = null;
|
|
3713
3725
|
lastEmbedFailAtMs = null;
|
|
3726
|
+
lastUpdateRunAtMs = null;
|
|
3727
|
+
warnedGlobalUpdateBehavior = false;
|
|
3714
3728
|
updateTimeoutMs;
|
|
3729
|
+
updateMinIntervalMs;
|
|
3715
3730
|
slowLog;
|
|
3716
3731
|
configuredQmdPath;
|
|
3717
3732
|
qmdPathSource = "auto-path";
|
|
@@ -3770,7 +3785,7 @@ ${stderr}`.trim();
|
|
|
3770
3785
|
};
|
|
3771
3786
|
if (this.configuredQmdPath) {
|
|
3772
3787
|
try {
|
|
3773
|
-
const result = await runQmd(["--version"],
|
|
3788
|
+
const result = await runQmd(["--version"], QMD_PROBE_TIMEOUT_MS, this.configuredQmdPath);
|
|
3774
3789
|
this.available = true;
|
|
3775
3790
|
this.qmdPath = this.configuredQmdPath;
|
|
3776
3791
|
this.qmdPathSource = "configured";
|
|
@@ -3785,7 +3800,7 @@ ${stderr}`.trim();
|
|
|
3785
3800
|
}
|
|
3786
3801
|
}
|
|
3787
3802
|
try {
|
|
3788
|
-
const result = await runQmd(["--version"],
|
|
3803
|
+
const result = await runQmd(["--version"], QMD_PROBE_TIMEOUT_MS, "qmd");
|
|
3789
3804
|
this.available = true;
|
|
3790
3805
|
this.qmdPath = "qmd";
|
|
3791
3806
|
this.qmdPathSource = "auto-path";
|
|
@@ -3796,7 +3811,7 @@ ${stderr}`.trim();
|
|
|
3796
3811
|
markProbeFailure(err);
|
|
3797
3812
|
for (const fallbackPath of QMD_FALLBACK_PATHS) {
|
|
3798
3813
|
try {
|
|
3799
|
-
const result = await runQmd(["--version"],
|
|
3814
|
+
const result = await runQmd(["--version"], QMD_PROBE_TIMEOUT_MS, fallbackPath);
|
|
3800
3815
|
this.available = true;
|
|
3801
3816
|
this.qmdPath = fallbackPath;
|
|
3802
3817
|
this.qmdPathSource = "auto-fallback";
|
|
@@ -3842,7 +3857,10 @@ ${stderr}`.trim();
|
|
|
3842
3857
|
await this.maybeProbeDaemon();
|
|
3843
3858
|
if (this.daemonAvailable) {
|
|
3844
3859
|
const results = await this.searchViaDaemon(trimmed, col, n);
|
|
3845
|
-
if (results !== null)
|
|
3860
|
+
if (results !== null) {
|
|
3861
|
+
if (results.length > 0) return results;
|
|
3862
|
+
log.debug("QMD daemon search returned 0 results; falling back to subprocess query");
|
|
3863
|
+
}
|
|
3846
3864
|
}
|
|
3847
3865
|
return this.searchViaSubprocess(trimmed, col, n);
|
|
3848
3866
|
}
|
|
@@ -3854,7 +3872,10 @@ ${stderr}`.trim();
|
|
|
3854
3872
|
await this.maybeProbeDaemon();
|
|
3855
3873
|
if (this.daemonAvailable) {
|
|
3856
3874
|
const results = await this.searchViaDaemon(trimmed, void 0, n);
|
|
3857
|
-
if (results !== null)
|
|
3875
|
+
if (results !== null) {
|
|
3876
|
+
if (results.length > 0) return results;
|
|
3877
|
+
log.debug("QMD daemon global search returned 0 results; falling back to subprocess query");
|
|
3878
|
+
}
|
|
3858
3879
|
}
|
|
3859
3880
|
return this.searchGlobalViaSubprocess(trimmed, n);
|
|
3860
3881
|
}
|
|
@@ -4076,17 +4097,28 @@ ${stderr}`.trim();
|
|
|
4076
4097
|
}
|
|
4077
4098
|
async update() {
|
|
4078
4099
|
if (this.available === false) return;
|
|
4100
|
+
if (this.lastUpdateRunAtMs && Date.now() - this.lastUpdateRunAtMs < this.updateMinIntervalMs) {
|
|
4101
|
+
log.debug("QMD update: suppressed due to min-interval gate");
|
|
4102
|
+
return;
|
|
4103
|
+
}
|
|
4079
4104
|
if (this.lastUpdateFailAtMs && Date.now() - this.lastUpdateFailAtMs < QMD_UPDATE_BACKOFF_MS) {
|
|
4080
4105
|
log.debug("QMD update: suppressed due to recent failures (backoff)");
|
|
4081
4106
|
return;
|
|
4082
4107
|
}
|
|
4083
4108
|
try {
|
|
4109
|
+
if (!this.warnedGlobalUpdateBehavior) {
|
|
4110
|
+
this.warnedGlobalUpdateBehavior = true;
|
|
4111
|
+
log.warn(
|
|
4112
|
+
"QMD update runs globally across collections in current CLI versions; Engram now rate-limits update calls to reduce gateway load."
|
|
4113
|
+
);
|
|
4114
|
+
}
|
|
4084
4115
|
const startedAtMs = Date.now();
|
|
4085
4116
|
await runQmd(["update", "-c", this.collection], this.updateTimeoutMs, this.qmdPath);
|
|
4086
4117
|
const durationMs = Date.now() - startedAtMs;
|
|
4087
4118
|
if (this.slowLog?.enabled && durationMs >= this.slowLog.thresholdMs) {
|
|
4088
4119
|
log.warn(`SLOW QMD update: durationMs=${durationMs}`);
|
|
4089
4120
|
}
|
|
4121
|
+
this.lastUpdateRunAtMs = Date.now();
|
|
4090
4122
|
log.debug("QMD update completed");
|
|
4091
4123
|
} catch (err) {
|
|
4092
4124
|
this.lastUpdateFailAtMs = Date.now();
|
|
@@ -5030,6 +5062,41 @@ ${sanitized.text}
|
|
|
5030
5062
|
await readDir(this.correctionsDir);
|
|
5031
5063
|
return memories;
|
|
5032
5064
|
}
|
|
5065
|
+
/**
|
|
5066
|
+
* Read archived memory markdown files under archive/.
|
|
5067
|
+
* Used by long-term recall fallback when hot recall has no hits.
|
|
5068
|
+
*/
|
|
5069
|
+
async readArchivedMemories() {
|
|
5070
|
+
const memories = [];
|
|
5071
|
+
const root = this.archiveDir;
|
|
5072
|
+
const readDir = async (dir) => {
|
|
5073
|
+
try {
|
|
5074
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
5075
|
+
for (const entry of entries) {
|
|
5076
|
+
const fullPath = path4.join(dir, entry.name);
|
|
5077
|
+
if (entry.isDirectory()) {
|
|
5078
|
+
await readDir(fullPath);
|
|
5079
|
+
} else if (entry.name.endsWith(".md")) {
|
|
5080
|
+
try {
|
|
5081
|
+
const raw = await readFile2(fullPath, "utf-8");
|
|
5082
|
+
const parsed = parseFrontmatter(raw);
|
|
5083
|
+
if (parsed) {
|
|
5084
|
+
memories.push({
|
|
5085
|
+
path: fullPath,
|
|
5086
|
+
frontmatter: parsed.frontmatter,
|
|
5087
|
+
content: parsed.content
|
|
5088
|
+
});
|
|
5089
|
+
}
|
|
5090
|
+
} catch {
|
|
5091
|
+
}
|
|
5092
|
+
}
|
|
5093
|
+
}
|
|
5094
|
+
} catch {
|
|
5095
|
+
}
|
|
5096
|
+
};
|
|
5097
|
+
await readDir(root);
|
|
5098
|
+
return memories;
|
|
5099
|
+
}
|
|
5033
5100
|
/** Read a single memory file by its absolute path. Returns null if unreadable. */
|
|
5034
5101
|
async readMemoryByPath(filePath) {
|
|
5035
5102
|
try {
|
|
@@ -8275,6 +8342,160 @@ function planRecallMode(prompt) {
|
|
|
8275
8342
|
return "full";
|
|
8276
8343
|
}
|
|
8277
8344
|
|
|
8345
|
+
// src/recall-query-policy.ts
|
|
8346
|
+
var DEFAULT_STOPWORDS = /* @__PURE__ */ new Set([
|
|
8347
|
+
"the",
|
|
8348
|
+
"and",
|
|
8349
|
+
"for",
|
|
8350
|
+
"with",
|
|
8351
|
+
"from",
|
|
8352
|
+
"that",
|
|
8353
|
+
"this",
|
|
8354
|
+
"your",
|
|
8355
|
+
"you",
|
|
8356
|
+
"are",
|
|
8357
|
+
"was",
|
|
8358
|
+
"were",
|
|
8359
|
+
"have",
|
|
8360
|
+
"has",
|
|
8361
|
+
"had",
|
|
8362
|
+
"not",
|
|
8363
|
+
"but",
|
|
8364
|
+
"its",
|
|
8365
|
+
"into",
|
|
8366
|
+
"only",
|
|
8367
|
+
"use",
|
|
8368
|
+
"run",
|
|
8369
|
+
"then",
|
|
8370
|
+
"when",
|
|
8371
|
+
"what",
|
|
8372
|
+
"where",
|
|
8373
|
+
"which",
|
|
8374
|
+
"will",
|
|
8375
|
+
"would",
|
|
8376
|
+
"should",
|
|
8377
|
+
"could",
|
|
8378
|
+
"goal",
|
|
8379
|
+
"output",
|
|
8380
|
+
"format",
|
|
8381
|
+
"rules",
|
|
8382
|
+
"section",
|
|
8383
|
+
"sections",
|
|
8384
|
+
"skip",
|
|
8385
|
+
"today",
|
|
8386
|
+
"yesterday",
|
|
8387
|
+
"return",
|
|
8388
|
+
"summary",
|
|
8389
|
+
"plain",
|
|
8390
|
+
"text",
|
|
8391
|
+
"before",
|
|
8392
|
+
"after",
|
|
8393
|
+
"time",
|
|
8394
|
+
"date",
|
|
8395
|
+
"daily",
|
|
8396
|
+
"cron",
|
|
8397
|
+
"agent",
|
|
8398
|
+
"mode",
|
|
8399
|
+
"data",
|
|
8400
|
+
"gathering",
|
|
8401
|
+
"context"
|
|
8402
|
+
]);
|
|
8403
|
+
function collapseWhitespace(text) {
|
|
8404
|
+
return text.replace(/\s+/g, " ").trim();
|
|
8405
|
+
}
|
|
8406
|
+
function stripFilesystemLikePaths(text) {
|
|
8407
|
+
return text.replace(/(?:^|\s)(~\/[^\s)]+)(?=\s|$)/g, " ").replace(/(?:^|\s)(\/[A-Za-z0-9._\-\/]+)(?=\s|$)/g, " ").replace(/(?:^|\s)([A-Za-z]:\\[^\s)]+)(?=\s|$)/g, " ");
|
|
8408
|
+
}
|
|
8409
|
+
function isBulletOrNumberedLine(line) {
|
|
8410
|
+
if (line.startsWith("-") || line.startsWith("*")) {
|
|
8411
|
+
return true;
|
|
8412
|
+
}
|
|
8413
|
+
let i = 0;
|
|
8414
|
+
while (i < line.length) {
|
|
8415
|
+
const code = line.charCodeAt(i);
|
|
8416
|
+
if (code < 48 || code > 57) {
|
|
8417
|
+
break;
|
|
8418
|
+
}
|
|
8419
|
+
i += 1;
|
|
8420
|
+
}
|
|
8421
|
+
return i > 0 && i < line.length && line.charAt(i) === ".";
|
|
8422
|
+
}
|
|
8423
|
+
function scoreInstructionHeavyShape(prompt) {
|
|
8424
|
+
const lines = prompt.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
8425
|
+
const lineCount = lines.length;
|
|
8426
|
+
if (lineCount === 0) return 0;
|
|
8427
|
+
const headingLineCount = lines.filter(
|
|
8428
|
+
(line) => /^(goal|output format|tone rules|grounding rules|data gathering|date computation|crm context|follow-up|social|current time|return)\b/i.test(
|
|
8429
|
+
line
|
|
8430
|
+
) || /^[A-Z][A-Z\s/-]{4,}:$/.test(line)
|
|
8431
|
+
).length;
|
|
8432
|
+
const bulletLineCount = lines.filter((line) => isBulletOrNumberedLine(line)).length;
|
|
8433
|
+
const longLineCount = lines.filter((line) => line.length >= 180).length;
|
|
8434
|
+
const hasPathDensity = (prompt.match(/(?:~\/|\/Users\/|[A-Za-z]:\\)/g)?.length ?? 0) >= 2;
|
|
8435
|
+
const hasImperativeDensity = (prompt.match(/\b(run|extract|read|parse|determine|include|omit|skip)\b/gi)?.length ?? 0) >= 8;
|
|
8436
|
+
let score = 0;
|
|
8437
|
+
if (lineCount >= 24) score += 2;
|
|
8438
|
+
if (headingLineCount >= 4) score += 2;
|
|
8439
|
+
if (bulletLineCount >= 8) score += 1;
|
|
8440
|
+
if (longLineCount >= 3) score += 1;
|
|
8441
|
+
if (hasPathDensity) score += 1;
|
|
8442
|
+
if (hasImperativeDensity) score += 1;
|
|
8443
|
+
return score;
|
|
8444
|
+
}
|
|
8445
|
+
function classifyRecallPromptShape(prompt) {
|
|
8446
|
+
const score = scoreInstructionHeavyShape(prompt);
|
|
8447
|
+
return score >= 5 ? "instruction_heavy" : "standard";
|
|
8448
|
+
}
|
|
8449
|
+
function tokenizeForCompactQuery(text) {
|
|
8450
|
+
const raw = text.toLowerCase().replace(/[^a-z0-9\s:_-]+/g, " ").split(/\s+/).filter((token) => token.length >= 3);
|
|
8451
|
+
const deduped = [];
|
|
8452
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8453
|
+
for (const token of raw) {
|
|
8454
|
+
if (DEFAULT_STOPWORDS.has(token)) continue;
|
|
8455
|
+
if (seen.has(token)) continue;
|
|
8456
|
+
seen.add(token);
|
|
8457
|
+
deduped.push(token);
|
|
8458
|
+
}
|
|
8459
|
+
return deduped;
|
|
8460
|
+
}
|
|
8461
|
+
function buildInstructionHeavyQuery(prompt, tokenCap, maxChars) {
|
|
8462
|
+
const cleaned = stripFilesystemLikePaths(prompt);
|
|
8463
|
+
const tokens = tokenizeForCompactQuery(cleaned).slice(0, Math.max(8, tokenCap));
|
|
8464
|
+
const joined = tokens.join(" ");
|
|
8465
|
+
const compact = collapseWhitespace(joined);
|
|
8466
|
+
if (compact.length <= maxChars) return compact;
|
|
8467
|
+
return compact.slice(0, maxChars).trim();
|
|
8468
|
+
}
|
|
8469
|
+
function buildStandardQuery(prompt, maxChars) {
|
|
8470
|
+
const trimmed = collapseWhitespace(prompt);
|
|
8471
|
+
if (trimmed.length <= maxChars) return trimmed;
|
|
8472
|
+
return trimmed.slice(0, maxChars).trim();
|
|
8473
|
+
}
|
|
8474
|
+
function buildRecallQueryPolicy(prompt, sessionKey, cfg) {
|
|
8475
|
+
const normalizedPrompt = collapseWhitespace(prompt);
|
|
8476
|
+
const isCron = (sessionKey ?? "").includes(":cron:");
|
|
8477
|
+
if (!cfg.cronRecallPolicyEnabled || !isCron) {
|
|
8478
|
+
return {
|
|
8479
|
+
promptShape: "standard",
|
|
8480
|
+
retrievalQuery: prompt,
|
|
8481
|
+
skipConversationRecall: false,
|
|
8482
|
+
retrievalBudgetMode: "full"
|
|
8483
|
+
};
|
|
8484
|
+
}
|
|
8485
|
+
const promptShape = classifyRecallPromptShape(prompt);
|
|
8486
|
+
const maxChars = Math.max(120, cfg.cronRecallNormalizedQueryMaxChars);
|
|
8487
|
+
const tokenCap = Math.max(8, cfg.cronRecallInstructionHeavyTokenCap);
|
|
8488
|
+
const retrievalQuery = promptShape === "instruction_heavy" ? buildInstructionHeavyQuery(prompt, tokenCap, maxChars) : buildStandardQuery(prompt, maxChars);
|
|
8489
|
+
const skipConversationRecall = cfg.cronConversationRecallMode === "never" ? true : cfg.cronConversationRecallMode === "always" ? false : promptShape === "instruction_heavy";
|
|
8490
|
+
const retrievalBudgetMode = promptShape === "instruction_heavy" ? "minimal" : "full";
|
|
8491
|
+
return {
|
|
8492
|
+
promptShape,
|
|
8493
|
+
retrievalQuery: retrievalQuery.length > 0 ? retrievalQuery : normalizedPrompt.slice(0, maxChars),
|
|
8494
|
+
skipConversationRecall,
|
|
8495
|
+
retrievalBudgetMode
|
|
8496
|
+
};
|
|
8497
|
+
}
|
|
8498
|
+
|
|
8278
8499
|
// src/boxes.ts
|
|
8279
8500
|
import { mkdir as mkdir10, writeFile as writeFile10, readFile as readFile11, readdir as readdir6 } from "fs/promises";
|
|
8280
8501
|
import path13 from "path";
|
|
@@ -10214,6 +10435,9 @@ function filterRecallCandidates(candidates, options) {
|
|
|
10214
10435
|
const scopedByNamespace = options.namespacesEnabled ? candidates.filter((r) => options.recallNamespaces.includes(options.resolveNamespace(r.path))) : candidates;
|
|
10215
10436
|
return scopedByNamespace.filter((r) => !isArtifactMemoryPath(r.path)).slice(0, Math.max(0, options.limit));
|
|
10216
10437
|
}
|
|
10438
|
+
function tokenizeRecallQuery(prompt) {
|
|
10439
|
+
return prompt.toLowerCase().split(/[^a-z0-9]+/i).map((t) => t.trim()).filter((t) => t.length >= 3);
|
|
10440
|
+
}
|
|
10217
10441
|
function hasLifecycleMetadata(frontmatter) {
|
|
10218
10442
|
return frontmatter.lifecycleState !== void 0 || frontmatter.verificationState !== void 0 || frontmatter.policyClass !== void 0 || frontmatter.lastValidatedAt !== void 0 || frontmatter.decayScore !== void 0 || frontmatter.heatScore !== void 0;
|
|
10219
10443
|
}
|
|
@@ -10441,6 +10665,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
10441
10665
|
thresholdMs: config.slowLogThresholdMs
|
|
10442
10666
|
},
|
|
10443
10667
|
updateTimeoutMs: config.qmdUpdateTimeoutMs,
|
|
10668
|
+
updateMinIntervalMs: config.qmdUpdateMinIntervalMs,
|
|
10444
10669
|
qmdPath: config.qmdPath,
|
|
10445
10670
|
daemonUrl: config.qmdDaemonEnabled ? config.qmdDaemonUrl : void 0,
|
|
10446
10671
|
daemonRecheckIntervalMs: config.qmdDaemonRecheckIntervalMs
|
|
@@ -10454,6 +10679,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
10454
10679
|
thresholdMs: config.slowLogThresholdMs
|
|
10455
10680
|
},
|
|
10456
10681
|
updateTimeoutMs: config.qmdUpdateTimeoutMs,
|
|
10682
|
+
updateMinIntervalMs: config.qmdUpdateMinIntervalMs,
|
|
10457
10683
|
qmdPath: config.qmdPath,
|
|
10458
10684
|
daemonUrl: config.qmdDaemonEnabled ? config.qmdDaemonUrl : void 0,
|
|
10459
10685
|
daemonRecheckIntervalMs: config.qmdDaemonRecheckIntervalMs
|
|
@@ -10967,10 +11193,28 @@ var Orchestrator = class _Orchestrator {
|
|
|
10967
11193
|
const maxFetchLimit = Math.min(800, Math.max(fetchLimit, qmdFetchLimit * 8));
|
|
10968
11194
|
const MAX_ATTEMPTS = 4;
|
|
10969
11195
|
let bestFiltered = [];
|
|
11196
|
+
let queryFallbackAttempted = false;
|
|
11197
|
+
const runQueryFallback = async () => {
|
|
11198
|
+
if (queryFallbackAttempted) return [];
|
|
11199
|
+
queryFallbackAttempted = true;
|
|
11200
|
+
const queryResults = await this.qmd.search(prompt, options.collection, fetchLimit);
|
|
11201
|
+
const filteredQueryResults = filterRecallCandidates(queryResults, {
|
|
11202
|
+
namespacesEnabled: options.namespacesEnabled,
|
|
11203
|
+
recallNamespaces: options.recallNamespaces,
|
|
11204
|
+
resolveNamespace: options.resolveNamespace,
|
|
11205
|
+
limit: fetchLimit
|
|
11206
|
+
});
|
|
11207
|
+
if (filteredQueryResults.length > 0) {
|
|
11208
|
+
log.debug(
|
|
11209
|
+
`QMD query fallback returned ${filteredQueryResults.length} candidates after hybrid underfilled`
|
|
11210
|
+
);
|
|
11211
|
+
}
|
|
11212
|
+
return filteredQueryResults;
|
|
11213
|
+
};
|
|
10970
11214
|
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt += 1) {
|
|
10971
11215
|
const memoryResults = await this.qmd.hybridSearch(
|
|
10972
11216
|
prompt,
|
|
10973
|
-
|
|
11217
|
+
options.collection,
|
|
10974
11218
|
fetchLimit
|
|
10975
11219
|
);
|
|
10976
11220
|
const filteredResults = filterRecallCandidates(memoryResults, {
|
|
@@ -10986,17 +11230,27 @@ var Orchestrator = class _Orchestrator {
|
|
|
10986
11230
|
bestFiltered = filteredResults;
|
|
10987
11231
|
}
|
|
10988
11232
|
if (memoryResults.length === 0) {
|
|
11233
|
+
const queryFallback = await runQueryFallback();
|
|
11234
|
+
if (queryFallback.length > 0) {
|
|
11235
|
+
return queryFallback.slice(0, qmdFetchLimit);
|
|
11236
|
+
}
|
|
10989
11237
|
return filteredResults;
|
|
10990
11238
|
}
|
|
10991
11239
|
if (memoryResults.length < fetchLimit && filteredResults.length > 0) {
|
|
10992
11240
|
return filteredResults;
|
|
10993
11241
|
}
|
|
10994
11242
|
if (fetchLimit >= maxFetchLimit) {
|
|
10995
|
-
|
|
11243
|
+
break;
|
|
10996
11244
|
}
|
|
10997
11245
|
const growth = Math.max(20, Math.floor(fetchLimit / 2));
|
|
10998
11246
|
fetchLimit = Math.min(maxFetchLimit, fetchLimit + growth);
|
|
10999
11247
|
}
|
|
11248
|
+
if (bestFiltered.length === 0) {
|
|
11249
|
+
const queryFallback = await runQueryFallback();
|
|
11250
|
+
if (queryFallback.length > 0) {
|
|
11251
|
+
return queryFallback.slice(0, qmdFetchLimit);
|
|
11252
|
+
}
|
|
11253
|
+
}
|
|
11000
11254
|
return bestFiltered;
|
|
11001
11255
|
}
|
|
11002
11256
|
async expandResultsViaGraph(options) {
|
|
@@ -11080,6 +11334,14 @@ var Orchestrator = class _Orchestrator {
|
|
|
11080
11334
|
const recallStart = Date.now();
|
|
11081
11335
|
const timings = {};
|
|
11082
11336
|
const sections = [];
|
|
11337
|
+
const queryPolicy = buildRecallQueryPolicy(prompt, sessionKey, {
|
|
11338
|
+
cronRecallPolicyEnabled: this.config.cronRecallPolicyEnabled,
|
|
11339
|
+
cronRecallNormalizedQueryMaxChars: this.config.cronRecallNormalizedQueryMaxChars,
|
|
11340
|
+
cronRecallInstructionHeavyTokenCap: this.config.cronRecallInstructionHeavyTokenCap,
|
|
11341
|
+
cronConversationRecallMode: this.config.cronConversationRecallMode
|
|
11342
|
+
});
|
|
11343
|
+
const retrievalQuery = queryPolicy.retrievalQuery || prompt;
|
|
11344
|
+
timings.queryPolicy = `${queryPolicy.promptShape}/${queryPolicy.retrievalBudgetMode}${queryPolicy.skipConversationRecall ? "/skip-conv" : ""}`;
|
|
11083
11345
|
const recallMode = resolveEffectiveRecallMode({
|
|
11084
11346
|
plannerEnabled: this.config.recallPlannerEnabled,
|
|
11085
11347
|
graphRecallEnabled: this.config.graphRecallEnabled,
|
|
@@ -11087,7 +11349,12 @@ var Orchestrator = class _Orchestrator {
|
|
|
11087
11349
|
prompt
|
|
11088
11350
|
});
|
|
11089
11351
|
timings.recallPlan = recallMode;
|
|
11090
|
-
const
|
|
11352
|
+
const plannerRecallResultLimit = recallMode === "no_recall" ? 0 : recallMode === "minimal" ? Math.max(0, Math.min(this.config.qmdMaxResults, this.config.recallPlannerMaxQmdResultsMinimal)) : this.config.qmdMaxResults;
|
|
11353
|
+
const policyMinimalLimit = Math.max(
|
|
11354
|
+
0,
|
|
11355
|
+
Math.min(this.config.qmdMaxResults, this.config.recallPlannerMaxQmdResultsMinimal)
|
|
11356
|
+
);
|
|
11357
|
+
const recallResultLimit = recallMode !== "no_recall" && queryPolicy.retrievalBudgetMode === "minimal" ? Math.min(plannerRecallResultLimit, policyMinimalLimit) : plannerRecallResultLimit;
|
|
11091
11358
|
const recallHeadroom = this.config.verbatimArtifactsEnabled ? Math.max(12, this.config.verbatimArtifactsMaxRecall * 4) : 12;
|
|
11092
11359
|
const computedFetchLimit = recallResultLimit === 0 ? 0 : Math.max(recallResultLimit, Math.min(200, recallResultLimit + recallHeadroom));
|
|
11093
11360
|
const qmdFetchLimit = computedFetchLimit;
|
|
@@ -11154,7 +11421,11 @@ var Orchestrator = class _Orchestrator {
|
|
|
11154
11421
|
timings.artifacts = "skip(limit=0)";
|
|
11155
11422
|
return [];
|
|
11156
11423
|
}
|
|
11157
|
-
const results = await this.recallArtifactsAcrossNamespaces(
|
|
11424
|
+
const results = await this.recallArtifactsAcrossNamespaces(
|
|
11425
|
+
retrievalQuery,
|
|
11426
|
+
recallNamespaces,
|
|
11427
|
+
targetCount
|
|
11428
|
+
);
|
|
11158
11429
|
timings.artifacts = `${Date.now() - t0}ms`;
|
|
11159
11430
|
return results;
|
|
11160
11431
|
})();
|
|
@@ -11170,7 +11441,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
11170
11441
|
}
|
|
11171
11442
|
const t0 = Date.now();
|
|
11172
11443
|
const filteredResults = await this.fetchQmdMemoryResultsWithArtifactTopUp(
|
|
11173
|
-
|
|
11444
|
+
retrievalQuery,
|
|
11174
11445
|
qmdFetchLimit,
|
|
11175
11446
|
qmdHybridFetchLimit,
|
|
11176
11447
|
{
|
|
@@ -11254,17 +11525,17 @@ ${tmtNode.summary}`);
|
|
|
11254
11525
|
memoryResults = merged;
|
|
11255
11526
|
await this.recordLastGraphRecallSnapshot({
|
|
11256
11527
|
storage: profileStorage,
|
|
11257
|
-
prompt,
|
|
11528
|
+
prompt: retrievalQuery,
|
|
11258
11529
|
recallMode,
|
|
11259
11530
|
recallNamespaces,
|
|
11260
11531
|
seedPaths,
|
|
11261
11532
|
expandedPaths
|
|
11262
11533
|
});
|
|
11263
11534
|
}
|
|
11264
|
-
memoryResults = await this.boostSearchResults(memoryResults, recallNamespaces,
|
|
11535
|
+
memoryResults = await this.boostSearchResults(memoryResults, recallNamespaces, retrievalQuery);
|
|
11265
11536
|
if (this.config.rerankEnabled && this.config.rerankProvider === "local") {
|
|
11266
11537
|
const ranked = await rerankLocalOrNoop({
|
|
11267
|
-
query:
|
|
11538
|
+
query: retrievalQuery,
|
|
11268
11539
|
candidates: memoryResults.slice(0, this.config.rerankMaxCandidates).map((r) => ({
|
|
11269
11540
|
id: r.path,
|
|
11270
11541
|
snippet: r.snippet || r.path
|
|
@@ -11296,33 +11567,48 @@ ${tmtNode.summary}`);
|
|
|
11296
11567
|
}
|
|
11297
11568
|
memoryResults = memoryResults.slice(0, recallResultLimit);
|
|
11298
11569
|
if (memoryResults.length > 0) {
|
|
11299
|
-
|
|
11300
|
-
|
|
11301
|
-
|
|
11302
|
-
|
|
11303
|
-
|
|
11304
|
-
|
|
11305
|
-
|
|
11570
|
+
this.publishRecallResults({
|
|
11571
|
+
title: "Relevant Memories",
|
|
11572
|
+
results: memoryResults,
|
|
11573
|
+
sections,
|
|
11574
|
+
retrievalQuery,
|
|
11575
|
+
sessionKey
|
|
11576
|
+
});
|
|
11306
11577
|
} else {
|
|
11307
|
-
const embeddingResults = await this.searchEmbeddingFallback(
|
|
11578
|
+
const embeddingResults = await this.searchEmbeddingFallback(retrievalQuery, embeddingFetchLimit);
|
|
11308
11579
|
const scopedCandidates = filterRecallCandidates(embeddingResults, {
|
|
11309
11580
|
namespacesEnabled: this.config.namespacesEnabled,
|
|
11310
11581
|
recallNamespaces,
|
|
11311
11582
|
resolveNamespace: (p) => this.namespaceFromPath(p),
|
|
11312
11583
|
limit: embeddingFetchLimit
|
|
11313
11584
|
});
|
|
11314
|
-
const scoped = (await this.boostSearchResults(scopedCandidates, recallNamespaces,
|
|
11585
|
+
const scoped = (await this.boostSearchResults(scopedCandidates, recallNamespaces, retrievalQuery)).slice(
|
|
11315
11586
|
0,
|
|
11316
11587
|
recallResultLimit
|
|
11317
11588
|
);
|
|
11318
11589
|
if (scoped.length > 0) {
|
|
11319
|
-
|
|
11320
|
-
|
|
11321
|
-
|
|
11322
|
-
|
|
11323
|
-
|
|
11590
|
+
this.publishRecallResults({
|
|
11591
|
+
title: "Relevant Memories",
|
|
11592
|
+
results: scoped,
|
|
11593
|
+
sections,
|
|
11594
|
+
retrievalQuery,
|
|
11595
|
+
sessionKey
|
|
11596
|
+
});
|
|
11597
|
+
} else {
|
|
11598
|
+
const longTerm = await this.applyColdFallbackPipeline({
|
|
11599
|
+
prompt: retrievalQuery,
|
|
11600
|
+
recallNamespaces,
|
|
11601
|
+
recallResultLimit
|
|
11602
|
+
});
|
|
11603
|
+
if (longTerm.length > 0) {
|
|
11604
|
+
this.publishRecallResults({
|
|
11605
|
+
title: "Long-Term Memories (Fallback)",
|
|
11606
|
+
results: longTerm,
|
|
11607
|
+
sections,
|
|
11608
|
+
retrievalQuery,
|
|
11609
|
+
sessionKey
|
|
11610
|
+
});
|
|
11324
11611
|
}
|
|
11325
|
-
sections.push(this.formatQmdResults("Relevant Memories", scoped));
|
|
11326
11612
|
}
|
|
11327
11613
|
}
|
|
11328
11614
|
if (globalResults.length > 0) {
|
|
@@ -11345,25 +11631,26 @@ ${tmtNode.summary}`);
|
|
|
11345
11631
|
);
|
|
11346
11632
|
}
|
|
11347
11633
|
} else if (recallResultLimit > 0 && (!this.config.qmdEnabled || !this.qmd.isAvailable())) {
|
|
11348
|
-
const embeddingResults = await this.searchEmbeddingFallback(
|
|
11634
|
+
const embeddingResults = await this.searchEmbeddingFallback(retrievalQuery, embeddingFetchLimit);
|
|
11349
11635
|
const scopedCandidates = filterRecallCandidates(embeddingResults, {
|
|
11350
11636
|
namespacesEnabled: this.config.namespacesEnabled,
|
|
11351
11637
|
recallNamespaces,
|
|
11352
11638
|
resolveNamespace: (p) => this.namespaceFromPath(p),
|
|
11353
11639
|
limit: embeddingFetchLimit
|
|
11354
11640
|
});
|
|
11355
|
-
const scoped = (await this.boostSearchResults(
|
|
11356
|
-
|
|
11357
|
-
|
|
11358
|
-
|
|
11641
|
+
const scoped = (await this.boostSearchResults(
|
|
11642
|
+
scopedCandidates,
|
|
11643
|
+
recallNamespaces,
|
|
11644
|
+
retrievalQuery
|
|
11645
|
+
)).slice(0, recallResultLimit);
|
|
11359
11646
|
if (scoped.length > 0) {
|
|
11360
|
-
|
|
11361
|
-
|
|
11362
|
-
|
|
11363
|
-
|
|
11364
|
-
|
|
11365
|
-
|
|
11366
|
-
|
|
11647
|
+
this.publishRecallResults({
|
|
11648
|
+
title: "Relevant Memories",
|
|
11649
|
+
results: scoped,
|
|
11650
|
+
sections,
|
|
11651
|
+
retrievalQuery,
|
|
11652
|
+
sessionKey
|
|
11653
|
+
});
|
|
11367
11654
|
} else {
|
|
11368
11655
|
const memories = await this.readAllMemoriesForNamespaces(recallNamespaces);
|
|
11369
11656
|
if (memories.length > 0) {
|
|
@@ -11382,14 +11669,51 @@ ${tmtNode.summary}`);
|
|
|
11382
11669
|
snippet: m.content,
|
|
11383
11670
|
score: 1 - i / Math.max(recentSorted.length, 1)
|
|
11384
11671
|
}));
|
|
11385
|
-
const recent = (await this.boostSearchResults(
|
|
11386
|
-
|
|
11387
|
-
|
|
11388
|
-
|
|
11389
|
-
|
|
11390
|
-
|
|
11672
|
+
const recent = (await this.boostSearchResults(
|
|
11673
|
+
recentAsResults,
|
|
11674
|
+
recallNamespaces,
|
|
11675
|
+
retrievalQuery,
|
|
11676
|
+
preloadedMap
|
|
11677
|
+
)).sort((a, b) => b.score - a.score).slice(0, recallResultLimit);
|
|
11678
|
+
if (recent.length > 0) {
|
|
11679
|
+
this.publishRecallResults({
|
|
11680
|
+
title: "Recent Memories",
|
|
11681
|
+
results: recent,
|
|
11682
|
+
sections,
|
|
11683
|
+
retrievalQuery,
|
|
11684
|
+
sessionKey
|
|
11685
|
+
});
|
|
11686
|
+
} else {
|
|
11687
|
+
const longTerm = await this.applyColdFallbackPipeline({
|
|
11688
|
+
prompt: retrievalQuery,
|
|
11689
|
+
recallNamespaces,
|
|
11690
|
+
recallResultLimit
|
|
11691
|
+
});
|
|
11692
|
+
if (longTerm.length > 0) {
|
|
11693
|
+
this.publishRecallResults({
|
|
11694
|
+
title: "Long-Term Memories (Fallback)",
|
|
11695
|
+
results: longTerm,
|
|
11696
|
+
sections,
|
|
11697
|
+
retrievalQuery,
|
|
11698
|
+
sessionKey
|
|
11699
|
+
});
|
|
11700
|
+
}
|
|
11701
|
+
}
|
|
11702
|
+
} else {
|
|
11703
|
+
const longTerm = await this.applyColdFallbackPipeline({
|
|
11704
|
+
prompt: retrievalQuery,
|
|
11705
|
+
recallNamespaces,
|
|
11706
|
+
recallResultLimit
|
|
11707
|
+
});
|
|
11708
|
+
if (longTerm.length > 0) {
|
|
11709
|
+
this.publishRecallResults({
|
|
11710
|
+
title: "Long-Term Memories (Fallback)",
|
|
11711
|
+
results: longTerm,
|
|
11712
|
+
sections,
|
|
11713
|
+
retrievalQuery,
|
|
11714
|
+
sessionKey
|
|
11715
|
+
});
|
|
11391
11716
|
}
|
|
11392
|
-
sections.push(this.formatQmdResults("Recent Memories", recent));
|
|
11393
11717
|
}
|
|
11394
11718
|
}
|
|
11395
11719
|
if (isDisagreementPrompt(prompt)) {
|
|
@@ -11465,13 +11789,13 @@ ${formatted}`);
|
|
|
11465
11789
|
}
|
|
11466
11790
|
timings.summaries = `${Date.now() - summariesT0}ms`;
|
|
11467
11791
|
const convT0 = Date.now();
|
|
11468
|
-
if (this.config.conversationIndexEnabled && this.conversationQmd && this.conversationQmd.isAvailable()) {
|
|
11792
|
+
if (this.config.conversationIndexEnabled && !queryPolicy.skipConversationRecall && this.conversationQmd && this.conversationQmd.isAvailable()) {
|
|
11469
11793
|
const startedAtMs = Date.now();
|
|
11470
11794
|
const timeoutMs = Math.max(200, this.config.conversationRecallTimeoutMs);
|
|
11471
11795
|
const topK = Math.max(1, this.config.conversationRecallTopK);
|
|
11472
11796
|
const maxChars = Math.max(400, this.config.conversationRecallMaxChars);
|
|
11473
11797
|
const results = await Promise.race([
|
|
11474
|
-
this.conversationQmd.search(
|
|
11798
|
+
this.conversationQmd.search(retrievalQuery, void 0, topK),
|
|
11475
11799
|
new Promise((resolve) => setTimeout(() => resolve([]), timeoutMs))
|
|
11476
11800
|
]).catch(() => []);
|
|
11477
11801
|
const durationMs = Date.now() - startedAtMs;
|
|
@@ -12614,6 +12938,15 @@ ${snippet}`;
|
|
|
12614
12938
|
|
|
12615
12939
|
${lines.join("\n\n")}`;
|
|
12616
12940
|
}
|
|
12941
|
+
publishRecallResults(options) {
|
|
12942
|
+
const memoryIds = this.extractMemoryIdsFromResults(options.results);
|
|
12943
|
+
this.trackMemoryAccess(memoryIds);
|
|
12944
|
+
if (options.sessionKey) {
|
|
12945
|
+
const unique = Array.from(new Set(memoryIds)).slice(0, 40);
|
|
12946
|
+
this.lastRecall.record({ sessionKey: options.sessionKey, query: options.retrievalQuery, memoryIds: unique }).catch((err) => log.debug(`last recall record failed: ${err}`));
|
|
12947
|
+
}
|
|
12948
|
+
options.sections.push(this.formatQmdResults(options.title, options.results));
|
|
12949
|
+
}
|
|
12617
12950
|
async searchEmbeddingFallback(query, limit) {
|
|
12618
12951
|
if (!this.config.embeddingFallbackEnabled) return [];
|
|
12619
12952
|
if (!await this.embeddingFallback.isAvailable()) return [];
|
|
@@ -12633,6 +12966,123 @@ ${lines.join("\n\n")}`;
|
|
|
12633
12966
|
}
|
|
12634
12967
|
return results;
|
|
12635
12968
|
}
|
|
12969
|
+
/**
|
|
12970
|
+
* Long-term fallback retrieval.
|
|
12971
|
+
* Searches archived memories only, and is invoked only when hot recall returns zero hits.
|
|
12972
|
+
*/
|
|
12973
|
+
async searchLongTermArchiveFallback(prompt, recallNamespaces, limit) {
|
|
12974
|
+
const cappedLimit = Math.max(0, limit);
|
|
12975
|
+
if (cappedLimit === 0) return [];
|
|
12976
|
+
const tokens = Array.from(new Set(tokenizeRecallQuery(prompt)));
|
|
12977
|
+
if (tokens.length === 0) return [];
|
|
12978
|
+
const archivedMemories = await this.readArchivedMemoriesForNamespaces(recallNamespaces);
|
|
12979
|
+
if (archivedMemories.length === 0) return [];
|
|
12980
|
+
const scored = [];
|
|
12981
|
+
for (const memory of archivedMemories) {
|
|
12982
|
+
const haystack = [
|
|
12983
|
+
memory.content,
|
|
12984
|
+
memory.frontmatter.category,
|
|
12985
|
+
...memory.frontmatter.tags ?? []
|
|
12986
|
+
].join(" ").toLowerCase();
|
|
12987
|
+
let hits = 0;
|
|
12988
|
+
for (const token of tokens) {
|
|
12989
|
+
if (haystack.includes(token)) hits += 1;
|
|
12990
|
+
}
|
|
12991
|
+
if (hits === 0) continue;
|
|
12992
|
+
const normalized = hits / tokens.length;
|
|
12993
|
+
scored.push({
|
|
12994
|
+
docid: memory.frontmatter.id,
|
|
12995
|
+
path: memory.path,
|
|
12996
|
+
score: normalized,
|
|
12997
|
+
snippet: memory.content.slice(0, 400).replace(/\n/g, " ")
|
|
12998
|
+
});
|
|
12999
|
+
}
|
|
13000
|
+
return scored.sort((a, b) => b.score - a.score).slice(0, cappedLimit);
|
|
13001
|
+
}
|
|
13002
|
+
async applyColdFallbackPipeline(options) {
|
|
13003
|
+
const coldQmdEnabled = this.config.qmdColdTierEnabled === true;
|
|
13004
|
+
const coldCollection = this.config.qmdColdCollection ?? "openclaw-engram-cold";
|
|
13005
|
+
const coldMaxResults = this.config.qmdColdMaxResults ?? this.config.qmdMaxResults;
|
|
13006
|
+
let longTerm = [];
|
|
13007
|
+
if (coldQmdEnabled && this.config.qmdEnabled && this.qmd.isAvailable()) {
|
|
13008
|
+
const coldFetchLimit = Math.max(
|
|
13009
|
+
0,
|
|
13010
|
+
Math.min(options.recallResultLimit, Math.max(0, coldMaxResults))
|
|
13011
|
+
);
|
|
13012
|
+
if (coldFetchLimit > 0) {
|
|
13013
|
+
const coldHybridLimit = computeQmdHybridFetchLimit(
|
|
13014
|
+
coldFetchLimit,
|
|
13015
|
+
false,
|
|
13016
|
+
0
|
|
13017
|
+
);
|
|
13018
|
+
longTerm = await this.fetchQmdMemoryResultsWithArtifactTopUp(
|
|
13019
|
+
options.prompt,
|
|
13020
|
+
coldFetchLimit,
|
|
13021
|
+
coldHybridLimit,
|
|
13022
|
+
{
|
|
13023
|
+
namespacesEnabled: this.config.namespacesEnabled,
|
|
13024
|
+
recallNamespaces: options.recallNamespaces,
|
|
13025
|
+
resolveNamespace: (p) => this.namespaceFromPath(p),
|
|
13026
|
+
collection: coldCollection
|
|
13027
|
+
}
|
|
13028
|
+
);
|
|
13029
|
+
if (longTerm.length > 0) {
|
|
13030
|
+
log.debug(`cold-tier recall source=cold-qmd collection=${coldCollection} hits=${longTerm.length}`);
|
|
13031
|
+
}
|
|
13032
|
+
}
|
|
13033
|
+
}
|
|
13034
|
+
if (longTerm.length === 0) {
|
|
13035
|
+
longTerm = await this.searchLongTermArchiveFallback(
|
|
13036
|
+
options.prompt,
|
|
13037
|
+
options.recallNamespaces,
|
|
13038
|
+
options.recallResultLimit
|
|
13039
|
+
);
|
|
13040
|
+
if (longTerm.length > 0) {
|
|
13041
|
+
log.debug("cold-tier recall source=archive-scan");
|
|
13042
|
+
}
|
|
13043
|
+
}
|
|
13044
|
+
if (longTerm.length === 0) return [];
|
|
13045
|
+
let results = await this.boostSearchResults(
|
|
13046
|
+
longTerm,
|
|
13047
|
+
options.recallNamespaces,
|
|
13048
|
+
options.prompt,
|
|
13049
|
+
void 0,
|
|
13050
|
+
{ allowLifecycleFiltered: true }
|
|
13051
|
+
);
|
|
13052
|
+
if (this.config.rerankEnabled && this.config.rerankProvider === "local") {
|
|
13053
|
+
const ranked = await rerankLocalOrNoop({
|
|
13054
|
+
query: options.prompt,
|
|
13055
|
+
candidates: results.slice(0, this.config.rerankMaxCandidates).map((r) => ({
|
|
13056
|
+
id: r.path,
|
|
13057
|
+
snippet: r.snippet || r.path
|
|
13058
|
+
})),
|
|
13059
|
+
local: this.localLlm,
|
|
13060
|
+
enabled: true,
|
|
13061
|
+
timeoutMs: this.config.rerankTimeoutMs,
|
|
13062
|
+
maxCandidates: this.config.rerankMaxCandidates,
|
|
13063
|
+
cache: this.rerankCache,
|
|
13064
|
+
cacheEnabled: this.config.rerankCacheEnabled,
|
|
13065
|
+
cacheTtlMs: this.config.rerankCacheTtlMs
|
|
13066
|
+
});
|
|
13067
|
+
if (ranked && ranked.length > 0) {
|
|
13068
|
+
const byPath = new Map(results.map((r) => [r.path, r]));
|
|
13069
|
+
const reordered = [];
|
|
13070
|
+
for (const p of ranked) {
|
|
13071
|
+
const it = byPath.get(p);
|
|
13072
|
+
if (it) reordered.push(it);
|
|
13073
|
+
}
|
|
13074
|
+
const rankedSet = new Set(ranked);
|
|
13075
|
+
for (const r of results) {
|
|
13076
|
+
if (!rankedSet.has(r.path)) reordered.push(r);
|
|
13077
|
+
}
|
|
13078
|
+
results = reordered;
|
|
13079
|
+
}
|
|
13080
|
+
}
|
|
13081
|
+
if (this.config.rerankEnabled && this.config.rerankProvider === "cloud") {
|
|
13082
|
+
log.debug("rerankProvider=cloud is reserved/experimental in v2.2.0; skipping rerank");
|
|
13083
|
+
}
|
|
13084
|
+
return results.slice(0, options.recallResultLimit);
|
|
13085
|
+
}
|
|
12636
13086
|
// ---------------------------------------------------------------------------
|
|
12637
13087
|
// Access Tracking (Phase 1A)
|
|
12638
13088
|
// ---------------------------------------------------------------------------
|
|
@@ -12701,7 +13151,7 @@ ${lines.join("\n\n")}`;
|
|
|
12701
13151
|
* Apply recency, access count, and importance boosting to QMD search results.
|
|
12702
13152
|
* Returns re-ranked results.
|
|
12703
13153
|
*/
|
|
12704
|
-
async boostSearchResults(results, _recallNamespaces, prompt, preloadedMemoryMap) {
|
|
13154
|
+
async boostSearchResults(results, _recallNamespaces, prompt, preloadedMemoryMap, options) {
|
|
12705
13155
|
if (results.length === 0) return results;
|
|
12706
13156
|
const now = Date.now();
|
|
12707
13157
|
const memoryByPath = preloadedMemoryMap ? new Map(preloadedMemoryMap) : /* @__PURE__ */ new Map();
|
|
@@ -12749,7 +13199,7 @@ ${lines.join("\n\n")}`;
|
|
|
12749
13199
|
const memory = memoryByPath.get(r.path);
|
|
12750
13200
|
let score = r.score;
|
|
12751
13201
|
if (memory) {
|
|
12752
|
-
if (shouldFilterLifecycleRecallCandidate(memory.frontmatter, {
|
|
13202
|
+
if (options?.allowLifecycleFiltered !== true && shouldFilterLifecycleRecallCandidate(memory.frontmatter, {
|
|
12753
13203
|
lifecyclePolicyEnabled: this.config.lifecyclePolicyEnabled,
|
|
12754
13204
|
lifecycleFilterStaleEnabled: this.config.lifecycleFilterStaleEnabled
|
|
12755
13205
|
})) {
|
|
@@ -12945,6 +13395,16 @@ ${lines.join("\n\n")}`;
|
|
|
12945
13395
|
);
|
|
12946
13396
|
return lists.flat();
|
|
12947
13397
|
}
|
|
13398
|
+
async readArchivedMemoriesForNamespaces(namespaces) {
|
|
13399
|
+
const uniq = Array.from(new Set(namespaces.filter(Boolean)));
|
|
13400
|
+
const lists = await Promise.all(
|
|
13401
|
+
uniq.map(async (ns) => {
|
|
13402
|
+
const sm = await this.storageRouter.storageFor(ns);
|
|
13403
|
+
return sm.readArchivedMemories();
|
|
13404
|
+
})
|
|
13405
|
+
);
|
|
13406
|
+
return lists.flat();
|
|
13407
|
+
}
|
|
12948
13408
|
};
|
|
12949
13409
|
|
|
12950
13410
|
// src/tools.ts
|
|
@@ -14483,6 +14943,7 @@ function registerCli(api, orchestrator) {
|
|
|
14483
14943
|
console.log("Missing query. Usage: openclaw engram search <query>");
|
|
14484
14944
|
return;
|
|
14485
14945
|
}
|
|
14946
|
+
await orchestrator.qmd.probe();
|
|
14486
14947
|
if (orchestrator.qmd.isAvailable()) {
|
|
14487
14948
|
const results = await orchestrator.qmd.search(
|
|
14488
14949
|
query,
|
|
@@ -14511,12 +14972,18 @@ function registerCli(api, orchestrator) {
|
|
|
14511
14972
|
const matches = memories.filter(
|
|
14512
14973
|
(m) => m.content.toLowerCase().includes(lowerQuery) || m.frontmatter.tags.some((t) => t.includes(lowerQuery))
|
|
14513
14974
|
);
|
|
14975
|
+
const qmdStatus = orchestrator.qmd.debugStatus();
|
|
14514
14976
|
if (matches.length === 0) {
|
|
14515
|
-
console.log(
|
|
14977
|
+
console.log(
|
|
14978
|
+
`No results for: "${query}" (QMD unavailable in this CLI process; text search fallback).`
|
|
14979
|
+
);
|
|
14980
|
+
console.log(`QMD status: ${qmdStatus}`);
|
|
14516
14981
|
return;
|
|
14517
14982
|
}
|
|
14518
14983
|
console.log(`
|
|
14519
|
-
=== Text Search: "${query}" (${matches.length} results) ===
|
|
14984
|
+
=== Text Search Fallback: "${query}" (${matches.length} results) ===
|
|
14985
|
+
`);
|
|
14986
|
+
console.log(`QMD status: ${qmdStatus}
|
|
14520
14987
|
`);
|
|
14521
14988
|
for (const m of matches.slice(0, maxResults)) {
|
|
14522
14989
|
console.log(` [${m.frontmatter.category}] ${m.content.slice(0, 120)}`);
|
|
@@ -14932,6 +15399,7 @@ import { readFile as readFile23, writeFile as writeFile21 } from "fs/promises";
|
|
|
14932
15399
|
import { readFileSync as readFileSync4 } from "fs";
|
|
14933
15400
|
import path34 from "path";
|
|
14934
15401
|
import os5 from "os";
|
|
15402
|
+
var ENGRAM_REGISTERED_GUARD = "__openclawEngramRegistered";
|
|
14935
15403
|
function loadPluginConfigFromFile() {
|
|
14936
15404
|
try {
|
|
14937
15405
|
const explicitConfigPath = process.env.OPENCLAW_ENGRAM_CONFIG_PATH || process.env.OPENCLAW_CONFIG_PATH;
|
|
@@ -14946,6 +15414,24 @@ function loadPluginConfigFromFile() {
|
|
|
14946
15414
|
return void 0;
|
|
14947
15415
|
}
|
|
14948
15416
|
}
|
|
15417
|
+
function wildcardToRegExp(pattern) {
|
|
15418
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
15419
|
+
return new RegExp(`^${escaped.replace(/\*/g, ".*")}$`);
|
|
15420
|
+
}
|
|
15421
|
+
function shouldSkipRecallForSession(sessionKey, cfg) {
|
|
15422
|
+
const isCron = sessionKey.includes(":cron:");
|
|
15423
|
+
if (!isCron) return false;
|
|
15424
|
+
if (cfg.cronRecallMode === "none") return true;
|
|
15425
|
+
if (cfg.cronRecallMode === "all") return false;
|
|
15426
|
+
if (cfg.cronRecallAllowlist.length === 0) return true;
|
|
15427
|
+
return !cfg.cronRecallAllowlist.some((pattern) => {
|
|
15428
|
+
try {
|
|
15429
|
+
return wildcardToRegExp(pattern).test(sessionKey);
|
|
15430
|
+
} catch {
|
|
15431
|
+
return false;
|
|
15432
|
+
}
|
|
15433
|
+
});
|
|
15434
|
+
}
|
|
14949
15435
|
var index_default = {
|
|
14950
15436
|
id: "openclaw-engram",
|
|
14951
15437
|
name: "Engram (Local Memory)",
|
|
@@ -14965,6 +15451,11 @@ var index_default = {
|
|
|
14965
15451
|
log.info(
|
|
14966
15452
|
`initialized (debug=${cfg.debug}, qmdEnabled=${cfg.qmdEnabled}, transcriptEnabled=${cfg.transcriptEnabled}, hourlySummariesEnabled=${cfg.hourlySummariesEnabled}, localLlmEnabled=${cfg.localLlmEnabled})`
|
|
14967
15453
|
);
|
|
15454
|
+
if (globalThis[ENGRAM_REGISTERED_GUARD] === true) {
|
|
15455
|
+
log.warn("register called more than once; skipping duplicate hook/tool registration");
|
|
15456
|
+
return;
|
|
15457
|
+
}
|
|
15458
|
+
globalThis[ENGRAM_REGISTERED_GUARD] = true;
|
|
14968
15459
|
const existing = globalThis.__openclawEngramOrchestrator;
|
|
14969
15460
|
const orchestrator = existing?.recall ? existing : new Orchestrator(cfg);
|
|
14970
15461
|
globalThis.__openclawEngramOrchestrator = orchestrator;
|
|
@@ -14978,6 +15469,24 @@ var index_default = {
|
|
|
14978
15469
|
if (!prompt || prompt.length < 5) return;
|
|
14979
15470
|
const sessionKey = ctx?.sessionKey ?? "default";
|
|
14980
15471
|
log.debug(`before_agent_start: sessionKey=${sessionKey}, promptLen=${prompt.length}`);
|
|
15472
|
+
log.debug(
|
|
15473
|
+
`before_agent_start: cronRecallMode=${cfg.cronRecallMode}, allowlistCount=${cfg.cronRecallAllowlist.length}`
|
|
15474
|
+
);
|
|
15475
|
+
if (sessionKey.includes(":cron:") && cfg.cronRecallMode === "allowlist") {
|
|
15476
|
+
const matchedPattern = cfg.cronRecallAllowlist.find((pattern) => {
|
|
15477
|
+
const re = wildcardToRegExp(pattern);
|
|
15478
|
+
return re.test(sessionKey);
|
|
15479
|
+
});
|
|
15480
|
+
log.debug(
|
|
15481
|
+
`before_agent_start: cron allowlist match=${matchedPattern ? "yes" : "no"} pattern=${matchedPattern ?? "none"}`
|
|
15482
|
+
);
|
|
15483
|
+
}
|
|
15484
|
+
if (shouldSkipRecallForSession(sessionKey, cfg)) {
|
|
15485
|
+
log.debug(
|
|
15486
|
+
`before_agent_start: skip recall for cron session ${sessionKey} (mode=${cfg.cronRecallMode})`
|
|
15487
|
+
);
|
|
15488
|
+
return;
|
|
15489
|
+
}
|
|
14981
15490
|
try {
|
|
14982
15491
|
await orchestrator.maybeRunFileHygiene().catch(() => void 0);
|
|
14983
15492
|
const context = await orchestrator.recall(prompt, sessionKey);
|
|
@@ -15159,6 +15668,7 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
15159
15668
|
log.info("engram memory system ready");
|
|
15160
15669
|
},
|
|
15161
15670
|
stop: () => {
|
|
15671
|
+
globalThis[ENGRAM_REGISTERED_GUARD] = false;
|
|
15162
15672
|
log.info("stopped");
|
|
15163
15673
|
}
|
|
15164
15674
|
});
|