@remnic/core 1.1.16 → 1.1.18

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 (98) hide show
  1. package/dist/access-cli.js +16 -16
  2. package/dist/access-http.d.ts +2 -1
  3. package/dist/access-http.js +10 -10
  4. package/dist/access-mcp.d.ts +1 -1
  5. package/dist/access-mcp.js +9 -9
  6. package/dist/access-schema.d.ts +23 -3
  7. package/dist/access-schema.js +6 -1
  8. package/dist/{access-service-DZXc7qwR.d.ts → access-service-DT9L2DW4.d.ts} +14 -2
  9. package/dist/access-service.d.ts +1 -1
  10. package/dist/access-service.js +7 -7
  11. package/dist/briefing.js +4 -4
  12. package/dist/causal-consolidation.js +5 -5
  13. package/dist/{chunk-NOHC2L57.js → chunk-2IRT26RZ.js} +2 -2
  14. package/dist/{chunk-HJILHQOR.js → chunk-2XX25T6U.js} +15 -15
  15. package/dist/{chunk-66H2DZYB.js → chunk-4J3BTQRB.js} +13 -1
  16. package/dist/chunk-4J3BTQRB.js.map +1 -0
  17. package/dist/{chunk-MS3ULOZF.js → chunk-5IQC4OG6.js} +2 -2
  18. package/dist/{chunk-ZPXYWTN5.js → chunk-5ML4TH3E.js} +4 -4
  19. package/dist/{chunk-V7WH7DEM.js → chunk-6ORWKANA.js} +2 -2
  20. package/dist/{chunk-IOAY54RF.js → chunk-7Q2P774N.js} +11 -11
  21. package/dist/{chunk-JFEH2LZM.js → chunk-CN4P6SVA.js} +2 -2
  22. package/dist/{chunk-OGROP7ZN.js → chunk-FFU4GMST.js} +5 -5
  23. package/dist/{chunk-C7DGCHJE.js → chunk-FSODDMR2.js} +2 -2
  24. package/dist/{chunk-AAX3SUM3.js → chunk-GGCJ253V.js} +11 -11
  25. package/dist/{chunk-2OZ6GP27.js → chunk-J4VRI4BH.js} +101 -5
  26. package/dist/chunk-J4VRI4BH.js.map +1 -0
  27. package/dist/{chunk-IG5VGHYB.js → chunk-KSFBM6TV.js} +2 -2
  28. package/dist/{chunk-M3AA636B.js → chunk-NOQ74SJN.js} +2 -2
  29. package/dist/{chunk-MTYLGYOQ.js → chunk-P7FRFY6S.js} +38 -4
  30. package/dist/chunk-P7FRFY6S.js.map +1 -0
  31. package/dist/{chunk-QLKBF3TI.js → chunk-QOHBYVZG.js} +2 -2
  32. package/dist/{chunk-OJRKZLZ4.js → chunk-SGIXDVSF.js} +2 -2
  33. package/dist/{chunk-SK42SSAN.js → chunk-SPEVF47M.js} +4 -4
  34. package/dist/{chunk-YCVWX2NF.js → chunk-SZKCBLS5.js} +2 -2
  35. package/dist/{chunk-BEB4GUU5.js → chunk-TLM762GT.js} +2 -2
  36. package/dist/{chunk-65HQPW6O.js → chunk-TOFUTKQN.js} +2 -2
  37. package/dist/{chunk-Y2YBRCEF.js → chunk-TUQXQOGR.js} +34 -9
  38. package/dist/chunk-TUQXQOGR.js.map +1 -0
  39. package/dist/{chunk-G7JBLD65.js → chunk-YO3AZEE5.js} +3 -3
  40. package/dist/{cli-kVwab1_L.d.ts → cli-BN0CkYzI.d.ts} +1 -1
  41. package/dist/cli.d.ts +2 -2
  42. package/dist/cli.js +19 -19
  43. package/dist/compounding/engine.js +4 -4
  44. package/dist/connectors/codex-materialize-runner.js +4 -4
  45. package/dist/connectors/index.js +4 -4
  46. package/dist/entity-retrieval.js +4 -4
  47. package/dist/index.d.ts +4 -4
  48. package/dist/index.js +50 -46
  49. package/dist/index.js.map +1 -1
  50. package/dist/maintenance/memory-governance.js +4 -4
  51. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +4 -4
  52. package/dist/maintenance/rebuild-memory-projection.js +5 -5
  53. package/dist/mcp-memory-inspector-app.d.ts +1 -1
  54. package/dist/namespaces/migrate.js +5 -5
  55. package/dist/namespaces/storage.js +4 -4
  56. package/dist/offline-sync.d.ts +16 -1
  57. package/dist/offline-sync.js +8 -2
  58. package/dist/operator-toolkit.js +7 -7
  59. package/dist/orchestrator.js +12 -12
  60. package/dist/schemas.d.ts +22 -22
  61. package/dist/secure-store/index.js +17 -17
  62. package/dist/semantic-consolidation.js +5 -5
  63. package/dist/semantic-rule-promotion.js +4 -4
  64. package/dist/semantic-rule-verifier.js +4 -4
  65. package/dist/storage.js +3 -3
  66. package/dist/transfer/types.d.ts +12 -12
  67. package/dist/verified-recall.js +4 -4
  68. package/package.json +1 -1
  69. package/src/access-http.test.ts +90 -0
  70. package/src/access-http.ts +47 -0
  71. package/src/access-schema.ts +13 -0
  72. package/src/access-service.ts +48 -0
  73. package/src/index.ts +3 -0
  74. package/src/offline-sync.test.ts +81 -0
  75. package/src/offline-sync.ts +117 -0
  76. package/dist/chunk-2OZ6GP27.js.map +0 -1
  77. package/dist/chunk-66H2DZYB.js.map +0 -1
  78. package/dist/chunk-MTYLGYOQ.js.map +0 -1
  79. package/dist/chunk-Y2YBRCEF.js.map +0 -1
  80. /package/dist/{chunk-NOHC2L57.js.map → chunk-2IRT26RZ.js.map} +0 -0
  81. /package/dist/{chunk-HJILHQOR.js.map → chunk-2XX25T6U.js.map} +0 -0
  82. /package/dist/{chunk-MS3ULOZF.js.map → chunk-5IQC4OG6.js.map} +0 -0
  83. /package/dist/{chunk-ZPXYWTN5.js.map → chunk-5ML4TH3E.js.map} +0 -0
  84. /package/dist/{chunk-V7WH7DEM.js.map → chunk-6ORWKANA.js.map} +0 -0
  85. /package/dist/{chunk-IOAY54RF.js.map → chunk-7Q2P774N.js.map} +0 -0
  86. /package/dist/{chunk-JFEH2LZM.js.map → chunk-CN4P6SVA.js.map} +0 -0
  87. /package/dist/{chunk-OGROP7ZN.js.map → chunk-FFU4GMST.js.map} +0 -0
  88. /package/dist/{chunk-C7DGCHJE.js.map → chunk-FSODDMR2.js.map} +0 -0
  89. /package/dist/{chunk-AAX3SUM3.js.map → chunk-GGCJ253V.js.map} +0 -0
  90. /package/dist/{chunk-IG5VGHYB.js.map → chunk-KSFBM6TV.js.map} +0 -0
  91. /package/dist/{chunk-M3AA636B.js.map → chunk-NOQ74SJN.js.map} +0 -0
  92. /package/dist/{chunk-QLKBF3TI.js.map → chunk-QOHBYVZG.js.map} +0 -0
  93. /package/dist/{chunk-OJRKZLZ4.js.map → chunk-SGIXDVSF.js.map} +0 -0
  94. /package/dist/{chunk-SK42SSAN.js.map → chunk-SPEVF47M.js.map} +0 -0
  95. /package/dist/{chunk-YCVWX2NF.js.map → chunk-SZKCBLS5.js.map} +0 -0
  96. /package/dist/{chunk-BEB4GUU5.js.map → chunk-TLM762GT.js.map} +0 -0
  97. /package/dist/{chunk-65HQPW6O.js.map → chunk-TOFUTKQN.js.map} +0 -0
  98. /package/dist/{chunk-G7JBLD65.js.map → chunk-YO3AZEE5.js.map} +0 -0
