@joshuaswarren/openclaw-engram 9.0.35 → 9.0.37
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/README.md +1 -0
- package/dist/index.js +221 -17
- package/dist/index.js.map +1 -1
- package/openclaw.plugin.json +29 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,6 +38,7 @@ AI agents forget everything between conversations. Engram fixes that.
|
|
|
38
38
|
- **Trust-zone recall** — Engram can now, when `trustZoneRecallEnabled` is enabled, inject prompt-relevant `working` and `trusted` trust-zone records into recall context as a separate `Trust Zones` section while keeping `quarantine` material out of recall by default.
|
|
39
39
|
- **Poisoning-defense corroboration** — Engram can now, when `memoryPoisoningDefenseEnabled` is enabled, score trust-zone provenance deterministically and require independent non-quarantine corroboration before risky `working -> trusted` promotions succeed.
|
|
40
40
|
- **Red-team benchmark packs** — Engram's eval harness can now validate and count typed `memory-red-team` benchmark packs so poisoning-defense regression suites stay explicit and reviewable instead of hiding inside generic benchmark metadata.
|
|
41
|
+
- **Cue-anchor index foundation** — Engram can now, when `harmonicRetrievalEnabled` and `abstractionAnchorsEnabled` are enabled, persist typed cue anchors for entities, files, tools, outcomes, constraints, and dates, inspect them with `openclaw engram cue-anchor-status`, and keep harmonic retrieval grounded in explicit abstraction-to-cue links before blending logic lands.
|
|
41
42
|
- **Zero-config start** — Install, add an API key, restart. Engram works out of the box with sensible defaults and progressively unlocks advanced features as you enable them.
|
|
42
43
|
|
|
43
44
|
## Quick Start
|
package/dist/index.js
CHANGED
|
@@ -302,6 +302,9 @@ function parseConfig(raw) {
|
|
|
302
302
|
trustZoneRecallEnabled: cfg.trustZoneRecallEnabled === true,
|
|
303
303
|
memoryPoisoningDefenseEnabled: cfg.memoryPoisoningDefenseEnabled === true,
|
|
304
304
|
memoryRedTeamBenchEnabled: cfg.memoryRedTeamBenchEnabled === true,
|
|
305
|
+
harmonicRetrievalEnabled: cfg.harmonicRetrievalEnabled === true,
|
|
306
|
+
abstractionAnchorsEnabled: cfg.abstractionAnchorsEnabled === true,
|
|
307
|
+
abstractionNodeStoreDir: typeof cfg.abstractionNodeStoreDir === "string" && cfg.abstractionNodeStoreDir.trim().length > 0 ? cfg.abstractionNodeStoreDir.trim() : path.join(memoryDir, "state", "abstraction-nodes"),
|
|
305
308
|
// Local LLM Provider (v2.1)
|
|
306
309
|
localLlmEnabled: cfg.localLlmEnabled === true || cfg.localLlmEnabled === "true",
|
|
307
310
|
// default: false
|
|
@@ -24220,7 +24223,7 @@ promotionCandidates: ${res.promotionCandidateCount}`
|
|
|
24220
24223
|
}
|
|
24221
24224
|
|
|
24222
24225
|
// src/cli.ts
|
|
24223
|
-
import
|
|
24226
|
+
import path56 from "path";
|
|
24224
24227
|
import { access as access3, readFile as readFile37, readdir as readdir24, unlink as unlink7 } from "fs/promises";
|
|
24225
24228
|
import { createHash as createHash10 } from "crypto";
|
|
24226
24229
|
|
|
@@ -25101,8 +25104,8 @@ function gatherCandidates(input, warnings) {
|
|
|
25101
25104
|
const record = rec;
|
|
25102
25105
|
const content = typeof record.content === "string" ? record.content : null;
|
|
25103
25106
|
if (!content) continue;
|
|
25104
|
-
const
|
|
25105
|
-
if (!
|
|
25107
|
+
const path58 = typeof record.path === "string" ? record.path : "";
|
|
25108
|
+
if (!path58.startsWith("transcripts/") && !path58.includes("/transcripts/")) continue;
|
|
25106
25109
|
rows.push(...parseJsonl(content, warnings));
|
|
25107
25110
|
}
|
|
25108
25111
|
return rows;
|
|
@@ -26736,6 +26739,171 @@ async function runCompatChecks(options) {
|
|
|
26736
26739
|
};
|
|
26737
26740
|
}
|
|
26738
26741
|
|
|
26742
|
+
// src/abstraction-nodes.ts
|
|
26743
|
+
import path54 from "path";
|
|
26744
|
+
import { mkdir as mkdir36, writeFile as writeFile33 } from "fs/promises";
|
|
26745
|
+
function validateKind2(raw) {
|
|
26746
|
+
const value = assertString2(raw, "kind");
|
|
26747
|
+
if (!["episode", "topic", "project", "workflow", "constraint"].includes(value)) {
|
|
26748
|
+
throw new Error("kind must be one of episode|topic|project|workflow|constraint");
|
|
26749
|
+
}
|
|
26750
|
+
return value;
|
|
26751
|
+
}
|
|
26752
|
+
function validateLevel(raw) {
|
|
26753
|
+
const value = assertString2(raw, "abstractionLevel");
|
|
26754
|
+
if (!["micro", "meso", "macro"].includes(value)) {
|
|
26755
|
+
throw new Error("abstractionLevel must be one of micro|meso|macro");
|
|
26756
|
+
}
|
|
26757
|
+
return value;
|
|
26758
|
+
}
|
|
26759
|
+
function resolveAbstractionNodeStoreDir(memoryDir, overrideDir) {
|
|
26760
|
+
if (typeof overrideDir === "string" && overrideDir.trim().length > 0) {
|
|
26761
|
+
return overrideDir.trim();
|
|
26762
|
+
}
|
|
26763
|
+
return path54.join(memoryDir, "state", "abstraction-nodes");
|
|
26764
|
+
}
|
|
26765
|
+
function validateAbstractionNode(raw) {
|
|
26766
|
+
if (!isRecord2(raw)) throw new Error("abstraction node must be an object");
|
|
26767
|
+
if (raw.schemaVersion !== 1) throw new Error("schemaVersion must be 1");
|
|
26768
|
+
return {
|
|
26769
|
+
schemaVersion: 1,
|
|
26770
|
+
nodeId: assertSafePathSegment(assertString2(raw.nodeId, "nodeId"), "nodeId"),
|
|
26771
|
+
recordedAt: assertIsoRecordedAt(assertString2(raw.recordedAt, "recordedAt")),
|
|
26772
|
+
sessionKey: assertString2(raw.sessionKey, "sessionKey"),
|
|
26773
|
+
kind: validateKind2(raw.kind),
|
|
26774
|
+
abstractionLevel: validateLevel(raw.abstractionLevel),
|
|
26775
|
+
title: assertString2(raw.title, "title"),
|
|
26776
|
+
summary: assertString2(raw.summary, "summary"),
|
|
26777
|
+
sourceMemoryIds: optionalStringArray2(raw.sourceMemoryIds, "sourceMemoryIds"),
|
|
26778
|
+
entityRefs: optionalStringArray2(raw.entityRefs, "entityRefs"),
|
|
26779
|
+
tags: optionalStringArray2(raw.tags, "tags"),
|
|
26780
|
+
metadata: validateStringRecord(raw.metadata, "metadata")
|
|
26781
|
+
};
|
|
26782
|
+
}
|
|
26783
|
+
async function getAbstractionNodeStoreStatus(options) {
|
|
26784
|
+
const rootDir = resolveAbstractionNodeStoreDir(options.memoryDir, options.abstractionNodeStoreDir);
|
|
26785
|
+
const nodesDir = path54.join(rootDir, "nodes");
|
|
26786
|
+
const files = await listJsonFiles(nodesDir);
|
|
26787
|
+
const nodes = [];
|
|
26788
|
+
const invalidNodes = [];
|
|
26789
|
+
for (const filePath of files) {
|
|
26790
|
+
try {
|
|
26791
|
+
nodes.push(validateAbstractionNode(await readJsonFile(filePath)));
|
|
26792
|
+
} catch (error) {
|
|
26793
|
+
invalidNodes.push({
|
|
26794
|
+
path: filePath,
|
|
26795
|
+
error: error instanceof Error ? error.message : String(error)
|
|
26796
|
+
});
|
|
26797
|
+
}
|
|
26798
|
+
}
|
|
26799
|
+
nodes.sort((a, b) => b.recordedAt.localeCompare(a.recordedAt));
|
|
26800
|
+
const byKind = {};
|
|
26801
|
+
const byLevel = {};
|
|
26802
|
+
for (const node of nodes) {
|
|
26803
|
+
byKind[node.kind] = (byKind[node.kind] ?? 0) + 1;
|
|
26804
|
+
byLevel[node.abstractionLevel] = (byLevel[node.abstractionLevel] ?? 0) + 1;
|
|
26805
|
+
}
|
|
26806
|
+
return {
|
|
26807
|
+
enabled: options.enabled,
|
|
26808
|
+
anchorsEnabled: options.anchorsEnabled,
|
|
26809
|
+
rootDir,
|
|
26810
|
+
nodesDir,
|
|
26811
|
+
nodes: {
|
|
26812
|
+
total: files.length,
|
|
26813
|
+
valid: nodes.length,
|
|
26814
|
+
invalid: invalidNodes.length,
|
|
26815
|
+
byKind,
|
|
26816
|
+
byLevel,
|
|
26817
|
+
latestNodeId: nodes[0]?.nodeId,
|
|
26818
|
+
latestRecordedAt: nodes[0]?.recordedAt,
|
|
26819
|
+
latestSessionKey: nodes[0]?.sessionKey
|
|
26820
|
+
},
|
|
26821
|
+
latestNode: nodes[0],
|
|
26822
|
+
invalidNodes
|
|
26823
|
+
};
|
|
26824
|
+
}
|
|
26825
|
+
|
|
26826
|
+
// src/cue-anchors.ts
|
|
26827
|
+
import path55 from "path";
|
|
26828
|
+
import { mkdir as mkdir37, writeFile as writeFile34 } from "fs/promises";
|
|
26829
|
+
function validateAnchorType(raw) {
|
|
26830
|
+
const value = assertString2(raw, "anchorType");
|
|
26831
|
+
if (!["entity", "file", "tool", "outcome", "constraint", "date"].includes(value)) {
|
|
26832
|
+
throw new Error("anchorType must be one of entity|file|tool|outcome|constraint|date");
|
|
26833
|
+
}
|
|
26834
|
+
return value;
|
|
26835
|
+
}
|
|
26836
|
+
function validateNodeRefs(raw) {
|
|
26837
|
+
const nodeRefs = optionalStringArray2(raw, "nodeRefs");
|
|
26838
|
+
if (!nodeRefs || nodeRefs.length === 0) {
|
|
26839
|
+
throw new Error("nodeRefs must contain at least one node reference");
|
|
26840
|
+
}
|
|
26841
|
+
return nodeRefs.map((nodeRef, index) => assertSafePathSegment(nodeRef, `nodeRefs[${index}]`));
|
|
26842
|
+
}
|
|
26843
|
+
function resolveCueAnchorStoreDir(abstractionNodeStoreDir, overrideDir) {
|
|
26844
|
+
if (typeof overrideDir === "string" && overrideDir.trim().length > 0) {
|
|
26845
|
+
return overrideDir.trim();
|
|
26846
|
+
}
|
|
26847
|
+
return path55.join(abstractionNodeStoreDir, "anchors");
|
|
26848
|
+
}
|
|
26849
|
+
function validateCueAnchor(raw) {
|
|
26850
|
+
if (!isRecord2(raw)) throw new Error("cue anchor must be an object");
|
|
26851
|
+
if (raw.schemaVersion !== 1) throw new Error("schemaVersion must be 1");
|
|
26852
|
+
return {
|
|
26853
|
+
schemaVersion: 1,
|
|
26854
|
+
anchorId: assertSafePathSegment(assertString2(raw.anchorId, "anchorId"), "anchorId"),
|
|
26855
|
+
anchorType: validateAnchorType(raw.anchorType),
|
|
26856
|
+
anchorValue: assertString2(raw.anchorValue, "anchorValue"),
|
|
26857
|
+
normalizedCue: assertString2(raw.normalizedCue, "normalizedCue"),
|
|
26858
|
+
recordedAt: assertIsoRecordedAt(assertString2(raw.recordedAt, "recordedAt")),
|
|
26859
|
+
sessionKey: assertString2(raw.sessionKey, "sessionKey"),
|
|
26860
|
+
nodeRefs: validateNodeRefs(raw.nodeRefs),
|
|
26861
|
+
tags: optionalStringArray2(raw.tags, "tags"),
|
|
26862
|
+
metadata: validateStringRecord(raw.metadata, "metadata")
|
|
26863
|
+
};
|
|
26864
|
+
}
|
|
26865
|
+
async function getCueAnchorStoreStatus(options) {
|
|
26866
|
+
const abstractionNodeStoreDir = options.abstractionNodeStoreDir?.trim().length ? options.abstractionNodeStoreDir.trim() : path55.join(options.memoryDir, "state", "abstraction-nodes");
|
|
26867
|
+
const rootDir = resolveCueAnchorStoreDir(abstractionNodeStoreDir, options.cueAnchorStoreDir);
|
|
26868
|
+
const files = await listJsonFiles(rootDir);
|
|
26869
|
+
const anchors = [];
|
|
26870
|
+
const invalidAnchors = [];
|
|
26871
|
+
for (const filePath of files) {
|
|
26872
|
+
try {
|
|
26873
|
+
anchors.push(validateCueAnchor(await readJsonFile(filePath)));
|
|
26874
|
+
} catch (error) {
|
|
26875
|
+
invalidAnchors.push({
|
|
26876
|
+
path: filePath,
|
|
26877
|
+
error: error instanceof Error ? error.message : String(error)
|
|
26878
|
+
});
|
|
26879
|
+
}
|
|
26880
|
+
}
|
|
26881
|
+
anchors.sort((a, b) => b.recordedAt.localeCompare(a.recordedAt));
|
|
26882
|
+
const byType = {};
|
|
26883
|
+
let totalNodeRefs = 0;
|
|
26884
|
+
for (const anchor of anchors) {
|
|
26885
|
+
byType[anchor.anchorType] = (byType[anchor.anchorType] ?? 0) + 1;
|
|
26886
|
+
totalNodeRefs += anchor.nodeRefs.length;
|
|
26887
|
+
}
|
|
26888
|
+
return {
|
|
26889
|
+
enabled: options.enabled,
|
|
26890
|
+
anchorsEnabled: options.anchorsEnabled,
|
|
26891
|
+
rootDir,
|
|
26892
|
+
anchors: {
|
|
26893
|
+
total: files.length,
|
|
26894
|
+
valid: anchors.length,
|
|
26895
|
+
invalid: invalidAnchors.length,
|
|
26896
|
+
byType,
|
|
26897
|
+
totalNodeRefs,
|
|
26898
|
+
latestAnchorId: anchors[0]?.anchorId,
|
|
26899
|
+
latestRecordedAt: anchors[0]?.recordedAt,
|
|
26900
|
+
latestSessionKey: anchors[0]?.sessionKey
|
|
26901
|
+
},
|
|
26902
|
+
latestAnchor: anchors[0],
|
|
26903
|
+
invalidAnchors
|
|
26904
|
+
};
|
|
26905
|
+
}
|
|
26906
|
+
|
|
26739
26907
|
// src/cli.ts
|
|
26740
26908
|
function rankCandidateForKeep(a, b) {
|
|
26741
26909
|
const aConfidence = typeof a.frontmatter.confidence === "number" ? a.frontmatter.confidence : 0;
|
|
@@ -26945,6 +27113,22 @@ async function runTrustZoneStatusCliCommand(options) {
|
|
|
26945
27113
|
poisoningDefenseEnabled: options.memoryPoisoningDefenseEnabled
|
|
26946
27114
|
});
|
|
26947
27115
|
}
|
|
27116
|
+
async function runAbstractionNodeStatusCliCommand(options) {
|
|
27117
|
+
return getAbstractionNodeStoreStatus({
|
|
27118
|
+
memoryDir: options.memoryDir,
|
|
27119
|
+
abstractionNodeStoreDir: options.abstractionNodeStoreDir,
|
|
27120
|
+
enabled: options.harmonicRetrievalEnabled,
|
|
27121
|
+
anchorsEnabled: options.abstractionAnchorsEnabled
|
|
27122
|
+
});
|
|
27123
|
+
}
|
|
27124
|
+
async function runCueAnchorStatusCliCommand(options) {
|
|
27125
|
+
return getCueAnchorStoreStatus({
|
|
27126
|
+
memoryDir: options.memoryDir,
|
|
27127
|
+
abstractionNodeStoreDir: options.abstractionNodeStoreDir,
|
|
27128
|
+
enabled: options.harmonicRetrievalEnabled,
|
|
27129
|
+
anchorsEnabled: options.abstractionAnchorsEnabled
|
|
27130
|
+
});
|
|
27131
|
+
}
|
|
26948
27132
|
async function runTrustZonePromoteCliCommand(options) {
|
|
26949
27133
|
const result = await promoteTrustZoneRecord({
|
|
26950
27134
|
memoryDir: options.memoryDir,
|
|
@@ -27191,7 +27375,7 @@ function policyVersionForValues(values, config) {
|
|
|
27191
27375
|
return createHash10("sha256").update(JSON.stringify(normalized)).digest("hex").slice(0, 12);
|
|
27192
27376
|
}
|
|
27193
27377
|
async function readRuntimePolicySnapshot2(config, fileName) {
|
|
27194
|
-
const filePath =
|
|
27378
|
+
const filePath = path56.join(config.memoryDir, "state", fileName);
|
|
27195
27379
|
const snapshot = await readRuntimePolicySnapshot(filePath, {
|
|
27196
27380
|
maxStaleDecayThreshold: config.lifecycleArchiveDecayThreshold
|
|
27197
27381
|
});
|
|
@@ -27775,14 +27959,14 @@ async function resolveMemoryDirForNamespace(orchestrator, namespace) {
|
|
|
27775
27959
|
const ns = (namespace ?? "").trim();
|
|
27776
27960
|
if (!ns) return orchestrator.config.memoryDir;
|
|
27777
27961
|
if (!orchestrator.config.namespacesEnabled) return orchestrator.config.memoryDir;
|
|
27778
|
-
const candidate =
|
|
27962
|
+
const candidate = path56.join(orchestrator.config.memoryDir, "namespaces", ns);
|
|
27779
27963
|
if (ns === orchestrator.config.defaultNamespace) {
|
|
27780
27964
|
return await exists2(candidate) ? candidate : orchestrator.config.memoryDir;
|
|
27781
27965
|
}
|
|
27782
27966
|
return candidate;
|
|
27783
27967
|
}
|
|
27784
27968
|
async function readAllMemoryFiles(memoryDir) {
|
|
27785
|
-
const roots = [
|
|
27969
|
+
const roots = [path56.join(memoryDir, "facts"), path56.join(memoryDir, "corrections")];
|
|
27786
27970
|
const out = [];
|
|
27787
27971
|
const walk = async (dir) => {
|
|
27788
27972
|
let entries;
|
|
@@ -27793,7 +27977,7 @@ async function readAllMemoryFiles(memoryDir) {
|
|
|
27793
27977
|
}
|
|
27794
27978
|
for (const entry of entries) {
|
|
27795
27979
|
const entryName = typeof entry.name === "string" ? entry.name : entry.name.toString("utf-8");
|
|
27796
|
-
const fullPath =
|
|
27980
|
+
const fullPath = path56.join(dir, entryName);
|
|
27797
27981
|
if (entry.isDirectory()) {
|
|
27798
27982
|
await walk(fullPath);
|
|
27799
27983
|
continue;
|
|
@@ -28136,6 +28320,26 @@ function registerCli(api, orchestrator) {
|
|
|
28136
28320
|
console.log(JSON.stringify(status, null, 2));
|
|
28137
28321
|
console.log("OK");
|
|
28138
28322
|
});
|
|
28323
|
+
cmd.command("abstraction-node-status").description("Show abstraction-node store status, abstraction counts, and latest stored node").action(async () => {
|
|
28324
|
+
const status = await runAbstractionNodeStatusCliCommand({
|
|
28325
|
+
memoryDir: orchestrator.config.memoryDir,
|
|
28326
|
+
abstractionNodeStoreDir: orchestrator.config.abstractionNodeStoreDir,
|
|
28327
|
+
harmonicRetrievalEnabled: orchestrator.config.harmonicRetrievalEnabled,
|
|
28328
|
+
abstractionAnchorsEnabled: orchestrator.config.abstractionAnchorsEnabled
|
|
28329
|
+
});
|
|
28330
|
+
console.log(JSON.stringify(status, null, 2));
|
|
28331
|
+
console.log("OK");
|
|
28332
|
+
});
|
|
28333
|
+
cmd.command("cue-anchor-status").description("Show cue-anchor index status, anchor counts, and the latest stored cue anchor").action(async () => {
|
|
28334
|
+
const status = await runCueAnchorStatusCliCommand({
|
|
28335
|
+
memoryDir: orchestrator.config.memoryDir,
|
|
28336
|
+
abstractionNodeStoreDir: orchestrator.config.abstractionNodeStoreDir,
|
|
28337
|
+
harmonicRetrievalEnabled: orchestrator.config.harmonicRetrievalEnabled,
|
|
28338
|
+
abstractionAnchorsEnabled: orchestrator.config.abstractionAnchorsEnabled
|
|
28339
|
+
});
|
|
28340
|
+
console.log(JSON.stringify(status, null, 2));
|
|
28341
|
+
console.log("OK");
|
|
28342
|
+
});
|
|
28139
28343
|
cmd.command("trust-zone-promote").description("Dry-run or apply a trust-zone promotion with provenance enforcement").requiredOption("--record-id <recordId>", "Source trust-zone record id").requiredOption("--target-zone <targetZone>", "Promotion target zone (working|trusted)").requiredOption("--reason <reason>", "Human-readable promotion reason").option("--recorded-at <isoTimestamp>", "Promotion timestamp (defaults to now)").option("--summary <summary>", "Optional replacement summary for the promoted record").option("--dry-run", "Show the promotion plan without writing the promoted record").action(async (...args) => {
|
|
28140
28344
|
const options = args[0] ?? {};
|
|
28141
28345
|
const result = await runTrustZonePromoteCliCommand({
|
|
@@ -28803,7 +29007,7 @@ function registerCli(api, orchestrator) {
|
|
|
28803
29007
|
}
|
|
28804
29008
|
});
|
|
28805
29009
|
cmd.command("identity").description("Show agent identity reflections").action(async () => {
|
|
28806
|
-
const workspaceDir =
|
|
29010
|
+
const workspaceDir = path56.join(process.env.HOME ?? "~", ".openclaw", "workspace");
|
|
28807
29011
|
const identity = await orchestrator.storage.readIdentity(workspaceDir);
|
|
28808
29012
|
if (!identity) {
|
|
28809
29013
|
console.log("No identity file found.");
|
|
@@ -29026,8 +29230,8 @@ function registerCli(api, orchestrator) {
|
|
|
29026
29230
|
const options = args[0] ?? {};
|
|
29027
29231
|
const threadId = options.thread;
|
|
29028
29232
|
const top = parseInt(options.top ?? "10", 10);
|
|
29029
|
-
const memoryDir =
|
|
29030
|
-
const threading = new ThreadingManager(
|
|
29233
|
+
const memoryDir = path56.join(process.env.HOME ?? "~", ".openclaw", "workspace", "memory", "local");
|
|
29234
|
+
const threading = new ThreadingManager(path56.join(memoryDir, "threads"));
|
|
29031
29235
|
if (threadId) {
|
|
29032
29236
|
const thread = await threading.loadThread(threadId);
|
|
29033
29237
|
if (!thread) {
|
|
@@ -29503,9 +29707,9 @@ async function recordObjectiveStateSnapshotsFromAgentMessages(options) {
|
|
|
29503
29707
|
}
|
|
29504
29708
|
|
|
29505
29709
|
// src/index.ts
|
|
29506
|
-
import { readFile as readFile38, writeFile as
|
|
29710
|
+
import { readFile as readFile38, writeFile as writeFile35 } from "fs/promises";
|
|
29507
29711
|
import { readFileSync as readFileSync4 } from "fs";
|
|
29508
|
-
import
|
|
29712
|
+
import path57 from "path";
|
|
29509
29713
|
import os6 from "os";
|
|
29510
29714
|
var ENGRAM_REGISTERED_GUARD = "__openclawEngramRegistered";
|
|
29511
29715
|
var ENGRAM_HOOK_APIS = "__openclawEngramHookApis";
|
|
@@ -29513,7 +29717,7 @@ function loadPluginConfigFromFile() {
|
|
|
29513
29717
|
try {
|
|
29514
29718
|
const explicitConfigPath = process.env.OPENCLAW_ENGRAM_CONFIG_PATH || process.env.OPENCLAW_CONFIG_PATH;
|
|
29515
29719
|
const homeDir = process.env.HOME ?? os6.homedir();
|
|
29516
|
-
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath :
|
|
29720
|
+
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath : path57.join(homeDir, ".openclaw", "openclaw.json");
|
|
29517
29721
|
const content = readFileSync4(configPath, "utf-8");
|
|
29518
29722
|
const config = JSON.parse(content);
|
|
29519
29723
|
const pluginEntry = config?.plugins?.entries?.["openclaw-engram"];
|
|
@@ -29763,11 +29967,11 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
29763
29967
|
`session reset via API for ${sessionKey}, new sessionId=${result.sessionId}`
|
|
29764
29968
|
);
|
|
29765
29969
|
const safeSessionKey = sanitizeSessionKeyForFilename(sessionKey);
|
|
29766
|
-
const signalPath =
|
|
29970
|
+
const signalPath = path57.join(
|
|
29767
29971
|
workspaceDir,
|
|
29768
29972
|
`.compaction-reset-signal-${safeSessionKey}`
|
|
29769
29973
|
);
|
|
29770
|
-
await
|
|
29974
|
+
await writeFile35(
|
|
29771
29975
|
signalPath,
|
|
29772
29976
|
JSON.stringify({
|
|
29773
29977
|
sessionKey,
|
|
@@ -29794,7 +29998,7 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
29794
29998
|
);
|
|
29795
29999
|
async function ensureHourlySummaryCron(api2) {
|
|
29796
30000
|
const jobId = "engram-hourly-summary";
|
|
29797
|
-
const cronFilePath =
|
|
30001
|
+
const cronFilePath = path57.join(os6.homedir(), ".openclaw", "cron", "jobs.json");
|
|
29798
30002
|
try {
|
|
29799
30003
|
let jobsData = { version: 1, jobs: [] };
|
|
29800
30004
|
try {
|
|
@@ -29835,7 +30039,7 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
29835
30039
|
state: {}
|
|
29836
30040
|
};
|
|
29837
30041
|
jobsData.jobs.push(newJob);
|
|
29838
|
-
await
|
|
30042
|
+
await writeFile35(cronFilePath, JSON.stringify(jobsData, null, 2), "utf-8");
|
|
29839
30043
|
log.info("auto-registered hourly summary cron job");
|
|
29840
30044
|
} catch (err) {
|
|
29841
30045
|
log.error("failed to auto-register hourly summary cron job:", err);
|