@remnic/plugin-openclaw 1.0.10 → 1.0.11

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 (69) hide show
  1. package/dist/{calibration-674TDQNV.js → calibration-WCHOK6DX.js} +12 -4
  2. package/dist/capsule-cli-TFKLAG3S.js +329 -0
  3. package/dist/capsule-crypto-K3IRTKRH.js +17 -0
  4. package/dist/capsule-export-CVA3CKUQ.js +265 -0
  5. package/dist/capsule-import-CFX7BY5W.js +16 -0
  6. package/dist/capsule-merge-7RVOHJK3.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-JD6KJJH6.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-3I7RHWYT.js +214 -0
  15. package/dist/chunk-4G2XCSD2.js +186 -0
  16. package/dist/chunk-6IWEAUN6.js +148 -0
  17. package/dist/{chunk-LN5UZQVG.js → chunk-6UFI73TJ.js} +5 -3
  18. package/dist/chunk-7OQEPGQF.js +529 -0
  19. package/dist/chunk-B52XADV3.js +244 -0
  20. package/dist/chunk-BU5KJVWF.js +78 -0
  21. package/dist/chunk-CXM7EBAO.js +289 -0
  22. package/dist/chunk-ETJZRIAM.js +227 -0
  23. package/dist/chunk-FQRSVYY4.js +110 -0
  24. package/dist/chunk-HRGFO6AW.js +349 -0
  25. package/dist/chunk-I6B2W2IY.js +47 -0
  26. package/dist/chunk-JZBOXOUC.js +259 -0
  27. package/dist/chunk-K7EUBNDD.js +185 -0
  28. package/dist/chunk-L4PRBB2A.js +1860 -0
  29. package/dist/chunk-MBIFE6SA.js +250 -0
  30. package/dist/chunk-N7EOZY6F.js +400 -0
  31. package/dist/chunk-NKVIN6RD.js +118 -0
  32. package/dist/chunk-OEI7GLV2.js +17 -0
  33. package/dist/{chunk-S2ISS4AH.js → chunk-P3DIW2SD.js} +10 -10
  34. package/dist/{chunk-7TENHBV2.js → chunk-RQCTMECT.js} +10 -48
  35. package/dist/chunk-SSFTU6LP.js +182 -0
  36. package/dist/{chunk-BXTMZDRT.js → chunk-SVSQAG6M.js} +7 -5
  37. package/dist/chunk-TLVIQLB4.js +874 -0
  38. package/dist/{chunk-JJSNPSCD.js → chunk-TNH24SF6.js} +352 -50
  39. package/dist/chunk-TVKKIS53.js +720 -0
  40. package/dist/{chunk-YHH3SXKD.js → chunk-WPINX4MF.js} +1 -59
  41. package/dist/{chunk-HCFFXBLV.js → chunk-XMSDA5WA.js} +5 -1861
  42. package/dist/chunk-YGGGUTG3.js +125 -0
  43. package/dist/chunk-YGXXBRV7.js +10 -0
  44. package/dist/cipher-VHAFCG7Z.js +27 -0
  45. package/dist/dreams-ledger-3I52ISYR.js +285 -0
  46. package/dist/{engine-65C2J63X.js → engine-VMTFKFGO.js} +5 -2
  47. package/dist/{fallback-llm-LVK5PDIM.js → fallback-llm-WCWNGIQ3.js} +2 -1
  48. package/dist/first-start-migration-I24M2JEE.js +258 -0
  49. package/dist/forget-NI4RBDPB.js +68 -0
  50. package/dist/fs-utils-PZRI2HDZ.js +29 -0
  51. package/dist/graph-edge-decay-5CVKWBYH.js +203 -0
  52. package/dist/index.js +9775 -2900
  53. package/dist/kdf-H5B23ZM2.js +25 -0
  54. package/dist/memory-governance-DWGFV4FX.js +25 -0
  55. package/dist/metadata-JAGIWHEA.js +20 -0
  56. package/dist/migrate-from-identity-anchor-N3354WMP.js +7 -0
  57. package/dist/path-5LCUBAAZ.js +8 -0
  58. package/dist/peers-JF2I6RCR.js +43 -0
  59. package/dist/purge-XN2VSPZ2.js +204 -0
  60. package/dist/secure-store-FWJ7LBPH.js +149 -0
  61. package/dist/state-PVISYXRH.js +7 -0
  62. package/dist/state-store-LP5BO6SF.js +15 -0
  63. package/dist/{storage-DM4ZGOCN.js → storage-T2OGFUF4.js} +3 -1
  64. package/dist/tier-stats-IZNW66NC.js +147 -0
  65. package/dist/trace-NJESSGH7.js +289 -0
  66. package/dist/tui-MGK2LYJY.js +12 -0
  67. package/dist/types-H5R5D3WF.js +30 -0
  68. package/openclaw.plugin.json +519 -4
  69. package/package.json +1 -1