@@ -1,12 +1,10 @@
1
1
  import {
2
2
  searchVerifiedSemanticRules
3
- } from "./chunk-QLKBF3TI.js";
4
- import "./chunk-IOAY54RF.js";
3
+ } from "./chunk-QOHBYVZG.js";
4
+ import "./chunk-7Q2P774N.js";
5
5
  import "./chunk-5UZXUTVO.js";
6
- import "./chunk-SH5S7XYD.js";
7
6
  import "./chunk-NN2DKE4T.js";
8
7
  import "./chunk-Q7P4WJDP.js";
9
- import "./chunk-P7FMDTKL.js";
10
8
  import "./chunk-SCU65EZI.js";
11
9
  import "./chunk-3KW65B36.js";
12
10
  import "./chunk-3HPAPHUK.js";
@@ -20,7 +18,9 @@ import "./chunk-FAAFWE4G.js";
20
18
  import "./chunk-DT5TVLJE.js";
21
19
  import "./chunk-4DJQYKMN.js";
22
20
  import "./chunk-2ODBA7MQ.js";
21
+ import "./chunk-SH5S7XYD.js";
23
22
  import "./chunk-A6XUJE5D.js";
23
+ import "./chunk-P7FMDTKL.js";
24
24
  import "./chunk-PZ5AY32C.js";
