@joshuaswarren/openclaw-engram 8.3.14 → 8.3.15
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 +425 -56
- package/dist/index.js.map +1 -1
- package/openclaw.plugin.json +25 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -139,6 +139,7 @@ function parseConfig(raw) {
|
|
|
139
139
|
// Retrieval options
|
|
140
140
|
recencyWeight: typeof cfg.recencyWeight === "number" ? cfg.recencyWeight : 0.2,
|
|
141
141
|
boostAccessCount: cfg.boostAccessCount !== false,
|
|
142
|
+
recordEmptyRecallImpressions: cfg.recordEmptyRecallImpressions === true,
|
|
142
143
|
// v2.2 Advanced Retrieval (safe defaults: off unless enabled)
|
|
143
144
|
queryExpansionEnabled: cfg.queryExpansionEnabled === true,
|
|
144
145
|
queryExpansionMaxQueries: typeof cfg.queryExpansionMaxQueries === "number" ? cfg.queryExpansionMaxQueries : 4,
|
|
@@ -366,8 +367,12 @@ function parseConfig(raw) {
|
|
|
366
367
|
// v8.2: Multi-graph memory (PR 18)
|
|
367
368
|
multiGraphMemoryEnabled: cfg.multiGraphMemoryEnabled === true,
|
|
368
369
|
graphRecallEnabled: cfg.graphRecallEnabled === true,
|
|
370
|
+
graphExpandedIntentEnabled: cfg.graphExpandedIntentEnabled !== false,
|
|
371
|
+
graphAssistInFullModeEnabled: cfg.graphAssistInFullModeEnabled !== false,
|
|
372
|
+
graphAssistMinSeedResults: typeof cfg.graphAssistMinSeedResults === "number" ? Math.max(1, Math.floor(cfg.graphAssistMinSeedResults)) : 3,
|
|
369
373
|
entityGraphEnabled: cfg.entityGraphEnabled !== false,
|
|
370
374
|
timeGraphEnabled: cfg.timeGraphEnabled !== false,
|
|
375
|
+
graphWriteSessionAdjacencyEnabled: cfg.graphWriteSessionAdjacencyEnabled !== false,
|
|
371
376
|
causalGraphEnabled: cfg.causalGraphEnabled !== false,
|
|
372
377
|
maxGraphTraversalSteps: typeof cfg.maxGraphTraversalSteps === "number" ? Math.max(0, cfg.maxGraphTraversalSteps) : 3,
|
|
373
378
|
graphActivationDecay: typeof cfg.graphActivationDecay === "number" ? Math.min(1, Math.max(0, cfg.graphActivationDecay)) : 0.7,
|
|
@@ -3503,11 +3508,27 @@ var QMD_DAEMON_TIMEOUT_MS = 6e4;
|
|
|
3503
3508
|
var QMD_PROBE_TIMEOUT_MS = 8e3;
|
|
3504
3509
|
var QMD_UPDATE_BACKOFF_MS = 15 * 60 * 1e3;
|
|
3505
3510
|
var QMD_EMBED_BACKOFF_MS = 60 * 60 * 1e3;
|
|
3511
|
+
var QMD_CLI_WARN_THROTTLE_MS = 15 * 60 * 1e3;
|
|
3506
3512
|
var QMD_FALLBACK_PATHS = [
|
|
3507
3513
|
path2.join(os2.homedir(), ".bun", "bin", "qmd"),
|
|
3508
3514
|
"/usr/local/bin/qmd",
|
|
3509
3515
|
"/opt/homebrew/bin/qmd"
|
|
3510
3516
|
];
|
|
3517
|
+
var QMD_GLOBAL_STATE_KEY = "__openclawEngramQmdGlobalState";
|
|
3518
|
+
function getGlobalQmdState() {
|
|
3519
|
+
const g = globalThis;
|
|
3520
|
+
if (!g[QMD_GLOBAL_STATE_KEY]) {
|
|
3521
|
+
g[QMD_GLOBAL_STATE_KEY] = {
|
|
3522
|
+
warnedGlobalUpdateBehavior: false,
|
|
3523
|
+
lastGlobalUpdateRunAtMs: null,
|
|
3524
|
+
lastGlobalUpdateFailAtMs: null,
|
|
3525
|
+
lastGlobalEmbedRunAtMs: null,
|
|
3526
|
+
lastGlobalEmbedFailAtMs: null,
|
|
3527
|
+
lastCliWarnAtMs: null
|
|
3528
|
+
};
|
|
3529
|
+
}
|
|
3530
|
+
return g[QMD_GLOBAL_STATE_KEY];
|
|
3531
|
+
}
|
|
3511
3532
|
function sleep(ms) {
|
|
3512
3533
|
return new Promise((r) => setTimeout(r, ms));
|
|
3513
3534
|
}
|
|
@@ -3724,7 +3745,6 @@ var QmdClient = class {
|
|
|
3724
3745
|
lastUpdateFailAtMs = null;
|
|
3725
3746
|
lastEmbedFailAtMs = null;
|
|
3726
3747
|
lastUpdateRunAtMs = null;
|
|
3727
|
-
warnedGlobalUpdateBehavior = false;
|
|
3728
3748
|
updateTimeoutMs;
|
|
3729
3749
|
updateMinIntervalMs;
|
|
3730
3750
|
slowLog;
|
|
@@ -3794,9 +3814,9 @@ ${stderr}`.trim();
|
|
|
3794
3814
|
return true;
|
|
3795
3815
|
} catch (err) {
|
|
3796
3816
|
markProbeFailure(err);
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3817
|
+
this.logCliProbeWarning(
|
|
3818
|
+
`QMD: configured qmdPath failed (${this.configuredQmdPath}): ${this.lastCliProbeError}`
|
|
3819
|
+
);
|
|
3800
3820
|
}
|
|
3801
3821
|
}
|
|
3802
3822
|
try {
|
|
@@ -3827,6 +3847,21 @@ ${stderr}`.trim();
|
|
|
3827
3847
|
return false;
|
|
3828
3848
|
}
|
|
3829
3849
|
}
|
|
3850
|
+
logCliProbeWarning(message) {
|
|
3851
|
+
const state = getGlobalQmdState();
|
|
3852
|
+
const now = Date.now();
|
|
3853
|
+
const canWarn = state.lastCliWarnAtMs === null || now - state.lastCliWarnAtMs >= QMD_CLI_WARN_THROTTLE_MS;
|
|
3854
|
+
if (!canWarn) {
|
|
3855
|
+
log.debug(message);
|
|
3856
|
+
return;
|
|
3857
|
+
}
|
|
3858
|
+
state.lastCliWarnAtMs = now;
|
|
3859
|
+
if (this.daemonAvailable) {
|
|
3860
|
+
log.debug(message);
|
|
3861
|
+
return;
|
|
3862
|
+
}
|
|
3863
|
+
log.warn(message);
|
|
3864
|
+
}
|
|
3830
3865
|
/** Re-probe daemon if it was down and recheck interval has elapsed. */
|
|
3831
3866
|
async maybeProbeDaemon() {
|
|
3832
3867
|
if (!this.daemonSession) return;
|
|
@@ -3951,9 +3986,11 @@ ${stderr}`.trim();
|
|
|
3951
3986
|
*/
|
|
3952
3987
|
async hybridSearch(query, collection, maxResults) {
|
|
3953
3988
|
const n = maxResults ?? this.maxResults;
|
|
3989
|
+
const trimmed = query.trim();
|
|
3990
|
+
if (!trimmed) return [];
|
|
3954
3991
|
const [bm25Results, vectorResults] = await Promise.all([
|
|
3955
|
-
this.bm25Search(
|
|
3956
|
-
this.vectorSearch(
|
|
3992
|
+
this.bm25Search(trimmed, collection, n),
|
|
3993
|
+
this.vectorSearch(trimmed, collection, n)
|
|
3957
3994
|
]);
|
|
3958
3995
|
const merged = /* @__PURE__ */ new Map();
|
|
3959
3996
|
for (const r of [...bm25Results, ...vectorResults]) {
|
|
@@ -4097,6 +4134,7 @@ ${stderr}`.trim();
|
|
|
4097
4134
|
}
|
|
4098
4135
|
async update() {
|
|
4099
4136
|
if (this.available === false) return;
|
|
4137
|
+
const globalState = getGlobalQmdState();
|
|
4100
4138
|
if (this.lastUpdateRunAtMs && Date.now() - this.lastUpdateRunAtMs < this.updateMinIntervalMs) {
|
|
4101
4139
|
log.debug("QMD update: suppressed due to min-interval gate");
|
|
4102
4140
|
return;
|
|
@@ -4105,9 +4143,17 @@ ${stderr}`.trim();
|
|
|
4105
4143
|
log.debug("QMD update: suppressed due to recent failures (backoff)");
|
|
4106
4144
|
return;
|
|
4107
4145
|
}
|
|
4146
|
+
if (globalState.lastGlobalUpdateRunAtMs && Date.now() - globalState.lastGlobalUpdateRunAtMs < this.updateMinIntervalMs) {
|
|
4147
|
+
log.debug("QMD update: suppressed by global min-interval gate");
|
|
4148
|
+
return;
|
|
4149
|
+
}
|
|
4150
|
+
if (globalState.lastGlobalUpdateFailAtMs && Date.now() - globalState.lastGlobalUpdateFailAtMs < QMD_UPDATE_BACKOFF_MS) {
|
|
4151
|
+
log.debug("QMD update: suppressed by global failure backoff");
|
|
4152
|
+
return;
|
|
4153
|
+
}
|
|
4108
4154
|
try {
|
|
4109
|
-
if (!
|
|
4110
|
-
|
|
4155
|
+
if (!globalState.warnedGlobalUpdateBehavior) {
|
|
4156
|
+
globalState.warnedGlobalUpdateBehavior = true;
|
|
4111
4157
|
log.warn(
|
|
4112
4158
|
"QMD update runs globally across collections in current CLI versions; Engram now rate-limits update calls to reduce gateway load."
|
|
4113
4159
|
);
|
|
@@ -4118,20 +4164,33 @@ ${stderr}`.trim();
|
|
|
4118
4164
|
if (this.slowLog?.enabled && durationMs >= this.slowLog.thresholdMs) {
|
|
4119
4165
|
log.warn(`SLOW QMD update: durationMs=${durationMs}`);
|
|
4120
4166
|
}
|
|
4121
|
-
|
|
4167
|
+
const now = Date.now();
|
|
4168
|
+
this.lastUpdateRunAtMs = now;
|
|
4169
|
+
globalState.lastGlobalUpdateRunAtMs = now;
|
|
4122
4170
|
log.debug("QMD update completed");
|
|
4123
4171
|
} catch (err) {
|
|
4124
|
-
|
|
4172
|
+
const now = Date.now();
|
|
4173
|
+
this.lastUpdateFailAtMs = now;
|
|
4174
|
+
globalState.lastGlobalUpdateFailAtMs = now;
|
|
4125
4175
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4126
4176
|
log.warn(`QMD update failed: ${msg}`);
|
|
4127
4177
|
}
|
|
4128
4178
|
}
|
|
4129
4179
|
async embed() {
|
|
4130
4180
|
if (this.available === false) return;
|
|
4181
|
+
const globalState = getGlobalQmdState();
|
|
4131
4182
|
if (this.lastEmbedFailAtMs && Date.now() - this.lastEmbedFailAtMs < QMD_EMBED_BACKOFF_MS) {
|
|
4132
4183
|
log.debug("QMD embed: suppressed due to recent failures (backoff)");
|
|
4133
4184
|
return;
|
|
4134
4185
|
}
|
|
4186
|
+
if (globalState.lastGlobalEmbedRunAtMs && Date.now() - globalState.lastGlobalEmbedRunAtMs < this.updateMinIntervalMs) {
|
|
4187
|
+
log.debug("QMD embed: suppressed by global min-interval gate");
|
|
4188
|
+
return;
|
|
4189
|
+
}
|
|
4190
|
+
if (globalState.lastGlobalEmbedFailAtMs && Date.now() - globalState.lastGlobalEmbedFailAtMs < QMD_EMBED_BACKOFF_MS) {
|
|
4191
|
+
log.debug("QMD embed: suppressed by global failure backoff");
|
|
4192
|
+
return;
|
|
4193
|
+
}
|
|
4135
4194
|
try {
|
|
4136
4195
|
const startedAtMs = Date.now();
|
|
4137
4196
|
await runQmd(["embed", "-c", this.collection], 3e5, this.qmdPath);
|
|
@@ -4139,9 +4198,12 @@ ${stderr}`.trim();
|
|
|
4139
4198
|
if (this.slowLog?.enabled && durationMs >= this.slowLog.thresholdMs) {
|
|
4140
4199
|
log.warn(`SLOW QMD embed: durationMs=${durationMs}`);
|
|
4141
4200
|
}
|
|
4201
|
+
globalState.lastGlobalEmbedRunAtMs = Date.now();
|
|
4142
4202
|
log.debug("QMD embed completed");
|
|
4143
4203
|
} catch (err) {
|
|
4144
|
-
|
|
4204
|
+
const now = Date.now();
|
|
4205
|
+
this.lastEmbedFailAtMs = now;
|
|
4206
|
+
globalState.lastGlobalEmbedFailAtMs = now;
|
|
4145
4207
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4146
4208
|
log.warn(`QMD embed failed: ${msg}`);
|
|
4147
4209
|
}
|
|
@@ -8341,6 +8403,13 @@ function planRecallMode(prompt) {
|
|
|
8341
8403
|
}
|
|
8342
8404
|
return "full";
|
|
8343
8405
|
}
|
|
8406
|
+
function hasBroadGraphIntent(prompt) {
|
|
8407
|
+
const p = normalizeTextInput(prompt).trim().toLowerCase();
|
|
8408
|
+
if (!p) return false;
|
|
8409
|
+
return /\b(what changed|how did we get here|why did this happen|what led to|cause chain|dependency chain|regression chain|failure chain)\b/i.test(
|
|
8410
|
+
p
|
|
8411
|
+
);
|
|
8412
|
+
}
|
|
8344
8413
|
|
|
8345
8414
|
// src/recall-query-policy.ts
|
|
8346
8415
|
var DEFAULT_STOPWORDS = /* @__PURE__ */ new Set([
|
|
@@ -8883,6 +8952,32 @@ import * as fs from "fs";
|
|
|
8883
8952
|
import * as path14 from "path";
|
|
8884
8953
|
import { mkdir as mkdir11, readFile as readFile12, writeFile as writeFile11, readdir as readdir7 } from "fs/promises";
|
|
8885
8954
|
var TMT_DIR = "tmt";
|
|
8955
|
+
var TMT_LEVEL_INPUT_LIMITS = {
|
|
8956
|
+
hour: { totalChars: 48e3, itemChars: 2e3, maxItems: 64 },
|
|
8957
|
+
day: { totalChars: 64e3, itemChars: 1800, maxItems: 96 },
|
|
8958
|
+
week: { totalChars: 72e3, itemChars: 1600, maxItems: 120 },
|
|
8959
|
+
persona: { totalChars: 8e4, itemChars: 1600, maxItems: 120 }
|
|
8960
|
+
};
|
|
8961
|
+
function capTmtSummaryInputs(inputs, level) {
|
|
8962
|
+
const { totalChars, itemChars, maxItems } = TMT_LEVEL_INPUT_LIMITS[level];
|
|
8963
|
+
if (inputs.length === 0) return [];
|
|
8964
|
+
const capped = [];
|
|
8965
|
+
let usedChars = 0;
|
|
8966
|
+
for (const raw of inputs) {
|
|
8967
|
+
if (capped.length >= maxItems || usedChars >= totalChars) break;
|
|
8968
|
+
const trimmed = raw.trim();
|
|
8969
|
+
if (!trimmed) continue;
|
|
8970
|
+
const next = trimmed.length > itemChars ? `${trimmed.slice(0, itemChars - 1)}\u2026` : trimmed;
|
|
8971
|
+
const separator = capped.length === 0 ? 0 : 2;
|
|
8972
|
+
if (usedChars + separator + next.length > totalChars) break;
|
|
8973
|
+
capped.push(next);
|
|
8974
|
+
usedChars += separator + next.length;
|
|
8975
|
+
}
|
|
8976
|
+
if (capped.length > 0) return capped;
|
|
8977
|
+
const fallback = inputs[0]?.trim() ?? "";
|
|
8978
|
+
if (!fallback) return [];
|
|
8979
|
+
return [fallback.length > itemChars ? `${fallback.slice(0, itemChars - 1)}\u2026` : fallback];
|
|
8980
|
+
}
|
|
8886
8981
|
function tmtDir(baseDir) {
|
|
8887
8982
|
return path14.join(baseDir, TMT_DIR);
|
|
8888
8983
|
}
|
|
@@ -8979,7 +9074,7 @@ var TmtBuilder = class {
|
|
|
8979
9074
|
if (!shouldBuild) continue;
|
|
8980
9075
|
let summary;
|
|
8981
9076
|
try {
|
|
8982
|
-
summary = await summarize(entries.map((e) => e.content), "hour");
|
|
9077
|
+
summary = await summarize(capTmtSummaryInputs(entries.map((e) => e.content), "hour"), "hour");
|
|
8983
9078
|
} catch (err) {
|
|
8984
9079
|
console.warn(`[engram] tmt: hour node summarize failed for ${key} (ignored): ${err}`);
|
|
8985
9080
|
continue;
|
|
@@ -9044,7 +9139,7 @@ var TmtBuilder = class {
|
|
|
9044
9139
|
if (inputs.length === 0) inputs.push(...entries.map((e) => e.content));
|
|
9045
9140
|
let summary;
|
|
9046
9141
|
try {
|
|
9047
|
-
summary = await summarize(inputs, "day");
|
|
9142
|
+
summary = await summarize(capTmtSummaryInputs(inputs, "day"), "day");
|
|
9048
9143
|
} catch (err) {
|
|
9049
9144
|
console.warn(`[engram] tmt: day node summarize failed for ${date} (ignored): ${err}`);
|
|
9050
9145
|
continue;
|
|
@@ -9114,7 +9209,7 @@ var TmtBuilder = class {
|
|
|
9114
9209
|
}
|
|
9115
9210
|
}
|
|
9116
9211
|
const inputs = daySummaries.length > 0 ? daySummaries : entries.map((e) => e.content);
|
|
9117
|
-
const summary = await summarize(inputs, "week");
|
|
9212
|
+
const summary = await summarize(capTmtSummaryInputs(inputs, "week"), "week");
|
|
9118
9213
|
const sortedCreated = entries.map((e) => e.created).sort();
|
|
9119
9214
|
const fm = {
|
|
9120
9215
|
level: "week",
|
|
@@ -9184,7 +9279,7 @@ var TmtBuilder = class {
|
|
|
9184
9279
|
}
|
|
9185
9280
|
}
|
|
9186
9281
|
if (!shouldBuild) return;
|
|
9187
|
-
const summary = await summarize(weekSummaries, "persona");
|
|
9282
|
+
const summary = await summarize(capTmtSummaryInputs(weekSummaries, "persona"), "persona");
|
|
9188
9283
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9189
9284
|
const fm = {
|
|
9190
9285
|
level: "persona",
|
|
@@ -10484,7 +10579,10 @@ function computeArtifactRecallLimit(recallMode, recallResultLimit, verbatimArtif
|
|
|
10484
10579
|
return base;
|
|
10485
10580
|
}
|
|
10486
10581
|
function resolveEffectiveRecallMode(options) {
|
|
10487
|
-
|
|
10582
|
+
let plannedMode = options.plannerEnabled ? planRecallMode(options.prompt) : "full";
|
|
10583
|
+
if (plannedMode !== "graph_mode" && options.plannerEnabled && options.graphExpandedIntentEnabled === true && hasBroadGraphIntent(options.prompt)) {
|
|
10584
|
+
plannedMode = "graph_mode";
|
|
10585
|
+
}
|
|
10488
10586
|
if (plannedMode === "graph_mode" && (!options.graphRecallEnabled || !options.multiGraphMemoryEnabled)) {
|
|
10489
10587
|
return "full";
|
|
10490
10588
|
}
|
|
@@ -11190,34 +11288,39 @@ var Orchestrator = class _Orchestrator {
|
|
|
11190
11288
|
}
|
|
11191
11289
|
async fetchQmdMemoryResultsWithArtifactTopUp(prompt, qmdFetchLimit, qmdHybridFetchLimit, options) {
|
|
11192
11290
|
let fetchLimit = Math.max(qmdFetchLimit, qmdHybridFetchLimit);
|
|
11193
|
-
const maxFetchLimit = Math.min(
|
|
11194
|
-
const MAX_ATTEMPTS =
|
|
11291
|
+
const maxFetchLimit = Math.min(320, Math.max(fetchLimit, qmdFetchLimit * 5));
|
|
11292
|
+
const MAX_ATTEMPTS = 2;
|
|
11293
|
+
const QMD_RECALL_BUDGET_MS = 25e3;
|
|
11294
|
+
const startedAtMs = Date.now();
|
|
11195
11295
|
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
|
-
};
|
|
11214
11296
|
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt += 1) {
|
|
11215
|
-
|
|
11297
|
+
if (Date.now() - startedAtMs >= QMD_RECALL_BUDGET_MS) {
|
|
11298
|
+
break;
|
|
11299
|
+
}
|
|
11300
|
+
const primaryResults = await this.qmd.search(
|
|
11216
11301
|
prompt,
|
|
11217
11302
|
options.collection,
|
|
11218
11303
|
fetchLimit
|
|
11219
11304
|
);
|
|
11220
|
-
|
|
11305
|
+
let mergedResults = primaryResults;
|
|
11306
|
+
if (primaryResults.length < qmdFetchLimit && Date.now() - startedAtMs < QMD_RECALL_BUDGET_MS) {
|
|
11307
|
+
const hybridResults = await this.qmd.hybridSearch(prompt, options.collection, fetchLimit);
|
|
11308
|
+
if (hybridResults.length > 0) {
|
|
11309
|
+
const mergedByPath = /* @__PURE__ */ new Map();
|
|
11310
|
+
for (const result of [...primaryResults, ...hybridResults]) {
|
|
11311
|
+
const key = result.path || result.docid;
|
|
11312
|
+
const existing = mergedByPath.get(key);
|
|
11313
|
+
if (!existing || result.score > existing.score) {
|
|
11314
|
+
mergedByPath.set(key, {
|
|
11315
|
+
...result,
|
|
11316
|
+
snippet: result.snippet || existing?.snippet || ""
|
|
11317
|
+
});
|
|
11318
|
+
}
|
|
11319
|
+
}
|
|
11320
|
+
mergedResults = [...mergedByPath.values()].sort((a, b) => b.score - a.score).slice(0, fetchLimit);
|
|
11321
|
+
}
|
|
11322
|
+
}
|
|
11323
|
+
const filteredResults = filterRecallCandidates(mergedResults, {
|
|
11221
11324
|
namespacesEnabled: options.namespacesEnabled,
|
|
11222
11325
|
recallNamespaces: options.recallNamespaces,
|
|
11223
11326
|
resolveNamespace: options.resolveNamespace,
|
|
@@ -11229,14 +11332,10 @@ var Orchestrator = class _Orchestrator {
|
|
|
11229
11332
|
if (filteredResults.length > bestFiltered.length) {
|
|
11230
11333
|
bestFiltered = filteredResults;
|
|
11231
11334
|
}
|
|
11232
|
-
if (
|
|
11233
|
-
const queryFallback = await runQueryFallback();
|
|
11234
|
-
if (queryFallback.length > 0) {
|
|
11235
|
-
return queryFallback.slice(0, qmdFetchLimit);
|
|
11236
|
-
}
|
|
11335
|
+
if (mergedResults.length === 0) {
|
|
11237
11336
|
return filteredResults;
|
|
11238
11337
|
}
|
|
11239
|
-
if (
|
|
11338
|
+
if (mergedResults.length < fetchLimit && filteredResults.length > 0) {
|
|
11240
11339
|
return filteredResults;
|
|
11241
11340
|
}
|
|
11242
11341
|
if (fetchLimit >= maxFetchLimit) {
|
|
@@ -11245,13 +11344,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
11245
11344
|
const growth = Math.max(20, Math.floor(fetchLimit / 2));
|
|
11246
11345
|
fetchLimit = Math.min(maxFetchLimit, fetchLimit + growth);
|
|
11247
11346
|
}
|
|
11248
|
-
|
|
11249
|
-
const queryFallback = await runQueryFallback();
|
|
11250
|
-
if (queryFallback.length > 0) {
|
|
11251
|
-
return queryFallback.slice(0, qmdFetchLimit);
|
|
11252
|
-
}
|
|
11253
|
-
}
|
|
11254
|
-
return bestFiltered;
|
|
11347
|
+
return bestFiltered.slice(0, qmdFetchLimit);
|
|
11255
11348
|
}
|
|
11256
11349
|
async expandResultsViaGraph(options) {
|
|
11257
11350
|
const byNamespace = /* @__PURE__ */ new Map();
|
|
@@ -11333,6 +11426,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
11333
11426
|
async recallInternal(prompt, sessionKey) {
|
|
11334
11427
|
const recallStart = Date.now();
|
|
11335
11428
|
const timings = {};
|
|
11429
|
+
const promptHash = createHash4("sha256").update(prompt).digest("hex");
|
|
11336
11430
|
const sections = [];
|
|
11337
11431
|
const queryPolicy = buildRecallQueryPolicy(prompt, sessionKey, {
|
|
11338
11432
|
cronRecallPolicyEnabled: this.config.cronRecallPolicyEnabled,
|
|
@@ -11341,11 +11435,16 @@ var Orchestrator = class _Orchestrator {
|
|
|
11341
11435
|
cronConversationRecallMode: this.config.cronConversationRecallMode
|
|
11342
11436
|
});
|
|
11343
11437
|
const retrievalQuery = queryPolicy.retrievalQuery || prompt;
|
|
11438
|
+
const retrievalQueryHash = createHash4("sha256").update(retrievalQuery).digest("hex");
|
|
11439
|
+
let impressionRecorded = false;
|
|
11440
|
+
let recallSource = "none";
|
|
11441
|
+
let recalledMemoryCount = 0;
|
|
11344
11442
|
timings.queryPolicy = `${queryPolicy.promptShape}/${queryPolicy.retrievalBudgetMode}${queryPolicy.skipConversationRecall ? "/skip-conv" : ""}`;
|
|
11345
11443
|
const recallMode = resolveEffectiveRecallMode({
|
|
11346
11444
|
plannerEnabled: this.config.recallPlannerEnabled,
|
|
11347
11445
|
graphRecallEnabled: this.config.graphRecallEnabled,
|
|
11348
11446
|
multiGraphMemoryEnabled: this.config.multiGraphMemoryEnabled,
|
|
11447
|
+
graphExpandedIntentEnabled: this.config.graphExpandedIntentEnabled === true,
|
|
11349
11448
|
prompt
|
|
11350
11449
|
});
|
|
11351
11450
|
timings.recallPlan = recallMode;
|
|
@@ -11366,6 +11465,27 @@ var Orchestrator = class _Orchestrator {
|
|
|
11366
11465
|
const embeddingFetchLimit = computedFetchLimit;
|
|
11367
11466
|
if (recallMode === "no_recall") {
|
|
11368
11467
|
timings.total = `${Date.now() - recallStart}ms`;
|
|
11468
|
+
this.emitTrace({
|
|
11469
|
+
kind: "recall_summary",
|
|
11470
|
+
traceId: createHash4("sha256").update(`${sessionKey ?? "default"}:${Date.now()}:${promptHash}`).digest("hex").slice(0, 16),
|
|
11471
|
+
operation: "recall",
|
|
11472
|
+
sessionKey,
|
|
11473
|
+
promptHash,
|
|
11474
|
+
promptLength: prompt.length,
|
|
11475
|
+
retrievalQueryHash,
|
|
11476
|
+
retrievalQueryLength: retrievalQuery.length,
|
|
11477
|
+
recallMode,
|
|
11478
|
+
recallResultLimit,
|
|
11479
|
+
qmdEnabled: this.config.qmdEnabled,
|
|
11480
|
+
qmdAvailable: this.qmd.isAvailable(),
|
|
11481
|
+
recallNamespaces: [],
|
|
11482
|
+
source: recallSource,
|
|
11483
|
+
recalledMemoryCount,
|
|
11484
|
+
injected: false,
|
|
11485
|
+
contextChars: 0,
|
|
11486
|
+
durationMs: Date.now() - recallStart,
|
|
11487
|
+
timings: { ...timings }
|
|
11488
|
+
});
|
|
11369
11489
|
return "";
|
|
11370
11490
|
}
|
|
11371
11491
|
const principal = resolvePrincipal(sessionKey, this.config);
|
|
@@ -11512,7 +11632,8 @@ ${tmtNode.summary}`);
|
|
|
11512
11632
|
);
|
|
11513
11633
|
}
|
|
11514
11634
|
memoryResults = memoryResults.filter((r) => !isArtifactMemoryPath(r.path));
|
|
11515
|
-
|
|
11635
|
+
const shouldRunGraphExpansion = recallMode === "graph_mode" || this.config.multiGraphMemoryEnabled && this.config.graphAssistInFullModeEnabled !== false && recallMode === "full" && memoryResults.length >= Math.max(1, this.config.graphAssistMinSeedResults ?? 3);
|
|
11636
|
+
if (shouldRunGraphExpansion) {
|
|
11516
11637
|
const {
|
|
11517
11638
|
merged,
|
|
11518
11639
|
seedPaths,
|
|
@@ -11567,6 +11688,8 @@ ${tmtNode.summary}`);
|
|
|
11567
11688
|
}
|
|
11568
11689
|
memoryResults = memoryResults.slice(0, recallResultLimit);
|
|
11569
11690
|
if (memoryResults.length > 0) {
|
|
11691
|
+
recallSource = "hot_qmd";
|
|
11692
|
+
recalledMemoryCount = memoryResults.length;
|
|
11570
11693
|
this.publishRecallResults({
|
|
11571
11694
|
title: "Relevant Memories",
|
|
11572
11695
|
results: memoryResults,
|
|
@@ -11574,6 +11697,7 @@ ${tmtNode.summary}`);
|
|
|
11574
11697
|
retrievalQuery,
|
|
11575
11698
|
sessionKey
|
|
11576
11699
|
});
|
|
11700
|
+
impressionRecorded = true;
|
|
11577
11701
|
} else {
|
|
11578
11702
|
const embeddingResults = await this.searchEmbeddingFallback(retrievalQuery, embeddingFetchLimit);
|
|
11579
11703
|
const scopedCandidates = filterRecallCandidates(embeddingResults, {
|
|
@@ -11587,6 +11711,8 @@ ${tmtNode.summary}`);
|
|
|
11587
11711
|
recallResultLimit
|
|
11588
11712
|
);
|
|
11589
11713
|
if (scoped.length > 0) {
|
|
11714
|
+
recallSource = "hot_embedding";
|
|
11715
|
+
recalledMemoryCount = scoped.length;
|
|
11590
11716
|
this.publishRecallResults({
|
|
11591
11717
|
title: "Relevant Memories",
|
|
11592
11718
|
results: scoped,
|
|
@@ -11594,6 +11720,7 @@ ${tmtNode.summary}`);
|
|
|
11594
11720
|
retrievalQuery,
|
|
11595
11721
|
sessionKey
|
|
11596
11722
|
});
|
|
11723
|
+
impressionRecorded = true;
|
|
11597
11724
|
} else {
|
|
11598
11725
|
const longTerm = await this.applyColdFallbackPipeline({
|
|
11599
11726
|
prompt: retrievalQuery,
|
|
@@ -11601,6 +11728,8 @@ ${tmtNode.summary}`);
|
|
|
11601
11728
|
recallResultLimit
|
|
11602
11729
|
});
|
|
11603
11730
|
if (longTerm.length > 0) {
|
|
11731
|
+
recallSource = "cold_fallback";
|
|
11732
|
+
recalledMemoryCount = longTerm.length;
|
|
11604
11733
|
this.publishRecallResults({
|
|
11605
11734
|
title: "Long-Term Memories (Fallback)",
|
|
11606
11735
|
results: longTerm,
|
|
@@ -11608,6 +11737,7 @@ ${tmtNode.summary}`);
|
|
|
11608
11737
|
retrievalQuery,
|
|
11609
11738
|
sessionKey
|
|
11610
11739
|
});
|
|
11740
|
+
impressionRecorded = true;
|
|
11611
11741
|
}
|
|
11612
11742
|
}
|
|
11613
11743
|
}
|
|
@@ -11644,6 +11774,8 @@ ${tmtNode.summary}`);
|
|
|
11644
11774
|
retrievalQuery
|
|
11645
11775
|
)).slice(0, recallResultLimit);
|
|
11646
11776
|
if (scoped.length > 0) {
|
|
11777
|
+
recallSource = "hot_embedding";
|
|
11778
|
+
recalledMemoryCount = scoped.length;
|
|
11647
11779
|
this.publishRecallResults({
|
|
11648
11780
|
title: "Relevant Memories",
|
|
11649
11781
|
results: scoped,
|
|
@@ -11651,6 +11783,7 @@ ${tmtNode.summary}`);
|
|
|
11651
11783
|
retrievalQuery,
|
|
11652
11784
|
sessionKey
|
|
11653
11785
|
});
|
|
11786
|
+
impressionRecorded = true;
|
|
11654
11787
|
} else {
|
|
11655
11788
|
const memories = await this.readAllMemoriesForNamespaces(recallNamespaces);
|
|
11656
11789
|
if (memories.length > 0) {
|
|
@@ -11676,6 +11809,8 @@ ${tmtNode.summary}`);
|
|
|
11676
11809
|
preloadedMap
|
|
11677
11810
|
)).sort((a, b) => b.score - a.score).slice(0, recallResultLimit);
|
|
11678
11811
|
if (recent.length > 0) {
|
|
11812
|
+
recallSource = "recent_scan";
|
|
11813
|
+
recalledMemoryCount = recent.length;
|
|
11679
11814
|
this.publishRecallResults({
|
|
11680
11815
|
title: "Recent Memories",
|
|
11681
11816
|
results: recent,
|
|
@@ -11683,6 +11818,7 @@ ${tmtNode.summary}`);
|
|
|
11683
11818
|
retrievalQuery,
|
|
11684
11819
|
sessionKey
|
|
11685
11820
|
});
|
|
11821
|
+
impressionRecorded = true;
|
|
11686
11822
|
} else {
|
|
11687
11823
|
const longTerm = await this.applyColdFallbackPipeline({
|
|
11688
11824
|
prompt: retrievalQuery,
|
|
@@ -11690,6 +11826,8 @@ ${tmtNode.summary}`);
|
|
|
11690
11826
|
recallResultLimit
|
|
11691
11827
|
});
|
|
11692
11828
|
if (longTerm.length > 0) {
|
|
11829
|
+
recallSource = "cold_fallback";
|
|
11830
|
+
recalledMemoryCount = longTerm.length;
|
|
11693
11831
|
this.publishRecallResults({
|
|
11694
11832
|
title: "Long-Term Memories (Fallback)",
|
|
11695
11833
|
results: longTerm,
|
|
@@ -11697,6 +11835,7 @@ ${tmtNode.summary}`);
|
|
|
11697
11835
|
retrievalQuery,
|
|
11698
11836
|
sessionKey
|
|
11699
11837
|
});
|
|
11838
|
+
impressionRecorded = true;
|
|
11700
11839
|
}
|
|
11701
11840
|
}
|
|
11702
11841
|
} else {
|
|
@@ -11706,6 +11845,8 @@ ${tmtNode.summary}`);
|
|
|
11706
11845
|
recallResultLimit
|
|
11707
11846
|
});
|
|
11708
11847
|
if (longTerm.length > 0) {
|
|
11848
|
+
recallSource = "cold_fallback";
|
|
11849
|
+
recalledMemoryCount = longTerm.length;
|
|
11709
11850
|
this.publishRecallResults({
|
|
11710
11851
|
title: "Long-Term Memories (Fallback)",
|
|
11711
11852
|
results: longTerm,
|
|
@@ -11713,6 +11854,7 @@ ${tmtNode.summary}`);
|
|
|
11713
11854
|
retrievalQuery,
|
|
11714
11855
|
sessionKey
|
|
11715
11856
|
});
|
|
11857
|
+
impressionRecorded = true;
|
|
11716
11858
|
}
|
|
11717
11859
|
}
|
|
11718
11860
|
}
|
|
@@ -11848,8 +11990,32 @@ _Context: ${topQuestion.context}_`);
|
|
|
11848
11990
|
timings.total = `${Date.now() - recallStart}ms`;
|
|
11849
11991
|
const timingParts = Object.entries(timings).map(([k, v]) => `${k}=${v}`).join(", ");
|
|
11850
11992
|
log.debug(`recall: ${timingParts}`);
|
|
11851
|
-
if (
|
|
11852
|
-
|
|
11993
|
+
if (!impressionRecorded && sessionKey && this.config.recordEmptyRecallImpressions) {
|
|
11994
|
+
this.lastRecall.record({ sessionKey, query: retrievalQuery, memoryIds: [] }).catch((err) => log.debug(`last recall record failed: ${err}`));
|
|
11995
|
+
}
|
|
11996
|
+
const context = sections.length === 0 ? "" : sections.join("\n\n---\n\n");
|
|
11997
|
+
this.emitTrace({
|
|
11998
|
+
kind: "recall_summary",
|
|
11999
|
+
traceId: createHash4("sha256").update(`${sessionKey ?? "default"}:${Date.now()}:${promptHash}`).digest("hex").slice(0, 16),
|
|
12000
|
+
operation: "recall",
|
|
12001
|
+
sessionKey,
|
|
12002
|
+
promptHash,
|
|
12003
|
+
promptLength: prompt.length,
|
|
12004
|
+
retrievalQueryHash,
|
|
12005
|
+
retrievalQueryLength: retrievalQuery.length,
|
|
12006
|
+
recallMode,
|
|
12007
|
+
recallResultLimit,
|
|
12008
|
+
qmdEnabled: this.config.qmdEnabled,
|
|
12009
|
+
qmdAvailable: this.qmd.isAvailable(),
|
|
12010
|
+
recallNamespaces,
|
|
12011
|
+
source: recallSource,
|
|
12012
|
+
recalledMemoryCount,
|
|
12013
|
+
injected: context.length > 0,
|
|
12014
|
+
contextChars: context.length,
|
|
12015
|
+
durationMs: Date.now() - recallStart,
|
|
12016
|
+
timings: { ...timings }
|
|
12017
|
+
});
|
|
12018
|
+
return context;
|
|
11853
12019
|
}
|
|
11854
12020
|
async processTurn(role, content, sessionKey) {
|
|
11855
12021
|
if (role !== "user" && role !== "assistant") {
|
|
@@ -12426,6 +12592,9 @@ _Context: ${topQuestion.context}_`);
|
|
|
12426
12592
|
} catch {
|
|
12427
12593
|
}
|
|
12428
12594
|
}
|
|
12595
|
+
if (recentInThread.length === 0 && this.config.graphWriteSessionAdjacencyEnabled !== false && fallbackCausalPredecessor && fallbackCausalPredecessor !== memoryRelPath) {
|
|
12596
|
+
recentInThread.push(fallbackCausalPredecessor);
|
|
12597
|
+
}
|
|
12429
12598
|
const causalPredecessor = recentInThread[recentInThread.length - 1] ?? fallbackCausalPredecessor;
|
|
12430
12599
|
await this.graphIndexFor(storage).onMemoryWritten({
|
|
12431
12600
|
memoryPath: memoryRelPath,
|
|
@@ -12938,6 +13107,14 @@ ${snippet}`;
|
|
|
12938
13107
|
|
|
12939
13108
|
${lines.join("\n\n")}`;
|
|
12940
13109
|
}
|
|
13110
|
+
emitTrace(event) {
|
|
13111
|
+
try {
|
|
13112
|
+
const cb = globalThis.__openclawEngramTrace;
|
|
13113
|
+
if (typeof cb === "function") cb(event);
|
|
13114
|
+
} catch (err) {
|
|
13115
|
+
log.debug(`trace callback failed: ${err}`);
|
|
13116
|
+
}
|
|
13117
|
+
}
|
|
12941
13118
|
publishRecallResults(options) {
|
|
12942
13119
|
const memoryIds = this.extractMemoryIdsFromResults(options.results);
|
|
12943
13120
|
this.trackMemoryAccess(memoryIds);
|
|
@@ -14298,7 +14475,7 @@ mistakes: ${res.mistakesCount} patterns`
|
|
|
14298
14475
|
|
|
14299
14476
|
// src/cli.ts
|
|
14300
14477
|
import path33 from "path";
|
|
14301
|
-
import { access as access2, readFile as readFile22 } from "fs/promises";
|
|
14478
|
+
import { access as access2, readFile as readFile22, readdir as readdir12, unlink as unlink3 } from "fs/promises";
|
|
14302
14479
|
|
|
14303
14480
|
// src/transfer/export-json.ts
|
|
14304
14481
|
import path25 from "path";
|
|
@@ -14769,6 +14946,54 @@ async function detectImportFormat(fromPath) {
|
|
|
14769
14946
|
}
|
|
14770
14947
|
|
|
14771
14948
|
// src/cli.ts
|
|
14949
|
+
function rankCandidateForKeep(a, b) {
|
|
14950
|
+
const aConfidence = typeof a.frontmatter.confidence === "number" ? a.frontmatter.confidence : 0;
|
|
14951
|
+
const bConfidence = typeof b.frontmatter.confidence === "number" ? b.frontmatter.confidence : 0;
|
|
14952
|
+
if (aConfidence !== bConfidence) return bConfidence - aConfidence;
|
|
14953
|
+
const aTs = Date.parse(a.frontmatter.updated ?? a.frontmatter.created ?? "");
|
|
14954
|
+
const bTs = Date.parse(b.frontmatter.updated ?? b.frontmatter.created ?? "");
|
|
14955
|
+
const aTime = Number.isNaN(aTs) ? 0 : aTs;
|
|
14956
|
+
const bTime = Number.isNaN(bTs) ? 0 : bTs;
|
|
14957
|
+
if (aTime !== bTime) return bTime - aTime;
|
|
14958
|
+
return a.path.localeCompare(b.path);
|
|
14959
|
+
}
|
|
14960
|
+
function buildDedupePlan(memories, keyBuilder) {
|
|
14961
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
14962
|
+
for (const memory of memories) {
|
|
14963
|
+
const key = keyBuilder(memory);
|
|
14964
|
+
if (key.length === 0) continue;
|
|
14965
|
+
const existing = byKey.get(key);
|
|
14966
|
+
if (existing) {
|
|
14967
|
+
existing.push(memory);
|
|
14968
|
+
} else {
|
|
14969
|
+
byKey.set(key, [memory]);
|
|
14970
|
+
}
|
|
14971
|
+
}
|
|
14972
|
+
const keepPaths = [];
|
|
14973
|
+
const deletePaths = [];
|
|
14974
|
+
let groups = 0;
|
|
14975
|
+
let duplicates = 0;
|
|
14976
|
+
for (const entries of byKey.values()) {
|
|
14977
|
+
if (entries.length <= 1) continue;
|
|
14978
|
+
groups += 1;
|
|
14979
|
+
duplicates += entries.length - 1;
|
|
14980
|
+
const ranked = [...entries].sort(rankCandidateForKeep);
|
|
14981
|
+
keepPaths.push(ranked[0].path);
|
|
14982
|
+
for (let i = 1; i < ranked.length; i += 1) {
|
|
14983
|
+
deletePaths.push(ranked[i].path);
|
|
14984
|
+
}
|
|
14985
|
+
}
|
|
14986
|
+
return { groups, duplicates, keepPaths, deletePaths };
|
|
14987
|
+
}
|
|
14988
|
+
function normalizeAggressiveBody(content) {
|
|
14989
|
+
return content.normalize("NFKC").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/[`*_~>#-]+/g, " ").replace(/[^\p{L}\p{N}\s]/gu, " ").replace(/\s+/g, " ").trim().toLowerCase();
|
|
14990
|
+
}
|
|
14991
|
+
function planExactDuplicateDeletions(memories) {
|
|
14992
|
+
return buildDedupePlan(memories, (memory) => memory.content.trim());
|
|
14993
|
+
}
|
|
14994
|
+
function planAggressiveDuplicateDeletions(memories) {
|
|
14995
|
+
return buildDedupePlan(memories, (memory) => normalizeAggressiveBody(memory.content));
|
|
14996
|
+
}
|
|
14772
14997
|
async function getPluginVersion() {
|
|
14773
14998
|
try {
|
|
14774
14999
|
const pkgPath = new URL("../package.json", import.meta.url);
|
|
@@ -14797,6 +15022,55 @@ async function resolveMemoryDirForNamespace(orchestrator, namespace) {
|
|
|
14797
15022
|
}
|
|
14798
15023
|
return candidate;
|
|
14799
15024
|
}
|
|
15025
|
+
async function readAllMemoryFiles(memoryDir) {
|
|
15026
|
+
const roots = [path33.join(memoryDir, "facts"), path33.join(memoryDir, "corrections")];
|
|
15027
|
+
const out = [];
|
|
15028
|
+
const walk = async (dir) => {
|
|
15029
|
+
let entries;
|
|
15030
|
+
try {
|
|
15031
|
+
entries = await readdir12(dir, { withFileTypes: true });
|
|
15032
|
+
} catch {
|
|
15033
|
+
return;
|
|
15034
|
+
}
|
|
15035
|
+
for (const entry of entries) {
|
|
15036
|
+
const entryName = typeof entry.name === "string" ? entry.name : entry.name.toString("utf-8");
|
|
15037
|
+
const fullPath = path33.join(dir, entryName);
|
|
15038
|
+
if (entry.isDirectory()) {
|
|
15039
|
+
await walk(fullPath);
|
|
15040
|
+
continue;
|
|
15041
|
+
}
|
|
15042
|
+
if (!entry.isFile() || !entryName.endsWith(".md")) continue;
|
|
15043
|
+
try {
|
|
15044
|
+
const raw = await readFile22(fullPath, "utf-8");
|
|
15045
|
+
const parsed = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
15046
|
+
if (!parsed) continue;
|
|
15047
|
+
const fmRaw = parsed[1];
|
|
15048
|
+
const body = parsed[2] ?? "";
|
|
15049
|
+
const get = (key) => {
|
|
15050
|
+
const match = fmRaw.match(new RegExp(`^${key}:\\s*(.+)$`, "m"));
|
|
15051
|
+
return match ? match[1].trim() : "";
|
|
15052
|
+
};
|
|
15053
|
+
const confidenceRaw = get("confidence");
|
|
15054
|
+
const confidence = confidenceRaw.length > 0 ? Number(confidenceRaw) : void 0;
|
|
15055
|
+
out.push({
|
|
15056
|
+
path: fullPath,
|
|
15057
|
+
content: body,
|
|
15058
|
+
frontmatter: {
|
|
15059
|
+
id: get("id") || void 0,
|
|
15060
|
+
confidence: Number.isFinite(confidence) ? confidence : void 0,
|
|
15061
|
+
updated: get("updated") || void 0,
|
|
15062
|
+
created: get("created") || void 0
|
|
15063
|
+
}
|
|
15064
|
+
});
|
|
15065
|
+
} catch {
|
|
15066
|
+
}
|
|
15067
|
+
}
|
|
15068
|
+
};
|
|
15069
|
+
for (const root of roots) {
|
|
15070
|
+
await walk(root);
|
|
15071
|
+
}
|
|
15072
|
+
return out;
|
|
15073
|
+
}
|
|
14800
15074
|
function registerCli(api, orchestrator) {
|
|
14801
15075
|
api.registerCli(
|
|
14802
15076
|
({ program }) => {
|
|
@@ -14935,6 +15209,100 @@ function registerCli(api, orchestrator) {
|
|
|
14935
15209
|
});
|
|
14936
15210
|
console.log("OK");
|
|
14937
15211
|
});
|
|
15212
|
+
cmd.command("dedupe-exact").description("Delete exact duplicate memory entries (same body text), keeping highest-confidence/newest copy").option("--dry-run", "Show what would be deleted without deleting files").option("--namespace <ns>", "Namespace to dedupe (v3.0+, default: config defaultNamespace)", "").option("--qmd-sync", "Run QMD update/embed after deletions (default: off)").action(async (...args) => {
|
|
15213
|
+
const options = args[0] ?? {};
|
|
15214
|
+
const dryRun = options.dryRun === true;
|
|
15215
|
+
const namespace = options.namespace ? String(options.namespace) : "";
|
|
15216
|
+
const qmdSync = options.qmdSync === true;
|
|
15217
|
+
const memoryDir = await resolveMemoryDirForNamespace(orchestrator, namespace);
|
|
15218
|
+
const memories = await readAllMemoryFiles(memoryDir);
|
|
15219
|
+
const plan = planExactDuplicateDeletions(memories);
|
|
15220
|
+
console.log(`Scanned ${memories.length} memory files in ${memoryDir}`);
|
|
15221
|
+
console.log(`Duplicate groups: ${plan.groups}`);
|
|
15222
|
+
console.log(`Duplicate files to delete: ${plan.deletePaths.length}`);
|
|
15223
|
+
if (plan.deletePaths.length === 0) {
|
|
15224
|
+
console.log("No exact duplicates found.");
|
|
15225
|
+
return;
|
|
15226
|
+
}
|
|
15227
|
+
if (dryRun) {
|
|
15228
|
+
console.log("Dry run enabled. No files deleted.");
|
|
15229
|
+
for (const filePath of plan.deletePaths.slice(0, 50)) {
|
|
15230
|
+
console.log(` - ${filePath}`);
|
|
15231
|
+
}
|
|
15232
|
+
if (plan.deletePaths.length > 50) {
|
|
15233
|
+
console.log(` ... and ${plan.deletePaths.length - 50} more`);
|
|
15234
|
+
}
|
|
15235
|
+
return;
|
|
15236
|
+
}
|
|
15237
|
+
let deleted = 0;
|
|
15238
|
+
for (const filePath of plan.deletePaths) {
|
|
15239
|
+
try {
|
|
15240
|
+
await unlink3(filePath);
|
|
15241
|
+
deleted += 1;
|
|
15242
|
+
} catch (err) {
|
|
15243
|
+
console.log(` failed to delete ${filePath}: ${String(err)}`);
|
|
15244
|
+
}
|
|
15245
|
+
}
|
|
15246
|
+
console.log(`Deleted ${deleted}/${plan.deletePaths.length} duplicate files.`);
|
|
15247
|
+
if (qmdSync) {
|
|
15248
|
+
await orchestrator.qmd.probe();
|
|
15249
|
+
if (orchestrator.qmd.isAvailable()) {
|
|
15250
|
+
await orchestrator.qmd.update();
|
|
15251
|
+
await orchestrator.qmd.embed();
|
|
15252
|
+
console.log("QMD sync complete.");
|
|
15253
|
+
} else {
|
|
15254
|
+
console.log(`QMD unavailable in this process; skipped sync. Status: ${orchestrator.qmd.debugStatus()}`);
|
|
15255
|
+
}
|
|
15256
|
+
}
|
|
15257
|
+
});
|
|
15258
|
+
cmd.command("dedupe-aggressive").description(
|
|
15259
|
+
"Delete aggressively-normalized duplicate memory entries (formatting/case/punctuation-insensitive)"
|
|
15260
|
+
).option("--dry-run", "Show what would be deleted without deleting files").option("--namespace <ns>", "Namespace to dedupe (v3.0+, default: config defaultNamespace)", "").option("--qmd-sync", "Run QMD update/embed after deletions (default: off)").action(async (...args) => {
|
|
15261
|
+
const options = args[0] ?? {};
|
|
15262
|
+
const dryRun = options.dryRun === true;
|
|
15263
|
+
const namespace = options.namespace ? String(options.namespace) : "";
|
|
15264
|
+
const qmdSync = options.qmdSync === true;
|
|
15265
|
+
const memoryDir = await resolveMemoryDirForNamespace(orchestrator, namespace);
|
|
15266
|
+
const memories = await readAllMemoryFiles(memoryDir);
|
|
15267
|
+
const plan = planAggressiveDuplicateDeletions(memories);
|
|
15268
|
+
console.log(`Scanned ${memories.length} memory files in ${memoryDir}`);
|
|
15269
|
+
console.log(`Duplicate groups: ${plan.groups}`);
|
|
15270
|
+
console.log(`Duplicate files to delete: ${plan.deletePaths.length}`);
|
|
15271
|
+
if (plan.deletePaths.length === 0) {
|
|
15272
|
+
console.log("No aggressive duplicates found.");
|
|
15273
|
+
return;
|
|
15274
|
+
}
|
|
15275
|
+
if (dryRun) {
|
|
15276
|
+
console.log("Dry run enabled. No files deleted.");
|
|
15277
|
+
for (const filePath of plan.deletePaths.slice(0, 50)) {
|
|
15278
|
+
console.log(` - ${filePath}`);
|
|
15279
|
+
}
|
|
15280
|
+
if (plan.deletePaths.length > 50) {
|
|
15281
|
+
console.log(` ... and ${plan.deletePaths.length - 50} more`);
|
|
15282
|
+
}
|
|
15283
|
+
return;
|
|
15284
|
+
}
|
|
15285
|
+
let deleted = 0;
|
|
15286
|
+
for (const filePath of plan.deletePaths) {
|
|
15287
|
+
try {
|
|
15288
|
+
await unlink3(filePath);
|
|
15289
|
+
deleted += 1;
|
|
15290
|
+
} catch (err) {
|
|
15291
|
+
console.log(` failed to delete ${filePath}: ${String(err)}`);
|
|
15292
|
+
}
|
|
15293
|
+
}
|
|
15294
|
+
console.log(`Deleted ${deleted}/${plan.deletePaths.length} duplicate files.`);
|
|
15295
|
+
if (qmdSync) {
|
|
15296
|
+
await orchestrator.qmd.probe();
|
|
15297
|
+
if (orchestrator.qmd.isAvailable()) {
|
|
15298
|
+
await orchestrator.qmd.update();
|
|
15299
|
+
await orchestrator.qmd.embed();
|
|
15300
|
+
console.log("QMD sync complete.");
|
|
15301
|
+
} else {
|
|
15302
|
+
console.log(`QMD unavailable in this process; skipped sync. Status: ${orchestrator.qmd.debugStatus()}`);
|
|
15303
|
+
}
|
|
15304
|
+
}
|
|
15305
|
+
});
|
|
14938
15306
|
cmd.command("search").argument("<query>", "Search query").option("-n, --max-results <number>", "Max results", "8").description("Search memories via QMD").action(async (...args) => {
|
|
14939
15307
|
const query = typeof args[0] === "string" ? args[0] : String(args[0] ?? "");
|
|
14940
15308
|
const options = args[1] ?? {};
|
|
@@ -15452,7 +15820,7 @@ var index_default = {
|
|
|
15452
15820
|
`initialized (debug=${cfg.debug}, qmdEnabled=${cfg.qmdEnabled}, transcriptEnabled=${cfg.transcriptEnabled}, hourlySummariesEnabled=${cfg.hourlySummariesEnabled}, localLlmEnabled=${cfg.localLlmEnabled})`
|
|
15453
15821
|
);
|
|
15454
15822
|
if (globalThis[ENGRAM_REGISTERED_GUARD] === true) {
|
|
15455
|
-
log.
|
|
15823
|
+
log.debug("register called more than once; skipping duplicate hook/tool registration");
|
|
15456
15824
|
return;
|
|
15457
15825
|
}
|
|
15458
15826
|
globalThis[ENGRAM_REGISTERED_GUARD] = true;
|
|
@@ -15620,6 +15988,7 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
15620
15988
|
const newJob = {
|
|
15621
15989
|
id: jobId,
|
|
15622
15990
|
agentId: "generalist",
|
|
15991
|
+
model,
|
|
15623
15992
|
name: "Engram Hourly Summary",
|
|
15624
15993
|
enabled: true,
|
|
15625
15994
|
createdAtMs: Date.now(),
|