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