@remnic/plugin-openclaw 1.0.10 → 1.0.12
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/{calibration-674TDQNV.js → calibration-WCHOK6DX.js} +12 -4
- package/dist/capsule-cli-GBM3WPAM.js +33 -0
- package/dist/capsule-crypto-K3IRTKRH.js +17 -0
- package/dist/capsule-export-IXVERCQG.js +17 -0
- package/dist/capsule-import-IA6VIOPQ.js +16 -0
- package/dist/capsule-merge-IWOQ34KL.js +189 -0
- package/dist/{causal-chain-OKDZSDEB.js → causal-chain-WYN5QOPS.js} +3 -2
- package/dist/{causal-consolidation-5BEXLQV5.js → causal-consolidation-YI53C2AO.js} +16 -12
- package/dist/{causal-retrieval-3BKBXVXD.js → causal-retrieval-NZHQOZOE.js} +6 -5
- package/dist/{causal-trajectory-graph-RQIT37DN.js → causal-trajectory-graph-VBPE2WPM.js} +1 -1
- package/dist/chunk-37NKFWSO.js +233 -0
- package/dist/chunk-3G7FAF6S.js +60 -0
- package/dist/{chunk-Z7GRLVK3.js → chunk-3GUF7RQI.js} +235 -19
- package/dist/chunk-4G2XCSD2.js +186 -0
- package/dist/chunk-4LYQ4ONL.js +185 -0
- package/dist/chunk-6F6EKSVP.js +453 -0
- package/dist/chunk-6IWEAUN6.js +148 -0
- package/dist/{chunk-LN5UZQVG.js → chunk-6UFI73TJ.js} +5 -3
- package/dist/chunk-7OQEPGQF.js +529 -0
- package/dist/{chunk-JJSNPSCD.js → chunk-7UZNLMW5.js} +652 -174
- package/dist/chunk-B52XADV3.js +244 -0
- package/dist/chunk-BU5KJVWF.js +78 -0
- package/dist/chunk-CDAZGIGT.js +720 -0
- package/dist/chunk-CXM7EBAO.js +289 -0
- package/dist/{chunk-HCFFXBLV.js → chunk-EXDYWXMB.js} +6 -1861
- package/dist/chunk-FGTYFLL5.js +274 -0
- package/dist/chunk-FQRSVYY4.js +110 -0
- package/dist/chunk-HRGFO6AW.js +349 -0
- package/dist/chunk-I6B2W2IY.js +47 -0
- package/dist/chunk-JZBOXOUC.js +259 -0
- package/dist/chunk-L6I4MQKO.js +227 -0
- package/dist/chunk-LLUROTZJ.js +328 -0
- package/dist/chunk-MBIFE6SA.js +250 -0
- package/dist/chunk-NKVIN6RD.js +118 -0
- package/dist/chunk-OAE7AQ6R.js +1832 -0
- package/dist/chunk-OEI7GLV2.js +17 -0
- package/dist/chunk-RKR6PTPA.js +308 -0
- package/dist/{chunk-7TENHBV2.js → chunk-RQCTMECT.js} +10 -48
- package/dist/chunk-SSFTU6LP.js +182 -0
- package/dist/{chunk-BXTMZDRT.js → chunk-SVSQAG6M.js} +7 -5
- package/dist/{chunk-S2ISS4AH.js → chunk-TILAJIJR.js} +10 -10
- package/dist/chunk-TLVIQLB4.js +874 -0
- package/dist/{chunk-YHH3SXKD.js → chunk-WPINX4MF.js} +1 -59
- package/dist/chunk-YGGGUTG3.js +125 -0
- package/dist/cipher-VHAFCG7Z.js +27 -0
- package/dist/dreams-ledger-3I52ISYR.js +285 -0
- package/dist/{engine-65C2J63X.js → engine-BIYI3P4J.js} +7 -2
- package/dist/{fallback-llm-LVK5PDIM.js → fallback-llm-WCWNGIQ3.js} +2 -1
- package/dist/first-start-migration-I24M2JEE.js +258 -0
- package/dist/forget-NI4RBDPB.js +68 -0
- package/dist/fs-utils-PZRI2HDZ.js +29 -0
- package/dist/graph-edge-decay-5CVKWBYH.js +203 -0
- package/dist/index.js +10654 -3067
- package/dist/kdf-H5B23ZM2.js +25 -0
- package/dist/memory-governance-SJ5DGRB3.js +25 -0
- package/dist/metadata-JAGIWHEA.js +20 -0
- package/dist/migrate-from-identity-anchor-N3354WMP.js +7 -0
- package/dist/path-5LCUBAAZ.js +8 -0
- package/dist/peers-JF2I6RCR.js +43 -0
- package/dist/purge-XN2VSPZ2.js +204 -0
- package/dist/secure-store-A4NGCNXV.js +155 -0
- package/dist/state-PVISYXRH.js +7 -0
- package/dist/state-store-LP5BO6SF.js +15 -0
- package/dist/{storage-DM4ZGOCN.js → storage-PTQ2H2YJ.js} +3 -1
- package/dist/tier-stats-IZNW66NC.js +147 -0
- package/dist/trace-NJESSGH7.js +289 -0
- package/dist/tui-MGK2LYJY.js +12 -0
- package/dist/types-R4DO7AKM.js +30 -0
- package/openclaw.plugin.json +519 -4
- package/package.json +2 -2
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createVersion
|
|
3
3
|
} from "./chunk-6OJAU466.js";
|
|
4
|
+
import {
|
|
5
|
+
SecureStoreLockedError,
|
|
6
|
+
isEncryptedFile,
|
|
7
|
+
readMaybeEncryptedFile,
|
|
8
|
+
writeMaybeEncryptedFile
|
|
9
|
+
} from "./chunk-RKR6PTPA.js";
|
|
4
10
|
import {
|
|
5
11
|
log
|
|
6
12
|
} from "./chunk-UFU5GGGA.js";
|
|
7
13
|
|
|
8
14
|
// ../remnic-core/src/storage.ts
|
|
9
|
-
import { access, readdir, readFile as readFile2, stat as stat2, writeFile as writeFile2, mkdir as mkdir2, unlink,
|
|
15
|
+
import { access, readdir, readFile as readFile2, stat as stat2, writeFile as writeFile2, mkdir as mkdir2, unlink, appendFile } from "fs/promises";
|
|
10
16
|
import { appendFileSync, mkdirSync, statSync } from "fs";
|
|
11
17
|
import { createHash } from "crypto";
|
|
12
18
|
import path4 from "path";
|
|
@@ -29,6 +35,12 @@ function setCachedEntities(baseDir, entities, version, schemaKey = "") {
|
|
|
29
35
|
loadedAt: Date.now()
|
|
30
36
|
});
|
|
31
37
|
}
|
|
38
|
+
function invalidateCachedEntities(baseDir) {
|
|
39
|
+
const prefix = `${baseDir}\0`;
|
|
40
|
+
for (const key of entityCacheByDir.keys()) {
|
|
41
|
+
if (key.startsWith(prefix)) entityCacheByDir.delete(key);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
32
44
|
var episodeMapByDir = /* @__PURE__ */ new Map();
|
|
33
45
|
var ruleMemoriesByDir = /* @__PURE__ */ new Map();
|
|
34
46
|
function getCachedEpisodeMap(baseDir, currentVersion) {
|
|
@@ -40,7 +52,7 @@ function getCachedEpisodeMap(baseDir, currentVersion) {
|
|
|
40
52
|
function setCachedEpisodeMap(baseDir, memories, version) {
|
|
41
53
|
const map = /* @__PURE__ */ new Map();
|
|
42
54
|
for (const m of memories) {
|
|
43
|
-
if (m.frontmatter.status === "archived") continue;
|
|
55
|
+
if (m.frontmatter.status === "archived" || m.frontmatter.status === "forgotten") continue;
|
|
44
56
|
if (m.frontmatter.memoryKind !== "episode") continue;
|
|
45
57
|
map.set(m.frontmatter.id, m);
|
|
46
58
|
}
|
|
@@ -58,7 +70,7 @@ function setCachedRuleMemories(baseDir, memories, version) {
|
|
|
58
70
|
const all = [];
|
|
59
71
|
for (const m of memories) {
|
|
60
72
|
byId.set(m.frontmatter.id, m);
|
|
61
|
-
if (m.frontmatter.category === "rule" && m.frontmatter.status !== "archived") {
|
|
73
|
+
if (m.frontmatter.category === "rule" && m.frontmatter.status !== "archived" && m.frontmatter.status !== "forgotten") {
|
|
62
74
|
all.push(m);
|
|
63
75
|
}
|
|
64
76
|
}
|
|
@@ -180,21 +192,41 @@ function sanitizeMemoryContent(text) {
|
|
|
180
192
|
var CONSOLIDATION_OPERATORS = [
|
|
181
193
|
"split",
|
|
182
194
|
"merge",
|
|
183
|
-
"update"
|
|
195
|
+
"update",
|
|
196
|
+
"pattern-reinforcement"
|
|
184
197
|
];
|
|
185
198
|
var DERIVED_FROM_ENTRY_RE = /^(.+):(\d+)$/;
|
|
186
|
-
|
|
187
|
-
|
|
199
|
+
var DERIVED_FROM_MEMORY_ID_RE = /^[A-Za-z0-9][A-Za-z0-9_:-]*$/;
|
|
200
|
+
function looksLikeSnapshotEntry(entry) {
|
|
188
201
|
const match = entry.match(DERIVED_FROM_ENTRY_RE);
|
|
189
202
|
if (!match) return false;
|
|
190
203
|
const pathPart = match[1];
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
204
|
+
return pathPart.includes("/") || pathPart.includes(".");
|
|
205
|
+
}
|
|
206
|
+
function isValidDerivedFromEntry(entry) {
|
|
207
|
+
if (typeof entry !== "string") return false;
|
|
208
|
+
if (entry.length === 0) return false;
|
|
209
|
+
if (looksLikeSnapshotEntry(entry)) {
|
|
210
|
+
const match = entry.match(DERIVED_FROM_ENTRY_RE);
|
|
211
|
+
if (!match) return false;
|
|
212
|
+
const pathPart = match[1];
|
|
213
|
+
if (pathPart.length === 0 || pathPart.trim().length === 0) return false;
|
|
214
|
+
const versionNum = Number(match[2]);
|
|
215
|
+
return Number.isInteger(versionNum) && versionNum >= 0;
|
|
216
|
+
}
|
|
217
|
+
return DERIVED_FROM_MEMORY_ID_RE.test(entry);
|
|
194
218
|
}
|
|
195
219
|
function isConsolidationOperator(value) {
|
|
196
220
|
return typeof value === "string" && CONSOLIDATION_OPERATORS.includes(value);
|
|
197
221
|
}
|
|
222
|
+
var SEMANTIC_CONSOLIDATION_LLM_OPERATORS = [
|
|
223
|
+
"split",
|
|
224
|
+
"merge",
|
|
225
|
+
"update"
|
|
226
|
+
];
|
|
227
|
+
function isSemanticConsolidationLlmOperator(value) {
|
|
228
|
+
return typeof value === "string" && SEMANTIC_CONSOLIDATION_LLM_OPERATORS.includes(value);
|
|
229
|
+
}
|
|
198
230
|
|
|
199
231
|
// ../remnic-core/src/entity-schema.ts
|
|
200
232
|
var DEFAULT_ENTITY_SCHEMAS = {
|
|
@@ -541,6 +573,15 @@ function stripCitationForTemplate(text, template) {
|
|
|
541
573
|
}
|
|
542
574
|
|
|
543
575
|
// ../remnic-core/src/types.ts
|
|
576
|
+
var RECALL_DISCLOSURE_LEVELS = [
|
|
577
|
+
"chunk",
|
|
578
|
+
"section",
|
|
579
|
+
"raw"
|
|
580
|
+
];
|
|
581
|
+
var DEFAULT_RECALL_DISCLOSURE = "chunk";
|
|
582
|
+
function isRecallDisclosure(value) {
|
|
583
|
+
return typeof value === "string" && RECALL_DISCLOSURE_LEVELS.includes(value);
|
|
584
|
+
}
|
|
544
585
|
function confidenceTier(score) {
|
|
545
586
|
if (score >= 0.95) return "explicit";
|
|
546
587
|
if (score >= 0.7) return "implied";
|
|
@@ -560,25 +601,44 @@ function loadBetterSqlite3() {
|
|
|
560
601
|
if (cachedCtor) return cachedCtor;
|
|
561
602
|
const require2 = createRequire(import.meta.url);
|
|
562
603
|
try {
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
if (typeof ctor !== "function") {
|
|
566
|
-
throw new Error("module did not export a constructor");
|
|
567
|
-
}
|
|
568
|
-
cachedCtor = ctor;
|
|
569
|
-
return ctor;
|
|
604
|
+
cachedCtor = requireBetterSqlite3Ctor(require2);
|
|
605
|
+
return cachedCtor;
|
|
570
606
|
} catch (error) {
|
|
571
|
-
|
|
572
|
-
throw new Error(
|
|
573
|
-
"better-sqlite3 is unavailable. Rebuild it in the plugin install with `npm rebuild better-sqlite3 --build-from-source` before using SQLite-backed Engram features" + detail,
|
|
574
|
-
{ cause: error instanceof Error ? error : void 0 }
|
|
575
|
-
);
|
|
607
|
+
throw unavailableError(error);
|
|
576
608
|
}
|
|
577
609
|
}
|
|
578
610
|
function openBetterSqlite3(file, options) {
|
|
579
611
|
const Database = loadBetterSqlite3();
|
|
580
612
|
return new Database(file, options);
|
|
581
613
|
}
|
|
614
|
+
function requireBetterSqlite3Ctor(require2) {
|
|
615
|
+
const loaded = require2("better-sqlite3");
|
|
616
|
+
const ctor = typeof loaded === "function" ? loaded : loaded.default;
|
|
617
|
+
if (typeof ctor !== "function") {
|
|
618
|
+
throw new Error("module did not export a constructor");
|
|
619
|
+
}
|
|
620
|
+
return ctor;
|
|
621
|
+
}
|
|
622
|
+
function isLikelyBetterSqlite3NativeBindingError(error) {
|
|
623
|
+
const detail = errorDetail(error);
|
|
624
|
+
return detail.includes("Could not locate the bindings file") || detail.includes("better_sqlite3.node") || detail.includes("node-v") && detail.includes("better-sqlite3") || detail.includes("NODE_MODULE_VERSION") && detail.includes("better-sqlite3") || detail.includes("was compiled against a different Node.js version");
|
|
625
|
+
}
|
|
626
|
+
function unavailableError(error) {
|
|
627
|
+
const detail = errorDetail(error);
|
|
628
|
+
const nativeBindingHint = isLikelyBetterSqlite3NativeBindingError(error) ? " This usually means the better-sqlite3 native binding was not compiled for this Node.js/platform combination. Run `node scripts/ensure-better-sqlite3.mjs` from the Remnic install directory, or run `npx node-gyp rebuild --directory=node_modules/better-sqlite3` if the verification script is unavailable." : "";
|
|
629
|
+
return new Error(
|
|
630
|
+
"better-sqlite3 is unavailable. Remnic attempted to load the native SQLite binding and could not." + nativeBindingHint + (detail ? ` Original error: ${detail}` : ""),
|
|
631
|
+
{ cause: error instanceof Error ? error : void 0 }
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
function errorDetail(error) {
|
|
635
|
+
if (error instanceof Error) {
|
|
636
|
+
const stack = error.stack && error.stack !== error.message ? `
|
|
637
|
+
${error.stack}` : "";
|
|
638
|
+
return `${error.message}${stack}`;
|
|
639
|
+
}
|
|
640
|
+
return String(error ?? "");
|
|
641
|
+
}
|
|
582
642
|
|
|
583
643
|
// ../remnic-core/src/memory-projection-store.ts
|
|
584
644
|
var MEMORY_PROJECTION_SCHEMA_VERSION = 2;
|
|
@@ -1269,6 +1329,9 @@ function inferMemoryStatus(frontmatter, pathRel, fallbackStatus = "active") {
|
|
|
1269
1329
|
if (frontmatter.status) return frontmatter.status;
|
|
1270
1330
|
return fallbackStatus;
|
|
1271
1331
|
}
|
|
1332
|
+
function isActiveMemoryStatus(status) {
|
|
1333
|
+
return status === void 0 || status === "active";
|
|
1334
|
+
}
|
|
1272
1335
|
function summarizeMemoryLifecycleState(memory) {
|
|
1273
1336
|
return {
|
|
1274
1337
|
category: memory.frontmatter.category,
|
|
@@ -1632,6 +1695,88 @@ function assertMemoryWorthCounter(field, value) {
|
|
|
1632
1695
|
throw new Error(`${field} must be >= 0, got ${value}`);
|
|
1633
1696
|
}
|
|
1634
1697
|
}
|
|
1698
|
+
function isErrnoCode(error, code) {
|
|
1699
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
1700
|
+
}
|
|
1701
|
+
function trimTrailingSpacesAndTabs(value) {
|
|
1702
|
+
let end = value.length;
|
|
1703
|
+
while (end > 0 && (value[end - 1] === " " || value[end - 1] === " ")) {
|
|
1704
|
+
end -= 1;
|
|
1705
|
+
}
|
|
1706
|
+
return end === value.length ? value : value.slice(0, end);
|
|
1707
|
+
}
|
|
1708
|
+
function trimLeadingSpacesAndTabs(value) {
|
|
1709
|
+
let start = 0;
|
|
1710
|
+
while (start < value.length && (value[start] === " " || value[start] === " ")) {
|
|
1711
|
+
start += 1;
|
|
1712
|
+
}
|
|
1713
|
+
return start === 0 ? value : value.slice(start);
|
|
1714
|
+
}
|
|
1715
|
+
function stripDefaultCitationMarkersWithoutRegex(value) {
|
|
1716
|
+
return stripCitationMarkersForHashRemoval(value, DEFAULT_CITATION_FORMAT);
|
|
1717
|
+
}
|
|
1718
|
+
function citationTemplateLiteralParts(template) {
|
|
1719
|
+
const parts = [];
|
|
1720
|
+
let cursor = 0;
|
|
1721
|
+
while (cursor < template.length) {
|
|
1722
|
+
const open = template.indexOf("{", cursor);
|
|
1723
|
+
if (open === -1) {
|
|
1724
|
+
parts.push(template.slice(cursor));
|
|
1725
|
+
break;
|
|
1726
|
+
}
|
|
1727
|
+
parts.push(template.slice(cursor, open));
|
|
1728
|
+
const close = template.indexOf("}", open + 1);
|
|
1729
|
+
if (close === -1) {
|
|
1730
|
+
cursor = open + 1;
|
|
1731
|
+
} else {
|
|
1732
|
+
cursor = close + 1;
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
return parts.filter((part) => part.length > 0);
|
|
1736
|
+
}
|
|
1737
|
+
function stripCitationMarkersForHashRemoval(value, template) {
|
|
1738
|
+
const parts = citationTemplateLiteralParts(template);
|
|
1739
|
+
if (parts.length === 0) return value;
|
|
1740
|
+
const first = parts[0];
|
|
1741
|
+
const lowerValue = value.toLowerCase();
|
|
1742
|
+
const lowerFirst = first.toLowerCase();
|
|
1743
|
+
const lowerParts = parts.map((part) => part.toLowerCase());
|
|
1744
|
+
if (!lowerValue.includes(lowerFirst)) return value;
|
|
1745
|
+
let result = "";
|
|
1746
|
+
let cursor = 0;
|
|
1747
|
+
let removed = false;
|
|
1748
|
+
while (cursor < value.length) {
|
|
1749
|
+
const markerStart = lowerValue.indexOf(lowerFirst, cursor);
|
|
1750
|
+
if (markerStart === -1) {
|
|
1751
|
+
result += value.slice(cursor);
|
|
1752
|
+
break;
|
|
1753
|
+
}
|
|
1754
|
+
const boundedEnd = first.startsWith("[") ? value.indexOf("]", markerStart + first.length) : -1;
|
|
1755
|
+
if (first.startsWith("[") && boundedEnd === -1) {
|
|
1756
|
+
result += value.slice(cursor);
|
|
1757
|
+
break;
|
|
1758
|
+
}
|
|
1759
|
+
const searchLimit = boundedEnd === -1 ? value.length : boundedEnd + 1;
|
|
1760
|
+
let markerEnd = markerStart + first.length;
|
|
1761
|
+
let matched = true;
|
|
1762
|
+
for (let i = 1; i < lowerParts.length; i += 1) {
|
|
1763
|
+
const partIndex = lowerValue.indexOf(lowerParts[i], markerEnd);
|
|
1764
|
+
if (partIndex === -1 || partIndex + parts[i].length > searchLimit) {
|
|
1765
|
+
matched = false;
|
|
1766
|
+
break;
|
|
1767
|
+
}
|
|
1768
|
+
markerEnd = partIndex + parts[i].length;
|
|
1769
|
+
}
|
|
1770
|
+
if (!matched) {
|
|
1771
|
+
result += value.slice(cursor);
|
|
1772
|
+
break;
|
|
1773
|
+
}
|
|
1774
|
+
result += trimTrailingSpacesAndTabs(value.slice(cursor, markerStart));
|
|
1775
|
+
cursor = markerEnd;
|
|
1776
|
+
removed = true;
|
|
1777
|
+
}
|
|
1778
|
+
return removed ? trimLeadingSpacesAndTabs(result) : value;
|
|
1779
|
+
}
|
|
1635
1780
|
function serializeFrontmatter(fm) {
|
|
1636
1781
|
const lines = [
|
|
1637
1782
|
"---",
|
|
@@ -1654,6 +1799,10 @@ function serializeFrontmatter(fm) {
|
|
|
1654
1799
|
if (fm.supersededBy) lines.push(`supersededBy: ${fm.supersededBy}`);
|
|
1655
1800
|
if (fm.supersededAt) lines.push(`supersededAt: ${fm.supersededAt}`);
|
|
1656
1801
|
if (fm.archivedAt) lines.push(`archivedAt: ${fm.archivedAt}`);
|
|
1802
|
+
if (fm.valid_at) lines.push(`validAt: ${fm.valid_at}`);
|
|
1803
|
+
if (fm.invalid_at) lines.push(`invalidAt: ${fm.invalid_at}`);
|
|
1804
|
+
if (fm.forgottenAt) lines.push(`forgottenAt: ${fm.forgottenAt}`);
|
|
1805
|
+
if (fm.forgottenReason) lines.push(`forgottenReason: ${JSON.stringify(fm.forgottenReason)}`);
|
|
1657
1806
|
if (fm.lifecycleState) lines.push(`lifecycleState: ${fm.lifecycleState}`);
|
|
1658
1807
|
if (fm.verificationState) lines.push(`verificationState: ${fm.verificationState}`);
|
|
1659
1808
|
if (fm.policyClass) lines.push(`policyClass: ${fm.policyClass}`);
|
|
@@ -1731,11 +1880,22 @@ function serializeFrontmatter(fm) {
|
|
|
1731
1880
|
if (fm.derived_via !== void 0) {
|
|
1732
1881
|
if (!isConsolidationOperator(fm.derived_via)) {
|
|
1733
1882
|
throw new Error(
|
|
1734
|
-
`serializeFrontmatter: invalid derived_via ${JSON.stringify(fm.derived_via)} \u2014 expected one of "split" | "merge" | "update"`
|
|
1883
|
+
`serializeFrontmatter: invalid derived_via ${JSON.stringify(fm.derived_via)} \u2014 expected one of "split" | "merge" | "update" | "pattern-reinforcement"`
|
|
1735
1884
|
);
|
|
1736
1885
|
}
|
|
1737
1886
|
lines.push(`derived_via: ${fm.derived_via}`);
|
|
1738
1887
|
}
|
|
1888
|
+
if (fm.reinforcement_count !== void 0) {
|
|
1889
|
+
if (!Number.isInteger(fm.reinforcement_count) || fm.reinforcement_count <= 0) {
|
|
1890
|
+
throw new Error(
|
|
1891
|
+
`serializeFrontmatter: reinforcement_count must be a positive integer (got ${JSON.stringify(fm.reinforcement_count)})`
|
|
1892
|
+
);
|
|
1893
|
+
}
|
|
1894
|
+
lines.push(`reinforcement_count: ${fm.reinforcement_count}`);
|
|
1895
|
+
}
|
|
1896
|
+
if (fm.last_reinforced_at) {
|
|
1897
|
+
lines.push(`last_reinforced_at: ${fm.last_reinforced_at}`);
|
|
1898
|
+
}
|
|
1739
1899
|
lines.push("---");
|
|
1740
1900
|
return lines.join("\n");
|
|
1741
1901
|
}
|
|
@@ -1768,6 +1928,20 @@ function parseLinkReasonValue(rawValue) {
|
|
|
1768
1928
|
return legacyValue;
|
|
1769
1929
|
}
|
|
1770
1930
|
}
|
|
1931
|
+
function parseFrontmatterStringValue(rawValue) {
|
|
1932
|
+
if (rawValue === void 0) return void 0;
|
|
1933
|
+
const trimmed = rawValue.trim();
|
|
1934
|
+
if (trimmed.length === 0) return void 0;
|
|
1935
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
1936
|
+
try {
|
|
1937
|
+
const parsed = JSON.parse(trimmed);
|
|
1938
|
+
return typeof parsed === "string" ? parsed : trimmed;
|
|
1939
|
+
} catch {
|
|
1940
|
+
return trimmed.slice(1, -1).replace(/\\"/g, '"');
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
return trimmed;
|
|
1944
|
+
}
|
|
1771
1945
|
function parseMemoryWorthCounterField(raw) {
|
|
1772
1946
|
if (raw === void 0) return void 0;
|
|
1773
1947
|
const trimmed = raw.trim();
|
|
@@ -1776,6 +1950,14 @@ function parseMemoryWorthCounterField(raw) {
|
|
|
1776
1950
|
if (!Number.isFinite(n) || !Number.isInteger(n) || n < 0) return void 0;
|
|
1777
1951
|
return n;
|
|
1778
1952
|
}
|
|
1953
|
+
function parseReinforcementCountField(raw) {
|
|
1954
|
+
if (raw === void 0) return void 0;
|
|
1955
|
+
const trimmed = raw.trim();
|
|
1956
|
+
if (trimmed.length === 0) return void 0;
|
|
1957
|
+
const n = Number(trimmed);
|
|
1958
|
+
if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) return void 0;
|
|
1959
|
+
return n;
|
|
1960
|
+
}
|
|
1779
1961
|
function parseFrontmatter2(raw) {
|
|
1780
1962
|
const match = raw.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
1781
1963
|
if (!match) return null;
|
|
@@ -1961,6 +2143,11 @@ function parseFrontmatter2(raw) {
|
|
|
1961
2143
|
supersededBy: fm.supersededBy || void 0,
|
|
1962
2144
|
supersededAt: fm.supersededAt || void 0,
|
|
1963
2145
|
archivedAt: fm.archivedAt || void 0,
|
|
2146
|
+
// Issue #680 — explicit fact lifecycle round-trip.
|
|
2147
|
+
valid_at: fm.validAt || void 0,
|
|
2148
|
+
invalid_at: fm.invalidAt || void 0,
|
|
2149
|
+
forgottenAt: fm.forgottenAt || void 0,
|
|
2150
|
+
forgottenReason: parseFrontmatterStringValue(fm.forgottenReason),
|
|
1964
2151
|
lifecycleState: fm.lifecycleState || void 0,
|
|
1965
2152
|
verificationState: fm.verificationState || void 0,
|
|
1966
2153
|
policyClass: fm.policyClass || void 0,
|
|
@@ -1995,7 +2182,14 @@ function parseFrontmatter2(raw) {
|
|
|
1995
2182
|
// Consolidation provenance (issue #561) — read-through only in this
|
|
1996
2183
|
// PR; no code produces these fields yet.
|
|
1997
2184
|
derived_from,
|
|
1998
|
-
derived_via
|
|
2185
|
+
derived_via,
|
|
2186
|
+
// Pattern-reinforcement metadata (issue #687 PR 2/4). Parse
|
|
2187
|
+
// permissively: invalid values (negative, non-integer, blank
|
|
2188
|
+
// ISO-strings) are dropped to undefined so a corrupt frontmatter
|
|
2189
|
+
// never poisons downstream scoring. Validation lives on the
|
|
2190
|
+
// write path in serializeFrontmatter.
|
|
2191
|
+
reinforcement_count: parseReinforcementCountField(fm.reinforcement_count),
|
|
2192
|
+
last_reinforced_at: fm.last_reinforced_at || void 0
|
|
1999
2193
|
},
|
|
2000
2194
|
content
|
|
2001
2195
|
};
|
|
@@ -2106,13 +2300,19 @@ var ContentHashIndex = class _ContentHashIndex {
|
|
|
2106
2300
|
hashes = /* @__PURE__ */ new Set();
|
|
2107
2301
|
dirty = false;
|
|
2108
2302
|
filePath;
|
|
2109
|
-
|
|
2303
|
+
secureStoreKeyProvider;
|
|
2304
|
+
secureStoreWriteKeyProvider;
|
|
2305
|
+
memoryDir;
|
|
2306
|
+
constructor(stateDir, secureStoreKeyProvider = () => null, secureStoreWriteKeyProvider = secureStoreKeyProvider, memoryDir = path4.dirname(stateDir)) {
|
|
2110
2307
|
this.filePath = path4.join(stateDir, "fact-hashes.txt");
|
|
2308
|
+
this.secureStoreKeyProvider = secureStoreKeyProvider;
|
|
2309
|
+
this.secureStoreWriteKeyProvider = secureStoreWriteKeyProvider;
|
|
2310
|
+
this.memoryDir = memoryDir;
|
|
2111
2311
|
}
|
|
2112
2312
|
/** Load existing hashes from disk. Safe to call multiple times. */
|
|
2113
2313
|
async load() {
|
|
2114
2314
|
try {
|
|
2115
|
-
const raw = await
|
|
2315
|
+
const raw = await readMaybeEncryptedFile(this.filePath, this.secureStoreKeyProvider(), this.memoryDir);
|
|
2116
2316
|
for (const line of raw.split("\n")) {
|
|
2117
2317
|
const trimmed = line.trim();
|
|
2118
2318
|
if (trimmed.length > 0) {
|
|
@@ -2120,7 +2320,9 @@ var ContentHashIndex = class _ContentHashIndex {
|
|
|
2120
2320
|
}
|
|
2121
2321
|
}
|
|
2122
2322
|
log.debug(`content-hash index: loaded ${this.hashes.size} hashes`);
|
|
2123
|
-
} catch {
|
|
2323
|
+
} catch (err) {
|
|
2324
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
2325
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
2124
2326
|
log.debug("content-hash index: no existing index \u2014 starting fresh");
|
|
2125
2327
|
}
|
|
2126
2328
|
}
|
|
@@ -2143,7 +2345,13 @@ var ContentHashIndex = class _ContentHashIndex {
|
|
|
2143
2345
|
async save() {
|
|
2144
2346
|
if (!this.dirty) return;
|
|
2145
2347
|
await mkdir2(path4.dirname(this.filePath), { recursive: true });
|
|
2146
|
-
await
|
|
2348
|
+
await writeMaybeEncryptedFile(
|
|
2349
|
+
this.filePath,
|
|
2350
|
+
[...this.hashes].join("\n") + "\n",
|
|
2351
|
+
this.secureStoreWriteKeyProvider(),
|
|
2352
|
+
{},
|
|
2353
|
+
this.memoryDir
|
|
2354
|
+
);
|
|
2147
2355
|
this.dirty = false;
|
|
2148
2356
|
log.debug(`content-hash index: saved ${this.hashes.size} hashes`);
|
|
2149
2357
|
}
|
|
@@ -3006,6 +3214,8 @@ var StorageManager = class _StorageManager {
|
|
|
3006
3214
|
// 1 minute
|
|
3007
3215
|
static artifactWriteVersionByDir = /* @__PURE__ */ new Map();
|
|
3008
3216
|
static memoryStatusVersionByDir = /* @__PURE__ */ new Map();
|
|
3217
|
+
static secureStoreEntityCacheKeyIds = /* @__PURE__ */ new WeakMap();
|
|
3218
|
+
static nextSecureStoreEntityCacheKeyId = 1;
|
|
3009
3219
|
// In-process fallback for the cold-write sentinel (used when the disk file
|
|
3010
3220
|
// is not accessible). The canonical source of truth is state/cold-write.log.
|
|
3011
3221
|
static coldWriteVersionByDir = /* @__PURE__ */ new Map();
|
|
@@ -3053,6 +3263,7 @@ var StorageManager = class _StorageManager {
|
|
|
3053
3263
|
factHashIndexLoadPromise = null;
|
|
3054
3264
|
factHashIndexAuthoritative = null;
|
|
3055
3265
|
factHashIndexAuthoritativePromise = null;
|
|
3266
|
+
secureAppendChains = /* @__PURE__ */ new Map();
|
|
3056
3267
|
/** Optional: set by the orchestrator after construction to enable template-aware citation stripping during legacy hash rebuild. */
|
|
3057
3268
|
citationTemplate = DEFAULT_CITATION_FORMAT;
|
|
3058
3269
|
/** Page-versioning configuration. Set by the orchestrator after construction. */
|
|
@@ -3061,6 +3272,87 @@ var StorageManager = class _StorageManager {
|
|
|
3061
3272
|
setVersioningConfig(config) {
|
|
3062
3273
|
this._versioningConfig = config;
|
|
3063
3274
|
}
|
|
3275
|
+
/**
|
|
3276
|
+
* At-rest encryption key (issue #690 PR 3/4).
|
|
3277
|
+
*
|
|
3278
|
+
* When non-null, every memory file read is decrypted and every write
|
|
3279
|
+
* is encrypted using the secure-fs layer. When null, the storage
|
|
3280
|
+
* layer operates in plain-text mode (legacy/unencrypted store).
|
|
3281
|
+
*
|
|
3282
|
+
* Set by the orchestrator after init/unlock; cleared on lock.
|
|
3283
|
+
* The key buffer is NEVER logged or serialized.
|
|
3284
|
+
*/
|
|
3285
|
+
_secureStoreKey = null;
|
|
3286
|
+
/**
|
|
3287
|
+
* When true (and `_secureStoreKey` is non-null), new writes are
|
|
3288
|
+
* encrypted. Set to false to pause encryption of new writes while
|
|
3289
|
+
* still decrypting existing files.
|
|
3290
|
+
*/
|
|
3291
|
+
_secureStoreEncryptOnWrite = true;
|
|
3292
|
+
/**
|
|
3293
|
+
* When true, the secure-store is configured as required — writes
|
|
3294
|
+
* MUST be encrypted and a locked store MUST reject writes rather
|
|
3295
|
+
* than silently falling back to plaintext. Set by the orchestrator
|
|
3296
|
+
* from `config.secureStoreEnabled`.
|
|
3297
|
+
*/
|
|
3298
|
+
_secureStoreRequired = false;
|
|
3299
|
+
/**
|
|
3300
|
+
* Set or clear the at-rest encryption key.
|
|
3301
|
+
*
|
|
3302
|
+
* Pass a 32-byte Buffer to enable encryption; pass null to clear
|
|
3303
|
+
* (lock) the store. The caller is responsible for key lifecycle —
|
|
3304
|
+
* this method does not zero the buffer on replacement; the keyring
|
|
3305
|
+
* module (`keyring.ts`) owns zeroization.
|
|
3306
|
+
*/
|
|
3307
|
+
setSecureStoreKey(key, encryptOnWrite = true) {
|
|
3308
|
+
this._secureStoreKey = key;
|
|
3309
|
+
this._secureStoreEncryptOnWrite = encryptOnWrite;
|
|
3310
|
+
invalidateCachedEntities(this.baseDir);
|
|
3311
|
+
this.invalidateKnowledgeIndexCache();
|
|
3312
|
+
}
|
|
3313
|
+
getEntityCacheSecureStoreKey() {
|
|
3314
|
+
if (!this._secureStoreKey) return "secure-store:locked";
|
|
3315
|
+
let id = _StorageManager.secureStoreEntityCacheKeyIds.get(this._secureStoreKey);
|
|
3316
|
+
if (id === void 0) {
|
|
3317
|
+
id = _StorageManager.nextSecureStoreEntityCacheKeyId++;
|
|
3318
|
+
_StorageManager.secureStoreEntityCacheKeyIds.set(this._secureStoreKey, id);
|
|
3319
|
+
}
|
|
3320
|
+
return `secure-store:key:${id}`;
|
|
3321
|
+
}
|
|
3322
|
+
/**
|
|
3323
|
+
* Mark the secure-store as required for this storage instance.
|
|
3324
|
+
* When required and locked, writes throw SecureStoreLockedError
|
|
3325
|
+
* rather than silently writing plaintext.
|
|
3326
|
+
*/
|
|
3327
|
+
setSecureStoreRequired(required) {
|
|
3328
|
+
this._secureStoreRequired = required;
|
|
3329
|
+
}
|
|
3330
|
+
/** Return true iff the secure-store key is currently set (store is unlocked). */
|
|
3331
|
+
isSecureStoreUnlocked() {
|
|
3332
|
+
return this._secureStoreKey !== null;
|
|
3333
|
+
}
|
|
3334
|
+
/**
|
|
3335
|
+
* Resolve the effective write key.
|
|
3336
|
+
*
|
|
3337
|
+
* - If `_secureStoreEncryptOnWrite` is false: returns null (plain write).
|
|
3338
|
+
* - If `_secureStoreEncryptOnWrite` is true AND key is set: returns key.
|
|
3339
|
+
* - If `_secureStoreEncryptOnWrite` is true AND key is null AND
|
|
3340
|
+
* `_secureStoreRequired` is true: throws SecureStoreLockedError so the
|
|
3341
|
+
* write fails loudly rather than silently writing plaintext (P1 finding
|
|
3342
|
+
* from Cursor review of PR #767).
|
|
3343
|
+
* - If `_secureStoreEncryptOnWrite` is true AND key is null AND
|
|
3344
|
+
* `_secureStoreRequired` is false: returns null (unencrypted store).
|
|
3345
|
+
*/
|
|
3346
|
+
resolveWriteKey() {
|
|
3347
|
+
if (!this._secureStoreEncryptOnWrite) return null;
|
|
3348
|
+
if (this._secureStoreKey !== null) return this._secureStoreKey;
|
|
3349
|
+
if (this._secureStoreRequired) {
|
|
3350
|
+
throw new SecureStoreLockedError(
|
|
3351
|
+
"secure-store is locked \u2014 cannot write memory file. Run `remnic secure-store unlock` to decrypt, or restart the daemon after unlocking."
|
|
3352
|
+
);
|
|
3353
|
+
}
|
|
3354
|
+
return null;
|
|
3355
|
+
}
|
|
3064
3356
|
/**
|
|
3065
3357
|
* Snapshot the current content of a page before overwriting.
|
|
3066
3358
|
* No-op when versioning is disabled or the file does not yet exist.
|
|
@@ -3068,7 +3360,7 @@ var StorageManager = class _StorageManager {
|
|
|
3068
3360
|
async snapshotBeforeWrite(filePath, trigger) {
|
|
3069
3361
|
if (!this._versioningConfig || !this._versioningConfig.enabled) return;
|
|
3070
3362
|
try {
|
|
3071
|
-
const existing = await
|
|
3363
|
+
const existing = await readMaybeEncryptedFile(filePath, this._secureStoreKey, this.baseDir);
|
|
3072
3364
|
await createVersion(filePath, existing, trigger, this._versioningConfig, log, void 0, this.baseDir);
|
|
3073
3365
|
} catch {
|
|
3074
3366
|
}
|
|
@@ -3091,7 +3383,7 @@ var StorageManager = class _StorageManager {
|
|
|
3091
3383
|
if (!this._versioningConfig || !this._versioningConfig.enabled) return null;
|
|
3092
3384
|
let existing;
|
|
3093
3385
|
try {
|
|
3094
|
-
existing = await
|
|
3386
|
+
existing = await readMaybeEncryptedFile(filePath, this._secureStoreKey, this.baseDir);
|
|
3095
3387
|
} catch {
|
|
3096
3388
|
return null;
|
|
3097
3389
|
}
|
|
@@ -3177,6 +3469,57 @@ var StorageManager = class _StorageManager {
|
|
|
3177
3469
|
get entitiesDir() {
|
|
3178
3470
|
return path4.join(this.baseDir, "entities");
|
|
3179
3471
|
}
|
|
3472
|
+
readStorageSecureFile(filePath) {
|
|
3473
|
+
return readMaybeEncryptedFile(filePath, this._secureStoreKey, this.baseDir);
|
|
3474
|
+
}
|
|
3475
|
+
writeStorageSecureFile(filePath, content) {
|
|
3476
|
+
return writeMaybeEncryptedFile(filePath, content, this.resolveWriteKey(), {}, this.baseDir);
|
|
3477
|
+
}
|
|
3478
|
+
createContentHashIndex() {
|
|
3479
|
+
return new ContentHashIndex(
|
|
3480
|
+
this.stateDir,
|
|
3481
|
+
() => this._secureStoreKey,
|
|
3482
|
+
() => this.resolveWriteKey(),
|
|
3483
|
+
this.baseDir
|
|
3484
|
+
);
|
|
3485
|
+
}
|
|
3486
|
+
async appendStorageSecureFile(filePath, content) {
|
|
3487
|
+
const previous = this.secureAppendChains.get(filePath) ?? Promise.resolve();
|
|
3488
|
+
const current = previous.catch(() => void 0).then(() => this.appendStorageSecureFileUnlocked(filePath, content));
|
|
3489
|
+
const next = current.catch(() => void 0);
|
|
3490
|
+
this.secureAppendChains.set(filePath, next);
|
|
3491
|
+
try {
|
|
3492
|
+
await current;
|
|
3493
|
+
} finally {
|
|
3494
|
+
if (this.secureAppendChains.get(filePath) === next) {
|
|
3495
|
+
this.secureAppendChains.delete(filePath);
|
|
3496
|
+
}
|
|
3497
|
+
}
|
|
3498
|
+
}
|
|
3499
|
+
async appendStorageSecureFileUnlocked(filePath, content) {
|
|
3500
|
+
const writeKey = this.resolveWriteKey();
|
|
3501
|
+
await mkdir2(path4.dirname(filePath), { recursive: true });
|
|
3502
|
+
if (writeKey === null) {
|
|
3503
|
+
try {
|
|
3504
|
+
if (isEncryptedFile(await readFile2(filePath))) {
|
|
3505
|
+
const existing2 = await this.readStorageSecureFile(filePath);
|
|
3506
|
+
await writeMaybeEncryptedFile(filePath, `${existing2}${content}`, null, {}, this.baseDir);
|
|
3507
|
+
return;
|
|
3508
|
+
}
|
|
3509
|
+
} catch (err) {
|
|
3510
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
3511
|
+
}
|
|
3512
|
+
await appendFile(filePath, content, "utf-8");
|
|
3513
|
+
return;
|
|
3514
|
+
}
|
|
3515
|
+
let existing = "";
|
|
3516
|
+
try {
|
|
3517
|
+
existing = await this.readStorageSecureFile(filePath);
|
|
3518
|
+
} catch (err) {
|
|
3519
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
3520
|
+
}
|
|
3521
|
+
await writeMaybeEncryptedFile(filePath, `${existing}${content}`, writeKey, {}, this.baseDir);
|
|
3522
|
+
}
|
|
3180
3523
|
get stateDir() {
|
|
3181
3524
|
return path4.join(this.baseDir, "state");
|
|
3182
3525
|
}
|
|
@@ -3191,7 +3534,7 @@ var StorageManager = class _StorageManager {
|
|
|
3191
3534
|
return this.factHashIndex;
|
|
3192
3535
|
}
|
|
3193
3536
|
if (!this.factHashIndexLoadPromise) {
|
|
3194
|
-
const index =
|
|
3537
|
+
const index = this.createContentHashIndex();
|
|
3195
3538
|
this.factHashIndexLoadPromise = index.load().then(() => {
|
|
3196
3539
|
this.factHashIndex = index;
|
|
3197
3540
|
return index;
|
|
@@ -3433,7 +3776,7 @@ ${sanitized.text}
|
|
|
3433
3776
|
filePath = path4.join(this.factsDir, today, `${id}.md`);
|
|
3434
3777
|
}
|
|
3435
3778
|
await this.snapshotBeforeWrite(filePath, "write");
|
|
3436
|
-
await
|
|
3779
|
+
await writeMaybeEncryptedFile(filePath, fileContent, this.resolveWriteKey(), {}, this.baseDir);
|
|
3437
3780
|
this.invalidateAllMemoriesCache();
|
|
3438
3781
|
await this.appendGeneratedMemoryLifecycleEventFailOpen("storage.writeMemory", {
|
|
3439
3782
|
memoryId: id,
|
|
@@ -3469,6 +3812,50 @@ ${sanitized.text}
|
|
|
3469
3812
|
const sanitized = sanitizeMemoryContent(content);
|
|
3470
3813
|
return factHashIndex.has(sanitized.text);
|
|
3471
3814
|
}
|
|
3815
|
+
factContentHashForRemoval(memory) {
|
|
3816
|
+
if (memory.frontmatter.category !== "fact") return null;
|
|
3817
|
+
if (typeof memory.frontmatter.contentHash === "string" && memory.frontmatter.contentHash.length > 0) {
|
|
3818
|
+
return memory.frontmatter.contentHash;
|
|
3819
|
+
}
|
|
3820
|
+
const configuredHashSource = stripCitationMarkersForHashRemoval(memory.content, this.citationTemplate);
|
|
3821
|
+
const hashSource = configuredHashSource !== memory.content ? configuredHashSource : stripDefaultCitationMarkersWithoutRegex(memory.content);
|
|
3822
|
+
return ContentHashIndex.computeHash(sanitizeMemoryContent(hashSource).text);
|
|
3823
|
+
}
|
|
3824
|
+
async removeFactContentHashesForMemories(memories) {
|
|
3825
|
+
await this.ensureFactHashIndexAuthoritative();
|
|
3826
|
+
const factHashIndex = await this.getFactHashIndex();
|
|
3827
|
+
const removedIds = new Set(
|
|
3828
|
+
memories.map((memory) => memory.frontmatter.id).filter((id) => typeof id === "string" && id.length > 0)
|
|
3829
|
+
);
|
|
3830
|
+
const removedHashes = /* @__PURE__ */ new Map();
|
|
3831
|
+
for (const memory of memories) {
|
|
3832
|
+
const hash = this.factContentHashForRemoval(memory);
|
|
3833
|
+
if (hash) {
|
|
3834
|
+
removedHashes.set(memory, hash);
|
|
3835
|
+
}
|
|
3836
|
+
}
|
|
3837
|
+
if (removedHashes.size === 0) return;
|
|
3838
|
+
const remainingActiveHashes = /* @__PURE__ */ new Set();
|
|
3839
|
+
const remainingMemories = [
|
|
3840
|
+
...await this.readAllMemories(),
|
|
3841
|
+
...await this.readAllColdMemories()
|
|
3842
|
+
];
|
|
3843
|
+
for (const memory of remainingMemories) {
|
|
3844
|
+
if (memory.frontmatter.category !== "fact") continue;
|
|
3845
|
+
if (removedIds.has(memory.frontmatter.id)) continue;
|
|
3846
|
+
if (inferMemoryStatus(memory.frontmatter, memory.path) !== "active") continue;
|
|
3847
|
+
const hash = this.factContentHashForRemoval(memory);
|
|
3848
|
+
if (hash) {
|
|
3849
|
+
remainingActiveHashes.add(hash);
|
|
3850
|
+
}
|
|
3851
|
+
}
|
|
3852
|
+
for (const hash of removedHashes.values()) {
|
|
3853
|
+
if (!remainingActiveHashes.has(hash)) {
|
|
3854
|
+
factHashIndex.removeByHash(hash);
|
|
3855
|
+
}
|
|
3856
|
+
}
|
|
3857
|
+
await factHashIndex.save();
|
|
3858
|
+
}
|
|
3472
3859
|
async isFactContentHashAuthoritative() {
|
|
3473
3860
|
await this.ensureFactHashIndexAuthoritative();
|
|
3474
3861
|
return true;
|
|
@@ -3502,10 +3889,10 @@ ${sanitized.text}
|
|
|
3502
3889
|
return "";
|
|
3503
3890
|
}
|
|
3504
3891
|
const filePath = path4.join(dir, `${id}.md`);
|
|
3505
|
-
await
|
|
3892
|
+
await writeMaybeEncryptedFile(filePath, `${serializeFrontmatter(fm)}
|
|
3506
3893
|
|
|
3507
3894
|
${sanitized.text}
|
|
3508
|
-
`,
|
|
3895
|
+
`, this.resolveWriteKey(), {}, this.baseDir);
|
|
3509
3896
|
const actor = typeof options.actor === "string" && options.actor.length > 0 ? options.actor : "storage.writeArtifact";
|
|
3510
3897
|
await this.appendGeneratedMemoryLifecycleEventFailOpen("storage.writeArtifact", {
|
|
3511
3898
|
memoryId: id,
|
|
@@ -3614,9 +4001,11 @@ ${sanitized.text}
|
|
|
3614
4001
|
aliases: []
|
|
3615
4002
|
};
|
|
3616
4003
|
try {
|
|
3617
|
-
const existing = await
|
|
4004
|
+
const existing = await this.readStorageSecureFile(filePath);
|
|
3618
4005
|
entity = parseEntityFile(existing, this.entitySchemas);
|
|
3619
|
-
} catch {
|
|
4006
|
+
} catch (err) {
|
|
4007
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
4008
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
3620
4009
|
}
|
|
3621
4010
|
const timestamp = options.timestamp?.trim() || (/* @__PURE__ */ new Date()).toISOString();
|
|
3622
4011
|
const source = options.source?.trim() || void 0;
|
|
@@ -3672,7 +4061,7 @@ ${sanitized.text}
|
|
|
3672
4061
|
entity.created = entity.created || timestamp;
|
|
3673
4062
|
entity.updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
3674
4063
|
await this.snapshotBeforeWrite(filePath, "write");
|
|
3675
|
-
await
|
|
4064
|
+
await this.writeStorageSecureFile(filePath, serializeEntityFile(entity, this.entitySchemas));
|
|
3676
4065
|
this.invalidateKnowledgeIndexCache();
|
|
3677
4066
|
this.bumpMemoryStatusVersion();
|
|
3678
4067
|
log.debug(`wrote entity ${normalized}`);
|
|
@@ -3680,15 +4069,19 @@ ${sanitized.text}
|
|
|
3680
4069
|
}
|
|
3681
4070
|
async readProfile() {
|
|
3682
4071
|
try {
|
|
3683
|
-
return await
|
|
3684
|
-
} catch {
|
|
3685
|
-
|
|
4072
|
+
return await readMaybeEncryptedFile(this.profilePath, this._secureStoreKey, this.baseDir);
|
|
4073
|
+
} catch (error) {
|
|
4074
|
+
if (error instanceof SecureStoreLockedError) {
|
|
4075
|
+
throw error;
|
|
4076
|
+
}
|
|
4077
|
+
if (isErrnoCode(error, "ENOENT")) return "";
|
|
4078
|
+
throw error;
|
|
3686
4079
|
}
|
|
3687
4080
|
}
|
|
3688
4081
|
async writeProfile(content) {
|
|
3689
4082
|
await this.ensureDirectories();
|
|
3690
4083
|
await this.snapshotBeforeWrite(this.profilePath, "consolidation");
|
|
3691
|
-
await
|
|
4084
|
+
await writeMaybeEncryptedFile(this.profilePath, content, this.resolveWriteKey(), {}, this.baseDir);
|
|
3692
4085
|
log.debug("updated profile.md");
|
|
3693
4086
|
}
|
|
3694
4087
|
/**
|
|
@@ -3771,6 +4164,24 @@ ${sanitized.text}
|
|
|
3771
4164
|
invalidateAllMemoriesCacheForDir() {
|
|
3772
4165
|
this.invalidateAllMemoriesCache();
|
|
3773
4166
|
}
|
|
4167
|
+
/** Invalidate only the cache layers affected by direct tier file deletes. */
|
|
4168
|
+
invalidateMemoryCachesForTiers(tiers) {
|
|
4169
|
+
let hotChanged = false;
|
|
4170
|
+
let coldChanged = false;
|
|
4171
|
+
for (const tier of tiers) {
|
|
4172
|
+
if (tier === "cold") {
|
|
4173
|
+
coldChanged = true;
|
|
4174
|
+
} else if (tier === "hot" || tier === "archive") {
|
|
4175
|
+
hotChanged = true;
|
|
4176
|
+
}
|
|
4177
|
+
}
|
|
4178
|
+
if (hotChanged) {
|
|
4179
|
+
this.invalidateAllMemoriesCache();
|
|
4180
|
+
}
|
|
4181
|
+
if (coldChanged) {
|
|
4182
|
+
this.invalidateColdMemoriesCache();
|
|
4183
|
+
}
|
|
4184
|
+
}
|
|
3774
4185
|
/** Clear ALL static caches. Use in tests that write files directly
|
|
3775
4186
|
* (bypassing StorageManager.writeMemory) to avoid stale reads. */
|
|
3776
4187
|
static clearAllStaticCaches() {
|
|
@@ -3862,7 +4273,7 @@ ${sanitized.text}
|
|
|
3862
4273
|
const results = await Promise.all(
|
|
3863
4274
|
batch.map(async (fullPath) => {
|
|
3864
4275
|
try {
|
|
3865
|
-
const raw = await
|
|
4276
|
+
const raw = await readMaybeEncryptedFile(fullPath, this._secureStoreKey, this.baseDir);
|
|
3866
4277
|
const parsed = parseFrontmatter2(raw);
|
|
3867
4278
|
if (!parsed) return null;
|
|
3868
4279
|
return {
|
|
@@ -3874,7 +4285,8 @@ ${sanitized.text}
|
|
|
3874
4285
|
),
|
|
3875
4286
|
content: parsed.content
|
|
3876
4287
|
};
|
|
3877
|
-
} catch {
|
|
4288
|
+
} catch (err) {
|
|
4289
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
3878
4290
|
return null;
|
|
3879
4291
|
}
|
|
3880
4292
|
})
|
|
@@ -3887,7 +4299,7 @@ ${sanitized.text}
|
|
|
3887
4299
|
}
|
|
3888
4300
|
async readWindowUpdatedMs(filePath) {
|
|
3889
4301
|
try {
|
|
3890
|
-
const raw = await
|
|
4302
|
+
const raw = await readMaybeEncryptedFile(filePath, this._secureStoreKey, this.baseDir);
|
|
3891
4303
|
const match = raw.match(/^---\n([\s\S]*?)\n---\n?/);
|
|
3892
4304
|
if (!match) return null;
|
|
3893
4305
|
const frontmatterBlock = match[1];
|
|
@@ -4071,7 +4483,7 @@ ${sanitized.text}
|
|
|
4071
4483
|
await readDir(fullPath);
|
|
4072
4484
|
} else if (entry.name.endsWith(".md")) {
|
|
4073
4485
|
try {
|
|
4074
|
-
const raw = await
|
|
4486
|
+
const raw = await readMaybeEncryptedFile(fullPath, this._secureStoreKey, this.baseDir);
|
|
4075
4487
|
const parsed = parseFrontmatter2(raw);
|
|
4076
4488
|
if (parsed) {
|
|
4077
4489
|
memories.push({
|
|
@@ -4084,7 +4496,8 @@ ${sanitized.text}
|
|
|
4084
4496
|
content: parsed.content
|
|
4085
4497
|
});
|
|
4086
4498
|
}
|
|
4087
|
-
} catch {
|
|
4499
|
+
} catch (err) {
|
|
4500
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
4088
4501
|
}
|
|
4089
4502
|
}
|
|
4090
4503
|
}
|
|
@@ -4097,7 +4510,7 @@ ${sanitized.text}
|
|
|
4097
4510
|
/** Read a single memory file by its absolute path. Returns null if unreadable. */
|
|
4098
4511
|
async readMemoryByPath(filePath) {
|
|
4099
4512
|
try {
|
|
4100
|
-
const raw = await
|
|
4513
|
+
const raw = await readMaybeEncryptedFile(filePath, this._secureStoreKey, this.baseDir);
|
|
4101
4514
|
const parsed = parseFrontmatter2(raw);
|
|
4102
4515
|
if (parsed) {
|
|
4103
4516
|
return {
|
|
@@ -4132,7 +4545,8 @@ ${sanitized.text}
|
|
|
4132
4545
|
};
|
|
4133
4546
|
}
|
|
4134
4547
|
return null;
|
|
4135
|
-
} catch {
|
|
4548
|
+
} catch (err) {
|
|
4549
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
4136
4550
|
return null;
|
|
4137
4551
|
}
|
|
4138
4552
|
}
|
|
@@ -4170,19 +4584,8 @@ ${sanitized.text}
|
|
|
4170
4584
|
|
|
4171
4585
|
${memory.content}
|
|
4172
4586
|
`;
|
|
4173
|
-
await
|
|
4174
|
-
|
|
4175
|
-
try {
|
|
4176
|
-
await writeFile2(tempPath, fileContent, "utf-8");
|
|
4177
|
-
await rename(tempPath, targetPath);
|
|
4178
|
-
this.invalidateAllMemoriesCache();
|
|
4179
|
-
} catch (err) {
|
|
4180
|
-
try {
|
|
4181
|
-
await unlink(tempPath);
|
|
4182
|
-
} catch {
|
|
4183
|
-
}
|
|
4184
|
-
throw err;
|
|
4185
|
-
}
|
|
4587
|
+
await writeMaybeEncryptedFile(targetPath, fileContent, this.resolveWriteKey(), {}, this.baseDir);
|
|
4588
|
+
this.invalidateAllMemoriesCache();
|
|
4186
4589
|
}
|
|
4187
4590
|
async moveMemoryToPath(memory, targetPath) {
|
|
4188
4591
|
await this.writeMemoryFileAtomic(targetPath, memory);
|
|
@@ -4253,7 +4656,7 @@ ${memory.content}
|
|
|
4253
4656
|
${memory.content}
|
|
4254
4657
|
`;
|
|
4255
4658
|
const destPath = path4.join(destDir, path4.basename(memory.path));
|
|
4256
|
-
await
|
|
4659
|
+
await writeMaybeEncryptedFile(destPath, fileContent, this.resolveWriteKey(), {}, this.baseDir);
|
|
4257
4660
|
await unlink(memory.path);
|
|
4258
4661
|
this.invalidateAllMemoriesCache();
|
|
4259
4662
|
await this.appendGeneratedMemoryLifecycleEventFailOpen(
|
|
@@ -4289,8 +4692,10 @@ ${memory.content}
|
|
|
4289
4692
|
}
|
|
4290
4693
|
async readEntity(name) {
|
|
4291
4694
|
try {
|
|
4292
|
-
return await
|
|
4293
|
-
} catch {
|
|
4695
|
+
return await this.readStorageSecureFile(path4.join(this.entitiesDir, `${name}.md`));
|
|
4696
|
+
} catch (err) {
|
|
4697
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
4698
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
4294
4699
|
return "";
|
|
4295
4700
|
}
|
|
4296
4701
|
}
|
|
@@ -4374,7 +4779,7 @@ ${memory.content}
|
|
|
4374
4779
|
|
|
4375
4780
|
${sanitized.text}
|
|
4376
4781
|
`;
|
|
4377
|
-
await
|
|
4782
|
+
await writeMaybeEncryptedFile(memory.path, fileContent, this.resolveWriteKey(), {}, this.baseDir);
|
|
4378
4783
|
this.invalidateAllMemoriesCache();
|
|
4379
4784
|
await this.appendGeneratedMemoryLifecycleEventFailOpen("storage.updateMemory", {
|
|
4380
4785
|
memoryId: id,
|
|
@@ -4406,7 +4811,7 @@ ${sanitized.text}
|
|
|
4406
4811
|
|
|
4407
4812
|
${memory.content}
|
|
4408
4813
|
`;
|
|
4409
|
-
await
|
|
4814
|
+
await writeMaybeEncryptedFile(memory.path, fileContent, this.resolveWriteKey(), {}, this.baseDir);
|
|
4410
4815
|
this.invalidateAllMemoriesCache();
|
|
4411
4816
|
if (memory.path.includes(`${path4.sep}cold${path4.sep}`)) {
|
|
4412
4817
|
this.invalidateColdMemoriesCache();
|
|
@@ -4471,21 +4876,23 @@ ${memory.content}
|
|
|
4471
4876
|
async loadBuffer() {
|
|
4472
4877
|
const bufferPath = path4.join(this.stateDir, "buffer.json");
|
|
4473
4878
|
try {
|
|
4474
|
-
const raw = await
|
|
4879
|
+
const raw = await this.readStorageSecureFile(bufferPath);
|
|
4475
4880
|
return JSON.parse(raw);
|
|
4476
|
-
} catch {
|
|
4881
|
+
} catch (err) {
|
|
4882
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
4883
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
4477
4884
|
return { turns: [], lastExtractionAt: null, extractionCount: 0 };
|
|
4478
4885
|
}
|
|
4479
4886
|
}
|
|
4480
4887
|
async saveBuffer(state) {
|
|
4481
4888
|
await this.ensureDirectories();
|
|
4482
4889
|
const bufferPath = path4.join(this.stateDir, "buffer.json");
|
|
4483
|
-
await
|
|
4890
|
+
await this.writeStorageSecureFile(bufferPath, JSON.stringify(state, null, 2));
|
|
4484
4891
|
}
|
|
4485
4892
|
async loadMeta() {
|
|
4486
4893
|
const metaPath = path4.join(this.stateDir, "meta.json");
|
|
4487
4894
|
try {
|
|
4488
|
-
const raw = await
|
|
4895
|
+
const raw = await this.readStorageSecureFile(metaPath);
|
|
4489
4896
|
const parsed = JSON.parse(raw);
|
|
4490
4897
|
return {
|
|
4491
4898
|
extractionCount: typeof parsed.extractionCount === "number" ? parsed.extractionCount : 0,
|
|
@@ -4502,7 +4909,9 @@ ${memory.content}
|
|
|
4502
4909
|
observedAt: entry.observedAt
|
|
4503
4910
|
})) : []
|
|
4504
4911
|
};
|
|
4505
|
-
} catch {
|
|
4912
|
+
} catch (err) {
|
|
4913
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
4914
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
4506
4915
|
return {
|
|
4507
4916
|
extractionCount: 0,
|
|
4508
4917
|
lastExtractionAt: null,
|
|
@@ -4516,7 +4925,7 @@ ${memory.content}
|
|
|
4516
4925
|
async saveMeta(state) {
|
|
4517
4926
|
await this.ensureDirectories();
|
|
4518
4927
|
const metaPath = path4.join(this.stateDir, "meta.json");
|
|
4519
|
-
await
|
|
4928
|
+
await this.writeStorageSecureFile(metaPath, JSON.stringify(state, null, 2));
|
|
4520
4929
|
}
|
|
4521
4930
|
async appendMemoryActionEvents(events) {
|
|
4522
4931
|
if (events.length === 0) return 0;
|
|
@@ -4530,7 +4939,7 @@ ${memory.content}
|
|
|
4530
4939
|
return `${JSON.stringify(normalized)}
|
|
4531
4940
|
`;
|
|
4532
4941
|
}).join("");
|
|
4533
|
-
await
|
|
4942
|
+
await this.appendStorageSecureFile(this.memoryActionsPath, payload);
|
|
4534
4943
|
return events.length;
|
|
4535
4944
|
}
|
|
4536
4945
|
async appendMemoryLifecycleEvents(events) {
|
|
@@ -4545,7 +4954,7 @@ ${memory.content}
|
|
|
4545
4954
|
return `${JSON.stringify(normalized)}
|
|
4546
4955
|
`;
|
|
4547
4956
|
}).join("");
|
|
4548
|
-
await
|
|
4957
|
+
await this.appendStorageSecureFile(this.memoryLifecycleLedgerPath, payload);
|
|
4549
4958
|
return events.length;
|
|
4550
4959
|
}
|
|
4551
4960
|
/**
|
|
@@ -4570,7 +4979,7 @@ ${memory.content}
|
|
|
4570
4979
|
return `${JSON.stringify(normalized)}
|
|
4571
4980
|
`;
|
|
4572
4981
|
}).join("");
|
|
4573
|
-
await
|
|
4982
|
+
await this.appendStorageSecureFile(this.bufferSurpriseLedgerPath, payload);
|
|
4574
4983
|
return events.length;
|
|
4575
4984
|
}
|
|
4576
4985
|
/**
|
|
@@ -4607,8 +5016,9 @@ ${memory.content}
|
|
|
4607
5016
|
async readBufferSurpriseEvents(options = {}) {
|
|
4608
5017
|
let raw;
|
|
4609
5018
|
try {
|
|
4610
|
-
raw = await
|
|
5019
|
+
raw = await this.readStorageSecureFile(this.bufferSurpriseLedgerPath);
|
|
4611
5020
|
} catch (err) {
|
|
5021
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
4612
5022
|
const code = err.code;
|
|
4613
5023
|
if (code === "ENOENT") return [];
|
|
4614
5024
|
throw err;
|
|
@@ -4643,7 +5053,7 @@ ${memory.content}
|
|
|
4643
5053
|
await this.ensureDirectories();
|
|
4644
5054
|
let existingKeys = /* @__PURE__ */ new Set();
|
|
4645
5055
|
try {
|
|
4646
|
-
const raw = await
|
|
5056
|
+
const raw = await this.readStorageSecureFile(this.behaviorSignalsPath);
|
|
4647
5057
|
const lines = raw.split("\n");
|
|
4648
5058
|
for (const line of lines) {
|
|
4649
5059
|
const row = line.trim();
|
|
@@ -4656,7 +5066,9 @@ ${memory.content}
|
|
|
4656
5066
|
} catch {
|
|
4657
5067
|
}
|
|
4658
5068
|
}
|
|
4659
|
-
} catch {
|
|
5069
|
+
} catch (err) {
|
|
5070
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
5071
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
4660
5072
|
existingKeys = /* @__PURE__ */ new Set();
|
|
4661
5073
|
}
|
|
4662
5074
|
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -4673,7 +5085,7 @@ ${memory.content}
|
|
|
4673
5085
|
if (deduped.length === 0) return 0;
|
|
4674
5086
|
const payload = deduped.map((event) => `${JSON.stringify(event)}
|
|
4675
5087
|
`).join("");
|
|
4676
|
-
await
|
|
5088
|
+
await this.appendStorageSecureFile(this.behaviorSignalsPath, payload);
|
|
4677
5089
|
return deduped.length;
|
|
4678
5090
|
}
|
|
4679
5091
|
async appendReextractJobs(events) {
|
|
@@ -4682,9 +5094,11 @@ ${memory.content}
|
|
|
4682
5094
|
const filePath = path4.join(this.stateDir, "reextract-jobs.jsonl");
|
|
4683
5095
|
const lines = events.map((event) => JSON.stringify(event)).join("\n") + "\n";
|
|
4684
5096
|
try {
|
|
4685
|
-
await
|
|
5097
|
+
await this.appendStorageSecureFile(filePath, lines);
|
|
4686
5098
|
return events.length;
|
|
4687
|
-
} catch {
|
|
5099
|
+
} catch (err) {
|
|
5100
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
5101
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
4688
5102
|
return 0;
|
|
4689
5103
|
}
|
|
4690
5104
|
}
|
|
@@ -4692,7 +5106,7 @@ ${memory.content}
|
|
|
4692
5106
|
const safeLimit = Number.isFinite(limit) ? Math.max(1, Math.min(1e3, Math.floor(limit))) : 200;
|
|
4693
5107
|
const filePath = path4.join(this.stateDir, "reextract-jobs.jsonl");
|
|
4694
5108
|
try {
|
|
4695
|
-
const raw = await
|
|
5109
|
+
const raw = await this.readStorageSecureFile(filePath);
|
|
4696
5110
|
const lines = raw.split("\n").filter((line) => line.trim().length > 0);
|
|
4697
5111
|
const parsed = [];
|
|
4698
5112
|
for (const line of lines) {
|
|
@@ -4712,7 +5126,9 @@ ${memory.content}
|
|
|
4712
5126
|
}
|
|
4713
5127
|
}
|
|
4714
5128
|
return parsed.slice(-safeLimit);
|
|
4715
|
-
} catch {
|
|
5129
|
+
} catch (err) {
|
|
5130
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
5131
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
4716
5132
|
return [];
|
|
4717
5133
|
}
|
|
4718
5134
|
}
|
|
@@ -4720,7 +5136,7 @@ ${memory.content}
|
|
|
4720
5136
|
const cappedLimit = Math.max(0, Math.floor(limit));
|
|
4721
5137
|
if (cappedLimit === 0) return [];
|
|
4722
5138
|
try {
|
|
4723
|
-
const raw = await
|
|
5139
|
+
const raw = await this.readStorageSecureFile(this.behaviorSignalsPath);
|
|
4724
5140
|
const out = [];
|
|
4725
5141
|
const lines = raw.split("\n");
|
|
4726
5142
|
for (let i = lines.length - 1; i >= 0 && out.length < cappedLimit; i -= 1) {
|
|
@@ -4735,15 +5151,20 @@ ${memory.content}
|
|
|
4735
5151
|
}
|
|
4736
5152
|
}
|
|
4737
5153
|
return out.reverse();
|
|
4738
|
-
} catch {
|
|
5154
|
+
} catch (err) {
|
|
5155
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
5156
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
4739
5157
|
return [];
|
|
4740
5158
|
}
|
|
4741
5159
|
}
|
|
4742
5160
|
async readMemoryActionEvents(limit = 200) {
|
|
5161
|
+
return (await this.readMemoryActionEventRows(limit)).map((row) => row.event);
|
|
5162
|
+
}
|
|
5163
|
+
async readMemoryActionEventRows(limit = 200) {
|
|
4743
5164
|
const cappedLimit = Math.max(0, Math.floor(limit));
|
|
4744
5165
|
if (cappedLimit === 0) return [];
|
|
4745
5166
|
try {
|
|
4746
|
-
const raw = await
|
|
5167
|
+
const raw = await this.readStorageSecureFile(this.memoryActionsPath);
|
|
4747
5168
|
const out = [];
|
|
4748
5169
|
const lines = raw.split("\n");
|
|
4749
5170
|
for (let i = lines.length - 1; i >= 0 && out.length < cappedLimit; i -= 1) {
|
|
@@ -4751,20 +5172,29 @@ ${memory.content}
|
|
|
4751
5172
|
if (!line) continue;
|
|
4752
5173
|
try {
|
|
4753
5174
|
const parsed = JSON.parse(line);
|
|
4754
|
-
|
|
4755
|
-
|
|
5175
|
+
const outcome = parsed.outcome === "applied" || parsed.outcome === "skipped" || parsed.outcome === "failed" ? parsed.outcome : null;
|
|
5176
|
+
if (typeof parsed.timestamp === "string" && typeof parsed.action === "string" && outcome !== null) {
|
|
5177
|
+
out.push({
|
|
5178
|
+
line: i + 1,
|
|
5179
|
+
event: {
|
|
5180
|
+
...parsed,
|
|
5181
|
+
outcome
|
|
5182
|
+
}
|
|
5183
|
+
});
|
|
4756
5184
|
}
|
|
4757
5185
|
} catch {
|
|
4758
5186
|
}
|
|
4759
5187
|
}
|
|
4760
5188
|
return out.reverse();
|
|
4761
|
-
} catch {
|
|
5189
|
+
} catch (err) {
|
|
5190
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
5191
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
4762
5192
|
return [];
|
|
4763
5193
|
}
|
|
4764
5194
|
}
|
|
4765
5195
|
async readAllMemoryLifecycleEvents() {
|
|
4766
5196
|
try {
|
|
4767
|
-
const raw = await
|
|
5197
|
+
const raw = await this.readStorageSecureFile(this.memoryLifecycleLedgerPath);
|
|
4768
5198
|
const out = [];
|
|
4769
5199
|
const lines = raw.split("\n");
|
|
4770
5200
|
for (const line of lines) {
|
|
@@ -4779,7 +5209,9 @@ ${memory.content}
|
|
|
4779
5209
|
}
|
|
4780
5210
|
}
|
|
4781
5211
|
return sortMemoryLifecycleEvents(out);
|
|
4782
|
-
} catch {
|
|
5212
|
+
} catch (err) {
|
|
5213
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
5214
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
4783
5215
|
return [];
|
|
4784
5216
|
}
|
|
4785
5217
|
}
|
|
@@ -4791,35 +5223,39 @@ ${memory.content}
|
|
|
4791
5223
|
}
|
|
4792
5224
|
async writeCompressionGuidelines(content) {
|
|
4793
5225
|
await this.ensureDirectories();
|
|
4794
|
-
await
|
|
5226
|
+
await this.writeStorageSecureFile(this.compressionGuidelinesPath, content);
|
|
4795
5227
|
}
|
|
4796
5228
|
async readCompressionGuidelines() {
|
|
4797
5229
|
try {
|
|
4798
|
-
return await
|
|
4799
|
-
} catch {
|
|
5230
|
+
return await this.readStorageSecureFile(this.compressionGuidelinesPath);
|
|
5231
|
+
} catch (err) {
|
|
5232
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
5233
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
4800
5234
|
return null;
|
|
4801
5235
|
}
|
|
4802
5236
|
}
|
|
4803
5237
|
async writeCompressionGuidelineDraft(content) {
|
|
4804
5238
|
await this.ensureDirectories();
|
|
4805
|
-
await
|
|
5239
|
+
await this.writeStorageSecureFile(this.compressionGuidelineDraftPath, content);
|
|
4806
5240
|
}
|
|
4807
5241
|
async readCompressionGuidelineDraft() {
|
|
4808
5242
|
try {
|
|
4809
|
-
return await
|
|
4810
|
-
} catch {
|
|
5243
|
+
return await this.readStorageSecureFile(this.compressionGuidelineDraftPath);
|
|
5244
|
+
} catch (err) {
|
|
5245
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
5246
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
4811
5247
|
return null;
|
|
4812
5248
|
}
|
|
4813
5249
|
}
|
|
4814
5250
|
async writeCompressionGuidelineOptimizerState(state) {
|
|
4815
5251
|
await this.ensureDirectories();
|
|
4816
|
-
await
|
|
4817
|
-
|
|
5252
|
+
await this.writeStorageSecureFile(this.compressionGuidelineStatePath, `${JSON.stringify(state, null, 2)}
|
|
5253
|
+
`);
|
|
4818
5254
|
}
|
|
4819
5255
|
async writeCompressionGuidelineDraftState(state) {
|
|
4820
5256
|
await this.ensureDirectories();
|
|
4821
|
-
await
|
|
4822
|
-
|
|
5257
|
+
await this.writeStorageSecureFile(this.compressionGuidelineDraftStatePath, `${JSON.stringify(state, null, 2)}
|
|
5258
|
+
`);
|
|
4823
5259
|
}
|
|
4824
5260
|
async readCompressionGuidelineOptimizerState() {
|
|
4825
5261
|
return this.readCompressionGuidelineStateFile(this.compressionGuidelineStatePath);
|
|
@@ -4867,7 +5303,7 @@ ${memory.content}
|
|
|
4867
5303
|
return typeof rule.action === "string" && typeof rule.delta === "number" && Number.isFinite(rule.delta) && (rule.direction === "increase" || rule.direction === "decrease" || rule.direction === "hold") && (rule.confidence === "low" || rule.confidence === "medium" || rule.confidence === "high") && Array.isArray(rule.notes) && rule.notes.every((note) => typeof note === "string");
|
|
4868
5304
|
};
|
|
4869
5305
|
try {
|
|
4870
|
-
const raw = await
|
|
5306
|
+
const raw = await this.readStorageSecureFile(filePath);
|
|
4871
5307
|
const parsed = JSON.parse(raw);
|
|
4872
5308
|
const sourceWindow = parsed?.sourceWindow;
|
|
4873
5309
|
const eventCounts = parsed?.eventCounts;
|
|
@@ -4897,18 +5333,21 @@ ${memory.content}
|
|
|
4897
5333
|
...actionSummaries ? { actionSummaries } : {},
|
|
4898
5334
|
...ruleUpdates ? { ruleUpdates } : {}
|
|
4899
5335
|
};
|
|
4900
|
-
} catch {
|
|
5336
|
+
} catch (err) {
|
|
5337
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
5338
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
4901
5339
|
return null;
|
|
4902
5340
|
}
|
|
4903
5341
|
}
|
|
4904
5342
|
async writeIdentityAnchor(content) {
|
|
4905
5343
|
await this.ensureDirectories();
|
|
4906
|
-
await
|
|
5344
|
+
await this.writeStorageSecureFile(this.identityAnchorPath, content);
|
|
4907
5345
|
}
|
|
4908
5346
|
async readIdentityAnchor() {
|
|
4909
5347
|
try {
|
|
4910
|
-
return await
|
|
4911
|
-
} catch {
|
|
5348
|
+
return await this.readStorageSecureFile(this.identityAnchorPath);
|
|
5349
|
+
} catch (err) {
|
|
5350
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
4912
5351
|
return null;
|
|
4913
5352
|
}
|
|
4914
5353
|
}
|
|
@@ -4920,7 +5359,7 @@ ${memory.content}
|
|
|
4920
5359
|
const id = this.generateId("incident");
|
|
4921
5360
|
const incident = createContinuityIncidentRecord(id, input, nowIso);
|
|
4922
5361
|
const filePath = path4.join(this.identityIncidentsDir, `${date}-${id}.md`);
|
|
4923
|
-
await
|
|
5362
|
+
await this.writeStorageSecureFile(filePath, serializeContinuityIncident(incident));
|
|
4924
5363
|
return { ...incident, filePath };
|
|
4925
5364
|
}
|
|
4926
5365
|
async readContinuityIncidents(limit = 200, state = "all") {
|
|
@@ -4934,16 +5373,19 @@ ${memory.content}
|
|
|
4934
5373
|
if (incidents.length >= cappedLimit) break;
|
|
4935
5374
|
const filePath = path4.join(this.identityIncidentsDir, file);
|
|
4936
5375
|
try {
|
|
4937
|
-
const raw = await
|
|
5376
|
+
const raw = await this.readStorageSecureFile(filePath);
|
|
4938
5377
|
const parsed = parseContinuityIncident(raw);
|
|
4939
5378
|
if (!parsed) continue;
|
|
4940
5379
|
if (state !== "all" && parsed.state !== state) continue;
|
|
4941
5380
|
incidents.push({ ...parsed, filePath });
|
|
4942
|
-
} catch {
|
|
5381
|
+
} catch (err) {
|
|
5382
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
4943
5383
|
}
|
|
4944
5384
|
}
|
|
4945
5385
|
return incidents;
|
|
4946
|
-
} catch {
|
|
5386
|
+
} catch (err) {
|
|
5387
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
5388
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
4947
5389
|
return [];
|
|
4948
5390
|
}
|
|
4949
5391
|
}
|
|
@@ -4953,7 +5395,7 @@ ${memory.content}
|
|
|
4953
5395
|
if (!target || !directFilePath) return null;
|
|
4954
5396
|
if (target.state === "closed") return target;
|
|
4955
5397
|
const closed = closeContinuityIncidentRecord(target, closure, (/* @__PURE__ */ new Date()).toISOString());
|
|
4956
|
-
await
|
|
5398
|
+
await this.writeStorageSecureFile(directFilePath, serializeContinuityIncident(closed));
|
|
4957
5399
|
return { ...closed, filePath: directFilePath };
|
|
4958
5400
|
}
|
|
4959
5401
|
async writeIdentityAudit(period, key, content) {
|
|
@@ -4961,26 +5403,29 @@ ${memory.content}
|
|
|
4961
5403
|
const safeKey = this.sanitizeIdentityAuditKey(key);
|
|
4962
5404
|
const dir = period === "weekly" ? this.identityAuditsWeeklyDir : this.identityAuditsMonthlyDir;
|
|
4963
5405
|
const filePath = path4.join(dir, `${safeKey}.md`);
|
|
4964
|
-
await
|
|
5406
|
+
await this.writeStorageSecureFile(filePath, content);
|
|
4965
5407
|
return filePath;
|
|
4966
5408
|
}
|
|
4967
5409
|
async readIdentityAudit(period, key) {
|
|
4968
5410
|
try {
|
|
4969
5411
|
const safeKey = this.sanitizeIdentityAuditKey(key);
|
|
4970
5412
|
const dir = period === "weekly" ? this.identityAuditsWeeklyDir : this.identityAuditsMonthlyDir;
|
|
4971
|
-
return await
|
|
4972
|
-
} catch {
|
|
5413
|
+
return await this.readStorageSecureFile(path4.join(dir, `${safeKey}.md`));
|
|
5414
|
+
} catch (err) {
|
|
5415
|
+
if (err instanceof Error && err.message === "Invalid identity audit key") return null;
|
|
5416
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
4973
5417
|
return null;
|
|
4974
5418
|
}
|
|
4975
5419
|
}
|
|
4976
5420
|
async writeIdentityImprovementLoops(content) {
|
|
4977
5421
|
await this.ensureDirectories();
|
|
4978
|
-
await
|
|
5422
|
+
await this.writeStorageSecureFile(this.identityImprovementLoopsPath, content);
|
|
4979
5423
|
}
|
|
4980
5424
|
async readIdentityImprovementLoops() {
|
|
4981
5425
|
try {
|
|
4982
|
-
return await
|
|
4983
|
-
} catch {
|
|
5426
|
+
return await this.readStorageSecureFile(this.identityImprovementLoopsPath);
|
|
5427
|
+
} catch (err) {
|
|
5428
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
4984
5429
|
return null;
|
|
4985
5430
|
}
|
|
4986
5431
|
}
|
|
@@ -5017,10 +5462,11 @@ ${memory.content}
|
|
|
5017
5462
|
}
|
|
5018
5463
|
async readContinuityIncidentFile(filePath) {
|
|
5019
5464
|
try {
|
|
5020
|
-
const raw = await
|
|
5465
|
+
const raw = await this.readStorageSecureFile(filePath);
|
|
5021
5466
|
const parsed = parseContinuityIncident(raw);
|
|
5022
5467
|
return parsed ? { ...parsed, filePath } : null;
|
|
5023
|
-
} catch {
|
|
5468
|
+
} catch (err) {
|
|
5469
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
5024
5470
|
return null;
|
|
5025
5471
|
}
|
|
5026
5472
|
}
|
|
@@ -5088,7 +5534,7 @@ ${question}
|
|
|
5088
5534
|
for (const file of files) {
|
|
5089
5535
|
if (!file.endsWith(".md")) continue;
|
|
5090
5536
|
const filePath = path4.join(this.questionsDir, file);
|
|
5091
|
-
const raw = await
|
|
5537
|
+
const raw = await readMaybeEncryptedFile(filePath, this._secureStoreKey, this.baseDir);
|
|
5092
5538
|
const parsed = this.parseQuestionFile(raw, filePath);
|
|
5093
5539
|
if (parsed) {
|
|
5094
5540
|
questions.push(parsed);
|
|
@@ -5219,20 +5665,22 @@ ${reflection}
|
|
|
5219
5665
|
}
|
|
5220
5666
|
async readIdentityReflections() {
|
|
5221
5667
|
try {
|
|
5222
|
-
return await
|
|
5223
|
-
} catch {
|
|
5668
|
+
return await this.readStorageSecureFile(this.identityReflectionsPath);
|
|
5669
|
+
} catch (err) {
|
|
5670
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
5224
5671
|
return null;
|
|
5225
5672
|
}
|
|
5226
5673
|
}
|
|
5227
5674
|
async writeIdentityReflections(content) {
|
|
5228
5675
|
await mkdir2(this.identityDir, { recursive: true });
|
|
5229
|
-
await
|
|
5676
|
+
await this.writeStorageSecureFile(this.identityReflectionsPath, content);
|
|
5230
5677
|
}
|
|
5231
5678
|
async appendIdentityReflection(reflection) {
|
|
5232
5679
|
let existing = "";
|
|
5233
5680
|
try {
|
|
5234
|
-
existing = await
|
|
5235
|
-
} catch {
|
|
5681
|
+
existing = await this.readStorageSecureFile(this.identityReflectionsPath);
|
|
5682
|
+
} catch (err) {
|
|
5683
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
5236
5684
|
}
|
|
5237
5685
|
if (existing.length > _StorageManager.IDENTITY_MAX_BYTES) {
|
|
5238
5686
|
log.debug(
|
|
@@ -5257,7 +5705,7 @@ ${reflection}
|
|
|
5257
5705
|
${reflection}
|
|
5258
5706
|
`;
|
|
5259
5707
|
await mkdir2(this.identityDir, { recursive: true });
|
|
5260
|
-
await
|
|
5708
|
+
await this.writeStorageSecureFile(this.identityReflectionsPath, `${existing.trimEnd()}${section}`);
|
|
5261
5709
|
log.debug(`appended namespace-local reflection to ${this.identityReflectionsPath}`);
|
|
5262
5710
|
}
|
|
5263
5711
|
// ---------------------------------------------------------------------------
|
|
@@ -5271,9 +5719,11 @@ ${reflection}
|
|
|
5271
5719
|
const filePath = path4.join(this.entitiesDir, `${name}.md`);
|
|
5272
5720
|
let entity;
|
|
5273
5721
|
try {
|
|
5274
|
-
const content = await
|
|
5722
|
+
const content = await this.readStorageSecureFile(filePath);
|
|
5275
5723
|
entity = parseEntityFile(content, this.entitySchemas);
|
|
5276
|
-
} catch {
|
|
5724
|
+
} catch (err) {
|
|
5725
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
5726
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
5277
5727
|
log.debug(`addEntityRelationship: entity file ${name}.md not found`);
|
|
5278
5728
|
return;
|
|
5279
5729
|
}
|
|
@@ -5283,7 +5733,7 @@ ${reflection}
|
|
|
5283
5733
|
if (exists) return;
|
|
5284
5734
|
entity.relationships.push(rel);
|
|
5285
5735
|
entity.updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
5286
|
-
await
|
|
5736
|
+
await this.writeStorageSecureFile(filePath, serializeEntityFile(entity, this.entitySchemas));
|
|
5287
5737
|
this.invalidateKnowledgeIndexCache();
|
|
5288
5738
|
}
|
|
5289
5739
|
/**
|
|
@@ -5294,9 +5744,11 @@ ${reflection}
|
|
|
5294
5744
|
const filePath = path4.join(this.entitiesDir, `${name}.md`);
|
|
5295
5745
|
let entity;
|
|
5296
5746
|
try {
|
|
5297
|
-
const content = await
|
|
5747
|
+
const content = await this.readStorageSecureFile(filePath);
|
|
5298
5748
|
entity = parseEntityFile(content, this.entitySchemas);
|
|
5299
|
-
} catch {
|
|
5749
|
+
} catch (err) {
|
|
5750
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
5751
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
5300
5752
|
log.debug(`addEntityActivity: entity file ${name}.md not found`);
|
|
5301
5753
|
return;
|
|
5302
5754
|
}
|
|
@@ -5305,7 +5757,7 @@ ${reflection}
|
|
|
5305
5757
|
entity.activity = entity.activity.slice(0, maxEntries);
|
|
5306
5758
|
}
|
|
5307
5759
|
entity.updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
5308
|
-
await
|
|
5760
|
+
await this.writeStorageSecureFile(filePath, serializeEntityFile(entity, this.entitySchemas));
|
|
5309
5761
|
this.invalidateKnowledgeIndexCache();
|
|
5310
5762
|
}
|
|
5311
5763
|
/**
|
|
@@ -5315,16 +5767,18 @@ ${reflection}
|
|
|
5315
5767
|
const filePath = path4.join(this.entitiesDir, `${name}.md`);
|
|
5316
5768
|
let entity;
|
|
5317
5769
|
try {
|
|
5318
|
-
const content = await
|
|
5770
|
+
const content = await this.readStorageSecureFile(filePath);
|
|
5319
5771
|
entity = parseEntityFile(content, this.entitySchemas);
|
|
5320
|
-
} catch {
|
|
5772
|
+
} catch (err) {
|
|
5773
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
5774
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
5321
5775
|
log.debug(`addEntityAlias: entity file ${name}.md not found`);
|
|
5322
5776
|
return;
|
|
5323
5777
|
}
|
|
5324
5778
|
if (entity.aliases.includes(alias)) return;
|
|
5325
5779
|
entity.aliases.push(alias);
|
|
5326
5780
|
entity.updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
5327
|
-
await
|
|
5781
|
+
await this.writeStorageSecureFile(filePath, serializeEntityFile(entity, this.entitySchemas));
|
|
5328
5782
|
this.invalidateKnowledgeIndexCache();
|
|
5329
5783
|
}
|
|
5330
5784
|
/**
|
|
@@ -5334,9 +5788,11 @@ ${reflection}
|
|
|
5334
5788
|
const filePath = path4.join(this.entitiesDir, `${name}.md`);
|
|
5335
5789
|
let entity;
|
|
5336
5790
|
try {
|
|
5337
|
-
const content = await
|
|
5791
|
+
const content = await this.readStorageSecureFile(filePath);
|
|
5338
5792
|
entity = parseEntityFile(content, this.entitySchemas);
|
|
5339
|
-
} catch {
|
|
5793
|
+
} catch (err) {
|
|
5794
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
5795
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
5340
5796
|
log.debug(`updateEntitySynthesis: entity file ${name}.md not found`);
|
|
5341
5797
|
return;
|
|
5342
5798
|
}
|
|
@@ -5353,7 +5809,7 @@ ${reflection}
|
|
|
5353
5809
|
entity.synthesisStructuredFactDigest = synthesisStructuredFactDigest;
|
|
5354
5810
|
entity.synthesisVersion = Math.max(0, entity.synthesisVersion ?? 0) + (options.incrementVersion === false ? 0 : 1);
|
|
5355
5811
|
entity.updated = entityUpdatedAt;
|
|
5356
|
-
await
|
|
5812
|
+
await this.writeStorageSecureFile(filePath, serializeEntityFile(entity, this.entitySchemas));
|
|
5357
5813
|
await this.removeEntitySynthesisQueueEntries([
|
|
5358
5814
|
.../* @__PURE__ */ new Set([name, normalizeEntityName(entity.name, entity.type)])
|
|
5359
5815
|
]);
|
|
@@ -5368,9 +5824,11 @@ ${reflection}
|
|
|
5368
5824
|
let synthesisTimelineCount;
|
|
5369
5825
|
try {
|
|
5370
5826
|
const filePath = path4.join(this.entitiesDir, `${name}.md`);
|
|
5371
|
-
const content = await
|
|
5827
|
+
const content = await this.readStorageSecureFile(filePath);
|
|
5372
5828
|
synthesisTimelineCount = parseEntityFile(content, this.entitySchemas).timeline.length;
|
|
5373
|
-
} catch {
|
|
5829
|
+
} catch (err) {
|
|
5830
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
5831
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
5374
5832
|
synthesisTimelineCount = void 0;
|
|
5375
5833
|
}
|
|
5376
5834
|
await this.updateEntitySynthesis(name, summary, {
|
|
@@ -5381,10 +5839,12 @@ ${reflection}
|
|
|
5381
5839
|
}
|
|
5382
5840
|
async readEntitySynthesisQueue() {
|
|
5383
5841
|
try {
|
|
5384
|
-
const raw = await
|
|
5842
|
+
const raw = await this.readStorageSecureFile(this.entitySynthesisQueuePath);
|
|
5385
5843
|
const parsed = JSON.parse(raw);
|
|
5386
5844
|
return Array.isArray(parsed.entityNames) ? parsed.entityNames.filter((value) => typeof value === "string") : [];
|
|
5387
|
-
} catch {
|
|
5845
|
+
} catch (err) {
|
|
5846
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
5847
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
5388
5848
|
return [];
|
|
5389
5849
|
}
|
|
5390
5850
|
}
|
|
@@ -5406,7 +5866,7 @@ ${reflection}
|
|
|
5406
5866
|
return compareEntityTimestamps(rightTs, leftTs);
|
|
5407
5867
|
}).map(({ entityName }) => entityName);
|
|
5408
5868
|
await mkdir2(this.stateDir, { recursive: true });
|
|
5409
|
-
await
|
|
5869
|
+
await this.writeStorageSecureFile(
|
|
5410
5870
|
this.entitySynthesisQueuePath,
|
|
5411
5871
|
JSON.stringify(
|
|
5412
5872
|
{
|
|
@@ -5415,8 +5875,7 @@ ${reflection}
|
|
|
5415
5875
|
},
|
|
5416
5876
|
null,
|
|
5417
5877
|
2
|
|
5418
|
-
) + "\n"
|
|
5419
|
-
"utf-8"
|
|
5878
|
+
) + "\n"
|
|
5420
5879
|
);
|
|
5421
5880
|
return staleEntityNames;
|
|
5422
5881
|
}
|
|
@@ -5427,7 +5886,7 @@ ${reflection}
|
|
|
5427
5886
|
const removals = new Set(entityNames);
|
|
5428
5887
|
const nextQueue = queue.filter((name) => !removals.has(name));
|
|
5429
5888
|
await mkdir2(this.stateDir, { recursive: true });
|
|
5430
|
-
await
|
|
5889
|
+
await this.writeStorageSecureFile(
|
|
5431
5890
|
this.entitySynthesisQueuePath,
|
|
5432
5891
|
JSON.stringify(
|
|
5433
5892
|
{
|
|
@@ -5436,8 +5895,7 @@ ${reflection}
|
|
|
5436
5895
|
},
|
|
5437
5896
|
null,
|
|
5438
5897
|
2
|
|
5439
|
-
) + "\n"
|
|
5440
|
-
"utf-8"
|
|
5898
|
+
) + "\n"
|
|
5441
5899
|
);
|
|
5442
5900
|
}
|
|
5443
5901
|
async migrateEntityFilesToCompiledTruthTimeline() {
|
|
@@ -5448,7 +5906,7 @@ ${reflection}
|
|
|
5448
5906
|
if (!raw) continue;
|
|
5449
5907
|
const serialized = serializeEntityFile(parseEntityFile(raw, this.entitySchemas), this.entitySchemas);
|
|
5450
5908
|
if (raw.trimEnd() === serialized.trimEnd()) continue;
|
|
5451
|
-
await
|
|
5909
|
+
await this.writeStorageSecureFile(path4.join(this.entitiesDir, `${entityName}.md`), serialized);
|
|
5452
5910
|
migrated += 1;
|
|
5453
5911
|
}
|
|
5454
5912
|
if (migrated > 0) {
|
|
@@ -5470,7 +5928,8 @@ ${reflection}
|
|
|
5470
5928
|
async readAllEntityFiles() {
|
|
5471
5929
|
const currentVersion = this.getMemoryStatusVersion();
|
|
5472
5930
|
const schemaCacheKey = buildEntitySchemaCacheKey(this.entitySchemas);
|
|
5473
|
-
const
|
|
5931
|
+
const cacheKey = `${this.getEntityCacheSecureStoreKey()}\0${schemaCacheKey}`;
|
|
5932
|
+
const cached = getCachedEntities(this.baseDir, currentVersion, cacheKey);
|
|
5474
5933
|
if (cached) return cached;
|
|
5475
5934
|
try {
|
|
5476
5935
|
const entries = await readdir(this.entitiesDir);
|
|
@@ -5481,17 +5940,24 @@ ${reflection}
|
|
|
5481
5940
|
for (let i = 0; i < mdFiles.length; i += BATCH_SIZE) {
|
|
5482
5941
|
const batch = mdFiles.slice(i, i + BATCH_SIZE);
|
|
5483
5942
|
const results = await Promise.all(
|
|
5484
|
-
batch.map(
|
|
5485
|
-
|
|
5486
|
-
|
|
5943
|
+
batch.map(async (entry) => {
|
|
5944
|
+
try {
|
|
5945
|
+
return await this.readStorageSecureFile(path4.join(this.entitiesDir, entry));
|
|
5946
|
+
} catch (err) {
|
|
5947
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
5948
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
5949
|
+
return null;
|
|
5950
|
+
}
|
|
5951
|
+
})
|
|
5487
5952
|
);
|
|
5488
5953
|
for (const content of results) {
|
|
5489
5954
|
if (content !== null) entities.push(parseEntityFile(content, this.entitySchemas));
|
|
5490
5955
|
}
|
|
5491
5956
|
}
|
|
5492
|
-
setCachedEntities(this.baseDir, entities, currentVersion,
|
|
5957
|
+
setCachedEntities(this.baseDir, entities, currentVersion, cacheKey);
|
|
5493
5958
|
return entities;
|
|
5494
|
-
} catch {
|
|
5959
|
+
} catch (err) {
|
|
5960
|
+
if (err instanceof SecureStoreLockedError || !isErrnoCode(err, "ENOENT")) throw err;
|
|
5495
5961
|
return [];
|
|
5496
5962
|
}
|
|
5497
5963
|
}
|
|
@@ -5624,7 +6090,7 @@ ${rows.join("\n")}
|
|
|
5624
6090
|
for (const file of files) {
|
|
5625
6091
|
const filePath = path4.join(this.entitiesDir, file);
|
|
5626
6092
|
try {
|
|
5627
|
-
const content = await
|
|
6093
|
+
const content = await this.readStorageSecureFile(filePath);
|
|
5628
6094
|
const parsed = parseEntityFile(content, this.entitySchemas);
|
|
5629
6095
|
if (!mergedEntity.type || mergedEntity.type === "other") {
|
|
5630
6096
|
mergedEntity.type = parsed.type;
|
|
@@ -5701,7 +6167,9 @@ ${rows.join("\n")}
|
|
|
5701
6167
|
title: section.title,
|
|
5702
6168
|
lines: [...section.lines]
|
|
5703
6169
|
})));
|
|
5704
|
-
} catch {
|
|
6170
|
+
} catch (err) {
|
|
6171
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
6172
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
5705
6173
|
}
|
|
5706
6174
|
}
|
|
5707
6175
|
const timelineKeys = /* @__PURE__ */ new Set();
|
|
@@ -5752,7 +6220,7 @@ ${rows.join("\n")}
|
|
|
5752
6220
|
mergedEntity.created = mergedEntity.created || mergedEntity.updated || (/* @__PURE__ */ new Date()).toISOString();
|
|
5753
6221
|
mergedEntity.updated = mergedEntity.updated || (/* @__PURE__ */ new Date()).toISOString();
|
|
5754
6222
|
const canonicalPath = path4.join(this.entitiesDir, `${canonical}.md`);
|
|
5755
|
-
await
|
|
6223
|
+
await this.writeStorageSecureFile(canonicalPath, serializeEntityFile(mergedEntity, this.entitySchemas));
|
|
5756
6224
|
for (const file of files) {
|
|
5757
6225
|
const filePath = path4.join(this.entitiesDir, file);
|
|
5758
6226
|
if (filePath !== canonicalPath) {
|
|
@@ -5765,7 +6233,8 @@ ${rows.join("\n")}
|
|
|
5765
6233
|
}
|
|
5766
6234
|
}
|
|
5767
6235
|
}
|
|
5768
|
-
} catch {
|
|
6236
|
+
} catch (err) {
|
|
6237
|
+
if (err instanceof SecureStoreLockedError || !isErrnoCode(err, "ENOENT")) throw err;
|
|
5769
6238
|
}
|
|
5770
6239
|
return merged;
|
|
5771
6240
|
}
|
|
@@ -5819,7 +6288,7 @@ ${rows.join("\n")}
|
|
|
5819
6288
|
${memory.content}
|
|
5820
6289
|
`;
|
|
5821
6290
|
try {
|
|
5822
|
-
await
|
|
6291
|
+
await this.writeStorageSecureFile(memory.path, fileContent);
|
|
5823
6292
|
updated++;
|
|
5824
6293
|
} catch (err) {
|
|
5825
6294
|
log.debug(`failed to update access tracking for ${entry.memoryId}: ${err}`);
|
|
@@ -5960,7 +6429,7 @@ ${sanitized.text}
|
|
|
5960
6429
|
} else {
|
|
5961
6430
|
filePath = path4.join(this.factsDir, today, `${id}.md`);
|
|
5962
6431
|
}
|
|
5963
|
-
await
|
|
6432
|
+
await this.writeStorageSecureFile(filePath, fileContent);
|
|
5964
6433
|
log.debug(`wrote chunk ${id} (${chunkIndex + 1}/${chunkTotal}) to ${filePath}`);
|
|
5965
6434
|
return id;
|
|
5966
6435
|
}
|
|
@@ -5996,7 +6465,7 @@ ${sanitized.text}
|
|
|
5996
6465
|
${oldMemory.content}
|
|
5997
6466
|
`;
|
|
5998
6467
|
try {
|
|
5999
|
-
await
|
|
6468
|
+
await writeMaybeEncryptedFile(oldMemory.path, fileContent, this.resolveWriteKey(), {}, this.baseDir);
|
|
6000
6469
|
await this.appendGeneratedMemoryLifecycleEventFailOpen("storage.supersedeMemory", {
|
|
6001
6470
|
memoryId: oldMemoryId,
|
|
6002
6471
|
eventType: "superseded",
|
|
@@ -6035,7 +6504,7 @@ Reason: ${reason}`, {
|
|
|
6035
6504
|
async writeSummary(summary) {
|
|
6036
6505
|
await mkdir2(this.summariesDir, { recursive: true });
|
|
6037
6506
|
const filePath = path4.join(this.summariesDir, `${summary.id}.json`);
|
|
6038
|
-
await
|
|
6507
|
+
await this.writeStorageSecureFile(filePath, JSON.stringify(summary, null, 2));
|
|
6039
6508
|
log.debug(`wrote summary ${summary.id}`);
|
|
6040
6509
|
}
|
|
6041
6510
|
/**
|
|
@@ -6048,11 +6517,13 @@ Reason: ${reason}`, {
|
|
|
6048
6517
|
for (const file of files) {
|
|
6049
6518
|
if (!file.endsWith(".json")) continue;
|
|
6050
6519
|
const filePath = path4.join(this.summariesDir, file);
|
|
6051
|
-
const raw = await
|
|
6520
|
+
const raw = await this.readStorageSecureFile(filePath);
|
|
6052
6521
|
summaries.push(JSON.parse(raw));
|
|
6053
6522
|
}
|
|
6054
6523
|
return summaries;
|
|
6055
|
-
} catch {
|
|
6524
|
+
} catch (err) {
|
|
6525
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
6526
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
6056
6527
|
return [];
|
|
6057
6528
|
}
|
|
6058
6529
|
}
|
|
@@ -6078,7 +6549,7 @@ Reason: ${reason}`, {
|
|
|
6078
6549
|
${memory.content}
|
|
6079
6550
|
`;
|
|
6080
6551
|
try {
|
|
6081
|
-
await
|
|
6552
|
+
await this.writeStorageSecureFile(memory.path, fileContent);
|
|
6082
6553
|
await this.appendGeneratedMemoryLifecycleEventFailOpen("storage.archiveMemories", {
|
|
6083
6554
|
memoryId: id,
|
|
6084
6555
|
eventType: "archived",
|
|
@@ -6108,7 +6579,7 @@ ${memory.content}
|
|
|
6108
6579
|
async saveTopics(topics) {
|
|
6109
6580
|
const metaPath = path4.join(this.stateDir, "topics.json");
|
|
6110
6581
|
await mkdir2(this.stateDir, { recursive: true });
|
|
6111
|
-
await
|
|
6582
|
+
await this.writeStorageSecureFile(metaPath, JSON.stringify({ topics, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
|
|
6112
6583
|
log.debug(`saved ${topics.length} topic scores`);
|
|
6113
6584
|
}
|
|
6114
6585
|
/**
|
|
@@ -6117,9 +6588,11 @@ ${memory.content}
|
|
|
6117
6588
|
async loadTopics() {
|
|
6118
6589
|
const metaPath = path4.join(this.stateDir, "topics.json");
|
|
6119
6590
|
try {
|
|
6120
|
-
const raw = await
|
|
6591
|
+
const raw = await this.readStorageSecureFile(metaPath);
|
|
6121
6592
|
return JSON.parse(raw);
|
|
6122
|
-
} catch {
|
|
6593
|
+
} catch (err) {
|
|
6594
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
6595
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
6123
6596
|
return { topics: [], updatedAt: null };
|
|
6124
6597
|
}
|
|
6125
6598
|
}
|
|
@@ -6195,6 +6668,12 @@ export {
|
|
|
6195
6668
|
normalizeEntitySchemas,
|
|
6196
6669
|
resolveRequestedEntitySectionKeys,
|
|
6197
6670
|
sanitizeMemoryContent,
|
|
6671
|
+
MEMORY_LIFECYCLE_EVENT_SORT_ORDER,
|
|
6672
|
+
toMemoryPathRel,
|
|
6673
|
+
inferMemoryStatus,
|
|
6674
|
+
isActiveMemoryStatus,
|
|
6675
|
+
buildLifecycleEventsForMemory,
|
|
6676
|
+
sortMemoryLifecycleEvents,
|
|
6198
6677
|
hasCitationForTemplate,
|
|
6199
6678
|
attachCitation,
|
|
6200
6679
|
stripCitationForTemplate,
|
|
@@ -6206,7 +6685,12 @@ export {
|
|
|
6206
6685
|
setCachedQmdSearch,
|
|
6207
6686
|
lintWorkspaceFiles,
|
|
6208
6687
|
rotateMarkdownFileToArchive,
|
|
6688
|
+
DERIVED_FROM_MEMORY_ID_RE,
|
|
6209
6689
|
isConsolidationOperator,
|
|
6690
|
+
isSemanticConsolidationLlmOperator,
|
|
6691
|
+
RECALL_DISCLOSURE_LEVELS,
|
|
6692
|
+
DEFAULT_RECALL_DISCLOSURE,
|
|
6693
|
+
isRecallDisclosure,
|
|
6210
6694
|
confidenceTier,
|
|
6211
6695
|
openBetterSqlite3,
|
|
6212
6696
|
MEMORY_PROJECTION_SCHEMA_VERSION,
|
|
@@ -6218,14 +6702,8 @@ export {
|
|
|
6218
6702
|
readProjectedEntityMentions,
|
|
6219
6703
|
readProjectedNativeKnowledgeChunks,
|
|
6220
6704
|
readProjectedGovernanceRecord,
|
|
6221
|
-
MEMORY_LIFECYCLE_EVENT_SORT_ORDER,
|
|
6222
|
-
toMemoryPathRel,
|
|
6223
|
-
inferMemoryStatus,
|
|
6224
|
-
buildLifecycleEventsForMemory,
|
|
6225
|
-
sortMemoryLifecycleEvents,
|
|
6226
6705
|
normalizeProjectionPreview,
|
|
6227
6706
|
normalizeProjectionTags,
|
|
6228
|
-
parseContinuityIncident,
|
|
6229
6707
|
parseContinuityImprovementLoops,
|
|
6230
6708
|
normalizeEntityName,
|
|
6231
6709
|
ContentHashIndex,
|