@remnic/core 9.3.517 → 9.3.519

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 (80) hide show
  1. package/dist/access-cli.js +13 -13
  2. package/dist/access-http.js +8 -8
  3. package/dist/access-mcp.js +7 -7
  4. package/dist/access-schema.d.ts +4 -4
  5. package/dist/access-schema.js +2 -2
  6. package/dist/access-service.js +5 -5
  7. package/dist/briefing.js +2 -2
  8. package/dist/causal-consolidation.js +3 -3
  9. package/dist/{chunk-6F6BXB7A.js → chunk-67VP6I47.js} +2 -2
  10. package/dist/{chunk-6XSPNR6L.js → chunk-6HF5VUBQ.js} +2 -2
  11. package/dist/{chunk-V2RCP53Q.js → chunk-6QKWLP4V.js} +2 -2
  12. package/dist/{chunk-XNLXAWHX.js → chunk-7TVK7E3R.js} +2 -2
  13. package/dist/{chunk-23UORJ4S.js → chunk-BE5XAWSA.js} +2 -2
  14. package/dist/{chunk-5UHVGNZD.js → chunk-DGDQZ3JW.js} +2 -2
  15. package/dist/{chunk-2AN2L4NL.js → chunk-DKOIMCGB.js} +2 -2
  16. package/dist/{chunk-5V456VRV.js → chunk-HX5XV3GL.js} +5 -5
  17. package/dist/{chunk-UIPDNLXA.js → chunk-K4LDMTTY.js} +118 -13
  18. package/dist/chunk-K4LDMTTY.js.map +1 -0
  19. package/dist/{chunk-YDMVYYD2.js → chunk-KAUXI453.js} +8 -8
  20. package/dist/{chunk-FCOQXV3T.js → chunk-KCLX6LOV.js} +12 -12
  21. package/dist/{chunk-ZEY4KYRQ.js → chunk-LKCE3NPZ.js} +13 -2
  22. package/dist/chunk-LKCE3NPZ.js.map +1 -0
  23. package/dist/{chunk-CHBI22MI.js → chunk-O2HWL47E.js} +2 -2
  24. package/dist/{chunk-TTGZV5R3.js → chunk-OHZXPJK7.js} +4 -4
  25. package/dist/{chunk-FPGE5NVO.js → chunk-OMHRQTOD.js} +2 -2
  26. package/dist/{chunk-E62SBGQ3.js → chunk-PYGKYEDU.js} +3 -3
  27. package/dist/{chunk-IQ3OI2RR.js → chunk-QK2G4EYH.js} +2 -2
  28. package/dist/{chunk-YNXOKMJP.js → chunk-RB7LF7K7.js} +4 -4
  29. package/dist/{chunk-YHV3KRKS.js → chunk-TKUQG2PE.js} +2 -2
  30. package/dist/{chunk-HC6EKOID.js → chunk-UMZRCQFC.js} +5 -5
  31. package/dist/{chunk-LJBOVCQG.js → chunk-XI5V7WSJ.js} +2 -2
  32. package/dist/{chunk-6BR7L222.js → chunk-ZQLC75BF.js} +2 -2
  33. package/dist/cli.js +17 -17
  34. package/dist/compounding/engine.js +2 -2
  35. package/dist/connectors/codex-materialize-runner.js +2 -2
  36. package/dist/connectors/index.js +2 -2
  37. package/dist/entity-retrieval.js +2 -2
  38. package/dist/index.js +22 -22
  39. package/dist/maintenance/memory-governance.js +2 -2
  40. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +2 -2
  41. package/dist/maintenance/rebuild-memory-projection.js +3 -3
  42. package/dist/namespaces/migrate.js +3 -3
  43. package/dist/namespaces/storage.js +2 -2
  44. package/dist/offline-sync.js +1 -1
  45. package/dist/operator-toolkit.js +5 -5
  46. package/dist/orchestrator.js +9 -9
  47. package/dist/schemas.d.ts +22 -22
  48. package/dist/semantic-consolidation.js +3 -3
  49. package/dist/semantic-rule-promotion.js +2 -2
  50. package/dist/semantic-rule-verifier.js +2 -2
  51. package/dist/storage.d.ts +10 -0
  52. package/dist/storage.js +1 -1
  53. package/dist/transfer/types.d.ts +12 -12
  54. package/dist/verified-recall.js +2 -2
  55. package/package.json +1 -1
  56. package/src/offline-sync.test.ts +84 -0
  57. package/src/offline-sync.ts +17 -1
  58. package/src/storage.ts +156 -12
  59. package/dist/chunk-UIPDNLXA.js.map +0 -1
  60. package/dist/chunk-ZEY4KYRQ.js.map +0 -1
  61. /package/dist/{chunk-6F6BXB7A.js.map → chunk-67VP6I47.js.map} +0 -0
  62. /package/dist/{chunk-6XSPNR6L.js.map → chunk-6HF5VUBQ.js.map} +0 -0
  63. /package/dist/{chunk-V2RCP53Q.js.map → chunk-6QKWLP4V.js.map} +0 -0
  64. /package/dist/{chunk-XNLXAWHX.js.map → chunk-7TVK7E3R.js.map} +0 -0
  65. /package/dist/{chunk-23UORJ4S.js.map → chunk-BE5XAWSA.js.map} +0 -0
  66. /package/dist/{chunk-5UHVGNZD.js.map → chunk-DGDQZ3JW.js.map} +0 -0
  67. /package/dist/{chunk-2AN2L4NL.js.map → chunk-DKOIMCGB.js.map} +0 -0
  68. /package/dist/{chunk-5V456VRV.js.map → chunk-HX5XV3GL.js.map} +0 -0
  69. /package/dist/{chunk-YDMVYYD2.js.map → chunk-KAUXI453.js.map} +0 -0
  70. /package/dist/{chunk-FCOQXV3T.js.map → chunk-KCLX6LOV.js.map} +0 -0
  71. /package/dist/{chunk-CHBI22MI.js.map → chunk-O2HWL47E.js.map} +0 -0
  72. /package/dist/{chunk-TTGZV5R3.js.map → chunk-OHZXPJK7.js.map} +0 -0
  73. /package/dist/{chunk-FPGE5NVO.js.map → chunk-OMHRQTOD.js.map} +0 -0
  74. /package/dist/{chunk-E62SBGQ3.js.map → chunk-PYGKYEDU.js.map} +0 -0
  75. /package/dist/{chunk-IQ3OI2RR.js.map → chunk-QK2G4EYH.js.map} +0 -0
  76. /package/dist/{chunk-YNXOKMJP.js.map → chunk-RB7LF7K7.js.map} +0 -0
  77. /package/dist/{chunk-YHV3KRKS.js.map → chunk-TKUQG2PE.js.map} +0 -0
  78. /package/dist/{chunk-HC6EKOID.js.map → chunk-UMZRCQFC.js.map} +0 -0
  79. /package/dist/{chunk-LJBOVCQG.js.map → chunk-XI5V7WSJ.js.map} +0 -0
  80. /package/dist/{chunk-6BR7L222.js.map → chunk-ZQLC75BF.js.map} +0 -0
