@remnic/cli 9.45.3 → 9.45.4

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 +1129 -612
  2. package/package.json +29 -29
package/dist/index.js CHANGED
@@ -18,18 +18,18 @@ async function persistEnrichmentCandidate(storage, entityName, candidate) {
18
18
  }
19
19
 
20
20
  // src/index.ts
21
- import fs12 from "fs";
21
+ import fs13 from "fs";
22
22
  import os from "os";
23
23
  import path15 from "path";
24
- import { createHash as createHash3 } from "crypto";
24
+ import { createHash as createHash4 } from "crypto";
25
25
  import * as childProcess2 from "child_process";
26
26
  import { fileURLToPath as fileURLToPath4 } from "url";
27
27
  import { gzipSync } from "zlib";
28
28
  import {
29
- parseConfig as parseConfig5,
29
+ parseConfig as parseConfig6,
30
30
  isOpenaiApiKeyDisabled,
31
31
  resolveEnvVars,
32
- resolveRemnicConfigRecord as resolveRemnicConfigRecord4,
32
+ resolveRemnicConfigRecord as resolveRemnicConfigRecord5,
33
33
  Orchestrator as Orchestrator3,
34
34
  EngramAccessService as EngramAccessService2,
35
35
  initLogger as initLogger2,
@@ -111,7 +111,7 @@ import {
111
111
  renderXray,
112
112
  OFFLINE_SYNC_APPLY_MAX_BODY_BYTES,
113
113
  OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES as OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES2,
114
- OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES as OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES3,
114
+ OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES as OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES4,
115
115
  OFFLINE_SYNC_SNAPSHOT_BASE_MAX_BODY_BYTES,
116
116
  applyOfflineSyncFileContentChunk as applyOfflineSyncFileContentChunk2,
117
117
  applyOfflineSyncSnapshot,
@@ -190,6 +190,34 @@ async function runMeetingsBinaryCommand(rest) {
190
190
  }
191
191
  }
192
192
 
193
+ // src/commands/external-wiki.ts
194
+ import fs2 from "fs";
195
+ import { parseConfig as parseConfig2, resolveRemnicConfigRecord as resolveRemnicConfigRecord2, runExternalWikiCliCommand } from "@remnic/core";
196
+ async function runExternalWikiBinaryCommand(rest) {
197
+ let roots;
198
+ try {
199
+ const configPath = resolveConfigPath();
200
+ const raw = fs2.existsSync(configPath) ? JSON.parse(fs2.readFileSync(configPath, "utf8")) : {};
201
+ roots = parseConfig2(resolveRemnicConfigRecord2(raw)).externalWikis;
202
+ } catch {
203
+ console.error(
204
+ "external-wiki: failed to load the Remnic config - run `remnic doctor` and check the config file for errors"
205
+ );
206
+ process.exitCode = 1;
207
+ return;
208
+ }
209
+ try {
210
+ const code = await runExternalWikiCliCommand(roots, rest, {
211
+ stdout: process.stdout,
212
+ stderr: process.stderr
213
+ });
214
+ if (code !== 0) process.exitCode = code;
215
+ } catch {
216
+ console.error("external-wiki: search failed");
217
+ process.exitCode = 1;
218
+ }
219
+ }
220
+
193
221
  // src/optional-module-loader.ts
194
222
  function isSpecifierNotFoundError(err, specifier) {
195
223
  if (!err || typeof err !== "object") {
@@ -240,19 +268,19 @@ async function loadWecloneExportModule() {
240
268
  }
241
269
 
242
270
  // src/converge.ts
243
- import * as fs3 from "fs";
244
- import { createHash as createHash2 } from "crypto";
271
+ import * as fs4 from "fs";
272
+ import { createHash as createHash3 } from "crypto";
245
273
  import * as path2 from "path";
246
274
  import {
247
275
  CONVERGE_CONFLICT_POLICIES,
248
276
  DEFAULT_CONVERGE_CONFLICT_POLICY,
249
- parseConfig as parseConfig2,
277
+ parseConfig as parseConfig3,
250
278
  buildOfflineSyncSnapshotFromBase,
251
- OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES,
252
- OFFLINE_SYNC_CHANGESET_FORMAT,
253
- OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES as OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES2,
254
- applyOfflineSyncFileContentChunk
279
+ applyOfflineSyncFileContentChunk,
280
+ isInternalRemnicStatePath as isInternalRemnicStatePath3,
281
+ OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES as OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES3
255
282
  } from "@remnic/core";
283
+ import { parseFrontmatter } from "@remnic/core/storage.js";
256
284
  import { resolveCorpusNamespaceRoots } from "@remnic/core/corpus-watermark.js";
257
285
  import { listNamespaces } from "@remnic/core/namespaces/migrate.js";
258
286
  import {
@@ -262,7 +290,8 @@ import {
262
290
  defaultConvergeCursorPath,
263
291
  deriveConvergeCursorBase,
264
292
  readConvergeCursor,
265
- writeConvergeCursor
293
+ writeConvergeCursor,
294
+ normalizeConvergePeerUrl as normalizeConvergePeerUrl2
266
295
  } from "@remnic/core/reconcile/cursor.js";
267
296
  import {
268
297
  buildReconcileManifest,
@@ -271,7 +300,7 @@ import {
271
300
 
272
301
  // src/offline-storage-io.ts
273
302
  import { mkdtemp, readdir, lstat, rm } from "fs/promises";
274
- import fs2 from "fs";
303
+ import fs3 from "fs";
275
304
  import path from "path";
276
305
  import { createHash, createDecipheriv } from "crypto";
277
306
  import {
@@ -337,6 +366,7 @@ async function createOfflineStorageIo(memoryDir, configuredStorage) {
337
366
  const { storage, secureStoreKey } = configuredStorage ?? await createConfiguredOfflineStorage(memoryDir);
338
367
  return {
339
368
  readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
369
+ readDeletionRevisions: () => storage.readDeletionRevisions(),
340
370
  readFileDigest: async ({ filePath }) => {
341
371
  const hash = createHash("sha256");
342
372
  let bytes = 0;
@@ -364,7 +394,8 @@ async function createOfflineStorageIo(memoryDir, configuredStorage) {
364
394
  writeFile: async ({ filePath, content }) => storage.writeOfflineSyncFile(filePath, content),
365
395
  writeStagingFile: async ({ filePath, content }) => storage.writeOfflineSyncStagingFile(filePath, content),
366
396
  writeFileChunks: async ({ filePath, chunks }) => storage.writeOfflineSyncFileChunks(filePath, chunks),
367
- deleteFile: async ({ filePath }) => storage.deleteOfflineSyncFile(filePath)
397
+ deleteFile: async ({ filePath, mtimeMs }) => storage.deleteOfflineSyncFile(filePath, mtimeMs ?? null),
398
+ recordDeletionRevision: async ({ filePath, mtimeMs }) => storage.recordReplicatedDeletionRevision(filePath, mtimeMs)
368
399
  };
369
400
  }
370
401
  var OFFLINE_DECRYPT_STAGING_ORPHAN_MS = 60 * 60 * 1e3;
@@ -407,7 +438,7 @@ async function* readOfflineSyncFileChunks(options) {
407
438
  });
408
439
  }
409
440
  async function readFilePrefix(filePath, length) {
410
- const handle = await fs2.promises.open(filePath, "r");
441
+ const handle = await fs3.promises.open(filePath, "r");
411
442
  try {
412
443
  const out = Buffer.alloc(length);
413
444
  const { bytesRead } = await handle.read(out, 0, length, 0);
@@ -417,7 +448,7 @@ async function readFilePrefix(filePath, length) {
417
448
  }
418
449
  }
419
450
  async function* readPlainOfflineFileChunks(filePath, chunkSize) {
420
- const stream = fs2.createReadStream(filePath, { highWaterMark: chunkSize });
451
+ const stream = fs3.createReadStream(filePath, { highWaterMark: chunkSize });
421
452
  for await (const chunk of stream) {
422
453
  yield Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
423
454
  }
@@ -460,9 +491,9 @@ async function* readEncryptedOfflineFileChunks(options) {
460
491
  });
461
492
  decipher.setAuthTag(authTag);
462
493
  decipher.setAAD(Buffer.concat([secureStoreEnvelopeHeaderAad(salt), aad]));
463
- const output = fs2.createWriteStream(tempPath, { mode: 384 });
494
+ const output = fs3.createWriteStream(tempPath, { mode: 384 });
464
495
  try {
465
- const stream = fs2.createReadStream(options.filePath, {
496
+ const stream = fs3.createReadStream(options.filePath, {
466
497
  start: MAGIC_HEADER_SIZE + ENVELOPE_HEADER_SIZE,
467
498
  highWaterMark: options.chunkSize
468
499
  });
@@ -471,16 +502,16 @@ async function* readEncryptedOfflineFileChunks(options) {
471
502
  Buffer.isBuffer(encryptedChunk) ? encryptedChunk : Buffer.from(encryptedChunk)
472
503
  );
473
504
  if (plain.length > 0 && !output.write(plain)) {
474
- await new Promise((resolve, reject) => {
475
- output.once("drain", resolve);
505
+ await new Promise((resolve2, reject) => {
506
+ output.once("drain", resolve2);
476
507
  output.once("error", reject);
477
508
  });
478
509
  }
479
510
  }
480
511
  const finalPlain = decipher.final();
481
512
  if (finalPlain.length > 0 && !output.write(finalPlain)) {
482
- await new Promise((resolve, reject) => {
483
- output.once("drain", resolve);
513
+ await new Promise((resolve2, reject) => {
514
+ output.once("drain", resolve2);
484
515
  output.once("error", reject);
485
516
  });
486
517
  }
@@ -516,9 +547,9 @@ function offlineFileAadCandidates(filePath, memoryDir) {
516
547
  return candidates;
517
548
  }
518
549
  async function closeWriteStream(stream) {
519
- await new Promise((resolve, reject) => {
550
+ await new Promise((resolve2, reject) => {
520
551
  stream.once("error", reject);
521
- stream.end(() => resolve());
552
+ stream.end(() => resolve2());
522
553
  });
523
554
  }
524
555
  function secureStoreEnvelopeHeaderAad(salt) {
@@ -530,39 +561,215 @@ function secureStoreEnvelopeHeaderAad(salt) {
530
561
 
531
562
  // src/converge.ts
532
563
  import { resolveAgentAccessAuthToken } from "@remnic/core/resolve-auth-token.js";
533
- async function readLocalTombstones(rootDir) {
534
- const shaSet = /* @__PURE__ */ new Set();
535
- const candidates = [
536
- path2.join(rootDir, "state", "tombstones.jsonl"),
537
- path2.join(rootDir, "tombstones.jsonl")
538
- ];
539
- for (const tombPath of candidates) {
564
+
565
+ // src/converge-peer-transport.ts
566
+ import { createHash as createHash2 } from "crypto";
567
+ import {
568
+ isInternalRemnicStatePath as isInternalRemnicStatePath2,
569
+ OFFLINE_SYNC_CHANGESET_FORMAT,
570
+ OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES,
571
+ OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES as OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES2
572
+ } from "@remnic/core";
573
+ import { normalizeConvergePeerUrl } from "@remnic/core/reconcile/cursor.js";
574
+
575
+ // src/converge-peer-manifest.ts
576
+ import { isInternalRemnicStatePath } from "@remnic/core";
577
+ import {
578
+ RECONCILE_MANIFEST_FORMAT,
579
+ RECONCILE_MANIFEST_SCHEMA_VERSION
580
+ } from "@remnic/core/reconcile/manifest.js";
581
+ var SHA256_PATTERN = /^[a-f0-9]{64}$/i;
582
+ var MEMORY_STATUSES = /* @__PURE__ */ new Set([
583
+ "active",
584
+ "pending_review",
585
+ "rejected",
586
+ "quarantined",
587
+ "superseded",
588
+ "archived",
589
+ "forgotten"
590
+ ]);
591
+ var BODY_FIELDS = ["body", "content", "contentBase64", "rawContent"];
592
+ function record(value, message) {
593
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(message);
594
+ return value;
595
+ }
596
+ function assertBodyFree(value, message) {
597
+ if (BODY_FIELDS.some((field) => field in value)) throw new Error(message);
598
+ }
599
+ function optionalNonNegativeNumber(value, name) {
600
+ if (value === void 0) return void 0;
601
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
602
+ throw new Error(`peer manifest file had invalid ${name}`);
603
+ }
604
+ return value;
605
+ }
606
+ function parseMemory(value) {
607
+ if (value === void 0) return void 0;
608
+ const memory = record(value, "peer manifest file had malformed memory metadata");
609
+ assertBodyFree(memory, "peer manifest memory metadata contained a raw body");
610
+ if (typeof memory.id !== "string" || memory.id.length === 0) {
611
+ throw new Error("peer manifest memory metadata had invalid id");
612
+ }
613
+ if (typeof memory.category !== "string" || memory.category.length === 0) {
614
+ throw new Error("peer manifest memory metadata had invalid category");
615
+ }
616
+ if (typeof memory.contentHash !== "string" || !SHA256_PATTERN.test(memory.contentHash)) {
617
+ throw new Error("peer manifest memory metadata had invalid contentHash");
618
+ }
619
+ if (typeof memory.status !== "string" || !MEMORY_STATUSES.has(memory.status)) {
620
+ throw new Error("peer manifest memory metadata had invalid status");
621
+ }
622
+ return {
623
+ id: memory.id,
624
+ category: memory.category,
625
+ contentHash: memory.contentHash.toLowerCase(),
626
+ status: memory.status
627
+ };
628
+ }
629
+ function parseFile(value) {
630
+ const file = record(value, "peer manifest row had malformed file metadata");
631
+ assertBodyFree(file, "peer manifest file row contained a raw body");
632
+ if (typeof file.path !== "string" || file.path.length === 0) {
633
+ throw new Error("peer manifest file had invalid path");
634
+ }
635
+ if (isInternalRemnicStatePath(file.path)) return void 0;
636
+ if (typeof file.sha256 !== "string" || !SHA256_PATTERN.test(file.sha256)) {
637
+ throw new Error("peer manifest file had invalid sha256");
638
+ }
639
+ const bytes = optionalNonNegativeNumber(file.bytes, "bytes");
640
+ const mtimeMs = optionalNonNegativeNumber(file.mtimeMs, "mtimeMs");
641
+ const memory = parseMemory(file.memory);
642
+ return {
643
+ path: file.path,
644
+ sha256: file.sha256.toLowerCase(),
645
+ ...bytes === void 0 ? {} : { bytes },
646
+ ...mtimeMs === void 0 ? {} : { mtimeMs },
647
+ ...memory === void 0 ? {} : { memory }
648
+ };
649
+ }
650
+ async function* responseLines(response) {
651
+ if (!response.body) throw new Error("peer manifest response had no body");
652
+ const reader = response.body.getReader();
653
+ const decoder = new TextDecoder();
654
+ let pending = "";
655
+ try {
656
+ for (; ; ) {
657
+ const { value, done } = await reader.read();
658
+ pending += decoder.decode(value, { stream: !done });
659
+ let newline = pending.indexOf("\n");
660
+ while (newline >= 0) {
661
+ const line = pending.slice(0, newline).replace(/\r$/, "");
662
+ pending = pending.slice(newline + 1);
663
+ if (line.trim().length > 0) yield line;
664
+ newline = pending.indexOf("\n");
665
+ }
666
+ if (done) break;
667
+ }
668
+ if (pending.trim().length > 0) yield pending.replace(/\r$/, "");
669
+ } finally {
670
+ reader.releaseLock();
671
+ }
672
+ }
673
+ async function parsePeerManifestStream(response, expectedNamespace) {
674
+ let headerSeen = false;
675
+ const files = [];
676
+ for await (const line of responseLines(response)) {
677
+ let value;
540
678
  try {
541
- const content = await fs3.promises.readFile(tombPath, "utf-8");
542
- for (const line of content.split("\n")) {
543
- const trimmed = line.trim();
544
- if (!trimmed) continue;
545
- try {
546
- const record = JSON.parse(trimmed);
547
- if (typeof record.contentHash === "string" && /^[0-9a-f]{64}$/i.test(record.contentHash)) {
548
- shaSet.add(record.contentHash.toLowerCase());
549
- }
550
- if (typeof record.fileSha256 === "string" && /^[0-9a-f]{64}$/i.test(record.fileSha256)) {
551
- shaSet.add(record.fileSha256.toLowerCase());
552
- }
553
- } catch {
554
- }
555
- }
679
+ value = JSON.parse(line);
556
680
  } catch {
681
+ throw new Error(`invalid peer manifest for namespace ${expectedNamespace}: row was not JSON`);
557
682
  }
683
+ const row = record(value, `invalid peer manifest for namespace ${expectedNamespace}: row was not an object`);
684
+ assertBodyFree(row, `invalid peer manifest for namespace ${expectedNamespace}: row contained a raw body`);
685
+ if (!headerSeen) {
686
+ if (row.type !== "manifest" || row.namespace !== expectedNamespace || row.format !== RECONCILE_MANIFEST_FORMAT || row.schemaVersion !== RECONCILE_MANIFEST_SCHEMA_VERSION) {
687
+ throw new Error(`invalid peer manifest for namespace ${expectedNamespace}: malformed header`);
688
+ }
689
+ headerSeen = true;
690
+ continue;
691
+ }
692
+ if (row.type !== "file") {
693
+ throw new Error(`invalid peer manifest for namespace ${expectedNamespace}: malformed row type`);
694
+ }
695
+ const file = parseFile(row.file);
696
+ if (file) files.push(file);
558
697
  }
559
- return shaSet;
698
+ if (!headerSeen) throw new Error(`invalid peer manifest for namespace ${expectedNamespace}: missing header`);
699
+ return {
700
+ format: RECONCILE_MANIFEST_FORMAT,
701
+ schemaVersion: RECONCILE_MANIFEST_SCHEMA_VERSION,
702
+ files
703
+ };
560
704
  }
561
- async function fetchPeerSnapshot(peerUrl, namespace, token, fetchImpl = globalThis.fetch) {
562
- let base = peerUrl;
563
- while (base.endsWith("/")) {
564
- base = base.slice(0, -1);
705
+
706
+ // src/converge-peer-transport.ts
707
+ var DEFAULT_PEER_REQUEST_TIMEOUT_MS = 3e4;
708
+ function normalizePeerBaseUrl(peerUrl) {
709
+ const normalized = normalizeConvergePeerUrl(peerUrl);
710
+ const url = new URL(normalized);
711
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
712
+ throw new Error(`unsupported peer URL protocol: ${url.protocol}`);
565
713
  }
714
+ return normalized;
715
+ }
716
+ function assertTransferablePeerPath(filePath) {
717
+ if (isInternalRemnicStatePath2(filePath)) {
718
+ throw new Error(`peer transport rejects internal Remnic state path: ${filePath}`);
719
+ }
720
+ }
721
+ async function fetchPeerRequest(fetchImpl, input, init, timeoutMs) {
722
+ const timeoutSignal = AbortSignal.timeout(timeoutMs);
723
+ const signal = init.signal ? AbortSignal.any([init.signal, timeoutSignal]) : timeoutSignal;
724
+ return fetchImpl(input, { ...init, signal });
725
+ }
726
+ async function fetchPeerSyncCapabilities(peerUrl, token, fetchImpl, timeoutMs) {
727
+ const base = normalizePeerBaseUrl(peerUrl);
728
+ const headers = token ? { authorization: `Bearer ${token}` } : {};
729
+ const routes = [
730
+ "/remnic/v1/offline-sync/capabilities",
731
+ "/engram/v1/offline-sync/capabilities"
732
+ ];
733
+ for (const route of routes) {
734
+ const response = await fetchPeerRequest(fetchImpl, `${base}${route}`, { headers }, timeoutMs);
735
+ if (response.status === 404 || response.status === 405) continue;
736
+ if (response.status === 401 || response.status === 403) {
737
+ throw new Error(`peer capability authentication failed: HTTP ${response.status}`);
738
+ }
739
+ if (!response.ok) {
740
+ throw new Error(`peer capability request failed: HTTP ${response.status}`);
741
+ }
742
+ const payload = await response.json().catch(() => null);
743
+ if (!payload || typeof payload !== "object" || !("convergenceFinalization" in payload) || typeof payload.convergenceFinalization !== "boolean" || !("manifestStream" in payload) || typeof payload.manifestStream !== "boolean") {
744
+ throw new Error("peer capability response was malformed");
745
+ }
746
+ return {
747
+ convergenceFinalization: payload.convergenceFinalization,
748
+ manifestStream: payload.manifestStream
749
+ };
750
+ }
751
+ return null;
752
+ }
753
+ async function fetchPeerManifestStream(peerUrl, namespace, token, fetchImpl = globalThis.fetch, timeoutMs = DEFAULT_PEER_REQUEST_TIMEOUT_MS) {
754
+ const base = normalizePeerBaseUrl(peerUrl);
755
+ const headers = token ? { authorization: `Bearer ${token}` } : {};
756
+ const routes = [
757
+ `/remnic/v1/offline-sync/manifest-stream?namespace=${encodeURIComponent(namespace)}&include_transcripts=false`,
758
+ `/engram/v1/offline-sync/manifest-stream?namespace=${encodeURIComponent(namespace)}&include_transcripts=false`
759
+ ];
760
+ for (const route of routes) {
761
+ const response = await fetchPeerRequest(fetchImpl, `${base}${route}`, { headers }, timeoutMs);
762
+ if (response.status === 404 || response.status === 405) continue;
763
+ if (response.status === 401 || response.status === 403) {
764
+ throw new Error(`peer manifest authentication failed: HTTP ${response.status}`);
765
+ }
766
+ if (!response.ok) throw new Error(`peer manifest request failed: HTTP ${response.status}`);
767
+ return parsePeerManifestStream(response, namespace);
768
+ }
769
+ return null;
770
+ }
771
+ async function fetchPeerSnapshot(peerUrl, namespace, token, fetchImpl = globalThis.fetch, timeoutMs = DEFAULT_PEER_REQUEST_TIMEOUT_MS) {
772
+ const base = normalizePeerBaseUrl(peerUrl);
566
773
  const routes = [
567
774
  `/remnic/v1/offline-sync/snapshot?namespace=${encodeURIComponent(namespace)}&content=false`,
568
775
  `/engram/v1/offline-sync/snapshot?namespace=${encodeURIComponent(namespace)}&content=false`
@@ -572,7 +779,7 @@ async function fetchPeerSnapshot(peerUrl, namespace, token, fetchImpl = globalTh
572
779
  for (const route of routes) {
573
780
  let response;
574
781
  try {
575
- response = await fetchImpl(`${base}${route}`, { headers });
782
+ response = await fetchPeerRequest(fetchImpl, `${base}${route}`, { headers }, timeoutMs);
576
783
  } catch (error) {
577
784
  lastFailure = error instanceof Error ? error.message : String(error);
578
785
  continue;
@@ -600,7 +807,7 @@ async function fetchPeerSnapshot(peerUrl, namespace, token, fetchImpl = globalTh
600
807
  mtimeMs: "mtimeMs" in item && typeof item.mtimeMs === "number" ? item.mtimeMs : void 0,
601
808
  bytes: "bytes" in item && typeof item.bytes === "number" ? item.bytes : void 0
602
809
  };
603
- });
810
+ }).filter((file) => !isInternalRemnicStatePath2(file.path));
604
811
  const rawTombstones = "tombstones" in data ? data.tombstones : void 0;
605
812
  if (rawTombstones !== void 0 && !Array.isArray(rawTombstones)) {
606
813
  throw new Error(`invalid peer snapshot for namespace ${namespace}: tombstones must be an array`);
@@ -624,8 +831,9 @@ function requiredResponseNumber(response, name) {
624
831
  }
625
832
  return value;
626
833
  }
627
- async function fetchPeerFileContent(peerUrl, namespace, filePath, token, fetchImpl = globalThis.fetch) {
628
- const base = peerUrl.replace(/\/+$/, "");
834
+ async function streamPeerFileContent(peerUrl, namespace, filePath, onChunk, token, fetchImpl = globalThis.fetch, timeoutMs = DEFAULT_PEER_REQUEST_TIMEOUT_MS) {
835
+ assertTransferablePeerPath(filePath);
836
+ const base = normalizePeerBaseUrl(peerUrl);
629
837
  const routes = [
630
838
  "/remnic/v1/offline-sync/file-content",
631
839
  "/engram/v1/offline-sync/file-content"
@@ -634,16 +842,17 @@ async function fetchPeerFileContent(peerUrl, namespace, filePath, token, fetchIm
634
842
  "content-type": "application/json",
635
843
  ...token ? { authorization: `Bearer ${token}` } : {}
636
844
  };
845
+ const hash = createHash2("sha256");
846
+ let offset = 0;
847
+ let expectedBytes;
848
+ let expectedSha256;
849
+ let mtimeMs;
637
850
  for (const route of routes) {
638
- try {
639
- const chunks = [];
640
- const hash = createHash2("sha256");
641
- let offset = 0;
642
- let expectedBytes;
643
- let expectedSha256;
644
- let mtimeMs;
645
- do {
646
- const response = await fetchImpl(`${base}${route}`, {
851
+ let routeFailed = false;
852
+ do {
853
+ let response;
854
+ try {
855
+ response = await fetchPeerRequest(fetchImpl, `${base}${route}`, {
647
856
  method: "POST",
648
857
  headers,
649
858
  body: JSON.stringify({
@@ -653,116 +862,148 @@ async function fetchPeerFileContent(peerUrl, namespace, filePath, token, fetchIm
653
862
  offset,
654
863
  length: OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES
655
864
  })
656
- });
865
+ }, timeoutMs);
657
866
  if (!response.ok) throw new Error(`offline file content request failed: ${response.status}`);
658
- const content = Buffer.from(await response.arrayBuffer());
867
+ } catch {
868
+ routeFailed = true;
869
+ break;
870
+ }
871
+ let content;
872
+ let totalBytes;
873
+ let responseMtimeMs;
874
+ let sha256;
875
+ try {
876
+ content = Buffer.from(await response.arrayBuffer());
659
877
  const chunkOffset = requiredResponseNumber(response, "x-remnic-chunk-offset");
660
878
  const chunkBytes = requiredResponseNumber(response, "x-remnic-chunk-bytes");
661
- const totalBytes = requiredResponseNumber(response, "x-remnic-file-bytes");
662
- const responseMtimeMs = requiredResponseNumber(response, "x-remnic-file-mtime-ms");
663
- const sha256 = response.headers.get("x-remnic-file-sha256");
879
+ totalBytes = requiredResponseNumber(response, "x-remnic-file-bytes");
880
+ responseMtimeMs = requiredResponseNumber(response, "x-remnic-file-mtime-ms");
881
+ sha256 = response.headers.get("x-remnic-file-sha256");
664
882
  const encodedPath = response.headers.get("x-remnic-file-path");
665
- if (!sha256 || chunkOffset !== offset || chunkBytes !== content.length || encodedPath !== null && decodeURIComponent(encodedPath) !== filePath || expectedBytes !== void 0 && expectedBytes !== totalBytes || expectedSha256 !== void 0 && expectedSha256 !== sha256) {
883
+ if (!sha256 || chunkOffset !== offset || chunkBytes !== content.length || encodedPath !== null && decodeURIComponent(encodedPath) !== filePath || expectedBytes !== void 0 && expectedBytes !== totalBytes || expectedSha256 !== void 0 && expectedSha256 !== sha256 || content.length === 0 && offset < totalBytes) {
666
884
  throw new Error(`offline file content response changed during transfer: ${filePath}`);
667
885
  }
668
- if (content.length === 0 && offset < totalBytes) {
669
- throw new Error(`offline file content chunk was empty before EOF: ${filePath}`);
670
- }
671
- expectedBytes = totalBytes;
672
- expectedSha256 = sha256;
673
- mtimeMs = responseMtimeMs;
674
- chunks.push(content);
675
- hash.update(content);
676
- offset += content.length;
677
- } while (expectedBytes === void 0 || offset < expectedBytes);
678
- if (expectedBytes === void 0 || expectedSha256 === void 0 || mtimeMs === void 0 || offset !== expectedBytes || hash.digest("hex") !== expectedSha256) {
679
- throw new Error(`offline file content checksum mismatch: ${filePath}`);
886
+ } catch {
887
+ routeFailed = true;
888
+ break;
680
889
  }
681
- return {
682
- content: Buffer.concat(chunks, expectedBytes),
683
- sha256: expectedSha256,
684
- bytes: expectedBytes,
685
- mtimeMs
686
- };
687
- } catch {
688
- }
890
+ expectedBytes = totalBytes;
891
+ expectedSha256 = sha256;
892
+ mtimeMs = responseMtimeMs;
893
+ await onChunk({
894
+ content,
895
+ offset,
896
+ sha256,
897
+ bytes: totalBytes,
898
+ mtimeMs: responseMtimeMs
899
+ });
900
+ hash.update(content);
901
+ offset += content.length;
902
+ } while (expectedBytes === void 0 || offset < expectedBytes);
903
+ if (!routeFailed && expectedBytes !== void 0 && offset === expectedBytes) break;
689
904
  }
690
- return null;
905
+ if (expectedBytes === void 0 || expectedSha256 === void 0 || mtimeMs === void 0 || offset !== expectedBytes || hash.digest("hex") !== expectedSha256) {
906
+ return null;
907
+ }
908
+ return { sha256: expectedSha256, bytes: expectedBytes, mtimeMs };
691
909
  }
692
- function withoutTrailingSlashes(value) {
693
- let end = value.length;
694
- while (end > 0 && value.charCodeAt(end - 1) === 47) end -= 1;
695
- return value.slice(0, end);
910
+ async function fetchPeerFileContent(peerUrl, namespace, filePath, token, fetchImpl = globalThis.fetch, timeoutMs = DEFAULT_PEER_REQUEST_TIMEOUT_MS) {
911
+ const chunks = [];
912
+ const metadata = await streamPeerFileContent(
913
+ peerUrl,
914
+ namespace,
915
+ filePath,
916
+ async (chunk) => {
917
+ chunks.push(chunk.content);
918
+ },
919
+ token,
920
+ fetchImpl,
921
+ timeoutMs
922
+ );
923
+ if (!metadata) return null;
924
+ return {
925
+ ...metadata,
926
+ content: Buffer.concat(chunks, metadata.bytes)
927
+ };
696
928
  }
697
- async function postPeerFileContent(peerUrl, namespace, filePath, content, metadata, token, fetchImpl = globalThis.fetch) {
698
- const base = withoutTrailingSlashes(peerUrl);
929
+ async function postPeerFileContent(peerUrl, namespace, filePath, source, token, fetchImpl = globalThis.fetch, timeoutMs = DEFAULT_PEER_REQUEST_TIMEOUT_MS) {
930
+ assertTransferablePeerPath(filePath);
931
+ const base = normalizePeerBaseUrl(peerUrl);
699
932
  const routes = [
700
933
  `/remnic/v1/offline-sync/apply-file-content?namespace=${encodeURIComponent(namespace)}`,
701
934
  `/engram/v1/offline-sync/apply-file-content?namespace=${encodeURIComponent(namespace)}`
702
935
  ];
936
+ let offset = 0;
703
937
  let previousAttemptFailed = false;
704
938
  for (const route of routes) {
705
- try {
706
- let offset = 0;
707
- do {
708
- const chunk = content.subarray(
709
- offset,
710
- Math.min(content.length, offset + OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES2)
711
- );
712
- const headers = {
713
- "content-type": "application/octet-stream",
714
- "x-remnic-include-transcripts": "false",
715
- "x-remnic-source-id": encodeURIComponent("remnic-converge"),
716
- "x-remnic-file-path": encodeURIComponent(filePath),
717
- "x-remnic-file-sha256": metadata.sha256,
718
- "x-remnic-file-bytes": String(content.length),
719
- "x-remnic-file-mtime-ms": String(metadata.mtimeMs),
720
- "x-remnic-chunk-offset": String(offset),
721
- ...metadata.baseSha256 ? { "x-remnic-base-sha256": metadata.baseSha256 } : {},
722
- ...token ? { authorization: `Bearer ${token}` } : {}
723
- };
724
- const response = await fetchImpl(`${base}${route}`, {
939
+ let restartedRoute = false;
940
+ while (offset < source.bytes || source.bytes === 0 && offset === 0) {
941
+ const length = Math.min(OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES2, source.bytes - offset);
942
+ let chunk;
943
+ try {
944
+ chunk = await source.readChunk(offset, length);
945
+ } catch {
946
+ return false;
947
+ }
948
+ if (chunk.length !== length) return false;
949
+ const headers = {
950
+ "content-type": "application/octet-stream",
951
+ "x-remnic-include-transcripts": "false",
952
+ "x-remnic-source-id": encodeURIComponent("remnic-converge"),
953
+ "x-remnic-file-path": encodeURIComponent(filePath),
954
+ "x-remnic-file-sha256": source.sha256,
955
+ "x-remnic-file-bytes": String(source.bytes),
956
+ "x-remnic-file-mtime-ms": String(source.mtimeMs),
957
+ "x-remnic-chunk-offset": String(offset),
958
+ ...source.baseSha256 ? { "x-remnic-base-sha256": source.baseSha256 } : {},
959
+ ...token ? { authorization: `Bearer ${token}` } : {}
960
+ };
961
+ let response;
962
+ try {
963
+ response = await fetchPeerRequest(fetchImpl, `${base}${route}`, {
725
964
  method: "POST",
726
965
  headers,
727
966
  body: new Uint8Array(chunk)
728
- });
967
+ }, timeoutMs);
729
968
  if (!response.ok) throw new Error(`offline apply-file-content request failed: ${response.status}`);
730
- const result = await response.json().catch(() => null);
731
- if (!result || typeof result !== "object" || !("done" in result) || typeof result.done !== "boolean" || !("applied" in result) || typeof result.applied !== "boolean" || !("skipped" in result) || typeof result.skipped !== "boolean" || "conflict" in result && result.conflict) {
732
- return false;
733
- }
734
- if (result.done) {
735
- if (result.skipped) return previousAttemptFailed ? "applied" : "skipped";
736
- if (result.applied && offset + chunk.length === content.length) return "applied";
737
- return false;
738
- }
739
- if (result.applied || result.skipped || chunk.length === 0) {
740
- return false;
969
+ } catch {
970
+ if (previousAttemptFailed && offset > 0 && !restartedRoute) {
971
+ offset = 0;
972
+ restartedRoute = true;
973
+ continue;
741
974
  }
742
- offset += chunk.length;
743
- } while (offset < content.length);
744
- return false;
745
- } catch {
746
- previousAttemptFailed = true;
975
+ previousAttemptFailed = true;
976
+ break;
977
+ }
978
+ const result = await response.json().catch(() => null);
979
+ if (!result || typeof result !== "object" || !("done" in result) || typeof result.done !== "boolean" || !("applied" in result) || typeof result.applied !== "boolean" || !("skipped" in result) || typeof result.skipped !== "boolean" || "conflict" in result && result.conflict) {
980
+ return false;
981
+ }
982
+ if (result.done) {
983
+ if (result.skipped) return previousAttemptFailed ? "applied" : "skipped";
984
+ return result.applied && offset + chunk.length === source.bytes ? "applied" : false;
985
+ }
986
+ if (result.applied || result.skipped || chunk.length === 0) return false;
987
+ offset += chunk.length;
747
988
  }
748
989
  }
749
990
  return false;
750
991
  }
751
- async function postPeerConvergenceComplete(peerUrl, namespaces, token, fetchImpl = globalThis.fetch) {
752
- const base = withoutTrailingSlashes(peerUrl);
992
+ async function postPeerConvergenceComplete(peerUrl, namespaces, token, fetchImpl = globalThis.fetch, timeoutMs = DEFAULT_PEER_REQUEST_TIMEOUT_MS) {
993
+ const base = normalizePeerBaseUrl(peerUrl);
753
994
  const query = namespaces.map((namespace) => `namespace=${encodeURIComponent(namespace)}`).join("&");
754
995
  const routes = [
755
996
  "/remnic/v1/offline-sync/convergence-complete",
756
997
  "/engram/v1/offline-sync/convergence-complete"
757
998
  ];
758
999
  for (const route of routes) {
759
- const response = await fetchImpl(`${base}${route}?${query}`, {
1000
+ const response = await fetchPeerRequest(fetchImpl, `${base}${route}?${query}`, {
760
1001
  method: "POST",
761
1002
  headers: {
762
1003
  "x-remnic-source-id": encodeURIComponent("remnic-converge"),
763
1004
  ...token ? { authorization: `Bearer ${token}` } : {}
764
1005
  }
765
- }).catch(() => null);
1006
+ }, timeoutMs).catch(() => null);
766
1007
  if (!response?.ok) continue;
767
1008
  const result = await response.json().catch(() => null);
768
1009
  if (result && typeof result === "object" && "namespaces" in result && Array.isArray(result.namespaces) && result.namespaces.length === namespaces.length && result.namespaces.every((namespace, index) => namespace === namespaces[index]) && "refreshed" in result && result.refreshed === true) {
@@ -771,8 +1012,9 @@ async function postPeerConvergenceComplete(peerUrl, namespaces, token, fetchImpl
771
1012
  }
772
1013
  return false;
773
1014
  }
774
- async function postPeerFileDeletion(peerUrl, namespace, filePath, baseSha256, token, fetchImpl = globalThis.fetch) {
775
- const base = withoutTrailingSlashes(peerUrl);
1015
+ async function postPeerFileDeletion(peerUrl, namespace, filePath, baseSha256, token, fetchImpl = globalThis.fetch, timeoutMs = DEFAULT_PEER_REQUEST_TIMEOUT_MS) {
1016
+ assertTransferablePeerPath(filePath);
1017
+ const base = normalizePeerBaseUrl(peerUrl);
776
1018
  const routes = ["/remnic/v1/offline-sync/apply", "/engram/v1/offline-sync/apply"];
777
1019
  const headers = {
778
1020
  "content-type": "application/json",
@@ -781,7 +1023,7 @@ async function postPeerFileDeletion(peerUrl, namespace, filePath, baseSha256, to
781
1023
  let previousAttemptFailed = false;
782
1024
  for (const route of routes) {
783
1025
  try {
784
- const response = await fetchImpl(`${base}${route}`, {
1026
+ const response = await fetchPeerRequest(fetchImpl, `${base}${route}`, {
785
1027
  method: "POST",
786
1028
  headers,
787
1029
  body: JSON.stringify({
@@ -795,7 +1037,7 @@ async function postPeerFileDeletion(peerUrl, namespace, filePath, baseSha256, to
795
1037
  changes: [{ type: "delete", path: filePath, baseSha256 }]
796
1038
  }
797
1039
  })
798
- });
1040
+ }, timeoutMs);
799
1041
  if (!response.ok) throw new Error(`offline apply request failed: ${response.status}`);
800
1042
  const result = await response.json().catch(() => null);
801
1043
  if (!result || typeof result !== "object" || !("appliedDeletes" in result) || typeof result.appliedDeletes !== "number" || !("skipped" in result) || typeof result.skipped !== "number" || !("conflicts" in result) || !Array.isArray(result.conflicts) || result.conflicts.length > 0) {
@@ -810,6 +1052,73 @@ async function postPeerFileDeletion(peerUrl, namespace, filePath, baseSha256, to
810
1052
  }
811
1053
  return false;
812
1054
  }
1055
+
1056
+ // src/converge.ts
1057
+ var TOMBSTONE_PATHS = ["state/tombstones.jsonl", "tombstones.jsonl"];
1058
+ function parseTombstoneEvidence(content) {
1059
+ const contentHashes = /* @__PURE__ */ new Set();
1060
+ const fileSha256 = /* @__PURE__ */ new Set();
1061
+ for (const line of content.split("\n")) {
1062
+ const trimmed = line.trim();
1063
+ if (!trimmed) continue;
1064
+ try {
1065
+ const record2 = JSON.parse(trimmed);
1066
+ if (typeof record2.contentHash === "string" && /^[0-9a-f]{64}$/i.test(record2.contentHash)) {
1067
+ contentHashes.add(record2.contentHash.toLowerCase());
1068
+ }
1069
+ if (typeof record2.fileSha256 === "string" && /^[0-9a-f]{64}$/i.test(record2.fileSha256)) {
1070
+ fileSha256.add(record2.fileSha256.toLowerCase());
1071
+ }
1072
+ } catch {
1073
+ continue;
1074
+ }
1075
+ }
1076
+ return { contentHashes, fileSha256 };
1077
+ }
1078
+ function tombstonedFileDigests(evidence, manifest) {
1079
+ const result = new Set(evidence.fileSha256);
1080
+ for (const file of manifest?.files ?? []) {
1081
+ if (file.memory && evidence.contentHashes.has(file.memory.contentHash.toLowerCase())) {
1082
+ result.add(file.sha256.toLowerCase());
1083
+ }
1084
+ }
1085
+ return result;
1086
+ }
1087
+ async function readLocalTombstoneEvidence(rootDir) {
1088
+ const merged = { contentHashes: /* @__PURE__ */ new Set(), fileSha256: /* @__PURE__ */ new Set() };
1089
+ for (const relativePath of TOMBSTONE_PATHS) {
1090
+ let content;
1091
+ try {
1092
+ content = await fs4.promises.readFile(path2.join(rootDir, relativePath), "utf-8");
1093
+ } catch (error) {
1094
+ if (error.code === "ENOENT") continue;
1095
+ throw error;
1096
+ }
1097
+ const parsed = parseTombstoneEvidence(content);
1098
+ for (const value of parsed.contentHashes) merged.contentHashes.add(value);
1099
+ for (const value of parsed.fileSha256) merged.fileSha256.add(value);
1100
+ }
1101
+ return merged;
1102
+ }
1103
+ async function discoverCursorNamespaces(memoryDir, peerUrl) {
1104
+ const cursorDir = path2.join(path2.resolve(memoryDir), ".remnic", "state", "converge-cursors");
1105
+ let entries;
1106
+ try {
1107
+ entries = await fs4.promises.readdir(cursorDir, { withFileTypes: true });
1108
+ } catch (error) {
1109
+ if (error.code === "ENOENT") return [];
1110
+ throw error;
1111
+ }
1112
+ const namespaces = /* @__PURE__ */ new Set();
1113
+ for (const entry of entries) {
1114
+ if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
1115
+ const cursor = await readConvergeCursor(path2.join(cursorDir, entry.name));
1116
+ if (!cursor) throw new Error(`invalid converge cursor: ${entry.name}`);
1117
+ if (path2.basename(defaultConvergeCursorPath(memoryDir, peerUrl, cursor.namespace)) !== entry.name) continue;
1118
+ namespaces.add(cursor.namespace);
1119
+ }
1120
+ return [...namespaces].sort();
1121
+ }
813
1122
  async function computeConvergePlan(options = {}) {
814
1123
  const baseMap = /* @__PURE__ */ new Map();
815
1124
  const semanticAgreementMap = /* @__PURE__ */ new Map();
@@ -825,7 +1134,7 @@ async function computeConvergePlan(options = {}) {
825
1134
  if (options.baseFilesByNamespace) {
826
1135
  for (const [ns, files] of options.baseFilesByNamespace) {
827
1136
  namespacesToPlan.add(ns);
828
- baseMap.set(ns, files);
1137
+ baseMap.set(ns, files.filter((file) => !isInternalRemnicStatePath3(file.path)));
829
1138
  }
830
1139
  }
831
1140
  if (options.semanticAgreementsByNamespace) {
@@ -837,7 +1146,7 @@ async function computeConvergePlan(options = {}) {
837
1146
  if (options.localFilesByNamespace) {
838
1147
  for (const [ns, files] of options.localFilesByNamespace) {
839
1148
  namespacesToPlan.add(ns);
840
- localMap.set(ns, files);
1149
+ localMap.set(ns, files.filter((file) => !isInternalRemnicStatePath3(file.path)));
841
1150
  }
842
1151
  }
843
1152
  if (options.localTombstonesByNamespace) {
@@ -854,7 +1163,7 @@ async function computeConvergePlan(options = {}) {
854
1163
  if (options.peerFilesByNamespace) {
855
1164
  for (const [ns, files] of options.peerFilesByNamespace) {
856
1165
  namespacesToPlan.add(ns);
857
- peerMap.set(ns, files);
1166
+ peerMap.set(ns, files.filter((file) => !isInternalRemnicStatePath3(file.path)));
858
1167
  }
859
1168
  }
860
1169
  if (options.peerTombstonesByNamespace) {
@@ -871,10 +1180,11 @@ async function computeConvergePlan(options = {}) {
871
1180
  let config = options.config;
872
1181
  if (!config) {
873
1182
  try {
874
- config = parseConfig2({});
1183
+ config = parseConfig3({});
875
1184
  } catch {
876
1185
  }
877
1186
  }
1187
+ const memoryDir = options.cursorDir ?? config?.memoryDir;
878
1188
  if (!options.localFilesByNamespace && config) {
879
1189
  const roots = await resolveCorpusNamespaceRoots({ config });
880
1190
  const discovered = await listNamespaces({ config });
@@ -884,47 +1194,55 @@ async function computeConvergePlan(options = {}) {
884
1194
  for (const rootInfo of roots) {
885
1195
  const ns = rootInfo.namespace;
886
1196
  namespacesToPlan.add(ns);
887
- try {
888
- const snapshot = await buildOfflineSyncSnapshotFromBase({
889
- root: rootInfo.rootDir,
890
- sourceId: "local",
891
- includeContent: false
892
- });
893
- const files = snapshot.files.map((record) => ({
894
- path: record.path,
895
- sha256: record.sha256,
896
- mtimeMs: record.mtimeMs,
897
- bytes: record.bytes
898
- }));
899
- localMap.set(ns, files);
900
- try {
901
- const io = await createOfflineStorageIo(rootInfo.rootDir);
902
- localManifests.set(
903
- ns,
904
- await buildReconcileManifest({
905
- files,
906
- readFile: async (file) => {
907
- const readFile2 = io.readFile;
908
- if (!readFile2) throw new Error("offline storage cannot read reconciliation manifest files");
909
- return await readFile2({
910
- root: rootInfo.rootDir,
911
- path: file.path,
912
- filePath: path2.join(rootInfo.rootDir, file.path)
913
- });
914
- }
915
- })
916
- );
917
- } catch {
918
- localManifests.delete(ns);
1197
+ const snapshot = await buildOfflineSyncSnapshotFromBase({
1198
+ root: rootInfo.rootDir,
1199
+ sourceId: "local",
1200
+ includeContent: false
1201
+ });
1202
+ const files = snapshot.files.filter((record2) => !isInternalRemnicStatePath3(record2.path)).map((record2) => ({
1203
+ path: record2.path,
1204
+ sha256: record2.sha256,
1205
+ mtimeMs: record2.mtimeMs,
1206
+ bytes: record2.bytes
1207
+ }));
1208
+ localMap.set(ns, files);
1209
+ const evidence = await readLocalTombstoneEvidence(rootInfo.rootDir);
1210
+ const io = await createOfflineStorageIo(rootInfo.rootDir);
1211
+ let manifestReadFailed = false;
1212
+ const manifest = await buildReconcileManifest({
1213
+ files,
1214
+ parseMemory: parseFrontmatter,
1215
+ readFile: async (file) => {
1216
+ const readFile2 = io.readFile;
1217
+ if (!readFile2) {
1218
+ manifestReadFailed = true;
1219
+ throw new Error("offline storage cannot read reconciliation manifest files");
1220
+ }
1221
+ try {
1222
+ return await readFile2({
1223
+ root: rootInfo.rootDir,
1224
+ path: file.path,
1225
+ filePath: path2.join(rootInfo.rootDir, file.path)
1226
+ });
1227
+ } catch (error) {
1228
+ manifestReadFailed = true;
1229
+ throw error;
1230
+ }
919
1231
  }
920
- const tombstones = await readLocalTombstones(rootInfo.rootDir);
921
- localTombstones.set(ns, tombstones);
922
- } catch {
923
- localMap.set(ns, []);
1232
+ });
1233
+ if (manifestReadFailed) {
1234
+ throw new Error(`failed to build local reconciliation manifest for namespace ${ns}`);
924
1235
  }
1236
+ localManifests.set(ns, manifest);
1237
+ localTombstones.set(ns, tombstonedFileDigests(evidence, manifest));
925
1238
  }
926
1239
  }
927
1240
  const peerUrl = options.peerUrl;
1241
+ if (memoryDir && peerUrl) {
1242
+ for (const namespace of await discoverCursorNamespaces(memoryDir, peerUrl)) {
1243
+ namespacesToPlan.add(namespace);
1244
+ }
1245
+ }
928
1246
  if (!options.peerFilesByNamespace && peerUrl) {
929
1247
  let resolvedToken;
930
1248
  if (options.peerToken) {
@@ -937,36 +1255,84 @@ async function computeConvergePlan(options = {}) {
937
1255
  }
938
1256
  }
939
1257
  const fetchFn = options.fetchImpl ?? globalThis.fetch;
1258
+ const timeoutMs = options.peerRequestTimeoutMs ?? DEFAULT_PEER_REQUEST_TIMEOUT_MS;
1259
+ const capabilities = await fetchPeerSyncCapabilities(
1260
+ peerUrl,
1261
+ resolvedToken,
1262
+ fetchFn,
1263
+ timeoutMs
1264
+ );
940
1265
  for (const ns of namespacesToPlan) {
941
- const peerData = await fetchPeerSnapshot(peerUrl, ns, resolvedToken, fetchFn);
942
- peerMap.set(ns, peerData.files);
943
- peerTombstones.set(ns, peerData.tombstones);
944
- peerManifests.set(
945
- ns,
946
- await buildReconcileManifest({
947
- files: peerData.files,
1266
+ const peerData = await fetchPeerSnapshot(peerUrl, ns, resolvedToken, fetchFn, timeoutMs);
1267
+ const streamedManifest = capabilities?.manifestStream ? await fetchPeerManifestStream(peerUrl, ns, resolvedToken, fetchFn, timeoutMs) : null;
1268
+ const peerFiles = streamedManifest?.files ?? peerData.files;
1269
+ peerMap.set(ns, peerFiles);
1270
+ let peerManifest = streamedManifest;
1271
+ if (!peerManifest) {
1272
+ let readFailure;
1273
+ peerManifest = await buildReconcileManifest({
1274
+ files: peerFiles,
1275
+ parseMemory: parseFrontmatter,
948
1276
  cachedFiles: localManifests.get(ns)?.files,
949
1277
  readFile: async (file) => {
950
- const remote = await fetchPeerFileContent(peerUrl, ns, file.path, resolvedToken, fetchFn);
1278
+ let remote;
1279
+ try {
1280
+ remote = await fetchPeerFileContent(peerUrl, ns, file.path, resolvedToken, fetchFn, timeoutMs);
1281
+ } catch (error) {
1282
+ readFailure = error instanceof Error ? error : new Error(String(error));
1283
+ throw readFailure;
1284
+ }
951
1285
  if (!remote || remote.sha256 !== file.sha256) {
952
- throw new Error(`failed to read peer reconciliation manifest file: ${file.path}`);
1286
+ readFailure = new Error(`failed to read peer reconciliation manifest file: ${file.path}`);
1287
+ throw readFailure;
953
1288
  }
954
1289
  return remote.content;
955
1290
  }
956
- })
957
- );
1291
+ });
1292
+ if (readFailure) throw readFailure;
1293
+ }
1294
+ peerManifests.set(ns, peerManifest);
1295
+ const evidence = { contentHashes: /* @__PURE__ */ new Set(), fileSha256: /* @__PURE__ */ new Set() };
1296
+ for (const tombstonePath of TOMBSTONE_PATHS) {
1297
+ const state = peerFiles.find((file) => file.path === tombstonePath);
1298
+ if (!state) continue;
1299
+ const remote = await fetchPeerFileContent(
1300
+ peerUrl,
1301
+ ns,
1302
+ tombstonePath,
1303
+ resolvedToken,
1304
+ fetchFn,
1305
+ timeoutMs
1306
+ );
1307
+ if (!remote || remote.sha256.toLowerCase() !== state.sha256.toLowerCase()) {
1308
+ throw new Error(`failed to read peer tombstone evidence: ${tombstonePath}`);
1309
+ }
1310
+ const parsed = parseTombstoneEvidence(remote.content.toString("utf8"));
1311
+ for (const value of parsed.contentHashes) evidence.contentHashes.add(value);
1312
+ for (const value of parsed.fileSha256) evidence.fileSha256.add(value);
1313
+ }
1314
+ const mapped = tombstonedFileDigests(evidence, peerManifests.get(ns));
1315
+ for (const digest of peerData.tombstones) mapped.add(digest);
1316
+ peerTombstones.set(ns, mapped);
958
1317
  }
959
1318
  }
960
- const memoryDir = options.cursorDir ?? config?.memoryDir;
961
1319
  if (memoryDir && options.peerUrl && (!options.baseFilesByNamespace || !options.semanticAgreementsByNamespace)) {
962
1320
  for (const ns of namespacesToPlan) {
963
1321
  const cursorPath = defaultConvergeCursorPath(memoryDir, options.peerUrl, ns);
964
1322
  const cursor = await readConvergeCursor(cursorPath);
965
1323
  if (!options.baseFilesByNamespace && cursor?.baseFiles && cursor.baseFiles.length > 0) {
966
- baseMap.set(ns, cursor.baseFiles);
1324
+ baseMap.set(
1325
+ ns,
1326
+ cursor.baseFiles.filter((file) => !isInternalRemnicStatePath3(file.path))
1327
+ );
967
1328
  }
968
1329
  if (!options.semanticAgreementsByNamespace && cursor?.semanticAgreements && cursor.semanticAgreements.length > 0) {
969
- semanticAgreementMap.set(ns, cursor.semanticAgreements);
1330
+ semanticAgreementMap.set(
1331
+ ns,
1332
+ cursor.semanticAgreements.filter(
1333
+ (agreement) => !isInternalRemnicStatePath3(agreement.local.path) && !isInternalRemnicStatePath3(agreement.peer.path)
1334
+ )
1335
+ );
970
1336
  }
971
1337
  }
972
1338
  }
@@ -1030,7 +1396,7 @@ async function executeConvergeApply(options = {}) {
1030
1396
  }
1031
1397
  if (options.dryRun) {
1032
1398
  return {
1033
- converged: false,
1399
+ converged: plan.converged,
1034
1400
  status: "dry_run",
1035
1401
  plan,
1036
1402
  transfers: plannedTransfers,
@@ -1056,10 +1422,11 @@ async function executeConvergeApply(options = {}) {
1056
1422
  }
1057
1423
  }
1058
1424
  const fetchFn = options.fetchImpl ?? globalThis.fetch;
1425
+ const timeoutMs = options.peerRequestTimeoutMs ?? DEFAULT_PEER_REQUEST_TIMEOUT_MS;
1059
1426
  let config = options.config;
1060
1427
  if (!config) {
1061
1428
  try {
1062
- config = parseConfig2({});
1429
+ config = parseConfig3({});
1063
1430
  } catch {
1064
1431
  }
1065
1432
  }
@@ -1089,134 +1456,202 @@ async function executeConvergeApply(options = {}) {
1089
1456
  transferType = entry.localSha256 ? "push" : "delete-peer";
1090
1457
  }
1091
1458
  }
1459
+ const localPath = entry.semanticAgreement?.local.path ?? entry.path;
1460
+ const peerPath = entry.semanticAgreement?.peer.path ?? entry.path;
1092
1461
  if (transferType === "pull") {
1093
- let remoteFile = null;
1094
- const buffered = options.peerFileBuffers?.get(entry.namespace)?.get(entry.path);
1462
+ const buffered = options.peerFileBuffers?.get(entry.namespace)?.get(peerPath);
1463
+ if (options.localFileBuffers) {
1464
+ let remoteFile = null;
1465
+ if (buffered) {
1466
+ const state = options.peerFilesByNamespace?.get(entry.namespace)?.find((file) => file.path === peerPath);
1467
+ remoteFile = {
1468
+ content: buffered,
1469
+ sha256: state?.sha256 ?? entry.peerSha256 ?? createHash3("sha256").update(buffered).digest("hex"),
1470
+ bytes: buffered.length,
1471
+ mtimeMs: state?.mtimeMs ?? 0
1472
+ };
1473
+ } else if (options.peerUrl) {
1474
+ remoteFile = await fetchPeerFileContent(
1475
+ options.peerUrl,
1476
+ entry.namespace,
1477
+ peerPath,
1478
+ resolvedToken,
1479
+ fetchFn,
1480
+ timeoutMs
1481
+ );
1482
+ }
1483
+ if (!remoteFile || entry.peerSha256 && remoteFile.sha256 !== entry.peerSha256) {
1484
+ actualTransfers.failed += 1;
1485
+ continue;
1486
+ }
1487
+ let namespaceFiles = options.localFileBuffers.get(entry.namespace);
1488
+ if (!namespaceFiles) {
1489
+ namespaceFiles = /* @__PURE__ */ new Map();
1490
+ options.localFileBuffers.set(entry.namespace, namespaceFiles);
1491
+ }
1492
+ namespaceFiles.set(localPath, remoteFile.content);
1493
+ if (entry.action === "conflict") actualTransfers.conflictsResolved += 1;
1494
+ else actualTransfers.pulled += 1;
1495
+ continue;
1496
+ }
1497
+ const rootDir = rootMap.get(entry.namespace);
1498
+ if (!rootDir) {
1499
+ actualTransfers.failed += 1;
1500
+ continue;
1501
+ }
1502
+ const io = await createOfflineStorageIo(rootDir);
1503
+ const expectedLocalSha256 = entry.action === "conflict" ? entry.localSha256 : entry.baseSha256;
1504
+ let transferComplete = false;
1505
+ let transferRejected = false;
1506
+ const applyChunk = async (chunk) => {
1507
+ if (transferRejected) return;
1508
+ if (entry.peerSha256 && chunk.sha256 !== entry.peerSha256) {
1509
+ transferRejected = true;
1510
+ return;
1511
+ }
1512
+ const chunkResult = await applyOfflineSyncFileContentChunk({
1513
+ root: rootDir,
1514
+ sourceId: "remnic-converge",
1515
+ path: localPath,
1516
+ sha256: chunk.sha256,
1517
+ bytes: chunk.bytes,
1518
+ mtimeMs: chunk.mtimeMs,
1519
+ offset: chunk.offset,
1520
+ content: chunk.content,
1521
+ ...expectedLocalSha256 ? { baseSha256: expectedLocalSha256 } : {},
1522
+ readFile: io.readFile,
1523
+ readFileDigest: io.readFileDigest,
1524
+ writeFile: io.writeFile,
1525
+ writeStagingFile: io.writeStagingFile,
1526
+ writeFileChunks: io.writeFileChunks
1527
+ });
1528
+ if (chunkResult.conflict) {
1529
+ transferRejected = true;
1530
+ } else if (chunkResult.done) {
1531
+ transferComplete = chunkResult.applied || chunkResult.skipped;
1532
+ } else if (chunkResult.applied || chunkResult.skipped || chunk.content.length === 0) {
1533
+ transferRejected = true;
1534
+ }
1535
+ };
1536
+ let metadata = null;
1095
1537
  if (buffered) {
1096
- const state = options.peerFilesByNamespace?.get(entry.namespace)?.find((file) => file.path === entry.path);
1097
- remoteFile = {
1098
- content: buffered,
1099
- sha256: state?.sha256 ?? entry.peerSha256 ?? createHash2("sha256").update(buffered).digest("hex"),
1100
- bytes: buffered.length,
1101
- mtimeMs: state?.mtimeMs ?? 0
1102
- };
1538
+ const state = options.peerFilesByNamespace?.get(entry.namespace)?.find((file) => file.path === peerPath);
1539
+ const sha256 = state?.sha256 ?? entry.peerSha256 ?? createHash3("sha256").update(buffered).digest("hex");
1540
+ const bytes = buffered.length;
1541
+ const mtimeMs = state?.mtimeMs ?? 0;
1542
+ let offset = 0;
1543
+ do {
1544
+ const content = buffered.subarray(
1545
+ offset,
1546
+ Math.min(bytes, offset + OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES3)
1547
+ );
1548
+ await applyChunk({ content, offset, sha256, bytes, mtimeMs });
1549
+ offset += content.length;
1550
+ } while (!transferRejected && !transferComplete && offset < bytes);
1551
+ metadata = { sha256, bytes, mtimeMs };
1103
1552
  } else if (options.peerUrl) {
1104
- remoteFile = await fetchPeerFileContent(
1553
+ metadata = await streamPeerFileContent(
1105
1554
  options.peerUrl,
1106
1555
  entry.namespace,
1107
- entry.path,
1556
+ peerPath,
1557
+ applyChunk,
1108
1558
  resolvedToken,
1109
- fetchFn
1559
+ fetchFn,
1560
+ timeoutMs
1110
1561
  );
1111
1562
  }
1112
- if (remoteFile !== null && (!entry.peerSha256 || remoteFile.sha256 === entry.peerSha256)) {
1113
- if (options.localFileBuffers) {
1114
- let nsMap = options.localFileBuffers.get(entry.namespace);
1115
- if (!nsMap) {
1116
- nsMap = /* @__PURE__ */ new Map();
1117
- options.localFileBuffers.set(entry.namespace, nsMap);
1118
- }
1119
- nsMap.set(entry.path, remoteFile.content);
1120
- if (entry.action === "conflict") actualTransfers.conflictsResolved += 1;
1121
- else actualTransfers.pulled += 1;
1122
- } else {
1123
- const rootDir = rootMap.get(entry.namespace);
1124
- if (rootDir) {
1125
- const io = await createOfflineStorageIo(rootDir);
1126
- const expectedLocalSha256 = entry.action === "conflict" ? entry.localSha256 : entry.baseSha256;
1127
- let offset = 0;
1128
- let transferComplete = false;
1129
- do {
1130
- const chunk = remoteFile.content.subarray(
1131
- offset,
1132
- Math.min(
1133
- remoteFile.content.length,
1134
- offset + OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES2
1135
- )
1136
- );
1137
- const chunkResult = await applyOfflineSyncFileContentChunk({
1138
- root: rootDir,
1139
- sourceId: "remnic-converge",
1140
- path: entry.path,
1141
- sha256: remoteFile.sha256,
1142
- bytes: remoteFile.bytes,
1143
- mtimeMs: remoteFile.mtimeMs,
1144
- offset,
1145
- content: chunk,
1146
- ...expectedLocalSha256 ? { baseSha256: expectedLocalSha256 } : {},
1147
- readFile: io.readFile,
1148
- readFileDigest: io.readFileDigest,
1149
- writeFile: io.writeFile,
1150
- writeStagingFile: io.writeStagingFile,
1151
- writeFileChunks: io.writeFileChunks
1152
- });
1153
- if (chunkResult.conflict) {
1154
- break;
1155
- }
1156
- if (chunkResult.done) {
1157
- transferComplete = chunkResult.applied || chunkResult.skipped;
1158
- break;
1159
- }
1160
- if (chunkResult.applied || chunkResult.skipped || chunk.length === 0) {
1161
- break;
1162
- }
1163
- offset += chunk.length;
1164
- } while (offset < remoteFile.content.length);
1165
- if (transferComplete) {
1166
- if (entry.action === "conflict") actualTransfers.conflictsResolved += 1;
1167
- else actualTransfers.pulled += 1;
1168
- } else {
1169
- actualTransfers.failed += 1;
1170
- }
1171
- } else {
1172
- actualTransfers.failed += 1;
1173
- }
1174
- }
1563
+ if (metadata && transferComplete && !transferRejected && (!entry.peerSha256 || metadata.sha256 === entry.peerSha256)) {
1564
+ if (entry.action === "conflict") actualTransfers.conflictsResolved += 1;
1565
+ else actualTransfers.pulled += 1;
1175
1566
  } else {
1176
1567
  actualTransfers.failed += 1;
1177
1568
  }
1178
1569
  } else if (transferType === "push") {
1179
- let content = null;
1180
- let mtimeMs = options.localFilesByNamespace?.get(entry.namespace)?.find((file) => file.path === entry.path)?.mtimeMs;
1181
- if (options.localFileBuffers?.get(entry.namespace)?.has(entry.path)) {
1182
- content = options.localFileBuffers.get(entry.namespace).get(entry.path);
1183
- } else {
1570
+ const localBuffer = options.localFileBuffers?.get(entry.namespace)?.get(localPath);
1571
+ let source = null;
1572
+ let closeSource;
1573
+ const expectedPeerSha256 = entry.action === "conflict" ? entry.peerSha256 : entry.baseSha256;
1574
+ if (localBuffer && entry.localSha256) {
1575
+ source = {
1576
+ sha256: entry.localSha256,
1577
+ bytes: localBuffer.length,
1578
+ mtimeMs: options.localFilesByNamespace?.get(entry.namespace)?.find((file) => file.path === localPath)?.mtimeMs ?? 0,
1579
+ ...expectedPeerSha256 ? { baseSha256: expectedPeerSha256 } : {},
1580
+ readChunk: async (offset, length) => localBuffer.subarray(offset, offset + length)
1581
+ };
1582
+ } else if (entry.localSha256) {
1184
1583
  const rootDir = rootMap.get(entry.namespace);
1185
1584
  if (rootDir) {
1186
- const filePath = path2.join(rootDir, entry.path);
1187
1585
  try {
1586
+ const filePath = path2.join(rootDir, localPath);
1188
1587
  const io = await createOfflineStorageIo(rootDir);
1189
- content = await io.readFile({ root: rootDir, path: entry.path, filePath });
1190
- mtimeMs ??= (await fs3.promises.stat(filePath)).mtimeMs;
1588
+ const current = await io.readFileDigest({ root: rootDir, path: localPath, filePath });
1589
+ if (current.sha256 !== entry.localSha256) {
1590
+ throw new Error(`local file changed during push: ${localPath}`);
1591
+ }
1592
+ const stat = await fs4.promises.stat(filePath);
1593
+ let chunks;
1594
+ let chunkOffset = 0;
1595
+ const resetChunks = async () => {
1596
+ await chunks?.return?.();
1597
+ chunks = io.readFileChunks({
1598
+ root: rootDir,
1599
+ path: localPath,
1600
+ filePath,
1601
+ chunkSize: OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES3
1602
+ })[Symbol.asyncIterator]();
1603
+ chunkOffset = 0;
1604
+ };
1605
+ closeSource = async () => {
1606
+ await chunks?.return?.();
1607
+ };
1608
+ source = {
1609
+ sha256: entry.localSha256,
1610
+ bytes: current.bytes,
1611
+ mtimeMs: stat.mtimeMs,
1612
+ ...expectedPeerSha256 ? { baseSha256: expectedPeerSha256 } : {},
1613
+ readChunk: async (offset, length) => {
1614
+ if (!chunks || offset < chunkOffset) await resetChunks();
1615
+ while (chunkOffset < offset) {
1616
+ const skipped = await chunks.next();
1617
+ if (skipped.done || chunkOffset + skipped.value.length > offset) {
1618
+ throw new Error(`cannot resume local file upload at offset ${offset}: ${localPath}`);
1619
+ }
1620
+ chunkOffset += skipped.value.length;
1621
+ }
1622
+ const next = await chunks.next();
1623
+ if (next.done) return Buffer.alloc(0);
1624
+ if (next.value.length > length) {
1625
+ throw new Error(`local file chunk exceeds requested length: ${localPath}`);
1626
+ }
1627
+ chunkOffset += next.value.length;
1628
+ return next.value;
1629
+ }
1630
+ };
1191
1631
  } catch {
1192
- content = null;
1632
+ source = null;
1193
1633
  }
1194
1634
  }
1195
1635
  }
1196
- if (content !== null) {
1197
- if (options.peerFileBuffers) {
1198
- let nsMap = options.peerFileBuffers.get(entry.namespace);
1199
- if (!nsMap) {
1200
- nsMap = /* @__PURE__ */ new Map();
1201
- options.peerFileBuffers.set(entry.namespace, nsMap);
1636
+ try {
1637
+ if (options.peerFileBuffers && localBuffer) {
1638
+ let namespaceFiles = options.peerFileBuffers.get(entry.namespace);
1639
+ if (!namespaceFiles) {
1640
+ namespaceFiles = /* @__PURE__ */ new Map();
1641
+ options.peerFileBuffers.set(entry.namespace, namespaceFiles);
1202
1642
  }
1203
- nsMap.set(entry.path, content);
1643
+ namespaceFiles.set(peerPath, localBuffer);
1204
1644
  if (entry.action === "conflict") actualTransfers.conflictsResolved += 1;
1205
1645
  else actualTransfers.pushed += 1;
1206
- } else if (options.peerUrl && entry.localSha256) {
1207
- const expectedPeerSha256 = entry.action === "conflict" ? entry.peerSha256 : entry.baseSha256;
1646
+ } else if (options.peerUrl && source) {
1208
1647
  const applied = await postPeerFileContent(
1209
1648
  options.peerUrl,
1210
1649
  entry.namespace,
1211
- entry.path,
1212
- content,
1213
- {
1214
- sha256: entry.localSha256,
1215
- mtimeMs: mtimeMs ?? 0,
1216
- ...expectedPeerSha256 ? { baseSha256: expectedPeerSha256 } : {}
1217
- },
1650
+ peerPath,
1651
+ source,
1218
1652
  resolvedToken,
1219
- fetchFn
1653
+ fetchFn,
1654
+ timeoutMs
1220
1655
  );
1221
1656
  if (applied) {
1222
1657
  if (applied === "applied") peerMutatedNamespaces.add(entry.namespace);
@@ -1228,16 +1663,16 @@ async function executeConvergeApply(options = {}) {
1228
1663
  } else {
1229
1664
  actualTransfers.failed += 1;
1230
1665
  }
1231
- } else {
1232
- actualTransfers.failed += 1;
1666
+ } finally {
1667
+ await closeSource?.();
1233
1668
  }
1234
1669
  } else if (transferType === "delete-local") {
1235
1670
  let deleted = false;
1236
1671
  const bufferedFiles = options.localFileBuffers?.get(entry.namespace);
1237
1672
  if (options.localFileBuffers) {
1238
- const current = bufferedFiles?.get(entry.path);
1239
- if (current && entry.localSha256 && createHash2("sha256").update(current).digest("hex") === entry.localSha256) {
1240
- bufferedFiles.delete(entry.path);
1673
+ const current = bufferedFiles?.get(localPath);
1674
+ if (current && entry.localSha256 && createHash3("sha256").update(current).digest("hex") === entry.localSha256) {
1675
+ bufferedFiles.delete(localPath);
1241
1676
  deleted = true;
1242
1677
  }
1243
1678
  } else {
@@ -1245,10 +1680,10 @@ async function executeConvergeApply(options = {}) {
1245
1680
  if (rootDir && entry.localSha256) {
1246
1681
  try {
1247
1682
  const io = await createOfflineStorageIo(rootDir);
1248
- const filePath = path2.join(rootDir, entry.path);
1249
- const current = await io.readFileDigest({ root: rootDir, path: entry.path, filePath });
1683
+ const filePath = path2.join(rootDir, localPath);
1684
+ const current = await io.readFileDigest({ root: rootDir, path: localPath, filePath });
1250
1685
  if (current.sha256 === entry.localSha256) {
1251
- await io.deleteFile({ root: rootDir, path: entry.path, filePath });
1686
+ await io.deleteFile({ root: rootDir, path: localPath, filePath });
1252
1687
  deleted = true;
1253
1688
  }
1254
1689
  } catch {
@@ -1262,19 +1697,20 @@ async function executeConvergeApply(options = {}) {
1262
1697
  let deleted = false;
1263
1698
  const bufferedFiles = options.peerFileBuffers?.get(entry.namespace);
1264
1699
  if (options.peerFileBuffers) {
1265
- const current = bufferedFiles?.get(entry.path);
1266
- if (current && entry.peerSha256 && createHash2("sha256").update(current).digest("hex") === entry.peerSha256) {
1267
- bufferedFiles.delete(entry.path);
1700
+ const current = bufferedFiles?.get(peerPath);
1701
+ if (current && entry.peerSha256 && createHash3("sha256").update(current).digest("hex") === entry.peerSha256) {
1702
+ bufferedFiles.delete(peerPath);
1268
1703
  deleted = true;
1269
1704
  }
1270
1705
  } else if (options.peerUrl && entry.peerSha256) {
1271
1706
  const deletionResult = await postPeerFileDeletion(
1272
1707
  options.peerUrl,
1273
1708
  entry.namespace,
1274
- entry.path,
1709
+ peerPath,
1275
1710
  entry.peerSha256,
1276
1711
  resolvedToken,
1277
- fetchFn
1712
+ fetchFn,
1713
+ timeoutMs
1278
1714
  );
1279
1715
  deleted = Boolean(deletionResult);
1280
1716
  if (deletionResult === "applied") peerMutatedNamespaces.add(entry.namespace);
@@ -1282,7 +1718,60 @@ async function executeConvergeApply(options = {}) {
1282
1718
  if (deleted) actualTransfers.conflictsResolved += 1;
1283
1719
  else actualTransfers.failed += 1;
1284
1720
  } else if (transferType === "suppress") {
1285
- actualTransfers.suppressed += 1;
1721
+ let suppressed = entry.suppressSide !== void 0;
1722
+ if (entry.suppressSide === "local" || entry.suppressSide === "both") {
1723
+ let deletedLocal = false;
1724
+ if (entry.localSha256 && options.localFileBuffers) {
1725
+ const files = options.localFileBuffers.get(entry.namespace);
1726
+ const current = files?.get(localPath);
1727
+ if (current && createHash3("sha256").update(current).digest("hex") === entry.localSha256) {
1728
+ files.delete(localPath);
1729
+ deletedLocal = true;
1730
+ }
1731
+ } else if (entry.localSha256) {
1732
+ const rootDir = rootMap.get(entry.namespace);
1733
+ if (rootDir) {
1734
+ try {
1735
+ const io = await createOfflineStorageIo(rootDir);
1736
+ const filePath = path2.join(rootDir, localPath);
1737
+ const current = await io.readFileDigest({ root: rootDir, path: localPath, filePath });
1738
+ if (current.sha256 === entry.localSha256) {
1739
+ await io.deleteFile({ root: rootDir, path: localPath, filePath });
1740
+ deletedLocal = true;
1741
+ }
1742
+ } catch {
1743
+ deletedLocal = false;
1744
+ }
1745
+ }
1746
+ }
1747
+ suppressed &&= deletedLocal;
1748
+ }
1749
+ if (entry.suppressSide === "peer" || entry.suppressSide === "both") {
1750
+ let deletedPeer = false;
1751
+ if (entry.peerSha256 && options.peerFileBuffers) {
1752
+ const files = options.peerFileBuffers.get(entry.namespace);
1753
+ const current = files?.get(peerPath);
1754
+ if (current && createHash3("sha256").update(current).digest("hex") === entry.peerSha256) {
1755
+ files.delete(peerPath);
1756
+ deletedPeer = true;
1757
+ }
1758
+ } else if (entry.peerSha256 && options.peerUrl) {
1759
+ const result = await postPeerFileDeletion(
1760
+ options.peerUrl,
1761
+ entry.namespace,
1762
+ peerPath,
1763
+ entry.peerSha256,
1764
+ resolvedToken,
1765
+ fetchFn,
1766
+ timeoutMs
1767
+ );
1768
+ deletedPeer = Boolean(result);
1769
+ if (result === "applied") peerMutatedNamespaces.add(entry.namespace);
1770
+ }
1771
+ suppressed &&= deletedPeer;
1772
+ }
1773
+ if (suppressed) actualTransfers.suppressed += 1;
1774
+ else actualTransfers.failed += 1;
1286
1775
  }
1287
1776
  }
1288
1777
  if (options.peerUrl && peerMutatedNamespaces.size > 0) {
@@ -1291,7 +1780,8 @@ async function executeConvergeApply(options = {}) {
1291
1780
  options.peerUrl,
1292
1781
  namespaces,
1293
1782
  resolvedToken,
1294
- fetchFn
1783
+ fetchFn,
1784
+ timeoutMs
1295
1785
  )) {
1296
1786
  actualTransfers.failed += 1;
1297
1787
  }
@@ -1310,7 +1800,7 @@ async function executeConvergeApply(options = {}) {
1310
1800
  };
1311
1801
  }
1312
1802
  async function updateCursorsForPlan(plan, options) {
1313
- const peerUrl = options.peerUrl ?? "local";
1803
+ const peerUrl = normalizeConvergePeerUrl2(options.peerUrl ?? "local");
1314
1804
  let memoryDir;
1315
1805
  if (options.cursorDir) {
1316
1806
  memoryDir = options.cursorDir;
@@ -1376,7 +1866,7 @@ function formatConvergeApplyReport(result) {
1376
1866
  lines.push(formatConvergeReport(result.plan));
1377
1867
  return lines.join("\n");
1378
1868
  }
1379
- async function cmdConverge(action, rest, json, config = parseConfig2({})) {
1869
+ async function cmdConverge(action, rest, json, config = parseConfig3({})) {
1380
1870
  if (action === "help" || action === "--help" || action === "-h" || rest.includes("--help") || rest.includes("-h")) {
1381
1871
  console.log(`Usage: remnic converge <plan|apply> [options]
1382
1872
 
@@ -1483,14 +1973,14 @@ function buildNamespacePolicyCheck(args) {
1483
1973
  import { WriteQuarantineStore as WriteQuarantineStore2 } from "@remnic/core/write-quarantine.js";
1484
1974
 
1485
1975
  // src/quarantine-cli.ts
1486
- import { basename } from "path";
1976
+ import { basename as basename2 } from "path";
1487
1977
  function renderQuarantineList(records, format) {
1488
1978
  if (format === "json") {
1489
- const summary = records.map((record) => ({
1490
- timestamp: record.timestamp,
1491
- operation: record.operation,
1492
- principal: record.principal,
1493
- attemptedNamespace: record.attemptedNamespace
1979
+ const summary = records.map((record2) => ({
1980
+ timestamp: record2.timestamp,
1981
+ operation: record2.operation,
1982
+ principal: record2.principal,
1983
+ attemptedNamespace: record2.attemptedNamespace
1494
1984
  }));
1495
1985
  return JSON.stringify(summary, null, 2);
1496
1986
  }
@@ -1499,9 +1989,9 @@ function renderQuarantineList(records, format) {
1499
1989
  }
1500
1990
  if (records.length === 0) return "No quarantined writes.";
1501
1991
  const lines = [`Quarantined writes (${records.length}):`, ""];
1502
- for (const record of records) {
1992
+ for (const record2 of records) {
1503
1993
  lines.push(
1504
- ` ${record.timestamp} ${record.operation} principal=${record.principal ?? "-"} attemptedNamespace=${record.attemptedNamespace}`
1994
+ ` ${record2.timestamp} ${record2.operation} principal=${record2.principal ?? "-"} attemptedNamespace=${record2.attemptedNamespace}`
1505
1995
  );
1506
1996
  }
1507
1997
  return lines.join("\n");
@@ -1509,10 +1999,10 @@ function renderQuarantineList(records, format) {
1509
1999
  async function replayQuarantine(opts) {
1510
2000
  const result = { replayed: 0, failures: [], deleteFailures: [] };
1511
2001
  for (const entry of await opts.store.entries()) {
1512
- const { record } = entry;
1513
- const basePayload = record.payload;
1514
- const principal = opts.principal ?? record.principal ?? void 0;
1515
- const idempotencyKey = typeof basePayload.idempotencyKey === "string" && basePayload.idempotencyKey.length > 0 ? basePayload.idempotencyKey : `quarantine-replay:${basename(entry.path)}`;
2002
+ const { record: record2 } = entry;
2003
+ const basePayload = record2.payload;
2004
+ const principal = opts.principal ?? record2.principal ?? void 0;
2005
+ const idempotencyKey = typeof basePayload.idempotencyKey === "string" && basePayload.idempotencyKey.length > 0 ? basePayload.idempotencyKey : `quarantine-replay:${basename2(entry.path)}`;
1516
2006
  const request = {
1517
2007
  ...basePayload,
1518
2008
  namespace: opts.targetNamespace,
@@ -1521,10 +2011,10 @@ async function replayQuarantine(opts) {
1521
2011
  ...principal ? { authenticatedPrincipal: principal } : {}
1522
2012
  };
1523
2013
  try {
1524
- await opts.submit(record.operation, request);
2014
+ await opts.submit(record2.operation, request);
1525
2015
  } catch (err) {
1526
2016
  result.failures.push({
1527
- operation: record.operation,
2017
+ operation: record2.operation,
1528
2018
  attemptedNamespace: opts.targetNamespace,
1529
2019
  error: err instanceof Error ? err.message : String(err)
1530
2020
  });
@@ -1582,8 +2072,8 @@ function renderReplayResult(result, targetNamespace, format) {
1582
2072
  }
1583
2073
 
1584
2074
  // src/quarantine-replay.ts
1585
- import * as fs4 from "fs";
1586
- import { EngramAccessService, Orchestrator as Orchestrator2, initLogger, parseConfig as parseConfig3, resolveRemnicConfigRecord as resolveRemnicConfigRecord2 } from "@remnic/core";
2075
+ import * as fs5 from "fs";
2076
+ import { EngramAccessService, Orchestrator as Orchestrator2, initLogger, parseConfig as parseConfig4, resolveRemnicConfigRecord as resolveRemnicConfigRecord3 } from "@remnic/core";
1587
2077
  import { WriteQuarantineStore } from "@remnic/core/write-quarantine.js";
1588
2078
  function valueFlag(args, flag) {
1589
2079
  const occurrences = args.filter((a) => a === flag).length;
@@ -1631,8 +2121,8 @@ async function runQuarantineReplay(rest, format, resolveConfigPath2) {
1631
2121
  let orchestrator;
1632
2122
  try {
1633
2123
  const configPath = resolveConfigPath2();
1634
- const raw = fs4.existsSync(configPath) ? JSON.parse(fs4.readFileSync(configPath, "utf8")) : {};
1635
- const config = parseConfig3(resolveRemnicConfigRecord2(raw));
2124
+ const raw = fs5.existsSync(configPath) ? JSON.parse(fs5.readFileSync(configPath, "utf8")) : {};
2125
+ const config = parseConfig4(resolveRemnicConfigRecord3(raw));
1636
2126
  orchestrator = new Orchestrator2(config);
1637
2127
  await orchestrator.initialize();
1638
2128
  await orchestrator.deferredReady;
@@ -1663,15 +2153,15 @@ async function runQuarantineReplay(rest, format, resolveConfigPath2) {
1663
2153
  }
1664
2154
 
1665
2155
  // src/offline-impression-rotation.ts
1666
- import fs5 from "fs";
1667
- import { parseConfig as parseConfig4, resolveRemnicConfigRecord as resolveRemnicConfigRecord3, drainPendingImpressionsForOfflineSync } from "@remnic/core";
2156
+ import fs6 from "fs";
2157
+ import { parseConfig as parseConfig5, resolveRemnicConfigRecord as resolveRemnicConfigRecord4, drainPendingImpressionsForOfflineSync } from "@remnic/core";
1668
2158
  import { LastRecallStore } from "@remnic/core/recall-state";
1669
2159
  function parseConfigQuietly(raw) {
1670
2160
  const originalWarn = console.warn;
1671
2161
  console.warn = () => {
1672
2162
  };
1673
2163
  try {
1674
- return parseConfig4(resolveRemnicConfigRecord3(raw));
2164
+ return parseConfig5(resolveRemnicConfigRecord4(raw));
1675
2165
  } finally {
1676
2166
  console.warn = originalWarn;
1677
2167
  }
@@ -1685,7 +2175,7 @@ var OFFLINE_CONFIG_KEYS = [
1685
2175
  function pickOfflineConfigRecord(raw) {
1686
2176
  let resolved;
1687
2177
  try {
1688
- resolved = resolveRemnicConfigRecord3(raw);
2178
+ resolved = resolveRemnicConfigRecord4(raw);
1689
2179
  } catch {
1690
2180
  resolved = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
1691
2181
  }
@@ -1698,7 +2188,7 @@ function pickOfflineConfigRecord(raw) {
1698
2188
  function resolveOfflineImpressionRotation(configPath) {
1699
2189
  let raw;
1700
2190
  try {
1701
- raw = fs5.existsSync(configPath) ? JSON.parse(fs5.readFileSync(configPath, "utf8")) : {};
2191
+ raw = fs6.existsSync(configPath) ? JSON.parse(fs6.readFileSync(configPath, "utf8")) : {};
1702
2192
  } catch {
1703
2193
  throw new Error(
1704
2194
  `cannot read recall-impression rotation from ${configPath}: config file could not be read as JSON`
@@ -1956,7 +2446,7 @@ function assertBenchModuleFreshForDevelopment() {
1956
2446
  }
1957
2447
 
1958
2448
  // src/daemon-service-candidates.ts
1959
- import fs6 from "fs";
2449
+ import fs7 from "fs";
1960
2450
  import path5 from "path";
1961
2451
  var LAUNCHD_LABEL = "ai.remnic.daemon";
1962
2452
  var LEGACY_REMNIC_SERVER_LAUNCHD_LABEL = "ai.remnic.server";
@@ -1978,7 +2468,7 @@ function systemdUnitPaths(homeDir) {
1978
2468
  function anyFileExists(paths) {
1979
2469
  return paths.some((candidate) => {
1980
2470
  try {
1981
- return fs6.statSync(candidate).isFile();
2471
+ return fs7.statSync(candidate).isFile();
1982
2472
  } catch {
1983
2473
  return false;
1984
2474
  }
@@ -1990,7 +2480,7 @@ function commandNames(command) {
1990
2480
  }
1991
2481
  function isRunnableNodeScript(filePath) {
1992
2482
  try {
1993
- const text = fs6.readFileSync(filePath, "utf8").slice(0, 4096);
2483
+ const text = fs7.readFileSync(filePath, "utf8").slice(0, 4096);
1994
2484
  const firstLine = text.split(/\r?\n/, 1)[0] ?? "";
1995
2485
  if (/^#!.*\bnode\b/.test(firstLine)) return true;
1996
2486
  if (firstLine.startsWith("#!")) return false;
@@ -2003,7 +2493,7 @@ function isRunnableNodeScript(filePath) {
2003
2493
  function resolveShimNodeScript(filePath) {
2004
2494
  let text;
2005
2495
  try {
2006
- text = fs6.readFileSync(filePath, "utf8").slice(0, 16384);
2496
+ text = fs7.readFileSync(filePath, "utf8").slice(0, 16384);
2007
2497
  } catch {
2008
2498
  return void 0;
2009
2499
  }
@@ -2015,8 +2505,8 @@ function resolveShimNodeScript(filePath) {
2015
2505
  const candidate = raw.replaceAll("${basedir}", basedir).replaceAll("$basedir", basedir).replaceAll("\\ ", " ");
2016
2506
  const resolved = path5.isAbsolute(candidate) ? candidate : path5.resolve(basedir, candidate);
2017
2507
  try {
2018
- if (fs6.statSync(resolved).isFile() && isRunnableNodeScript(resolved)) {
2019
- return fs6.realpathSync(resolved);
2508
+ if (fs7.statSync(resolved).isFile() && isRunnableNodeScript(resolved)) {
2509
+ return fs7.realpathSync(resolved);
2020
2510
  }
2021
2511
  } catch {
2022
2512
  }
@@ -2024,7 +2514,7 @@ function resolveShimNodeScript(filePath) {
2024
2514
  return void 0;
2025
2515
  }
2026
2516
  function resolveRunnableNodeScript(filePath) {
2027
- const realPath = fs6.realpathSync(filePath);
2517
+ const realPath = fs7.realpathSync(filePath);
2028
2518
  if (isRunnableNodeScript(realPath)) return realPath;
2029
2519
  return resolveShimNodeScript(realPath);
2030
2520
  }
@@ -2034,9 +2524,9 @@ function findCommandOnPath(command, pathEnv = process.env.PATH ?? "") {
2034
2524
  for (const name of commandNames(command)) {
2035
2525
  const candidate = path5.join(dir, name);
2036
2526
  try {
2037
- const stat = fs6.statSync(candidate);
2527
+ const stat = fs7.statSync(candidate);
2038
2528
  if (!stat.isFile()) continue;
2039
- if (process.platform !== "win32") fs6.accessSync(candidate, fs6.constants.X_OK);
2529
+ if (process.platform !== "win32") fs7.accessSync(candidate, fs7.constants.X_OK);
2040
2530
  const runnable = resolveRunnableNodeScript(candidate);
2041
2531
  if (runnable) return runnable;
2042
2532
  } catch {
@@ -2161,6 +2651,8 @@ var BENCH_VALUE_FLAGS = Object.freeze([
2161
2651
  "--memcorrect-adapter",
2162
2652
  "--run",
2163
2653
  "--memory-dir",
2654
+ "--qmd",
2655
+ "--collection",
2164
2656
  "--users",
2165
2657
  "--epochs",
2166
2658
  "--facts-per-epoch",
@@ -2342,7 +2834,7 @@ var BENCH_ACTION_FLAGS = {
2342
2834
  legacyEqualsPrefixes: ["--baseline=", "--report="]
2343
2835
  },
2344
2836
  attribute: {
2345
- value: ["--run", "--results-dir", "--memory-dir", "--threshold"],
2837
+ value: ["--run", "--results-dir", "--memory-dir", "--threshold", "--qmd", "--collection"],
2346
2838
  boolean: ["--json", "--help", "-h"]
2347
2839
  },
2348
2840
  "drift-gen": {
@@ -2475,6 +2967,14 @@ function parseBenchResearchArgs(action, args) {
2475
2967
  throw new Error("ERROR: bench attribute requires --run <id>.");
2476
2968
  }
2477
2969
  const memoryDirRaw = readBenchOptionValue(args, "--memory-dir");
2970
+ const qmdPathRaw = readBenchOptionValue(args, "--qmd");
2971
+ const collection = readBenchOptionValue(args, "--collection");
2972
+ if (action === "attribute" && Boolean(qmdPathRaw) !== Boolean(collection)) {
2973
+ throw new Error("ERROR: --qmd <path> and --collection <name> must be provided together.");
2974
+ }
2975
+ if (collection !== void 0 && collection.trim().length === 0) {
2976
+ throw new Error("ERROR: --collection requires a non-empty value.");
2977
+ }
2478
2978
  let seed;
2479
2979
  let out;
2480
2980
  if (action === "drift-gen") {
@@ -2503,6 +3003,8 @@ function parseBenchResearchArgs(action, args) {
2503
3003
  return {
2504
3004
  runRef,
2505
3005
  memoryDir: memoryDirRaw ? path6.resolve(expandTilde(memoryDirRaw)) : void 0,
3006
+ qmdPath: qmdPathRaw ? path6.resolve(expandTilde(qmdPathRaw)) : void 0,
3007
+ collection,
2506
3008
  users: readPositiveInteger(args, "--users"),
2507
3009
  epochs,
2508
3010
  seed,
@@ -3493,7 +3995,7 @@ function finalizeBenchStatus(filePath) {
3493
3995
  }
3494
3996
 
3495
3997
  // src/bench-fallback.ts
3496
- import fs7 from "fs";
3998
+ import fs8 from "fs";
3497
3999
  import path9 from "path";
3498
4000
  var FALLBACK_RESULTS_DIRNAME = "fallback-runs";
3499
4001
  function buildBenchRunnerArgs(parsed, benchmarkId, outputDir) {
@@ -3565,7 +4067,7 @@ function createFallbackBenchOutputDir(resultsDir, benchmarkId, pid, startedAtMs
3565
4067
  );
3566
4068
  }
3567
4069
  function resolveFallbackBenchResultPath(outputDir) {
3568
- const entries = fs7.readdirSync(outputDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name).sort();
4070
+ const entries = fs8.readdirSync(outputDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name).sort();
3569
4071
  if (entries.length === 0) {
3570
4072
  throw new Error(`Fallback benchmark runner did not write a JSON result artifact in ${outputDir}`);
3571
4073
  }
@@ -3573,7 +4075,7 @@ function resolveFallbackBenchResultPath(outputDir) {
3573
4075
  }
3574
4076
 
3575
4077
  // src/openclaw-upgrade-swap.ts
3576
- import fs8 from "fs";
4078
+ import fs9 from "fs";
3577
4079
  import path10 from "path";
3578
4080
  function describeError(error) {
3579
4081
  return error instanceof Error ? error.message : String(error);
@@ -3585,7 +4087,7 @@ function createSiblingTempFilePath(targetPath, label) {
3585
4087
  function resolveAtomicWriteMode(targetPath, explicitMode) {
3586
4088
  if (explicitMode !== void 0) return explicitMode;
3587
4089
  try {
3588
- return fs8.statSync(targetPath).mode & 4095;
4090
+ return fs9.statSync(targetPath).mode & 4095;
3589
4091
  } catch (error) {
3590
4092
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
3591
4093
  return 384;
@@ -3595,8 +4097,8 @@ function resolveAtomicWriteMode(targetPath, explicitMode) {
3595
4097
  }
3596
4098
  function resolveAtomicReplacementPath(targetPath) {
3597
4099
  try {
3598
- if (fs8.lstatSync(targetPath).isSymbolicLink()) {
3599
- return fs8.realpathSync(targetPath);
4100
+ if (fs9.lstatSync(targetPath).isSymbolicLink()) {
4101
+ return fs9.realpathSync(targetPath);
3600
4102
  }
3601
4103
  } catch (error) {
3602
4104
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
@@ -3613,7 +4115,7 @@ function createSiblingSwapPath(targetDir, label) {
3613
4115
  function cleanupDisplacedDirectoryBestEffort(displacedDir, context) {
3614
4116
  if (!displacedDir) return void 0;
3615
4117
  try {
3616
- fs8.rmSync(displacedDir, { recursive: true, force: true });
4118
+ fs9.rmSync(displacedDir, { recursive: true, force: true });
3617
4119
  return void 0;
3618
4120
  } catch (error) {
3619
4121
  return `Warning: ${context}, but failed to remove the displaced plugin copy at ${displacedDir}: ${describeError(error)}`;
@@ -3621,55 +4123,55 @@ function cleanupDisplacedDirectoryBestEffort(displacedDir, context) {
3621
4123
  }
3622
4124
  function atomicWriteFileSync(targetPath, data, options = {}) {
3623
4125
  const resolvedTargetPath = resolveAtomicReplacementPath(targetPath);
3624
- fs8.mkdirSync(path10.dirname(resolvedTargetPath), { recursive: true });
4126
+ fs9.mkdirSync(path10.dirname(resolvedTargetPath), { recursive: true });
3625
4127
  const tempPath = createSiblingTempFilePath(resolvedTargetPath, "write");
3626
4128
  const mode = resolveAtomicWriteMode(resolvedTargetPath, options.mode);
3627
4129
  try {
3628
4130
  if (options.hooks?.writeTempFileSync) {
3629
4131
  options.hooks.writeTempFileSync(tempPath);
3630
4132
  } else {
3631
- fs8.writeFileSync(tempPath, data, { mode });
4133
+ fs9.writeFileSync(tempPath, data, { mode });
3632
4134
  }
3633
- fs8.chmodSync(tempPath, mode);
3634
- const renameTempFileSync = options.hooks?.renameTempFileSync ?? fs8.renameSync;
4135
+ fs9.chmodSync(tempPath, mode);
4136
+ const renameTempFileSync = options.hooks?.renameTempFileSync ?? fs9.renameSync;
3635
4137
  renameTempFileSync(tempPath, resolvedTargetPath);
3636
4138
  } catch (error) {
3637
- fs8.rmSync(tempPath, { force: true });
4139
+ fs9.rmSync(tempPath, { force: true });
3638
4140
  throw error;
3639
4141
  }
3640
4142
  }
3641
4143
  function atomicCopyFileSync(sourcePath, targetPath, options = {}) {
3642
- if (!fs8.existsSync(sourcePath)) return;
4144
+ if (!fs9.existsSync(sourcePath)) return;
3643
4145
  const resolvedTargetPath = resolveAtomicReplacementPath(targetPath);
3644
- fs8.mkdirSync(path10.dirname(resolvedTargetPath), { recursive: true });
4146
+ fs9.mkdirSync(path10.dirname(resolvedTargetPath), { recursive: true });
3645
4147
  const tempPath = createSiblingTempFilePath(resolvedTargetPath, "copy");
3646
- const mode = fs8.statSync(sourcePath).mode & 4095;
4148
+ const mode = fs9.statSync(sourcePath).mode & 4095;
3647
4149
  try {
3648
- const copyTempFileSync = options.hooks?.copyTempFileSync ?? fs8.copyFileSync;
4150
+ const copyTempFileSync = options.hooks?.copyTempFileSync ?? fs9.copyFileSync;
3649
4151
  copyTempFileSync(sourcePath, tempPath);
3650
- fs8.chmodSync(tempPath, mode);
3651
- const renameTempFileSync = options.hooks?.renameTempFileSync ?? fs8.renameSync;
4152
+ fs9.chmodSync(tempPath, mode);
4153
+ const renameTempFileSync = options.hooks?.renameTempFileSync ?? fs9.renameSync;
3652
4154
  renameTempFileSync(tempPath, resolvedTargetPath);
3653
4155
  } catch (error) {
3654
- fs8.rmSync(tempPath, { force: true });
4156
+ fs9.rmSync(tempPath, { force: true });
3655
4157
  throw error;
3656
4158
  }
3657
4159
  }
3658
4160
  function swapDirectoryWithRollback(stagedDir, targetDir, rollbackDir) {
3659
4161
  let hasRollbackCopy = false;
3660
- fs8.mkdirSync(path10.dirname(targetDir), { recursive: true });
3661
- fs8.rmSync(rollbackDir, { recursive: true, force: true });
3662
- if (fs8.existsSync(targetDir)) {
3663
- fs8.renameSync(targetDir, rollbackDir);
4162
+ fs9.mkdirSync(path10.dirname(targetDir), { recursive: true });
4163
+ fs9.rmSync(rollbackDir, { recursive: true, force: true });
4164
+ if (fs9.existsSync(targetDir)) {
4165
+ fs9.renameSync(targetDir, rollbackDir);
3664
4166
  hasRollbackCopy = true;
3665
4167
  }
3666
4168
  try {
3667
- fs8.renameSync(stagedDir, targetDir);
4169
+ fs9.renameSync(stagedDir, targetDir);
3668
4170
  } catch (swapError) {
3669
- fs8.rmSync(targetDir, { recursive: true, force: true });
3670
- if (hasRollbackCopy && fs8.existsSync(rollbackDir)) {
4171
+ fs9.rmSync(targetDir, { recursive: true, force: true });
4172
+ if (hasRollbackCopy && fs9.existsSync(rollbackDir)) {
3671
4173
  try {
3672
- fs8.renameSync(rollbackDir, targetDir);
4174
+ fs9.renameSync(rollbackDir, targetDir);
3673
4175
  hasRollbackCopy = false;
3674
4176
  } catch (restoreError) {
3675
4177
  throw new AggregateError(
@@ -3684,7 +4186,7 @@ function swapDirectoryWithRollback(stagedDir, targetDir, rollbackDir) {
3684
4186
  }
3685
4187
  function cleanupRollbackDirectory(rollbackDir) {
3686
4188
  if (!rollbackDir) return;
3687
- fs8.rmSync(rollbackDir, { recursive: true, force: true });
4189
+ fs9.rmSync(rollbackDir, { recursive: true, force: true });
3688
4190
  }
3689
4191
  function cleanupRollbackDirectoryBestEffort(rollbackDir) {
3690
4192
  if (!rollbackDir) return void 0;
@@ -3696,20 +4198,20 @@ function cleanupRollbackDirectoryBestEffort(rollbackDir) {
3696
4198
  }
3697
4199
  }
3698
4200
  function restoreDirectoryFromRollback(targetDir, rollbackDir) {
3699
- if (!fs8.existsSync(rollbackDir)) {
4201
+ if (!fs9.existsSync(rollbackDir)) {
3700
4202
  throw new Error(`Rollback directory is missing: ${rollbackDir}`);
3701
4203
  }
3702
- fs8.mkdirSync(path10.dirname(targetDir), { recursive: true });
3703
- const displacedDir = fs8.existsSync(targetDir) ? createSiblingSwapPath(targetDir, "rollback-restore") : void 0;
4204
+ fs9.mkdirSync(path10.dirname(targetDir), { recursive: true });
4205
+ const displacedDir = fs9.existsSync(targetDir) ? createSiblingSwapPath(targetDir, "rollback-restore") : void 0;
3704
4206
  if (displacedDir) {
3705
- fs8.renameSync(targetDir, displacedDir);
4207
+ fs9.renameSync(targetDir, displacedDir);
3706
4208
  }
3707
4209
  try {
3708
- fs8.renameSync(rollbackDir, targetDir);
4210
+ fs9.renameSync(rollbackDir, targetDir);
3709
4211
  } catch (restoreError) {
3710
- if (displacedDir && fs8.existsSync(displacedDir)) {
4212
+ if (displacedDir && fs9.existsSync(displacedDir)) {
3711
4213
  try {
3712
- fs8.renameSync(displacedDir, targetDir);
4214
+ fs9.renameSync(displacedDir, targetDir);
3713
4215
  } catch (revertError) {
3714
4216
  throw new AggregateError(
3715
4217
  [restoreError, revertError],
@@ -3728,23 +4230,23 @@ function restoreDirectoryFromRollback(targetDir, rollbackDir) {
3728
4230
  );
3729
4231
  }
3730
4232
  function restoreDirectoryFromBackup(targetDir, backupDir) {
3731
- if (!fs8.existsSync(backupDir)) {
4233
+ if (!fs9.existsSync(backupDir)) {
3732
4234
  throw new Error(`Plugin backup directory is missing: ${backupDir}`);
3733
4235
  }
3734
- fs8.mkdirSync(path10.dirname(targetDir), { recursive: true });
4236
+ fs9.mkdirSync(path10.dirname(targetDir), { recursive: true });
3735
4237
  const stagedDir = createSiblingSwapPath(targetDir, "backup-restore");
3736
- const displacedDir = fs8.existsSync(targetDir) ? createSiblingSwapPath(targetDir, "pre-backup-restore") : void 0;
3737
- fs8.cpSync(backupDir, stagedDir, { recursive: true });
4238
+ const displacedDir = fs9.existsSync(targetDir) ? createSiblingSwapPath(targetDir, "pre-backup-restore") : void 0;
4239
+ fs9.cpSync(backupDir, stagedDir, { recursive: true });
3738
4240
  if (displacedDir) {
3739
- fs8.renameSync(targetDir, displacedDir);
4241
+ fs9.renameSync(targetDir, displacedDir);
3740
4242
  }
3741
4243
  try {
3742
- fs8.renameSync(stagedDir, targetDir);
4244
+ fs9.renameSync(stagedDir, targetDir);
3743
4245
  } catch (restoreError) {
3744
- fs8.rmSync(targetDir, { recursive: true, force: true });
3745
- if (displacedDir && fs8.existsSync(displacedDir)) {
4246
+ fs9.rmSync(targetDir, { recursive: true, force: true });
4247
+ if (displacedDir && fs9.existsSync(displacedDir)) {
3746
4248
  try {
3747
- fs8.renameSync(displacedDir, targetDir);
4249
+ fs9.renameSync(displacedDir, targetDir);
3748
4250
  } catch (revertError) {
3749
4251
  throw new AggregateError(
3750
4252
  [restoreError, revertError],
@@ -3752,7 +4254,7 @@ function restoreDirectoryFromBackup(targetDir, backupDir) {
3752
4254
  );
3753
4255
  }
3754
4256
  }
3755
- fs8.rmSync(stagedDir, { recursive: true, force: true });
4257
+ fs9.rmSync(stagedDir, { recursive: true, force: true });
3756
4258
  throw new Error(
3757
4259
  `Failed to restore the plugin backup into ${targetDir}. The durable backup remains preserved at ${backupDir}.`,
3758
4260
  { cause: restoreError }
@@ -3778,7 +4280,7 @@ function rollbackOpenclawUpgrade({
3778
4280
  let rollbackRestoreError;
3779
4281
  let pluginRestored = false;
3780
4282
  try {
3781
- if (rollbackDir && fs8.existsSync(rollbackDir)) {
4283
+ if (rollbackDir && fs9.existsSync(rollbackDir)) {
3782
4284
  const cleanupWarning = restoreDirectoryFromRollback(pluginDir, rollbackDir);
3783
4285
  notes.push(`Restored previous plugin from rollback copy at ${rollbackDir}`);
3784
4286
  if (cleanupWarning) notes.push(cleanupWarning);
@@ -3788,7 +4290,7 @@ function rollbackOpenclawUpgrade({
3788
4290
  rollbackRestoreError = error instanceof Error ? error.message : String(error);
3789
4291
  }
3790
4292
  try {
3791
- if (!pluginRestored && pluginBackupDir && fs8.existsSync(pluginBackupDir)) {
4293
+ if (!pluginRestored && pluginBackupDir && fs9.existsSync(pluginBackupDir)) {
3792
4294
  const cleanupWarning = restoreDirectoryFromBackup(pluginDir, pluginBackupDir);
3793
4295
  if (rollbackRestoreError) {
3794
4296
  notes.push(
@@ -3815,7 +4317,7 @@ function rollbackOpenclawUpgrade({
3815
4317
  notes.push("No previous plugin copy was available for automatic restore");
3816
4318
  }
3817
4319
  try {
3818
- if (configBackupPath && fs8.existsSync(configBackupPath)) {
4320
+ if (configBackupPath && fs9.existsSync(configBackupPath)) {
3819
4321
  restoreFileFromBackup(configPath, configBackupPath);
3820
4322
  notes.push(`Restored OpenClaw config from backup at ${configBackupPath}`);
3821
4323
  }
@@ -3855,7 +4357,7 @@ Run this manually when you're ready:
3855
4357
  }
3856
4358
 
3857
4359
  // src/daemon-service.ts
3858
- import fs9 from "fs";
4360
+ import fs10 from "fs";
3859
4361
  import path11 from "path";
3860
4362
  import * as childProcess from "child_process";
3861
4363
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -3867,7 +4369,7 @@ function launchdUnloadPlist(plistPath, processApi = childProcess) {
3867
4369
  processApi.execFileSync("launchctl", ["unload", plistPath], { stdio: "pipe" });
3868
4370
  }
3869
4371
  function resolveServerBinDetails(options = {}) {
3870
- const existsSync4 = options.existsSync ?? fs9.existsSync;
4372
+ const existsSync4 = options.existsSync ?? fs10.existsSync;
3871
4373
  const findCommandOnPath2 = options.findCommandOnPath ?? findCommandOnPath;
3872
4374
  const moduleDir = options.moduleDir ?? thisModuleDir;
3873
4375
  const packageResolve = options.packageResolve ?? resolveImportSpecifier;
@@ -3926,8 +4428,8 @@ function resolveServerBin(options = {}) {
3926
4428
  return resolveServerBinDetails(options).path;
3927
4429
  }
3928
4430
  function readVerifiedDaemonPid(options) {
3929
- const readFileSync4 = options.readFileSync ?? fs9.readFileSync;
3930
- const unlinkSync = options.unlinkSync ?? fs9.unlinkSync;
4431
+ const readFileSync4 = options.readFileSync ?? fs10.readFileSync;
4432
+ const unlinkSync = options.unlinkSync ?? fs10.unlinkSync;
3931
4433
  const processKill = options.processKill ?? process.kill;
3932
4434
  const platform = options.platform ?? process.platform;
3933
4435
  const execFileSync3 = options.execFileSync ?? ((command, args, execOptions) => childProcess.execFileSync(command, args, execOptions));
@@ -4027,8 +4529,8 @@ function removePidFileBestEffort(file, unlinkSync) {
4027
4529
  }
4028
4530
  }
4029
4531
  function inspectLaunchdPlist(plistPath, options = {}) {
4030
- const existsSync4 = options.existsSync ?? fs9.existsSync;
4031
- const readFileSync4 = options.readFileSync ?? fs9.readFileSync;
4532
+ const existsSync4 = options.existsSync ?? fs10.existsSync;
4533
+ const readFileSync4 = options.readFileSync ?? fs10.readFileSync;
4032
4534
  if (!existsSync4(plistPath)) {
4033
4535
  return {
4034
4536
  installed: false,
@@ -4262,7 +4764,7 @@ function stripConfigArgv(args) {
4262
4764
  }
4263
4765
 
4264
4766
  // src/import-dispatch.ts
4265
- import fs10 from "fs";
4767
+ import fs11 from "fs";
4266
4768
  import {
4267
4769
  runImporter,
4268
4770
  validateImportBatchSize,
@@ -4776,7 +5278,7 @@ async function cmdImport(rest, targetFactory, disposeTarget, ioOverrides = {}) {
4776
5278
  let materializedTarget;
4777
5279
  let materializePromise;
4778
5280
  const io = {
4779
- readFile: ioOverrides.readFile ?? (async (p) => fs10.promises.readFile(p, "utf-8")),
5281
+ readFile: ioOverrides.readFile ?? (async (p) => fs11.promises.readFile(p, "utf-8")),
4780
5282
  loadAdapter: ioOverrides.loadAdapter ?? (async (name) => (await loadImporterModule(name)).adapter),
4781
5283
  runImporter: ioOverrides.runImporter ?? runImporter,
4782
5284
  getWriteTarget: async () => {
@@ -4889,7 +5391,7 @@ async function cmdCapture(rest, io) {
4889
5391
  }
4890
5392
 
4891
5393
  // src/import-lossless-claw-cmd.ts
4892
- import fs11 from "fs";
5394
+ import fs12 from "fs";
4893
5395
  import path13 from "path";
4894
5396
  import {
4895
5397
  applyLcmSchema,
@@ -5001,15 +5503,15 @@ async function loadImportLosslessClawModule() {
5001
5503
 
5002
5504
  // src/import-lossless-claw-cmd.ts
5003
5505
  function assertDirectoryOrAbsent(p, label) {
5004
- if (fs11.existsSync(p) && !fs11.statSync(p).isDirectory()) {
5506
+ if (fs12.existsSync(p) && !fs12.statSync(p).isDirectory()) {
5005
5507
  throw new Error(`${label} is not a directory: ${p}`);
5006
5508
  }
5007
5509
  }
5008
5510
  function assertFile(p, label) {
5009
- if (!fs11.existsSync(p)) {
5511
+ if (!fs12.existsSync(p)) {
5010
5512
  throw new Error(`${label} does not exist: ${p}`);
5011
5513
  }
5012
- if (!fs11.statSync(p).isFile()) {
5514
+ if (!fs12.statSync(p).isFile()) {
5013
5515
  throw new Error(`${label} is not a file: ${p}`);
5014
5516
  }
5015
5517
  }
@@ -5041,7 +5543,7 @@ async function cmdImportLosslessClaw(argv, io, deps = {}) {
5041
5543
  try {
5042
5544
  if (parsed.dryRun) {
5043
5545
  const lcmPath = path13.join(memoryDir, "state", "lcm.sqlite");
5044
- if (fs11.existsSync(lcmPath)) {
5546
+ if (fs12.existsSync(lcmPath)) {
5045
5547
  destDb = mod.openExistingLcmDatabaseReadOnly(lcmPath);
5046
5548
  } else {
5047
5549
  destDb = mod.openInMemoryDestinationDatabase();
@@ -5179,6 +5681,8 @@ async function runBenchResearchCommand(parsed) {
5179
5681
  runRef: parsed.runRef,
5180
5682
  resultsDir: parsed.resultsDir ?? path14.join(resolveHomeDir(), ".remnic", "bench", "results"),
5181
5683
  memoryDir: parsed.memoryDir,
5684
+ qmdPath: parsed.qmdPath,
5685
+ collection: parsed.collection,
5182
5686
  threshold: parsed.threshold,
5183
5687
  json: parsed.json
5184
5688
  })
@@ -5242,7 +5746,9 @@ Commands:
5242
5746
  subsequent local artifacts carry the kappa + warning.
5243
5747
  check Legacy latency regression gate (compatibility)
5244
5748
  attribute --run <id> [--results-dir <path>] [--memory-dir <path>] [--threshold <value>]
5245
- Attribute operation-level benchmark failures to memory operations
5749
+ [--qmd <path> --collection <name>]
5750
+ Attribute failures from stored witnesses by default; paired QMD flags enable
5751
+ explicit live fallback for legacy runs without witnesses
5246
5752
  drift-gen [generate|validate <dir>] [--users <n>] [--epochs <n>] [--seed <n>]
5247
5753
  [--out <dir>] [--facts-per-epoch <n>] [--drifting-ratio <r>]
5248
5754
  [--contradicted-ratio <r>]
@@ -5349,6 +5855,8 @@ Options:
5349
5855
  --json Output JSON for \`list\`
5350
5856
  --run <id> Benchmark run reference for attribute
5351
5857
  --memory-dir <path> Memory directory for failure attribution
5858
+ --qmd <path> QMD executable for explicit legacy attribution fallback
5859
+ --collection <name> QMD collection paired with --qmd; never inferred or defaulted
5352
5860
  --users <n> Synthetic user count for drift-gen
5353
5861
  --epochs <n> Synthetic timeline epochs for drift-gen
5354
5862
  --facts-per-epoch <n> Facts generated per user per epoch for drift-gen
@@ -5384,6 +5892,7 @@ Examples:
5384
5892
  remnic bench run --custom ./my-bench.yaml
5385
5893
  remnic bench procedural-ablation --out ./artifacts/procedural-ablation.json
5386
5894
  remnic bench attribute --run run-12345 --memory-dir ./memories
5895
+ remnic bench attribute --run legacy-run --memory-dir ./memories --qmd /opt/qmd --collection memories
5387
5896
  remnic bench drift-gen generate --users 20 --epochs 10 --out ./corpus
5388
5897
  remnic bench drift-gen validate ./corpus
5389
5898
  remnic benchmark run --quick longmemeval`;
@@ -5605,7 +6114,7 @@ async function resolveAllBenchmarks() {
5605
6114
  if (packageBenchmarks) {
5606
6115
  return packageBenchmarks.filter((entry) => entry.runnerAvailable).map((entry) => entry.id);
5607
6116
  }
5608
- if (!fs12.existsSync(EVAL_RUNNER_PATH)) {
6117
+ if (!fs13.existsSync(EVAL_RUNNER_PATH)) {
5609
6118
  return [];
5610
6119
  }
5611
6120
  return BENCHMARK_CATALOG.filter((entry) => entry.category !== "ingestion").map((entry) => entry.id);
@@ -5653,7 +6162,7 @@ async function runBenchViaFallback(parsed, benchmarkId, runtimeProfile) {
5653
6162
  `Fallback benchmark runner does not support provider-backed, gateway, or thinking/timeout flags (${unsupportedOptions.join(", ")}). Build/install @remnic/bench to use those options.`
5654
6163
  );
5655
6164
  }
5656
- if (!fs12.existsSync(EVAL_RUNNER_PATH)) {
6165
+ if (!fs13.existsSync(EVAL_RUNNER_PATH)) {
5657
6166
  console.error(
5658
6167
  "Benchmark runner not found. Expected eval runner at evals/run.ts or a phase-1 @remnic/bench runtime export."
5659
6168
  );
@@ -5663,7 +6172,7 @@ async function runBenchViaFallback(parsed, benchmarkId, runtimeProfile) {
5663
6172
  path15.join(CLI_REPO_ROOT, "node_modules", ".bin", "tsx"),
5664
6173
  path15.join(CLI_REPO_ROOT, "packages", "remnic-cli", "node_modules", ".bin", "tsx")
5665
6174
  ];
5666
- const tsxCmd = tsxCandidates.find((candidate) => fs12.existsSync(candidate)) ?? "tsx";
6175
+ const tsxCmd = tsxCandidates.find((candidate) => fs13.existsSync(candidate)) ?? "tsx";
5667
6176
  const fallbackOutputDir = createFallbackBenchOutputDir(
5668
6177
  parsed.resultsDir ?? resolveBenchOutputDir(),
5669
6178
  benchmarkId,
@@ -5808,9 +6317,9 @@ var PERSONAMEM_COMPLETION_MARKER = path15.join(
5808
6317
  );
5809
6318
  function resolveRealpathWithinDataset(datasetPath, relativePath) {
5810
6319
  try {
5811
- const datasetRoot = fs12.realpathSync(datasetPath);
6320
+ const datasetRoot = fs13.realpathSync(datasetPath);
5812
6321
  const candidatePath = path15.resolve(datasetRoot, relativePath);
5813
- const candidateRealPath = fs12.realpathSync(candidatePath);
6322
+ const candidateRealPath = fs13.realpathSync(candidatePath);
5814
6323
  const relativeToRoot = path15.relative(datasetRoot, candidateRealPath);
5815
6324
  if (relativeToRoot.startsWith("..") || path15.isAbsolute(relativeToRoot)) {
5816
6325
  return null;
@@ -5869,14 +6378,14 @@ function parseCsvRows(raw) {
5869
6378
  function isPersonaMemDatasetComplete(datasetPath) {
5870
6379
  try {
5871
6380
  const completionMarkerPath = path15.join(datasetPath, PERSONAMEM_COMPLETION_MARKER);
5872
- if (fs12.statSync(completionMarkerPath).isFile()) {
6381
+ if (fs13.statSync(completionMarkerPath).isFile()) {
5873
6382
  return true;
5874
6383
  }
5875
6384
  } catch {
5876
6385
  }
5877
6386
  const datasetFile = PERSONAMEM_DATASET_FILE_CANDIDATES.find((candidate) => {
5878
6387
  try {
5879
- return fs12.statSync(path15.join(datasetPath, candidate)).isFile();
6388
+ return fs13.statSync(path15.join(datasetPath, candidate)).isFile();
5880
6389
  } catch {
5881
6390
  return false;
5882
6391
  }
@@ -5885,7 +6394,7 @@ function isPersonaMemDatasetComplete(datasetPath) {
5885
6394
  return false;
5886
6395
  }
5887
6396
  try {
5888
- const rows = parseCsvRows(fs12.readFileSync(path15.join(datasetPath, datasetFile), "utf8"));
6397
+ const rows = parseCsvRows(fs13.readFileSync(path15.join(datasetPath, datasetFile), "utf8"));
5889
6398
  if (rows.length < 2) {
5890
6399
  return false;
5891
6400
  }
@@ -5900,7 +6409,7 @@ function isPersonaMemDatasetComplete(datasetPath) {
5900
6409
  }
5901
6410
  return historyPaths.every((relativePath) => {
5902
6411
  const resolvedPath = resolveRealpathWithinDataset(datasetPath, relativePath);
5903
- return resolvedPath !== null && fs12.statSync(resolvedPath).isFile();
6412
+ return resolvedPath !== null && fs13.statSync(resolvedPath).isFile();
5904
6413
  });
5905
6414
  } catch {
5906
6415
  return false;
@@ -5908,7 +6417,7 @@ function isPersonaMemDatasetComplete(datasetPath) {
5908
6417
  }
5909
6418
  function hasDatasetFile(datasetPath, relativePath) {
5910
6419
  try {
5911
- return fs12.statSync(path15.join(datasetPath, relativePath)).isFile();
6420
+ return fs13.statSync(path15.join(datasetPath, relativePath)).isFile();
5912
6421
  } catch {
5913
6422
  return false;
5914
6423
  }
@@ -5928,10 +6437,10 @@ function memoryAgentBenchDatasetHasRecSysSamples(datasetPath) {
5928
6437
  return candidateFilenames.some((filename) => {
5929
6438
  const filePath = path15.join(datasetPath, filename);
5930
6439
  try {
5931
- if (!fs12.statSync(filePath).isFile()) {
6440
+ if (!fs13.statSync(filePath).isFile()) {
5932
6441
  return false;
5933
6442
  }
5934
- const raw = fs12.readFileSync(filePath, "utf8");
6443
+ const raw = fs13.readFileSync(filePath, "utf8");
5935
6444
  return /"source"\s*:\s*"recsys[_-]/i.test(raw);
5936
6445
  } catch {
5937
6446
  return false;
@@ -5947,7 +6456,7 @@ function isMemoryAgentBenchDatasetComplete(datasetPath) {
5947
6456
  function isDatasetDownloaded(datasetPath, benchmarkId) {
5948
6457
  let stats;
5949
6458
  try {
5950
- stats = fs12.statSync(datasetPath);
6459
+ stats = fs13.statSync(datasetPath);
5951
6460
  } catch {
5952
6461
  return false;
5953
6462
  }
@@ -5957,7 +6466,7 @@ function isDatasetDownloaded(datasetPath, benchmarkId) {
5957
6466
  const marker = DOWNLOADED_DATASET_MARKERS[benchmarkId];
5958
6467
  if (!marker) {
5959
6468
  try {
5960
- return fs12.readdirSync(datasetPath).length > 0;
6469
+ return fs13.readdirSync(datasetPath).length > 0;
5961
6470
  } catch {
5962
6471
  return false;
5963
6472
  }
@@ -5965,7 +6474,7 @@ function isDatasetDownloaded(datasetPath, benchmarkId) {
5965
6474
  if (marker.allOf) {
5966
6475
  const hasAllRequiredFiles = marker.allOf.every((name) => {
5967
6476
  try {
5968
- return fs12.statSync(path15.join(datasetPath, name)).isFile();
6477
+ return fs13.statSync(path15.join(datasetPath, name)).isFile();
5969
6478
  } catch {
5970
6479
  return false;
5971
6480
  }
@@ -5977,7 +6486,7 @@ function isDatasetDownloaded(datasetPath, benchmarkId) {
5977
6486
  if (marker.anyOf) {
5978
6487
  const hasMarkerFile = marker.anyOf.some((name) => {
5979
6488
  try {
5980
- return fs12.statSync(path15.join(datasetPath, name)).isFile();
6489
+ return fs13.statSync(path15.join(datasetPath, name)).isFile();
5981
6490
  } catch {
5982
6491
  return false;
5983
6492
  }
@@ -5995,7 +6504,7 @@ function isDatasetDownloaded(datasetPath, benchmarkId) {
5995
6504
  }
5996
6505
  if (marker.ext) {
5997
6506
  try {
5998
- return fs12.readdirSync(datasetPath).some(
6507
+ return fs13.readdirSync(datasetPath).some(
5999
6508
  (name) => name.endsWith(marker.ext) && !marker.exclude?.includes(name)
6000
6509
  );
6001
6510
  } catch {
@@ -6007,7 +6516,7 @@ function isDatasetDownloaded(datasetPath, benchmarkId) {
6007
6516
  async function launchBenchUi(resultsDir) {
6008
6517
  const benchUiDir = path15.join(CLI_REPO_ROOT, "packages", "bench-ui");
6009
6518
  const pnpmCmd = process.platform === "win32" ? "pnpm.cmd" : "pnpm";
6010
- if (!fs12.existsSync(path15.join(benchUiDir, "package.json"))) {
6519
+ if (!fs13.existsSync(path15.join(benchUiDir, "package.json"))) {
6011
6520
  console.error("ERROR: @remnic/bench-ui is not available in this checkout.");
6012
6521
  process.exit(1);
6013
6522
  }
@@ -6022,11 +6531,11 @@ async function launchBenchUi(resultsDir) {
6022
6531
  REMNIC_BENCH_RESULTS_DIR: resultsDir
6023
6532
  }
6024
6533
  });
6025
- await new Promise((resolve, reject) => {
6534
+ await new Promise((resolve2, reject) => {
6026
6535
  child.on("error", reject);
6027
6536
  child.on("close", (code, signal) => {
6028
6537
  if (code === 0 || signal === "SIGINT" || signal === "SIGTERM") {
6029
- resolve();
6538
+ resolve2();
6030
6539
  return;
6031
6540
  }
6032
6541
  reject(new Error(`bench UI exited with code ${code ?? "unknown"}`));
@@ -6045,13 +6554,13 @@ function listDownloadableBenchmarks() {
6045
6554
  }
6046
6555
  function resolveDatasetDownloadScriptPath() {
6047
6556
  const bundled = path15.join(CLI_MODULE_DIR, "assets", "download-datasets.sh");
6048
- if (fs12.existsSync(bundled)) {
6557
+ if (fs13.existsSync(bundled)) {
6049
6558
  return bundled;
6050
6559
  }
6051
6560
  return path15.join(CLI_REPO_ROOT, "evals", "scripts", "download-datasets.sh");
6052
6561
  }
6053
6562
  function isRepoCheckout() {
6054
- return fs12.existsSync(path15.join(CLI_REPO_ROOT, "pnpm-workspace.yaml")) && fs12.existsSync(path15.join(CLI_REPO_ROOT, "evals", "scripts", "download-datasets.sh"));
6563
+ return fs13.existsSync(path15.join(CLI_REPO_ROOT, "pnpm-workspace.yaml")) && fs13.existsSync(path15.join(CLI_REPO_ROOT, "evals", "scripts", "download-datasets.sh"));
6055
6564
  }
6056
6565
  function runDatasetDownloadScript(scriptPath, benchmarkId, datasetRoot, jsonMode) {
6057
6566
  const stdio = jsonMode ? ["inherit", process.stderr, "inherit"] : "inherit";
@@ -6364,8 +6873,8 @@ async function exportBenchPackageResult(parsed) {
6364
6873
  ...reportCardProvenance ? { reportCardProvenance } : {}
6365
6874
  });
6366
6875
  if (parsed.output) {
6367
- fs12.mkdirSync(path15.dirname(parsed.output), { recursive: true });
6368
- fs12.writeFileSync(parsed.output, rendered);
6876
+ fs13.mkdirSync(path15.dirname(parsed.output), { recursive: true });
6877
+ fs13.writeFileSync(parsed.output, rendered);
6369
6878
  console.log(`Exported ${summary.id} as ${parsed.format} to ${parsed.output}`);
6370
6879
  return;
6371
6880
  }
@@ -6410,7 +6919,7 @@ async function manageBenchDatasets(parsed) {
6410
6919
  process.exit(1);
6411
6920
  }
6412
6921
  const scriptPath = resolveDatasetDownloadScriptPath();
6413
- if (!fs12.existsSync(scriptPath)) {
6922
+ if (!fs13.existsSync(scriptPath)) {
6414
6923
  console.error(`ERROR: dataset download script not found: ${scriptPath}`);
6415
6924
  process.exit(1);
6416
6925
  }
@@ -6610,7 +7119,7 @@ async function calibrateBenchJudges(parsed, rawArgs) {
6610
7119
  );
6611
7120
  process.exit(1);
6612
7121
  }
6613
- const sourceResultSha256 = createHash3("sha256").update(fs12.readFileSync(latest.path)).digest("hex");
7122
+ const sourceResultSha256 = createHash4("sha256").update(fs13.readFileSync(latest.path)).digest("hex");
6614
7123
  const expandedManifestPath = expandTilde(manifestPath);
6615
7124
  if (!bench.resolveLocalLabJudgeProviderConfig) {
6616
7125
  console.error(
@@ -7025,7 +7534,7 @@ function loadPinnedLoCoMoTaskSelector(parsed) {
7025
7534
  }
7026
7535
  let decoded;
7027
7536
  try {
7028
- decoded = JSON.parse(fs12.readFileSync(parsed.taskIdsFile, "utf8"));
7537
+ decoded = JSON.parse(fs13.readFileSync(parsed.taskIdsFile, "utf8"));
7029
7538
  } catch (error) {
7030
7539
  throw new Error(
7031
7540
  `Unable to read --task-ids-file ${parsed.taskIdsFile}: ${error instanceof Error ? error.message : String(error)}`
@@ -7474,7 +7983,7 @@ function attachPreparedJudgeCalibration(result, judgeCalibration) {
7474
7983
  function hashCalibrationProviderConfig(config) {
7475
7984
  const canonicalize = (value, key = "") => {
7476
7985
  if (typeof value === "string" && /(?:api.?key|authorization|token|secret)/i.test(key)) {
7477
- return { secretSha256: createHash3("sha256").update(value).digest("hex") };
7986
+ return { secretSha256: createHash4("sha256").update(value).digest("hex") };
7478
7987
  }
7479
7988
  if (Array.isArray(value)) return value.map((item) => canonicalize(item));
7480
7989
  if (value && typeof value === "object") {
@@ -7485,7 +7994,7 @@ function hashCalibrationProviderConfig(config) {
7485
7994
  }
7486
7995
  return value;
7487
7996
  };
7488
- return createHash3("sha256").update(JSON.stringify(canonicalize(config))).digest("hex");
7997
+ return createHash4("sha256").update(JSON.stringify(canonicalize(config))).digest("hex");
7489
7998
  }
7490
7999
  function restoreOptionalEnv(key, previousValue) {
7491
8000
  if (previousValue === void 0) {
@@ -7697,7 +8206,7 @@ function resolveBenchReproDatasetDir(datasetDir) {
7697
8206
  return void 0;
7698
8207
  }
7699
8208
  try {
7700
- return fs12.realpathSync(datasetDir);
8209
+ return fs13.realpathSync(datasetDir);
7701
8210
  } catch {
7702
8211
  return datasetDir;
7703
8212
  }
@@ -7751,13 +8260,13 @@ async function writeBenchReproManifestForPackageRun(args) {
7751
8260
  }
7752
8261
  function loadStandaloneConvergeCommandConfig() {
7753
8262
  const configPath = resolveConfigPath();
7754
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
7755
- return parseConfig5(resolveRemnicConfigRecord4(raw));
8263
+ const raw = fs13.existsSync(configPath) ? JSON.parse(fs13.readFileSync(configPath, "utf8")) : {};
8264
+ return parseConfig6(resolveRemnicConfigRecord5(raw));
7756
8265
  }
7757
8266
  function parseConvergePluginConfig(value) {
7758
8267
  if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
7759
8268
  if (Object.keys(value).length === 0) return void 0;
7760
- return parseConfig5(resolveRemnicConfigRecord4(value));
8269
+ return parseConfig6(resolveRemnicConfigRecord5(value));
7761
8270
  }
7762
8271
  function loadConvergeCommandConfig() {
7763
8272
  if (readCompatEnv("REMNIC_CONFIG_PATH", "ENGRAM_CONFIG_PATH")) {
@@ -7780,13 +8289,13 @@ function resolveConfigPath(cliPath) {
7780
8289
  path15.join(resolveHomeDir(), ".config", "engram", "config.json")
7781
8290
  ];
7782
8291
  for (const candidate of candidates) {
7783
- if (fs12.existsSync(candidate)) return candidate;
8292
+ if (fs13.existsSync(candidate)) return candidate;
7784
8293
  }
7785
8294
  return path15.join(resolveHomeDir(), ".config", "remnic", "config.json");
7786
8295
  }
7787
8296
  function resolveExistingBenchRemnicConfigPath(cliPath) {
7788
8297
  const configPath = resolveConfigPath(cliPath);
7789
- if (fs12.existsSync(configPath)) {
8298
+ if (fs13.existsSync(configPath)) {
7790
8299
  return configPath;
7791
8300
  }
7792
8301
  if (cliPath) {
@@ -7796,7 +8305,7 @@ function resolveExistingBenchRemnicConfigPath(cliPath) {
7796
8305
  }
7797
8306
  function resolveExistingBenchOpenclawConfigPath(cliPath) {
7798
8307
  const configPath = resolveOpenclawConfigPath(cliPath);
7799
- if (fs12.existsSync(configPath)) {
8308
+ if (fs13.existsSync(configPath)) {
7800
8309
  return configPath;
7801
8310
  }
7802
8311
  if (cliPath) {
@@ -7903,8 +8412,8 @@ function resolveMemoryDir() {
7903
8412
  const envMemoryDir = readCompatEnv("REMNIC_MEMORY_DIR", "ENGRAM_MEMORY_DIR");
7904
8413
  if (envMemoryDir) return normalizeMemoryDirPath(envMemoryDir);
7905
8414
  const configPath = resolveConfigPath();
7906
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
7907
- const remnicCfg = resolveRemnicConfigRecord4(raw);
8415
+ const raw = fs13.existsSync(configPath) ? JSON.parse(fs13.readFileSync(configPath, "utf8")) : {};
8416
+ const remnicCfg = resolveRemnicConfigRecord5(raw);
7908
8417
  if (typeof remnicCfg.memoryDir === "string" && remnicCfg.memoryDir.length > 0) {
7909
8418
  return normalizeMemoryDirPath(remnicCfg.memoryDir);
7910
8419
  }
@@ -7912,18 +8421,18 @@ function resolveMemoryDir() {
7912
8421
  const standalonePath = path15.join(home, ".remnic", "memory");
7913
8422
  const legacyStandalonePath = path15.join(home, ".engram", "memory");
7914
8423
  const openclawPath = path15.join(home, ".openclaw", "workspace", "memory", "local");
7915
- if (fs12.existsSync(standalonePath)) return standalonePath;
7916
- if (fs12.existsSync(legacyStandalonePath)) return legacyStandalonePath;
8424
+ if (fs13.existsSync(standalonePath)) return standalonePath;
8425
+ if (fs13.existsSync(legacyStandalonePath)) return legacyStandalonePath;
7917
8426
  return openclawPath;
7918
8427
  })();
7919
8428
  const manifestPath = getManifestPath();
7920
- if (fs12.existsSync(manifestPath)) {
8429
+ if (fs13.existsSync(manifestPath)) {
7921
8430
  try {
7922
8431
  const active = getActiveSpace();
7923
8432
  if (active?.memoryDir) {
7924
8433
  const activeMemoryDir = normalizeMemoryDirPath(active.memoryDir);
7925
- if (!fs12.existsSync(activeMemoryDir)) {
7926
- fs12.mkdirSync(activeMemoryDir, { recursive: true });
8434
+ if (!fs13.existsSync(activeMemoryDir)) {
8435
+ fs13.mkdirSync(activeMemoryDir, { recursive: true });
7927
8436
  }
7928
8437
  return activeMemoryDir;
7929
8438
  }
@@ -7969,13 +8478,13 @@ function resolveOpenclawConfigPath(cliPath) {
7969
8478
  const envPath = process.env.OPENCLAW_CONFIG_PATH || process.env.OPENCLAW_ENGRAM_CONFIG_PATH;
7970
8479
  if (envPath) return path15.resolve(expandTilde(envPath));
7971
8480
  for (const candidate of DEFAULT_OPENCLAW_CONFIG_PATHS_FOR_DOCTOR) {
7972
- if (fs12.existsSync(candidate)) return candidate;
8481
+ if (fs13.existsSync(candidate)) return candidate;
7973
8482
  }
7974
8483
  return path15.join(resolveHomeDir(), ".openclaw", "openclaw.json");
7975
8484
  }
7976
8485
  function readOpenclawConfig(configPath) {
7977
- if (!fs12.existsSync(configPath)) return {};
7978
- const raw = fs12.readFileSync(configPath, "utf-8");
8486
+ if (!fs13.existsSync(configPath)) return {};
8487
+ const raw = fs13.readFileSync(configPath, "utf-8");
7979
8488
  let parsed;
7980
8489
  try {
7981
8490
  parsed = JSON.parse(raw);
@@ -8074,14 +8583,14 @@ function formatOpenclawUpgradeStamp(now = /* @__PURE__ */ new Date()) {
8074
8583
  return `${yyyy}${mm}${dd}-${hh}${min}${ss}`;
8075
8584
  }
8076
8585
  function backupPathIfPresent(sourcePath, backupPath) {
8077
- if (!fs12.existsSync(sourcePath)) return false;
8078
- fs12.mkdirSync(path15.dirname(backupPath), { recursive: true });
8079
- fs12.cpSync(sourcePath, backupPath, { recursive: true });
8586
+ if (!fs13.existsSync(sourcePath)) return false;
8587
+ fs13.mkdirSync(path15.dirname(backupPath), { recursive: true });
8588
+ fs13.cpSync(sourcePath, backupPath, { recursive: true });
8080
8589
  return true;
8081
8590
  }
8082
8591
  function assertDirectoryPathOrMissing(targetPath, label) {
8083
- if (!fs12.existsSync(targetPath)) return;
8084
- const stat = fs12.statSync(targetPath);
8592
+ if (!fs13.existsSync(targetPath)) return;
8593
+ const stat = fs13.statSync(targetPath);
8085
8594
  if (!stat.isDirectory()) {
8086
8595
  throw new Error(`${label} must be a directory when it already exists: ${targetPath}`);
8087
8596
  }
@@ -8106,7 +8615,7 @@ var PublishedOpenclawPluginInstallError = class extends Error {
8106
8615
  }
8107
8616
  };
8108
8617
  function installPublishedOpenclawPlugin(spec, pluginDir) {
8109
- const tempRoot = fs12.mkdtempSync(path15.join(os.tmpdir(), "remnic-openclaw-upgrade-"));
8618
+ const tempRoot = fs13.mkdtempSync(path15.join(os.tmpdir(), "remnic-openclaw-upgrade-"));
8110
8619
  const stagedDir = `${pluginDir}.next-${process.pid}-${Date.now()}`;
8111
8620
  const rollbackDir = `${pluginDir}.rollback-${process.pid}-${Date.now()}`;
8112
8621
  let swapRollbackDir;
@@ -8122,16 +8631,16 @@ function installPublishedOpenclawPlugin(spec, pluginDir) {
8122
8631
  throw new Error(`npm pack ${spec} did not return a tarball name`);
8123
8632
  }
8124
8633
  const unpackDir = path15.join(tempRoot, "unpacked");
8125
- fs12.mkdirSync(unpackDir, { recursive: true });
8634
+ fs13.mkdirSync(unpackDir, { recursive: true });
8126
8635
  childProcess2.execFileSync("tar", ["-xzf", path15.join(tempRoot, tarballName), "-C", unpackDir], {
8127
8636
  stdio: ["ignore", "pipe", "pipe"]
8128
8637
  });
8129
8638
  const packagedDir = path15.join(unpackDir, "package");
8130
- if (!fs12.existsSync(packagedDir)) {
8639
+ if (!fs13.existsSync(packagedDir)) {
8131
8640
  throw new Error(`npm pack ${spec} did not contain a package/ directory`);
8132
8641
  }
8133
- fs12.rmSync(stagedDir, { recursive: true, force: true });
8134
- fs12.cpSync(packagedDir, stagedDir, { recursive: true });
8642
+ fs13.rmSync(stagedDir, { recursive: true, force: true });
8643
+ fs13.cpSync(packagedDir, stagedDir, { recursive: true });
8135
8644
  childProcess2.execFileSync("npm", ["install", "--omit=dev"], {
8136
8645
  cwd: stagedDir,
8137
8646
  stdio: ["ignore", "pipe", "pipe"]
@@ -8147,7 +8656,7 @@ function installPublishedOpenclawPlugin(spec, pluginDir) {
8147
8656
  })();
8148
8657
  swapRollbackDir = swapResult.rollbackDir;
8149
8658
  const installedPackageJsonPath = path15.join(pluginDir, "package.json");
8150
- const installedPackage = fs12.existsSync(installedPackageJsonPath) ? JSON.parse(fs12.readFileSync(installedPackageJsonPath, "utf8")) : {};
8659
+ const installedPackage = fs13.existsSync(installedPackageJsonPath) ? JSON.parse(fs13.readFileSync(installedPackageJsonPath, "utf8")) : {};
8151
8660
  return {
8152
8661
  rollbackDir: swapRollbackDir,
8153
8662
  version: typeof installedPackage.version === "string" ? installedPackage.version : void 0
@@ -8162,8 +8671,8 @@ function installPublishedOpenclawPlugin(spec, pluginDir) {
8162
8671
  }
8163
8672
  );
8164
8673
  } finally {
8165
- fs12.rmSync(stagedDir, { recursive: true, force: true });
8166
- fs12.rmSync(tempRoot, { recursive: true, force: true });
8674
+ fs13.rmSync(stagedDir, { recursive: true, force: true });
8675
+ fs13.rmSync(tempRoot, { recursive: true, force: true });
8167
8676
  }
8168
8677
  }
8169
8678
  function restartOpenclawGateway() {
@@ -8182,7 +8691,7 @@ function restartOpenclawGateway() {
8182
8691
  }
8183
8692
  function cmdInit() {
8184
8693
  const configPath = path15.join(process.cwd(), "remnic.config.json");
8185
- if (fs12.existsSync(configPath)) {
8694
+ if (fs13.existsSync(configPath)) {
8186
8695
  console.log(`Config already exists: ${configPath}`);
8187
8696
  return;
8188
8697
  }
@@ -8198,7 +8707,7 @@ function cmdInit() {
8198
8707
  authToken: "${REMNIC_AUTH_TOKEN}"
8199
8708
  }
8200
8709
  };
8201
- fs12.writeFileSync(configPath, JSON.stringify(template, null, 2) + "\n");
8710
+ fs13.writeFileSync(configPath, JSON.stringify(template, null, 2) + "\n");
8202
8711
  console.log(`Created ${configPath}`);
8203
8712
  console.log("\nSet these environment variables:");
8204
8713
  console.log(" export OPENAI_API_KEY=sk-...");
@@ -8268,7 +8777,7 @@ async function cmdStatus(json) {
8268
8777
  }
8269
8778
  function oauthReadConfigRecord(configPath) {
8270
8779
  try {
8271
- const parsed = JSON.parse(fs12.readFileSync(configPath, "utf8"));
8780
+ const parsed = JSON.parse(fs13.readFileSync(configPath, "utf8"));
8272
8781
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
8273
8782
  return parsed;
8274
8783
  }
@@ -8413,7 +8922,7 @@ async function oauthPromptYesNo(question) {
8413
8922
  return false;
8414
8923
  }
8415
8924
  process.stdout.write(`${question} [y/N] `);
8416
- return new Promise((resolve) => {
8925
+ return new Promise((resolve2) => {
8417
8926
  let buffer = "";
8418
8927
  const onData = (chunk) => {
8419
8928
  const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
@@ -8422,7 +8931,7 @@ async function oauthPromptYesNo(question) {
8422
8931
  process.stdin.removeListener("data", onData);
8423
8932
  process.stdin.pause();
8424
8933
  const answer = buffer.trim().toLowerCase();
8425
- resolve(answer === "y" || answer === "yes");
8934
+ resolve2(answer === "y" || answer === "yes");
8426
8935
  }
8427
8936
  };
8428
8937
  process.stdin.resume();
@@ -8684,9 +9193,9 @@ async function cmdQuery(queryText, json, explain) {
8684
9193
  }
8685
9194
  initLogger2();
8686
9195
  const configPath = resolveConfigPath();
8687
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
8688
- const remnicCfg = resolveRemnicConfigRecord4(raw);
8689
- const config = parseConfig5(remnicCfg);
9196
+ const raw = fs13.existsSync(configPath) ? JSON.parse(fs13.readFileSync(configPath, "utf8")) : {};
9197
+ const remnicCfg = resolveRemnicConfigRecord5(raw);
9198
+ const config = parseConfig6(remnicCfg);
8690
9199
  const orchestrator = new Orchestrator3(config);
8691
9200
  await orchestrator.initialize();
8692
9201
  const service = new EngramAccessService2(orchestrator);
@@ -8855,9 +9364,9 @@ async function cmdXray(rest) {
8855
9364
  parseXrayCliOptions(rawQuery, options);
8856
9365
  initLogger2();
8857
9366
  const configPath = resolveConfigPath();
8858
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
8859
- const remnicCfg = resolveRemnicConfigRecord4(raw);
8860
- const config = parseConfig5(remnicCfg);
9367
+ const raw = fs13.existsSync(configPath) ? JSON.parse(fs13.readFileSync(configPath, "utf8")) : {};
9368
+ const remnicCfg = resolveRemnicConfigRecord5(raw);
9369
+ const config = parseConfig6(remnicCfg);
8861
9370
  const orchestrator = new Orchestrator3(config);
8862
9371
  await orchestrator.initialize();
8863
9372
  await orchestrator.deferredReady;
@@ -8878,9 +9387,9 @@ async function cmdXray(rest) {
8878
9387
  async function cmdVersions(rest) {
8879
9388
  initLogger2();
8880
9389
  const configPath = resolveConfigPath();
8881
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
8882
- const remnicCfg = resolveRemnicConfigRecord4(raw);
8883
- const config = parseConfig5(remnicCfg);
9390
+ const raw = fs13.existsSync(configPath) ? JSON.parse(fs13.readFileSync(configPath, "utf8")) : {};
9391
+ const remnicCfg = resolveRemnicConfigRecord5(raw);
9392
+ const config = parseConfig6(remnicCfg);
8884
9393
  if (!config.versioningEnabled) {
8885
9394
  console.error("Page versioning is disabled (versioningEnabled = false).");
8886
9395
  process.exit(1);
@@ -8994,9 +9503,9 @@ Options:
8994
9503
  async function cmdEnrich(rest) {
8995
9504
  initLogger2();
8996
9505
  const configPath = resolveConfigPath();
8997
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
8998
- const remnicCfg = resolveRemnicConfigRecord4(raw);
8999
- const config = parseConfig5(remnicCfg);
9506
+ const raw = fs13.existsSync(configPath) ? JSON.parse(fs13.readFileSync(configPath, "utf8")) : {};
9507
+ const remnicCfg = resolveRemnicConfigRecord5(raw);
9508
+ const config = parseConfig6(remnicCfg);
9000
9509
  const subcommand = rest[0];
9001
9510
  if (subcommand === "audit") {
9002
9511
  const memoryDir2 = expandTilde(config.memoryDir);
@@ -9239,9 +9748,9 @@ Shared with:
9239
9748
  process.exit(1);
9240
9749
  }
9241
9750
  const configPath = resolveConfigPath();
9242
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
9243
- const remnicCfg = resolveRemnicConfigRecord4(raw);
9244
- const config = parseConfig5(remnicCfg);
9751
+ const raw = fs13.existsSync(configPath) ? JSON.parse(fs13.readFileSync(configPath, "utf8")) : {};
9752
+ const remnicCfg = resolveRemnicConfigRecord5(raw);
9753
+ const config = parseConfig6(remnicCfg);
9245
9754
  const memoryDir = expandTilde(
9246
9755
  typeof memoryDirOverride === "string" && memoryDirOverride.length > 0 ? memoryDirOverride : config.memoryDir ?? resolveMemoryDir()
9247
9756
  );
@@ -9256,9 +9765,9 @@ Shared with:
9256
9765
  async function cmdExtensions(action, rest) {
9257
9766
  initLogger2();
9258
9767
  const configPath = resolveConfigPath();
9259
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
9260
- const remnicCfg = resolveRemnicConfigRecord4(raw);
9261
- const config = parseConfig5(remnicCfg);
9768
+ const raw = fs13.existsSync(configPath) ? JSON.parse(fs13.readFileSync(configPath, "utf8")) : {};
9769
+ const remnicCfg = resolveRemnicConfigRecord5(raw);
9770
+ const config = parseConfig6(remnicCfg);
9262
9771
  const root = resolveExtensionsRoot(config);
9263
9772
  const noopLog = { warn: () => {
9264
9773
  }, debug: () => {
@@ -9307,7 +9816,7 @@ Root: ${root}`);
9307
9816
  const extensions = await discoverMemoryExtensions(root, warnLog);
9308
9817
  let entries = [];
9309
9818
  try {
9310
- entries = fs12.readdirSync(root);
9819
+ entries = fs13.readdirSync(root);
9311
9820
  } catch {
9312
9821
  console.log(`Extensions root does not exist: ${root}`);
9313
9822
  process.exitCode = 0;
@@ -9318,7 +9827,7 @@ Root: ${root}`);
9318
9827
  for (const entry of entries) {
9319
9828
  const entryPath = path15.join(root, entry);
9320
9829
  try {
9321
- if (!fs12.statSync(entryPath).isDirectory()) continue;
9830
+ if (!fs13.statSync(entryPath).isDirectory()) continue;
9322
9831
  } catch {
9323
9832
  continue;
9324
9833
  }
@@ -9350,9 +9859,9 @@ Root: ${root}`);
9350
9859
  async function cmdBriefing(rest) {
9351
9860
  initLogger2();
9352
9861
  const configPath = resolveConfigPath();
9353
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
9354
- const remnicCfg = resolveRemnicConfigRecord4(raw);
9355
- const config = parseConfig5(remnicCfg);
9862
+ const raw = fs13.existsSync(configPath) ? JSON.parse(fs13.readFileSync(configPath, "utf8")) : {};
9863
+ const remnicCfg = resolveRemnicConfigRecord5(raw);
9864
+ const config = parseConfig6(remnicCfg);
9356
9865
  if (!config.briefing.enabled) {
9357
9866
  console.error("Briefing is disabled in config (briefing.enabled = false).");
9358
9867
  process.exit(1);
@@ -9430,10 +9939,10 @@ async function cmdBriefing(rest) {
9430
9939
  if (save) {
9431
9940
  try {
9432
9941
  const saveDir = resolveBriefingSaveDir(config.briefing.saveDir);
9433
- fs12.mkdirSync(saveDir, { recursive: true });
9942
+ fs13.mkdirSync(saveDir, { recursive: true });
9434
9943
  const filename = briefingFilename(new Date(result.window.to), format);
9435
9944
  const filePath = path15.join(saveDir, filename);
9436
- fs12.writeFileSync(filePath, payload + (payload.endsWith("\n") ? "" : "\n"));
9945
+ fs13.writeFileSync(filePath, payload + (payload.endsWith("\n") ? "" : "\n"));
9437
9946
  console.error(`Saved briefing: ${filePath}`);
9438
9947
  } catch (err) {
9439
9948
  console.error(`Failed to save briefing: ${err instanceof Error ? err.message : String(err)}`);
@@ -9451,7 +9960,7 @@ async function cmdDoctor() {
9451
9960
  detail: `${nodeVersion} (requires >= 22.12.0)`
9452
9961
  });
9453
9962
  const configPath = resolveConfigPath();
9454
- const configExists = fs12.existsSync(configPath);
9963
+ const configExists = fs13.existsSync(configPath);
9455
9964
  checks.push({ name: "Config file", ok: configExists, detail: configPath });
9456
9965
  let standaloneConfig;
9457
9966
  let standaloneConfigError;
@@ -9459,11 +9968,11 @@ async function cmdDoctor() {
9459
9968
  let configuredNs = { invalid: false };
9460
9969
  if (configExists) {
9461
9970
  try {
9462
- const raw = JSON.parse(fs12.readFileSync(configPath, "utf8"));
9463
- const remnicCfg = resolveRemnicConfigRecord4(raw);
9971
+ const raw = JSON.parse(fs13.readFileSync(configPath, "utf8"));
9972
+ const remnicCfg = resolveRemnicConfigRecord5(raw);
9464
9973
  standaloneOpenaiApiKeyExplicitlyFalse = isOpenaiApiKeyDisabled(remnicCfg.openaiApiKey);
9465
9974
  configuredNs = readConfiguredNamespace(remnicCfg);
9466
- standaloneConfig = parseConfig5(remnicCfg);
9975
+ standaloneConfig = parseConfig6(remnicCfg);
9467
9976
  } catch (err) {
9468
9977
  standaloneConfigError = err instanceof Error ? err.message : String(err);
9469
9978
  }
@@ -9472,10 +9981,10 @@ async function cmdDoctor() {
9472
9981
  try {
9473
9982
  memoryDir = resolveMemoryDir();
9474
9983
  } catch {
9475
- memoryDir = parseConfig5({}).memoryDir;
9984
+ memoryDir = parseConfig6({}).memoryDir;
9476
9985
  }
9477
9986
  try {
9478
- fs12.mkdirSync(memoryDir, { recursive: true });
9987
+ fs13.mkdirSync(memoryDir, { recursive: true });
9479
9988
  checks.push({ name: "Memory directory", ok: true, detail: memoryDir });
9480
9989
  } catch {
9481
9990
  checks.push({ name: "Memory directory", ok: false, detail: `cannot create ${memoryDir}` });
@@ -9504,7 +10013,7 @@ async function cmdDoctor() {
9504
10013
  });
9505
10014
  if (nsPolicyCheck) checks.push(nsPolicyCheck);
9506
10015
  const openclawConfigPath = resolveOpenclawConfigPath();
9507
- const openclawConfigExists = fs12.existsSync(openclawConfigPath);
10016
+ const openclawConfigExists = fs13.existsSync(openclawConfigPath);
9508
10017
  let openclawConfig = {};
9509
10018
  let openclawConfigValid = false;
9510
10019
  let openclawPluginModeConfigured = false;
@@ -9512,7 +10021,7 @@ async function cmdDoctor() {
9512
10021
  let activeOpenclawEntryConfig = null;
9513
10022
  if (openclawConfigExists) {
9514
10023
  try {
9515
- const parsed = JSON.parse(fs12.readFileSync(openclawConfigPath, "utf-8"));
10024
+ const parsed = JSON.parse(fs13.readFileSync(openclawConfigPath, "utf-8"));
9516
10025
  if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
9517
10026
  openclawConfig = parsed;
9518
10027
  openclawConfigValid = true;
@@ -9592,9 +10101,9 @@ async function cmdDoctor() {
9592
10101
  let memDirOk = false;
9593
10102
  let memDirDetail = `${resolvedMemDir} (not found)`;
9594
10103
  let memDirRemediation = `Run \`remnic openclaw install --memory-dir "${resolvedMemDir}"\` to create the directory.`;
9595
- if (fs12.existsSync(resolvedMemDir)) {
10104
+ if (fs13.existsSync(resolvedMemDir)) {
9596
10105
  try {
9597
- const stat = fs12.statSync(resolvedMemDir);
10106
+ const stat = fs13.statSync(resolvedMemDir);
9598
10107
  if (stat.isDirectory()) {
9599
10108
  memDirOk = true;
9600
10109
  memDirDetail = resolvedMemDir;
@@ -9736,12 +10245,12 @@ async function cmdDoctor() {
9736
10245
  }
9737
10246
  function cmdConfig() {
9738
10247
  const configPath = resolveConfigPath();
9739
- if (!fs12.existsSync(configPath)) {
10248
+ if (!fs13.existsSync(configPath)) {
9740
10249
  console.log("No config file found. Run `remnic init` to create one.");
9741
10250
  return;
9742
10251
  }
9743
10252
  console.log(`Config: ${configPath}`);
9744
- const rawConfig = fs12.readFileSync(configPath, "utf8");
10253
+ const rawConfig = fs13.readFileSync(configPath, "utf8");
9745
10254
  const redacted = rawConfig.replace(
9746
10255
  /("(?:openaiApiKey|localLlmApiKey|authToken|apiKey|remoteSearchApiKey|meilisearchApiKey|opikApiKey)"\s*:\s*")([^"]*)(")/g,
9747
10256
  "$1[REDACTED]$3"
@@ -9849,9 +10358,9 @@ async function cmdReview(action, rest) {
9849
10358
  const configPath = resolveConfigPath();
9850
10359
  let tombstonesConfig = null;
9851
10360
  try {
9852
- const rawCfg = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
9853
- const remnicCfg = resolveRemnicConfigRecord4(rawCfg);
9854
- const config = parseConfig5(remnicCfg);
10361
+ const rawCfg = fs13.existsSync(configPath) ? JSON.parse(fs13.readFileSync(configPath, "utf8")) : {};
10362
+ const remnicCfg = resolveRemnicConfigRecord5(rawCfg);
10363
+ const config = parseConfig6(remnicCfg);
9855
10364
  tombstonesConfig = {
9856
10365
  enabled: config.tombstonesEnabled,
9857
10366
  semanticMatch: config.tombstonesSemanticMatch,
@@ -9937,7 +10446,7 @@ async function cmdSync(action, rest, json) {
9937
10446
  }
9938
10447
  function localOfflineSourceId(memoryDir) {
9939
10448
  const host = os.hostname() || "unknown-host";
9940
- const dirHash = createHash3("sha256").update(path15.resolve(memoryDir)).digest("hex").slice(0, 16);
10449
+ const dirHash = createHash4("sha256").update(path15.resolve(memoryDir)).digest("hex").slice(0, 16);
9941
10450
  return `remnic-local:${host}:${dirHash}`;
9942
10451
  }
9943
10452
  function normalizeOfflineRemoteUrl(raw) {
@@ -10301,8 +10810,8 @@ var APPEND_TOLERANT_RUNTIME_STATE_FILES = /* @__PURE__ */ new Set([
10301
10810
  function isAppendTolerantOfflineRuntimeFile(relPath) {
10302
10811
  if (!shouldPreferIncomingOfflineRuntimeFile(relPath)) return false;
10303
10812
  const parts = relPath.split("/");
10304
- const basename2 = parts[parts.length - 1] ?? "";
10305
- return APPEND_TOLERANT_RUNTIME_STATE_FILES.has(basename2);
10813
+ const basename3 = parts[parts.length - 1] ?? "";
10814
+ return APPEND_TOLERANT_RUNTIME_STATE_FILES.has(basename3);
10306
10815
  }
10307
10816
  function offlineFileContentChunkMatchesExpected(options) {
10308
10817
  const { chunk, expected, offset } = options;
@@ -10537,7 +11046,7 @@ function resolveOfflineDirectHydrationPath(memoryDir, relPath) {
10537
11046
  }
10538
11047
  return target;
10539
11048
  }
10540
- var OFFLINE_SYNC_FILE_CONTENT_UPLOAD_CHUNK_BYTES = OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES3;
11049
+ var OFFLINE_SYNC_FILE_CONTENT_UPLOAD_CHUNK_BYTES = OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES4;
10541
11050
  async function pushOfflineFileContent(args) {
10542
11051
  if (args.readFileChunks) {
10543
11052
  return pushOfflineFileContentFromChunkReader(args);
@@ -10545,7 +11054,7 @@ async function pushOfflineFileContent(args) {
10545
11054
  let offset = 0;
10546
11055
  let finalResult = null;
10547
11056
  let remoteSatisfiedResult = null;
10548
- const hash = createHash3("sha256");
11057
+ const hash = createHash4("sha256");
10549
11058
  let bytes = 0;
10550
11059
  while (offset < args.file.bytes || args.file.bytes === 0 && offset === 0) {
10551
11060
  const chunk = await readOfflineSyncFileContentChunk({
@@ -10602,11 +11111,11 @@ async function pushOfflineFileContent(args) {
10602
11111
  }
10603
11112
  async function pushOfflineFileContentFromChunkReader(args) {
10604
11113
  const filePath = resolveOfflineDirectHydrationPath(args.memoryDir, args.file.path);
10605
- const stat = fs12.statSync(filePath);
11114
+ const stat = fs13.statSync(filePath);
10606
11115
  if (stat.mtimeMs !== args.file.mtimeMs) {
10607
11116
  throw new Error(`local file changed while pushing offline content: ${args.file.path}`);
10608
11117
  }
10609
- const hash = createHash3("sha256");
11118
+ const hash = createHash4("sha256");
10610
11119
  const chunks = args.readFileChunks({
10611
11120
  root: path15.resolve(args.memoryDir),
10612
11121
  path: args.file.path,
@@ -10869,7 +11378,7 @@ function formatMissingDecodedContentError(missing) {
10869
11378
  }
10870
11379
  async function waitForMissingOfflineContentRetry(delayMs) {
10871
11380
  if (delayMs <= 0) return;
10872
- await new Promise((resolve) => setTimeout(resolve, delayMs));
11381
+ await new Promise((resolve2) => setTimeout(resolve2, delayMs));
10873
11382
  }
10874
11383
  async function hydrateOfflineSnapshotContent(args) {
10875
11384
  const snapshot = normalizeOfflineSyncSnapshot(args.snapshot);
@@ -11034,15 +11543,15 @@ function parseOfflineIntervalMs(args) {
11034
11543
  return parsed;
11035
11544
  }
11036
11545
  function waitForOfflineInterval(ms, setCancel) {
11037
- return new Promise((resolve) => {
11546
+ return new Promise((resolve2) => {
11038
11547
  const timer = setTimeout(() => {
11039
11548
  setCancel(null);
11040
- resolve();
11549
+ resolve2();
11041
11550
  }, ms);
11042
11551
  setCancel(() => {
11043
11552
  clearTimeout(timer);
11044
11553
  setCancel(null);
11045
- resolve();
11554
+ resolve2();
11046
11555
  });
11047
11556
  });
11048
11557
  }
@@ -11093,7 +11602,7 @@ function advanceOfflineBaseFilesForSuccessfulPush(options) {
11093
11602
  return [...next.values()].sort((left, right) => left.path.localeCompare(right.path));
11094
11603
  }
11095
11604
  async function runOfflineSyncOnce(options) {
11096
- fs12.mkdirSync(options.memoryDir, { recursive: true });
11605
+ fs13.mkdirSync(options.memoryDir, { recursive: true });
11097
11606
  let activeStatePath = options.statePath;
11098
11607
  let priorState = await readOfflineSyncState(activeStatePath);
11099
11608
  let syncNamespace = options.namespace ?? priorState?.namespace;
@@ -11512,7 +12021,8 @@ async function runOfflineSyncOnce(options) {
11512
12021
  readFile: storageIo.readFile,
11513
12022
  readFileDigest: storageIo.readFileDigest,
11514
12023
  writeFile: storageIo.writeFile,
11515
- deleteFile: storageIo.deleteFile
12024
+ deleteFile: storageIo.deleteFile,
12025
+ recordDeletionRevision: storageIo.recordDeletionRevision
11516
12026
  });
11517
12027
  } catch (error) {
11518
12028
  if (!isMissingOfflineContentError(error)) {
@@ -11558,7 +12068,8 @@ async function runOfflineSyncOnce(options) {
11558
12068
  readFile: storageIo.readFile,
11559
12069
  readFileDigest: storageIo.readFileDigest,
11560
12070
  writeFile: storageIo.writeFile,
11561
- deleteFile: storageIo.deleteFile
12071
+ deleteFile: storageIo.deleteFile,
12072
+ recordDeletionRevision: storageIo.recordDeletionRevision
11562
12073
  });
11563
12074
  } catch (retryApplyError) {
11564
12075
  if (pushed || partialHydration.hydratedFiles.length > 0) {
@@ -11719,7 +12230,7 @@ Environment fallbacks:
11719
12230
  const configPath = resolveConfigPath();
11720
12231
  let config;
11721
12232
  try {
11722
- const rawConfig = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
12233
+ const rawConfig = fs13.existsSync(configPath) ? JSON.parse(fs13.readFileSync(configPath, "utf8")) : {};
11723
12234
  config = parseConfigQuietly(pickOfflineConfigRecord(rawConfig));
11724
12235
  } catch {
11725
12236
  throw new Error(
@@ -11734,7 +12245,7 @@ Environment fallbacks:
11734
12245
  const statePath = statePathExplicit ? path15.resolve(expandTilde(stateOverride)) : remoteUrl !== void 0 ? defaultOfflineSyncStatePath(memoryDir, remoteUrl, namespace) : void 0;
11735
12246
  if (action === "prepare") {
11736
12247
  if (!remoteUrl || !token || !statePath) throw new Error("offline prepare requires remote URL and token");
11737
- fs12.mkdirSync(memoryDir, { recursive: true });
12248
+ fs13.mkdirSync(memoryDir, { recursive: true });
11738
12249
  const remoteSnapshot = await fetchOfflineSnapshot({
11739
12250
  remoteUrl,
11740
12251
  token,
@@ -11771,7 +12282,8 @@ Environment fallbacks:
11771
12282
  readFile: storageIo.readFile,
11772
12283
  readFileDigest: storageIo.readFileDigest,
11773
12284
  writeFile: storageIo.writeFile,
11774
- deleteFile: storageIo.deleteFile
12285
+ deleteFile: storageIo.deleteFile,
12286
+ recordDeletionRevision: storageIo.recordDeletionRevision
11775
12287
  });
11776
12288
  const state = offlineSyncStateFromSnapshot({
11777
12289
  remoteId: remoteUrl,
@@ -11832,7 +12344,7 @@ Environment fallbacks:
11832
12344
  return;
11833
12345
  }
11834
12346
  if (action === "status") {
11835
- fs12.mkdirSync(memoryDir, { recursive: true });
12347
+ fs13.mkdirSync(memoryDir, { recursive: true });
11836
12348
  const state = statePath ? await readOfflineSyncState(statePath) : null;
11837
12349
  if (state && remoteUrl && statePath) {
11838
12350
  assertOfflineStateMatches({
@@ -11970,7 +12482,7 @@ function cmdDedup(json) {
11970
12482
  function readInstalledConnectorConfig(configPath, fallback) {
11971
12483
  if (!configPath) return fallback;
11972
12484
  try {
11973
- const parsed = JSON.parse(fs12.readFileSync(configPath, "utf8"));
12485
+ const parsed = JSON.parse(fs13.readFileSync(configPath, "utf8"));
11974
12486
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return fallback;
11975
12487
  const { token: _token, ...config } = parsed;
11976
12488
  return config;
@@ -12148,7 +12660,7 @@ async function cmdConnectors(action, rest, json) {
12148
12660
  const pub = factory();
12149
12661
  const available = await pub.isHostAvailable();
12150
12662
  const extRoot = available ? await pub.resolveExtensionRoot() : "(host not installed)";
12151
- const extensionExists = available && extRoot ? fs12.existsSync(extRoot) : false;
12663
+ const extensionExists = available && extRoot ? fs13.existsSync(extRoot) : false;
12152
12664
  publisherChecks.push({
12153
12665
  name: `Publisher: ${targetHostId}`,
12154
12666
  ok: !available || extensionExists,
@@ -12222,7 +12734,7 @@ async function cmdConnectors(action, rest, json) {
12222
12734
  let connectorsCfg;
12223
12735
  const configPath = resolveConfigPath();
12224
12736
  try {
12225
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
12737
+ const raw = fs13.existsSync(configPath) ? JSON.parse(fs13.readFileSync(configPath, "utf8")) : {};
12226
12738
  connectorsCfg = parseConfigQuietly(raw).connectors;
12227
12739
  } catch {
12228
12740
  process.stderr.write(
@@ -12298,9 +12810,9 @@ async function cmdConnectors(action, rest, json) {
12298
12810
  }
12299
12811
  initLogger2();
12300
12812
  const configPath = resolveConfigPath();
12301
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
12302
- const remnicCfg = resolveRemnicConfigRecord4(raw);
12303
- const config = parseConfig5(remnicCfg);
12813
+ const raw = fs13.existsSync(configPath) ? JSON.parse(fs13.readFileSync(configPath, "utf8")) : {};
12814
+ const remnicCfg = resolveRemnicConfigRecord5(raw);
12815
+ const config = parseConfig6(remnicCfg);
12304
12816
  const orchestrator = new Orchestrator3(config);
12305
12817
  try {
12306
12818
  await orchestrator.initialize();
@@ -12423,9 +12935,9 @@ async function cmdConnectorsMarketplace(subAction, rest, json) {
12423
12935
  console.error(`connectors marketplace: ${err instanceof Error ? err.message : String(err)}`);
12424
12936
  process.exit(1);
12425
12937
  }
12426
- const rawConfig = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
12427
- const pluginConfig = resolveRemnicConfigRecord4(rawConfig);
12428
- const config = parseConfig5(pluginConfig);
12938
+ const rawConfig = fs13.existsSync(configPath) ? JSON.parse(fs13.readFileSync(configPath, "utf8")) : {};
12939
+ const pluginConfig = resolveRemnicConfigRecord5(rawConfig);
12940
+ const config = parseConfig6(pluginConfig);
12429
12941
  if (subAction === "generate") {
12430
12942
  let outputDir;
12431
12943
  try {
@@ -12445,13 +12957,13 @@ async function cmdConnectorsMarketplace(subAction, rest, json) {
12445
12957
  } else if (subAction === "validate") {
12446
12958
  const targetPath = rest.filter((a) => !a.startsWith("--"))[0] ?? path15.join(process.cwd(), "marketplace.json");
12447
12959
  const resolved = path15.resolve(targetPath);
12448
- if (!fs12.existsSync(resolved)) {
12960
+ if (!fs13.existsSync(resolved)) {
12449
12961
  console.error(`File not found: ${resolved}`);
12450
12962
  process.exit(1);
12451
12963
  }
12452
12964
  let parsed;
12453
12965
  try {
12454
- parsed = JSON.parse(fs12.readFileSync(resolved, "utf8"));
12966
+ parsed = JSON.parse(fs13.readFileSync(resolved, "utf8"));
12455
12967
  } catch {
12456
12968
  console.error(`Invalid JSON in ${resolved}`);
12457
12969
  process.exit(1);
@@ -12581,7 +13093,7 @@ async function cmdSpace(action, rest, json) {
12581
13093
  console.error("Usage: remnic space push <source> <target>");
12582
13094
  process.exit(1);
12583
13095
  }
12584
- const result = pushToSpace(sourceId, targetId, { force: rest.includes("--force") });
13096
+ const result = await pushToSpace(sourceId, targetId, { force: rest.includes("--force") });
12585
13097
  if (json) {
12586
13098
  console.log(JSON.stringify(result, null, 2));
12587
13099
  } else {
@@ -12596,7 +13108,7 @@ async function cmdSpace(action, rest, json) {
12596
13108
  console.error("Usage: remnic space pull <source> <target>");
12597
13109
  process.exit(1);
12598
13110
  }
12599
- const result = pullFromSpace(sourceId, targetId, { force: rest.includes("--force") });
13111
+ const result = await pullFromSpace(sourceId, targetId, { force: rest.includes("--force") });
12600
13112
  if (json) {
12601
13113
  console.log(JSON.stringify(result, null, 2));
12602
13114
  } else {
@@ -12620,7 +13132,7 @@ async function cmdSpace(action, rest, json) {
12620
13132
  console.error("Usage: remnic space promote <source> <target>");
12621
13133
  process.exit(1);
12622
13134
  }
12623
- const result = promoteSpace(sourceId, targetId, {
13135
+ const result = await promoteSpace(sourceId, targetId, {
12624
13136
  force: rest.includes("--force"),
12625
13137
  forceOverwrite: rest.includes("--force-overwrite")
12626
13138
  });
@@ -12652,9 +13164,9 @@ async function cmdSpace(action, rest, json) {
12652
13164
  async function cmdLegacyBenchmark(action, rest, json) {
12653
13165
  initLogger2();
12654
13166
  const configPath = resolveConfigPath();
12655
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
12656
- const remnicCfg = resolveRemnicConfigRecord4(raw);
12657
- const config = parseConfig5(remnicCfg);
13167
+ const raw = fs13.existsSync(configPath) ? JSON.parse(fs13.readFileSync(configPath, "utf8")) : {};
13168
+ const remnicCfg = resolveRemnicConfigRecord5(raw);
13169
+ const config = parseConfig6(remnicCfg);
12658
13170
  const orchestrator = new Orchestrator3(config);
12659
13171
  const service = new EngramAccessService2(orchestrator);
12660
13172
  const { runBenchSuite, loadBaseline, checkRegression } = await loadBenchModule();
@@ -13055,7 +13567,7 @@ function readPid() {
13055
13567
  function inferPort() {
13056
13568
  try {
13057
13569
  const configPath = resolveConfigPath();
13058
- const raw = JSON.parse(fs12.readFileSync(configPath, "utf8"));
13570
+ const raw = JSON.parse(fs13.readFileSync(configPath, "utf8"));
13059
13571
  return raw.server?.port ?? 4318;
13060
13572
  } catch {
13061
13573
  return 4318;
@@ -13150,13 +13662,13 @@ function daemonInstall() {
13150
13662
  process.exit(1);
13151
13663
  }
13152
13664
  const vars = { HOME: home, NODE_PATH: nodePath, REMNIC_SERVER_BIN: serverBin };
13153
- fs12.mkdirSync(LOGS_DIR, { recursive: true });
13665
+ fs13.mkdirSync(LOGS_DIR, { recursive: true });
13154
13666
  if (isMacOS()) {
13155
13667
  const templatePath = path15.resolve(import.meta.dirname, "../templates/launchd/ai.remnic.daemon.plist");
13156
- const template = fs12.readFileSync(templatePath, "utf8");
13668
+ const template = fs13.readFileSync(templatePath, "utf8");
13157
13669
  const plist = renderTemplate(template, vars);
13158
- fs12.mkdirSync(path15.dirname(LAUNCHD_PLIST_PATH), { recursive: true });
13159
- fs12.writeFileSync(LAUNCHD_PLIST_PATH, plist);
13670
+ fs13.mkdirSync(path15.dirname(LAUNCHD_PLIST_PATH), { recursive: true });
13671
+ fs13.writeFileSync(LAUNCHD_PLIST_PATH, plist);
13160
13672
  try {
13161
13673
  launchdLoadPlist(LAUNCHD_PLIST_PATH);
13162
13674
  } catch (err) {
@@ -13173,10 +13685,10 @@ function daemonInstall() {
13173
13685
  console.log(` Logs: ${LOGS_DIR}/daemon.log`);
13174
13686
  } else if (isLinux()) {
13175
13687
  const templatePath = path15.resolve(import.meta.dirname, "../templates/systemd/remnic.service");
13176
- const template = fs12.readFileSync(templatePath, "utf8");
13688
+ const template = fs13.readFileSync(templatePath, "utf8");
13177
13689
  const unit = renderTemplate(template, vars);
13178
- fs12.mkdirSync(path15.dirname(SYSTEMD_UNIT_PATH), { recursive: true });
13179
- fs12.writeFileSync(SYSTEMD_UNIT_PATH, unit);
13690
+ fs13.mkdirSync(path15.dirname(SYSTEMD_UNIT_PATH), { recursive: true });
13691
+ fs13.writeFileSync(SYSTEMD_UNIT_PATH, unit);
13180
13692
  try {
13181
13693
  childProcess2.execSync("systemctl --user daemon-reload", { stdio: "pipe" });
13182
13694
  } catch (err) {
@@ -13212,7 +13724,7 @@ function daemonUninstall() {
13212
13724
  } catch {
13213
13725
  }
13214
13726
  try {
13215
- fs12.unlinkSync(plistPath);
13727
+ fs13.unlinkSync(plistPath);
13216
13728
  removed = true;
13217
13729
  console.log(`Removed launchd service: ${plistPath}`);
13218
13730
  } catch {
@@ -13232,7 +13744,7 @@ function daemonUninstall() {
13232
13744
  let removed = false;
13233
13745
  for (const unitPath of SYSTEMD_UNIT_PATHS) {
13234
13746
  try {
13235
- fs12.unlinkSync(unitPath);
13747
+ fs13.unlinkSync(unitPath);
13236
13748
  removed = true;
13237
13749
  console.log(`Removed systemd service: ${unitPath}`);
13238
13750
  } catch {
@@ -13299,13 +13811,13 @@ async function daemonStatus() {
13299
13811
  console.log(` Port: ${port}`);
13300
13812
  console.log(` Service: ${serviceInstalled ? "installed" : "not installed"}`);
13301
13813
  console.log(` Platform: ${process.platform}`);
13302
- console.log(` PID file: ${fs12.existsSync(PID_FILE) ? PID_FILE : LEGACY_PID_FILE}`);
13303
- console.log(` Log file: ${fs12.existsSync(LOG_FILE) ? LOG_FILE : LEGACY_LOG_FILE}`);
13814
+ console.log(` PID file: ${fs13.existsSync(PID_FILE) ? PID_FILE : LEGACY_PID_FILE}`);
13815
+ console.log(` Log file: ${fs13.existsSync(LOG_FILE) ? LOG_FILE : LEGACY_LOG_FILE}`);
13304
13816
  try {
13305
13817
  const configPath = resolveConfigPath();
13306
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
13307
- const remnicCfg = resolveRemnicConfigRecord4(raw);
13308
- const config = parseConfig5(remnicCfg);
13818
+ const raw = fs13.existsSync(configPath) ? JSON.parse(fs13.readFileSync(configPath, "utf8")) : {};
13819
+ const remnicCfg = resolveRemnicConfigRecord5(raw);
13820
+ const config = parseConfig6(remnicCfg);
13309
13821
  const extRoot = resolveExtensionsRoot(config);
13310
13822
  const noopLog = { warn: () => {
13311
13823
  }, debug: () => {
@@ -13344,9 +13856,9 @@ function daemonStart() {
13344
13856
  return;
13345
13857
  }
13346
13858
  }
13347
- fs12.mkdirSync(PID_DIR, { recursive: true });
13348
- fs12.mkdirSync(LOGS_DIR, { recursive: true });
13349
- const logStream = fs12.openSync(LOG_FILE, "a");
13859
+ fs13.mkdirSync(PID_DIR, { recursive: true });
13860
+ fs13.mkdirSync(LOGS_DIR, { recursive: true });
13861
+ const logStream = fs13.openSync(LOG_FILE, "a");
13350
13862
  const serverBin = resolveServerBin();
13351
13863
  const isSource = serverBin.endsWith(".ts");
13352
13864
  let cmd;
@@ -13368,7 +13880,7 @@ function daemonStart() {
13368
13880
  }
13369
13881
  });
13370
13882
  child.unref();
13371
- fs12.writeFileSync(PID_FILE, String(child.pid));
13883
+ fs13.writeFileSync(PID_FILE, String(child.pid));
13372
13884
  console.log(`Started remnic server (pid ${child.pid})`);
13373
13885
  console.log(` Log: ${LOG_FILE}`);
13374
13886
  }
@@ -13402,11 +13914,11 @@ function daemonStop() {
13402
13914
  console.log("Process not found (cleaning up PID file)");
13403
13915
  }
13404
13916
  try {
13405
- fs12.unlinkSync(PID_FILE);
13917
+ fs13.unlinkSync(PID_FILE);
13406
13918
  } catch {
13407
13919
  }
13408
13920
  try {
13409
- fs12.unlinkSync(LEGACY_PID_FILE);
13921
+ fs13.unlinkSync(LEGACY_PID_FILE);
13410
13922
  } catch {
13411
13923
  }
13412
13924
  }
@@ -13498,7 +14010,7 @@ function cmdTokenRevoke(connector) {
13498
14010
  async function promptYesNo(question, defaultYes = true) {
13499
14011
  if (!process.stdin.isTTY) return defaultYes;
13500
14012
  process.stdout.write(question + " ");
13501
- return new Promise((resolve) => {
14013
+ return new Promise((resolve2) => {
13502
14014
  let buf = "";
13503
14015
  const cleanup = () => {
13504
14016
  process.stdin.removeListener("data", onData);
@@ -13508,7 +14020,7 @@ async function promptYesNo(question, defaultYes = true) {
13508
14020
  };
13509
14021
  const onEnd = () => {
13510
14022
  cleanup();
13511
- resolve(defaultYes);
14023
+ resolve2(defaultYes);
13512
14024
  };
13513
14025
  const onData = (chunk) => {
13514
14026
  buf += chunk.toString();
@@ -13517,11 +14029,11 @@ async function promptYesNo(question, defaultYes = true) {
13517
14029
  cleanup();
13518
14030
  const answer = buf.slice(0, nl).trim().toLowerCase();
13519
14031
  if (answer === "" || answer === "y" || answer === "yes") {
13520
- resolve(defaultYes || answer !== "");
14032
+ resolve2(defaultYes || answer !== "");
13521
14033
  } else if (answer === "n" || answer === "no") {
13522
- resolve(false);
14034
+ resolve2(false);
13523
14035
  } else {
13524
- resolve(defaultYes);
14036
+ resolve2(defaultYes);
13525
14037
  }
13526
14038
  }
13527
14039
  };
@@ -13534,9 +14046,9 @@ async function promptYesNo(question, defaultYes = true) {
13534
14046
  async function cmdBinary(rest) {
13535
14047
  initLogger2();
13536
14048
  const configPath = resolveConfigPath();
13537
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
13538
- const remnicCfg = resolveRemnicConfigRecord4(raw);
13539
- const config = parseConfig5(remnicCfg);
14049
+ const raw = fs13.existsSync(configPath) ? JSON.parse(fs13.readFileSync(configPath, "utf8")) : {};
14050
+ const remnicCfg = resolveRemnicConfigRecord5(raw);
14051
+ const config = parseConfig6(remnicCfg);
13540
14052
  const memoryDir = resolveMemoryDir();
13541
14053
  const blConfig = {
13542
14054
  enabled: config.binaryLifecycleEnabled,
@@ -13725,7 +14237,7 @@ async function cmdOpenclawInstall(opts) {
13725
14237
  } else if (slotIsActiveLegacy) {
13726
14238
  changes.push(` Slot left as "${REMNIC_OPENCLAW_LEGACY_PLUGIN_ID}" \u2014 re-run with --yes to activate the new entry`);
13727
14239
  }
13728
- if (!fs12.existsSync(memoryDir)) changes.push(`+ Will create memory directory: ${memoryDir}`);
14240
+ if (!fs13.existsSync(memoryDir)) changes.push(`+ Will create memory directory: ${memoryDir}`);
13729
14241
  if (hasLegacy && migrateLegacy) {
13730
14242
  changes.push(`~ Legacy '${REMNIC_OPENCLAW_LEGACY_PLUGIN_ID}' entry retained (safe to remove after verifying hooks fire)`);
13731
14243
  }
@@ -13745,8 +14257,8 @@ async function cmdOpenclawInstall(opts) {
13745
14257
  Resulting plugins.slots.memory: ${dryRunPlugins.slots?.memory ?? "(unset)"}`);
13746
14258
  return;
13747
14259
  }
13748
- if (fs12.existsSync(memoryDir)) {
13749
- const st = fs12.statSync(memoryDir);
14260
+ if (fs13.existsSync(memoryDir)) {
14261
+ const st = fs13.statSync(memoryDir);
13750
14262
  if (!st.isDirectory()) {
13751
14263
  throw new Error(
13752
14264
  `Cannot use ${memoryDir} as the memory directory \u2014 a file already exists at that path.
@@ -13754,12 +14266,12 @@ Remove it first and re-run, or choose a different path with --memory-dir.`
13754
14266
  );
13755
14267
  }
13756
14268
  } else {
13757
- fs12.mkdirSync(memoryDir, { recursive: true });
14269
+ fs13.mkdirSync(memoryDir, { recursive: true });
13758
14270
  console.log(`Created memory directory: ${memoryDir}`);
13759
14271
  }
13760
14272
  const configDir = path15.dirname(configPath);
13761
- if (!fs12.existsSync(configDir)) {
13762
- fs12.mkdirSync(configDir, { recursive: true });
14273
+ if (!fs13.existsSync(configDir)) {
14274
+ fs13.mkdirSync(configDir, { recursive: true });
13763
14275
  }
13764
14276
  atomicWriteFileSync(configPath, JSON.stringify(updatedConfig, null, 2) + "\n");
13765
14277
  console.log("\nDone! Summary of changes:");
@@ -13931,15 +14443,15 @@ async function cmdOpenclawMigrateEngram(opts) {
13931
14443
  }
13932
14444
  function createOpenclawUpgradeBackupDir() {
13933
14445
  const backupsRoot = path15.join(resolveHomeDir(), ".openclaw", "backups");
13934
- fs12.mkdirSync(backupsRoot, { recursive: true });
13935
- return fs12.mkdtempSync(path15.join(backupsRoot, `remnic-openclaw-upgrade-${formatOpenclawUpgradeStamp()}-`));
14446
+ fs13.mkdirSync(backupsRoot, { recursive: true });
14447
+ return fs13.mkdtempSync(path15.join(backupsRoot, `remnic-openclaw-upgrade-${formatOpenclawUpgradeStamp()}-`));
13936
14448
  }
13937
14449
  async function cmdTaxonomy(rest) {
13938
14450
  initLogger2();
13939
14451
  const configPath = resolveConfigPath();
13940
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
13941
- const remnicCfg = resolveRemnicConfigRecord4(raw);
13942
- const config = parseConfig5(remnicCfg);
14452
+ const raw = fs13.existsSync(configPath) ? JSON.parse(fs13.readFileSync(configPath, "utf8")) : {};
14453
+ const remnicCfg = resolveRemnicConfigRecord5(raw);
14454
+ const config = parseConfig6(remnicCfg);
13943
14455
  if (!config.taxonomyEnabled) {
13944
14456
  console.error(
13945
14457
  "Taxonomy is disabled in config (taxonomyEnabled = false). Enable it to use taxonomy commands."
@@ -13975,8 +14487,8 @@ async function cmdTaxonomy(rest) {
13975
14487
  console.log(doc);
13976
14488
  if (config.taxonomyAutoGenResolver) {
13977
14489
  const resolverPath = path15.join(config.memoryDir, ".taxonomy", "RESOLVER.md");
13978
- fs12.mkdirSync(path15.dirname(resolverPath), { recursive: true });
13979
- fs12.writeFileSync(resolverPath, doc);
14490
+ fs13.mkdirSync(path15.dirname(resolverPath), { recursive: true });
14491
+ fs13.writeFileSync(resolverPath, doc);
13980
14492
  console.error(`Written: ${resolverPath}`);
13981
14493
  }
13982
14494
  break;
@@ -14022,7 +14534,7 @@ async function cmdTaxonomy(rest) {
14022
14534
  if (config.taxonomyAutoGenResolver) {
14023
14535
  const doc = generateResolverDocument(taxonomy);
14024
14536
  const resolverPath = path15.join(config.memoryDir, ".taxonomy", "RESOLVER.md");
14025
- fs12.writeFileSync(resolverPath, doc);
14537
+ fs13.writeFileSync(resolverPath, doc);
14026
14538
  console.error(`Regenerated: ${resolverPath}`);
14027
14539
  }
14028
14540
  break;
@@ -14053,7 +14565,7 @@ async function cmdTaxonomy(rest) {
14053
14565
  if (config.taxonomyAutoGenResolver) {
14054
14566
  const doc = generateResolverDocument(taxonomy);
14055
14567
  const resolverPath = path15.join(config.memoryDir, ".taxonomy", "RESOLVER.md");
14056
- fs12.writeFileSync(resolverPath, doc);
14568
+ fs13.writeFileSync(resolverPath, doc);
14057
14569
  console.error(`Regenerated: ${resolverPath}`);
14058
14570
  }
14059
14571
  break;
@@ -14244,12 +14756,12 @@ async function runTrainingExport(args, stdout = process.stdout) {
14244
14756
  `Unknown training-export format "${args.format}". ${validList}`
14245
14757
  );
14246
14758
  }
14247
- if (!fs12.existsSync(args.memoryDir)) {
14759
+ if (!fs13.existsSync(args.memoryDir)) {
14248
14760
  throw new Error(
14249
14761
  `--memory-dir "${args.memoryDir}" does not exist. Provide the path to an existing memory directory.`
14250
14762
  );
14251
14763
  }
14252
- if (!fs12.statSync(args.memoryDir).isDirectory()) {
14764
+ if (!fs13.statSync(args.memoryDir).isDirectory()) {
14253
14765
  throw new Error(
14254
14766
  `--memory-dir "${args.memoryDir}" is not a directory. Provide the path to a memory directory, not a file.`
14255
14767
  );
@@ -14335,10 +14847,10 @@ async function runTrainingExport(args, stdout = process.stdout) {
14335
14847
  }
14336
14848
  const formatted = adapter.formatRecords(records);
14337
14849
  const outDir = path15.dirname(args.output);
14338
- fs12.mkdirSync(outDir, { recursive: true });
14850
+ fs13.mkdirSync(outDir, { recursive: true });
14339
14851
  const tmpPath = `${args.output}.tmp-${process.pid}-${Date.now()}`;
14340
- fs12.writeFileSync(tmpPath, formatted, "utf-8");
14341
- fs12.renameSync(tmpPath, args.output);
14852
+ fs13.writeFileSync(tmpPath, formatted, "utf-8");
14853
+ fs13.renameSync(tmpPath, args.output);
14342
14854
  stdout.write(
14343
14855
  `Exported ${records.length} records to ${args.output} (${adapter.name} format)
14344
14856
  `
@@ -14507,7 +15019,7 @@ async function main(argv = process.argv.slice(2)) {
14507
15019
  }
14508
15020
  }, 500);
14509
15021
  };
14510
- fs12.watch(memoryDir, { recursive: true }, (_event, filename) => {
15022
+ fs13.watch(memoryDir, { recursive: true }, (_event, filename) => {
14511
15023
  if (filename && filename.startsWith(".")) return;
14512
15024
  rebuild();
14513
15025
  });
@@ -14515,12 +15027,12 @@ async function main(argv = process.argv.slice(2)) {
14515
15027
  });
14516
15028
  } else if (subAction === "validate") {
14517
15029
  const treeDir = outputDir;
14518
- if (!fs12.existsSync(treeDir)) {
15030
+ if (!fs13.existsSync(treeDir)) {
14519
15031
  console.error(`Context tree not found at ${treeDir}. Run 'remnic tree generate' first.`);
14520
15032
  process.exit(1);
14521
15033
  }
14522
15034
  const indexPath = path15.join(treeDir, "INDEX.md");
14523
- if (!fs12.existsSync(indexPath)) {
15035
+ if (!fs13.existsSync(indexPath)) {
14524
15036
  console.error(`INDEX.md missing in ${treeDir}. Tree may be corrupt \u2014 regenerate.`);
14525
15037
  process.exit(1);
14526
15038
  }
@@ -14702,9 +15214,9 @@ Other:
14702
15214
  let wearablesService;
14703
15215
  try {
14704
15216
  const configPath = resolveConfigPath();
14705
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
14706
- const remnicCfg = resolveRemnicConfigRecord4(raw);
14707
- const config = parseConfig5(remnicCfg);
15217
+ const raw = fs13.existsSync(configPath) ? JSON.parse(fs13.readFileSync(configPath, "utf8")) : {};
15218
+ const remnicCfg = resolveRemnicConfigRecord5(raw);
15219
+ const config = parseConfig6(remnicCfg);
14708
15220
  wearablesOrchestrator = new Orchestrator3(config);
14709
15221
  await wearablesOrchestrator.initialize();
14710
15222
  await wearablesOrchestrator.deferredReady;
@@ -14744,6 +15256,10 @@ Other:
14744
15256
  await runMeetingsBinaryCommand(rest);
14745
15257
  break;
14746
15258
  }
15259
+ case "external-wiki": {
15260
+ await runExternalWikiBinaryCommand(rest);
15261
+ break;
15262
+ }
14747
15263
  case "import": {
14748
15264
  if (rest.includes("--help") || rest.includes("-h") || rest.length === 0) {
14749
15265
  console.log(IMPORT_USAGE);
@@ -14753,9 +15269,9 @@ Other:
14753
15269
  const targetFactory = async () => {
14754
15270
  if (!orchestratorSingleton) {
14755
15271
  const configPath = resolveConfigPath();
14756
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
14757
- const remnicCfg = resolveRemnicConfigRecord4(raw);
14758
- const config = parseConfig5(remnicCfg);
15272
+ const raw = fs13.existsSync(configPath) ? JSON.parse(fs13.readFileSync(configPath, "utf8")) : {};
15273
+ const remnicCfg = resolveRemnicConfigRecord5(raw);
15274
+ const config = parseConfig6(remnicCfg);
14759
15275
  orchestratorSingleton = new Orchestrator3(config);
14760
15276
  await orchestratorSingleton.initialize();
14761
15277
  await orchestratorSingleton.deferredReady;
@@ -14952,6 +15468,7 @@ Usage:
14952
15468
  Retrospective meetings: list stored records, show one by id, or build
14953
15469
  (detect + fuse + store) a day's meetings from ingested audio + screen
14954
15470
  activity. Run "remnic meetings help" for details.
15471
+ remnic external-wiki search <query...> [--wiki-id <id>] [--limit <1-20>] [--max-chars-per-hit <100-8000>] [--json]
14955
15472
 
14956
15473
  remnic doctor Run diagnostics
14957
15474
  remnic config Show current config
@@ -15039,8 +15556,8 @@ function waitForStreamDrain(stream) {
15039
15556
  if (!stream.writableNeedDrain) {
15040
15557
  return Promise.resolve();
15041
15558
  }
15042
- return new Promise((resolve) => {
15043
- stream.once("drain", resolve);
15559
+ return new Promise((resolve2) => {
15560
+ stream.once("drain", resolve2);
15044
15561
  });
15045
15562
  }
15046
15563
  function activeNonStdioHandleCount() {
@@ -15059,7 +15576,7 @@ async function armCliSuccessExitWatchdog() {
15059
15576
  waitForStreamDrain(process.stdout),
15060
15577
  waitForStreamDrain(process.stderr)
15061
15578
  ]),
15062
- new Promise((resolve) => setTimeout(resolve, CLI_OUTPUT_FLUSH_GRACE_MS))
15579
+ new Promise((resolve2) => setTimeout(resolve2, CLI_OUTPUT_FLUSH_GRACE_MS))
15063
15580
  ]);
15064
15581
  const watchdog = setTimeout(() => {
15065
15582
  if (activeNonStdioHandleCount() > 0) {