@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.
Files changed (70) hide show
  1. package/dist/{calibration-674TDQNV.js → calibration-WCHOK6DX.js} +12 -4
  2. package/dist/capsule-cli-GBM3WPAM.js +33 -0
  3. package/dist/capsule-crypto-K3IRTKRH.js +17 -0
  4. package/dist/capsule-export-IXVERCQG.js +17 -0
  5. package/dist/capsule-import-IA6VIOPQ.js +16 -0
  6. package/dist/capsule-merge-IWOQ34KL.js +189 -0
  7. package/dist/{causal-chain-OKDZSDEB.js → causal-chain-WYN5QOPS.js} +3 -2
  8. package/dist/{causal-consolidation-5BEXLQV5.js → causal-consolidation-YI53C2AO.js} +16 -12
  9. package/dist/{causal-retrieval-3BKBXVXD.js → causal-retrieval-NZHQOZOE.js} +6 -5
  10. package/dist/{causal-trajectory-graph-RQIT37DN.js → causal-trajectory-graph-VBPE2WPM.js} +1 -1
  11. package/dist/chunk-37NKFWSO.js +233 -0
  12. package/dist/chunk-3G7FAF6S.js +60 -0
  13. package/dist/{chunk-Z7GRLVK3.js → chunk-3GUF7RQI.js} +235 -19
  14. package/dist/chunk-4G2XCSD2.js +186 -0
  15. package/dist/chunk-4LYQ4ONL.js +185 -0
  16. package/dist/chunk-6F6EKSVP.js +453 -0
  17. package/dist/chunk-6IWEAUN6.js +148 -0
  18. package/dist/{chunk-LN5UZQVG.js → chunk-6UFI73TJ.js} +5 -3
  19. package/dist/chunk-7OQEPGQF.js +529 -0
  20. package/dist/{chunk-JJSNPSCD.js → chunk-7UZNLMW5.js} +652 -174
  21. package/dist/chunk-B52XADV3.js +244 -0
  22. package/dist/chunk-BU5KJVWF.js +78 -0
  23. package/dist/chunk-CDAZGIGT.js +720 -0
  24. package/dist/chunk-CXM7EBAO.js +289 -0
  25. package/dist/{chunk-HCFFXBLV.js → chunk-EXDYWXMB.js} +6 -1861
  26. package/dist/chunk-FGTYFLL5.js +274 -0
  27. package/dist/chunk-FQRSVYY4.js +110 -0
  28. package/dist/chunk-HRGFO6AW.js +349 -0
  29. package/dist/chunk-I6B2W2IY.js +47 -0
  30. package/dist/chunk-JZBOXOUC.js +259 -0
  31. package/dist/chunk-L6I4MQKO.js +227 -0
  32. package/dist/chunk-LLUROTZJ.js +328 -0
  33. package/dist/chunk-MBIFE6SA.js +250 -0
  34. package/dist/chunk-NKVIN6RD.js +118 -0
  35. package/dist/chunk-OAE7AQ6R.js +1832 -0
  36. package/dist/chunk-OEI7GLV2.js +17 -0
  37. package/dist/chunk-RKR6PTPA.js +308 -0
  38. package/dist/{chunk-7TENHBV2.js → chunk-RQCTMECT.js} +10 -48
  39. package/dist/chunk-SSFTU6LP.js +182 -0
  40. package/dist/{chunk-BXTMZDRT.js → chunk-SVSQAG6M.js} +7 -5
  41. package/dist/{chunk-S2ISS4AH.js → chunk-TILAJIJR.js} +10 -10
  42. package/dist/chunk-TLVIQLB4.js +874 -0
  43. package/dist/{chunk-YHH3SXKD.js → chunk-WPINX4MF.js} +1 -59
  44. package/dist/chunk-YGGGUTG3.js +125 -0
  45. package/dist/cipher-VHAFCG7Z.js +27 -0
  46. package/dist/dreams-ledger-3I52ISYR.js +285 -0
  47. package/dist/{engine-65C2J63X.js → engine-BIYI3P4J.js} +7 -2
  48. package/dist/{fallback-llm-LVK5PDIM.js → fallback-llm-WCWNGIQ3.js} +2 -1
  49. package/dist/first-start-migration-I24M2JEE.js +258 -0
  50. package/dist/forget-NI4RBDPB.js +68 -0
  51. package/dist/fs-utils-PZRI2HDZ.js +29 -0
  52. package/dist/graph-edge-decay-5CVKWBYH.js +203 -0
  53. package/dist/index.js +10654 -3067
  54. package/dist/kdf-H5B23ZM2.js +25 -0
  55. package/dist/memory-governance-SJ5DGRB3.js +25 -0
  56. package/dist/metadata-JAGIWHEA.js +20 -0
  57. package/dist/migrate-from-identity-anchor-N3354WMP.js +7 -0
  58. package/dist/path-5LCUBAAZ.js +8 -0
  59. package/dist/peers-JF2I6RCR.js +43 -0
  60. package/dist/purge-XN2VSPZ2.js +204 -0
  61. package/dist/secure-store-A4NGCNXV.js +155 -0
  62. package/dist/state-PVISYXRH.js +7 -0
  63. package/dist/state-store-LP5BO6SF.js +15 -0
  64. package/dist/{storage-DM4ZGOCN.js → storage-PTQ2H2YJ.js} +3 -1
  65. package/dist/tier-stats-IZNW66NC.js +147 -0
  66. package/dist/trace-NJESSGH7.js +289 -0
  67. package/dist/tui-MGK2LYJY.js +12 -0
  68. package/dist/types-R4DO7AKM.js +30 -0
  69. package/openclaw.plugin.json +519 -4
  70. package/package.json +2 -2