package/src/storage.ts CHANGED
@@ -135,6 +135,15 @@ const ARTIFACT_SEARCH_STOPWORDS = new Set([
135
135
  "with",
136
136
  ]);
137
137
 
138
+ type OfflineSyncDigestCacheEntry = {
139
+ statBytes: number;
140
+ mtimeMs: number;
141
+ ctimeMs: number;
142
+ encrypted: boolean;
143
+ sha256: string;
144
+ bytes: number;
145
+ };
146
+
138
147
  export interface ReextractJobRequest {
139
148
  memoryId: string;
140
149
  model: string;
@@ -2236,6 +2245,10 @@ export class StorageManager {
2236
2245
  private factHashIndexAuthoritative: boolean | null = null;
2237
2246
  private factHashIndexAuthoritativePromise: Promise<void> | null = null;
2238
2247
  private readonly secureAppendChains = new Map<string, Promise<void>>();
2248
+ private offlineSyncDigestCache: Map<string, OfflineSyncDigestCacheEntry> | null = null;
2249
+ private offlineSyncDigestCacheLoadPromise: Promise<Map<string, OfflineSyncDigestCacheEntry>> | null = null;
2250
+ private offlineSyncDigestCacheWriteChain: Promise<void> = Promise.resolve();
2251
+ private offlineSyncDigestCacheWriteTimer: ReturnType<typeof setTimeout> | null = null;
2239
2252
  /** Optional: set by the orchestrator after construction to enable template-aware citation stripping during legacy hash rebuild. */
2240
2253
  citationTemplate: string = DEFAULT_CITATION_FORMAT;
2241
2254
 
@@ -2516,24 +2529,152 @@ export class StorageManager {
2516
2529
 
2517
2530
  async digestOfflineSyncFile(filePath: string): Promise<{ sha256: string; bytes: number }> {
2518
2531
  const target = this.assertManagedStoragePath(filePath, "storage.digestOfflineSyncFile");
2519
- if (await this.offlineSyncFileIsEncrypted(target)) {
2520
- const content = await readMaybeEncryptedFileBuffer(target, this._secureStoreKey, this.baseDir);
2532
+ const st = await stat(target);
2533
+ const relPath = path.relative(this.baseDir, target).split(path.sep).join("/");
2534
+ const cache = await this.loadOfflineSyncDigestCache();
2535
+ const cached = cache.get(relPath);
2536
+ if (
2537
+ cached &&
2538
+ cached.statBytes === st.size &&
2539
+ cached.mtimeMs === st.mtimeMs &&
2540
+ cached.ctimeMs === st.ctimeMs &&
2541
+ !cached.encrypted
2542
+ ) {
2521
2543
  return {
2544
+ sha256: cached.sha256,
2545
+ bytes: cached.bytes,
2546
+ };
2547
+ }
2548
+
2549
+ const encrypted = await this.offlineSyncFileIsEncrypted(target);
2550
+ let digest: { sha256: string; bytes: number };
2551
+ if (encrypted) {
2552
+ const content = await readMaybeEncryptedFileBuffer(target, this._secureStoreKey, this.baseDir);
2553
+ digest = {
2522
2554
  sha256: createHash("sha256").update(content).digest("hex"),
2523
2555
  bytes: content.byteLength,
2524
2556
  };
2557
+ } else {
2558
+ const hash = createHash("sha256");
2559
+ let bytes = 0;
2560
+ for await (const rawChunk of createReadStream(target)) {
2561
+ const chunk = Buffer.isBuffer(rawChunk) ? rawChunk : Buffer.from(rawChunk);
2562
+ hash.update(chunk);
2563
+ bytes += chunk.length;
2564
+ }
2565
+ digest = {
2566
+ sha256: hash.digest("hex"),
2567
+ bytes,
2568
+ };
2525
2569
  }
2526
- const hash = createHash("sha256");
2527
- let bytes = 0;
2528
- for await (const rawChunk of createReadStream(target)) {
2529
- const chunk = Buffer.isBuffer(rawChunk) ? rawChunk : Buffer.from(rawChunk);
2530
- hash.update(chunk);
2531
- bytes += chunk.length;
2570
+ if (!encrypted) {
2571
+ this.rememberOfflineSyncDigest(relPath, st, digest);
2532
2572
  }
2533
- return {
2534
- sha256: hash.digest("hex"),
2535
- bytes,
2536
- };
2573
+ return digest;
2574
+ }
2575
+
2576
+ private async loadOfflineSyncDigestCache(): Promise<Map<string, OfflineSyncDigestCacheEntry>> {
2577
+ if (this.offlineSyncDigestCache) return this.offlineSyncDigestCache;
2578
+ if (!this.offlineSyncDigestCacheLoadPromise) {
2579
+ this.offlineSyncDigestCacheLoadPromise = this.readOfflineSyncDigestCache();
2580
+ }
2581
+ this.offlineSyncDigestCache = await this.offlineSyncDigestCacheLoadPromise;
2582
+ return this.offlineSyncDigestCache;
2583
+ }
2584
+
2585
+ private async readOfflineSyncDigestCache(): Promise<Map<string, OfflineSyncDigestCacheEntry>> {
2586
+ const cache = new Map<string, OfflineSyncDigestCacheEntry>();
2587
+ try {
2588
+ const raw = await readFile(this.offlineSyncDigestCachePath, "utf-8");
2589
+ const parsed = JSON.parse(raw) as unknown;
2590
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return cache;
2591
+ const entries = (parsed as { entries?: unknown }).entries;
2592
+ if (!Array.isArray(entries)) return cache;
2593
+ for (const entry of entries) {
2594
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
2595
+ const record = entry as Record<string, unknown>;
2596
+ const cachePath = typeof record.path === "string" ? record.path : "";
2597
+ const statBytes = typeof record.statBytes === "number" ? record.statBytes : NaN;
2598
+ const mtimeMs = typeof record.mtimeMs === "number" ? record.mtimeMs : NaN;
2599
+ const ctimeMs = typeof record.ctimeMs === "number" ? record.ctimeMs : NaN;
2600
+ const bytes = typeof record.bytes === "number" ? record.bytes : NaN;
2601
+ const sha256 = typeof record.sha256 === "string" ? record.sha256 : "";
2602
+ const encrypted = record.encrypted === true;
2603
+ if (
2604
+ cachePath.length === 0 ||
2605
+ cachePath === ".." ||
2606
+ cachePath.startsWith("../") ||
2607
+ path.isAbsolute(cachePath) ||
2608
+ !Number.isFinite(statBytes) ||
2609
+ !Number.isFinite(mtimeMs) ||
2610
+ !Number.isFinite(ctimeMs) ||
2611
+ !Number.isFinite(bytes) ||
2612
+ !/^[a-f0-9]{64}$/i.test(sha256)
2613
+ ) {
2614
+ continue;
2615
+ }
2616
+ cache.set(cachePath, { statBytes, mtimeMs, ctimeMs, encrypted, sha256, bytes });
2617
+ }
2618
+ } catch (err) {
2619
+ if (!isErrnoCode(err, "ENOENT")) {
2620
+ log.warn(
2621
+ `storage.offlineSyncDigestCache: ignoring unreadable cache: ${err instanceof Error ? err.message : String(err)}`,
2622
+ );
2623
+ }
2624
+ }
2625
+ return cache;
2626
+ }
2627
+
2628
+ private rememberOfflineSyncDigest(
2629
+ relPath: string,
2630
+ st: { size: number; mtimeMs: number; ctimeMs: number },
2631
+ digest: { sha256: string; bytes: number },
2632
+ ): void {
2633
+ const cache = this.offlineSyncDigestCache;
2634
+ if (!cache) return;
2635
+ cache.set(relPath, {
2636
+ statBytes: st.size,
2637
+ mtimeMs: st.mtimeMs,
2638
+ ctimeMs: st.ctimeMs,
2639
+ encrypted: false,
2640
+ sha256: digest.sha256,
2641
+ bytes: digest.bytes,
2642
+ });
2643
+ this.scheduleOfflineSyncDigestCacheWrite();
2644
+ }
2645
+
2646
+ private scheduleOfflineSyncDigestCacheWrite(): void {
2647
+ if (this.offlineSyncDigestCacheWriteTimer) {
2648
+ clearTimeout(this.offlineSyncDigestCacheWriteTimer);
2649
+ }
2650
+ this.offlineSyncDigestCacheWriteTimer = setTimeout(() => {
2651
+ this.offlineSyncDigestCacheWriteTimer = null;
2652
+ this.queueOfflineSyncDigestCacheWrite();
2653
+ }, 1_000);
2654
+ this.offlineSyncDigestCacheWriteTimer.unref?.();
2655
+ }
2656
+
2657
+ private queueOfflineSyncDigestCacheWrite(): void {
2658
+ this.offlineSyncDigestCacheWriteChain = this.offlineSyncDigestCacheWriteChain
2659
+ .catch(() => undefined)
2660
+ .then(async () => {
2661
+ const cache = this.offlineSyncDigestCache;
2662
+ if (!cache) return;
2663
+ const entries = [...cache.entries()]
2664
+ .sort(([a], [b]) => a.localeCompare(b))
2665
+ .map(([entryPath, entry]) => ({ path: entryPath, ...entry }));
2666
+ await mkdir(path.dirname(this.offlineSyncDigestCachePath), { recursive: true });
2667
+ await writeFile(
2668
+ this.offlineSyncDigestCachePath,
2669
+ `${JSON.stringify({ version: 1, entries })}\n`,
2670
+ "utf-8",
2671
+ );
2672
+ })
2673
+ .catch((err) => {
2674
+ log.warn(
2675
+ `storage.offlineSyncDigestCache: failed to write cache: ${err instanceof Error ? err.message : String(err)}`,
2676
+ );
2677
+ });
2537
2678
  }
2538
2679
 
2539
2680
  private async offlineSyncFileIsEncrypted(filePath: string): Promise<boolean> {
@@ -2644,6 +2785,9 @@ export class StorageManager {
2644
2785
  private get stateDir(): string {
2645
2786
  return path.join(this.baseDir, "state");
2646
2787
  }
2788
+ private get offlineSyncDigestCachePath(): string {
2789
+ return path.join(this.baseDir, ".offline-sync", "digest-cache.v1.json");
2790
+ }
2647
2791
  private get entitySynthesisQueuePath(): string {
2648
2792
  return path.join(this.stateDir, "entity-synthesis-queue.json");
2649
2793
  }