25
25
  export {
26
26
  searchVerifiedSemanticRules
package/dist/storage.js CHANGED
@@ -8,12 +8,10 @@ import {
8
8
  normalizeEntityName,
9
9
  parseEntityFile,
10
10
  serializeEntityFile
11
- } from "./chunk-IOAY54RF.js";
11
+ } from "./chunk-7Q2P774N.js";
12
12
  import "./chunk-5UZXUTVO.js";
13
- import "./chunk-SH5S7XYD.js";
14
13
  import "./chunk-NN2DKE4T.js";
15
14
  import "./chunk-Q7P4WJDP.js";
16
- import "./chunk-P7FMDTKL.js";
17
15
  import "./chunk-SCU65EZI.js";
18
16
  import "./chunk-3KW65B36.js";
19
17
  import "./chunk-3HPAPHUK.js";
@@ -26,7 +24,9 @@ import "./chunk-G7D6GZ5J.js";
26
24
  import "./chunk-FAAFWE4G.js";
27
25
  import "./chunk-4DJQYKMN.js";
28
26
  import "./chunk-2ODBA7MQ.js";
27
+ import "./chunk-SH5S7XYD.js";
29
28
  import "./chunk-A6XUJE5D.js";
29
+ import "./chunk-P7FMDTKL.js";
30
30
  import "./chunk-PZ5AY32C.js";
