@remnic/cli 9.3.515 → 9.3.517

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 (2) hide show
  1. package/dist/index.js +111 -24
  2. package/package.json +22 -22
package/dist/index.js CHANGED
@@ -6704,7 +6704,7 @@ function normalizeOfflineRemoteUrl(raw) {
6704
6704
  return parsed.toString().replace(/\/$/, "");
6705
6705
  }
6706
6706
  function resolveOptionalOfflineRemoteUrl(args) {
6707
- const raw = resolveRequiredValueFlag(args, "--remote-url") ?? process.env.REMNIC_OFFLINE_REMOTE_URL ?? process.env.ENGRAM_OFFLINE_REMOTE_URL;
6707
+ const raw = resolveRequiredValueFlag(args, "--remote-url") ?? resolveRequiredValueFlag(args, "--remote") ?? process.env.REMNIC_OFFLINE_REMOTE_URL ?? process.env.ENGRAM_OFFLINE_REMOTE_URL;
6708
6708
  if (!raw || raw.trim().length === 0) return void 0;
6709
6709
  return normalizeOfflineRemoteUrl(raw);
6710
6710
  }
@@ -6762,6 +6762,12 @@ function offlineRequestTimeoutMs() {
6762
6762
  process.env.REMNIC_OFFLINE_REQUEST_TIMEOUT_MS ?? process.env.ENGRAM_OFFLINE_REQUEST_TIMEOUT_MS
6763
6763
  );
6764
6764
  }
