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