@@ -1,6 +1,84 @@
1
+ // ../remnic-core/src/graph-edge-reinforcement.ts
2
+ var CONFIDENCE_CEILING = 1;
3
+ var DEFAULT_DECAY_WINDOW_MS = 90 * 24 * 60 * 60 * 1e3;
4
+ var DEFAULT_DECAY_FLOOR = 0.1;
5
+ var DEFAULT_DECAY_PER_WINDOW = 0.1;
6
+ function readEdgeConfidence(edge) {
7
+ const raw = edge.confidence;
8
+ if (raw === void 0 || raw === null || !Number.isFinite(raw)) {
9
+ return CONFIDENCE_CEILING;
10
+ }
11
+ if (raw < 0) return 0;
12
+ if (raw > CONFIDENCE_CEILING) return CONFIDENCE_CEILING;
13
+ return raw;
14
+ }
15
+ function readLastReinforcedAt(edge) {
16
+ return edge.lastReinforcedAt ?? edge.ts;
17
+ }
18
+ function decayEdgeConfidence(edge, now, opts = {}) {
19
+ const windowMs = opts.windowMs ?? DEFAULT_DECAY_WINDOW_MS;
20
+ const perWindow = opts.perWindow ?? DEFAULT_DECAY_PER_WINDOW;
21
+ const floor = opts.floor ?? DEFAULT_DECAY_FLOOR;
22
+ if (!(windowMs > 0) || !Number.isFinite(perWindow) || perWindow < 0 || !Number.isFinite(floor)) {
23
+ return { ...edge, confidence: readEdgeConfidence(edge) };
24
+ }
25
+ const safeFloor = Math.min(CONFIDENCE_CEILING, Math.max(0, floor));
26
+ const nowMs = Date.parse(now);
27
+ const refMs = Date.parse(readLastReinforcedAt(edge));
28
+ if (!Number.isFinite(nowMs) || !Number.isFinite(refMs)) {
29
+ return { ...edge, confidence: readEdgeConfidence(edge) };
30
+ }
31
+ const age = nowMs - refMs;
32
+ const current = readEdgeConfidence(edge);
33
+ if (age <= windowMs) {
34
+ return { ...edge, confidence: current };
35
+ }
36
+ const windowsPast = Math.ceil((age - windowMs) / windowMs);
37
+ const decayed = current - perWindow * windowsPast;
38
+ const lowerBound = Math.min(safeFloor, current);
39
+ const next = Math.max(lowerBound, Math.min(current, decayed));
40
+ const newRefMs = refMs + windowsPast * windowMs;
41
+ const newRef = new Date(newRefMs).toISOString();
42
+ return { ...edge, confidence: next, lastReinforcedAt: newRef };
43
+ }
44
+
1
45
  // ../remnic-core/src/graph.ts
2
46
  import { mkdir, appendFile, readFile } from "fs/promises";
3
47
  import * as path from "path";