6765
+ function offlineSnapshotPostTimeoutMs() {
6766
+ return parseOfflineSyncRequestTimeoutMs(
6767
+ process.env.REMNIC_OFFLINE_SNAPSHOT_POST_TIMEOUT_MS ?? process.env.ENGRAM_OFFLINE_SNAPSHOT_POST_TIMEOUT_MS,
6768
+ Math.min(offlineRequestTimeoutMs(), 6e4)
6769
+ );
6770
+ }
6765
6771
  function offlineFetchHeaders(token, initHeaders, defaultContentType) {
6766
6772
  const headers = new Headers(initHeaders);
6767
6773
  headers.set("authorization", `Bearer ${token}`);
@@ -6932,18 +6938,23 @@ async function fetchOfflineSnapshot(args) {
6932
6938
  const postRequest = offlineSnapshotBasePostRequest(postBody);
6933
6939
  if (postRequest) {
6934
6940
  const postRequestUsesGzip = new Headers(postRequest.headers).get("content-encoding")?.toLowerCase() === "gzip";
6941
+ const postAbort = new AbortController();
6942
+ const postTimeout = setTimeout(() => postAbort.abort(), offlineSnapshotPostTimeoutMs());
6935
6943
  try {
6936
6944
  return await fetchOfflineJson(
6937
6945
  offlineEndpoint(args.remoteUrl, "/remnic/v1/offline-sync/snapshot"),
6938
6946
  args.token,
6939
6947
  {
6940
6948
  method: "POST",
6949
+ signal: postAbort.signal,
6941
6950
  ...postRequest
6942
6951
  }
6943
6952
  );
6944
6953
  } catch (error) {
6945
6954
  if (!isOfflineSnapshotPostFallbackError(error, { compressed: postRequestUsesGzip })) throw error;
6946
6955
  tryStreamSnapshot = true;
6956
+ } finally {
6957
+ clearTimeout(postTimeout);
6947
6958
  }
6948
6959
  } else {
6949
6960
  tryStreamSnapshot = true;
@@ -7005,6 +7016,9 @@ function offlineSnapshotBasePostRequest(body) {
7005
7016
  function isOfflineSnapshotPostFallbackError(error, options = {}) {
7006
7017
  const message = error instanceof Error ? error.message : String(error);
7007
7018
  if (/offline-sync\/snapshot\b.* returned (404|405|413)\b/.test(message)) return true;
7019
+ if (/^offline sync request timed out after \d+ms: POST .*\/offline-sync\/snapshot\b/.test(message) || /^offline sync request failed before response: POST .*\/offline-sync\/snapshot\b/.test(message) || /^(This operation was aborted|The operation was aborted|AbortError)/i.test(message)) {
7020
+ return true;
7021
+ }
7008
7022
  if (!options.compressed) return false;
7009
7023
  return /offline-sync\/snapshot\b.* returned (400|415)\b/.test(message) && /\b(unsupported_content_encoding|invalid_gzip_body|invalid_json)\b/.test(message);
7010
7024
  }
@@ -7040,6 +7054,8 @@ var OFFLINE_SYNC_DIRECT_PUSH_MIN_BYTES = Math.min(
7040
7054
  );
7041
7055
  var OFFLINE_SYNC_DIRECT_HYDRATE_MIN_BYTES = OFFLINE_SYNC_DIRECT_PUSH_MIN_BYTES;
7042
7056
  var OFFLINE_SYNC_CHANGESET_RETRY_MAX = 1024;
7057
+ var OFFLINE_SYNC_CONTENT_MISSING_RETRY_MAX = 3;
7058
+ var OFFLINE_SYNC_CONTENT_MISSING_RETRY_DELAY_MS = 250;
7043
7059
  var OfflineRemoteFileChangedError = class extends Error {
7044
7060
  path;
7045
7061
  constructor(path12) {
@@ -7521,10 +7537,53 @@ function isOfflineFilesUnsupportedError(error) {
7521
7537
  const message = error instanceof Error ? error.message : String(error);
7522
7538
  return /offline sync request failed: .* returned 404\b/.test(message);
7523
7539
  }
7540
+ var OfflineMissingContentError = class extends Error {
7541
+ missing;
7542
+ constructor(missing) {
7543
+ const preview = missing.slice(0, 8).map((file) => file.path).join(", ");
7544
+ const suffix = missing.length > 8 ? `, ... +${missing.length - 8} more` : "";
7545
+ super(
7546
+ `remote offline content response omitted ${missing.length} changed file${missing.length === 1 ? "" : "s"}: ${preview}${suffix}; retry sync`
7547
+ );
7548
+ this.name = "OfflineMissingContentError";
7549
+ this.missing = missing;
7550
+ }
7551
+ };
7524
7552
  function isMissingOfflineContentError(error) {
7553
+ if (error instanceof OfflineMissingContentError) return true;
7525
7554
  const message = error instanceof Error ? error.message : String(error);
7526
7555
  return /^missing decoded content for /.test(message);
7527
7556
  }
7557
+ function formatMissingOfflineContentError(missing) {
7558
+ return new OfflineMissingContentError(missing);
7559
+ }
7560
+ function isOfflineMissingContentDeferrablePath(relPath) {
7561
+ const parts = relPath.split("/");
7562
+ return parts[0] === "profiling" || parts[0] === "namespaces" && parts[2] === "profiling";
7563
+ }
7564
+ function deferMissingOfflineContent(missing, deferredPaths) {
7565
+ if (!deferredPaths) return [...missing];
7566
+ const stillMissing = [];
7567
+ for (const file of missing) {
7568
+ if (isOfflineMissingContentDeferrablePath(file.path)) {
7569
+ deferredPaths.add(file.path);
7570
+ } else {
7571
+ stillMissing.push(file);
7572
+ }
7573
+ }
7574
+ return stillMissing;
7575
+ }
7576
+ function formatMissingDecodedContentError(missing) {
7577
+ const preview = missing.slice(0, 8).map((file) => file.path).join(", ");
7578
+ const suffix = missing.length > 8 ? `, ... +${missing.length - 8} more` : "";
7579
+ return new Error(
7580
+ `remote offline content response omitted ${missing.length} changed file${missing.length === 1 ? "" : "s"}: ${preview}${suffix}; retry sync`
7581
+ );
7582
+ }
7583
+ async function waitForMissingOfflineContentRetry(delayMs) {
7584
+ if (delayMs <= 0) return;
7585
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
7586
+ }
7528
7587
  async function hydrateOfflineSnapshotContent(args) {
7529
7588
  const snapshot = normalizeOfflineSyncSnapshot(args.snapshot);
7530
7589
  const neededFiles = offlineSnapshotContentFilesForApply({
@@ -7538,26 +7597,47 @@ async function hydrateOfflineSnapshotContent(args) {
7538
7597
  const expectedByPath = new Map(snapshot.files.map((file) => [file.path, file]));
7539
7598
  const contentByPath = /* @__PURE__ */ new Map();
7540
7599
  const updatedByPath = /* @__PURE__ */ new Map();
7600
+ const fetchFiles = args.fetchFiles ?? fetchOfflineFiles;
7601
+ const retryMax = args.missingContentRetryMax ?? OFFLINE_SYNC_CONTENT_MISSING_RETRY_MAX;
7602
+ const retryDelayMs = args.missingContentRetryDelayMs ?? OFFLINE_SYNC_CONTENT_MISSING_RETRY_DELAY_MS;
7603
+ if (!Number.isInteger(retryMax) || retryMax < 0) {
7604
+ throw new Error("offline sync missing content retry max must be an integer >= 0");
7605
+ }
7606
+ if (!Number.isInteger(retryDelayMs) || retryDelayMs < 0) {
7607
+ throw new Error("offline sync missing content retry delay must be an integer >= 0");
7608
+ }
7541
7609
  try {
7542
- for (const batch of chunkOfflineFileContentBatches(neededFiles)) {
7543
- const partial = await fetchOfflineFiles({
7544
- remoteUrl: args.remoteUrl,
7545
- token: args.token,
7546
- namespace: args.namespace,
7547
- includeTranscripts: args.includeTranscripts,
7548
- paths: batch.map((file) => file.path)
7549
- });
7550
- for (const file of partial.files) {
7551
- const expected = expectedByPath.get(file.path);
7552
- if (!expected) continue;
7553
- if (typeof file.contentBase64 !== "string") {
7554
- throw new Error(`remote offline content response omitted contentBase64 for ${file.path}`);
7555
- }
7556
- if (file.sha256 !== expected.sha256 || file.bytes !== expected.bytes || file.mtimeMs !== expected.mtimeMs) {
7557
- updatedByPath.set(file.path, file);
7610
+ let pendingFiles = neededFiles;
7611
+ for (let attempt = 0; ; attempt += 1) {
7612
+ for (const batch of chunkOfflineFileContentBatches(pendingFiles)) {
7613
+ const partial = await fetchFiles({
7614
+ remoteUrl: args.remoteUrl,
7615
+ token: args.token,
7616
+ namespace: args.namespace,
7617
+ includeTranscripts: args.includeTranscripts,
7618
+ paths: batch.map((file) => file.path)
7619
+ });
7620
+ for (const file of partial.files) {
7621
+ const expected = expectedByPath.get(file.path);
7622
+ if (!expected) continue;
7623
+ if (typeof file.contentBase64 !== "string") {
7624
+ throw new Error(`remote offline content response omitted contentBase64 for ${file.path}`);
7625
+ }
7626
+ if (file.sha256 !== expected.sha256 || file.bytes !== expected.bytes || file.mtimeMs !== expected.mtimeMs) {
7627
+ updatedByPath.set(file.path, file);
7628
+ }
7629
+ contentByPath.set(file.path, file.contentBase64);
7558
7630
  }
7559
- contentByPath.set(file.path, file.contentBase64);
7560
7631
  }
7632
+ const missing2 = pendingFiles.filter((file) => !contentByPath.has(file.path));
7633
+ if (missing2.length === 0) break;
7634
+ if (attempt >= retryMax) {
7635
+ const stillMissing = deferMissingOfflineContent(missing2, args.missingContentDeferredPaths);
7636
+ if (stillMissing.length > 0) throw formatMissingOfflineContentError(stillMissing);
7637
+ break;
7638
+ }
7639
+ pendingFiles = missing2;
7640
+ await waitForMissingOfflineContentRetry(retryDelayMs);
7561
7641
  }
7562
7642
  } catch (error) {
7563
7643
  if (!isOfflineFilesUnsupportedError(error)) throw error;
@@ -7569,10 +7649,10 @@ async function hydrateOfflineSnapshotContent(args) {
7569
7649
  includeContent: true
7570
7650
  });
7571
7651
  }
7572
- const missing = neededFiles.map((file) => file.path).filter((relPath) => !contentByPath.has(relPath));
7652
+ const missing = neededFiles.map((file) => file.path).filter((relPath) => !contentByPath.has(relPath) && !args.missingContentDeferredPaths?.has(relPath));
7573
7653
  if (missing.length > 0) {
7574
- throw new Error(
7575
- `remote offline content response omitted ${missing.length} changed file${missing.length === 1 ? "" : "s"}; retry sync`
7654
+ throw formatMissingDecodedContentError(
7655
+ neededFiles.filter((file) => missing.includes(file.path))
7576
7656
  );
7577
7657
  }
7578
7658
  return {
@@ -8231,7 +8311,8 @@ async function runOfflineSyncOnce(options) {
8231
8311
  snapshot: remoteSnapshotMetadata,
8232
8312
  baseFiles,
8233
8313
  currentFiles: applyCurrentSnapshot.files,
8234
- deferredPaths: [...remoteDeferredPaths]
8314
+ deferredPaths: [...remoteDeferredPaths],
8315
+ missingContentDeferredPaths: remoteDeferredPaths
8235
8316
  });
8236
8317
  } catch (error) {
8237
8318
  if (pushed || partialHydration.hydratedFiles.length > 0) {
@@ -8275,7 +8356,8 @@ async function runOfflineSyncOnce(options) {
8275
8356
  snapshot: remoteSnapshotMetadata,
8276
8357
  baseFiles,
8277
8358
  currentFiles: applyCurrentSnapshot.files,
8278
- deferredPaths: [...remoteDeferredPaths]
8359
+ deferredPaths: [...remoteDeferredPaths],
8360
+ missingContentDeferredPaths: remoteDeferredPaths
8279
8361
  });
8280
8362
  } catch (retryError) {
8281
8363
  if (pushed || partialHydration.hydratedFiles.length > 0) {
@@ -8397,7 +8479,7 @@ async function cmdOffline(action, rest, json) {
8397
8479
  console.log(`Usage: remnic offline <prepare|sync|status|watch> [options]
8398
8480
 
8399
8481
  Options:
8400
- --remote-url <url> Remote Remnic server URL, e.g. http://home:4242
8482
+ --remote-url <url> Remote Remnic server URL, e.g. http://home:4242 (--remote alias accepted)
8401
8483
  --token <token> Bearer token for the remote server
8402
8484
  --namespace <name> Namespace to sync
8403
8485
  --memory-dir <dir> Local memory dir (defaults to resolved memoryDir)
@@ -11538,6 +11620,8 @@ export {
11538
11620
  BENCHMARK_CATALOG,
11539
11621
  OFFLINE_SYNC_APPLY_MAX_REQUEST_BYTES,
11540
11622
  OFFLINE_SYNC_CHANGESET_RETRY_MAX,
11623
+ OFFLINE_SYNC_CONTENT_MISSING_RETRY_DELAY_MS,
11624
+ OFFLINE_SYNC_CONTENT_MISSING_RETRY_MAX,
11541
11625
  OFFLINE_SYNC_DIRECT_HYDRATE_MIN_BYTES,
11542
11626
  OFFLINE_SYNC_DIRECT_PUSH_MIN_BYTES,
11543
11627
  OFFLINE_SYNC_FILE_CONTENT_UPLOAD_CHUNK_BYTES,
@@ -11560,6 +11644,8 @@ export {
11560
11644
  formatOfflineRequestForError,
11561
11645
  getBenchUsageText,
11562
11646
  hasFlag,
11647
+ hydrateOfflineSnapshotContent,
11648
+ isOfflineMissingContentDeferrablePath,
11563
11649
  isOfflineSnapshotPostFallbackError,
11564
11650
  main,
11565
11651
  offlinePartialHydrationForPaths,
@@ -11579,6 +11665,7 @@ export {
11579
11665
  resolveConfigPath,
11580
11666
  resolveFlag,
11581
11667
  resolveMemoryDir,
11668
+ resolveOptionalOfflineRemoteUrl,
11582
11669
  resolveSyncSourceDir,
11583
11670
  runOfflineSyncOnce,
11584
11671
  runTrainingExport,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remnic/cli",
3
- "version": "9.3.515",
3
+ "version": "9.3.517",
4
4
  "description": "CLI for Remnic memory — init, query, doctor, daemon management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -26,20 +26,20 @@
26
26
  },
27
27
  "dependencies": {
28
28
  "yaml": "^2.4.2",
29
- "@remnic/plugin-pi": "^9.3.515",
30
- "@remnic/server": "^9.3.515",
31
- "@remnic/core": "^9.3.515"
29
+ "@remnic/plugin-pi": "^9.3.517",
30
+ "@remnic/server": "^9.3.517",
31
+ "@remnic/core": "^9.3.517"
32
32
  },
33
33
  "peerDependencies": {
34
- "@remnic/bench": "^9.3.515",
35
- "@remnic/export-weclone": "^9.3.515",
36
- "@remnic/import-weclone": "^9.3.515",
37
- "@remnic/import-chatgpt": "^9.3.515",
38
- "@remnic/import-claude": "^9.3.515",
39
- "@remnic/import-gemini": "^9.3.515",
40
- "@remnic/import-lossless-claw": "^9.3.515",
41
- "@remnic/import-mem0": "^9.3.515",
42
- "@remnic/import-supermemory": "^9.3.515"
34
+ "@remnic/bench": "^9.3.517",
35
+ "@remnic/export-weclone": "^9.3.517",
36
+ "@remnic/import-weclone": "^9.3.517",
37
+ "@remnic/import-chatgpt": "^9.3.517",
38
+ "@remnic/import-claude": "^9.3.517",
39
+ "@remnic/import-gemini": "^9.3.517",
40
+ "@remnic/import-lossless-claw": "^9.3.517",
41
+ "@remnic/import-mem0": "^9.3.517",
42
+ "@remnic/import-supermemory": "^9.3.517"
43
43
  },
44
44
  "peerDependenciesMeta": {
45
45
  "@remnic/bench": {
@@ -73,15 +73,15 @@
73
73
  "devDependencies": {
74
74
  "tsup": "^8.5.1",
75
75
  "typescript": "^5.9.3",
76
- "@remnic/bench": "9.3.515",
77
- "@remnic/export-weclone": "9.3.515",
78
- "@remnic/import-weclone": "9.3.515",
79
- "@remnic/import-claude": "9.3.515",
80
- "@remnic/import-gemini": "9.3.515",
81
- "@remnic/import-mem0": "9.3.515",
82
- "@remnic/import-lossless-claw": "9.3.515",
83
- "@remnic/import-chatgpt": "9.3.515",
84
- "@remnic/import-supermemory": "9.3.515"
76
+ "@remnic/export-weclone": "9.3.517",
77
+ "@remnic/bench": "9.3.517",
78
+ "@remnic/import-weclone": "9.3.517",
79
+ "@remnic/import-chatgpt": "9.3.517",
80
+ "@remnic/import-claude": "9.3.517",
81
+ "@remnic/import-gemini": "9.3.517",
82
+ "@remnic/import-lossless-claw": "9.3.517",
83
+ "@remnic/import-supermemory": "9.3.517",
84
+ "@remnic/import-mem0": "9.3.517"
85
85
  },
86
86
  "license": "MIT",
87
87
  "repository": {