@remnic/core 1.1.29 → 1.1.31

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 (100) hide show
  1. package/dist/access-cli.js +13 -13
  2. package/dist/access-http.d.ts +2 -1
  3. package/dist/access-http.js +8 -8
  4. package/dist/access-mcp.d.ts +1 -1
  5. package/dist/access-mcp.js +7 -7
  6. package/dist/access-schema.d.ts +55 -5
  7. package/dist/access-schema.js +4 -2
  8. package/dist/{access-service-CEyV8XJ5.d.ts → access-service-CkZyb35d.d.ts} +10 -2
  9. package/dist/access-service.d.ts +1 -1
  10. package/dist/access-service.js +5 -5
  11. package/dist/briefing.js +2 -2
  12. package/dist/causal-consolidation.js +3 -3
  13. package/dist/{chunk-25YQM6XW.js → chunk-2IWUMAES.js} +3 -3
  14. package/dist/{chunk-6CB4E7ZV.js → chunk-3ZLVGM76.js} +4 -4
  15. package/dist/{chunk-QYHQ2JHL.js → chunk-43PJZYGL.js} +2 -2
  16. package/dist/{chunk-YITUHONZ.js → chunk-4KGVTPGD.js} +2 -2
  17. package/dist/{chunk-TR4DK5OH.js → chunk-76FLAAUC.js} +2 -2
  18. package/dist/{chunk-6BFAEWQS.js → chunk-77H5NU3M.js} +2 -2
  19. package/dist/{chunk-IANK6Y5W.js → chunk-A6KTB5R6.js} +2 -2
  20. package/dist/{chunk-7D6O46PF.js → chunk-BVF3AGJP.js} +2 -2
  21. package/dist/{chunk-4H6DURG6.js → chunk-JA3AK3PT.js} +2 -2
  22. package/dist/{chunk-RCZRL5BE.js → chunk-MRILGULB.js} +2 -2
  23. package/dist/{chunk-CWWDIQZB.js → chunk-QLLBRHAT.js} +8 -8
  24. package/dist/{chunk-2WIPXV3Y.js → chunk-RR2PKP3I.js} +2 -2
  25. package/dist/{chunk-3F24QTRI.js → chunk-SAZS2QZB.js} +2 -2
  26. package/dist/{chunk-VYU7PXUS.js → chunk-SIC6U3GZ.js} +2 -2
  27. package/dist/{chunk-WDSIV3AK.js → chunk-TPU5L5EY.js} +12 -12
  28. package/dist/{chunk-AMVN77EU.js → chunk-U7EJOMFC.js} +371 -91
  29. package/dist/chunk-U7EJOMFC.js.map +1 -0
  30. package/dist/{chunk-F33CJ5CH.js → chunk-VBJ7V5SK.js} +40 -8
  31. package/dist/chunk-VBJ7V5SK.js.map +1 -0
  32. package/dist/{chunk-6WV2HYTZ.js → chunk-W6AQJ2PY.js} +4 -4
  33. package/dist/{chunk-PUXCIHRL.js → chunk-XSZEP4SF.js} +2 -2
  34. package/dist/{chunk-NW7JW5GA.js → chunk-YROHKYBY.js} +41 -6
  35. package/dist/chunk-YROHKYBY.js.map +1 -0
  36. package/dist/{chunk-JUYT2J3K.js → chunk-YU5KIWYQ.js} +136 -8
  37. package/dist/chunk-YU5KIWYQ.js.map +1 -0
  38. package/dist/{chunk-LCTP7YRU.js → chunk-ZAVUCJ4H.js} +38 -7
  39. package/dist/chunk-ZAVUCJ4H.js.map +1 -0
  40. package/dist/{cli-BguVmIwO.d.ts → cli-kuh9PwZ5.d.ts} +1 -1
  41. package/dist/cli.d.ts +2 -2
  42. package/dist/cli.js +17 -17
  43. package/dist/compounding/engine.js +2 -2
  44. package/dist/connectors/codex-materialize-runner.js +2 -2
  45. package/dist/connectors/index.js +2 -2
  46. package/dist/entity-retrieval.js +2 -2
  47. package/dist/index.d.ts +4 -4
  48. package/dist/index.js +34 -22
  49. package/dist/index.js.map +1 -1
  50. package/dist/maintenance/memory-governance.js +2 -2
  51. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +2 -2
  52. package/dist/maintenance/rebuild-memory-projection.js +3 -3
  53. package/dist/mcp-memory-inspector-app.d.ts +1 -1
  54. package/dist/namespaces/migrate.js +3 -3
  55. package/dist/namespaces/storage.js +2 -2
  56. package/dist/offline-sync.d.ts +56 -1
  57. package/dist/offline-sync.js +15 -1
  58. package/dist/operator-toolkit.js +5 -5
  59. package/dist/orchestrator.js +9 -9
  60. package/dist/schemas.d.ts +22 -22
  61. package/dist/semantic-consolidation.js +3 -3
  62. package/dist/semantic-rule-promotion.js +2 -2
  63. package/dist/semantic-rule-verifier.js +2 -2
  64. package/dist/storage.d.ts +5 -0
  65. package/dist/storage.js +1 -1
  66. package/dist/transfer/types.d.ts +12 -12
  67. package/dist/verified-recall.js +2 -2
  68. package/package.json +1 -1
  69. package/src/access-http.test.ts +355 -0
  70. package/src/access-http.ts +149 -1
  71. package/src/access-schema.ts +58 -3
  72. package/src/access-service-namespace.test.ts +56 -1
  73. package/src/access-service-offline-file-content.test.ts +17 -0
  74. package/src/access-service.ts +47 -1
  75. package/src/index.ts +7 -0
  76. package/src/offline-sync.test.ts +1055 -1
  77. package/src/offline-sync.ts +465 -97
  78. package/src/storage.ts +36 -2
  79. package/dist/chunk-AMVN77EU.js.map +0 -1
  80. package/dist/chunk-F33CJ5CH.js.map +0 -1
  81. package/dist/chunk-JUYT2J3K.js.map +0 -1
  82. package/dist/chunk-LCTP7YRU.js.map +0 -1
  83. package/dist/chunk-NW7JW5GA.js.map +0 -1
  84. /package/dist/{chunk-25YQM6XW.js.map → chunk-2IWUMAES.js.map} +0 -0
  85. /package/dist/{chunk-6CB4E7ZV.js.map → chunk-3ZLVGM76.js.map} +0 -0
  86. /package/dist/{chunk-QYHQ2JHL.js.map → chunk-43PJZYGL.js.map} +0 -0
  87. /package/dist/{chunk-YITUHONZ.js.map → chunk-4KGVTPGD.js.map} +0 -0
  88. /package/dist/{chunk-TR4DK5OH.js.map → chunk-76FLAAUC.js.map} +0 -0
  89. /package/dist/{chunk-6BFAEWQS.js.map → chunk-77H5NU3M.js.map} +0 -0
  90. /package/dist/{chunk-IANK6Y5W.js.map → chunk-A6KTB5R6.js.map} +0 -0
  91. /package/dist/{chunk-7D6O46PF.js.map → chunk-BVF3AGJP.js.map} +0 -0
  92. /package/dist/{chunk-4H6DURG6.js.map → chunk-JA3AK3PT.js.map} +0 -0
  93. /package/dist/{chunk-RCZRL5BE.js.map → chunk-MRILGULB.js.map} +0 -0
  94. /package/dist/{chunk-CWWDIQZB.js.map → chunk-QLLBRHAT.js.map} +0 -0
  95. /package/dist/{chunk-2WIPXV3Y.js.map → chunk-RR2PKP3I.js.map} +0 -0
  96. /package/dist/{chunk-3F24QTRI.js.map → chunk-SAZS2QZB.js.map} +0 -0
  97. /package/dist/{chunk-VYU7PXUS.js.map → chunk-SIC6U3GZ.js.map} +0 -0
  98. /package/dist/{chunk-WDSIV3AK.js.map → chunk-TPU5L5EY.js.map} +0 -0
  99. /package/dist/{chunk-6WV2HYTZ.js.map → chunk-W6AQJ2PY.js.map} +0 -0
  100. /package/dist/{chunk-PUXCIHRL.js.map → chunk-XSZEP4SF.js.map} +0 -0