48
+
49
+ // ../remnic-core/src/graph-events.ts
50
+ import { EventEmitter } from "events";
51
+ var buses = /* @__PURE__ */ new Map();
52
+ function getGraphEventBus(memoryDir) {
53
+ let bus = buses.get(memoryDir);
54
+ if (!bus) {
55
+ bus = new EventEmitter();
56
+ bus.setMaxListeners(200);
57
+ buses.set(memoryDir, bus);
58
+ }
59
+ return bus;
60
+ }
61
+ function emitGraphEvent(memoryDir, type, payload) {
62
+ const event = {
63
+ type,
64
+ memoryDir,
65
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
66
+ payload
67
+ };
68
+ const bus = getGraphEventBus(memoryDir);
69
+ try {
70
+ bus.emit("graph-event", event);
71
+ } catch {
72
+ }
73
+ }
74
+ function subscribeGraphEvents(memoryDir, listener) {
75
+ const bus = getGraphEventBus(memoryDir);
76
+ bus.on("graph-event", listener);
77
+ return () => bus.off("graph-event", listener);
78
+ }
79
+
80
+ // ../remnic-core/src/graph.ts
81
+ var DEFAULT_GRAPH_TRAVERSAL_CONFIDENCE_FLOOR = 0.2;
4
82
  var CAUSAL_PHRASES = [
5
83
  "as a result",
6
84
  "led to",
@@ -18,29 +96,71 @@ function graphFilePath(memoryDir, type) {
18
96
  async function ensureGraphsDir(memoryDir) {
19
97
  await mkdir(graphsDir(memoryDir), { recursive: true });
20
98
  }
99
+ var graphWriteLocks = /* @__PURE__ */ new Map();
100
+ function withGraphWriteLock(filePath, fn) {
101
+ const prev = graphWriteLocks.get(filePath) ?? Promise.resolve();
102
+ const next = prev.then(fn, fn);
103
+ graphWriteLocks.set(
104
+ filePath,
105
+ next.then(
106
+ () => void 0,
107
+ () => void 0
108
+ )
109
+ );
110
+ return next;
111
+ }
21
112
  async function appendEdge(memoryDir, edge) {
22
113
  await ensureGraphsDir(memoryDir);
114
+ const filePath = graphFilePath(memoryDir, edge.type);
23
115
  const line = JSON.stringify(edge) + "\n";
24
- await appendFile(graphFilePath(memoryDir, edge.type), line, "utf8");
116
+ await withGraphWriteLock(filePath, async () => {
117
+ await appendFile(filePath, line, "utf8");
118
+ });
119
+ emitGraphEvent(memoryDir, "edge-added", {
120
+ source: edge.from,
121
+ target: edge.to,
122
+ kind: edge.type,
123
+ weight: edge.weight,
124
+ label: edge.label,
125
+ confidence: typeof edge.confidence === "number" ? edge.confidence : 1
126
+ });
127
+ }
128
+ function isNodeError(err) {
129
+ return typeof err === "object" && err !== null && "code" in err;
130
+ }
131
+ function parseEdgesJsonl(raw) {
132
+ const edges = [];
133
+ for (const line of raw.split("\n")) {
134
+ const trimmed = line.trim();
135
+ if (!trimmed) continue;
136
+ try {
137
+ edges.push(JSON.parse(trimmed));
138
+ } catch {
139
+ }
140
+ }
141
+ return edges;
25
142
  }
26
143
  async function readEdges(memoryDir, type) {
27
144
  const filePath = graphFilePath(memoryDir, type);
28
145
  try {
29
146
  const raw = await readFile(filePath, "utf8");
30
- const edges = [];
31
- for (const line of raw.split("\n")) {
32
- const trimmed = line.trim();
33
- if (!trimmed) continue;
34
- try {
35
- edges.push(JSON.parse(trimmed));
36
- } catch {
37
- }
38
- }
39
- return edges;
147
+ return parseEdgesJsonl(raw);
40
148
  } catch {
41
149
  return [];
42
150
  }
43
151
  }
152
+ async function readEdgesStrict(memoryDir, type) {
153
+ const filePath = graphFilePath(memoryDir, type);
154
+ try {
155
+ const raw = await readFile(filePath, "utf8");
156
+ return parseEdgesJsonl(raw);
157
+ } catch (err) {
158
+ if (isNodeError(err) && err.code === "ENOENT") {
159
+ return [];
160
+ }
161
+ throw err;
162
+ }
163
+ }
44
164
  async function readAllEdges(memoryDir, config) {
45
165
  const parts = await Promise.all([
46
166
  config.entityGraphEnabled ? readEdges(memoryDir, "entity") : Promise.resolve([]),
@@ -238,21 +358,38 @@ var GraphIndex = class _GraphIndex {
238
358
  * Spreading activation BFS (SYNAPSE-inspired).
239
359
  *
240
360
  * Starting from `seeds`, traverse the combined graph for up to `maxSteps` hops.
241
- * Each candidate gets an activation score = edge.weight × decay^hop.
242
- * Returns top-N candidate paths sorted by descending activation score.
361
+ * Each candidate gets an activation score = edge.weight × edgeConfidence × decay^hop.
243
362
  *
244
- * @param seeds - initial memory paths to expand from (e.g. QMD top results)
363
+ * Issue #681 PR 3/3 confidence-aware traversal:
364
+ * - Each edge's `weight` is multiplied by its `confidence` (legacy edges
365
+ * missing `confidence` are treated as 1.0, preserving prior behavior).
366
+ * - Edges with `confidence < graphTraversalConfidenceFloor` are pruned and
367
+ * contribute neither activation nor downstream neighbors.
368
+ * - When `graphTraversalPageRankIterations > 0`, an additional PageRank-
369
+ * style refinement pass redistributes activation along confidence-weighted
370
+ * edges, sharpening the ranking among multi-hop candidates.
371
+ * - Per-result provenance includes the highest-confidence edge that landed
372
+ * on each candidate, so the X-ray surface can attribute pruning and
373
+ * ranking decisions back to specific edges.
374
+ *
375
+ * @param seeds - initial memory paths to expand from (e.g. QMD top results)
245
376
  * @param maxSteps - max BFS hops (from config: maxGraphTraversalSteps)
246
- * @returns Array of {path, score} sorted descending, not including seed paths
377
+ * @returns Array of {path, score, edgeConfidence, ...} sorted descending, not including seed paths
247
378
  */
248
- async spreadingActivation(seeds, maxSteps) {
379
+ async spreadingActivation(seeds, maxSteps, opts) {
249
380
  if (!this.cfg.multiGraphMemoryEnabled) return [];
250
381
  const steps = maxSteps ?? this.cfg.maxGraphTraversalSteps;
251
382
  const decay = this.cfg.graphActivationDecay;
383
+ const floor = opts?.includeLowConfidence === true ? 0 : clampConfidenceFloor(this.cfg.graphTraversalConfidenceFloor);
384
+ const iterations = clampPageRankIterations(
385
+ this.cfg.graphTraversalPageRankIterations
386
+ );
252
387
  try {
253
388
  const allEdges = await this.loadEdgesCached();
254
389
  const adj = /* @__PURE__ */ new Map();
255
390
  for (const edge of allEdges) {
391
+ const conf = readEdgeConfidence(edge);
392
+ if (conf < floor) continue;
256
393
  if (!adj.has(edge.from)) adj.set(edge.from, []);
257
394
  adj.get(edge.from).push(edge);
258
395
  if (edge.type !== "causal") {
@@ -271,7 +408,9 @@ var GraphIndex = class _GraphIndex {
271
408
  const edges = adj.get(node) ?? [];
272
409
  for (const edge of edges) {
273
410
  const neighbor = edge.to === node ? edge.from : edge.to;
274
- const score = edge.weight * Math.pow(decay, hop + 1);
411
+ const conf = readEdgeConfidence(edge);
412
+ if (conf < floor) continue;
413
+ const score = edge.weight * conf * Math.pow(decay, hop + 1);
275
414
  if (!seedSet.has(neighbor)) {
276
415
  const existing = scores.get(neighbor) ?? 0;
277
416
  scores.set(neighbor, existing + score);
@@ -281,7 +420,8 @@ var GraphIndex = class _GraphIndex {
281
420
  seed: sourceSeed,
282
421
  hopDepth: hop + 1,
283
422
  decayedWeight: score,
284
- graphType: edge.type
423
+ graphType: edge.type,
424
+ edgeConfidence: conf
285
425
  });
286
426
  }
287
427
  }
@@ -291,6 +431,13 @@ var GraphIndex = class _GraphIndex {
291
431
  }
292
432
  }
293
433
  }
434
+ if (iterations > 0 && scores.size > 1) {
435
+ applyPageRankRefinement(scores, adj, {
436
+ iterations,
437
+ floor,
438
+ damping: 0.85
439
+ });
440
+ }
294
441
  if (this.cfg.graphLateralInhibitionEnabled && scores.size > 1) {
295
442
  const inhibited = applyLateralInhibition(scores, {
296
443
  beta: this.cfg.graphLateralInhibitionBeta,
@@ -306,7 +453,8 @@ var GraphIndex = class _GraphIndex {
306
453
  seed: provenance.get(p)?.seed ?? "",
307
454
  hopDepth: provenance.get(p)?.hopDepth ?? 0,
308
455
  decayedWeight: provenance.get(p)?.decayedWeight ?? 0,
309
- graphType: provenance.get(p)?.graphType ?? "entity"
456
+ graphType: provenance.get(p)?.graphType ?? "entity",
457
+ edgeConfidence: provenance.get(p)?.edgeConfidence ?? 1
310
458
  })).sort((a, b) => b.score - a.score);
311
459
  } catch (err) {
312
460
  const { log } = await import("./logger-TNOKCH7X.js");
@@ -315,6 +463,63 @@ var GraphIndex = class _GraphIndex {
315
463
  }
316
464
  }
317
465
  };
466
+ function clampConfidenceFloor(raw) {
467
+ if (typeof raw !== "number" || !Number.isFinite(raw)) {
468
+ return DEFAULT_GRAPH_TRAVERSAL_CONFIDENCE_FLOOR;
469
+ }
470
+ if (raw < 0) return 0;
471
+ if (raw > 1) return 1;
472
+ return raw;
473
+ }
474
+ function clampPageRankIterations(raw) {
475
+ if (typeof raw !== "number" || !Number.isFinite(raw)) return 0;
476
+ if (raw <= 0) return 0;
477
+ return Math.floor(raw);
478
+ }
479
+ function applyPageRankRefinement(scores, adj, opts) {
480
+ const { iterations, floor, damping } = opts;
481
+ if (iterations <= 0 || scores.size === 0) return;
482
+ const safeDamping = Math.min(1, Math.max(0, damping));
483
+ const eligible = (edge, fromNode) => {
484
+ if (readEdgeConfidence(edge) < floor) return false;
485
+ const neighbor = edge.to === fromNode ? edge.from : edge.to;
486
+ return scores.has(neighbor);
487
+ };
488
+ const outboundTotal = /* @__PURE__ */ new Map();
489
+ for (const [node, edges] of adj.entries()) {
490
+ if (!scores.has(node)) continue;
491
+ let sum = 0;
492
+ for (const edge of edges) {
493
+ if (!eligible(edge, node)) continue;
494
+ sum += readEdgeConfidence(edge) * edge.weight;
495
+ }
496
+ if (sum > 0) outboundTotal.set(node, sum);
497
+ }
498
+ for (let i = 0; i < iterations; i += 1) {
499
+ const next = /* @__PURE__ */ new Map();
500
+ for (const [node, score] of scores) {
501
+ next.set(node, (1 - safeDamping) * score);
502
+ }
503
+ for (const [node, score] of scores) {
504
+ const outEdges = adj.get(node);
505
+ const total = outboundTotal.get(node);
506
+ if (!outEdges || outEdges.length === 0 || !total || total <= 0) {
507
+ next.set(node, (next.get(node) ?? 0) + safeDamping * score);
508
+ continue;
509
+ }
510
+ for (const edge of outEdges) {
511
+ if (!eligible(edge, node)) continue;
512
+ const conf = readEdgeConfidence(edge);
513
+ const neighbor = edge.to === node ? edge.from : edge.to;
514
+ const flow = safeDamping * score * (conf * edge.weight / total);
515
+ next.set(neighbor, (next.get(neighbor) ?? 0) + flow);
516
+ }
517
+ }
518
+ for (const [node, score] of next) {
519
+ scores.set(node, score);
520
+ }
521
+ }
522
+ }
318
523
  function applyLateralInhibition(scores, opts) {
319
524
  const { beta, topM } = opts;
320
525
  if (beta === 0 || topM === 0) return new Map(scores);
@@ -334,7 +539,18 @@ function applyLateralInhibition(scores, opts) {
334
539
  }
335
540
 
336
541
  export {
542
+ DEFAULT_DECAY_WINDOW_MS,
543
+ DEFAULT_DECAY_FLOOR,
544
+ DEFAULT_DECAY_PER_WINDOW,
545
+ readEdgeConfidence,
546
+ decayEdgeConfidence,
547
+ subscribeGraphEvents,
548
+ graphsDir,
549
+ graphFilePath,
550
+ withGraphWriteLock,
337
551
  appendEdge,
552
+ readEdgesStrict,
553
+ readAllEdges,
338
554
  analyzeGraphHealth,
339
555
  GraphIndex
340
556
  };
@@ -0,0 +1,214 @@
1
+ import {
2
+ generateSalt,
3
+ open,
4
+ seal
5
+ } from "./chunk-YGGGUTG3.js";
6
+
7
+ // ../remnic-core/src/secure-store/secure-fs.ts
8
+ import { lstat, mkdir, readFile, readdir, rename, unlink, writeFile } from "fs/promises";
9
+ import path from "path";
10
+ var SecureStoreLockedError = class extends Error {
11
+ constructor(message = "secure-store is locked \u2014 run `remnic secure-store unlock` to decrypt") {
12
+ super(message);
13
+ this.name = "SecureStoreLockedError";
14
+ }
15
+ };
16
+ var SecureStoreDecryptError = class extends Error {
17
+ constructor(message = "secure-store decryption failed \u2014 wrong key or tampered ciphertext") {
18
+ super(message);
19
+ this.name = "SecureStoreDecryptError";
20
+ }
21
+ };
22
+ var MAGIC_BYTES = Buffer.from("REMNIC-ENC", "ascii");
23
+ var FILE_FORMAT_VERSION = 1;
24
+ var FILE_FORMAT_FLAGS = 0;
25
+ var MAGIC_HEADER_SIZE = MAGIC_BYTES.length + 2;
26
+ function isEncryptedFile(buf) {
27
+ if (buf.length < MAGIC_HEADER_SIZE) return false;
28
+ const b = Buffer.isBuffer(buf) ? buf : Buffer.from(buf);
29
+ return b.subarray(0, MAGIC_BYTES.length).equals(MAGIC_BYTES);
30
+ }
31
+ function encryptFileBody(plain, key, aad) {
32
+ const plainBuf = typeof plain === "string" ? Buffer.from(plain, "utf8") : plain;
33
+ const salt = generateSalt();
34
+ const envelope = seal(key, salt, plainBuf, aad ? { aad } : {});
35
+ const header = Buffer.alloc(MAGIC_HEADER_SIZE);
36
+ MAGIC_BYTES.copy(header, 0);
37
+ header.writeUInt8(FILE_FORMAT_VERSION, MAGIC_BYTES.length);
38
+ header.writeUInt8(FILE_FORMAT_FLAGS, MAGIC_BYTES.length + 1);
39
+ return Buffer.concat([header, envelope]);
40
+ }
41
+ function decryptFileBody(buf, key, aad) {
42
+ if (!isEncryptedFile(buf)) {
43
+ throw new Error("decryptFileBody: buffer does not start with REMNIC-ENC magic header");
44
+ }
45
+ const version = buf.readUInt8(MAGIC_BYTES.length);
46
+ if (version !== FILE_FORMAT_VERSION) {
47
+ throw new Error(
48
+ `decryptFileBody: unsupported file format version ${version} (this build supports ${FILE_FORMAT_VERSION})`
49
+ );
50
+ }
51
+ const flags = buf.readUInt8(MAGIC_BYTES.length + 1);
52
+ if (flags !== FILE_FORMAT_FLAGS) {
53
+ throw new Error(`decryptFileBody: unknown flags byte 0x${flags.toString(16).padStart(2, "0")}`);
54
+ }
55
+ const envelope = buf.subarray(MAGIC_HEADER_SIZE);
56
+ try {
57
+ return open(key, envelope, aad ? { aad } : {});
58
+ } catch (err) {
59
+ const msg = err instanceof Error ? err.message : String(err);
60
+ throw new SecureStoreDecryptError(
61
+ `secure-store decryption failed: ${msg}`
62
+ );
63
+ }
64
+ }
65
+ function filePathAad(filePath, memoryDir) {
66
+ let rel = filePath;
67
+ if (memoryDir && path.isAbsolute(filePath)) {
68
+ rel = path.relative(memoryDir, filePath);
69
+ }
70
+ return Buffer.from(rel, "utf8");
71
+ }
72
+ async function readMaybeEncryptedFile(filePath, key, memoryDir) {
73
+ const buf = await readFile(filePath);
74
+ if (!isEncryptedFile(buf)) {
75
+ return buf.toString("utf8");
76
+ }
77
+ if (key === null) {
78
+ throw new SecureStoreLockedError(
79
+ `secure-store is locked \u2014 cannot read encrypted file at ${filePath}. Run \`remnic secure-store unlock\` to decrypt.`
80
+ );
81
+ }
82
+ const aad = filePathAad(filePath, memoryDir);
83
+ const plain = decryptFileBody(buf, key, aad);
84
+ return plain.toString("utf8");
85
+ }
86
+ async function writeMaybeEncryptedFile(filePath, content, key, options = {}, memoryDir) {
87
+ const { mode = 384, atomic = true } = options;
88
+ await mkdir(path.dirname(filePath), { recursive: true });
89
+ let data;
90
+ if (key !== null) {
91
+ const aad = filePathAad(filePath, memoryDir);
92
+ data = encryptFileBody(content, key, aad);
93
+ } else {
94
+ data = content;
95
+ }
96
+ if (atomic) {
97
+ const tempPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
98
+ try {
99
+ await writeFile(tempPath, data, { mode });
100
+ await rename(tempPath, filePath);
101
+ } catch (err) {
102
+ try {
103
+ await unlink(tempPath);
104
+ } catch {
105
+ }
106
+ throw err;
107
+ }
108
+ } else {
109
+ await writeFile(filePath, data, { mode });
110
+ }
111
+ }
112
+ async function migrateMemoryDirToEncrypted(dir, key, onBeforeEncrypt) {
113
+ const result = { encrypted: 0, skipped: 0, errors: [] };
114
+ const mdFiles = await collectMdFiles(dir);
115
+ for (const filePath of mdFiles) {
116
+ try {
117
+ const buf = await readFile(filePath);
118
+ if (isEncryptedFile(buf)) {
119
+ result.skipped++;
120
+ continue;
121
+ }
122
+ if (onBeforeEncrypt) {
123
+ try {
124
+ await onBeforeEncrypt(filePath);
125
+ } catch {
126
+ }
127
+ }
128
+ const content = buf.toString("utf8");
129
+ const aad = filePathAad(filePath, dir);
130
+ const encrypted = encryptFileBody(content, key, aad);
131
+ const tempPath = `${filePath}.enc-tmp-${process.pid}-${Date.now()}`;
132
+ try {
133
+ await writeFile(tempPath, encrypted, { mode: 384 });
134
+ await rename(tempPath, filePath);
135
+ result.encrypted++;
136
+ } catch (writeErr) {
137
+ try {
138
+ const { unlink: unlink2 } = await import("fs/promises");
139
+ await unlink2(tempPath);
140
+ } catch {
141
+ }
142
+ throw writeErr;
143
+ }
144
+ } catch (err) {
145
+ result.errors.push({
146
+ filePath,
147
+ error: err instanceof Error ? err.message : String(err)
148
+ });
149
+ }
150
+ }
151
+ return result;
152
+ }
153
+ async function collectMdFiles(dir, rootDir = dir) {
154
+ const results = [];
155
+ let names;
156
+ try {
157
+ names = await readdir(dir, { encoding: "utf8" });
158
+ } catch {
159
+ return results;
160
+ }
161
+ for (const name of names) {
162
+ if (name.startsWith(".secure-store")) continue;
163
+ const full = path.join(dir, name);
164
+ let isDir = false;
165
+ let isFile = false;
166
+ try {
167
+ const s = await lstat(full);
168
+ if (s.isSymbolicLink()) continue;
169
+ isDir = s.isDirectory();
170
+ isFile = s.isFile();
171
+ } catch {
172
+ continue;
173
+ }
174
+ if (isDir) {
175
+ const sub = await collectMdFiles(full, rootDir);
176
+ results.push(...sub);
177
+ } else if (isFile && name.endsWith(".md") && isEncryptableStoragePath(full, rootDir)) {
178
+ results.push(full);
179
+ }
180
+ }
181
+ return results;
182
+ }
183
+ function isEncryptableStoragePath(filePath, rootDir) {
184
+ const rel = path.relative(rootDir, filePath);
185
+ if (rel === "" || rel.startsWith("..") || path.isAbsolute(rel)) return false;
186
+ const normalized = rel.split(path.sep).join("/");
187
+ if (normalized === "profile.md") return true;
188
+ const firstSegment = normalized.split("/", 1)[0];
189
+ return ENCRYPTABLE_STORAGE_ROOTS.has(firstSegment);
190
+ }
191
+ var ENCRYPTABLE_STORAGE_ROOTS = /* @__PURE__ */ new Set([
192
+ "facts",
193
+ "corrections",
194
+ "procedures",
195
+ "reasoning-traces",
196
+ "artifacts",
197
+ "archive"
198
+ ]);
199
+
200
+ export {
201
+ SecureStoreLockedError,
202
+ SecureStoreDecryptError,
203
+ MAGIC_BYTES,
204
+ FILE_FORMAT_VERSION,
205
+ FILE_FORMAT_FLAGS,
206
+ MAGIC_HEADER_SIZE,
207
+ isEncryptedFile,
208
+ encryptFileBody,
209
+ decryptFileBody,
210
+ filePathAad,
211
+ readMaybeEncryptedFile,
212
+ writeMaybeEncryptedFile,
213
+ migrateMemoryDirToEncrypted
214
+ };