@joshuaswarren/openclaw-engram 8.3.77 → 8.3.79
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 +176 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -451,7 +451,7 @@ function parseConfig(raw) {
|
|
|
451
451
|
|
|
452
452
|
// src/orchestrator.ts
|
|
453
453
|
import path26 from "path";
|
|
454
|
-
import { createHash as
|
|
454
|
+
import { createHash as createHash6 } from "crypto";
|
|
455
455
|
import { mkdir as mkdir19, readdir as readdir11, readFile as readFile18, writeFile as writeFile17 } from "fs/promises";
|
|
456
456
|
|
|
457
457
|
// src/signal.ts
|
|
@@ -5313,6 +5313,9 @@ var StorageManager = class _StorageManager {
|
|
|
5313
5313
|
get compressionGuidelineStatePath() {
|
|
5314
5314
|
return path4.join(this.stateDir, "compression-guideline-state.json");
|
|
5315
5315
|
}
|
|
5316
|
+
get behaviorSignalsPath() {
|
|
5317
|
+
return path4.join(this.stateDir, "behavior-signals.jsonl");
|
|
5318
|
+
}
|
|
5316
5319
|
/**
|
|
5317
5320
|
* Load user-defined entity aliases from config/aliases.json in the memory store.
|
|
5318
5321
|
* File format: { "variant": "canonical", "variant2": "canonical", ... }
|
|
@@ -5998,6 +6001,67 @@ ${memory.content}
|
|
|
5998
6001
|
await appendFile(this.memoryActionsPath, payload, "utf-8");
|
|
5999
6002
|
return events.length;
|
|
6000
6003
|
}
|
|
6004
|
+
async appendBehaviorSignals(events) {
|
|
6005
|
+
if (events.length === 0) return 0;
|
|
6006
|
+
await this.ensureDirectories();
|
|
6007
|
+
let existingKeys = /* @__PURE__ */ new Set();
|
|
6008
|
+
try {
|
|
6009
|
+
const raw = await readFile2(this.behaviorSignalsPath, "utf-8");
|
|
6010
|
+
const lines = raw.split("\n");
|
|
6011
|
+
for (const line of lines) {
|
|
6012
|
+
const row = line.trim();
|
|
6013
|
+
if (!row) continue;
|
|
6014
|
+
try {
|
|
6015
|
+
const parsed = JSON.parse(row);
|
|
6016
|
+
if (typeof parsed.memoryId === "string" && typeof parsed.signalHash === "string") {
|
|
6017
|
+
existingKeys.add(`${parsed.memoryId}:${parsed.signalHash}`);
|
|
6018
|
+
}
|
|
6019
|
+
} catch {
|
|
6020
|
+
}
|
|
6021
|
+
}
|
|
6022
|
+
} catch {
|
|
6023
|
+
existingKeys = /* @__PURE__ */ new Set();
|
|
6024
|
+
}
|
|
6025
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
6026
|
+
const deduped = [];
|
|
6027
|
+
for (const event of events) {
|
|
6028
|
+
const key = `${event.memoryId}:${event.signalHash}`;
|
|
6029
|
+
if (existingKeys.has(key)) continue;
|
|
6030
|
+
existingKeys.add(key);
|
|
6031
|
+
deduped.push({
|
|
6032
|
+
...event,
|
|
6033
|
+
timestamp: event.timestamp && event.timestamp.length > 0 ? event.timestamp : nowIso
|
|
6034
|
+
});
|
|
6035
|
+
}
|
|
6036
|
+
if (deduped.length === 0) return 0;
|
|
6037
|
+
const payload = deduped.map((event) => `${JSON.stringify(event)}
|
|
6038
|
+
`).join("");
|
|
6039
|
+
await appendFile(this.behaviorSignalsPath, payload, "utf-8");
|
|
6040
|
+
return deduped.length;
|
|
6041
|
+
}
|
|
6042
|
+
async readBehaviorSignals(limit = 200) {
|
|
6043
|
+
const cappedLimit = Math.max(0, Math.floor(limit));
|
|
6044
|
+
if (cappedLimit === 0) return [];
|
|
6045
|
+
try {
|
|
6046
|
+
const raw = await readFile2(this.behaviorSignalsPath, "utf-8");
|
|
6047
|
+
const out = [];
|
|
6048
|
+
const lines = raw.split("\n");
|
|
6049
|
+
for (let i = lines.length - 1; i >= 0 && out.length < cappedLimit; i -= 1) {
|
|
6050
|
+
const row = lines[i]?.trim();
|
|
6051
|
+
if (!row) continue;
|
|
6052
|
+
try {
|
|
6053
|
+
const parsed = JSON.parse(row);
|
|
6054
|
+
if (typeof parsed.timestamp === "string" && typeof parsed.namespace === "string" && typeof parsed.memoryId === "string" && typeof parsed.category === "string" && typeof parsed.signalType === "string" && typeof parsed.direction === "string" && typeof parsed.confidence === "number" && typeof parsed.signalHash === "string" && typeof parsed.source === "string") {
|
|
6055
|
+
out.push(parsed);
|
|
6056
|
+
}
|
|
6057
|
+
} catch {
|
|
6058
|
+
}
|
|
6059
|
+
}
|
|
6060
|
+
return out.reverse();
|
|
6061
|
+
} catch {
|
|
6062
|
+
return [];
|
|
6063
|
+
}
|
|
6064
|
+
}
|
|
6001
6065
|
async readMemoryActionEvents(limit = 200) {
|
|
6002
6066
|
const cappedLimit = Math.max(0, Math.floor(limit));
|
|
6003
6067
|
if (cappedLimit === 0) return [];
|
|
@@ -9754,6 +9818,10 @@ function buildInstructionHeavyQuery(prompt, tokenCap, maxChars) {
|
|
|
9754
9818
|
if (compact.length <= maxChars) return compact;
|
|
9755
9819
|
return compact.slice(0, maxChars).trim();
|
|
9756
9820
|
}
|
|
9821
|
+
function clampInstructionHeavyTokenCap(value) {
|
|
9822
|
+
if (!Number.isFinite(value)) return 8;
|
|
9823
|
+
return Math.max(8, Math.round(value));
|
|
9824
|
+
}
|
|
9757
9825
|
function buildStandardQuery(prompt, maxChars) {
|
|
9758
9826
|
const trimmed = collapseWhitespace(prompt);
|
|
9759
9827
|
if (trimmed.length <= maxChars) return trimmed;
|
|
@@ -9772,7 +9840,7 @@ function buildRecallQueryPolicy(prompt, sessionKey, cfg) {
|
|
|
9772
9840
|
}
|
|
9773
9841
|
const promptShape = classifyRecallPromptShape(prompt);
|
|
9774
9842
|
const maxChars = Math.max(120, cfg.cronRecallNormalizedQueryMaxChars);
|
|
9775
|
-
const tokenCap =
|
|
9843
|
+
const tokenCap = clampInstructionHeavyTokenCap(cfg.cronRecallInstructionHeavyTokenCap);
|
|
9776
9844
|
const retrievalQuery = promptShape === "instruction_heavy" ? buildInstructionHeavyQuery(prompt, tokenCap, maxChars) : buildStandardQuery(prompt, maxChars);
|
|
9777
9845
|
const skipConversationRecall = cfg.cronConversationRecallMode === "never" ? true : cfg.cronConversationRecallMode === "always" ? false : promptShape === "instruction_heavy";
|
|
9778
9846
|
const retrievalBudgetMode = promptShape === "instruction_heavy" ? "minimal" : "full";
|
|
@@ -13057,6 +13125,63 @@ var RoutingRulesStore = class {
|
|
|
13057
13125
|
}
|
|
13058
13126
|
};
|
|
13059
13127
|
|
|
13128
|
+
// src/behavior-signals.ts
|
|
13129
|
+
import { createHash as createHash5 } from "crypto";
|
|
13130
|
+
function normalizeSignalText(input) {
|
|
13131
|
+
return input.replace(/\s+/g, " ").trim().toLowerCase();
|
|
13132
|
+
}
|
|
13133
|
+
function buildBehaviorSignalHash(category, content) {
|
|
13134
|
+
const normalized = `${category}:${normalizeSignalText(content)}`;
|
|
13135
|
+
return createHash5("sha256").update(normalized).digest("hex").slice(0, 16);
|
|
13136
|
+
}
|
|
13137
|
+
function buildBehaviorSignalsForMemory(input) {
|
|
13138
|
+
const timestamp = input.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
13139
|
+
const signalHash = buildBehaviorSignalHash(input.category, input.content);
|
|
13140
|
+
const source = input.source ?? "extraction";
|
|
13141
|
+
if (input.category === "correction") {
|
|
13142
|
+
return [
|
|
13143
|
+
{
|
|
13144
|
+
timestamp,
|
|
13145
|
+
namespace: input.namespace,
|
|
13146
|
+
memoryId: input.memoryId,
|
|
13147
|
+
category: "correction",
|
|
13148
|
+
signalType: "correction_override",
|
|
13149
|
+
direction: "negative",
|
|
13150
|
+
confidence: input.confidence,
|
|
13151
|
+
signalHash,
|
|
13152
|
+
source
|
|
13153
|
+
}
|
|
13154
|
+
];
|
|
13155
|
+
}
|
|
13156
|
+
if (input.category === "preference") {
|
|
13157
|
+
return [
|
|
13158
|
+
{
|
|
13159
|
+
timestamp,
|
|
13160
|
+
namespace: input.namespace,
|
|
13161
|
+
memoryId: input.memoryId,
|
|
13162
|
+
category: "preference",
|
|
13163
|
+
signalType: "preference_affinity",
|
|
13164
|
+
direction: "positive",
|
|
13165
|
+
confidence: input.confidence,
|
|
13166
|
+
signalHash,
|
|
13167
|
+
source
|
|
13168
|
+
}
|
|
13169
|
+
];
|
|
13170
|
+
}
|
|
13171
|
+
return [];
|
|
13172
|
+
}
|
|
13173
|
+
function dedupeBehaviorSignalsByMemoryAndHash(signals) {
|
|
13174
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13175
|
+
const out = [];
|
|
13176
|
+
for (const signal of signals) {
|
|
13177
|
+
const key = `${signal.memoryId}:${signal.signalHash}`;
|
|
13178
|
+
if (seen.has(key)) continue;
|
|
13179
|
+
seen.add(key);
|
|
13180
|
+
out.push(signal);
|
|
13181
|
+
}
|
|
13182
|
+
return out;
|
|
13183
|
+
}
|
|
13184
|
+
|
|
13060
13185
|
// src/orchestrator.ts
|
|
13061
13186
|
function isArtifactMemoryPath(filePath) {
|
|
13062
13187
|
return /(?:^|[\\/])artifacts(?:[\\/]|$)/i.test(filePath);
|
|
@@ -14213,7 +14338,7 @@ ${r.snippet.trim()}
|
|
|
14213
14338
|
const payload = {
|
|
14214
14339
|
recordedAt: now,
|
|
14215
14340
|
mode: options.recallMode,
|
|
14216
|
-
queryHash:
|
|
14341
|
+
queryHash: createHash6("sha256").update(options.prompt).digest("hex"),
|
|
14217
14342
|
queryLength: options.prompt.length,
|
|
14218
14343
|
namespaces: options.recallNamespaces,
|
|
14219
14344
|
seedCount: totalSeedCount,
|
|
@@ -14229,7 +14354,7 @@ ${r.snippet.trim()}
|
|
|
14229
14354
|
async recallInternal(prompt, sessionKey) {
|
|
14230
14355
|
const recallStart = Date.now();
|
|
14231
14356
|
const timings = {};
|
|
14232
|
-
const promptHash =
|
|
14357
|
+
const promptHash = createHash6("sha256").update(prompt).digest("hex");
|
|
14233
14358
|
const sections = [];
|
|
14234
14359
|
const queryPolicy = buildRecallQueryPolicy(prompt, sessionKey, {
|
|
14235
14360
|
cronRecallPolicyEnabled: this.config.cronRecallPolicyEnabled,
|
|
@@ -14238,7 +14363,7 @@ ${r.snippet.trim()}
|
|
|
14238
14363
|
cronConversationRecallMode: this.config.cronConversationRecallMode
|
|
14239
14364
|
});
|
|
14240
14365
|
const retrievalQuery = queryPolicy.retrievalQuery || prompt;
|
|
14241
|
-
const retrievalQueryHash =
|
|
14366
|
+
const retrievalQueryHash = createHash6("sha256").update(retrievalQuery).digest("hex");
|
|
14242
14367
|
let impressionRecorded = false;
|
|
14243
14368
|
let recallSource = "none";
|
|
14244
14369
|
let recalledMemoryCount = 0;
|
|
@@ -14273,7 +14398,7 @@ ${r.snippet.trim()}
|
|
|
14273
14398
|
timings.total = `${Date.now() - recallStart}ms`;
|
|
14274
14399
|
this.emitTrace({
|
|
14275
14400
|
kind: "recall_summary",
|
|
14276
|
-
traceId:
|
|
14401
|
+
traceId: createHash6("sha256").update(`${sessionKey ?? "default"}:${Date.now()}:${promptHash}`).digest("hex").slice(0, 16),
|
|
14277
14402
|
operation: "recall",
|
|
14278
14403
|
sessionKey,
|
|
14279
14404
|
promptHash,
|
|
@@ -14870,7 +14995,7 @@ _Context: ${topQuestion.context}_`);
|
|
|
14870
14995
|
const context = sections.length === 0 ? "" : sections.join("\n\n---\n\n");
|
|
14871
14996
|
this.emitTrace({
|
|
14872
14997
|
kind: "recall_summary",
|
|
14873
|
-
traceId:
|
|
14998
|
+
traceId: createHash6("sha256").update(`${sessionKey ?? "default"}:${Date.now()}:${promptHash}`).digest("hex").slice(0, 16),
|
|
14874
14999
|
operation: "recall",
|
|
14875
15000
|
sessionKey,
|
|
14876
15001
|
promptHash,
|
|
@@ -15019,7 +15144,7 @@ _Context: ${topQuestion.context}_`);
|
|
|
15019
15144
|
if (!Array.isArray(turns) || turns.length === 0) return false;
|
|
15020
15145
|
const normalized = turns.filter((t) => t.role === "user" || t.role === "assistant").map((t) => `${t.role}:${(t.content ?? "").trim().slice(0, this.config.extractionMaxTurnChars)}`).join("\n");
|
|
15021
15146
|
if (!normalized) return false;
|
|
15022
|
-
const fingerprint =
|
|
15147
|
+
const fingerprint = createHash6("sha256").update(normalized).digest("hex");
|
|
15023
15148
|
const now = Date.now();
|
|
15024
15149
|
const seenAt = this.recentExtractionFingerprints.get(fingerprint);
|
|
15025
15150
|
if (seenAt && now - seenAt < this.config.extractionDedupeWindowMs) {
|
|
@@ -15383,6 +15508,17 @@ _Context: ${topQuestion.context}_`);
|
|
|
15383
15508
|
persistedIdsByStorage.set(key, { storage: targetStorage, ids: [id] });
|
|
15384
15509
|
};
|
|
15385
15510
|
let dedupedCount = 0;
|
|
15511
|
+
const behaviorSignalsByStorage = /* @__PURE__ */ new Map();
|
|
15512
|
+
const trackBehaviorSignals = (targetStorage, events) => {
|
|
15513
|
+
if (events.length === 0) return;
|
|
15514
|
+
const key = targetStorage.dir;
|
|
15515
|
+
const existing = behaviorSignalsByStorage.get(key);
|
|
15516
|
+
if (existing) {
|
|
15517
|
+
existing.events.push(...events);
|
|
15518
|
+
return;
|
|
15519
|
+
}
|
|
15520
|
+
behaviorSignalsByStorage.set(key, { storage: targetStorage, events: [...events] });
|
|
15521
|
+
};
|
|
15386
15522
|
if (!result || !Array.isArray(result.facts)) {
|
|
15387
15523
|
log.warn("persistExtraction: result or result.facts is invalid, skipping", { resultType: typeof result, factsType: typeof result?.facts });
|
|
15388
15524
|
return persistedIds;
|
|
@@ -15573,6 +15709,17 @@ _Context: ${topQuestion.context}_`);
|
|
|
15573
15709
|
} catch {
|
|
15574
15710
|
}
|
|
15575
15711
|
}
|
|
15712
|
+
trackBehaviorSignals(
|
|
15713
|
+
targetStorage,
|
|
15714
|
+
buildBehaviorSignalsForMemory({
|
|
15715
|
+
memoryId: parentId,
|
|
15716
|
+
category: writeCategory,
|
|
15717
|
+
content: fact.content,
|
|
15718
|
+
namespace: this.namespaceFromStorageDir(targetStorage.dir),
|
|
15719
|
+
confidence: fact.confidence,
|
|
15720
|
+
source: "extraction"
|
|
15721
|
+
})
|
|
15722
|
+
);
|
|
15576
15723
|
continue;
|
|
15577
15724
|
}
|
|
15578
15725
|
}
|
|
@@ -15633,6 +15780,17 @@ _Context: ${topQuestion.context}_`);
|
|
|
15633
15780
|
`routing applied for memory ${memoryId}: rule=${routedRuleId} category=${writeCategory} storage=${targetStorage.dir}`
|
|
15634
15781
|
);
|
|
15635
15782
|
}
|
|
15783
|
+
trackBehaviorSignals(
|
|
15784
|
+
targetStorage,
|
|
15785
|
+
buildBehaviorSignalsForMemory({
|
|
15786
|
+
memoryId,
|
|
15787
|
+
category: writeCategory,
|
|
15788
|
+
content: fact.content,
|
|
15789
|
+
namespace: this.namespaceFromStorageDir(targetStorage.dir),
|
|
15790
|
+
confidence: fact.confidence,
|
|
15791
|
+
source: "extraction"
|
|
15792
|
+
})
|
|
15793
|
+
);
|
|
15636
15794
|
trackPersistedId(targetStorage, memoryId);
|
|
15637
15795
|
if (threadEpisodeIdsForGraph && !threadEpisodeIdsForGraph.includes(memoryId)) {
|
|
15638
15796
|
threadEpisodeIdsForGraph.push(memoryId);
|
|
@@ -15750,6 +15908,11 @@ _Context: ${topQuestion.context}_`);
|
|
|
15750
15908
|
(err) => log.warn(`content-hash index save failed: ${err}`)
|
|
15751
15909
|
);
|
|
15752
15910
|
}
|
|
15911
|
+
for (const { storage: targetStorage, events } of behaviorSignalsByStorage.values()) {
|
|
15912
|
+
const dedupedSignals = dedupeBehaviorSignalsByMemoryAndHash(events);
|
|
15913
|
+
if (dedupedSignals.length === 0) continue;
|
|
15914
|
+
await targetStorage.appendBehaviorSignals(dedupedSignals).catch((err) => log.warn(`appendBehaviorSignals failed (non-fatal): ${err}`));
|
|
15915
|
+
}
|
|
15753
15916
|
const dedupSuffix = dedupedCount > 0 ? ` (${dedupedCount} deduped)` : "";
|
|
15754
15917
|
log.info(
|
|
15755
15918
|
`persisted: ${facts.length - dedupedCount} facts${dedupSuffix}, ${entities.length} entities, ${questions.length} questions, ${profileUpdates.length} profile updates`
|
|
@@ -17096,7 +17259,7 @@ ${lines.join("\n\n")}`;
|
|
|
17096
17259
|
|
|
17097
17260
|
// src/tools.ts
|
|
17098
17261
|
import path28 from "path";
|
|
17099
|
-
import { createHash as
|
|
17262
|
+
import { createHash as createHash7 } from "crypto";
|
|
17100
17263
|
import { Type } from "@sinclair/typebox";
|
|
17101
17264
|
|
|
17102
17265
|
// src/work/storage.ts
|
|
@@ -17867,7 +18030,7 @@ function registerTools(api, orchestrator) {
|
|
|
17867
18030
|
];
|
|
17868
18031
|
function promptHashForTelemetry(input) {
|
|
17869
18032
|
if (typeof input !== "string" || input.trim().length === 0) return void 0;
|
|
17870
|
-
return
|
|
18033
|
+
return createHash7("sha256").update(input).digest("hex").slice(0, 16);
|
|
17871
18034
|
}
|
|
17872
18035
|
function namespaceFromPath(p) {
|
|
17873
18036
|
const m = p.match(/[\\/]+namespaces[\\/]+([^\\/]+)[\\/]+/);
|
|
@@ -19460,17 +19623,17 @@ var EXPORT_FORMAT = "openclaw-engram-export";
|
|
|
19460
19623
|
var EXPORT_SCHEMA_VERSION = 1;
|
|
19461
19624
|
|
|
19462
19625
|
// src/transfer/fs-utils.ts
|
|
19463
|
-
import { createHash as
|
|
19626
|
+
import { createHash as createHash8 } from "crypto";
|
|
19464
19627
|
import { mkdir as mkdir21, readdir as readdir13, readFile as readFile20, stat as stat6, writeFile as writeFile19 } from "fs/promises";
|
|
19465
19628
|
import path29 from "path";
|
|
19466
19629
|
async function sha256File(filePath) {
|
|
19467
19630
|
const buf = await readFile20(filePath);
|
|
19468
|
-
const sha256 =
|
|
19631
|
+
const sha256 = createHash8("sha256").update(buf).digest("hex");
|
|
19469
19632
|
return { sha256, bytes: buf.byteLength };
|
|
19470
19633
|
}
|
|
19471
19634
|
function sha256String(content) {
|
|
19472
19635
|
const buf = Buffer.from(content, "utf-8");
|
|
19473
|
-
const sha256 =
|
|
19636
|
+
const sha256 = createHash8("sha256").update(buf).digest("hex");
|
|
19474
19637
|
return { sha256, bytes: buf.byteLength };
|
|
19475
19638
|
}
|
|
19476
19639
|
async function writeJsonFile(filePath, value) {
|