@remnic/cli 9.45.2 → 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 +1149 -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,
@@ -138,6 +138,7 @@ import {
138
138
  OPERATION_NAMES,
139
139
  validateCapabilitiesForMint
140
140
  } from "@remnic/core";
141
+ import { resolveRemnicPluginEntry } from "@remnic/core/plugin-id.js";
141
142
 
142
143
  // src/commands/meetings.ts
143
144
  import fs from "fs";
@@ -189,6 +190,34 @@ async function runMeetingsBinaryCommand(rest) {
189
190
  }
190
191
  }
191
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
+
192
221
  // src/optional-module-loader.ts
193
222
  function isSpecifierNotFoundError(err, specifier) {
194
223
  if (!err || typeof err !== "object") {
@@ -239,19 +268,19 @@ async function loadWecloneExportModule() {
239
268
  }
240
269
 
241
270
  // src/converge.ts
242
- import * as fs3 from "fs";
243
- import { createHash as createHash2 } from "crypto";
271
+ import * as fs4 from "fs";
272
+ import { createHash as createHash3 } from "crypto";
244
273
  import * as path2 from "path";
245
274
  import {
246
275
  CONVERGE_CONFLICT_POLICIES,
247
276
  DEFAULT_CONVERGE_CONFLICT_POLICY,
248
- parseConfig as parseConfig2,
277
+ parseConfig as parseConfig3,
249
278
  buildOfflineSyncSnapshotFromBase,
250
- OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES,
251
- OFFLINE_SYNC_CHANGESET_FORMAT,
252
- OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES as OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES2,
253
- applyOfflineSyncFileContentChunk
279
+ applyOfflineSyncFileContentChunk,
280
+ isInternalRemnicStatePath as isInternalRemnicStatePath3,
281
+ OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES as OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES3
254
282
  } from "@remnic/core";
283
+ import { parseFrontmatter } from "@remnic/core/storage.js";
255
284
  import { resolveCorpusNamespaceRoots } from "@remnic/core/corpus-watermark.js";
256
285
  import { listNamespaces } from "@remnic/core/namespaces/migrate.js";
257
286
  import {
@@ -261,7 +290,8 @@ import {
261
290
  defaultConvergeCursorPath,
262
291
  deriveConvergeCursorBase,
263
292
  readConvergeCursor,
264
- writeConvergeCursor
293
+ writeConvergeCursor,
294
+ normalizeConvergePeerUrl as normalizeConvergePeerUrl2
265
295
  } from "@remnic/core/reconcile/cursor.js";
266
296
  import {
267
297
  buildReconcileManifest,
@@ -270,7 +300,7 @@ import {
270
300
 
271
301
  // src/offline-storage-io.ts
272
302
  import { mkdtemp, readdir, lstat, rm } from "fs/promises";
273
- import fs2 from "fs";
303
+ import fs3 from "fs";
274
304
  import path from "path";
275
305
  import { createHash, createDecipheriv } from "crypto";
276
306
  import {
@@ -336,6 +366,7 @@ async function createOfflineStorageIo(memoryDir, configuredStorage) {
336
366
  const { storage, secureStoreKey } = configuredStorage ?? await createConfiguredOfflineStorage(memoryDir);
337
367
  return {
338
368
  readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
369
+ readDeletionRevisions: () => storage.readDeletionRevisions(),
339
370
  readFileDigest: async ({ filePath }) => {
340
371
  const hash = createHash("sha256");
341
372
  let bytes = 0;
@@ -363,7 +394,8 @@ async function createOfflineStorageIo(memoryDir, configuredStorage) {
363
394
  writeFile: async ({ filePath, content }) => storage.writeOfflineSyncFile(filePath, content),
364
395
  writeStagingFile: async ({ filePath, content }) => storage.writeOfflineSyncStagingFile(filePath, content),
365
396
  writeFileChunks: async ({ filePath, chunks }) => storage.writeOfflineSyncFileChunks(filePath, chunks),
366
- 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)
367
399
  };
368
400
  }
369
401
  var OFFLINE_DECRYPT_STAGING_ORPHAN_MS = 60 * 60 * 1e3;
@@ -406,7 +438,7 @@ async function* readOfflineSyncFileChunks(options) {
406
438
  });
407
439
  }
408
440
  async function readFilePrefix(filePath, length) {
409
- const handle = await fs2.promises.open(filePath, "r");
441
+ const handle = await fs3.promises.open(filePath, "r");
410
442
  try {
411
443
  const out = Buffer.alloc(length);
412
444
  const { bytesRead } = await handle.read(out, 0, length, 0);
@@ -416,7 +448,7 @@ async function readFilePrefix(filePath, length) {
416
448
  }
417
449
  }
418
450
  async function* readPlainOfflineFileChunks(filePath, chunkSize) {
419
- const stream = fs2.createReadStream(filePath, { highWaterMark: chunkSize });
451
+ const stream = fs3.createReadStream(filePath, { highWaterMark: chunkSize });
420
452
  for await (const chunk of stream) {
421
453
  yield Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
422
454
  }
@@ -459,9 +491,9 @@ async function* readEncryptedOfflineFileChunks(options) {
459
491
  });
460
492
  decipher.setAuthTag(authTag);
461
493
  decipher.setAAD(Buffer.concat([secureStoreEnvelopeHeaderAad(salt), aad]));
462
- const output = fs2.createWriteStream(tempPath, { mode: 384 });
494
+ const output = fs3.createWriteStream(tempPath, { mode: 384 });
463
495
  try {
464
- const stream = fs2.createReadStream(options.filePath, {
496
+ const stream = fs3.createReadStream(options.filePath, {
465
497
  start: MAGIC_HEADER_SIZE + ENVELOPE_HEADER_SIZE,
466
498
  highWaterMark: options.chunkSize
467
499
  });
@@ -470,16 +502,16 @@ async function* readEncryptedOfflineFileChunks(options) {
470
502
  Buffer.isBuffer(encryptedChunk) ? encryptedChunk : Buffer.from(encryptedChunk)
471
503
  );
472
504
  if (plain.length > 0 && !output.write(plain)) {
473
- await new Promise((resolve, reject) => {
474
- output.once("drain", resolve);
505
+ await new Promise((resolve2, reject) => {
506
+ output.once("drain", resolve2);
475
507
  output.once("error", reject);
476
508
  });
477
509
  }
478
510
  }
479
511
  const finalPlain = decipher.final();
480
512
  if (finalPlain.length > 0 && !output.write(finalPlain)) {
481
- await new Promise((resolve, reject) => {
482
- output.once("drain", resolve);
513
+ await new Promise((resolve2, reject) => {
514
+ output.once("drain", resolve2);
483
515
  output.once("error", reject);
484
516
  });
485
517
  }
@@ -515,9 +547,9 @@ function offlineFileAadCandidates(filePath, memoryDir) {
515
547
  return candidates;
516
548
  }
517
549
  async function closeWriteStream(stream) {
518
- await new Promise((resolve, reject) => {
550
+ await new Promise((resolve2, reject) => {
519
551
  stream.once("error", reject);
520
- stream.end(() => resolve());
552
+ stream.end(() => resolve2());
521
553
  });
522
554
  }
523
555
  function secureStoreEnvelopeHeaderAad(salt) {
@@ -529,39 +561,215 @@ function secureStoreEnvelopeHeaderAad(salt) {
529
561
 
530
562
  // src/converge.ts
531
563
  import { resolveAgentAccessAuthToken } from "@remnic/core/resolve-auth-token.js";
532
- async function readLocalTombstones(rootDir) {
533
- const shaSet = /* @__PURE__ */ new Set();
534
- const candidates = [
535
- path2.join(rootDir, "state", "tombstones.jsonl"),
536
- path2.join(rootDir, "tombstones.jsonl")
537
- ];
538
- 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;
539
678
  try {
540
- const content = await fs3.promises.readFile(tombPath, "utf-8");
541
- for (const line of content.split("\n")) {
542
- const trimmed = line.trim();
543
- if (!trimmed) continue;
544
- try {
545
- const record = JSON.parse(trimmed);
546
- if (typeof record.contentHash === "string" && /^[0-9a-f]{64}$/i.test(record.contentHash)) {
547
- shaSet.add(record.contentHash.toLowerCase());
548
- }
549
- if (typeof record.fileSha256 === "string" && /^[0-9a-f]{64}$/i.test(record.fileSha256)) {
550
- shaSet.add(record.fileSha256.toLowerCase());
551
- }
552
- } catch {
553
- }
554
- }
679
+ value = JSON.parse(line);
555
680
  } catch {
681
+ throw new Error(`invalid peer manifest for namespace ${expectedNamespace}: row was not JSON`);
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);
697
+ }
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
+ };
704
+ }
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}`);
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}`);
556
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
+ };
557
750
  }
558
- return shaSet;
751
+ return null;
559
752
  }
560
- async function fetchPeerSnapshot(peerUrl, namespace, token, fetchImpl = globalThis.fetch) {
561
- let base = peerUrl;
562
- while (base.endsWith("/")) {
563
- base = base.slice(0, -1);
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);
564
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);
565
773
  const routes = [
566
774
  `/remnic/v1/offline-sync/snapshot?namespace=${encodeURIComponent(namespace)}&content=false`,
567
775
  `/engram/v1/offline-sync/snapshot?namespace=${encodeURIComponent(namespace)}&content=false`
@@ -571,7 +779,7 @@ async function fetchPeerSnapshot(peerUrl, namespace, token, fetchImpl = globalTh
571
779
  for (const route of routes) {
572
780
  let response;
573
781
  try {
574
- response = await fetchImpl(`${base}${route}`, { headers });
782
+ response = await fetchPeerRequest(fetchImpl, `${base}${route}`, { headers }, timeoutMs);
575
783
  } catch (error) {
576
784
  lastFailure = error instanceof Error ? error.message : String(error);
577
785
  continue;
@@ -599,7 +807,7 @@ async function fetchPeerSnapshot(peerUrl, namespace, token, fetchImpl = globalTh
599
807
  mtimeMs: "mtimeMs" in item && typeof item.mtimeMs === "number" ? item.mtimeMs : void 0,
600
808
  bytes: "bytes" in item && typeof item.bytes === "number" ? item.bytes : void 0
601
809
  };
602
- });
810
+ }).filter((file) => !isInternalRemnicStatePath2(file.path));
603
811
  const rawTombstones = "tombstones" in data ? data.tombstones : void 0;
604
812
  if (rawTombstones !== void 0 && !Array.isArray(rawTombstones)) {
605
813
  throw new Error(`invalid peer snapshot for namespace ${namespace}: tombstones must be an array`);
@@ -623,8 +831,9 @@ function requiredResponseNumber(response, name) {
623
831
  }
624
832
  return value;
625
833
  }
626
- async function fetchPeerFileContent(peerUrl, namespace, filePath, token, fetchImpl = globalThis.fetch) {
627
- 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);
628
837
  const routes = [
629
838
  "/remnic/v1/offline-sync/file-content",
630
839
  "/engram/v1/offline-sync/file-content"
@@ -633,16 +842,17 @@ async function fetchPeerFileContent(peerUrl, namespace, filePath, token, fetchIm
633
842
  "content-type": "application/json",
634
843
  ...token ? { authorization: `Bearer ${token}` } : {}
635
844
  };
845
+ const hash = createHash2("sha256");
846
+ let offset = 0;
847
+ let expectedBytes;
848
+ let expectedSha256;
849
+ let mtimeMs;
636
850
  for (const route of routes) {
637
- try {
638
- const chunks = [];
639
- const hash = createHash2("sha256");
640
- let offset = 0;
641
- let expectedBytes;
642
- let expectedSha256;
643
- let mtimeMs;
644
- do {
645
- const response = await fetchImpl(`${base}${route}`, {
851
+ let routeFailed = false;
852
+ do {
853
+ let response;
854
+ try {
855
+ response = await fetchPeerRequest(fetchImpl, `${base}${route}`, {
646
856
  method: "POST",
647
857
  headers,
648
858
  body: JSON.stringify({
@@ -652,116 +862,148 @@ async function fetchPeerFileContent(peerUrl, namespace, filePath, token, fetchIm
652
862
  offset,
653
863
  length: OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES
654
864
  })
655
- });
865
+ }, timeoutMs);
656
866
  if (!response.ok) throw new Error(`offline file content request failed: ${response.status}`);
657
- 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());
658
877
  const chunkOffset = requiredResponseNumber(response, "x-remnic-chunk-offset");
659
878
  const chunkBytes = requiredResponseNumber(response, "x-remnic-chunk-bytes");
660
- const totalBytes = requiredResponseNumber(response, "x-remnic-file-bytes");
661
- const responseMtimeMs = requiredResponseNumber(response, "x-remnic-file-mtime-ms");
662
- 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");
663
882
  const encodedPath = response.headers.get("x-remnic-file-path");
664
- 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) {
665
884
  throw new Error(`offline file content response changed during transfer: ${filePath}`);
666
885
  }
667
- if (content.length === 0 && offset < totalBytes) {
668
- throw new Error(`offline file content chunk was empty before EOF: ${filePath}`);
669
- }
670
- expectedBytes = totalBytes;
671
- expectedSha256 = sha256;
672
- mtimeMs = responseMtimeMs;
673
- chunks.push(content);
674
- hash.update(content);
675
- offset += content.length;
676
- } while (expectedBytes === void 0 || offset < expectedBytes);
677
- if (expectedBytes === void 0 || expectedSha256 === void 0 || mtimeMs === void 0 || offset !== expectedBytes || hash.digest("hex") !== expectedSha256) {
678
- throw new Error(`offline file content checksum mismatch: ${filePath}`);
886
+ } catch {
887
+ routeFailed = true;
888
+ break;
679
889
  }
680
- return {
681
- content: Buffer.concat(chunks, expectedBytes),
682
- sha256: expectedSha256,
683
- bytes: expectedBytes,
684
- mtimeMs
685
- };
686
- } catch {
687
- }
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;
688
904
  }
689
- 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 };
690
909
  }
691
- function withoutTrailingSlashes(value) {
692
- let end = value.length;
693
- while (end > 0 && value.charCodeAt(end - 1) === 47) end -= 1;
694
- 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
+ };
695
928
  }
696
- async function postPeerFileContent(peerUrl, namespace, filePath, content, metadata, token, fetchImpl = globalThis.fetch) {
697
- 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);
698
932
  const routes = [
699
933
  `/remnic/v1/offline-sync/apply-file-content?namespace=${encodeURIComponent(namespace)}`,
700
934
  `/engram/v1/offline-sync/apply-file-content?namespace=${encodeURIComponent(namespace)}`
701
935
  ];
936
+ let offset = 0;
702
937
  let previousAttemptFailed = false;
703
938
  for (const route of routes) {
704
- try {
705
- let offset = 0;
706
- do {
707
- const chunk = content.subarray(
708
- offset,
709
- Math.min(content.length, offset + OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES2)
710
- );
711
- const headers = {
712
- "content-type": "application/octet-stream",
713
- "x-remnic-include-transcripts": "false",
714
- "x-remnic-source-id": encodeURIComponent("remnic-converge"),
715
- "x-remnic-file-path": encodeURIComponent(filePath),
716
- "x-remnic-file-sha256": metadata.sha256,
717
- "x-remnic-file-bytes": String(content.length),
718
- "x-remnic-file-mtime-ms": String(metadata.mtimeMs),
719
- "x-remnic-chunk-offset": String(offset),
720
- ...metadata.baseSha256 ? { "x-remnic-base-sha256": metadata.baseSha256 } : {},
721
- ...token ? { authorization: `Bearer ${token}` } : {}
722
- };
723
- 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}`, {
724
964
  method: "POST",
725
965
  headers,
726
966
  body: new Uint8Array(chunk)
727
- });
967
+ }, timeoutMs);
728
968
  if (!response.ok) throw new Error(`offline apply-file-content request failed: ${response.status}`);
729
- const result = await response.json().catch(() => null);
730
- 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) {
731
- return false;
732
- }
733
- if (result.done) {
734
- if (result.skipped) return previousAttemptFailed ? "applied" : "skipped";
735
- if (result.applied && offset + chunk.length === content.length) return "applied";
736
- return false;
737
- }
738
- if (result.applied || result.skipped || chunk.length === 0) {
739
- return false;
969
+ } catch {
970
+ if (previousAttemptFailed && offset > 0 && !restartedRoute) {
971
+ offset = 0;
972
+ restartedRoute = true;
973
+ continue;
740
974
  }
741
- offset += chunk.length;
742
- } while (offset < content.length);
743
- return false;
744
- } catch {
745
- 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;
746
988
  }
747
989
  }
748
990
  return false;
749
991
  }
750
- async function postPeerConvergenceComplete(peerUrl, namespaces, token, fetchImpl = globalThis.fetch) {
751
- 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);
752
994
  const query = namespaces.map((namespace) => `namespace=${encodeURIComponent(namespace)}`).join("&");
753
995
  const routes = [
754
996
  "/remnic/v1/offline-sync/convergence-complete",
755
997
  "/engram/v1/offline-sync/convergence-complete"
756
998
  ];
757
999
  for (const route of routes) {
758
- const response = await fetchImpl(`${base}${route}?${query}`, {
1000
+ const response = await fetchPeerRequest(fetchImpl, `${base}${route}?${query}`, {
759
1001
  method: "POST",
760
1002
  headers: {
761
1003
  "x-remnic-source-id": encodeURIComponent("remnic-converge"),
762
1004
  ...token ? { authorization: `Bearer ${token}` } : {}
763
1005
  }
764
- }).catch(() => null);
1006
+ }, timeoutMs).catch(() => null);
765
1007
  if (!response?.ok) continue;
766
1008
  const result = await response.json().catch(() => null);
767
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) {
@@ -770,8 +1012,9 @@ async function postPeerConvergenceComplete(peerUrl, namespaces, token, fetchImpl
770
1012
  }
771
1013
  return false;
772
1014
  }
773
- async function postPeerFileDeletion(peerUrl, namespace, filePath, baseSha256, token, fetchImpl = globalThis.fetch) {
774
- 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);
775
1018
  const routes = ["/remnic/v1/offline-sync/apply", "/engram/v1/offline-sync/apply"];
776
1019
  const headers = {
777
1020
  "content-type": "application/json",
@@ -780,7 +1023,7 @@ async function postPeerFileDeletion(peerUrl, namespace, filePath, baseSha256, to
780
1023
  let previousAttemptFailed = false;
781
1024
  for (const route of routes) {
782
1025
  try {
783
- const response = await fetchImpl(`${base}${route}`, {
1026
+ const response = await fetchPeerRequest(fetchImpl, `${base}${route}`, {
784
1027
  method: "POST",
785
1028
  headers,
786
1029
  body: JSON.stringify({
@@ -794,7 +1037,7 @@ async function postPeerFileDeletion(peerUrl, namespace, filePath, baseSha256, to
794
1037
  changes: [{ type: "delete", path: filePath, baseSha256 }]
795
1038
  }
796
1039
  })
797
- });
1040
+ }, timeoutMs);
798
1041
  if (!response.ok) throw new Error(`offline apply request failed: ${response.status}`);
799
1042
  const result = await response.json().catch(() => null);
800
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) {
@@ -809,6 +1052,73 @@ async function postPeerFileDeletion(peerUrl, namespace, filePath, baseSha256, to
809
1052
  }
810
1053
  return false;
811
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
+ }
812
1122
  async function computeConvergePlan(options = {}) {
813
1123
  const baseMap = /* @__PURE__ */ new Map();
814
1124
  const semanticAgreementMap = /* @__PURE__ */ new Map();
@@ -824,7 +1134,7 @@ async function computeConvergePlan(options = {}) {
824
1134
  if (options.baseFilesByNamespace) {
825
1135
  for (const [ns, files] of options.baseFilesByNamespace) {
826
1136
  namespacesToPlan.add(ns);
827
- baseMap.set(ns, files);
1137
+ baseMap.set(ns, files.filter((file) => !isInternalRemnicStatePath3(file.path)));
828
1138
  }
829
1139
  }
830
1140
  if (options.semanticAgreementsByNamespace) {
@@ -836,7 +1146,7 @@ async function computeConvergePlan(options = {}) {
836
1146
  if (options.localFilesByNamespace) {
837
1147
  for (const [ns, files] of options.localFilesByNamespace) {
838
1148
  namespacesToPlan.add(ns);
839
- localMap.set(ns, files);
1149
+ localMap.set(ns, files.filter((file) => !isInternalRemnicStatePath3(file.path)));
840
1150
  }
841
1151
  }
842
1152
  if (options.localTombstonesByNamespace) {
@@ -853,7 +1163,7 @@ async function computeConvergePlan(options = {}) {
853
1163
  if (options.peerFilesByNamespace) {
854
1164
  for (const [ns, files] of options.peerFilesByNamespace) {
855
1165
  namespacesToPlan.add(ns);
856
- peerMap.set(ns, files);
1166
+ peerMap.set(ns, files.filter((file) => !isInternalRemnicStatePath3(file.path)));
857
1167
  }
858
1168
  }
859
1169
  if (options.peerTombstonesByNamespace) {
@@ -870,10 +1180,11 @@ async function computeConvergePlan(options = {}) {
870
1180
  let config = options.config;
871
1181
  if (!config) {
872
1182
  try {
873
- config = parseConfig2({});
1183
+ config = parseConfig3({});
874
1184
  } catch {
875
1185
  }
876
1186
  }
1187
+ const memoryDir = options.cursorDir ?? config?.memoryDir;
877
1188
  if (!options.localFilesByNamespace && config) {
878
1189
  const roots = await resolveCorpusNamespaceRoots({ config });
879
1190
  const discovered = await listNamespaces({ config });
@@ -883,47 +1194,55 @@ async function computeConvergePlan(options = {}) {
883
1194
  for (const rootInfo of roots) {
884
1195
  const ns = rootInfo.namespace;
885
1196
  namespacesToPlan.add(ns);
886
- try {
887
- const snapshot = await buildOfflineSyncSnapshotFromBase({
888
- root: rootInfo.rootDir,
889
- sourceId: "local",
890
- includeContent: false
891
- });
892
- const files = snapshot.files.map((record) => ({
893
- path: record.path,
894
- sha256: record.sha256,
895
- mtimeMs: record.mtimeMs,
896
- bytes: record.bytes
897
- }));
898
- localMap.set(ns, files);
899
- try {
900
- const io = await createOfflineStorageIo(rootInfo.rootDir);
901
- localManifests.set(
902
- ns,
903
- await buildReconcileManifest({
904
- files,
905
- readFile: async (file) => {
906
- const readFile2 = io.readFile;
907
- if (!readFile2) throw new Error("offline storage cannot read reconciliation manifest files");
908
- return await readFile2({
909
- root: rootInfo.rootDir,
910
- path: file.path,
911
- filePath: path2.join(rootInfo.rootDir, file.path)
912
- });
913
- }
914
- })
915
- );
916
- } catch {
917
- 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
+ }
918
1231
  }
919
- const tombstones = await readLocalTombstones(rootInfo.rootDir);
920
- localTombstones.set(ns, tombstones);
921
- } catch {
922
- localMap.set(ns, []);
1232
+ });
1233
+ if (manifestReadFailed) {
1234
+ throw new Error(`failed to build local reconciliation manifest for namespace ${ns}`);
923
1235
  }
1236
+ localManifests.set(ns, manifest);
1237
+ localTombstones.set(ns, tombstonedFileDigests(evidence, manifest));
924
1238
  }
925
1239
  }
926
1240
  const peerUrl = options.peerUrl;
1241
+ if (memoryDir && peerUrl) {
1242
+ for (const namespace of await discoverCursorNamespaces(memoryDir, peerUrl)) {
1243
+ namespacesToPlan.add(namespace);
1244
+ }
1245
+ }
927
1246
  if (!options.peerFilesByNamespace && peerUrl) {
928
1247
  let resolvedToken;
929
1248
  if (options.peerToken) {
@@ -936,36 +1255,84 @@ async function computeConvergePlan(options = {}) {
936
1255
  }
937
1256
  }
938
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
+ );
939
1265
  for (const ns of namespacesToPlan) {
940
- const peerData = await fetchPeerSnapshot(peerUrl, ns, resolvedToken, fetchFn);
941
- peerMap.set(ns, peerData.files);
942
- peerTombstones.set(ns, peerData.tombstones);
943
- peerManifests.set(
944
- ns,
945
- await buildReconcileManifest({
946
- 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,
947
1276
  cachedFiles: localManifests.get(ns)?.files,
948
1277
  readFile: async (file) => {
949
- 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
+ }
950
1285
  if (!remote || remote.sha256 !== file.sha256) {
951
- 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;
952
1288
  }
953
1289
  return remote.content;
954
1290
  }
955
- })
956
- );
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);
957
1317
  }
958
1318
  }
959
- const memoryDir = options.cursorDir ?? config?.memoryDir;
960
1319
  if (memoryDir && options.peerUrl && (!options.baseFilesByNamespace || !options.semanticAgreementsByNamespace)) {
961
1320
  for (const ns of namespacesToPlan) {
962
1321
  const cursorPath = defaultConvergeCursorPath(memoryDir, options.peerUrl, ns);
963
1322
  const cursor = await readConvergeCursor(cursorPath);
964
1323
  if (!options.baseFilesByNamespace && cursor?.baseFiles && cursor.baseFiles.length > 0) {
965
- baseMap.set(ns, cursor.baseFiles);
1324
+ baseMap.set(
1325
+ ns,
1326
+ cursor.baseFiles.filter((file) => !isInternalRemnicStatePath3(file.path))
1327
+ );
966
1328
  }
967
1329
  if (!options.semanticAgreementsByNamespace && cursor?.semanticAgreements && cursor.semanticAgreements.length > 0) {
968
- 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
+ );
969
1336
  }
970
1337
  }
971
1338
  }
@@ -1029,7 +1396,7 @@ async function executeConvergeApply(options = {}) {
1029
1396
  }
1030
1397
  if (options.dryRun) {
1031
1398
  return {
1032
- converged: false,
1399
+ converged: plan.converged,
1033
1400
  status: "dry_run",
1034
1401
  plan,
1035
1402
  transfers: plannedTransfers,
@@ -1055,10 +1422,11 @@ async function executeConvergeApply(options = {}) {
1055
1422
  }
1056
1423
  }
1057
1424
  const fetchFn = options.fetchImpl ?? globalThis.fetch;
1425
+ const timeoutMs = options.peerRequestTimeoutMs ?? DEFAULT_PEER_REQUEST_TIMEOUT_MS;
1058
1426
  let config = options.config;
1059
1427
  if (!config) {
1060
1428
  try {
1061
- config = parseConfig2({});
1429
+ config = parseConfig3({});
1062
1430
  } catch {
1063
1431
  }
1064
1432
  }
@@ -1088,134 +1456,202 @@ async function executeConvergeApply(options = {}) {
1088
1456
  transferType = entry.localSha256 ? "push" : "delete-peer";
1089
1457
  }
1090
1458
  }
1459
+ const localPath = entry.semanticAgreement?.local.path ?? entry.path;
1460
+ const peerPath = entry.semanticAgreement?.peer.path ?? entry.path;
1091
1461
  if (transferType === "pull") {
1092
- let remoteFile = null;
1093
- 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;
1094
1537
  if (buffered) {
1095
- const state = options.peerFilesByNamespace?.get(entry.namespace)?.find((file) => file.path === entry.path);
1096
- remoteFile = {
1097
- content: buffered,
1098
- sha256: state?.sha256 ?? entry.peerSha256 ?? createHash2("sha256").update(buffered).digest("hex"),
1099
- bytes: buffered.length,
1100
- mtimeMs: state?.mtimeMs ?? 0
1101
- };
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 };
1102
1552
  } else if (options.peerUrl) {
1103
- remoteFile = await fetchPeerFileContent(
1553
+ metadata = await streamPeerFileContent(
1104
1554
  options.peerUrl,
1105
1555
  entry.namespace,
1106
- entry.path,
1556
+ peerPath,
1557
+ applyChunk,
1107
1558
  resolvedToken,
1108
- fetchFn
1559
+ fetchFn,
1560
+ timeoutMs
1109
1561
  );
1110
1562
  }
1111
- if (remoteFile !== null && (!entry.peerSha256 || remoteFile.sha256 === entry.peerSha256)) {
1112
- if (options.localFileBuffers) {
1113
- let nsMap = options.localFileBuffers.get(entry.namespace);
1114
- if (!nsMap) {
1115
- nsMap = /* @__PURE__ */ new Map();
1116
- options.localFileBuffers.set(entry.namespace, nsMap);
1117
- }
1118
- nsMap.set(entry.path, remoteFile.content);
1119
- if (entry.action === "conflict") actualTransfers.conflictsResolved += 1;
1120
- else actualTransfers.pulled += 1;
1121
- } else {
1122
- const rootDir = rootMap.get(entry.namespace);
1123
- if (rootDir) {
1124
- const io = await createOfflineStorageIo(rootDir);
1125
- const expectedLocalSha256 = entry.action === "conflict" ? entry.localSha256 : entry.baseSha256;
1126
- let offset = 0;
1127
- let transferComplete = false;
1128
- do {
1129
- const chunk = remoteFile.content.subarray(
1130
- offset,
1131
- Math.min(
1132
- remoteFile.content.length,
1133
- offset + OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES2
1134
- )
1135
- );
1136
- const chunkResult = await applyOfflineSyncFileContentChunk({
1137
- root: rootDir,
1138
- sourceId: "remnic-converge",
1139
- path: entry.path,
1140
- sha256: remoteFile.sha256,
1141
- bytes: remoteFile.bytes,
1142
- mtimeMs: remoteFile.mtimeMs,
1143
- offset,
1144
- content: chunk,
1145
- ...expectedLocalSha256 ? { baseSha256: expectedLocalSha256 } : {},
1146
- readFile: io.readFile,
1147
- readFileDigest: io.readFileDigest,
1148
- writeFile: io.writeFile,
1149
- writeStagingFile: io.writeStagingFile,
1150
- writeFileChunks: io.writeFileChunks
1151
- });
1152
- if (chunkResult.conflict) {
1153
- break;
1154
- }
1155
- if (chunkResult.done) {
1156
- transferComplete = chunkResult.applied || chunkResult.skipped;
1157
- break;
1158
- }
1159
- if (chunkResult.applied || chunkResult.skipped || chunk.length === 0) {
1160
- break;
1161
- }
1162
- offset += chunk.length;
1163
- } while (offset < remoteFile.content.length);
1164
- if (transferComplete) {
1165
- if (entry.action === "conflict") actualTransfers.conflictsResolved += 1;
1166
- else actualTransfers.pulled += 1;
1167
- } else {
1168
- actualTransfers.failed += 1;
1169
- }
1170
- } else {
1171
- actualTransfers.failed += 1;
1172
- }
1173
- }
1563
+ if (metadata && transferComplete && !transferRejected && (!entry.peerSha256 || metadata.sha256 === entry.peerSha256)) {
1564
+ if (entry.action === "conflict") actualTransfers.conflictsResolved += 1;
1565
+ else actualTransfers.pulled += 1;
1174
1566
  } else {
1175
1567
  actualTransfers.failed += 1;
1176
1568
  }
1177
1569
  } else if (transferType === "push") {
1178
- let content = null;
1179
- let mtimeMs = options.localFilesByNamespace?.get(entry.namespace)?.find((file) => file.path === entry.path)?.mtimeMs;
1180
- if (options.localFileBuffers?.get(entry.namespace)?.has(entry.path)) {
1181
- content = options.localFileBuffers.get(entry.namespace).get(entry.path);
1182
- } 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) {
1183
1583
  const rootDir = rootMap.get(entry.namespace);
1184
1584
  if (rootDir) {
1185
- const filePath = path2.join(rootDir, entry.path);
1186
1585
  try {
1586
+ const filePath = path2.join(rootDir, localPath);
1187
1587
  const io = await createOfflineStorageIo(rootDir);
1188
- content = await io.readFile({ root: rootDir, path: entry.path, filePath });
1189
- 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
+ };
1190
1631
  } catch {
1191
- content = null;
1632
+ source = null;
1192
1633
  }
1193
1634
  }
1194
1635
  }
1195
- if (content !== null) {
1196
- if (options.peerFileBuffers) {
1197
- let nsMap = options.peerFileBuffers.get(entry.namespace);
1198
- if (!nsMap) {
1199
- nsMap = /* @__PURE__ */ new Map();
1200
- 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);
1201
1642
  }
1202
- nsMap.set(entry.path, content);
1643
+ namespaceFiles.set(peerPath, localBuffer);
1203
1644
  if (entry.action === "conflict") actualTransfers.conflictsResolved += 1;
1204
1645
  else actualTransfers.pushed += 1;
1205
- } else if (options.peerUrl && entry.localSha256) {
1206
- const expectedPeerSha256 = entry.action === "conflict" ? entry.peerSha256 : entry.baseSha256;
1646
+ } else if (options.peerUrl && source) {
1207
1647
  const applied = await postPeerFileContent(
1208
1648
  options.peerUrl,
1209
1649
  entry.namespace,
1210
- entry.path,
1211
- content,
1212
- {
1213
- sha256: entry.localSha256,
1214
- mtimeMs: mtimeMs ?? 0,
1215
- ...expectedPeerSha256 ? { baseSha256: expectedPeerSha256 } : {}
1216
- },
1650
+ peerPath,
1651
+ source,
1217
1652
  resolvedToken,
1218
- fetchFn
1653
+ fetchFn,
1654
+ timeoutMs
1219
1655
  );
1220
1656
  if (applied) {
1221
1657
  if (applied === "applied") peerMutatedNamespaces.add(entry.namespace);
@@ -1227,16 +1663,16 @@ async function executeConvergeApply(options = {}) {
1227
1663
  } else {
1228
1664
  actualTransfers.failed += 1;
1229
1665
  }
1230
- } else {
1231
- actualTransfers.failed += 1;
1666
+ } finally {
1667
+ await closeSource?.();
1232
1668
  }
1233
1669
  } else if (transferType === "delete-local") {
1234
1670
  let deleted = false;
1235
1671
  const bufferedFiles = options.localFileBuffers?.get(entry.namespace);
1236
1672
  if (options.localFileBuffers) {
1237
- const current = bufferedFiles?.get(entry.path);
1238
- if (current && entry.localSha256 && createHash2("sha256").update(current).digest("hex") === entry.localSha256) {
1239
- 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);
1240
1676
  deleted = true;
1241
1677
  }
1242
1678
  } else {
@@ -1244,10 +1680,10 @@ async function executeConvergeApply(options = {}) {
1244
1680
  if (rootDir && entry.localSha256) {
1245
1681
  try {
1246
1682
  const io = await createOfflineStorageIo(rootDir);
1247
- const filePath = path2.join(rootDir, entry.path);
1248
- 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 });
1249
1685
  if (current.sha256 === entry.localSha256) {
1250
- await io.deleteFile({ root: rootDir, path: entry.path, filePath });
1686
+ await io.deleteFile({ root: rootDir, path: localPath, filePath });
1251
1687
  deleted = true;
1252
1688
  }
1253
1689
  } catch {
@@ -1261,19 +1697,20 @@ async function executeConvergeApply(options = {}) {
1261
1697
  let deleted = false;
1262
1698
  const bufferedFiles = options.peerFileBuffers?.get(entry.namespace);
1263
1699
  if (options.peerFileBuffers) {
1264
- const current = bufferedFiles?.get(entry.path);
1265
- if (current && entry.peerSha256 && createHash2("sha256").update(current).digest("hex") === entry.peerSha256) {
1266
- 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);
1267
1703
  deleted = true;
1268
1704
  }
1269
1705
  } else if (options.peerUrl && entry.peerSha256) {
1270
1706
  const deletionResult = await postPeerFileDeletion(
1271
1707
  options.peerUrl,
1272
1708
  entry.namespace,
1273
- entry.path,
1709
+ peerPath,
1274
1710
  entry.peerSha256,
1275
1711
  resolvedToken,
1276
- fetchFn
1712
+ fetchFn,
1713
+ timeoutMs
1277
1714
  );
1278
1715
  deleted = Boolean(deletionResult);
1279
1716
  if (deletionResult === "applied") peerMutatedNamespaces.add(entry.namespace);
@@ -1281,7 +1718,60 @@ async function executeConvergeApply(options = {}) {
1281
1718
  if (deleted) actualTransfers.conflictsResolved += 1;
1282
1719
  else actualTransfers.failed += 1;
1283
1720
  } else if (transferType === "suppress") {
1284
- 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;
1285
1775
  }
1286
1776
  }
1287
1777
  if (options.peerUrl && peerMutatedNamespaces.size > 0) {
@@ -1290,7 +1780,8 @@ async function executeConvergeApply(options = {}) {
1290
1780
  options.peerUrl,
1291
1781
  namespaces,
1292
1782
  resolvedToken,
1293
- fetchFn
1783
+ fetchFn,
1784
+ timeoutMs
1294
1785
  )) {
1295
1786
  actualTransfers.failed += 1;
1296
1787
  }
@@ -1309,7 +1800,7 @@ async function executeConvergeApply(options = {}) {
1309
1800
  };
1310
1801
  }
1311
1802
  async function updateCursorsForPlan(plan, options) {
1312
- const peerUrl = options.peerUrl ?? "local";
1803
+ const peerUrl = normalizeConvergePeerUrl2(options.peerUrl ?? "local");
1313
1804
  let memoryDir;
1314
1805
  if (options.cursorDir) {
1315
1806
  memoryDir = options.cursorDir;
@@ -1375,7 +1866,7 @@ function formatConvergeApplyReport(result) {
1375
1866
  lines.push(formatConvergeReport(result.plan));
1376
1867
  return lines.join("\n");
1377
1868
  }
1378
- async function cmdConverge(action, rest, json, config = parseConfig2({})) {
1869
+ async function cmdConverge(action, rest, json, config = parseConfig3({})) {
1379
1870
  if (action === "help" || action === "--help" || action === "-h" || rest.includes("--help") || rest.includes("-h")) {
1380
1871
  console.log(`Usage: remnic converge <plan|apply> [options]
1381
1872
 
@@ -1482,14 +1973,14 @@ function buildNamespacePolicyCheck(args) {
1482
1973
  import { WriteQuarantineStore as WriteQuarantineStore2 } from "@remnic/core/write-quarantine.js";
1483
1974
 
1484
1975
  // src/quarantine-cli.ts
1485
- import { basename } from "path";
1976
+ import { basename as basename2 } from "path";
1486
1977
  function renderQuarantineList(records, format) {
1487
1978
  if (format === "json") {
1488
- const summary = records.map((record) => ({
1489
- timestamp: record.timestamp,
1490
- operation: record.operation,
1491
- principal: record.principal,
1492
- 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
1493
1984
  }));
1494
1985
  return JSON.stringify(summary, null, 2);
1495
1986
  }
@@ -1498,9 +1989,9 @@ function renderQuarantineList(records, format) {
1498
1989
  }
1499
1990
  if (records.length === 0) return "No quarantined writes.";
1500
1991
  const lines = [`Quarantined writes (${records.length}):`, ""];
1501
- for (const record of records) {
1992
+ for (const record2 of records) {
1502
1993
  lines.push(
1503
- ` ${record.timestamp} ${record.operation} principal=${record.principal ?? "-"} attemptedNamespace=${record.attemptedNamespace}`
1994
+ ` ${record2.timestamp} ${record2.operation} principal=${record2.principal ?? "-"} attemptedNamespace=${record2.attemptedNamespace}`
1504
1995
  );
1505
1996
  }
1506
1997
  return lines.join("\n");
@@ -1508,10 +1999,10 @@ function renderQuarantineList(records, format) {
1508
1999
  async function replayQuarantine(opts) {
1509
2000
  const result = { replayed: 0, failures: [], deleteFailures: [] };
1510
2001
  for (const entry of await opts.store.entries()) {
1511
- const { record } = entry;
1512
- const basePayload = record.payload;
1513
- const principal = opts.principal ?? record.principal ?? void 0;
1514
- 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)}`;
1515
2006
  const request = {
1516
2007
  ...basePayload,
1517
2008
  namespace: opts.targetNamespace,
@@ -1520,10 +2011,10 @@ async function replayQuarantine(opts) {
1520
2011
  ...principal ? { authenticatedPrincipal: principal } : {}
1521
2012
  };
1522
2013
  try {
1523
- await opts.submit(record.operation, request);
2014
+ await opts.submit(record2.operation, request);
1524
2015
  } catch (err) {
1525
2016
  result.failures.push({
1526
- operation: record.operation,
2017
+ operation: record2.operation,
1527
2018
  attemptedNamespace: opts.targetNamespace,
1528
2019
  error: err instanceof Error ? err.message : String(err)
1529
2020
  });
@@ -1581,8 +2072,8 @@ function renderReplayResult(result, targetNamespace, format) {
1581
2072
  }
1582
2073
 
1583
2074
  // src/quarantine-replay.ts
1584
- import * as fs4 from "fs";
1585
- 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";
1586
2077
  import { WriteQuarantineStore } from "@remnic/core/write-quarantine.js";
1587
2078
  function valueFlag(args, flag) {
1588
2079
  const occurrences = args.filter((a) => a === flag).length;
@@ -1630,8 +2121,8 @@ async function runQuarantineReplay(rest, format, resolveConfigPath2) {
1630
2121
  let orchestrator;
1631
2122
  try {
1632
2123
  const configPath = resolveConfigPath2();
1633
- const raw = fs4.existsSync(configPath) ? JSON.parse(fs4.readFileSync(configPath, "utf8")) : {};
1634
- const config = parseConfig3(resolveRemnicConfigRecord2(raw));
2124
+ const raw = fs5.existsSync(configPath) ? JSON.parse(fs5.readFileSync(configPath, "utf8")) : {};
2125
+ const config = parseConfig4(resolveRemnicConfigRecord3(raw));
1635
2126
  orchestrator = new Orchestrator2(config);
1636
2127
  await orchestrator.initialize();
1637
2128
  await orchestrator.deferredReady;
@@ -1662,15 +2153,15 @@ async function runQuarantineReplay(rest, format, resolveConfigPath2) {
1662
2153
  }
1663
2154
 
1664
2155
  // src/offline-impression-rotation.ts
1665
- import fs5 from "fs";
1666
- 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";
1667
2158
  import { LastRecallStore } from "@remnic/core/recall-state";
1668
2159
  function parseConfigQuietly(raw) {
1669
2160
  const originalWarn = console.warn;
1670
2161
  console.warn = () => {
1671
2162
  };
1672
2163
  try {
1673
- return parseConfig4(resolveRemnicConfigRecord3(raw));
2164
+ return parseConfig5(resolveRemnicConfigRecord4(raw));
1674
2165
  } finally {
1675
2166
  console.warn = originalWarn;
1676
2167
  }
@@ -1684,7 +2175,7 @@ var OFFLINE_CONFIG_KEYS = [
1684
2175
  function pickOfflineConfigRecord(raw) {
1685
2176
  let resolved;
1686
2177
  try {
1687
- resolved = resolveRemnicConfigRecord3(raw);
2178
+ resolved = resolveRemnicConfigRecord4(raw);
1688
2179
  } catch {
1689
2180
  resolved = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
1690
2181
  }
@@ -1697,7 +2188,7 @@ function pickOfflineConfigRecord(raw) {
1697
2188
  function resolveOfflineImpressionRotation(configPath) {
1698
2189
  let raw;
1699
2190
  try {
1700
- raw = fs5.existsSync(configPath) ? JSON.parse(fs5.readFileSync(configPath, "utf8")) : {};
2191
+ raw = fs6.existsSync(configPath) ? JSON.parse(fs6.readFileSync(configPath, "utf8")) : {};
1701
2192
  } catch {
1702
2193
  throw new Error(
1703
2194
  `cannot read recall-impression rotation from ${configPath}: config file could not be read as JSON`
@@ -1955,7 +2446,7 @@ function assertBenchModuleFreshForDevelopment() {
1955
2446
  }
1956
2447
 
1957
2448
  // src/daemon-service-candidates.ts
1958
- import fs6 from "fs";
2449
+ import fs7 from "fs";
1959
2450
  import path5 from "path";
1960
2451
  var LAUNCHD_LABEL = "ai.remnic.daemon";
1961
2452
  var LEGACY_REMNIC_SERVER_LAUNCHD_LABEL = "ai.remnic.server";
@@ -1977,7 +2468,7 @@ function systemdUnitPaths(homeDir) {
1977
2468
  function anyFileExists(paths) {
1978
2469
  return paths.some((candidate) => {
1979
2470
  try {
1980
- return fs6.statSync(candidate).isFile();
2471
+ return fs7.statSync(candidate).isFile();
1981
2472
  } catch {
1982
2473
  return false;
1983
2474
  }
@@ -1989,7 +2480,7 @@ function commandNames(command) {
1989
2480
  }
1990
2481
  function isRunnableNodeScript(filePath) {
1991
2482
  try {
1992
- const text = fs6.readFileSync(filePath, "utf8").slice(0, 4096);
2483
+ const text = fs7.readFileSync(filePath, "utf8").slice(0, 4096);
1993
2484
  const firstLine = text.split(/\r?\n/, 1)[0] ?? "";
1994
2485
  if (/^#!.*\bnode\b/.test(firstLine)) return true;
1995
2486
  if (firstLine.startsWith("#!")) return false;
@@ -2002,7 +2493,7 @@ function isRunnableNodeScript(filePath) {
2002
2493
  function resolveShimNodeScript(filePath) {
2003
2494
  let text;
2004
2495
  try {
2005
- text = fs6.readFileSync(filePath, "utf8").slice(0, 16384);
2496
+ text = fs7.readFileSync(filePath, "utf8").slice(0, 16384);
2006
2497
  } catch {
2007
2498
  return void 0;
2008
2499
  }
@@ -2014,8 +2505,8 @@ function resolveShimNodeScript(filePath) {
2014
2505
  const candidate = raw.replaceAll("${basedir}", basedir).replaceAll("$basedir", basedir).replaceAll("\\ ", " ");
2015
2506
  const resolved = path5.isAbsolute(candidate) ? candidate : path5.resolve(basedir, candidate);
2016
2507
  try {
2017
- if (fs6.statSync(resolved).isFile() && isRunnableNodeScript(resolved)) {
2018
- return fs6.realpathSync(resolved);
2508
+ if (fs7.statSync(resolved).isFile() && isRunnableNodeScript(resolved)) {
2509
+ return fs7.realpathSync(resolved);
2019
2510
  }
2020
2511
  } catch {
2021
2512
  }
@@ -2023,7 +2514,7 @@ function resolveShimNodeScript(filePath) {
2023
2514
  return void 0;
2024
2515
  }
2025
2516
  function resolveRunnableNodeScript(filePath) {
2026
- const realPath = fs6.realpathSync(filePath);
2517
+ const realPath = fs7.realpathSync(filePath);
2027
2518
  if (isRunnableNodeScript(realPath)) return realPath;
2028
2519
  return resolveShimNodeScript(realPath);
2029
2520
  }
@@ -2033,9 +2524,9 @@ function findCommandOnPath(command, pathEnv = process.env.PATH ?? "") {
2033
2524
  for (const name of commandNames(command)) {
2034
2525
  const candidate = path5.join(dir, name);
2035
2526
  try {
2036
- const stat = fs6.statSync(candidate);
2527
+ const stat = fs7.statSync(candidate);
2037
2528
  if (!stat.isFile()) continue;
2038
- if (process.platform !== "win32") fs6.accessSync(candidate, fs6.constants.X_OK);
2529
+ if (process.platform !== "win32") fs7.accessSync(candidate, fs7.constants.X_OK);
2039
2530
  const runnable = resolveRunnableNodeScript(candidate);
2040
2531
  if (runnable) return runnable;
2041
2532
  } catch {
@@ -2160,6 +2651,8 @@ var BENCH_VALUE_FLAGS = Object.freeze([
2160
2651
  "--memcorrect-adapter",
2161
2652
  "--run",
2162
2653
  "--memory-dir",
2654
+ "--qmd",
2655
+ "--collection",
2163
2656
  "--users",
2164
2657
  "--epochs",
2165
2658
  "--facts-per-epoch",
@@ -2341,7 +2834,7 @@ var BENCH_ACTION_FLAGS = {
2341
2834
  legacyEqualsPrefixes: ["--baseline=", "--report="]
2342
2835
  },
2343
2836
  attribute: {
2344
- value: ["--run", "--results-dir", "--memory-dir", "--threshold"],
2837
+ value: ["--run", "--results-dir", "--memory-dir", "--threshold", "--qmd", "--collection"],
2345
2838
  boolean: ["--json", "--help", "-h"]
2346
2839
  },
2347
2840
  "drift-gen": {
@@ -2474,6 +2967,14 @@ function parseBenchResearchArgs(action, args) {
2474
2967
  throw new Error("ERROR: bench attribute requires --run <id>.");
2475
2968
  }
2476
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
+ }
2477
2978
  let seed;
2478
2979
  let out;
2479
2980
  if (action === "drift-gen") {
@@ -2502,6 +3003,8 @@ function parseBenchResearchArgs(action, args) {
2502
3003
  return {
2503
3004
  runRef,
2504
3005
  memoryDir: memoryDirRaw ? path6.resolve(expandTilde(memoryDirRaw)) : void 0,
3006
+ qmdPath: qmdPathRaw ? path6.resolve(expandTilde(qmdPathRaw)) : void 0,
3007
+ collection,
2505
3008
  users: readPositiveInteger(args, "--users"),
2506
3009
  epochs,
2507
3010
  seed,
@@ -3492,7 +3995,7 @@ function finalizeBenchStatus(filePath) {
3492
3995
  }
3493
3996
 
3494
3997
  // src/bench-fallback.ts
3495
- import fs7 from "fs";
3998
+ import fs8 from "fs";
3496
3999
  import path9 from "path";
3497
4000
  var FALLBACK_RESULTS_DIRNAME = "fallback-runs";
3498
4001
  function buildBenchRunnerArgs(parsed, benchmarkId, outputDir) {
@@ -3564,7 +4067,7 @@ function createFallbackBenchOutputDir(resultsDir, benchmarkId, pid, startedAtMs
3564
4067
  );
3565
4068
  }
3566
4069
  function resolveFallbackBenchResultPath(outputDir) {
3567
- 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();
3568
4071
  if (entries.length === 0) {
3569
4072
  throw new Error(`Fallback benchmark runner did not write a JSON result artifact in ${outputDir}`);
3570
4073
  }
@@ -3572,7 +4075,7 @@ function resolveFallbackBenchResultPath(outputDir) {
3572
4075
  }
3573
4076
 
3574
4077
  // src/openclaw-upgrade-swap.ts
3575
- import fs8 from "fs";
4078
+ import fs9 from "fs";
3576
4079
  import path10 from "path";
3577
4080
  function describeError(error) {
3578
4081
  return error instanceof Error ? error.message : String(error);
@@ -3584,7 +4087,7 @@ function createSiblingTempFilePath(targetPath, label) {
3584
4087
  function resolveAtomicWriteMode(targetPath, explicitMode) {
3585
4088
  if (explicitMode !== void 0) return explicitMode;
3586
4089
  try {
3587
- return fs8.statSync(targetPath).mode & 4095;
4090
+ return fs9.statSync(targetPath).mode & 4095;
3588
4091
  } catch (error) {
3589
4092
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
3590
4093
  return 384;
@@ -3594,8 +4097,8 @@ function resolveAtomicWriteMode(targetPath, explicitMode) {
3594
4097
  }
3595
4098
  function resolveAtomicReplacementPath(targetPath) {
3596
4099
  try {
3597
- if (fs8.lstatSync(targetPath).isSymbolicLink()) {
3598
- return fs8.realpathSync(targetPath);
4100
+ if (fs9.lstatSync(targetPath).isSymbolicLink()) {
4101
+ return fs9.realpathSync(targetPath);
3599
4102
  }
3600
4103
  } catch (error) {
3601
4104
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
@@ -3612,7 +4115,7 @@ function createSiblingSwapPath(targetDir, label) {
3612
4115
  function cleanupDisplacedDirectoryBestEffort(displacedDir, context) {
3613
4116
  if (!displacedDir) return void 0;
3614
4117
  try {
3615
- fs8.rmSync(displacedDir, { recursive: true, force: true });
4118
+ fs9.rmSync(displacedDir, { recursive: true, force: true });
3616
4119
  return void 0;
3617
4120
  } catch (error) {
3618
4121
  return `Warning: ${context}, but failed to remove the displaced plugin copy at ${displacedDir}: ${describeError(error)}`;
@@ -3620,55 +4123,55 @@ function cleanupDisplacedDirectoryBestEffort(displacedDir, context) {
3620
4123
  }
3621
4124
  function atomicWriteFileSync(targetPath, data, options = {}) {
3622
4125
  const resolvedTargetPath = resolveAtomicReplacementPath(targetPath);
3623
- fs8.mkdirSync(path10.dirname(resolvedTargetPath), { recursive: true });
4126
+ fs9.mkdirSync(path10.dirname(resolvedTargetPath), { recursive: true });
3624
4127
  const tempPath = createSiblingTempFilePath(resolvedTargetPath, "write");
3625
4128
  const mode = resolveAtomicWriteMode(resolvedTargetPath, options.mode);
3626
4129
  try {
3627
4130
  if (options.hooks?.writeTempFileSync) {
3628
4131
  options.hooks.writeTempFileSync(tempPath);
3629
4132
  } else {
3630
- fs8.writeFileSync(tempPath, data, { mode });
4133
+ fs9.writeFileSync(tempPath, data, { mode });
3631
4134
  }
3632
- fs8.chmodSync(tempPath, mode);
3633
- const renameTempFileSync = options.hooks?.renameTempFileSync ?? fs8.renameSync;
4135
+ fs9.chmodSync(tempPath, mode);
4136
+ const renameTempFileSync = options.hooks?.renameTempFileSync ?? fs9.renameSync;
3634
4137
  renameTempFileSync(tempPath, resolvedTargetPath);
3635
4138
  } catch (error) {
3636
- fs8.rmSync(tempPath, { force: true });
4139
+ fs9.rmSync(tempPath, { force: true });
3637
4140
  throw error;
3638
4141
  }
3639
4142
  }
3640
4143
  function atomicCopyFileSync(sourcePath, targetPath, options = {}) {
3641
- if (!fs8.existsSync(sourcePath)) return;
4144
+ if (!fs9.existsSync(sourcePath)) return;
3642
4145
  const resolvedTargetPath = resolveAtomicReplacementPath(targetPath);
3643
- fs8.mkdirSync(path10.dirname(resolvedTargetPath), { recursive: true });
4146
+ fs9.mkdirSync(path10.dirname(resolvedTargetPath), { recursive: true });
3644
4147
  const tempPath = createSiblingTempFilePath(resolvedTargetPath, "copy");
3645
- const mode = fs8.statSync(sourcePath).mode & 4095;
4148
+ const mode = fs9.statSync(sourcePath).mode & 4095;
3646
4149
  try {
3647
- const copyTempFileSync = options.hooks?.copyTempFileSync ?? fs8.copyFileSync;
4150
+ const copyTempFileSync = options.hooks?.copyTempFileSync ?? fs9.copyFileSync;
3648
4151
  copyTempFileSync(sourcePath, tempPath);
3649
- fs8.chmodSync(tempPath, mode);
3650
- const renameTempFileSync = options.hooks?.renameTempFileSync ?? fs8.renameSync;
4152
+ fs9.chmodSync(tempPath, mode);
4153
+ const renameTempFileSync = options.hooks?.renameTempFileSync ?? fs9.renameSync;
3651
4154
  renameTempFileSync(tempPath, resolvedTargetPath);
3652
4155
  } catch (error) {
3653
- fs8.rmSync(tempPath, { force: true });
4156
+ fs9.rmSync(tempPath, { force: true });
3654
4157
  throw error;
3655
4158
  }
3656
4159
  }
3657
4160
  function swapDirectoryWithRollback(stagedDir, targetDir, rollbackDir) {
3658
4161
  let hasRollbackCopy = false;
3659
- fs8.mkdirSync(path10.dirname(targetDir), { recursive: true });
3660
- fs8.rmSync(rollbackDir, { recursive: true, force: true });
3661
- if (fs8.existsSync(targetDir)) {
3662
- 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);
3663
4166
  hasRollbackCopy = true;
3664
4167
  }
3665
4168
  try {
3666
- fs8.renameSync(stagedDir, targetDir);
4169
+ fs9.renameSync(stagedDir, targetDir);
3667
4170
  } catch (swapError) {
3668
- fs8.rmSync(targetDir, { recursive: true, force: true });
3669
- if (hasRollbackCopy && fs8.existsSync(rollbackDir)) {
4171
+ fs9.rmSync(targetDir, { recursive: true, force: true });
4172
+ if (hasRollbackCopy && fs9.existsSync(rollbackDir)) {
3670
4173
  try {
3671
- fs8.renameSync(rollbackDir, targetDir);
4174
+ fs9.renameSync(rollbackDir, targetDir);
3672
4175
  hasRollbackCopy = false;
3673
4176
  } catch (restoreError) {
3674
4177
  throw new AggregateError(
@@ -3683,7 +4186,7 @@ function swapDirectoryWithRollback(stagedDir, targetDir, rollbackDir) {
3683
4186
  }
3684
4187
  function cleanupRollbackDirectory(rollbackDir) {
3685
4188
  if (!rollbackDir) return;
3686
- fs8.rmSync(rollbackDir, { recursive: true, force: true });
4189
+ fs9.rmSync(rollbackDir, { recursive: true, force: true });
3687
4190
  }
3688
4191
  function cleanupRollbackDirectoryBestEffort(rollbackDir) {
3689
4192
  if (!rollbackDir) return void 0;
@@ -3695,20 +4198,20 @@ function cleanupRollbackDirectoryBestEffort(rollbackDir) {
3695
4198
  }
3696
4199
  }
3697
4200
  function restoreDirectoryFromRollback(targetDir, rollbackDir) {
3698
- if (!fs8.existsSync(rollbackDir)) {
4201
+ if (!fs9.existsSync(rollbackDir)) {
3699
4202
  throw new Error(`Rollback directory is missing: ${rollbackDir}`);
3700
4203
  }
3701
- fs8.mkdirSync(path10.dirname(targetDir), { recursive: true });
3702
- 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;
3703
4206
  if (displacedDir) {
3704
- fs8.renameSync(targetDir, displacedDir);
4207
+ fs9.renameSync(targetDir, displacedDir);
3705
4208
  }
3706
4209
  try {
3707
- fs8.renameSync(rollbackDir, targetDir);
4210
+ fs9.renameSync(rollbackDir, targetDir);
3708
4211
  } catch (restoreError) {
3709
- if (displacedDir && fs8.existsSync(displacedDir)) {
4212
+ if (displacedDir && fs9.existsSync(displacedDir)) {
3710
4213
  try {
3711
- fs8.renameSync(displacedDir, targetDir);
4214
+ fs9.renameSync(displacedDir, targetDir);
3712
4215
  } catch (revertError) {
3713
4216
  throw new AggregateError(
3714
4217
  [restoreError, revertError],
@@ -3727,23 +4230,23 @@ function restoreDirectoryFromRollback(targetDir, rollbackDir) {
3727
4230
  );
3728
4231
  }
3729
4232
  function restoreDirectoryFromBackup(targetDir, backupDir) {
3730
- if (!fs8.existsSync(backupDir)) {
4233
+ if (!fs9.existsSync(backupDir)) {
3731
4234
  throw new Error(`Plugin backup directory is missing: ${backupDir}`);
3732
4235
  }
3733
- fs8.mkdirSync(path10.dirname(targetDir), { recursive: true });
4236
+ fs9.mkdirSync(path10.dirname(targetDir), { recursive: true });
3734
4237
  const stagedDir = createSiblingSwapPath(targetDir, "backup-restore");
3735
- const displacedDir = fs8.existsSync(targetDir) ? createSiblingSwapPath(targetDir, "pre-backup-restore") : void 0;
3736
- 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 });
3737
4240
  if (displacedDir) {
3738
- fs8.renameSync(targetDir, displacedDir);
4241
+ fs9.renameSync(targetDir, displacedDir);
3739
4242
  }
3740
4243
  try {
3741
- fs8.renameSync(stagedDir, targetDir);
4244
+ fs9.renameSync(stagedDir, targetDir);
3742
4245
  } catch (restoreError) {
3743
- fs8.rmSync(targetDir, { recursive: true, force: true });
3744
- if (displacedDir && fs8.existsSync(displacedDir)) {
4246
+ fs9.rmSync(targetDir, { recursive: true, force: true });
4247
+ if (displacedDir && fs9.existsSync(displacedDir)) {
3745
4248
  try {
3746
- fs8.renameSync(displacedDir, targetDir);
4249
+ fs9.renameSync(displacedDir, targetDir);
3747
4250
  } catch (revertError) {
3748
4251
  throw new AggregateError(
3749
4252
  [restoreError, revertError],
@@ -3751,7 +4254,7 @@ function restoreDirectoryFromBackup(targetDir, backupDir) {
3751
4254
  );
3752
4255
  }
3753
4256
  }
3754
- fs8.rmSync(stagedDir, { recursive: true, force: true });
4257
+ fs9.rmSync(stagedDir, { recursive: true, force: true });
3755
4258
  throw new Error(
3756
4259
  `Failed to restore the plugin backup into ${targetDir}. The durable backup remains preserved at ${backupDir}.`,
3757
4260
  { cause: restoreError }
@@ -3777,7 +4280,7 @@ function rollbackOpenclawUpgrade({
3777
4280
  let rollbackRestoreError;
3778
4281
  let pluginRestored = false;
3779
4282
  try {
3780
- if (rollbackDir && fs8.existsSync(rollbackDir)) {
4283
+ if (rollbackDir && fs9.existsSync(rollbackDir)) {
3781
4284
  const cleanupWarning = restoreDirectoryFromRollback(pluginDir, rollbackDir);
3782
4285
  notes.push(`Restored previous plugin from rollback copy at ${rollbackDir}`);
3783
4286
  if (cleanupWarning) notes.push(cleanupWarning);
@@ -3787,7 +4290,7 @@ function rollbackOpenclawUpgrade({
3787
4290
  rollbackRestoreError = error instanceof Error ? error.message : String(error);
3788
4291
  }
3789
4292
  try {
3790
- if (!pluginRestored && pluginBackupDir && fs8.existsSync(pluginBackupDir)) {
4293
+ if (!pluginRestored && pluginBackupDir && fs9.existsSync(pluginBackupDir)) {
3791
4294
  const cleanupWarning = restoreDirectoryFromBackup(pluginDir, pluginBackupDir);
3792
4295
  if (rollbackRestoreError) {
3793
4296
  notes.push(
@@ -3814,7 +4317,7 @@ function rollbackOpenclawUpgrade({
3814
4317
  notes.push("No previous plugin copy was available for automatic restore");
3815
4318
  }
3816
4319
  try {
3817
- if (configBackupPath && fs8.existsSync(configBackupPath)) {
4320
+ if (configBackupPath && fs9.existsSync(configBackupPath)) {
3818
4321
  restoreFileFromBackup(configPath, configBackupPath);
3819
4322
  notes.push(`Restored OpenClaw config from backup at ${configBackupPath}`);
3820
4323
  }
@@ -3854,7 +4357,7 @@ Run this manually when you're ready:
3854
4357
  }
3855
4358
 
3856
4359
  // src/daemon-service.ts
3857
- import fs9 from "fs";
4360
+ import fs10 from "fs";
3858
4361
  import path11 from "path";
3859
4362
  import * as childProcess from "child_process";
3860
4363
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -3866,7 +4369,7 @@ function launchdUnloadPlist(plistPath, processApi = childProcess) {
3866
4369
  processApi.execFileSync("launchctl", ["unload", plistPath], { stdio: "pipe" });
3867
4370
  }
3868
4371
  function resolveServerBinDetails(options = {}) {
3869
- const existsSync4 = options.existsSync ?? fs9.existsSync;
4372
+ const existsSync4 = options.existsSync ?? fs10.existsSync;
3870
4373
  const findCommandOnPath2 = options.findCommandOnPath ?? findCommandOnPath;
3871
4374
  const moduleDir = options.moduleDir ?? thisModuleDir;
3872
4375
  const packageResolve = options.packageResolve ?? resolveImportSpecifier;
@@ -3925,8 +4428,8 @@ function resolveServerBin(options = {}) {
3925
4428
  return resolveServerBinDetails(options).path;
3926
4429
  }
3927
4430
  function readVerifiedDaemonPid(options) {
3928
- const readFileSync4 = options.readFileSync ?? fs9.readFileSync;
3929
- const unlinkSync = options.unlinkSync ?? fs9.unlinkSync;
4431
+ const readFileSync4 = options.readFileSync ?? fs10.readFileSync;
4432
+ const unlinkSync = options.unlinkSync ?? fs10.unlinkSync;
3930
4433
  const processKill = options.processKill ?? process.kill;
3931
4434
  const platform = options.platform ?? process.platform;
3932
4435
  const execFileSync3 = options.execFileSync ?? ((command, args, execOptions) => childProcess.execFileSync(command, args, execOptions));
@@ -4026,8 +4529,8 @@ function removePidFileBestEffort(file, unlinkSync) {
4026
4529
  }
4027
4530
  }
4028
4531
  function inspectLaunchdPlist(plistPath, options = {}) {
4029
- const existsSync4 = options.existsSync ?? fs9.existsSync;
4030
- const readFileSync4 = options.readFileSync ?? fs9.readFileSync;
4532
+ const existsSync4 = options.existsSync ?? fs10.existsSync;
4533
+ const readFileSync4 = options.readFileSync ?? fs10.readFileSync;
4031
4534
  if (!existsSync4(plistPath)) {
4032
4535
  return {
4033
4536
  installed: false,
@@ -4261,7 +4764,7 @@ function stripConfigArgv(args) {
4261
4764
  }
4262
4765
 
4263
4766
  // src/import-dispatch.ts
4264
- import fs10 from "fs";
4767
+ import fs11 from "fs";
4265
4768
  import {
4266
4769
  runImporter,
4267
4770
  validateImportBatchSize,
@@ -4775,7 +5278,7 @@ async function cmdImport(rest, targetFactory, disposeTarget, ioOverrides = {}) {
4775
5278
  let materializedTarget;
4776
5279
  let materializePromise;
4777
5280
  const io = {
4778
- readFile: ioOverrides.readFile ?? (async (p) => fs10.promises.readFile(p, "utf-8")),
5281
+ readFile: ioOverrides.readFile ?? (async (p) => fs11.promises.readFile(p, "utf-8")),
4779
5282
  loadAdapter: ioOverrides.loadAdapter ?? (async (name) => (await loadImporterModule(name)).adapter),
4780
5283
  runImporter: ioOverrides.runImporter ?? runImporter,
4781
5284
  getWriteTarget: async () => {
@@ -4888,7 +5391,7 @@ async function cmdCapture(rest, io) {
4888
5391
  }
4889
5392
 
4890
5393
  // src/import-lossless-claw-cmd.ts
4891
- import fs11 from "fs";
5394
+ import fs12 from "fs";
4892
5395
  import path13 from "path";
4893
5396
  import {
4894
5397
  applyLcmSchema,
@@ -5000,15 +5503,15 @@ async function loadImportLosslessClawModule() {
5000
5503
 
5001
5504
  // src/import-lossless-claw-cmd.ts
5002
5505
  function assertDirectoryOrAbsent(p, label) {
5003
- if (fs11.existsSync(p) && !fs11.statSync(p).isDirectory()) {
5506
+ if (fs12.existsSync(p) && !fs12.statSync(p).isDirectory()) {
5004
5507
  throw new Error(`${label} is not a directory: ${p}`);
5005
5508
  }
5006
5509
  }
5007
5510
  function assertFile(p, label) {
5008
- if (!fs11.existsSync(p)) {
5511
+ if (!fs12.existsSync(p)) {
5009
5512
  throw new Error(`${label} does not exist: ${p}`);
5010
5513
  }
5011
- if (!fs11.statSync(p).isFile()) {
5514
+ if (!fs12.statSync(p).isFile()) {
5012
5515
  throw new Error(`${label} is not a file: ${p}`);
5013
5516
  }
5014
5517
  }
@@ -5040,7 +5543,7 @@ async function cmdImportLosslessClaw(argv, io, deps = {}) {
5040
5543
  try {
5041
5544
  if (parsed.dryRun) {
5042
5545
  const lcmPath = path13.join(memoryDir, "state", "lcm.sqlite");
5043
- if (fs11.existsSync(lcmPath)) {
5546
+ if (fs12.existsSync(lcmPath)) {
5044
5547
  destDb = mod.openExistingLcmDatabaseReadOnly(lcmPath);
5045
5548
  } else {
5046
5549
  destDb = mod.openInMemoryDestinationDatabase();
@@ -5178,6 +5681,8 @@ async function runBenchResearchCommand(parsed) {
5178
5681
  runRef: parsed.runRef,
5179
5682
  resultsDir: parsed.resultsDir ?? path14.join(resolveHomeDir(), ".remnic", "bench", "results"),
5180
5683
  memoryDir: parsed.memoryDir,
5684
+ qmdPath: parsed.qmdPath,
5685
+ collection: parsed.collection,
5181
5686
  threshold: parsed.threshold,
5182
5687
  json: parsed.json
5183
5688
  })
@@ -5241,7 +5746,9 @@ Commands:
5241
5746
  subsequent local artifacts carry the kappa + warning.
5242
5747
  check Legacy latency regression gate (compatibility)
5243
5748
  attribute --run <id> [--results-dir <path>] [--memory-dir <path>] [--threshold <value>]
5244
- 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
5245
5752
  drift-gen [generate|validate <dir>] [--users <n>] [--epochs <n>] [--seed <n>]
5246
5753
  [--out <dir>] [--facts-per-epoch <n>] [--drifting-ratio <r>]
5247
5754
  [--contradicted-ratio <r>]
@@ -5348,6 +5855,8 @@ Options:
5348
5855
  --json Output JSON for \`list\`
5349
5856
  --run <id> Benchmark run reference for attribute
5350
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
5351
5860
  --users <n> Synthetic user count for drift-gen
5352
5861
  --epochs <n> Synthetic timeline epochs for drift-gen
5353
5862
  --facts-per-epoch <n> Facts generated per user per epoch for drift-gen
@@ -5383,6 +5892,7 @@ Examples:
5383
5892
  remnic bench run --custom ./my-bench.yaml
5384
5893
  remnic bench procedural-ablation --out ./artifacts/procedural-ablation.json
5385
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
5386
5896
  remnic bench drift-gen generate --users 20 --epochs 10 --out ./corpus
5387
5897
  remnic bench drift-gen validate ./corpus
5388
5898
  remnic benchmark run --quick longmemeval`;
@@ -5604,7 +6114,7 @@ async function resolveAllBenchmarks() {
5604
6114
  if (packageBenchmarks) {
5605
6115
  return packageBenchmarks.filter((entry) => entry.runnerAvailable).map((entry) => entry.id);
5606
6116
  }
5607
- if (!fs12.existsSync(EVAL_RUNNER_PATH)) {
6117
+ if (!fs13.existsSync(EVAL_RUNNER_PATH)) {
5608
6118
  return [];
5609
6119
  }
5610
6120
  return BENCHMARK_CATALOG.filter((entry) => entry.category !== "ingestion").map((entry) => entry.id);
@@ -5652,7 +6162,7 @@ async function runBenchViaFallback(parsed, benchmarkId, runtimeProfile) {
5652
6162
  `Fallback benchmark runner does not support provider-backed, gateway, or thinking/timeout flags (${unsupportedOptions.join(", ")}). Build/install @remnic/bench to use those options.`
5653
6163
  );
5654
6164
  }
5655
- if (!fs12.existsSync(EVAL_RUNNER_PATH)) {
6165
+ if (!fs13.existsSync(EVAL_RUNNER_PATH)) {
5656
6166
  console.error(
5657
6167
  "Benchmark runner not found. Expected eval runner at evals/run.ts or a phase-1 @remnic/bench runtime export."
5658
6168
  );
@@ -5662,7 +6172,7 @@ async function runBenchViaFallback(parsed, benchmarkId, runtimeProfile) {
5662
6172
  path15.join(CLI_REPO_ROOT, "node_modules", ".bin", "tsx"),
5663
6173
  path15.join(CLI_REPO_ROOT, "packages", "remnic-cli", "node_modules", ".bin", "tsx")
5664
6174
  ];
5665
- const tsxCmd = tsxCandidates.find((candidate) => fs12.existsSync(candidate)) ?? "tsx";
6175
+ const tsxCmd = tsxCandidates.find((candidate) => fs13.existsSync(candidate)) ?? "tsx";
5666
6176
  const fallbackOutputDir = createFallbackBenchOutputDir(
5667
6177
  parsed.resultsDir ?? resolveBenchOutputDir(),
5668
6178
  benchmarkId,
@@ -5807,9 +6317,9 @@ var PERSONAMEM_COMPLETION_MARKER = path15.join(
5807
6317
  );
5808
6318
  function resolveRealpathWithinDataset(datasetPath, relativePath) {
5809
6319
  try {
5810
- const datasetRoot = fs12.realpathSync(datasetPath);
6320
+ const datasetRoot = fs13.realpathSync(datasetPath);
5811
6321
  const candidatePath = path15.resolve(datasetRoot, relativePath);
5812
- const candidateRealPath = fs12.realpathSync(candidatePath);
6322
+ const candidateRealPath = fs13.realpathSync(candidatePath);
5813
6323
  const relativeToRoot = path15.relative(datasetRoot, candidateRealPath);
5814
6324
  if (relativeToRoot.startsWith("..") || path15.isAbsolute(relativeToRoot)) {
5815
6325
  return null;
@@ -5868,14 +6378,14 @@ function parseCsvRows(raw) {
5868
6378
  function isPersonaMemDatasetComplete(datasetPath) {
5869
6379
  try {
5870
6380
  const completionMarkerPath = path15.join(datasetPath, PERSONAMEM_COMPLETION_MARKER);
5871
- if (fs12.statSync(completionMarkerPath).isFile()) {
6381
+ if (fs13.statSync(completionMarkerPath).isFile()) {
5872
6382
  return true;
5873
6383
  }
5874
6384
  } catch {
5875
6385
  }
5876
6386
  const datasetFile = PERSONAMEM_DATASET_FILE_CANDIDATES.find((candidate) => {
5877
6387
  try {
5878
- return fs12.statSync(path15.join(datasetPath, candidate)).isFile();
6388
+ return fs13.statSync(path15.join(datasetPath, candidate)).isFile();
5879
6389
  } catch {
5880
6390
  return false;
5881
6391
  }
@@ -5884,7 +6394,7 @@ function isPersonaMemDatasetComplete(datasetPath) {
5884
6394
  return false;
5885
6395
  }
5886
6396
  try {
5887
- const rows = parseCsvRows(fs12.readFileSync(path15.join(datasetPath, datasetFile), "utf8"));
6397
+ const rows = parseCsvRows(fs13.readFileSync(path15.join(datasetPath, datasetFile), "utf8"));
5888
6398
  if (rows.length < 2) {
5889
6399
  return false;
5890
6400
  }
@@ -5899,7 +6409,7 @@ function isPersonaMemDatasetComplete(datasetPath) {
5899
6409
  }
5900
6410
  return historyPaths.every((relativePath) => {
5901
6411
  const resolvedPath = resolveRealpathWithinDataset(datasetPath, relativePath);
5902
- return resolvedPath !== null && fs12.statSync(resolvedPath).isFile();
6412
+ return resolvedPath !== null && fs13.statSync(resolvedPath).isFile();
5903
6413
  });
5904
6414
  } catch {
5905
6415
  return false;
@@ -5907,7 +6417,7 @@ function isPersonaMemDatasetComplete(datasetPath) {
5907
6417
  }
5908
6418
  function hasDatasetFile(datasetPath, relativePath) {
5909
6419
  try {
5910
- return fs12.statSync(path15.join(datasetPath, relativePath)).isFile();
6420
+ return fs13.statSync(path15.join(datasetPath, relativePath)).isFile();
5911
6421
  } catch {
5912
6422
  return false;
5913
6423
  }
@@ -5927,10 +6437,10 @@ function memoryAgentBenchDatasetHasRecSysSamples(datasetPath) {
5927
6437
  return candidateFilenames.some((filename) => {
5928
6438
  const filePath = path15.join(datasetPath, filename);
5929
6439
  try {
5930
- if (!fs12.statSync(filePath).isFile()) {
6440
+ if (!fs13.statSync(filePath).isFile()) {
5931
6441
  return false;
5932
6442
  }
5933
- const raw = fs12.readFileSync(filePath, "utf8");
6443
+ const raw = fs13.readFileSync(filePath, "utf8");
5934
6444
  return /"source"\s*:\s*"recsys[_-]/i.test(raw);
5935
6445
  } catch {
5936
6446
  return false;
@@ -5946,7 +6456,7 @@ function isMemoryAgentBenchDatasetComplete(datasetPath) {
5946
6456
  function isDatasetDownloaded(datasetPath, benchmarkId) {
5947
6457
  let stats;
5948
6458
  try {
5949
- stats = fs12.statSync(datasetPath);
6459
+ stats = fs13.statSync(datasetPath);
5950
6460
  } catch {
5951
6461
  return false;
5952
6462
  }
@@ -5956,7 +6466,7 @@ function isDatasetDownloaded(datasetPath, benchmarkId) {
5956
6466
  const marker = DOWNLOADED_DATASET_MARKERS[benchmarkId];
5957
6467
  if (!marker) {
5958
6468
  try {
5959
- return fs12.readdirSync(datasetPath).length > 0;
6469
+ return fs13.readdirSync(datasetPath).length > 0;
5960
6470
  } catch {
5961
6471
  return false;
5962
6472
  }
@@ -5964,7 +6474,7 @@ function isDatasetDownloaded(datasetPath, benchmarkId) {
5964
6474
  if (marker.allOf) {
5965
6475
  const hasAllRequiredFiles = marker.allOf.every((name) => {
5966
6476
  try {
5967
- return fs12.statSync(path15.join(datasetPath, name)).isFile();
6477
+ return fs13.statSync(path15.join(datasetPath, name)).isFile();
5968
6478
  } catch {
5969
6479
  return false;
5970
6480
  }
@@ -5976,7 +6486,7 @@ function isDatasetDownloaded(datasetPath, benchmarkId) {
5976
6486
  if (marker.anyOf) {
5977
6487
  const hasMarkerFile = marker.anyOf.some((name) => {
5978
6488
  try {
5979
- return fs12.statSync(path15.join(datasetPath, name)).isFile();
6489
+ return fs13.statSync(path15.join(datasetPath, name)).isFile();
5980
6490
  } catch {
5981
6491
  return false;
5982
6492
  }
@@ -5994,7 +6504,7 @@ function isDatasetDownloaded(datasetPath, benchmarkId) {
5994
6504
  }
5995
6505
  if (marker.ext) {
5996
6506
  try {
5997
- return fs12.readdirSync(datasetPath).some(
6507
+ return fs13.readdirSync(datasetPath).some(
5998
6508
  (name) => name.endsWith(marker.ext) && !marker.exclude?.includes(name)
5999
6509
  );
6000
6510
  } catch {
@@ -6006,7 +6516,7 @@ function isDatasetDownloaded(datasetPath, benchmarkId) {
6006
6516
  async function launchBenchUi(resultsDir) {
6007
6517
  const benchUiDir = path15.join(CLI_REPO_ROOT, "packages", "bench-ui");
6008
6518
  const pnpmCmd = process.platform === "win32" ? "pnpm.cmd" : "pnpm";
6009
- if (!fs12.existsSync(path15.join(benchUiDir, "package.json"))) {
6519
+ if (!fs13.existsSync(path15.join(benchUiDir, "package.json"))) {
6010
6520
  console.error("ERROR: @remnic/bench-ui is not available in this checkout.");
6011
6521
  process.exit(1);
6012
6522
  }
@@ -6021,11 +6531,11 @@ async function launchBenchUi(resultsDir) {
6021
6531
  REMNIC_BENCH_RESULTS_DIR: resultsDir
6022
6532
  }
6023
6533
  });
6024
- await new Promise((resolve, reject) => {
6534
+ await new Promise((resolve2, reject) => {
6025
6535
  child.on("error", reject);
6026
6536
  child.on("close", (code, signal) => {
6027
6537
  if (code === 0 || signal === "SIGINT" || signal === "SIGTERM") {
6028
- resolve();
6538
+ resolve2();
6029
6539
  return;
6030
6540
  }
6031
6541
  reject(new Error(`bench UI exited with code ${code ?? "unknown"}`));
@@ -6044,13 +6554,13 @@ function listDownloadableBenchmarks() {
6044
6554
  }
6045
6555
  function resolveDatasetDownloadScriptPath() {
6046
6556
  const bundled = path15.join(CLI_MODULE_DIR, "assets", "download-datasets.sh");
6047
- if (fs12.existsSync(bundled)) {
6557
+ if (fs13.existsSync(bundled)) {
6048
6558
  return bundled;
6049
6559
  }
6050
6560
  return path15.join(CLI_REPO_ROOT, "evals", "scripts", "download-datasets.sh");
6051
6561
  }
6052
6562
  function isRepoCheckout() {
6053
- 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"));
6054
6564
  }
6055
6565
  function runDatasetDownloadScript(scriptPath, benchmarkId, datasetRoot, jsonMode) {
6056
6566
  const stdio = jsonMode ? ["inherit", process.stderr, "inherit"] : "inherit";
@@ -6363,8 +6873,8 @@ async function exportBenchPackageResult(parsed) {
6363
6873
  ...reportCardProvenance ? { reportCardProvenance } : {}
6364
6874
  });
6365
6875
  if (parsed.output) {
6366
- fs12.mkdirSync(path15.dirname(parsed.output), { recursive: true });
6367
- fs12.writeFileSync(parsed.output, rendered);
6876
+ fs13.mkdirSync(path15.dirname(parsed.output), { recursive: true });
6877
+ fs13.writeFileSync(parsed.output, rendered);
6368
6878
  console.log(`Exported ${summary.id} as ${parsed.format} to ${parsed.output}`);
6369
6879
  return;
6370
6880
  }
@@ -6409,7 +6919,7 @@ async function manageBenchDatasets(parsed) {
6409
6919
  process.exit(1);
6410
6920
  }
6411
6921
  const scriptPath = resolveDatasetDownloadScriptPath();
6412
- if (!fs12.existsSync(scriptPath)) {
6922
+ if (!fs13.existsSync(scriptPath)) {
6413
6923
  console.error(`ERROR: dataset download script not found: ${scriptPath}`);
6414
6924
  process.exit(1);
6415
6925
  }
@@ -6609,7 +7119,7 @@ async function calibrateBenchJudges(parsed, rawArgs) {
6609
7119
  );
6610
7120
  process.exit(1);
6611
7121
  }
6612
- const sourceResultSha256 = createHash3("sha256").update(fs12.readFileSync(latest.path)).digest("hex");
7122
+ const sourceResultSha256 = createHash4("sha256").update(fs13.readFileSync(latest.path)).digest("hex");
6613
7123
  const expandedManifestPath = expandTilde(manifestPath);
6614
7124
  if (!bench.resolveLocalLabJudgeProviderConfig) {
6615
7125
  console.error(
@@ -7024,7 +7534,7 @@ function loadPinnedLoCoMoTaskSelector(parsed) {
7024
7534
  }
7025
7535
  let decoded;
7026
7536
  try {
7027
- decoded = JSON.parse(fs12.readFileSync(parsed.taskIdsFile, "utf8"));
7537
+ decoded = JSON.parse(fs13.readFileSync(parsed.taskIdsFile, "utf8"));
7028
7538
  } catch (error) {
7029
7539
  throw new Error(
7030
7540
  `Unable to read --task-ids-file ${parsed.taskIdsFile}: ${error instanceof Error ? error.message : String(error)}`
@@ -7473,7 +7983,7 @@ function attachPreparedJudgeCalibration(result, judgeCalibration) {
7473
7983
  function hashCalibrationProviderConfig(config) {
7474
7984
  const canonicalize = (value, key = "") => {
7475
7985
  if (typeof value === "string" && /(?:api.?key|authorization|token|secret)/i.test(key)) {
7476
- return { secretSha256: createHash3("sha256").update(value).digest("hex") };
7986
+ return { secretSha256: createHash4("sha256").update(value).digest("hex") };
7477
7987
  }
7478
7988
  if (Array.isArray(value)) return value.map((item) => canonicalize(item));
7479
7989
  if (value && typeof value === "object") {
@@ -7484,7 +7994,7 @@ function hashCalibrationProviderConfig(config) {
7484
7994
  }
7485
7995
  return value;
7486
7996
  };
7487
- return createHash3("sha256").update(JSON.stringify(canonicalize(config))).digest("hex");
7997
+ return createHash4("sha256").update(JSON.stringify(canonicalize(config))).digest("hex");
7488
7998
  }
7489
7999
  function restoreOptionalEnv(key, previousValue) {
7490
8000
  if (previousValue === void 0) {
@@ -7696,7 +8206,7 @@ function resolveBenchReproDatasetDir(datasetDir) {
7696
8206
  return void 0;
7697
8207
  }
7698
8208
  try {
7699
- return fs12.realpathSync(datasetDir);
8209
+ return fs13.realpathSync(datasetDir);
7700
8210
  } catch {
7701
8211
  return datasetDir;
7702
8212
  }
@@ -7748,6 +8258,26 @@ async function writeBenchReproManifestForPackageRun(args) {
7748
8258
  console.warn(`WARNING: failed to write reproducibility manifest: ${message}`);
7749
8259
  }
7750
8260
  }
8261
+ function loadStandaloneConvergeCommandConfig() {
8262
+ const configPath = resolveConfigPath();
8263
+ const raw = fs13.existsSync(configPath) ? JSON.parse(fs13.readFileSync(configPath, "utf8")) : {};
8264
+ return parseConfig6(resolveRemnicConfigRecord5(raw));
8265
+ }
8266
+ function parseConvergePluginConfig(value) {
8267
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
8268
+ if (Object.keys(value).length === 0) return void 0;
8269
+ return parseConfig6(resolveRemnicConfigRecord5(value));
8270
+ }
8271
+ function loadConvergeCommandConfig() {
8272
+ if (readCompatEnv("REMNIC_CONFIG_PATH", "ENGRAM_CONFIG_PATH")) {
8273
+ return loadStandaloneConvergeCommandConfig();
8274
+ }
8275
+ const openclawConfig = readOpenclawConfig(resolveOpenclawConfigPath());
8276
+ const pluginEntry = resolveRemnicPluginEntry(openclawConfig);
8277
+ const pluginConfig = parseConvergePluginConfig(pluginEntry?.["config"]);
8278
+ if (pluginConfig) return pluginConfig;
8279
+ return loadStandaloneConvergeCommandConfig();
8280
+ }
7751
8281
  function resolveConfigPath(cliPath) {
7752
8282
  if (cliPath) return path15.resolve(expandTilde(cliPath));
7753
8283
  const envPath = readCompatEnv("REMNIC_CONFIG_PATH", "ENGRAM_CONFIG_PATH");
@@ -7759,13 +8289,13 @@ function resolveConfigPath(cliPath) {
7759
8289
  path15.join(resolveHomeDir(), ".config", "engram", "config.json")
7760
8290
  ];
7761
8291
  for (const candidate of candidates) {
7762
- if (fs12.existsSync(candidate)) return candidate;
8292
+ if (fs13.existsSync(candidate)) return candidate;
7763
8293
  }
7764
8294
  return path15.join(resolveHomeDir(), ".config", "remnic", "config.json");
7765
8295
  }
7766
8296
  function resolveExistingBenchRemnicConfigPath(cliPath) {
7767
8297
  const configPath = resolveConfigPath(cliPath);
7768
- if (fs12.existsSync(configPath)) {
8298
+ if (fs13.existsSync(configPath)) {
7769
8299
  return configPath;
7770
8300
  }
7771
8301
  if (cliPath) {
@@ -7775,7 +8305,7 @@ function resolveExistingBenchRemnicConfigPath(cliPath) {
7775
8305
  }
7776
8306
  function resolveExistingBenchOpenclawConfigPath(cliPath) {
7777
8307
  const configPath = resolveOpenclawConfigPath(cliPath);
7778
- if (fs12.existsSync(configPath)) {
8308
+ if (fs13.existsSync(configPath)) {
7779
8309
  return configPath;
7780
8310
  }
7781
8311
  if (cliPath) {
@@ -7882,8 +8412,8 @@ function resolveMemoryDir() {
7882
8412
  const envMemoryDir = readCompatEnv("REMNIC_MEMORY_DIR", "ENGRAM_MEMORY_DIR");
7883
8413
  if (envMemoryDir) return normalizeMemoryDirPath(envMemoryDir);
7884
8414
  const configPath = resolveConfigPath();
7885
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
7886
- const remnicCfg = resolveRemnicConfigRecord4(raw);
8415
+ const raw = fs13.existsSync(configPath) ? JSON.parse(fs13.readFileSync(configPath, "utf8")) : {};
8416
+ const remnicCfg = resolveRemnicConfigRecord5(raw);
7887
8417
  if (typeof remnicCfg.memoryDir === "string" && remnicCfg.memoryDir.length > 0) {
7888
8418
  return normalizeMemoryDirPath(remnicCfg.memoryDir);
7889
8419
  }
@@ -7891,18 +8421,18 @@ function resolveMemoryDir() {
7891
8421
  const standalonePath = path15.join(home, ".remnic", "memory");
7892
8422
  const legacyStandalonePath = path15.join(home, ".engram", "memory");
7893
8423
  const openclawPath = path15.join(home, ".openclaw", "workspace", "memory", "local");
7894
- if (fs12.existsSync(standalonePath)) return standalonePath;
7895
- if (fs12.existsSync(legacyStandalonePath)) return legacyStandalonePath;
8424
+ if (fs13.existsSync(standalonePath)) return standalonePath;
8425
+ if (fs13.existsSync(legacyStandalonePath)) return legacyStandalonePath;
7896
8426
  return openclawPath;
7897
8427
  })();
7898
8428
  const manifestPath = getManifestPath();
7899
- if (fs12.existsSync(manifestPath)) {
8429
+ if (fs13.existsSync(manifestPath)) {
7900
8430
  try {
7901
8431
  const active = getActiveSpace();
7902
8432
  if (active?.memoryDir) {
7903
8433
  const activeMemoryDir = normalizeMemoryDirPath(active.memoryDir);
7904
- if (!fs12.existsSync(activeMemoryDir)) {
7905
- fs12.mkdirSync(activeMemoryDir, { recursive: true });
8434
+ if (!fs13.existsSync(activeMemoryDir)) {
8435
+ fs13.mkdirSync(activeMemoryDir, { recursive: true });
7906
8436
  }
7907
8437
  return activeMemoryDir;
7908
8438
  }
@@ -7948,13 +8478,13 @@ function resolveOpenclawConfigPath(cliPath) {
7948
8478
  const envPath = process.env.OPENCLAW_CONFIG_PATH || process.env.OPENCLAW_ENGRAM_CONFIG_PATH;
7949
8479
  if (envPath) return path15.resolve(expandTilde(envPath));
7950
8480
  for (const candidate of DEFAULT_OPENCLAW_CONFIG_PATHS_FOR_DOCTOR) {
7951
- if (fs12.existsSync(candidate)) return candidate;
8481
+ if (fs13.existsSync(candidate)) return candidate;
7952
8482
  }
7953
8483
  return path15.join(resolveHomeDir(), ".openclaw", "openclaw.json");
7954
8484
  }
7955
8485
  function readOpenclawConfig(configPath) {
7956
- if (!fs12.existsSync(configPath)) return {};
7957
- const raw = fs12.readFileSync(configPath, "utf-8");
8486
+ if (!fs13.existsSync(configPath)) return {};
8487
+ const raw = fs13.readFileSync(configPath, "utf-8");
7958
8488
  let parsed;
7959
8489
  try {
7960
8490
  parsed = JSON.parse(raw);
@@ -8053,14 +8583,14 @@ function formatOpenclawUpgradeStamp(now = /* @__PURE__ */ new Date()) {
8053
8583
  return `${yyyy}${mm}${dd}-${hh}${min}${ss}`;
8054
8584
  }
8055
8585
  function backupPathIfPresent(sourcePath, backupPath) {
8056
- if (!fs12.existsSync(sourcePath)) return false;
8057
- fs12.mkdirSync(path15.dirname(backupPath), { recursive: true });
8058
- 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 });
8059
8589
  return true;
8060
8590
  }
8061
8591
  function assertDirectoryPathOrMissing(targetPath, label) {
8062
- if (!fs12.existsSync(targetPath)) return;
8063
- const stat = fs12.statSync(targetPath);
8592
+ if (!fs13.existsSync(targetPath)) return;
8593
+ const stat = fs13.statSync(targetPath);
8064
8594
  if (!stat.isDirectory()) {
8065
8595
  throw new Error(`${label} must be a directory when it already exists: ${targetPath}`);
8066
8596
  }
@@ -8085,7 +8615,7 @@ var PublishedOpenclawPluginInstallError = class extends Error {
8085
8615
  }
8086
8616
  };
8087
8617
  function installPublishedOpenclawPlugin(spec, pluginDir) {
8088
- const tempRoot = fs12.mkdtempSync(path15.join(os.tmpdir(), "remnic-openclaw-upgrade-"));
8618
+ const tempRoot = fs13.mkdtempSync(path15.join(os.tmpdir(), "remnic-openclaw-upgrade-"));
8089
8619
  const stagedDir = `${pluginDir}.next-${process.pid}-${Date.now()}`;
8090
8620
  const rollbackDir = `${pluginDir}.rollback-${process.pid}-${Date.now()}`;
8091
8621
  let swapRollbackDir;
@@ -8101,16 +8631,16 @@ function installPublishedOpenclawPlugin(spec, pluginDir) {
8101
8631
  throw new Error(`npm pack ${spec} did not return a tarball name`);
8102
8632
  }
8103
8633
  const unpackDir = path15.join(tempRoot, "unpacked");
8104
- fs12.mkdirSync(unpackDir, { recursive: true });
8634
+ fs13.mkdirSync(unpackDir, { recursive: true });
8105
8635
  childProcess2.execFileSync("tar", ["-xzf", path15.join(tempRoot, tarballName), "-C", unpackDir], {
8106
8636
  stdio: ["ignore", "pipe", "pipe"]
8107
8637
  });
8108
8638
  const packagedDir = path15.join(unpackDir, "package");
8109
- if (!fs12.existsSync(packagedDir)) {
8639
+ if (!fs13.existsSync(packagedDir)) {
8110
8640
  throw new Error(`npm pack ${spec} did not contain a package/ directory`);
8111
8641
  }
8112
- fs12.rmSync(stagedDir, { recursive: true, force: true });
8113
- fs12.cpSync(packagedDir, stagedDir, { recursive: true });
8642
+ fs13.rmSync(stagedDir, { recursive: true, force: true });
8643
+ fs13.cpSync(packagedDir, stagedDir, { recursive: true });
8114
8644
  childProcess2.execFileSync("npm", ["install", "--omit=dev"], {
8115
8645
  cwd: stagedDir,
8116
8646
  stdio: ["ignore", "pipe", "pipe"]
@@ -8126,7 +8656,7 @@ function installPublishedOpenclawPlugin(spec, pluginDir) {
8126
8656
  })();
8127
8657
  swapRollbackDir = swapResult.rollbackDir;
8128
8658
  const installedPackageJsonPath = path15.join(pluginDir, "package.json");
8129
- const installedPackage = fs12.existsSync(installedPackageJsonPath) ? JSON.parse(fs12.readFileSync(installedPackageJsonPath, "utf8")) : {};
8659
+ const installedPackage = fs13.existsSync(installedPackageJsonPath) ? JSON.parse(fs13.readFileSync(installedPackageJsonPath, "utf8")) : {};
8130
8660
  return {
8131
8661
  rollbackDir: swapRollbackDir,
8132
8662
  version: typeof installedPackage.version === "string" ? installedPackage.version : void 0
@@ -8141,8 +8671,8 @@ function installPublishedOpenclawPlugin(spec, pluginDir) {
8141
8671
  }
8142
8672
  );
8143
8673
  } finally {
8144
- fs12.rmSync(stagedDir, { recursive: true, force: true });
8145
- fs12.rmSync(tempRoot, { recursive: true, force: true });
8674
+ fs13.rmSync(stagedDir, { recursive: true, force: true });
8675
+ fs13.rmSync(tempRoot, { recursive: true, force: true });
8146
8676
  }
8147
8677
  }
8148
8678
  function restartOpenclawGateway() {
@@ -8161,7 +8691,7 @@ function restartOpenclawGateway() {
8161
8691
  }
8162
8692
  function cmdInit() {
8163
8693
  const configPath = path15.join(process.cwd(), "remnic.config.json");
8164
- if (fs12.existsSync(configPath)) {
8694
+ if (fs13.existsSync(configPath)) {
8165
8695
  console.log(`Config already exists: ${configPath}`);
8166
8696
  return;
8167
8697
  }
@@ -8177,7 +8707,7 @@ function cmdInit() {
8177
8707
  authToken: "${REMNIC_AUTH_TOKEN}"
8178
8708
  }
8179
8709
  };
8180
- fs12.writeFileSync(configPath, JSON.stringify(template, null, 2) + "\n");
8710
+ fs13.writeFileSync(configPath, JSON.stringify(template, null, 2) + "\n");
8181
8711
  console.log(`Created ${configPath}`);
8182
8712
  console.log("\nSet these environment variables:");
8183
8713
  console.log(" export OPENAI_API_KEY=sk-...");
@@ -8247,7 +8777,7 @@ async function cmdStatus(json) {
8247
8777
  }
8248
8778
  function oauthReadConfigRecord(configPath) {
8249
8779
  try {
8250
- const parsed = JSON.parse(fs12.readFileSync(configPath, "utf8"));
8780
+ const parsed = JSON.parse(fs13.readFileSync(configPath, "utf8"));
8251
8781
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
8252
8782
  return parsed;
8253
8783
  }
@@ -8392,7 +8922,7 @@ async function oauthPromptYesNo(question) {
8392
8922
  return false;
8393
8923
  }
8394
8924
  process.stdout.write(`${question} [y/N] `);
8395
- return new Promise((resolve) => {
8925
+ return new Promise((resolve2) => {
8396
8926
  let buffer = "";
8397
8927
  const onData = (chunk) => {
8398
8928
  const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
@@ -8401,7 +8931,7 @@ async function oauthPromptYesNo(question) {
8401
8931
  process.stdin.removeListener("data", onData);
8402
8932
  process.stdin.pause();
8403
8933
  const answer = buffer.trim().toLowerCase();
8404
- resolve(answer === "y" || answer === "yes");
8934
+ resolve2(answer === "y" || answer === "yes");
8405
8935
  }
8406
8936
  };
8407
8937
  process.stdin.resume();
@@ -8663,9 +9193,9 @@ async function cmdQuery(queryText, json, explain) {
8663
9193
  }
8664
9194
  initLogger2();
8665
9195
  const configPath = resolveConfigPath();
8666
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
8667
- const remnicCfg = resolveRemnicConfigRecord4(raw);
8668
- 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);
8669
9199
  const orchestrator = new Orchestrator3(config);
8670
9200
  await orchestrator.initialize();
8671
9201
  const service = new EngramAccessService2(orchestrator);
@@ -8834,9 +9364,9 @@ async function cmdXray(rest) {
8834
9364
  parseXrayCliOptions(rawQuery, options);
8835
9365
  initLogger2();
8836
9366
  const configPath = resolveConfigPath();
8837
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
8838
- const remnicCfg = resolveRemnicConfigRecord4(raw);
8839
- 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);
8840
9370
  const orchestrator = new Orchestrator3(config);
8841
9371
  await orchestrator.initialize();
8842
9372
  await orchestrator.deferredReady;
@@ -8857,9 +9387,9 @@ async function cmdXray(rest) {
8857
9387
  async function cmdVersions(rest) {
8858
9388
  initLogger2();
8859
9389
  const configPath = resolveConfigPath();
8860
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
8861
- const remnicCfg = resolveRemnicConfigRecord4(raw);
8862
- 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);
8863
9393
  if (!config.versioningEnabled) {
8864
9394
  console.error("Page versioning is disabled (versioningEnabled = false).");
8865
9395
  process.exit(1);
@@ -8973,9 +9503,9 @@ Options:
8973
9503
  async function cmdEnrich(rest) {
8974
9504
  initLogger2();
8975
9505
  const configPath = resolveConfigPath();
8976
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
8977
- const remnicCfg = resolveRemnicConfigRecord4(raw);
8978
- 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);
8979
9509
  const subcommand = rest[0];
8980
9510
  if (subcommand === "audit") {
8981
9511
  const memoryDir2 = expandTilde(config.memoryDir);
@@ -9218,9 +9748,9 @@ Shared with:
9218
9748
  process.exit(1);
9219
9749
  }
9220
9750
  const configPath = resolveConfigPath();
9221
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
9222
- const remnicCfg = resolveRemnicConfigRecord4(raw);
9223
- 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);
9224
9754
  const memoryDir = expandTilde(
9225
9755
  typeof memoryDirOverride === "string" && memoryDirOverride.length > 0 ? memoryDirOverride : config.memoryDir ?? resolveMemoryDir()
9226
9756
  );
@@ -9235,9 +9765,9 @@ Shared with:
9235
9765
  async function cmdExtensions(action, rest) {
9236
9766
  initLogger2();
9237
9767
  const configPath = resolveConfigPath();
9238
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
9239
- const remnicCfg = resolveRemnicConfigRecord4(raw);
9240
- 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);
9241
9771
  const root = resolveExtensionsRoot(config);
9242
9772
  const noopLog = { warn: () => {
9243
9773
  }, debug: () => {
@@ -9286,7 +9816,7 @@ Root: ${root}`);
9286
9816
  const extensions = await discoverMemoryExtensions(root, warnLog);
9287
9817
  let entries = [];
9288
9818
  try {
9289
- entries = fs12.readdirSync(root);
9819
+ entries = fs13.readdirSync(root);
9290
9820
  } catch {
9291
9821
  console.log(`Extensions root does not exist: ${root}`);
9292
9822
  process.exitCode = 0;
@@ -9297,7 +9827,7 @@ Root: ${root}`);
9297
9827
  for (const entry of entries) {
9298
9828
  const entryPath = path15.join(root, entry);
9299
9829
  try {
9300
- if (!fs12.statSync(entryPath).isDirectory()) continue;
9830
+ if (!fs13.statSync(entryPath).isDirectory()) continue;
9301
9831
  } catch {
9302
9832
  continue;
9303
9833
  }
@@ -9329,9 +9859,9 @@ Root: ${root}`);
9329
9859
  async function cmdBriefing(rest) {
9330
9860
  initLogger2();
9331
9861
  const configPath = resolveConfigPath();
9332
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
9333
- const remnicCfg = resolveRemnicConfigRecord4(raw);
9334
- 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);
9335
9865
  if (!config.briefing.enabled) {
9336
9866
  console.error("Briefing is disabled in config (briefing.enabled = false).");
9337
9867
  process.exit(1);
@@ -9409,10 +9939,10 @@ async function cmdBriefing(rest) {
9409
9939
  if (save) {
9410
9940
  try {
9411
9941
  const saveDir = resolveBriefingSaveDir(config.briefing.saveDir);
9412
- fs12.mkdirSync(saveDir, { recursive: true });
9942
+ fs13.mkdirSync(saveDir, { recursive: true });
9413
9943
  const filename = briefingFilename(new Date(result.window.to), format);
9414
9944
  const filePath = path15.join(saveDir, filename);
9415
- fs12.writeFileSync(filePath, payload + (payload.endsWith("\n") ? "" : "\n"));
9945
+ fs13.writeFileSync(filePath, payload + (payload.endsWith("\n") ? "" : "\n"));
9416
9946
  console.error(`Saved briefing: ${filePath}`);
9417
9947
  } catch (err) {
9418
9948
  console.error(`Failed to save briefing: ${err instanceof Error ? err.message : String(err)}`);
@@ -9430,7 +9960,7 @@ async function cmdDoctor() {
9430
9960
  detail: `${nodeVersion} (requires >= 22.12.0)`
9431
9961
  });
9432
9962
  const configPath = resolveConfigPath();
9433
- const configExists = fs12.existsSync(configPath);
9963
+ const configExists = fs13.existsSync(configPath);
9434
9964
  checks.push({ name: "Config file", ok: configExists, detail: configPath });
9435
9965
  let standaloneConfig;
9436
9966
  let standaloneConfigError;
@@ -9438,11 +9968,11 @@ async function cmdDoctor() {
9438
9968
  let configuredNs = { invalid: false };
9439
9969
  if (configExists) {
9440
9970
  try {
9441
- const raw = JSON.parse(fs12.readFileSync(configPath, "utf8"));
9442
- const remnicCfg = resolveRemnicConfigRecord4(raw);
9971
+ const raw = JSON.parse(fs13.readFileSync(configPath, "utf8"));
9972
+ const remnicCfg = resolveRemnicConfigRecord5(raw);
9443
9973
  standaloneOpenaiApiKeyExplicitlyFalse = isOpenaiApiKeyDisabled(remnicCfg.openaiApiKey);
9444
9974
  configuredNs = readConfiguredNamespace(remnicCfg);
9445
- standaloneConfig = parseConfig5(remnicCfg);
9975
+ standaloneConfig = parseConfig6(remnicCfg);
9446
9976
  } catch (err) {
9447
9977
  standaloneConfigError = err instanceof Error ? err.message : String(err);
9448
9978
  }
@@ -9451,10 +9981,10 @@ async function cmdDoctor() {
9451
9981
  try {
9452
9982
  memoryDir = resolveMemoryDir();
9453
9983
  } catch {
9454
- memoryDir = parseConfig5({}).memoryDir;
9984
+ memoryDir = parseConfig6({}).memoryDir;
9455
9985
  }
9456
9986
  try {
9457
- fs12.mkdirSync(memoryDir, { recursive: true });
9987
+ fs13.mkdirSync(memoryDir, { recursive: true });
9458
9988
  checks.push({ name: "Memory directory", ok: true, detail: memoryDir });
9459
9989
  } catch {
9460
9990
  checks.push({ name: "Memory directory", ok: false, detail: `cannot create ${memoryDir}` });
@@ -9483,7 +10013,7 @@ async function cmdDoctor() {
9483
10013
  });
9484
10014
  if (nsPolicyCheck) checks.push(nsPolicyCheck);
9485
10015
  const openclawConfigPath = resolveOpenclawConfigPath();
9486
- const openclawConfigExists = fs12.existsSync(openclawConfigPath);
10016
+ const openclawConfigExists = fs13.existsSync(openclawConfigPath);
9487
10017
  let openclawConfig = {};
9488
10018
  let openclawConfigValid = false;
9489
10019
  let openclawPluginModeConfigured = false;
@@ -9491,7 +10021,7 @@ async function cmdDoctor() {
9491
10021
  let activeOpenclawEntryConfig = null;
9492
10022
  if (openclawConfigExists) {
9493
10023
  try {
9494
- const parsed = JSON.parse(fs12.readFileSync(openclawConfigPath, "utf-8"));
10024
+ const parsed = JSON.parse(fs13.readFileSync(openclawConfigPath, "utf-8"));
9495
10025
  if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
9496
10026
  openclawConfig = parsed;
9497
10027
  openclawConfigValid = true;
@@ -9571,9 +10101,9 @@ async function cmdDoctor() {
9571
10101
  let memDirOk = false;
9572
10102
  let memDirDetail = `${resolvedMemDir} (not found)`;
9573
10103
  let memDirRemediation = `Run \`remnic openclaw install --memory-dir "${resolvedMemDir}"\` to create the directory.`;
9574
- if (fs12.existsSync(resolvedMemDir)) {
10104
+ if (fs13.existsSync(resolvedMemDir)) {
9575
10105
  try {
9576
- const stat = fs12.statSync(resolvedMemDir);
10106
+ const stat = fs13.statSync(resolvedMemDir);
9577
10107
  if (stat.isDirectory()) {
9578
10108
  memDirOk = true;
9579
10109
  memDirDetail = resolvedMemDir;
@@ -9715,12 +10245,12 @@ async function cmdDoctor() {
9715
10245
  }
9716
10246
  function cmdConfig() {
9717
10247
  const configPath = resolveConfigPath();
9718
- if (!fs12.existsSync(configPath)) {
10248
+ if (!fs13.existsSync(configPath)) {
9719
10249
  console.log("No config file found. Run `remnic init` to create one.");
9720
10250
  return;
9721
10251
  }
9722
10252
  console.log(`Config: ${configPath}`);
9723
- const rawConfig = fs12.readFileSync(configPath, "utf8");
10253
+ const rawConfig = fs13.readFileSync(configPath, "utf8");
9724
10254
  const redacted = rawConfig.replace(
9725
10255
  /("(?:openaiApiKey|localLlmApiKey|authToken|apiKey|remoteSearchApiKey|meilisearchApiKey|opikApiKey)"\s*:\s*")([^"]*)(")/g,
9726
10256
  "$1[REDACTED]$3"
@@ -9828,9 +10358,9 @@ async function cmdReview(action, rest) {
9828
10358
  const configPath = resolveConfigPath();
9829
10359
  let tombstonesConfig = null;
9830
10360
  try {
9831
- const rawCfg = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
9832
- const remnicCfg = resolveRemnicConfigRecord4(rawCfg);
9833
- 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);
9834
10364
  tombstonesConfig = {
9835
10365
  enabled: config.tombstonesEnabled,
9836
10366
  semanticMatch: config.tombstonesSemanticMatch,
@@ -9916,7 +10446,7 @@ async function cmdSync(action, rest, json) {
9916
10446
  }
9917
10447
  function localOfflineSourceId(memoryDir) {
9918
10448
  const host = os.hostname() || "unknown-host";
9919
- 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);
9920
10450
  return `remnic-local:${host}:${dirHash}`;
9921
10451
  }
9922
10452
  function normalizeOfflineRemoteUrl(raw) {
@@ -10280,8 +10810,8 @@ var APPEND_TOLERANT_RUNTIME_STATE_FILES = /* @__PURE__ */ new Set([
10280
10810
  function isAppendTolerantOfflineRuntimeFile(relPath) {
10281
10811
  if (!shouldPreferIncomingOfflineRuntimeFile(relPath)) return false;
10282
10812
  const parts = relPath.split("/");
10283
- const basename2 = parts[parts.length - 1] ?? "";
10284
- return APPEND_TOLERANT_RUNTIME_STATE_FILES.has(basename2);
10813
+ const basename3 = parts[parts.length - 1] ?? "";
10814
+ return APPEND_TOLERANT_RUNTIME_STATE_FILES.has(basename3);
10285
10815
  }
10286
10816
  function offlineFileContentChunkMatchesExpected(options) {
10287
10817
  const { chunk, expected, offset } = options;
@@ -10516,7 +11046,7 @@ function resolveOfflineDirectHydrationPath(memoryDir, relPath) {
10516
11046
  }
10517
11047
  return target;
10518
11048
  }
10519
- 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;
10520
11050
  async function pushOfflineFileContent(args) {
10521
11051
  if (args.readFileChunks) {
10522
11052
  return pushOfflineFileContentFromChunkReader(args);
@@ -10524,7 +11054,7 @@ async function pushOfflineFileContent(args) {
10524
11054
  let offset = 0;
10525
11055
  let finalResult = null;
10526
11056
  let remoteSatisfiedResult = null;
10527
- const hash = createHash3("sha256");
11057
+ const hash = createHash4("sha256");
10528
11058
  let bytes = 0;
10529
11059
  while (offset < args.file.bytes || args.file.bytes === 0 && offset === 0) {
10530
11060
  const chunk = await readOfflineSyncFileContentChunk({
@@ -10581,11 +11111,11 @@ async function pushOfflineFileContent(args) {
10581
11111
  }
10582
11112
  async function pushOfflineFileContentFromChunkReader(args) {
10583
11113
  const filePath = resolveOfflineDirectHydrationPath(args.memoryDir, args.file.path);
10584
- const stat = fs12.statSync(filePath);
11114
+ const stat = fs13.statSync(filePath);
10585
11115
  if (stat.mtimeMs !== args.file.mtimeMs) {
10586
11116
  throw new Error(`local file changed while pushing offline content: ${args.file.path}`);
10587
11117
  }
10588
- const hash = createHash3("sha256");
11118
+ const hash = createHash4("sha256");
10589
11119
  const chunks = args.readFileChunks({
10590
11120
  root: path15.resolve(args.memoryDir),
10591
11121
  path: args.file.path,
@@ -10848,7 +11378,7 @@ function formatMissingDecodedContentError(missing) {
10848
11378
  }
10849
11379
  async function waitForMissingOfflineContentRetry(delayMs) {
10850
11380
  if (delayMs <= 0) return;
10851
- await new Promise((resolve) => setTimeout(resolve, delayMs));
11381
+ await new Promise((resolve2) => setTimeout(resolve2, delayMs));
10852
11382
  }
10853
11383
  async function hydrateOfflineSnapshotContent(args) {
10854
11384
  const snapshot = normalizeOfflineSyncSnapshot(args.snapshot);
@@ -11013,15 +11543,15 @@ function parseOfflineIntervalMs(args) {
11013
11543
  return parsed;
11014
11544
  }
11015
11545
  function waitForOfflineInterval(ms, setCancel) {
11016
- return new Promise((resolve) => {
11546
+ return new Promise((resolve2) => {
11017
11547
  const timer = setTimeout(() => {
11018
11548
  setCancel(null);
11019
- resolve();
11549
+ resolve2();
11020
11550
  }, ms);
11021
11551
  setCancel(() => {
11022
11552
  clearTimeout(timer);
11023
11553
  setCancel(null);
11024
- resolve();
11554
+ resolve2();
11025
11555
  });
11026
11556
  });
11027
11557
  }
@@ -11072,7 +11602,7 @@ function advanceOfflineBaseFilesForSuccessfulPush(options) {
11072
11602
  return [...next.values()].sort((left, right) => left.path.localeCompare(right.path));
11073
11603
  }
11074
11604
  async function runOfflineSyncOnce(options) {
11075
- fs12.mkdirSync(options.memoryDir, { recursive: true });
11605
+ fs13.mkdirSync(options.memoryDir, { recursive: true });
11076
11606
  let activeStatePath = options.statePath;
11077
11607
  let priorState = await readOfflineSyncState(activeStatePath);
11078
11608
  let syncNamespace = options.namespace ?? priorState?.namespace;
@@ -11491,7 +12021,8 @@ async function runOfflineSyncOnce(options) {
11491
12021
  readFile: storageIo.readFile,
11492
12022
  readFileDigest: storageIo.readFileDigest,
11493
12023
  writeFile: storageIo.writeFile,
11494
- deleteFile: storageIo.deleteFile
12024
+ deleteFile: storageIo.deleteFile,
12025
+ recordDeletionRevision: storageIo.recordDeletionRevision
11495
12026
  });
11496
12027
  } catch (error) {
11497
12028
  if (!isMissingOfflineContentError(error)) {
@@ -11537,7 +12068,8 @@ async function runOfflineSyncOnce(options) {
11537
12068
  readFile: storageIo.readFile,
11538
12069
  readFileDigest: storageIo.readFileDigest,
11539
12070
  writeFile: storageIo.writeFile,
11540
- deleteFile: storageIo.deleteFile
12071
+ deleteFile: storageIo.deleteFile,
12072
+ recordDeletionRevision: storageIo.recordDeletionRevision
11541
12073
  });
11542
12074
  } catch (retryApplyError) {
11543
12075
  if (pushed || partialHydration.hydratedFiles.length > 0) {
@@ -11698,7 +12230,7 @@ Environment fallbacks:
11698
12230
  const configPath = resolveConfigPath();
11699
12231
  let config;
11700
12232
  try {
11701
- const rawConfig = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
12233
+ const rawConfig = fs13.existsSync(configPath) ? JSON.parse(fs13.readFileSync(configPath, "utf8")) : {};
11702
12234
  config = parseConfigQuietly(pickOfflineConfigRecord(rawConfig));
11703
12235
  } catch {
11704
12236
  throw new Error(
@@ -11713,7 +12245,7 @@ Environment fallbacks:
11713
12245
  const statePath = statePathExplicit ? path15.resolve(expandTilde(stateOverride)) : remoteUrl !== void 0 ? defaultOfflineSyncStatePath(memoryDir, remoteUrl, namespace) : void 0;
11714
12246
  if (action === "prepare") {
11715
12247
  if (!remoteUrl || !token || !statePath) throw new Error("offline prepare requires remote URL and token");
11716
- fs12.mkdirSync(memoryDir, { recursive: true });
12248
+ fs13.mkdirSync(memoryDir, { recursive: true });
11717
12249
  const remoteSnapshot = await fetchOfflineSnapshot({
11718
12250
  remoteUrl,
11719
12251
  token,
@@ -11750,7 +12282,8 @@ Environment fallbacks:
11750
12282
  readFile: storageIo.readFile,
11751
12283
  readFileDigest: storageIo.readFileDigest,
11752
12284
  writeFile: storageIo.writeFile,
11753
- deleteFile: storageIo.deleteFile
12285
+ deleteFile: storageIo.deleteFile,
12286
+ recordDeletionRevision: storageIo.recordDeletionRevision
11754
12287
  });
11755
12288
  const state = offlineSyncStateFromSnapshot({
11756
12289
  remoteId: remoteUrl,
@@ -11811,7 +12344,7 @@ Environment fallbacks:
11811
12344
  return;
11812
12345
  }
11813
12346
  if (action === "status") {
11814
- fs12.mkdirSync(memoryDir, { recursive: true });
12347
+ fs13.mkdirSync(memoryDir, { recursive: true });
11815
12348
  const state = statePath ? await readOfflineSyncState(statePath) : null;
11816
12349
  if (state && remoteUrl && statePath) {
11817
12350
  assertOfflineStateMatches({
@@ -11949,7 +12482,7 @@ function cmdDedup(json) {
11949
12482
  function readInstalledConnectorConfig(configPath, fallback) {
11950
12483
  if (!configPath) return fallback;
11951
12484
  try {
11952
- const parsed = JSON.parse(fs12.readFileSync(configPath, "utf8"));
12485
+ const parsed = JSON.parse(fs13.readFileSync(configPath, "utf8"));
11953
12486
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return fallback;
11954
12487
  const { token: _token, ...config } = parsed;
11955
12488
  return config;
@@ -12127,7 +12660,7 @@ async function cmdConnectors(action, rest, json) {
12127
12660
  const pub = factory();
12128
12661
  const available = await pub.isHostAvailable();
12129
12662
  const extRoot = available ? await pub.resolveExtensionRoot() : "(host not installed)";
12130
- const extensionExists = available && extRoot ? fs12.existsSync(extRoot) : false;
12663
+ const extensionExists = available && extRoot ? fs13.existsSync(extRoot) : false;
12131
12664
  publisherChecks.push({
12132
12665
  name: `Publisher: ${targetHostId}`,
12133
12666
  ok: !available || extensionExists,
@@ -12201,7 +12734,7 @@ async function cmdConnectors(action, rest, json) {
12201
12734
  let connectorsCfg;
12202
12735
  const configPath = resolveConfigPath();
12203
12736
  try {
12204
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
12737
+ const raw = fs13.existsSync(configPath) ? JSON.parse(fs13.readFileSync(configPath, "utf8")) : {};
12205
12738
  connectorsCfg = parseConfigQuietly(raw).connectors;
12206
12739
  } catch {
12207
12740
  process.stderr.write(
@@ -12277,9 +12810,9 @@ async function cmdConnectors(action, rest, json) {
12277
12810
  }
12278
12811
  initLogger2();
12279
12812
  const configPath = resolveConfigPath();
12280
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
12281
- const remnicCfg = resolveRemnicConfigRecord4(raw);
12282
- 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);
12283
12816
  const orchestrator = new Orchestrator3(config);
12284
12817
  try {
12285
12818
  await orchestrator.initialize();
@@ -12402,9 +12935,9 @@ async function cmdConnectorsMarketplace(subAction, rest, json) {
12402
12935
  console.error(`connectors marketplace: ${err instanceof Error ? err.message : String(err)}`);
12403
12936
  process.exit(1);
12404
12937
  }
12405
- const rawConfig = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
12406
- const pluginConfig = resolveRemnicConfigRecord4(rawConfig);
12407
- 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);
12408
12941
  if (subAction === "generate") {
12409
12942
  let outputDir;
12410
12943
  try {
@@ -12424,13 +12957,13 @@ async function cmdConnectorsMarketplace(subAction, rest, json) {
12424
12957
  } else if (subAction === "validate") {
12425
12958
  const targetPath = rest.filter((a) => !a.startsWith("--"))[0] ?? path15.join(process.cwd(), "marketplace.json");
12426
12959
  const resolved = path15.resolve(targetPath);
12427
- if (!fs12.existsSync(resolved)) {
12960
+ if (!fs13.existsSync(resolved)) {
12428
12961
  console.error(`File not found: ${resolved}`);
12429
12962
  process.exit(1);
12430
12963
  }
12431
12964
  let parsed;
12432
12965
  try {
12433
- parsed = JSON.parse(fs12.readFileSync(resolved, "utf8"));
12966
+ parsed = JSON.parse(fs13.readFileSync(resolved, "utf8"));
12434
12967
  } catch {
12435
12968
  console.error(`Invalid JSON in ${resolved}`);
12436
12969
  process.exit(1);
@@ -12560,7 +13093,7 @@ async function cmdSpace(action, rest, json) {
12560
13093
  console.error("Usage: remnic space push <source> <target>");
12561
13094
  process.exit(1);
12562
13095
  }
12563
- const result = pushToSpace(sourceId, targetId, { force: rest.includes("--force") });
13096
+ const result = await pushToSpace(sourceId, targetId, { force: rest.includes("--force") });
12564
13097
  if (json) {
12565
13098
  console.log(JSON.stringify(result, null, 2));
12566
13099
  } else {
@@ -12575,7 +13108,7 @@ async function cmdSpace(action, rest, json) {
12575
13108
  console.error("Usage: remnic space pull <source> <target>");
12576
13109
  process.exit(1);
12577
13110
  }
12578
- const result = pullFromSpace(sourceId, targetId, { force: rest.includes("--force") });
13111
+ const result = await pullFromSpace(sourceId, targetId, { force: rest.includes("--force") });
12579
13112
  if (json) {
12580
13113
  console.log(JSON.stringify(result, null, 2));
12581
13114
  } else {
@@ -12599,7 +13132,7 @@ async function cmdSpace(action, rest, json) {
12599
13132
  console.error("Usage: remnic space promote <source> <target>");
12600
13133
  process.exit(1);
12601
13134
  }
12602
- const result = promoteSpace(sourceId, targetId, {
13135
+ const result = await promoteSpace(sourceId, targetId, {
12603
13136
  force: rest.includes("--force"),
12604
13137
  forceOverwrite: rest.includes("--force-overwrite")
12605
13138
  });
@@ -12631,9 +13164,9 @@ async function cmdSpace(action, rest, json) {
12631
13164
  async function cmdLegacyBenchmark(action, rest, json) {
12632
13165
  initLogger2();
12633
13166
  const configPath = resolveConfigPath();
12634
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
12635
- const remnicCfg = resolveRemnicConfigRecord4(raw);
12636
- 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);
12637
13170
  const orchestrator = new Orchestrator3(config);
12638
13171
  const service = new EngramAccessService2(orchestrator);
12639
13172
  const { runBenchSuite, loadBaseline, checkRegression } = await loadBenchModule();
@@ -13034,7 +13567,7 @@ function readPid() {
13034
13567
  function inferPort() {
13035
13568
  try {
13036
13569
  const configPath = resolveConfigPath();
13037
- const raw = JSON.parse(fs12.readFileSync(configPath, "utf8"));
13570
+ const raw = JSON.parse(fs13.readFileSync(configPath, "utf8"));
13038
13571
  return raw.server?.port ?? 4318;
13039
13572
  } catch {
13040
13573
  return 4318;
@@ -13129,13 +13662,13 @@ function daemonInstall() {
13129
13662
  process.exit(1);
13130
13663
  }
13131
13664
  const vars = { HOME: home, NODE_PATH: nodePath, REMNIC_SERVER_BIN: serverBin };
13132
- fs12.mkdirSync(LOGS_DIR, { recursive: true });
13665
+ fs13.mkdirSync(LOGS_DIR, { recursive: true });
13133
13666
  if (isMacOS()) {
13134
13667
  const templatePath = path15.resolve(import.meta.dirname, "../templates/launchd/ai.remnic.daemon.plist");
13135
- const template = fs12.readFileSync(templatePath, "utf8");
13668
+ const template = fs13.readFileSync(templatePath, "utf8");
13136
13669
  const plist = renderTemplate(template, vars);
13137
- fs12.mkdirSync(path15.dirname(LAUNCHD_PLIST_PATH), { recursive: true });
13138
- fs12.writeFileSync(LAUNCHD_PLIST_PATH, plist);
13670
+ fs13.mkdirSync(path15.dirname(LAUNCHD_PLIST_PATH), { recursive: true });
13671
+ fs13.writeFileSync(LAUNCHD_PLIST_PATH, plist);
13139
13672
  try {
13140
13673
  launchdLoadPlist(LAUNCHD_PLIST_PATH);
13141
13674
  } catch (err) {
@@ -13152,10 +13685,10 @@ function daemonInstall() {
13152
13685
  console.log(` Logs: ${LOGS_DIR}/daemon.log`);
13153
13686
  } else if (isLinux()) {
13154
13687
  const templatePath = path15.resolve(import.meta.dirname, "../templates/systemd/remnic.service");
13155
- const template = fs12.readFileSync(templatePath, "utf8");
13688
+ const template = fs13.readFileSync(templatePath, "utf8");
13156
13689
  const unit = renderTemplate(template, vars);
13157
- fs12.mkdirSync(path15.dirname(SYSTEMD_UNIT_PATH), { recursive: true });
13158
- fs12.writeFileSync(SYSTEMD_UNIT_PATH, unit);
13690
+ fs13.mkdirSync(path15.dirname(SYSTEMD_UNIT_PATH), { recursive: true });
13691
+ fs13.writeFileSync(SYSTEMD_UNIT_PATH, unit);
13159
13692
  try {
13160
13693
  childProcess2.execSync("systemctl --user daemon-reload", { stdio: "pipe" });
13161
13694
  } catch (err) {
@@ -13191,7 +13724,7 @@ function daemonUninstall() {
13191
13724
  } catch {
13192
13725
  }
13193
13726
  try {
13194
- fs12.unlinkSync(plistPath);
13727
+ fs13.unlinkSync(plistPath);
13195
13728
  removed = true;
13196
13729
  console.log(`Removed launchd service: ${plistPath}`);
13197
13730
  } catch {
@@ -13211,7 +13744,7 @@ function daemonUninstall() {
13211
13744
  let removed = false;
13212
13745
  for (const unitPath of SYSTEMD_UNIT_PATHS) {
13213
13746
  try {
13214
- fs12.unlinkSync(unitPath);
13747
+ fs13.unlinkSync(unitPath);
13215
13748
  removed = true;
13216
13749
  console.log(`Removed systemd service: ${unitPath}`);
13217
13750
  } catch {
@@ -13278,13 +13811,13 @@ async function daemonStatus() {
13278
13811
  console.log(` Port: ${port}`);
13279
13812
  console.log(` Service: ${serviceInstalled ? "installed" : "not installed"}`);
13280
13813
  console.log(` Platform: ${process.platform}`);
13281
- console.log(` PID file: ${fs12.existsSync(PID_FILE) ? PID_FILE : LEGACY_PID_FILE}`);
13282
- 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}`);
13283
13816
  try {
13284
13817
  const configPath = resolveConfigPath();
13285
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
13286
- const remnicCfg = resolveRemnicConfigRecord4(raw);
13287
- 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);
13288
13821
  const extRoot = resolveExtensionsRoot(config);
13289
13822
  const noopLog = { warn: () => {
13290
13823
  }, debug: () => {
@@ -13323,9 +13856,9 @@ function daemonStart() {
13323
13856
  return;
13324
13857
  }
13325
13858
  }
13326
- fs12.mkdirSync(PID_DIR, { recursive: true });
13327
- fs12.mkdirSync(LOGS_DIR, { recursive: true });
13328
- 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");
13329
13862
  const serverBin = resolveServerBin();
13330
13863
  const isSource = serverBin.endsWith(".ts");
13331
13864
  let cmd;
@@ -13347,7 +13880,7 @@ function daemonStart() {
13347
13880
  }
13348
13881
  });
13349
13882
  child.unref();
13350
- fs12.writeFileSync(PID_FILE, String(child.pid));
13883
+ fs13.writeFileSync(PID_FILE, String(child.pid));
13351
13884
  console.log(`Started remnic server (pid ${child.pid})`);
13352
13885
  console.log(` Log: ${LOG_FILE}`);
13353
13886
  }
@@ -13381,11 +13914,11 @@ function daemonStop() {
13381
13914
  console.log("Process not found (cleaning up PID file)");
13382
13915
  }
13383
13916
  try {
13384
- fs12.unlinkSync(PID_FILE);
13917
+ fs13.unlinkSync(PID_FILE);
13385
13918
  } catch {
13386
13919
  }
13387
13920
  try {
13388
- fs12.unlinkSync(LEGACY_PID_FILE);
13921
+ fs13.unlinkSync(LEGACY_PID_FILE);
13389
13922
  } catch {
13390
13923
  }
13391
13924
  }
@@ -13477,7 +14010,7 @@ function cmdTokenRevoke(connector) {
13477
14010
  async function promptYesNo(question, defaultYes = true) {
13478
14011
  if (!process.stdin.isTTY) return defaultYes;
13479
14012
  process.stdout.write(question + " ");
13480
- return new Promise((resolve) => {
14013
+ return new Promise((resolve2) => {
13481
14014
  let buf = "";
13482
14015
  const cleanup = () => {
13483
14016
  process.stdin.removeListener("data", onData);
@@ -13487,7 +14020,7 @@ async function promptYesNo(question, defaultYes = true) {
13487
14020
  };
13488
14021
  const onEnd = () => {
13489
14022
  cleanup();
13490
- resolve(defaultYes);
14023
+ resolve2(defaultYes);
13491
14024
  };
13492
14025
  const onData = (chunk) => {
13493
14026
  buf += chunk.toString();
@@ -13496,11 +14029,11 @@ async function promptYesNo(question, defaultYes = true) {
13496
14029
  cleanup();
13497
14030
  const answer = buf.slice(0, nl).trim().toLowerCase();
13498
14031
  if (answer === "" || answer === "y" || answer === "yes") {
13499
- resolve(defaultYes || answer !== "");
14032
+ resolve2(defaultYes || answer !== "");
13500
14033
  } else if (answer === "n" || answer === "no") {
13501
- resolve(false);
14034
+ resolve2(false);
13502
14035
  } else {
13503
- resolve(defaultYes);
14036
+ resolve2(defaultYes);
13504
14037
  }
13505
14038
  }
13506
14039
  };
@@ -13513,9 +14046,9 @@ async function promptYesNo(question, defaultYes = true) {
13513
14046
  async function cmdBinary(rest) {
13514
14047
  initLogger2();
13515
14048
  const configPath = resolveConfigPath();
13516
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
13517
- const remnicCfg = resolveRemnicConfigRecord4(raw);
13518
- 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);
13519
14052
  const memoryDir = resolveMemoryDir();
13520
14053
  const blConfig = {
13521
14054
  enabled: config.binaryLifecycleEnabled,
@@ -13704,7 +14237,7 @@ async function cmdOpenclawInstall(opts) {
13704
14237
  } else if (slotIsActiveLegacy) {
13705
14238
  changes.push(` Slot left as "${REMNIC_OPENCLAW_LEGACY_PLUGIN_ID}" \u2014 re-run with --yes to activate the new entry`);
13706
14239
  }
13707
- if (!fs12.existsSync(memoryDir)) changes.push(`+ Will create memory directory: ${memoryDir}`);
14240
+ if (!fs13.existsSync(memoryDir)) changes.push(`+ Will create memory directory: ${memoryDir}`);
13708
14241
  if (hasLegacy && migrateLegacy) {
13709
14242
  changes.push(`~ Legacy '${REMNIC_OPENCLAW_LEGACY_PLUGIN_ID}' entry retained (safe to remove after verifying hooks fire)`);
13710
14243
  }
@@ -13724,8 +14257,8 @@ async function cmdOpenclawInstall(opts) {
13724
14257
  Resulting plugins.slots.memory: ${dryRunPlugins.slots?.memory ?? "(unset)"}`);
13725
14258
  return;
13726
14259
  }
13727
- if (fs12.existsSync(memoryDir)) {
13728
- const st = fs12.statSync(memoryDir);
14260
+ if (fs13.existsSync(memoryDir)) {
14261
+ const st = fs13.statSync(memoryDir);
13729
14262
  if (!st.isDirectory()) {
13730
14263
  throw new Error(
13731
14264
  `Cannot use ${memoryDir} as the memory directory \u2014 a file already exists at that path.
@@ -13733,12 +14266,12 @@ Remove it first and re-run, or choose a different path with --memory-dir.`
13733
14266
  );
13734
14267
  }
13735
14268
  } else {
13736
- fs12.mkdirSync(memoryDir, { recursive: true });
14269
+ fs13.mkdirSync(memoryDir, { recursive: true });
13737
14270
  console.log(`Created memory directory: ${memoryDir}`);
13738
14271
  }
13739
14272
  const configDir = path15.dirname(configPath);
13740
- if (!fs12.existsSync(configDir)) {
13741
- fs12.mkdirSync(configDir, { recursive: true });
14273
+ if (!fs13.existsSync(configDir)) {
14274
+ fs13.mkdirSync(configDir, { recursive: true });
13742
14275
  }
13743
14276
  atomicWriteFileSync(configPath, JSON.stringify(updatedConfig, null, 2) + "\n");
13744
14277
  console.log("\nDone! Summary of changes:");
@@ -13910,15 +14443,15 @@ async function cmdOpenclawMigrateEngram(opts) {
13910
14443
  }
13911
14444
  function createOpenclawUpgradeBackupDir() {
13912
14445
  const backupsRoot = path15.join(resolveHomeDir(), ".openclaw", "backups");
13913
- fs12.mkdirSync(backupsRoot, { recursive: true });
13914
- 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()}-`));
13915
14448
  }
13916
14449
  async function cmdTaxonomy(rest) {
13917
14450
  initLogger2();
13918
14451
  const configPath = resolveConfigPath();
13919
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
13920
- const remnicCfg = resolveRemnicConfigRecord4(raw);
13921
- 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);
13922
14455
  if (!config.taxonomyEnabled) {
13923
14456
  console.error(
13924
14457
  "Taxonomy is disabled in config (taxonomyEnabled = false). Enable it to use taxonomy commands."
@@ -13954,8 +14487,8 @@ async function cmdTaxonomy(rest) {
13954
14487
  console.log(doc);
13955
14488
  if (config.taxonomyAutoGenResolver) {
13956
14489
  const resolverPath = path15.join(config.memoryDir, ".taxonomy", "RESOLVER.md");
13957
- fs12.mkdirSync(path15.dirname(resolverPath), { recursive: true });
13958
- fs12.writeFileSync(resolverPath, doc);
14490
+ fs13.mkdirSync(path15.dirname(resolverPath), { recursive: true });
14491
+ fs13.writeFileSync(resolverPath, doc);
13959
14492
  console.error(`Written: ${resolverPath}`);
13960
14493
  }
13961
14494
  break;
@@ -14001,7 +14534,7 @@ async function cmdTaxonomy(rest) {
14001
14534
  if (config.taxonomyAutoGenResolver) {
14002
14535
  const doc = generateResolverDocument(taxonomy);
14003
14536
  const resolverPath = path15.join(config.memoryDir, ".taxonomy", "RESOLVER.md");
14004
- fs12.writeFileSync(resolverPath, doc);
14537
+ fs13.writeFileSync(resolverPath, doc);
14005
14538
  console.error(`Regenerated: ${resolverPath}`);
14006
14539
  }
14007
14540
  break;
@@ -14032,7 +14565,7 @@ async function cmdTaxonomy(rest) {
14032
14565
  if (config.taxonomyAutoGenResolver) {
14033
14566
  const doc = generateResolverDocument(taxonomy);
14034
14567
  const resolverPath = path15.join(config.memoryDir, ".taxonomy", "RESOLVER.md");
14035
- fs12.writeFileSync(resolverPath, doc);
14568
+ fs13.writeFileSync(resolverPath, doc);
14036
14569
  console.error(`Regenerated: ${resolverPath}`);
14037
14570
  }
14038
14571
  break;
@@ -14223,12 +14756,12 @@ async function runTrainingExport(args, stdout = process.stdout) {
14223
14756
  `Unknown training-export format "${args.format}". ${validList}`
14224
14757
  );
14225
14758
  }
14226
- if (!fs12.existsSync(args.memoryDir)) {
14759
+ if (!fs13.existsSync(args.memoryDir)) {
14227
14760
  throw new Error(
14228
14761
  `--memory-dir "${args.memoryDir}" does not exist. Provide the path to an existing memory directory.`
14229
14762
  );
14230
14763
  }
14231
- if (!fs12.statSync(args.memoryDir).isDirectory()) {
14764
+ if (!fs13.statSync(args.memoryDir).isDirectory()) {
14232
14765
  throw new Error(
14233
14766
  `--memory-dir "${args.memoryDir}" is not a directory. Provide the path to a memory directory, not a file.`
14234
14767
  );
@@ -14314,10 +14847,10 @@ async function runTrainingExport(args, stdout = process.stdout) {
14314
14847
  }
14315
14848
  const formatted = adapter.formatRecords(records);
14316
14849
  const outDir = path15.dirname(args.output);
14317
- fs12.mkdirSync(outDir, { recursive: true });
14850
+ fs13.mkdirSync(outDir, { recursive: true });
14318
14851
  const tmpPath = `${args.output}.tmp-${process.pid}-${Date.now()}`;
14319
- fs12.writeFileSync(tmpPath, formatted, "utf-8");
14320
- fs12.renameSync(tmpPath, args.output);
14852
+ fs13.writeFileSync(tmpPath, formatted, "utf-8");
14853
+ fs13.renameSync(tmpPath, args.output);
14321
14854
  stdout.write(
14322
14855
  `Exported ${records.length} records to ${args.output} (${adapter.name} format)
14323
14856
  `
@@ -14486,7 +15019,7 @@ async function main(argv = process.argv.slice(2)) {
14486
15019
  }
14487
15020
  }, 500);
14488
15021
  };
14489
- fs12.watch(memoryDir, { recursive: true }, (_event, filename) => {
15022
+ fs13.watch(memoryDir, { recursive: true }, (_event, filename) => {
14490
15023
  if (filename && filename.startsWith(".")) return;
14491
15024
  rebuild();
14492
15025
  });
@@ -14494,12 +15027,12 @@ async function main(argv = process.argv.slice(2)) {
14494
15027
  });
14495
15028
  } else if (subAction === "validate") {
14496
15029
  const treeDir = outputDir;
14497
- if (!fs12.existsSync(treeDir)) {
15030
+ if (!fs13.existsSync(treeDir)) {
14498
15031
  console.error(`Context tree not found at ${treeDir}. Run 'remnic tree generate' first.`);
14499
15032
  process.exit(1);
14500
15033
  }
14501
15034
  const indexPath = path15.join(treeDir, "INDEX.md");
14502
- if (!fs12.existsSync(indexPath)) {
15035
+ if (!fs13.existsSync(indexPath)) {
14503
15036
  console.error(`INDEX.md missing in ${treeDir}. Tree may be corrupt \u2014 regenerate.`);
14504
15037
  process.exit(1);
14505
15038
  }
@@ -14561,9 +15094,7 @@ Options:
14561
15094
  await cmdConverge(action, args, json);
14562
15095
  break;
14563
15096
  }
14564
- const configPath = resolveConfigPath();
14565
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
14566
- const config = parseConfig5(resolveRemnicConfigRecord4(raw));
15097
+ const config = loadConvergeCommandConfig();
14567
15098
  await cmdConverge(action, args, json, config);
14568
15099
  break;
14569
15100
  }
@@ -14683,9 +15214,9 @@ Other:
14683
15214
  let wearablesService;
14684
15215
  try {
14685
15216
  const configPath = resolveConfigPath();
14686
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
14687
- const remnicCfg = resolveRemnicConfigRecord4(raw);
14688
- 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);
14689
15220
  wearablesOrchestrator = new Orchestrator3(config);
14690
15221
  await wearablesOrchestrator.initialize();
14691
15222
  await wearablesOrchestrator.deferredReady;
@@ -14725,6 +15256,10 @@ Other:
14725
15256
  await runMeetingsBinaryCommand(rest);
14726
15257
  break;
14727
15258
  }
15259
+ case "external-wiki": {
15260
+ await runExternalWikiBinaryCommand(rest);
15261
+ break;
15262
+ }
14728
15263
  case "import": {
14729
15264
  if (rest.includes("--help") || rest.includes("-h") || rest.length === 0) {
14730
15265
  console.log(IMPORT_USAGE);
@@ -14734,9 +15269,9 @@ Other:
14734
15269
  const targetFactory = async () => {
14735
15270
  if (!orchestratorSingleton) {
14736
15271
  const configPath = resolveConfigPath();
14737
- const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
14738
- const remnicCfg = resolveRemnicConfigRecord4(raw);
14739
- 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);
14740
15275
  orchestratorSingleton = new Orchestrator3(config);
14741
15276
  await orchestratorSingleton.initialize();
14742
15277
  await orchestratorSingleton.deferredReady;
@@ -14933,6 +15468,7 @@ Usage:
14933
15468
  Retrospective meetings: list stored records, show one by id, or build
14934
15469
  (detect + fuse + store) a day's meetings from ingested audio + screen
14935
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]
14936
15472
 
14937
15473
  remnic doctor Run diagnostics
14938
15474
  remnic config Show current config
@@ -15020,8 +15556,8 @@ function waitForStreamDrain(stream) {
15020
15556
  if (!stream.writableNeedDrain) {
15021
15557
  return Promise.resolve();
15022
15558
  }
15023
- return new Promise((resolve) => {
15024
- stream.once("drain", resolve);
15559
+ return new Promise((resolve2) => {
15560
+ stream.once("drain", resolve2);
15025
15561
  });
15026
15562
  }
15027
15563
  function activeNonStdioHandleCount() {
@@ -15040,7 +15576,7 @@ async function armCliSuccessExitWatchdog() {
15040
15576
  waitForStreamDrain(process.stdout),
15041
15577
  waitForStreamDrain(process.stderr)
15042
15578
  ]),
15043
- new Promise((resolve) => setTimeout(resolve, CLI_OUTPUT_FLUSH_GRACE_MS))
15579
+ new Promise((resolve2) => setTimeout(resolve2, CLI_OUTPUT_FLUSH_GRACE_MS))
15044
15580
  ]);
15045
15581
  const watchdog = setTimeout(() => {
15046
15582
  if (activeNonStdioHandleCount() > 0) {
@@ -15101,6 +15637,7 @@ export {
15101
15637
  hydrateOfflineSnapshotContent,
15102
15638
  isOfflineMissingContentDeferrablePath,
15103
15639
  isOfflineSnapshotPostFallbackError,
15640
+ loadConvergeCommandConfig,
15104
15641
  main,
15105
15642
  offlinePartialHydrationForPaths,
15106
15643
  offlineSnapshotBasePostBody,