@@ -17,6 +17,7 @@ import {
17
17
 
18
18
  // src/offline-sync.ts
19
19
  import { createHash, randomUUID } from "crypto";
20
+ import { createReadStream } from "fs";
20
21
  import {
21
22
  lstat,
22
23
  mkdir,
@@ -27,6 +28,7 @@ import {
27
28
  rm,
28
29
  stat,
29
30
  unlink,
31
+ utimes,
30
32
  writeFile
31
33
  } from "fs/promises";
32
34
  import path from "path";
@@ -36,8 +38,11 @@ var OFFLINE_SYNC_STATE_VERSION = 1;
36
38
  var OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES = 64 * 1024 * 1024;
37
39
  var OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES = 8 * 1024 * 1024;
38
40
  var OFFLINE_SYNC_APPLY_MAX_BODY_BYTES = 16 * 1024 * 1024;
41
+ var OFFLINE_SYNC_SNAPSHOT_BASE_MAX_BODY_BYTES = 64 * 1024 * 1024;
42
+ var OFFLINE_SYNC_MAX_MTIME_MS = 864e13;
39
43
  var SYNC_INTERNAL_DIR = ".offline-sync";
40
44
  var OFFLINE_SYNC_UPLOAD_STAGING_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
45
+ var OFFLINE_SYNC_FAST_BASE_MTIME_TOLERANCE_MS = 1e3;
41
46
  var EXCLUDED_FILE_NAMES = /* @__PURE__ */ new Set([
42
47
  ".sync-state.json"
43
48
  ]);
@@ -72,6 +77,13 @@ function assertNonNegativeFinite(value, field) {
72
77
  }
73
78
  return value;
74
79
  }
80
+ function assertOfflineSyncMtimeMs(value, field) {
81
+ const mtimeMs = assertNonNegativeFinite(value, field);
82
+ if (mtimeMs > OFFLINE_SYNC_MAX_MTIME_MS) {
83
+ throw new Error(`${field} must be within JavaScript Date range`);
84
+ }
85
+ return mtimeMs;
86
+ }
75
87
  function assertBoolean(value, field) {
76
88
  if (typeof value !== "boolean") {
77
89
  throw new Error(`${field} must be a boolean`);
@@ -94,7 +106,7 @@ function normalizeFileState(input, fieldPrefix) {
94
106
  path: relPath,
95
107
  sha256: assertSha256(obj.sha256, `${fieldPrefix}.sha256`),
96
108
  bytes: assertNonNegativeInteger(obj.bytes, `${fieldPrefix}.bytes`),
97
- mtimeMs: assertNonNegativeFinite(obj.mtimeMs, `${fieldPrefix}.mtimeMs`)
109
+ mtimeMs: assertOfflineSyncMtimeMs(obj.mtimeMs, `${fieldPrefix}.mtimeMs`)
98
110
  };
99
111
  }
100
112
  function normalizeFileRecord(input, fieldPrefix, requireContent) {
@@ -276,20 +288,72 @@ function isCanonicalRuntimeStatePath(parts) {
276
288
  if (parts[0] === "state") return true;
277
289
  return parts[0] === "namespaces" && parts.length >= 4 && parts[2] === "state";
278
290
  }
291
+ var REMOTE_AUTHORITATIVE_RUNTIME_STATE_FILES = /* @__PURE__ */ new Set([
292
+ ".artifact-write-version.log",
293
+ ".memory-status-version.log",
294
+ "buffer.json",
295
+ "embeddings.json",
296
+ "index_time.json",
297
+ "last_recall.json",
298
+ "memory-lifecycle-ledger.jsonl",
299
+ "recall_impressions.jsonl"
300
+ ]);
301
+ function shouldPreferIncomingOfflineRuntimeFile(relPosix) {
302
+ const parts = relPosix.split("/");
303
+ const basename = parts[parts.length - 1] ?? "";
304
+ return isCanonicalRuntimeStatePath(parts) && REMOTE_AUTHORITATIVE_RUNTIME_STATE_FILES.has(basename);
305
+ }
279
306
  function filterBaseFilesForMode(files, includeTranscripts) {
280
307
  return files.filter((file) => !shouldExcludeRelPath(file.path, includeTranscripts));
281
308
  }
309
+ function canReuseFastBaseFileState(baseEntry, st, baseCapturedAtMs) {
310
+ if (baseEntry.bytes !== st.size) return false;
311
+ if (Math.abs(baseEntry.mtimeMs - st.mtimeMs) > OFFLINE_SYNC_FAST_BASE_MTIME_TOLERANCE_MS) {
312
+ return false;
313
+ }
314
+ if (baseCapturedAtMs === null) return false;
315
+ return st.ctimeMs - baseCapturedAtMs <= OFFLINE_SYNC_FAST_BASE_MTIME_TOLERANCE_MS;
316
+ }
317
+ async function canReuseFastBaseFileStateFromDisk(baseEntry, filePath, st, baseCapturedAtMs) {
318
+ if (!canReuseFastBaseFileState(baseEntry, st, baseCapturedAtMs)) return false;
319
+ return !await fileIsSecureStoreEncrypted(filePath).catch(() => true);
320
+ }
282
321
  async function readOfflineSyncFileRecord(options) {
283
322
  const relPath = validateArchiveRelativePath(options.relPath, "offlineSyncFile.path");
284
- const bytes = options.readFile ? await options.readFile({ root: options.root.abs, path: relPath, filePath: options.filePath }) : await readFile(options.filePath);
285
- const digest = sha256Buffer(bytes);
323
+ let content = null;
324
+ let digest;
325
+ if (options.includeContent) {
326
+ content = options.readFile ? await options.readFile({ root: options.root.abs, path: relPath, filePath: options.filePath }) : await readFile(options.filePath);
327
+ digest = sha256Buffer(content);
328
+ } else if (options.readFileDigest) {
329
+ digest = await options.readFileDigest({ root: options.root.abs, path: relPath, filePath: options.filePath });
330
+ } else if (options.readFile) {
331
+ content = await options.readFile({ root: options.root.abs, path: relPath, filePath: options.filePath });
332
+ digest = sha256Buffer(content);
333
+ content = null;
334
+ } else {
335
+ digest = await sha256File(options.filePath);
336
+ }
286
337
  const st = await stat(options.filePath);
287
338
  return {
288
339
  path: relPath,
289
340
  sha256: digest.sha256,
290
341
  bytes: digest.bytes,
291
342
  mtimeMs: st.mtimeMs,
292
- ...options.includeContent ? { contentBase64: bytes.toString("base64") } : {}
343
+ ...content ? { contentBase64: content.toString("base64") } : {}
344
+ };
345
+ }
346
+ async function sha256File(filePath) {
347
+ const hash = createHash("sha256");
348
+ let bytes = 0;
349
+ for await (const chunk of createReadStream(filePath)) {
350
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
351
+ hash.update(buffer);
352
+ bytes += buffer.length;
353
+ }
354
+ return {
355
+ sha256: hash.digest("hex"),
356
+ bytes
293
357
  };
294
358
  }
295
359
  async function fileIsSecureStoreEncrypted(filePath) {
@@ -314,10 +378,58 @@ async function readPlainFileContentChunk(options) {
314
378
  await handle.close();
315
379
  }
316
380
  }
381
+ async function* iterateOfflineSyncSnapshotFileRecords(options) {
382
+ const rootAbs = path.resolve(options.root);
383
+ const root = await prepareSafeArchiveRoot(rootAbs, "iterateOfflineSyncSnapshotFileRecords", "root");
384
+ const includeTranscripts = options.includeTranscripts !== false;
385
+ async function* walk(dirAbs) {
386
+ let entries = await readdir(dirAbs, { withFileTypes: true });
387
+ entries = entries.sort((left, right) => left.name.localeCompare(right.name));
388
+ for (const entry of entries) {
389
+ const abs = path.join(dirAbs, entry.name);
390
+ const relPosix = path.relative(root.abs, abs).split(path.sep).join("/");
391
+ if (shouldExcludeRelPath(relPosix, includeTranscripts)) continue;
392
+ if (entry.isSymbolicLink()) continue;
393
+ if (entry.isDirectory()) {
394
+ yield* walk(abs);
395
+ continue;
396
+ }
397
+ if (!entry.isFile()) continue;
398
+ yield await readOfflineSyncFileRecord({
399
+ root,
400
+ relPath: relPosix,
401
+ filePath: abs,
402
+ includeContent: options.includeContent === true,
403
+ readFile: options.readFile,
404
+ readFileDigest: options.readFileDigest
405
+ });
406
+ }
407
+ }
408
+ yield* walk(root.abs);
409
+ }
317
410
  async function buildOfflineSyncSnapshot(options) {
411
+ const includeTranscripts = options.includeTranscripts !== false;
412
+ const files = [];
413
+ for await (const file of iterateOfflineSyncSnapshotFileRecords(options)) files.push(file);
414
+ return {
415
+ format: OFFLINE_SYNC_SNAPSHOT_FORMAT,
416
+ schemaVersion: 1,
417
+ createdAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString(),
418
+ sourceId: normalizeSourceId(options.sourceId, "sourceId"),
419
+ includeTranscripts,
420
+ files: files.sort(compareByPath)
421
+ };
422
+ }
423
+ async function buildOfflineSyncSnapshotFromBase(options) {
318
424
  const rootAbs = path.resolve(options.root);
319
- const root = await prepareSafeArchiveRoot(rootAbs, "buildOfflineSyncSnapshot", "root");
425
+ const root = await prepareSafeArchiveRoot(rootAbs, "buildOfflineSyncSnapshotFromBase", "root");
320
426
  const includeTranscripts = options.includeTranscripts !== false;
427
+ const base = byPath(filterBaseFilesForMode(
428
+ normalizeFileStates(options.baseFiles),
429
+ includeTranscripts
430
+ ));
431
+ const rawBaseCapturedAtMs = options.baseCapturedAt?.getTime();
432
+ const baseCapturedAtMs = rawBaseCapturedAtMs !== void 0 && Number.isFinite(rawBaseCapturedAtMs) ? rawBaseCapturedAtMs : null;
321
433
  const files = [];
322
434
  async function walk(dirAbs) {
323
435
  let entries = await readdir(dirAbs, { withFileTypes: true });
@@ -332,12 +444,19 @@ async function buildOfflineSyncSnapshot(options) {
332
444
  continue;
333
445
  }
334
446
  if (!entry.isFile()) continue;
447
+ const st = await stat(abs);
448
+ const baseEntry = base.get(relPosix);
449
+ if (options.includeContent !== true && baseEntry && baseCapturedAtMs !== null && await canReuseFastBaseFileStateFromDisk(baseEntry, abs, st, baseCapturedAtMs)) {
450
+ files.push(baseEntry);
451
+ continue;
452
+ }
335
453
  files.push(await readOfflineSyncFileRecord({
336
454
  root,
337
455
  relPath: relPosix,
338
456
  filePath: abs,
339
457
  includeContent: options.includeContent === true,
340
- readFile: options.readFile
458
+ readFile: options.readFile,
459
+ readFileDigest: options.readFileDigest
341
460
  }));
342
461
  }
343
462
  }
@@ -375,7 +494,8 @@ async function buildOfflineSyncSnapshotForPaths(options) {
375
494
  relPath,
376
495
  filePath,
377
496
  includeContent: options.includeContent === true,
378
- readFile: options.readFile
497
+ readFile: options.readFile,
498
+ readFileDigest: options.readFileDigest
379
499
  }));
380
500
  }