31
31
  export {
32
32
  ContentHashIndex,
@@ -313,13 +313,13 @@ declare const CapsuleBlockSchema: z.ZodObject<{
313
313
  peerProfiles: boolean;
314
314
  }>;
315
315
  }, "strip", z.ZodTypeAny, {
316
+ schemaVersion: string;
316
317
  includes: {
317
318
  procedural: boolean;
318
319
  taxonomy: boolean;
319
320
  identityAnchors: boolean;
320
321
  peerProfiles: boolean;
321
322
  };
322
- schemaVersion: string;
323
323
  id: string;
324
324
  description: string;
325
325
  version: string;
@@ -334,13 +334,13 @@ declare const CapsuleBlockSchema: z.ZodObject<{
334
334
  directAnswerEnabled: boolean;
335
335
  };
336
336
  }, {
337
+ schemaVersion: string;
337
338
  includes: {
338
339
  procedural: boolean;
339
340
  taxonomy: boolean;
340
341
  identityAnchors: boolean;
341
342
  peerProfiles: boolean;
342
343
  };
343
- schemaVersion: string;
344
344
  id: string;
345
345
  description: string;
346
346
  version: string;
@@ -464,13 +464,13 @@ declare const ExportManifestV2Schema: z.ZodObject<{
464
464
  peerProfiles: boolean;
465
465
  }>;
466
466
  }, "strip", z.ZodTypeAny, {
467
+ schemaVersion: string;
467
468
  includes: {
468
469
  procedural: boolean;
469
470
  taxonomy: boolean;
470
471
  identityAnchors: boolean;
471
472
  peerProfiles: boolean;
472
473
  };
473
- schemaVersion: string;
474
474
  id: string;
475
475
  description: string;
476
476
  version: string;
@@ -485,13 +485,13 @@ declare const ExportManifestV2Schema: z.ZodObject<{
485
485
  directAnswerEnabled: boolean;
486
486
  };
487
487
  }, {
488
+ schemaVersion: string;
488
489
  includes: {
489
490
  procedural: boolean;
490
491
  taxonomy: boolean;
491
492
  identityAnchors: boolean;
492
493
  peerProfiles: boolean;
493
494
  };
494
- schemaVersion: string;
495
495
  id: string;
496
496
  description: string;
497
497
  version: string;
@@ -518,13 +518,13 @@ declare const ExportManifestV2Schema: z.ZodObject<{
518
518
  pluginVersion: string;
519
519
  includesTranscripts: boolean;
520
520
  capsule: {
521
+ schemaVersion: string;
521
522
  includes: {
522
523
  procedural: boolean;
523
524
  taxonomy: boolean;
524
525
  identityAnchors: boolean;
525
526
  peerProfiles: boolean;
526
527
  };
527
- schemaVersion: string;
528
528
  id: string;
529
529
  description: string;
530
530
  version: string;
@@ -551,13 +551,13 @@ declare const ExportManifestV2Schema: z.ZodObject<{
551
551
  pluginVersion: string;
552
552
  includesTranscripts: boolean;
553
553
  capsule: {
554
+ schemaVersion: string;
554
555
  includes: {
555
556
  procedural: boolean;
556
557
  taxonomy: boolean;
557
558
  identityAnchors: boolean;
558
559
  peerProfiles: boolean;
559
560
  };
560
- schemaVersion: string;
561
561
  id: string;
562
562
  description: string;
563
563
  version: string;
@@ -683,13 +683,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
683
683
  peerProfiles: boolean;
684
684
  }>;
685
685
  }, "strip", z.ZodTypeAny, {
686
+ schemaVersion: string;
686
687
  includes: {
687
688
  procedural: boolean;
688
689
  taxonomy: boolean;
689
690
  identityAnchors: boolean;
690
691
  peerProfiles: boolean;
691
692
  };
692
- schemaVersion: string;
693
693
  id: string;
694
694
  description: string;
695
695
  version: string;
@@ -704,13 +704,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
704
704
  directAnswerEnabled: boolean;
705
705
  };
706
706
  }, {
707
+ schemaVersion: string;
707
708
  includes: {
708
709
  procedural: boolean;
709
710
  taxonomy: boolean;
710
711
  identityAnchors: boolean;
711
712
  peerProfiles: boolean;
712
713
  };
713
- schemaVersion: string;
714
714
  id: string;
715
715
  description: string;
716
716
  version: string;
@@ -737,13 +737,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
737
737
  pluginVersion: string;
738
738
  includesTranscripts: boolean;
739
739
  capsule: {
740
+ schemaVersion: string;
740
741
  includes: {
741
742
  procedural: boolean;
742
743
  taxonomy: boolean;
743
744
  identityAnchors: boolean;
744
745
  peerProfiles: boolean;
745
746
  };
746
- schemaVersion: string;
747
747
  id: string;
748
748
  description: string;
749
749
  version: string;
@@ -770,13 +770,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
770
770
  pluginVersion: string;
771
771
  includesTranscripts: boolean;
772
772
  capsule: {
773
+ schemaVersion: string;
773
774
  includes: {
774
775
  procedural: boolean;
775
776
  taxonomy: boolean;
776
777
  identityAnchors: boolean;
777
778
  peerProfiles: boolean;
778
779
  };
779
- schemaVersion: string;
780
780
  id: string;
781
781
  description: string;
782
782
  version: string;
@@ -815,13 +815,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
815
815
  pluginVersion: string;
816
816
  includesTranscripts: boolean;
817
817
  capsule: {
818
+ schemaVersion: string;
818
819
  includes: {
819
820
  procedural: boolean;
820
821
  taxonomy: boolean;
821
822
  identityAnchors: boolean;
822
823
  peerProfiles: boolean;
823
824
  };
824
- schemaVersion: string;
825
825
  id: string;
826
826
  description: string;
827
827
  version: string;
@@ -854,13 +854,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
854
854
  pluginVersion: string;
855
855
  includesTranscripts: boolean;
856
856
  capsule: {
857
+ schemaVersion: string;
857
858
  includes: {
858
859
  procedural: boolean;
859
860
  taxonomy: boolean;
860
861
  identityAnchors: boolean;
861
862
  peerProfiles: boolean;
862
863
  };
863
- schemaVersion: string;
864
864
  id: string;
865
865
  description: string;
866
866
  version: string;
@@ -1,13 +1,11 @@
1
1
  import {
2
2
  searchVerifiedEpisodes
3
- } from "./chunk-MS3ULOZF.js";
3
+ } from "./chunk-5IQC4OG6.js";
4
4
  import "./chunk-NZL6GGQE.js";
5
- import "./chunk-IOAY54RF.js";
5
+ import "./chunk-7Q2P774N.js";
6
6
  import "./chunk-5UZXUTVO.js";
7
- import "./chunk-SH5S7XYD.js";
8
7
  import "./chunk-NN2DKE4T.js";
9
8
  import "./chunk-Q7P4WJDP.js";
10
- import "./chunk-P7FMDTKL.js";
11
9
  import "./chunk-SCU65EZI.js";
12
10
  import "./chunk-3KW65B36.js";
13
11
  import "./chunk-3HPAPHUK.js";
@@ -21,7 +19,9 @@ import "./chunk-FAAFWE4G.js";
21
19
  import "./chunk-DT5TVLJE.js";
22
20
  import "./chunk-4DJQYKMN.js";
23
21
  import "./chunk-2ODBA7MQ.js";
22
+ import "./chunk-SH5S7XYD.js";
24
23
  import "./chunk-A6XUJE5D.js";
24
+ import "./chunk-P7FMDTKL.js";
25
25
  import "./chunk-PZ5AY32C.js";
26
26
  export {
27
27
  searchVerifiedEpisodes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remnic/core",
3
- "version": "1.1.16",
3
+ "version": "1.1.18",
4
4
  "description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -359,6 +359,96 @@ test("HTTP offline files forwards namespace and requested paths", async () => {
359
359
  }
360
360
  });
361
361
 
362
+ test("HTTP offline file-content forwards range options and returns binary metadata", async () => {
363
+ const calls: Array<{
364
+ namespace: string | undefined;
365
+ principal: string | undefined;
366
+ includeTranscripts: boolean | undefined;
367
+ path: string;
368
+ offset: number | undefined;
369
+ length: number | undefined;
370
+ }> = [];
371
+ const service = {
372
+ offlineSyncFileContent: async (options: {
373
+ namespace?: string;
374
+ principal?: string;
375
+ includeTranscripts?: boolean;
376
+ path: string;
377
+ offset?: number;
378
+ length?: number;
379
+ }) => {
380
+ calls.push({
381
+ namespace: options.namespace,
382
+ principal: options.principal,
383
+ includeTranscripts: options.includeTranscripts,
384
+ path: options.path,
385
+ offset: options.offset,
386
+ length: options.length,
387
+ });
388
+ return {
389
+ namespace: options.namespace ?? "default",
390
+ path: options.path,
391
+ sha256: "a".repeat(64),
392
+ bytes: 12,
393
+ mtimeMs: 1234,
394
+ offset: options.offset ?? 0,
395
+ chunkBytes: 5,
396
+ content: Buffer.from("hello"),
397
+ };
398
+ },
399
+ } as unknown as EngramAccessService;
400
+ const server = new EngramAccessHttpServer({
401
+ service,
402
+ port: 0,
403
+ authToken: "test-token",
404
+ principal: "reader",
405
+ adminConsoleEnabled: false,
406
+ });
407
+
408
+ const status = await server.start();
409
+ try {
410
+ const response = await fetch(
411
+ `http://127.0.0.1:${status.port}/remnic/v1/offline-sync/file-content`,
412
+ {
413
+ method: "POST",
414
+ headers: {
415
+ authorization: "Bearer test-token",
416
+ "content-type": "application/json",
417
+ },
418
+ body: JSON.stringify({
419
+ namespace: "team",
420
+ includeTranscripts: false,
421
+ path: "state/memory-lifecycle-ledger.jsonl",
422
+ offset: 8,
423
+ length: 5,
424
+ }),
425
+ },
426
+ );
427
+ const body = Buffer.from(await response.arrayBuffer());
428
+
429
+ assert.equal(response.status, 200);
430
+ assert.equal(body.toString("utf-8"), "hello");
431
+ assert.equal(response.headers.get("content-type"), "application/octet-stream");
432
+ assert.equal(response.headers.get("x-remnic-namespace"), "team");
433
+ assert.equal(response.headers.get("x-remnic-file-path"), "state%2Fmemory-lifecycle-ledger.jsonl");
434
+ assert.equal(response.headers.get("x-remnic-file-sha256"), "a".repeat(64));
435
+ assert.equal(response.headers.get("x-remnic-file-bytes"), "12");
436
+ assert.equal(response.headers.get("x-remnic-file-mtime-ms"), "1234");
437
+ assert.equal(response.headers.get("x-remnic-chunk-offset"), "8");
438
+ assert.equal(response.headers.get("x-remnic-chunk-bytes"), "5");
439
+ assert.deepEqual(calls, [{
440
+ namespace: "team",
441
+ principal: "reader",
442
+ includeTranscripts: false,
443
+ path: "state/memory-lifecycle-ledger.jsonl",
444
+ offset: 8,
445
+ length: 5,
446
+ }]);
447
+ } finally {
448
+ await server.stop();
449
+ }
450
+ });
451
+
362
452
  test("HTTP offline snapshot rejects invalid boolean query values", async () => {
363
453
  let calls = 0;
364
454
  const service = {
@@ -641,6 +641,34 @@ export class EngramAccessHttpServer {
641
641
  return;
642
642
  }
643
643
 
644
+ if (
645
+ req.method === "POST" &&
646
+ (
647
+ pathname === "/engram/v1/offline-sync/file-content" ||
648
+ pathname === "/remnic/v1/offline-sync/file-content"
649
+ )
650
+ ) {
651
+ const body = await this.readValidatedBody(req, "offlineSyncFileContent");
652
+ const result = await this.service.offlineSyncFileContent({
653
+ namespace: this.resolveNamespace(req, body.namespace),
654
+ principal: this.resolveRequestPrincipal(req),
655
+ includeTranscripts: body.includeTranscripts,
656
+ path: body.path,
657
+ offset: body.offset,
658
+ length: body.length,
659
+ });
660
+ this.respondBinary(res, 200, result.content, {
661
+ "x-remnic-namespace": encodeURIComponent(result.namespace),
662
+ "x-remnic-file-path": encodeURIComponent(result.path),
663
+ "x-remnic-file-bytes": String(result.bytes),
664
+ "x-remnic-file-mtime-ms": String(result.mtimeMs),
665
+ "x-remnic-chunk-offset": String(result.offset),
666
+ "x-remnic-chunk-bytes": String(result.chunkBytes),
667
+ ...(result.sha256 ? { "x-remnic-file-sha256": result.sha256 } : {}),
668
+ });
669
+ return;
670
+ }
671
+
644
672
  if (
645
673
  req.method === "POST" &&
646
674
  (pathname === "/engram/v1/offline-sync/apply" || pathname === "/remnic/v1/offline-sync/apply")
@@ -1803,6 +1831,25 @@ export class EngramAccessHttpServer {
1803
1831
  res.end(body);
1804
1832
  }
1805
1833
 
1834
+ private respondBinary(
1835
+ res: ServerResponse,
1836
+ status: number,
1837
+ body: Buffer,
1838
+ headers: Record<string, string> = {},
1839
+ ): void {
1840
+ res.statusCode = status;
1841
+ res.setHeader("content-type", "application/octet-stream");
1842
+ res.setHeader("content-length", String(body.length));
1843
+ for (const [key, value] of Object.entries(headers)) {
1844
+ res.setHeader(key, value);
1845
+ }
1846
+ const cid = correlationIdStore.getStore();
1847
+ if (cid) {
1848
+ res.setHeader("x-request-id", cid);
1849
+ }
1850
+ res.end(body);
1851
+ }
1852
+
1806
1853
  private async handleAdminConsole(
1807
1854
  req: IncomingMessage,
1808
1855
  res: ServerResponse,
@@ -10,6 +10,7 @@ import {
10
10
  } from "./action-confidence.js";
11
11
  import { isValidCapsuleSince } from "./transfer/capsule-export.js";
12
12
  import { CAPSULE_ID_PATTERN } from "./transfer/types.js";
13
+ import { OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES } from "./offline-sync.js";
13
14
 
14
15
  // ---------------------------------------------------------------------------
15
16
  // Error formatting
@@ -386,6 +387,14 @@ export const offlineSyncFilesRequestSchema = z.object({
386
387
  .max(5000, "paths must contain 5000 or fewer entries"),
387
388
  });
388
389
 
390
+ export const offlineSyncFileContentRequestSchema = z.object({
391
+ namespace: namespaceSchema,
392
+ includeTranscripts: z.boolean().optional(),
393
+ path: z.string().trim().min(1, "path must be non-empty").max(4096),
394
+ offset: z.number().int().min(0).optional(),
395
+ length: z.number().int().min(1).max(OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES).optional(),
396
+ });
397
+
389
398
  // ---------------------------------------------------------------------------
390
399
  // Action confidence
391
400
  // ---------------------------------------------------------------------------
@@ -453,6 +462,7 @@ export type CapsuleImportRequest = z.infer<typeof capsuleImportRequestSchema>;
453
462
  export type CapsuleListRequest = z.infer<typeof capsuleListRequestSchema>;
454
463
  export type OfflineSyncApplyRequest = z.infer<typeof offlineSyncApplyRequestSchema>;
455
464
  export type OfflineSyncFilesRequest = z.infer<typeof offlineSyncFilesRequestSchema>;
465
+ export type OfflineSyncFileContentRequest = z.infer<typeof offlineSyncFileContentRequestSchema>;
456
466
  export type ActionConfidenceRequest = z.infer<typeof actionConfidenceRequestSchema>;
457
467
 
458
468
  // ---------------------------------------------------------------------------
@@ -477,6 +487,7 @@ export type SchemaName =
477
487
  | "capsuleImport"
478
488
  | "capsuleList"
479
489
  | "offlineSyncFiles"
490
+ | "offlineSyncFileContent"
480
491
  | "offlineSyncApply"
481
492
  | "actionConfidence";
482
493
 
@@ -498,6 +509,7 @@ export type SchemaTypeFor<N extends SchemaName> =
498
509
  : N extends "capsuleImport" ? CapsuleImportRequest
499
510
  : N extends "capsuleList" ? CapsuleListRequest
500
511
  : N extends "offlineSyncFiles" ? OfflineSyncFilesRequest
512
+ : N extends "offlineSyncFileContent" ? OfflineSyncFileContentRequest
501
513
  : N extends "offlineSyncApply" ? OfflineSyncApplyRequest
502
514
  : N extends "actionConfidence" ? ActionConfidenceRequest
503
515
  : never;
@@ -520,6 +532,7 @@ const schemas: Record<SchemaName, z.ZodTypeAny> = {
520
532
  capsuleImport: capsuleImportRequestSchema,
521
533
  capsuleList: capsuleListRequestSchema,
522
534
  offlineSyncFiles: offlineSyncFilesRequestSchema,
535
+ offlineSyncFileContent: offlineSyncFileContentRequestSchema,
523
536
  offlineSyncApply: offlineSyncApplyRequestSchema,
524
537
  actionConfidence: actionConfidenceRequestSchema,
525
538
  };
@@ -139,7 +139,9 @@ import {
139
139
  applyOfflineSyncChangeset,
140
140
  buildOfflineSyncSnapshot,
141
141
  buildOfflineSyncSnapshotForPaths,
142
+ readOfflineSyncFileContentChunk,
142
143
  type OfflineSyncApplyChangesetResult,
144
+ type OfflineSyncFileContentChunk,
143
145
  type OfflineSyncSnapshot,
144
146
  } from "./offline-sync.js";
145
147
  import {
@@ -624,6 +626,15 @@ export interface EngramAccessOfflineSyncFilesRequest {
624
626
  paths: string[];
625
627
  }
626
628
 
629
+ export interface EngramAccessOfflineSyncFileContentRequest {
630
+ namespace?: string;
631
+ principal?: string;
632
+ includeTranscripts?: boolean;
633
+ path: string;
634
+ offset?: number;
635
+ length?: number;
636
+ }
637
+
627
638
  export interface EngramAccessOfflineSyncApplyRequest {
628
639
  namespace?: string;
629
640
  principal?: string;
@@ -638,6 +649,10 @@ export interface EngramAccessOfflineSyncFilesResponse extends OfflineSyncSnapsho
638
649
  namespace: string;
639
650
  }
640
651
 
652
+ export interface EngramAccessOfflineSyncFileContentResponse extends OfflineSyncFileContentChunk {
653
+ namespace: string;
654
+ }
655
+
641
656
  export interface EngramAccessOfflineSyncApplyResponse extends OfflineSyncApplyChangesetResult {
642
657
  namespace: string;
643
658
  }
@@ -5612,6 +5627,39 @@ export class EngramAccessService {
5612
5627
  }
5613
5628
  }
5614
5629
 
5630
+ async offlineSyncFileContent(
5631
+ options: EngramAccessOfflineSyncFileContentRequest,
5632
+ ): Promise<EngramAccessOfflineSyncFileContentResponse> {
5633
+ const resolvedNamespace = this.resolveReadableNamespace(options.namespace, options.principal);
5634
+ const storage = await this.orchestrator.getStorage(resolvedNamespace);
5635
+ try {
5636
+ const chunk = await readOfflineSyncFileContentChunk({
5637
+ root: storage.dir,
5638
+ path: options.path,
5639
+ offset: options.offset,
5640
+ length: options.length,
5641
+ includeTranscripts: options.includeTranscripts !== false,
5642
+ readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
5643
+ });
5644
+ return {
5645
+ namespace: resolvedNamespace,
5646
+ ...chunk,
5647
+ };
5648
+ } catch (error) {
5649
+ const message = error instanceof Error ? error.message : String(error);
5650
+ if (
5651
+ message.startsWith("path:") ||
5652
+ message.startsWith("offset ") ||
5653
+ message.startsWith("offset must ") ||
5654
+ message.startsWith("length ") ||
5655
+ message.startsWith("offline sync file content ")
5656
+ ) {
5657
+ throw new EngramAccessInputError(message);
5658
+ }
5659
+ throw error;
5660
+ }
5661
+ }
5662
+
5615
5663
  async offlineSyncApply(
5616
5664
  options: EngramAccessOfflineSyncApplyRequest,
5617
5665
  ): Promise<EngramAccessOfflineSyncApplyResponse> {
package/src/index.ts CHANGED
@@ -680,6 +680,7 @@ export {
680
680
 
681
681
  export {
682
682
  OFFLINE_SYNC_CHANGESET_FORMAT,
683
+ OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES,
683
684
  OFFLINE_SYNC_SNAPSHOT_FORMAT,
684
685
  OFFLINE_SYNC_STATE_VERSION,
685
686
  applyOfflineSyncChangeset,
@@ -692,6 +693,7 @@ export {
692
693
  normalizeOfflineSyncChangeset,
693
694
  normalizeOfflineSyncSnapshot,
694
695
  offlineSyncStateFromSnapshot,
696
+ readOfflineSyncFileContentChunk,
695
697
  readOfflineSyncState,
696
698
  summarizeOfflineSyncChangeset,
697
699
  writeOfflineSyncState,
@@ -701,6 +703,7 @@ export {
701
703
  type OfflineSyncChangeset,
702
704
  type OfflineSyncConflict,
703
705
  type OfflineSyncFileRecord,
706
+ type OfflineSyncFileContentChunk,
704
707
  type OfflineSyncFileState,
705
708
  type OfflineSyncFileTarget,
706
709
  type OfflineSyncFileWriteTarget,
@@ -1,4 +1,5 @@
1
1
  import assert from "node:assert/strict";
2
+ import { createHash } from "node:crypto";
2
3
  import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
3
4
  import os from "node:os";
4
5
  import path from "node:path";
@@ -10,6 +11,7 @@ import {
10
11
  buildOfflineSyncChangeset,
11
12
  buildOfflineSyncSnapshot,
12
13
  buildOfflineSyncSnapshotForPaths,
14
+ readOfflineSyncFileContentChunk,
13
15
  } from "./offline-sync.js";
14
16
  import { isEncryptedFile } from "./secure-store/secure-fs.js";
15
17
  import { StorageManager } from "./storage.js";
@@ -39,6 +41,9 @@ test("offline snapshot captures source-of-truth files and excludes private/inter
39
41
  await write(root, ".offline-sync/state/local.json", "state");
40
42
  await write(root, "state/fact-hashes.txt", "derived");
41
43
  await write(root, "state/fact-hashes.ready", "v1");
44
+ await write(root, "state/lcm.sqlite", "live db");
45
+ await write(root, "state/lcm.sqlite-shm", "live shm");
46
+ await write(root, "state/lcm.sqlite-wal", "live wal");
42
47
 
43
48
  const snapshot = await buildOfflineSyncSnapshot({
44
49
  root,
@@ -68,6 +73,82 @@ test("offline snapshot captures source-of-truth files and excludes private/inter
68
73
  }
69
74
  });
70
75
 
76
+ test("offline sync excludes live LCM sqlite artifacts without deleting existing local copies", async () => {
77
+ const root = await tempDir("remnic-offline-lcm-sqlite");
78
+ try {
79
+ await write(root, "facts/a.md", "alpha");
80
+ await write(root, "state/lcm.sqlite", "live db");
81
+ await write(root, "state/lcm.sqlite-shm", "live shm");
82
+ await write(root, "state/lcm.sqlite-wal", "live wal");
83
+
84
+ const snapshot = await buildOfflineSyncSnapshot({
85
+ root,
86
+ sourceId: "remote",
87
+ includeContent: true,
88
+ });
89
+
90
+ assert.deepEqual(snapshot.files.map((file) => file.path), ["facts/a.md"]);
91
+ await assert.rejects(
92
+ () =>
93
+ buildOfflineSyncSnapshotForPaths({
94
+ root,
95
+ sourceId: "remote",
96
+ paths: ["state/lcm.sqlite"],
97
+ includeContent: true,
98
+ }),
99
+ /offline sync snapshot path is excluded: state\/lcm\.sqlite/,
100
+ );
101
+
102
+ const oldDb = Buffer.from("old live db");
103
+ const pull = await applyOfflineSyncSnapshot({
104
+ root,
105
+ snapshot,
106
+ baseFiles: [{
107
+ path: "state/lcm.sqlite",
108
+ sha256: createHash("sha256").update(oldDb).digest("hex"),
109
+ bytes: oldDb.byteLength,
110
+ mtimeMs: 0,
111
+ }],
112
+ });
113
+
114
+ assert.equal(pull.deleted, 0);
115
+ assert.equal(await readUtf8(root, "state/lcm.sqlite"), "live db");
116
+ } finally {
117
+ await rm(root, { recursive: true, force: true });
118
+ }
119
+ });
120
+
121
+ test("offline sync reads bounded file content chunks with metadata", async () => {
122
+ const root = await tempDir("remnic-offline-file-content");
123
+ try {
124
+ await write(root, "state/memory-lifecycle-ledger.jsonl", "alpha\nbeta\ngamma\n");
125
+
126
+ const chunk = await readOfflineSyncFileContentChunk({
127
+ root,
128
+ path: "state/memory-lifecycle-ledger.jsonl",
129
+ offset: 6,
130
+ length: 5,
131
+ });
132
+
133
+ assert.equal(chunk.path, "state/memory-lifecycle-ledger.jsonl");
134
+ assert.equal(chunk.offset, 6);
135
+ assert.equal(chunk.chunkBytes, 5);
136
+ assert.equal(chunk.content.toString("utf-8"), "beta\n");
137
+ assert.equal(chunk.bytes, Buffer.byteLength("alpha\nbeta\ngamma\n"));
138
+
139
+ await assert.rejects(
140
+ () =>
141
+ readOfflineSyncFileContentChunk({
142
+ root,
143
+ path: "state/lcm.sqlite",
144
+ }),
145
+ /offline sync file content path is excluded: state\/lcm\.sqlite/,
146
+ );
147
+ } finally {
148
+ await rm(root, { recursive: true, force: true });
149
+ }
150
+ });
151
+
71
152
  test("offline changeset pushes local edits when the remote is still at the shared base", async () => {
72
153
  const remote = await tempDir("remnic-offline-remote");
73
154
  const local = await tempDir("remnic-offline-local");