@@ -0,0 +1,258 @@
1
+ import {
2
+ TierMigrationExecutor
3
+ } from "./chunk-BU5KJVWF.js";
4
+ import {
5
+ applyUtilityPromotionRuntimePolicy,
6
+ decideTierTransition,
7
+ loadUtilityRuntimeValues
8
+ } from "./chunk-7OQEPGQF.js";
9
+ import "./chunk-4G2XCSD2.js";
10
+ import "./chunk-3G7FAF6S.js";
11
+ import "./chunk-5LE4HTVL.js";
12
+ import "./chunk-MLKGABMK.js";
13
+
14
+ // ../remnic-core/src/maintenance/first-start-migration.ts
15
+ import path from "path";
16
+ import { access, mkdir, readFile, unlink, writeFile } from "fs/promises";
17
+ var FIRST_START_DEMOTION_CAP = 50;
18
+ var LIFECYCLE_INIT_DONE_MARKER = ".lifecycle-init-done";
19
+ var LIFECYCLE_QMD_REFRESH_PENDING_MARKER = ".lifecycle-qmd-refresh-pending";
20
+ function markerPath(memoryDir) {
21
+ return path.join(memoryDir, "state", LIFECYCLE_INIT_DONE_MARKER);
22
+ }
23
+ function qmdRefreshPendingPath(memoryDir) {
24
+ return path.join(memoryDir, "state", LIFECYCLE_QMD_REFRESH_PENDING_MARKER);
25
+ }
26
+ async function markerExists(memoryDir) {
27
+ try {
28
+ await access(markerPath(memoryDir));
29
+ return true;
30
+ } catch {
31
+ return false;
32
+ }
33
+ }
34
+ async function writeMarker(memoryDir, now) {
35
+ const p = markerPath(memoryDir);
36
+ await mkdir(path.dirname(p), { recursive: true });
37
+ await writeFile(p, JSON.stringify({ createdAt: now.toISOString() }), "utf-8");
38
+ }
39
+ async function readQmdRefreshPending(memoryDir) {
40
+ try {
41
+ const raw = await readFile(qmdRefreshPendingPath(memoryDir), "utf-8");
42
+ const parsed = JSON.parse(raw);
43
+ if (parsed && typeof parsed === "object" && typeof parsed.createdAt === "string" && typeof parsed.collection === "string" && parsed.collection.length > 0) {
44
+ return parsed;
45
+ }
46
+ return null;
47
+ } catch {
48
+ return null;
49
+ }
50
+ }
51
+ async function pathExists(p) {
52
+ try {
53
+ await access(p);
54
+ return true;
55
+ } catch {
56
+ return false;
57
+ }
58
+ }
59
+ async function writeQmdRefreshPending(memoryDir, now, collection) {
60
+ const p = qmdRefreshPendingPath(memoryDir);
61
+ await mkdir(path.dirname(p), { recursive: true });
62
+ await writeFile(p, JSON.stringify({ createdAt: now.toISOString(), collection }), "utf-8");
63
+ }
64
+ async function clearQmdRefreshPending(memoryDir) {
65
+ await unlink(qmdRefreshPendingPath(memoryDir)).catch(() => {
66
+ });
67
+ }
68
+ async function buildTierRoutingPolicy(config) {
69
+ const basePolicy = {
70
+ enabled: config.qmdTierMigrationEnabled,
71
+ demotionMinAgeDays: config.qmdTierDemotionMinAgeDays,
72
+ demotionValueThreshold: config.qmdTierDemotionValueThreshold,
73
+ promotionValueThreshold: config.qmdTierPromotionValueThreshold
74
+ };
75
+ const runtime = await loadUtilityRuntimeValues({
76
+ memoryDir: config.memoryDir,
77
+ memoryUtilityLearningEnabled: config.memoryUtilityLearningEnabled,
78
+ promotionByOutcomeEnabled: config.promotionByOutcomeEnabled
79
+ });
80
+ return applyUtilityPromotionRuntimePolicy(basePolicy, runtime);
81
+ }
82
+ async function refreshQmdCollection(qmd, collection, signal) {
83
+ if (typeof qmd.updateCollectionStrict === "function") {
84
+ await qmd.updateCollectionStrict(collection, { signal });
85
+ return;
86
+ }
87
+ await qmd.updateCollection(collection, { signal });
88
+ }
89
+ async function runFirstStartMigration(options) {
90
+ const {
91
+ storage,
92
+ config,
93
+ demotionCap = FIRST_START_DEMOTION_CAP,
94
+ dryRun = false,
95
+ qmd,
96
+ signal
97
+ } = options;
98
+ const now = (options.now ?? (() => /* @__PURE__ */ new Date()))();
99
+ const abortedResult = (candidateCount2 = 0, demotedCount2 = 0, failureCount2 = 0) => ({
100
+ skipped: candidateCount2 === 0 && demotedCount2 === 0 && failureCount2 === 0,
101
+ skipReason: candidateCount2 === 0 && demotedCount2 === 0 && failureCount2 === 0 ? "aborted" : void 0,
102
+ dryRun,
103
+ candidateCount: candidateCount2,
104
+ demotedCount: demotedCount2,
105
+ failureCount: failureCount2,
106
+ cappedAt: demotionCap
107
+ });
108
+ if (signal?.aborted) {
109
+ return abortedResult();
110
+ }
111
+ if (!config.lifecyclePolicyEnabled) {
112
+ return {
113
+ skipped: true,
114
+ skipReason: "lifecyclePolicyEnabled is false",
115
+ dryRun,
116
+ candidateCount: 0,
117
+ demotedCount: 0,
118
+ failureCount: 0,
119
+ cappedAt: demotionCap
120
+ };
121
+ }
122
+ if (!config.qmdTierMigrationEnabled) {
123
+ return {
124
+ skipped: true,
125
+ skipReason: "qmdTierMigrationEnabled is false",
126
+ dryRun,
127
+ candidateCount: 0,
128
+ demotedCount: 0,
129
+ failureCount: 0,
130
+ cappedAt: demotionCap
131
+ };
132
+ }
133
+ if (await markerExists(config.memoryDir)) {
134
+ return {
135
+ skipped: true,
136
+ skipReason: "lifecycle-init-done marker already present",
137
+ dryRun,
138
+ candidateCount: 0,
139
+ demotedCount: 0,
140
+ failureCount: 0,
141
+ cappedAt: demotionCap
142
+ };
143
+ }
144
+ if (signal?.aborted) {
145
+ return abortedResult();
146
+ }
147
+ const policy = await buildTierRoutingPolicy(config);
148
+ if (signal?.aborted) {
149
+ return abortedResult();
150
+ }
151
+ const hotMemories = await storage.readAllMemories();
152
+ if (signal?.aborted) {
153
+ return abortedResult();
154
+ }
155
+ const demotionCandidates = hotMemories.filter((m) => {
156
+ const decision = decideTierTransition(m, "hot", policy, now);
157
+ return decision.changed && decision.nextTier === "cold";
158
+ });
159
+ const candidateCount = demotionCandidates.length;
160
+ const batch = demotionCandidates.slice(0, demotionCap);
161
+ if (dryRun) {
162
+ return {
163
+ skipped: false,
164
+ dryRun: true,
165
+ candidateCount,
166
+ demotedCount: 0,
167
+ failureCount: 0,
168
+ cappedAt: demotionCap
169
+ };
170
+ }
171
+ let demotedCount = 0;
172
+ let failureCount = 0;
173
+ let qmdRefreshPendingForRun = false;
174
+ const executor = qmd ? new TierMigrationExecutor({
175
+ storage,
176
+ qmd,
177
+ hotCollection: options.hotCollection ?? config.qmdCollection ?? "openclaw-engram",
178
+ coldCollection: options.coldCollection ?? config.qmdColdCollection ?? "openclaw-engram-cold"
179
+ }) : null;
180
+ const coldCollection = options.coldCollection ?? config.qmdColdCollection ?? "openclaw-engram-cold";
181
+ for (const memory of batch) {
182
+ if (signal?.aborted) {
183
+ return abortedResult(candidateCount, demotedCount, failureCount);
184
+ }
185
+ try {
186
+ if (executor) {
187
+ await executor.migrateMemory({
188
+ memory,
189
+ fromTier: "hot",
190
+ toTier: "cold",
191
+ reason: "first-start-lifecycle-migration"
192
+ });
193
+ } else {
194
+ await storage.migrateMemoryToTier(memory, "cold");
195
+ }
196
+ demotedCount += 1;
197
+ } catch {
198
+ const targetPath = storage.buildTierMemoryPath(memory, "cold");
199
+ const [targetExists, sourceExists] = await Promise.all([
200
+ pathExists(targetPath),
201
+ pathExists(memory.path)
202
+ ]);
203
+ const movedToCold = targetExists && !sourceExists;
204
+ if (movedToCold) {
205
+ if (!qmd) {
206
+ demotedCount += 1;
207
+ continue;
208
+ }
209
+ try {
210
+ await refreshQmdCollection(qmd, coldCollection, signal);
211
+ demotedCount += 1;
212
+ continue;
213
+ } catch {
214
+ qmdRefreshPendingForRun = true;
215
+ try {
216
+ await writeQmdRefreshPending(config.memoryDir, now, coldCollection);
217
+ } catch {
218
+ }
219
+ demotedCount += 1;
220
+ continue;
221
+ }
222
+ }
223
+ failureCount += 1;
224
+ }
225
+ }
226
+ if (signal?.aborted) {
227
+ return abortedResult(candidateCount, demotedCount, failureCount);
228
+ }
229
+ const persistedQmdRefreshPending = qmd ? await readQmdRefreshPending(config.memoryDir) : null;
230
+ const hasPersistedQmdRefreshPending = persistedQmdRefreshPending?.collection === coldCollection;
231
+ if (qmd && persistedQmdRefreshPending && persistedQmdRefreshPending.collection !== coldCollection) {
232
+ failureCount += 1;
233
+ }
234
+ if (qmd && (qmdRefreshPendingForRun || hasPersistedQmdRefreshPending)) {
235
+ try {
236
+ await refreshQmdCollection(qmd, coldCollection, signal);
237
+ await clearQmdRefreshPending(config.memoryDir);
238
+ } catch {
239
+ failureCount += 1;
240
+ }
241
+ }
242
+ if (failureCount === 0) {
243
+ await writeMarker(config.memoryDir, now);
244
+ }
245
+ return {
246
+ skipped: false,
247
+ dryRun: false,
248
+ candidateCount,
249
+ demotedCount,
250
+ failureCount,
251
+ cappedAt: demotionCap
252
+ };
253
+ }
254
+ export {
255
+ FIRST_START_DEMOTION_CAP,
256
+ LIFECYCLE_INIT_DONE_MARKER,
257
+ runFirstStartMigration
258
+ };
@@ -0,0 +1,68 @@
1
+ import "./chunk-MLKGABMK.js";
2
+
3
+ // ../remnic-core/src/maintenance/forget.ts
4
+ var ForgetMemoryNotFoundError = class extends Error {
5
+ code = "memory_not_found";
6
+ constructor(id) {
7
+ super(`memory not found: ${id}`);
8
+ this.name = "ForgetMemoryNotFoundError";
9
+ }
10
+ };
11
+ var ForgetMemoryAlreadyForgottenError = class extends Error {
12
+ code = "already_forgotten";
13
+ constructor(id, forgottenAt) {
14
+ super(`memory ${id} was already forgotten at ${forgottenAt}`);
15
+ this.name = "ForgetMemoryAlreadyForgottenError";
16
+ }
17
+ };
18
+ async function forgetMemory(storage, request) {
19
+ const id = typeof request.id === "string" ? request.id.trim() : "";
20
+ if (id.length === 0) {
21
+ throw new Error("forget: memory id is required and must be non-empty");
22
+ }
23
+ const memory = await findMemoryById(storage, id);
24
+ if (!memory) {
25
+ throw new ForgetMemoryNotFoundError(id);
26
+ }
27
+ if (memory.frontmatter.status === "forgotten") {
28
+ throw new ForgetMemoryAlreadyForgottenError(
29
+ id,
30
+ memory.frontmatter.forgottenAt ?? "(unknown)"
31
+ );
32
+ }
33
+ const priorStatus = typeof memory.frontmatter.status === "string" ? memory.frontmatter.status : "active";
34
+ const now = (request.now ?? (() => /* @__PURE__ */ new Date()))();
35
+ const forgottenAt = now.toISOString();
36
+ const reason = typeof request.reason === "string" ? request.reason.trim() : "";
37
+ await storage.writeMemoryFrontmatter(memory, {
38
+ status: "forgotten",
39
+ forgottenAt,
40
+ forgottenReason: reason.length > 0 ? reason : void 0,
41
+ updated: forgottenAt
42
+ }, {
43
+ actor: "remnic-forget",
44
+ reasonCode: "operator_forget"
45
+ });
46
+ return {
47
+ id,
48
+ path: memory.path,
49
+ priorStatus,
50
+ forgottenAt,
51
+ reason
52
+ };
53
+ }
54
+ async function findMemoryById(storage, id) {
55
+ const hot = await storage.readAllMemories();
56
+ const hotMatch = hot.find((m) => m.frontmatter.id === id);
57
+ if (hotMatch) return hotMatch;
58
+ const archived = await storage.readArchivedMemories();
59
+ const archivedMatch = archived.find((m) => m.frontmatter.id === id);
60
+ if (archivedMatch) return archivedMatch;
61
+ const cold = await storage.readAllColdMemories();
62
+ return cold.find((m) => m.frontmatter.id === id) ?? null;
63
+ }
64
+ export {
65
+ ForgetMemoryAlreadyForgottenError,
66
+ ForgetMemoryNotFoundError,
67
+ forgetMemory
68
+ };
@@ -0,0 +1,29 @@
1
+ import {
2
+ assertIsDirectoryNotSymlink,
3
+ assertRealpathInsideRoot,
4
+ ensureDirExists,
5
+ fileExists,
6
+ fromPosixRelPath,
7
+ isPathInsideRoot,
8
+ listFilesRecursive,
9
+ readJsonFile,
10
+ sha256File,
11
+ sha256String,
12
+ toPosixRelPath,
13
+ writeJsonFile
14
+ } from "./chunk-NKVIN6RD.js";
15
+ import "./chunk-MLKGABMK.js";
16
+ export {
17
+ assertIsDirectoryNotSymlink,
18
+ assertRealpathInsideRoot,
19
+ ensureDirExists,
20
+ fileExists,
21
+ fromPosixRelPath,
22
+ isPathInsideRoot,
23
+ listFilesRecursive,
24
+ readJsonFile,
25
+ sha256File,
26
+ sha256String,
27
+ toPosixRelPath,
28
+ writeJsonFile
29
+ };
@@ -0,0 +1,203 @@
1
+ import {
2
+ isSafeRouteNamespace
3
+ } from "./chunk-FQRSVYY4.js";
4
+ import {
5
+ DEFAULT_DECAY_FLOOR,
6
+ DEFAULT_DECAY_PER_WINDOW,
7
+ DEFAULT_DECAY_WINDOW_MS,
8
+ decayEdgeConfidence,
9
+ graphFilePath,
10
+ graphsDir,
11
+ readEdgeConfidence,
12
+ readEdgesStrict,
13
+ withGraphWriteLock
14
+ } from "./chunk-3GUF7RQI.js";
15
+ import "./chunk-MLKGABMK.js";
16
+
17
+ // ../remnic-core/src/maintenance/graph-edge-decay.ts
18
+ import {
19
+ mkdir,
20
+ readdir,
21
+ readFile,
22
+ rename,
23
+ writeFile
24
+ } from "fs/promises";
25
+ import path from "path";
26
+ var DEFAULT_VISIBILITY_THRESHOLD = 0.2;
27
+ var GRAPH_TYPES = ["entity", "time", "causal"];
28
+ function graphEdgeDecayStatusPath(memoryDir) {
29
+ return path.join(memoryDir, "state", "graph-edge-decay-status.json");
30
+ }
31
+ async function writeJsonlAtomic(filePath, edges) {
32
+ await mkdir(path.dirname(filePath), { recursive: true });
33
+ const body = edges.length === 0 ? "" : edges.map((e) => JSON.stringify(e)).join("\n") + "\n";
34
+ const tempPath = `${filePath}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
35
+ await writeFile(tempPath, body, "utf-8");
36
+ await rename(tempPath, filePath);
37
+ }
38
+ async function runGraphEdgeDecayMaintenance(memoryDir, options = {}) {
39
+ const startedAt = Date.now();
40
+ const ranAt = options.now ?? (/* @__PURE__ */ new Date()).toISOString();
41
+ const windowMs = options.windowMs ?? DEFAULT_DECAY_WINDOW_MS;
42
+ const perWindow = options.perWindow ?? DEFAULT_DECAY_PER_WINDOW;
43
+ const floor = options.floor ?? DEFAULT_DECAY_FLOOR;
44
+ const visibilityThreshold = typeof options.visibilityThreshold === "number" && Number.isFinite(options.visibilityThreshold) ? Math.max(0, Math.min(1, options.visibilityThreshold)) : DEFAULT_VISIBILITY_THRESHOLD;
45
+ const dryRun = options.dryRun === true;
46
+ await mkdir(graphsDir(memoryDir), { recursive: true });
47
+ const perType = [];
48
+ const dropByLabel = /* @__PURE__ */ new Map();
49
+ let edgesTotal = 0;
50
+ let edgesDecayed = 0;
51
+ let edgesBelowVisibilityThreshold = 0;
52
+ for (const type of GRAPH_TYPES) {
53
+ const filePath = graphFilePath(memoryDir, type);
54
+ const {
55
+ typeDecayed,
56
+ typeBelow,
57
+ typeTotal
58
+ } = await withGraphWriteLock(filePath, async () => {
59
+ const edges = await readEdgesStrict(memoryDir, type);
60
+ const updated = new Array(edges.length);
61
+ let localDecayed = 0;
62
+ let localBelow = 0;
63
+ let localChangedAny = false;
64
+ for (let i = 0; i < edges.length; i += 1) {
65
+ const edge = edges[i];
66
+ const before = readEdgeConfidence(edge);
67
+ const decayed = decayEdgeConfidence(edge, ranAt, { windowMs, perWindow, floor });
68
+ const after = readEdgeConfidence(decayed);
69
+ if (after < before) {
70
+ localDecayed += 1;
71
+ const drop = before - after;
72
+ const label = typeof edge.label === "string" ? edge.label : "";
73
+ if (label.length > 0) {
74
+ const prev = dropByLabel.get(label);
75
+ if (prev) {
76
+ prev.totalDrop += drop;
77
+ prev.edgeCount += 1;
78
+ } else {
79
+ dropByLabel.set(label, { totalDrop: drop, edgeCount: 1 });
80
+ }
81
+ }
82
+ }
83
+ if (after < visibilityThreshold) {
84
+ localBelow += 1;
85
+ }
86
+ updated[i] = decayed;
87
+ if (decayed.confidence !== edge.confidence || decayed.lastReinforcedAt !== edge.lastReinforcedAt) {
88
+ localChangedAny = true;
89
+ }
90
+ }
91
+ if (!dryRun && localChangedAny && edges.length > 0) {
92
+ await writeJsonlAtomic(filePath, updated);
93
+ }
94
+ return {
95
+ typeDecayed: localDecayed,
96
+ typeBelow: localBelow,
97
+ typeTotal: edges.length
98
+ };
99
+ });
100
+ edgesDecayed += typeDecayed;
101
+ edgesBelowVisibilityThreshold += typeBelow;
102
+ edgesTotal += typeTotal;
103
+ perType.push({
104
+ type,
105
+ edgesTotal: typeTotal,
106
+ edgesDecayed: typeDecayed,
107
+ edgesBelowVisibilityThreshold: typeBelow
108
+ });
109
+ }
110
+ const topDecayedEntities = [...dropByLabel.entries()].map(([label, agg]) => ({ label, totalDrop: agg.totalDrop, edgeCount: agg.edgeCount })).sort((a, b) => {
111
+ if (b.totalDrop !== a.totalDrop) return b.totalDrop - a.totalDrop;
112
+ return a.label.localeCompare(b.label);
113
+ }).slice(0, 5);
114
+ const telemetry = {
115
+ ranAt,
116
+ durationMs: Date.now() - startedAt,
117
+ edgesTotal,
118
+ edgesDecayed,
119
+ edgesBelowVisibilityThreshold,
120
+ topDecayedEntities,
121
+ perType,
122
+ windowMs,
123
+ perWindow,
124
+ floor,
125
+ visibilityThreshold
126
+ };
127
+ if (!dryRun) {
128
+ await persistTelemetry(memoryDir, telemetry);
129
+ }
130
+ return telemetry;
131
+ }
132
+ async function persistTelemetry(memoryDir, telemetry) {
133
+ const statusPath = graphEdgeDecayStatusPath(memoryDir);
134
+ await mkdir(path.dirname(statusPath), { recursive: true });
135
+ const tempPath = `${statusPath}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
136
+ try {
137
+ await writeFile(tempPath, JSON.stringify(telemetry, null, 2) + "\n", "utf-8");
138
+ await rename(tempPath, statusPath);
139
+ } catch {
140
+ }
141
+ }
142
+ async function readGraphEdgeDecayStatus(memoryDir) {
143
+ try {
144
+ const raw = await readFile(graphEdgeDecayStatusPath(memoryDir), "utf-8");
145
+ const parsed = JSON.parse(raw);
146
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
147
+ return null;
148
+ }
149
+ return parsed;
150
+ } catch {
151
+ return null;
152
+ }
153
+ }
154
+ async function discoverGraphNamespaceRoots(memoryDir, options) {
155
+ const seen = /* @__PURE__ */ new Map();
156
+ seen.set(options.defaultNamespace, memoryDir);
157
+ if (!options.namespacesEnabled) {
158
+ return [...seen.entries()].map(([namespace, storageRoot]) => ({ namespace, storageRoot }));
159
+ }
160
+ const namespacesDir = path.join(memoryDir, "namespaces");
161
+ let entries;
162
+ try {
163
+ entries = await readdir(namespacesDir, { withFileTypes: true });
164
+ } catch {
165
+ return [...seen.entries()].map(([namespace, storageRoot]) => ({ namespace, storageRoot }));
166
+ }
167
+ for (const entry of entries) {
168
+ if (!entry.isDirectory()) continue;
169
+ if (!isSafeRouteNamespace(entry.name)) continue;
170
+ const root = path.join(namespacesDir, entry.name);
171
+ seen.set(entry.name, root);
172
+ }
173
+ return [...seen.entries()].map(([namespace, storageRoot]) => ({ namespace, storageRoot }));
174
+ }
175
+ async function runGraphEdgeDecayMaintenanceAcrossNamespaces(memoryDir, options) {
176
+ const { namespacesEnabled, defaultNamespace, ...decayOptions } = options;
177
+ const roots = await discoverGraphNamespaceRoots(memoryDir, {
178
+ namespacesEnabled,
179
+ defaultNamespace
180
+ });
181
+ const results = [];
182
+ for (const { namespace, storageRoot } of roots) {
183
+ try {
184
+ const telemetry = await runGraphEdgeDecayMaintenance(storageRoot, decayOptions);
185
+ results.push({ namespace, storageRoot, telemetry });
186
+ } catch (err) {
187
+ results.push({
188
+ namespace,
189
+ storageRoot,
190
+ error: err instanceof Error ? err.message : String(err)
191
+ });
192
+ }
193
+ }
194
+ return results;
195
+ }
196
+ export {
197
+ DEFAULT_VISIBILITY_THRESHOLD,
198
+ discoverGraphNamespaceRoots,
199
+ graphEdgeDecayStatusPath,
200
+ readGraphEdgeDecayStatus,
201
+ runGraphEdgeDecayMaintenance,
202
+ runGraphEdgeDecayMaintenanceAcrossNamespaces
203
+ };