381
501
  return {
@@ -452,25 +572,45 @@ async function readOfflineSyncFileContentChunk(options) {
452
572
  }
453
573
  async function buildOfflineSyncChangeset(options) {
454
574
  const includeTranscripts = options.includeTranscripts !== false;
455
- const excludedPaths = new Set(
456
- (options.excludePaths ?? []).map((relPath) => normalizeRelativePath(relPath, "excludePaths[]"))
457
- );
458
- const base = byPath(filterBaseFilesForMode(
459
- normalizeFileStates(options.baseFiles),
460
- includeTranscripts
461
- ));
462
- const current = await buildOfflineSyncSnapshot({
575
+ const current = await buildOfflineSyncSnapshotFromBase({
463
576
  root: options.root,
464
577
  sourceId: options.sourceId,
578
+ baseFiles: options.baseFiles,
579
+ baseCapturedAt: options.baseCapturedAt,
465
580
  includeContent: false,
466
581
  includeTranscripts,
467
582
  now: options.now,
583
+ readFile: options.readFile,
584
+ readFileDigest: options.readFileDigest
585
+ });
586
+ return buildOfflineSyncChangesetFromSnapshot({
587
+ root: options.root,
588
+ sourceId: options.sourceId,
589
+ baseFiles: options.baseFiles,
590
+ currentFiles: current.files,
591
+ excludePaths: options.excludePaths,
592
+ includeTranscripts,
593
+ now: options.now,
468
594
  readFile: options.readFile
469
595
  });
470
- const currentMap = byPath(current.files);
596
+ }
597
+ async function buildOfflineSyncChangesetFromSnapshot(options) {
598
+ const includeTranscripts = options.includeTranscripts !== false;
599
+ const excludedPaths = new Set(
600
+ (options.excludePaths ?? []).map((relPath) => normalizeRelativePath(relPath, "excludePaths[]"))
601
+ );
602
+ const base = byPath(filterBaseFilesForMode(
603
+ normalizeFileStates(options.baseFiles),
604
+ includeTranscripts
605
+ ));
606
+ const currentMap = byPath(filterBaseFilesForMode(
607
+ normalizeFileStates(options.currentFiles),
608
+ includeTranscripts
609
+ ));
471
610
  const changes = [];
472
611
  for (const relPath of unionPaths(base, currentMap)) {
473
612
  if (excludedPaths.has(relPath)) continue;
613
+ if (shouldPreferIncomingOfflineRuntimeFile(relPath)) continue;
474
614
  const baseEntry = base.get(relPath);
475
615
  const currentEntry = currentMap.get(relPath);
476
616
  if (currentEntry && currentEntry.sha256 !== baseEntry?.sha256) {
@@ -508,7 +648,7 @@ async function buildOfflineSyncChangeset(options) {
508
648
  schemaVersion: 1,
509
649
  createdAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString(),
510
650
  sourceId: normalizeSourceId(options.sourceId, "sourceId"),
511
- includeTranscripts: current.includeTranscripts,
651
+ includeTranscripts,
512
652
  changes: changes.sort(compareByPath)
513
653
  };
514
654
  }
@@ -523,22 +663,37 @@ function summarizeOfflineSyncChangeset(changeset) {
523
663
  }
524
664
  async function summarizeOfflineSyncPendingChanges(options) {
525
665
  const includeTranscripts = options.includeTranscripts !== false;
526
- const base = byPath(filterBaseFilesForMode(
527
- normalizeFileStates(options.baseFiles),
528
- includeTranscripts
529
- ));
530
- const current = await buildOfflineSyncSnapshot({
666
+ const current = await buildOfflineSyncSnapshotFromBase({
531
667
  root: options.root,
532
668
  sourceId: options.sourceId,
669
+ baseFiles: options.baseFiles,
670
+ baseCapturedAt: options.baseCapturedAt,
533
671
  includeContent: false,
534
672
  includeTranscripts,
535
673
  now: options.now,
536
- readFile: options.readFile
674
+ readFile: options.readFile,
675
+ readFileDigest: options.readFileDigest
537
676
  });
538
- const currentMap = byPath(current.files);
677
+ return summarizeOfflineSyncPendingFiles({
678
+ baseFiles: options.baseFiles,
679
+ currentFiles: current.files,
680
+ includeTranscripts
681
+ });
682
+ }
683
+ function summarizeOfflineSyncPendingFiles(options) {
684
+ const includeTranscripts = options.includeTranscripts !== false;
685
+ const base = byPath(filterBaseFilesForMode(
686
+ normalizeFileStates(options.baseFiles),
687
+ includeTranscripts
688
+ ));
689
+ const currentMap = byPath(filterBaseFilesForMode(
690
+ normalizeFileStates(options.currentFiles),
691
+ includeTranscripts
692
+ ));
539
693
  let upserts = 0;
540
694
  let deletes = 0;
541
695
  for (const relPath of unionPaths(base, currentMap)) {
696
+ if (shouldPreferIncomingOfflineRuntimeFile(relPath)) continue;
542
697
  const baseEntry = base.get(relPath);
543
698
  const currentEntry = currentMap.get(relPath);
544
699
  if (currentEntry && currentEntry.sha256 !== baseEntry?.sha256) {
@@ -566,30 +721,61 @@ async function applyOfflineSyncSnapshot(options) {
566
721
  requireContent: false
567
722
  });
568
723
  const root = await ensureSyncRoot(options.root, "applyOfflineSyncSnapshot");
569
- const current = await buildOfflineSyncSnapshot({
724
+ const currentFiles = options.currentFiles ? filterBaseFilesForMode(normalizeFileStates(options.currentFiles), snapshot.includeTranscripts).sort(compareByPath) : (await buildOfflineSyncSnapshot({
570
725
  root: root.abs,
571
726
  sourceId: "local",
572
727
  includeContent: false,
573
728
  includeTranscripts: snapshot.includeTranscripts,
574
- readFile: options.readFile
575
- });
576
- const currentMap = byPath(current.files);
729
+ readFile: options.readFile,
730
+ readFileDigest: options.readFileDigest
731
+ })).files;
732
+ const currentMap = byPath(currentFiles);
733
+ const deferredPaths = new Set(options.deferredPaths ?? []);
577
734
  const nextBase = new Map(baseMap);
578
735
  const conflicts = [];
579
736
  let upserted = 0;
580
737
  let deleted = 0;
581
738
  let skipped = 0;
582
739
  let pendingLocal = 0;
740
+ const conflictIncomingBuffer = (relPath) => {
741
+ if (options.writeConflictCopies === false) return void 0;
742
+ const buffer = incomingBuffers.get(relPath);
743
+ if (buffer || options.allowMissingConflictContent === true) return buffer;
744
+ return requiredBuffer(incomingBuffers, relPath);
745
+ };
583
746
  for (const relPath of unionPaths(baseMap, incomingMap, currentMap)) {
584
747
  const base = baseMap.get(relPath);
585
748
  const incoming = incomingMap.get(relPath);
586
749
  const currentEntry = currentMap.get(relPath);
750
+ if (deferredPaths.has(relPath)) {
751
+ if (base) nextBase.set(relPath, base);
752
+ else nextBase.delete(relPath);
753
+ skipped += 1;
754
+ continue;
755
+ }
587
756
  if (incoming) {
588
757
  if (currentEntry?.sha256 === incoming.sha256) {
589
- nextBase.set(relPath, toFileState(incoming));
758
+ if (await setSafeFileMtime(root, relPath, incoming.mtimeMs)) {
759
+ nextBase.set(relPath, toFileState(incoming));
760
+ } else {
761
+ if (base) nextBase.set(relPath, base);
762
+ else nextBase.delete(relPath);
763
+ pendingLocal += 1;
764
+ }
590
765
  skipped += 1;
591
766
  continue;
592
767
  }
768
+ if (shouldPreferIncomingOfflineRuntimeFile(relPath) && currentEntry && base && incoming.sha256 === base.sha256) {
769
+ nextBase.set(relPath, base);
770
+ skipped += 1;
771
+ continue;
772
+ }
773
+ if (shouldPreferIncomingOfflineRuntimeFile(relPath)) {
774
+ await writeSafeFile(root, relPath, requiredBuffer(incomingBuffers, relPath), options.writeFile, incoming.mtimeMs);
775
+ nextBase.set(relPath, toFileState(incoming));
776
+ upserted += 1;
777
+ continue;
778
+ }
593
779
  if (!currentEntry && base && incoming.sha256 === base.sha256) {
594
780
  nextBase.set(relPath, base);
595
781
  pendingLocal += 1;
@@ -603,7 +789,7 @@ async function applyOfflineSyncSnapshot(options) {
603
789
  reason: "local_deleted_remote_modified",
604
790
  baseSha256: base.sha256,
605
791
  incomingSha256: incoming.sha256,
606
- incomingBuffer: options.writeConflictCopies === false ? incomingBuffers.get(relPath) : requiredBuffer(incomingBuffers, relPath),
792
+ incomingBuffer: conflictIncomingBuffer(relPath),
607
793
  writeConflictCopies: options.writeConflictCopies !== false,
608
794
  sourceId: snapshot.sourceId,
609
795
  writeFile: options.writeFile
@@ -612,13 +798,13 @@ async function applyOfflineSyncSnapshot(options) {
612
798
  continue;
613
799
  }
614
800
  if (!currentEntry && !base) {
615
- await writeSafeFile(root, relPath, requiredBuffer(incomingBuffers, relPath), options.writeFile);
801
+ await writeSafeFile(root, relPath, requiredBuffer(incomingBuffers, relPath), options.writeFile, incoming.mtimeMs);
616
802
  nextBase.set(relPath, toFileState(incoming));
617
803
  upserted += 1;
618
804
  continue;
619
805
  }
620
806
  if (base && currentEntry && currentEntry.sha256 === base.sha256) {
621
- await writeSafeFile(root, relPath, requiredBuffer(incomingBuffers, relPath), options.writeFile);
807
+ await writeSafeFile(root, relPath, requiredBuffer(incomingBuffers, relPath), options.writeFile, incoming.mtimeMs);
622
808
  nextBase.set(relPath, toFileState(incoming));
623
809
  upserted += 1;
624
810
  continue;
@@ -636,7 +822,7 @@ async function applyOfflineSyncSnapshot(options) {
636
822
  baseSha256: base?.sha256,
637
823
  localSha256: currentEntry?.sha256,
638
824
  incomingSha256: incoming.sha256,
639
- incomingBuffer: options.writeConflictCopies === false ? incomingBuffers.get(relPath) : requiredBuffer(incomingBuffers, relPath),
825
+ incomingBuffer: conflictIncomingBuffer(relPath),
640
826
  writeConflictCopies: options.writeConflictCopies !== false,
641
827
  sourceId: snapshot.sourceId,
642
828
  writeFile: options.writeFile
@@ -649,6 +835,17 @@ async function applyOfflineSyncSnapshot(options) {
649
835
  skipped += 1;
650
836
  continue;
651
837
  }
838
+ if (shouldPreferIncomingOfflineRuntimeFile(relPath) && base) {
839
+ await deleteSafeFile(root, relPath, options.deleteFile);
840
+ nextBase.delete(relPath);
841
+ deleted += 1;
842
+ continue;
843
+ }
844
+ if (shouldPreferIncomingOfflineRuntimeFile(relPath)) {
845
+ pendingLocal += 1;
846
+ skipped += 1;
847
+ continue;
848
+ }
652
849
  if (base && currentEntry.sha256 === base.sha256) {
653
850
  await deleteSafeFile(root, relPath, options.deleteFile);
654
851
  nextBase.delete(relPath);
@@ -690,14 +887,16 @@ async function applyOfflineSyncChangeset(options) {
690
887
  const root = await ensureSyncRoot(options.root, "applyOfflineSyncChangeset");
691
888
  const records = changeset.changes.filter((change) => change.type === "upsert").map((change) => change.file);
692
889
  const incomingBuffers = verifyRecordContents(records, "offline sync changeset");
693
- const current = await buildOfflineSyncSnapshot({
890
+ const currentFiles = options.currentFiles ? filterBaseFilesForMode(normalizeFileStates(options.currentFiles), changeset.includeTranscripts).sort(compareByPath) : (await buildOfflineSyncSnapshotForPaths({
694
891
  root: root.abs,
695
892
  sourceId: "local",
893
+ paths: changeset.changes.map((change) => change.path),
696
894
  includeContent: false,
697
895
  includeTranscripts: changeset.includeTranscripts,
698
- readFile: options.readFile
699
- });
700
- const currentMap = byPath(current.files);
896
+ readFile: options.readFile,
897
+ readFileDigest: options.readFileDigest
898
+ })).files;
899
+ const currentMap = byPath(currentFiles);
701
900
  const conflicts = [];
702
901
  let appliedUpserts = 0;
703
902
  let appliedDeletes = 0;
@@ -706,12 +905,18 @@ async function applyOfflineSyncChangeset(options) {
706
905
  const currentEntry = currentMap.get(change.path);
707
906
  if (change.type === "upsert") {
708
907
  if (currentEntry?.sha256 === change.file.sha256) {
709
- skipped += 1;
908
+ if (await setSafeFileMtime(root, change.path, change.file.mtimeMs)) {
909
+ skipped += 1;
910
+ } else {
911
+ await writeSafeFile(root, change.path, requiredBuffer(incomingBuffers, change.path), options.writeFile, change.file.mtimeMs);
912
+ currentMap.set(change.path, toFileState(change.file));
913
+ appliedUpserts += 1;
914
+ }
710
915
  continue;
711
916
  }
712
917
  if (!change.baseSha256) {
713
918
  if (!currentEntry) {
714
- await writeSafeFile(root, change.path, requiredBuffer(incomingBuffers, change.path), options.writeFile);
919
+ await writeSafeFile(root, change.path, requiredBuffer(incomingBuffers, change.path), options.writeFile, change.file.mtimeMs);
715
920
  currentMap.set(change.path, toFileState(change.file));
716
921
  appliedUpserts += 1;
717
922
  continue;
@@ -730,7 +935,7 @@ async function applyOfflineSyncChangeset(options) {
730
935
  continue;
731
936
  }
732
937
  if (currentEntry?.sha256 === change.baseSha256) {
733
- await writeSafeFile(root, change.path, requiredBuffer(incomingBuffers, change.path), options.writeFile);
938
+ await writeSafeFile(root, change.path, requiredBuffer(incomingBuffers, change.path), options.writeFile, change.file.mtimeMs);
734
939
  currentMap.set(change.path, toFileState(change.file));
735
940
  appliedUpserts += 1;
736
941
  continue;
@@ -771,7 +976,15 @@ async function applyOfflineSyncChangeset(options) {
771
976
  appliedDeletes,
772
977
  skipped,
773
978
  conflicts,
774
- currentFiles: [...currentMap.values()].sort(compareByPath)
979
+ currentFiles: options.returnCurrentFiles === false ? [...currentMap.values()].sort(compareByPath) : (await buildOfflineSyncSnapshot({
980
+ root: root.abs,
981
+ sourceId: "local",
982
+ includeContent: false,
983
+ includeTranscripts: changeset.includeTranscripts,
984
+ readFile: options.readFile,
985
+ readFileDigest: options.readFileDigest
986
+ })).files,
987
+ ...options.returnCurrentFiles === false ? { currentFilesComplete: false } : {}
775
988
  };
776
989
  }
777
990
  function verifyRecordContents(records, context, options = {}) {
@@ -826,10 +1039,11 @@ function toFileState(file) {
826
1039
  mtimeMs: file.mtimeMs
827
1040
  };
828
1041
  }
829
- async function writeSafeFile(root, relPath, content, writeFileHook) {
1042
+ async function writeSafeFile(root, relPath, content, writeFileHook, mtimeMs) {
830
1043
  const target = await resolveSafeArchiveTarget(root, relPath);
831
1044
  if (writeFileHook) {
832
1045
  await writeFileHook({ root: root.abs, path: relPath, filePath: target, content });
1046
+ await setSafeFileMtime(root, relPath, mtimeMs);
833
1047
  return;
834
1048
  }
835
1049
  await mkdir(path.dirname(target), { recursive: true });
@@ -847,12 +1061,28 @@ async function writeSafeFile(root, relPath, content, writeFileHook) {
847
1061
  throw new Error(`offline sync target is a symlink: ${relPath}`);
848
1062
  }
849
1063
  await rename(tmp, target);
1064
+ await setSafeFileMtime(root, relPath, mtimeMs);
850
1065
  } catch (error) {
851
1066
  await unlink(tmp).catch(() => {
852
1067
  });
853
1068
  throw error;
854
1069
  }
855
1070
  }
1071
+ async function setSafeFileMtime(root, relPath, mtimeMs) {
1072
+ if (mtimeMs === void 0) return true;
1073
+ const target = await resolveSafeArchiveTarget(root, relPath);
1074
+ const targetStat = await lstat(target).catch((error) => {
1075
+ if (error.code === "ENOENT") return null;
1076
+ throw error;
1077
+ });
1078
+ if (!targetStat) return false;
1079
+ if (targetStat.isSymbolicLink()) {
1080
+ throw new Error(`offline sync target is a symlink: ${relPath}`);
1081
+ }
1082
+ const mtime = new Date(assertOfflineSyncMtimeMs(mtimeMs, "mtimeMs"));
1083
+ await utimes(target, mtime, mtime);
1084
+ return true;
1085
+ }
856
1086
  async function applyOfflineSyncFileContentChunk(options) {
857
1087
  const root = await ensureSyncRoot(options.root, "applyOfflineSyncFileContentChunk");
858
1088
  const sourceId = normalizeSourceId(options.sourceId, "sourceId");
@@ -863,9 +1093,10 @@ async function applyOfflineSyncFileContentChunk(options) {
863
1093
  }
864
1094
  const sha256 = assertSha256(options.sha256, "sha256");
865
1095
  const bytes = assertNonNegativeInteger(options.bytes, "bytes");
866
- const mtimeMs = assertNonNegativeFinite(options.mtimeMs, "mtimeMs");
1096
+ const mtimeMs = assertOfflineSyncMtimeMs(options.mtimeMs, "mtimeMs");
867
1097
  const offset = options.offset === void 0 ? 0 : assertNonNegativeInteger(options.offset, "offset");
868
1098
  const baseSha256 = options.baseSha256 === void 0 ? void 0 : assertSha256(options.baseSha256, "baseSha256");
1099
+ const preferIncomingRuntimeFile = shouldPreferIncomingOfflineRuntimeFile(relPath);
869
1100
  if (!Buffer.isBuffer(options.content)) {
870
1101
  throw new Error("content must be a Buffer");
871
1102
  }
@@ -886,8 +1117,85 @@ async function applyOfflineSyncFileContentChunk(options) {
886
1117
  if (options.writeFile && !options.writeStagingFile) {
887
1118
  throw new Error("offline sync upload storage hooks require writeStagingFile");
888
1119
  }
1120
+ const baseResult = {
1121
+ path: relPath,
1122
+ sha256,
1123
+ bytes,
1124
+ mtimeMs,
1125
+ offset,
1126
+ chunkBytes: options.content.length,
1127
+ done: offset + options.content.length === bytes
1128
+ };
1129
+ const currentFileConflict = async (currentFile2) => {
1130
+ if (!baseSha256 && currentFile2 && !preferIncomingRuntimeFile) {
1131
+ const conflict = await recordConflict({
1132
+ root,
1133
+ relPath,
1134
+ reason: "remote_exists_for_local_create",
1135
+ localSha256: currentFile2.sha256,
1136
+ incomingSha256: sha256,
1137
+ writeConflictCopies: false,
1138
+ sourceId,
1139
+ writeFile: options.writeFile
1140
+ });
1141
+ return {
1142
+ conflict,
1143
+ currentFile: currentFile2
1144
+ };
1145
+ }
1146
+ if (baseSha256 && currentFile2?.sha256 !== baseSha256 && !preferIncomingRuntimeFile) {
1147
+ const conflict = await recordConflict({
1148
+ root,
1149
+ relPath,
1150
+ reason: currentFile2 ? "remote_changed_for_local_update" : "remote_deleted_for_local_update",
1151
+ baseSha256,
1152
+ localSha256: currentFile2?.sha256,
1153
+ incomingSha256: sha256,
1154
+ writeConflictCopies: false,
1155
+ sourceId,
1156
+ writeFile: options.writeFile
1157
+ });
1158
+ return {
1159
+ conflict,
1160
+ ...currentFile2 ? { currentFile: currentFile2 } : {}
1161
+ };
1162
+ }
1163
+ return null;
1164
+ };
889
1165
  if (offset === 0) {
890
1166
  await pruneOfflineUploadStaging(root);
1167
+ const currentSnapshot2 = await buildOfflineSyncSnapshotForPaths({
1168
+ root: root.abs,
1169
+ sourceId: "local",
1170
+ paths: [relPath],
1171
+ includeContent: false,
1172
+ includeTranscripts,
1173
+ readFile: options.readFile,
1174
+ readFileDigest: options.readFileDigest
1175
+ });
1176
+ const currentFile2 = currentSnapshot2.files[0];
1177
+ if (currentFile2?.sha256 === sha256) {
1178
+ await setSafeFileMtime(root, relPath, mtimeMs);
1179
+ return {
1180
+ ...baseResult,
1181
+ done: true,
1182
+ chunkBytes: 0,
1183
+ applied: false,
1184
+ skipped: true,
1185
+ currentFile: toFileState(currentFile2)
1186
+ };
1187
+ }
1188
+ const conflictResult = await currentFileConflict(currentFile2 ? toFileState(currentFile2) : void 0);
1189
+ if (conflictResult) {
1190
+ return {
1191
+ ...baseResult,
1192
+ done: true,
1193
+ chunkBytes: 0,
1194
+ applied: false,
1195
+ skipped: false,
1196
+ ...conflictResult
1197
+ };
1198
+ }
891
1199
  }
892
1200
  const upload = await writeOfflineUploadChunk({
893
1201
  root,
@@ -901,16 +1209,7 @@ async function applyOfflineSyncFileContentChunk(options) {
901
1209
  writeFile: options.writeFile,
902
1210
  writeStagingFile: options.writeStagingFile
903
1211
  });
904
- const done = offset + options.content.length === bytes;
905
- const baseResult = {
906
- path: relPath,
907
- sha256,
908
- bytes,
909
- mtimeMs,
910
- offset,
911
- chunkBytes: options.content.length,
912
- done
913
- };
1212
+ const done = baseResult.done;
914
1213
  if (!done) {
915
1214
  return {
916
1215
  ...baseResult,
@@ -934,7 +1233,8 @@ async function applyOfflineSyncFileContentChunk(options) {
934
1233
  paths: [relPath],
935
1234
  includeContent: false,
936
1235
  includeTranscripts,
937
- readFile: options.readFile
1236
+ readFile: options.readFile,
1237
+ readFileDigest: options.readFileDigest
938
1238
  });
939
1239
  const currentFile = currentSnapshot.files[0];
940
1240
  const uploadedState = {
@@ -945,53 +1245,24 @@ async function applyOfflineSyncFileContentChunk(options) {
945
1245
  };
946
1246
  try {
947
1247
  if (currentFile?.sha256 === sha256) {
1248
+ await setSafeFileMtime(root, relPath, mtimeMs);
948
1249
  return {
949
1250
  ...baseResult,
950
1251
  applied: false,
951
1252
  skipped: true,
952
- currentFile: toFileState(currentFile)
1253
+ currentFile: uploadedState
953
1254
  };
954
1255
  }
955
- if (!baseSha256 && currentFile) {
956
- const conflict = await recordConflict({
957
- root,
958
- relPath,
959
- reason: "remote_exists_for_local_create",
960
- localSha256: currentFile.sha256,
961
- incomingSha256: sha256,
962
- writeConflictCopies: false,
963
- sourceId,
964
- writeFile: options.writeFile
965
- });
1256
+ const conflictResult = await currentFileConflict(currentFile ? toFileState(currentFile) : void 0);
1257
+ if (conflictResult) {
966
1258
  return {
967
1259
  ...baseResult,
968
1260
  applied: false,
969
1261
  skipped: false,
970
- conflict,
971
- currentFile: toFileState(currentFile)
972
- };
973
- }
974
- if (baseSha256 && currentFile?.sha256 !== baseSha256) {
975
- const conflict = await recordConflict({
976
- root,
977
- relPath,
978
- reason: currentFile ? "remote_changed_for_local_update" : "remote_deleted_for_local_update",
979
- baseSha256,
980
- localSha256: currentFile?.sha256,
981
- incomingSha256: sha256,
982
- writeConflictCopies: false,
983
- sourceId,
984
- writeFile: options.writeFile
985
- });
986
- return {
987
- ...baseResult,
988
- applied: false,
989
- skipped: false,
990
- conflict,
991
- ...currentFile ? { currentFile: toFileState(currentFile) } : {}
1262
+ ...conflictResult
992
1263
  };
993
1264
  }
994
- await writeSafeFileFromUpload(root, relPath, upload, options.readFile, options.writeFileChunks);
1265
+ await writeSafeFileFromUpload(root, relPath, upload, options.readFile, options.writeFileChunks, mtimeMs);
995
1266
  return {
996
1267
  ...baseResult,
997
1268
  applied: true,
@@ -1137,11 +1408,12 @@ async function digestOfflineUploadStagingContent(options) {
1137
1408
  }
1138
1409
  return { sha256: hash.digest("hex"), bytes };
1139
1410
  }
1140
- async function writeSafeFileFromUpload(root, relPath, upload, readFile2, writeFileChunks) {
1411
+ async function writeSafeFileFromUpload(root, relPath, upload, readFile2, writeFileChunks, mtimeMs) {
1141
1412
  const target = await resolveSafeArchiveTarget(root, relPath);
1142
1413
  const chunks = readOfflineUploadStagingChunks({ root, upload, readFile: readFile2 });
1143
1414
  if (writeFileChunks) {
1144
1415
  await writeFileChunks({ root: root.abs, path: relPath, filePath: target, chunks });
1416
+ await setSafeFileMtime(root, relPath, mtimeMs);
1145
1417
  return;
1146
1418
  }
1147
1419
  await mkdir(path.dirname(target), { recursive: true });
@@ -1163,6 +1435,7 @@ async function writeSafeFileFromUpload(root, relPath, upload, readFile2, writeFi
1163
1435
  throw new Error(`offline sync target is a symlink: ${relPath}`);
1164
1436
  }
1165
1437
  await rename(tmp, target);
1438
+ await setSafeFileMtime(root, relPath, mtimeMs);
1166
1439
  } catch (error) {
1167
1440
  await handle.close().catch(() => {
1168
1441
  });
@@ -1301,14 +1574,21 @@ export {
1301
1574
  OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES,
1302
1575
  OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES,
1303
1576
  OFFLINE_SYNC_APPLY_MAX_BODY_BYTES,
1577
+ OFFLINE_SYNC_SNAPSHOT_BASE_MAX_BODY_BYTES,
1578
+ OFFLINE_SYNC_MAX_MTIME_MS,
1304
1579
  normalizeOfflineSyncSnapshot,
1305
1580
  normalizeOfflineSyncChangeset,
1581
+ shouldPreferIncomingOfflineRuntimeFile,
1582
+ iterateOfflineSyncSnapshotFileRecords,
1306
1583
  buildOfflineSyncSnapshot,
1584
+ buildOfflineSyncSnapshotFromBase,
1307
1585
  buildOfflineSyncSnapshotForPaths,
1308
1586
  readOfflineSyncFileContentChunk,
1309
1587
  buildOfflineSyncChangeset,
1588
+ buildOfflineSyncChangesetFromSnapshot,
1310
1589
  summarizeOfflineSyncChangeset,
1311
1590
  summarizeOfflineSyncPendingChanges,
1591
+ summarizeOfflineSyncPendingFiles,
1312
1592
  applyOfflineSyncSnapshot,
1313
1593
  applyOfflineSyncChangeset,
1314
1594
  applyOfflineSyncFileContentChunk,
@@ -1319,4 +1599,4 @@ export {
1319
1599
  normalizeOfflineSyncState,
1320
1600
  fileStatesFromSnapshot
1321
1601
  };
1322
- //# sourceMappingURL=chunk-AMVN77EU.js.map
1602
+ //# sourceMappingURL=chunk-U7EJOMFC.js.map