@remnic/cli 9.69.32 → 9.69.34
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/README.md +2 -2
- package/assets/download-datasets.sh +4 -5
- package/dist/assets/download-datasets.sh +4 -5
- package/dist/index.js +1083 -811
- package/package.json +33 -33
package/dist/index.js
CHANGED
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
isOpenaiApiKeyDisabled,
|
|
32
32
|
resolveEnvVars,
|
|
33
33
|
resolveRemnicConfigRecord as resolveRemnicConfigRecord16,
|
|
34
|
-
Orchestrator as
|
|
34
|
+
Orchestrator as Orchestrator11,
|
|
35
35
|
EngramAccessService as EngramAccessService2,
|
|
36
36
|
initLogger as initLogger5,
|
|
37
37
|
onboard,
|
|
@@ -105,7 +105,7 @@ import {
|
|
|
105
105
|
discoverMemoryExtensions,
|
|
106
106
|
resolveExtensionsRoot,
|
|
107
107
|
coerceInstallExtension,
|
|
108
|
-
StorageManager as
|
|
108
|
+
StorageManager as StorageManager4,
|
|
109
109
|
parseXrayCliOptions,
|
|
110
110
|
renderXray,
|
|
111
111
|
extractWhoKnowsRawArgs,
|
|
@@ -204,10 +204,46 @@ async function runMeetingsBinaryCommand(rest) {
|
|
|
204
204
|
// src/commands/timeline.ts
|
|
205
205
|
import fs2 from "fs";
|
|
206
206
|
import {
|
|
207
|
+
ActivityStore,
|
|
208
|
+
activityDateInTimezone,
|
|
209
|
+
listPersistedTimelineDates,
|
|
207
210
|
parseConfig as parseConfig2,
|
|
211
|
+
regenerateTimelineDay,
|
|
208
212
|
resolveRemnicConfigRecord as resolveRemnicConfigRecord2,
|
|
213
|
+
resolveTimelineLoadDates,
|
|
209
214
|
runTimelineCliCommand
|
|
210
215
|
} from "@remnic/core";
|
|
216
|
+
async function loadProductionTimelineCards(config, window, now = () => /* @__PURE__ */ new Date()) {
|
|
217
|
+
const timeline = config.activity.timeline;
|
|
218
|
+
if (!timeline.enabled) return null;
|
|
219
|
+
const timezone = config.activity.timezone;
|
|
220
|
+
const store = ActivityStore.open(config.memoryDir);
|
|
221
|
+
try {
|
|
222
|
+
const dates = resolveTimelineLoadDates({
|
|
223
|
+
window,
|
|
224
|
+
timezone,
|
|
225
|
+
today: activityDateInTimezone(now(), timezone),
|
|
226
|
+
store,
|
|
227
|
+
persistedDates: listPersistedTimelineDates(config.memoryDir)
|
|
228
|
+
});
|
|
229
|
+
const cards = [];
|
|
230
|
+
for (const date of dates) {
|
|
231
|
+
const result = await regenerateTimelineDay({
|
|
232
|
+
date,
|
|
233
|
+
timezone,
|
|
234
|
+
memoryDir: config.memoryDir,
|
|
235
|
+
store,
|
|
236
|
+
timelineEnabled: true,
|
|
237
|
+
analysis: timeline.analysis,
|
|
238
|
+
pluginConfig: config
|
|
239
|
+
});
|
|
240
|
+
cards.push(...result.cards);
|
|
241
|
+
}
|
|
242
|
+
return cards;
|
|
243
|
+
} finally {
|
|
244
|
+
store.close();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
211
247
|
async function runTimelineBinaryCommand(rest) {
|
|
212
248
|
const timelineArgs = rest.length === 0 || rest[0] === "--help" || rest[0] === "-h" ? ["help"] : rest;
|
|
213
249
|
try {
|
|
@@ -228,7 +264,13 @@ async function runTimelineBinaryCommand(rest) {
|
|
|
228
264
|
return;
|
|
229
265
|
}
|
|
230
266
|
const code = await runTimelineCliCommand(
|
|
231
|
-
{
|
|
267
|
+
{
|
|
268
|
+
cards: null,
|
|
269
|
+
qa,
|
|
270
|
+
timelineEnabled,
|
|
271
|
+
config,
|
|
272
|
+
loadCards: (window) => loadProductionTimelineCards(config, window)
|
|
273
|
+
},
|
|
232
274
|
timelineArgs,
|
|
233
275
|
{ stdout: process.stdout, stderr: process.stderr }
|
|
234
276
|
);
|
|
@@ -548,12 +590,44 @@ async function runStandupBinaryCommand(rest) {
|
|
|
548
590
|
// src/commands/journal.ts
|
|
549
591
|
import fs9 from "fs";
|
|
550
592
|
import {
|
|
593
|
+
ExtractionEngine,
|
|
594
|
+
Orchestrator as Orchestrator7,
|
|
595
|
+
activityDateInTimezone as activityDateInTimezone2,
|
|
596
|
+
commitJournalHash,
|
|
597
|
+
createJournalMemoryWriter,
|
|
598
|
+
hashJournalText,
|
|
551
599
|
journalPath,
|
|
600
|
+
journalUnchanged,
|
|
552
601
|
parseConfig as parseConfig9,
|
|
602
|
+
readJournalForDate,
|
|
603
|
+
readTimelineState,
|
|
553
604
|
resolveRemnicConfigRecord as resolveRemnicConfigRecord9,
|
|
605
|
+
runJournalReviewExtraction,
|
|
554
606
|
seedJournal,
|
|
555
|
-
|
|
607
|
+
withJournalDateLock
|
|
556
608
|
} from "@remnic/core";
|
|
609
|
+
function defaultDeps(config, storage) {
|
|
610
|
+
return {
|
|
611
|
+
readJournal: readJournalForDate,
|
|
612
|
+
async extractionDeps() {
|
|
613
|
+
if (!storage) {
|
|
614
|
+
throw new Error(
|
|
615
|
+
"journal extract requires a resolved StorageManager from the orchestrator; constructing StorageManager(memoryDir) is not permitted"
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
await storage.ensureDirectories();
|
|
619
|
+
const engine = new ExtractionEngine(config, void 0, void 0, config.gatewayConfig);
|
|
620
|
+
return {
|
|
621
|
+
extract: (turns) => engine.extract(turns),
|
|
622
|
+
writer: createJournalMemoryWriter(storage)
|
|
623
|
+
};
|
|
624
|
+
},
|
|
625
|
+
now: () => /* @__PURE__ */ new Date()
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
function createJournalCommandDeps(config, storage) {
|
|
629
|
+
return defaultDeps(config, storage);
|
|
630
|
+
}
|
|
557
631
|
function takeFlag4(rest, name) {
|
|
558
632
|
const index = rest.indexOf(name);
|
|
559
633
|
if (index < 0) return void 0;
|
|
@@ -562,26 +636,156 @@ function takeFlag4(rest, name) {
|
|
|
562
636
|
return value;
|
|
563
637
|
}
|
|
564
638
|
function journalHelp() {
|
|
565
|
-
return `Usage: remnic journal <show|edit-path|seed> [--date YYYY-MM-DD] [--force]
|
|
639
|
+
return `Usage: remnic journal <show|edit-path|seed|extract> [--date YYYY-MM-DD] [--force]
|
|
566
640
|
|
|
567
|
-
show Print the journal
|
|
568
|
-
|
|
569
|
-
|
|
641
|
+
show Print the journal for the date (default: today in
|
|
642
|
+
activity.timezone). Vault mode prints a provenance header
|
|
643
|
+
naming the vault note first.
|
|
644
|
+
edit-path Print the journal file path (vault mode: the vault note path).
|
|
645
|
+
seed Write the file only if it is absent (memoryDir mode only).
|
|
646
|
+
extract Run the review-only extraction pass (requires
|
|
647
|
+
activity.timeline.journal.extractionMode "review").
|
|
648
|
+
Candidates land pending_review only \u2014 never auto-approved;
|
|
649
|
+
an unchanged day is hash-skipped.
|
|
570
650
|
`;
|
|
571
651
|
}
|
|
572
|
-
function
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
652
|
+
async function runJournalCommand(config, rest, io, deps = defaultDeps(config)) {
|
|
653
|
+
if (rest.length === 0 || rest[0] === "--help" || rest[0] === "-h" || rest[0] === "help") {
|
|
654
|
+
io.out(journalHelp().trimEnd());
|
|
655
|
+
return 0;
|
|
656
|
+
}
|
|
657
|
+
const action = rest[0];
|
|
658
|
+
const journalConfig = config.activity.timeline.journal;
|
|
659
|
+
if (!journalConfig.enabled) {
|
|
660
|
+
io.err("journal: timeline.journal.enabled is false \u2014 enable the journal first.");
|
|
661
|
+
return 1;
|
|
662
|
+
}
|
|
663
|
+
const date = takeFlag4(rest, "--date") ?? activityDateInTimezone2(deps.now(), config.activity.timezone);
|
|
664
|
+
const force = rest.includes("--force");
|
|
665
|
+
const vaultMode = journalConfig.source === "vault";
|
|
666
|
+
if (action === "edit-path") {
|
|
667
|
+
if (vaultMode) {
|
|
668
|
+
const read = deps.readJournal({
|
|
669
|
+
vault: config.activity.timeline.vault,
|
|
670
|
+
date,
|
|
671
|
+
timezone: config.activity.timezone
|
|
672
|
+
});
|
|
673
|
+
io.out(read.filePath);
|
|
674
|
+
return 0;
|
|
675
|
+
}
|
|
676
|
+
io.out(journalPath(config.memoryDir, date));
|
|
677
|
+
return 0;
|
|
678
|
+
}
|
|
679
|
+
if (action === "show") {
|
|
680
|
+
if (vaultMode) {
|
|
681
|
+
const read = deps.readJournal({
|
|
682
|
+
vault: config.activity.timeline.vault,
|
|
683
|
+
date,
|
|
684
|
+
timezone: config.activity.timezone
|
|
685
|
+
});
|
|
686
|
+
if (!read.ok) {
|
|
687
|
+
io.err(`journal: cannot read the vault note (${read.reason}): ${read.filePath}`);
|
|
688
|
+
return 1;
|
|
689
|
+
}
|
|
690
|
+
if (!read.exists) {
|
|
691
|
+
io.out(`exists:false (${read.reason})`);
|
|
692
|
+
return 0;
|
|
693
|
+
}
|
|
694
|
+
io.out(`# journal source: ${read.filePath} :: ${read.heading}`);
|
|
695
|
+
io.out(read.text);
|
|
696
|
+
return 0;
|
|
697
|
+
}
|
|
698
|
+
const filePath = journalPath(config.memoryDir, date);
|
|
699
|
+
if (!fs9.existsSync(filePath)) {
|
|
700
|
+
io.err(`journal: no file at ${filePath}. Run remnic journal seed --date ${date}.`);
|
|
701
|
+
return 1;
|
|
702
|
+
}
|
|
703
|
+
io.out(fs9.readFileSync(filePath, "utf8").trimEnd());
|
|
704
|
+
return 0;
|
|
705
|
+
}
|
|
706
|
+
if (action === "seed") {
|
|
707
|
+
if (vaultMode) {
|
|
708
|
+
io.err(
|
|
709
|
+
'journal: seed is not available when activity.timeline.journal.source is "vault" \u2014 the vault daily note owns the journal section and Remnic never writes to it. Create the note/section in your vault (or your vault note template) instead.'
|
|
710
|
+
);
|
|
711
|
+
return 1;
|
|
712
|
+
}
|
|
713
|
+
const result = seedJournal({ memoryDir: config.memoryDir, date, force });
|
|
714
|
+
io.out(result.wrote ? `wrote ${result.path}` : `unchanged ${result.path}`);
|
|
715
|
+
return 0;
|
|
716
|
+
}
|
|
717
|
+
if (action === "extract") {
|
|
718
|
+
if (journalConfig.extractionMode !== "review") {
|
|
719
|
+
io.err(
|
|
720
|
+
`journal: extract requires activity.timeline.journal.extractionMode "review" (currently "${journalConfig.extractionMode}") \u2014 extraction is opt-in and review-only by design.`
|
|
721
|
+
);
|
|
722
|
+
return 1;
|
|
723
|
+
}
|
|
724
|
+
let text;
|
|
725
|
+
if (vaultMode) {
|
|
726
|
+
const read = deps.readJournal({
|
|
727
|
+
vault: config.activity.timeline.vault,
|
|
728
|
+
date,
|
|
729
|
+
timezone: config.activity.timezone
|
|
730
|
+
});
|
|
731
|
+
if (!read.ok) {
|
|
732
|
+
io.err(`journal: cannot read the vault note (${read.reason}): ${read.filePath}`);
|
|
733
|
+
return 1;
|
|
734
|
+
}
|
|
735
|
+
if (!read.exists) {
|
|
736
|
+
io.out(`no journal ${date} (${read.reason})`);
|
|
737
|
+
return 0;
|
|
738
|
+
}
|
|
739
|
+
text = read.text;
|
|
740
|
+
} else {
|
|
741
|
+
const filePath = journalPath(config.memoryDir, date);
|
|
742
|
+
if (!fs9.existsSync(filePath)) {
|
|
743
|
+
io.out(`no journal ${date} (missing_file)`);
|
|
744
|
+
return 0;
|
|
745
|
+
}
|
|
746
|
+
text = fs9.readFileSync(filePath, "utf8");
|
|
747
|
+
}
|
|
748
|
+
return withJournalDateLock(config.memoryDir, date, async () => {
|
|
749
|
+
const state = readTimelineState(config.memoryDir);
|
|
750
|
+
if (journalUnchanged(state, date, text)) {
|
|
751
|
+
io.out(`unchanged ${date} (hash-skip)`);
|
|
752
|
+
return 0;
|
|
753
|
+
}
|
|
754
|
+
const result = await runJournalReviewExtraction({
|
|
755
|
+
date,
|
|
756
|
+
journalText: text,
|
|
757
|
+
source: vaultMode ? "vault" : "memoryDir",
|
|
758
|
+
journalConfig,
|
|
759
|
+
deps: await deps.extractionDeps()
|
|
760
|
+
});
|
|
761
|
+
if (!result.completed) {
|
|
762
|
+
io.err(`journal: extraction did not complete for ${date} \u2014 the day retries on the next run.`);
|
|
763
|
+
return 1;
|
|
764
|
+
}
|
|
765
|
+
await commitJournalHash(config.memoryDir, date, hashJournalText(text));
|
|
766
|
+
io.out(`pending_review: ${result.pendingReview}`);
|
|
767
|
+
io.out(`rejected_by_judge: ${result.rejectedByJudge}`);
|
|
768
|
+
io.out(`skipped: ${result.skipped}`);
|
|
769
|
+
for (const warning of result.warnings) {
|
|
770
|
+
io.err(`journal: ${warning}`);
|
|
771
|
+
}
|
|
772
|
+
return 0;
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
io.err(`journal: unknown action "${action}".`);
|
|
776
|
+
io.err(journalHelp().trimEnd());
|
|
777
|
+
return 1;
|
|
576
778
|
}
|
|
577
779
|
async function runJournalBinaryCommand(rest) {
|
|
578
780
|
if (rest.length === 0 || rest[0] === "--help" || rest[0] === "-h" || rest[0] === "help") {
|
|
579
|
-
console.log(journalHelp());
|
|
781
|
+
console.log(journalHelp().trimEnd());
|
|
580
782
|
return;
|
|
581
783
|
}
|
|
582
|
-
let
|
|
784
|
+
let config;
|
|
583
785
|
try {
|
|
584
|
-
|
|
786
|
+
const configPath = resolveConfigPath();
|
|
787
|
+
const raw = fs9.existsSync(configPath) ? JSON.parse(fs9.readFileSync(configPath, "utf8")) : {};
|
|
788
|
+
config = parseConfig9(resolveRemnicConfigRecord9(raw));
|
|
585
789
|
} catch {
|
|
586
790
|
console.error(
|
|
587
791
|
"journal: failed to load the Remnic config \u2014 run `remnic doctor` and check the config file for errors"
|
|
@@ -589,35 +793,32 @@ async function runJournalBinaryCommand(rest) {
|
|
|
589
793
|
process.exitCode = 1;
|
|
590
794
|
return;
|
|
591
795
|
}
|
|
796
|
+
const action = rest[0];
|
|
797
|
+
let orchestrator;
|
|
592
798
|
try {
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
}
|
|
601
|
-
if (action === "show") {
|
|
602
|
-
if (!fs9.existsSync(filePath)) {
|
|
603
|
-
console.error(`journal: no file at ${filePath}. Run remnic journal seed --date ${date}.`);
|
|
604
|
-
process.exitCode = 1;
|
|
605
|
-
return;
|
|
606
|
-
}
|
|
607
|
-
process.stdout.write(fs9.readFileSync(filePath, "utf8"));
|
|
608
|
-
return;
|
|
609
|
-
}
|
|
610
|
-
if (action === "seed") {
|
|
611
|
-
const result = seedJournal({ memoryDir, date, force });
|
|
612
|
-
console.log(result.wrote ? `wrote ${result.path}` : `unchanged ${result.path}`);
|
|
613
|
-
return;
|
|
799
|
+
let deps;
|
|
800
|
+
if (action === "extract") {
|
|
801
|
+
orchestrator = new Orchestrator7(config);
|
|
802
|
+
await orchestrator.initialize();
|
|
803
|
+
await orchestrator.deferredReady;
|
|
804
|
+
const storage = await orchestrator.getStorageForNamespace(config.defaultNamespace);
|
|
805
|
+
deps = createJournalCommandDeps(config, storage);
|
|
614
806
|
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
807
|
+
const code = await runJournalCommand(
|
|
808
|
+
config,
|
|
809
|
+
rest,
|
|
810
|
+
{
|
|
811
|
+
out: (line) => console.log(line),
|
|
812
|
+
err: (line) => console.error(line)
|
|
813
|
+
},
|
|
814
|
+
deps ?? defaultDeps(config)
|
|
815
|
+
);
|
|
816
|
+
if (code !== 0) process.exitCode = code;
|
|
618
817
|
} catch (err) {
|
|
619
818
|
console.error(err instanceof Error ? err.message : String(err));
|
|
620
819
|
process.exitCode = 1;
|
|
820
|
+
} finally {
|
|
821
|
+
await orchestrator?.destroy();
|
|
621
822
|
}
|
|
622
823
|
}
|
|
623
824
|
|
|
@@ -990,7 +1191,7 @@ async function runExternalWikiBinaryCommand(rest) {
|
|
|
990
1191
|
// src/commands/procedural.ts
|
|
991
1192
|
import fs13 from "fs";
|
|
992
1193
|
import {
|
|
993
|
-
StorageManager,
|
|
1194
|
+
StorageManager as StorageManager2,
|
|
994
1195
|
computeProcedureStats,
|
|
995
1196
|
formatProcedureStatsText,
|
|
996
1197
|
initLogger,
|
|
@@ -1075,7 +1276,7 @@ Shared with:
|
|
|
1075
1276
|
const memoryDir = expandTilde(
|
|
1076
1277
|
typeof memoryDirOverride === "string" && memoryDirOverride.length > 0 ? memoryDirOverride : config.memoryDir ?? resolveMemoryDir()
|
|
1077
1278
|
);
|
|
1078
|
-
const storage = new
|
|
1279
|
+
const storage = new StorageManager2(memoryDir);
|
|
1079
1280
|
if (subcommand === "maintain") {
|
|
1080
1281
|
const report2 = await runProcedureLibraryMaintenance({
|
|
1081
1282
|
memoryDir,
|
|
@@ -1128,7 +1329,7 @@ function formatProcedureMaintenanceText(report) {
|
|
|
1128
1329
|
// src/commands/drift.ts
|
|
1129
1330
|
import fs14 from "fs";
|
|
1130
1331
|
import {
|
|
1131
|
-
Orchestrator as
|
|
1332
|
+
Orchestrator as Orchestrator8,
|
|
1132
1333
|
initLogger as initLogger2,
|
|
1133
1334
|
parseConfig as parseConfig12,
|
|
1134
1335
|
resolveRemnicConfigRecord as resolveRemnicConfigRecord12,
|
|
@@ -1197,7 +1398,7 @@ Resolve a drifted item with the existing review surface:
|
|
|
1197
1398
|
const memoryDir = expandTilde(
|
|
1198
1399
|
memoryDirOverridden ? memoryDirOverride : config.memoryDir ?? resolveMemoryDir()
|
|
1199
1400
|
);
|
|
1200
|
-
const orchestrator = new
|
|
1401
|
+
const orchestrator = new Orchestrator8(
|
|
1201
1402
|
memoryDirOverridden ? { ...config, memoryDir } : config
|
|
1202
1403
|
);
|
|
1203
1404
|
await orchestrator.initialize();
|
|
@@ -1403,418 +1604,205 @@ async function loadWecloneExportModule() {
|
|
|
1403
1604
|
}
|
|
1404
1605
|
|
|
1405
1606
|
// src/converge.ts
|
|
1406
|
-
import * as fs17 from "fs";
|
|
1407
1607
|
import { createHash as createHash3 } from "crypto";
|
|
1408
|
-
import * as
|
|
1608
|
+
import * as fs18 from "fs";
|
|
1609
|
+
import * as path3 from "path";
|
|
1409
1610
|
import {
|
|
1410
1611
|
CONVERGE_CONFLICT_POLICIES,
|
|
1411
1612
|
DEFAULT_CONVERGE_CONFLICT_POLICY,
|
|
1412
|
-
|
|
1413
|
-
envConvergePeerRequestTimeoutMs as envConvergePeerRequestTimeoutMs2,
|
|
1414
|
-
normalizeConvergePeerRequestTimeoutMs,
|
|
1415
|
-
buildOfflineSyncSnapshotFromBase,
|
|
1613
|
+
OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES as OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES3,
|
|
1416
1614
|
applyOfflineSyncFileContentChunk,
|
|
1615
|
+
buildOfflineSyncSnapshotFromBase,
|
|
1616
|
+
envConvergePeerRequestTimeoutMs as envConvergePeerRequestTimeoutMs2,
|
|
1417
1617
|
isInternalRemnicStatePath as isInternalRemnicStatePath3,
|
|
1418
|
-
|
|
1618
|
+
normalizeConvergePeerRequestTimeoutMs,
|
|
1619
|
+
parseConfig as parseConfig13
|
|
1419
1620
|
} from "@remnic/core";
|
|
1420
|
-
import { parseFrontmatter } from "@remnic/core/storage.js";
|
|
1421
1621
|
import { resolveCorpusNamespaceRoots } from "@remnic/core/corpus-watermark.js";
|
|
1422
1622
|
import { listNamespaces } from "@remnic/core/namespaces/migrate.js";
|
|
1423
1623
|
import {
|
|
1424
|
-
|
|
1425
|
-
} from "@remnic/core/reconcile/plan.js";
|
|
1426
|
-
import {
|
|
1624
|
+
convergeIdentityCachePath,
|
|
1427
1625
|
defaultConvergeCursorPath,
|
|
1428
1626
|
deriveConvergeCursorBase,
|
|
1627
|
+
normalizeConvergePeerUrl as normalizeConvergePeerUrl2,
|
|
1429
1628
|
readConvergeCursor,
|
|
1430
|
-
writeConvergeCursor
|
|
1431
|
-
normalizeConvergePeerUrl as normalizeConvergePeerUrl2
|
|
1629
|
+
writeConvergeCursor
|
|
1432
1630
|
} from "@remnic/core/reconcile/cursor.js";
|
|
1433
1631
|
import {
|
|
1434
1632
|
buildReconcileManifest,
|
|
1435
1633
|
collapseActiveFactDuplicates
|
|
1436
1634
|
} from "@remnic/core/reconcile/manifest.js";
|
|
1437
|
-
|
|
1438
|
-
// src/offline-storage-io.ts
|
|
1439
|
-
import { createDecipheriv, createHash } from "crypto";
|
|
1440
|
-
import fs15 from "fs";
|
|
1441
|
-
import { lstat, mkdtemp, readdir, rm } from "fs/promises";
|
|
1442
|
-
import path from "path";
|
|
1443
1635
|
import {
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
} from "@remnic/core";
|
|
1448
|
-
|
|
1636
|
+
planReconciliation
|
|
1637
|
+
} from "@remnic/core/reconcile/plan.js";
|
|
1638
|
+
import { resolveAgentAccessAuthToken } from "@remnic/core/resolve-auth-token.js";
|
|
1639
|
+
import { parseFrontmatter } from "@remnic/core/storage.js";
|
|
1640
|
+
|
|
1641
|
+
// src/converge-identity-cache.ts
|
|
1642
|
+
import * as fs15 from "fs";
|
|
1643
|
+
import * as path from "path";
|
|
1449
1644
|
import {
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
isEncryptedFile,
|
|
1463
|
-
keyring,
|
|
1464
|
-
readHeader,
|
|
1465
|
-
secureStoreDir
|
|
1466
|
-
} from "@remnic/core/secure-store";
|
|
1467
|
-
var OFFLINE_SYNC_EXCLUSION_CONCURRENCY = 16;
|
|
1468
|
-
function resolveOfflineDirectHydrationPath(memoryDir, relPath) {
|
|
1469
|
-
const base = path.resolve(memoryDir);
|
|
1470
|
-
const target = path.resolve(base, relPath);
|
|
1471
|
-
const relative = path.relative(base, target);
|
|
1472
|
-
if (relative === "" || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
1473
|
-
throw new Error(`offline sync direct hydration path escapes memory dir: ${relPath}`);
|
|
1474
|
-
}
|
|
1475
|
-
return target;
|
|
1476
|
-
}
|
|
1477
|
-
async function filterOfflineSyncBaseFiles(memoryDir, files, excludeFile) {
|
|
1478
|
-
const excluded = new Array(files.length);
|
|
1479
|
-
for (let offset = 0; offset < files.length; offset += OFFLINE_SYNC_EXCLUSION_CONCURRENCY) {
|
|
1480
|
-
await Promise.all(
|
|
1481
|
-
files.slice(offset, offset + OFFLINE_SYNC_EXCLUSION_CONCURRENCY).map(async (file, index) => {
|
|
1482
|
-
const filePath = resolveOfflineDirectHydrationPath(memoryDir, file.path);
|
|
1483
|
-
excluded[offset + index] = await excludeFile({ root: memoryDir, path: file.path, filePath });
|
|
1484
|
-
})
|
|
1485
|
-
);
|
|
1645
|
+
citationTemplateFingerprint,
|
|
1646
|
+
isReconcileMemoryIdentity
|
|
1647
|
+
} from "@remnic/core/reconcile/manifest.js";
|
|
1648
|
+
var cacheWriteLocks = /* @__PURE__ */ new Map();
|
|
1649
|
+
async function withCacheWriteLock(cachePath, write) {
|
|
1650
|
+
const previous = cacheWriteLocks.get(cachePath) ?? Promise.resolve();
|
|
1651
|
+
const next = previous.then(write, write);
|
|
1652
|
+
cacheWriteLocks.set(cachePath, next);
|
|
1653
|
+
try {
|
|
1654
|
+
await next;
|
|
1655
|
+
} finally {
|
|
1656
|
+
if (cacheWriteLocks.get(cachePath) === next) cacheWriteLocks.delete(cachePath);
|
|
1486
1657
|
}
|
|
1487
|
-
return files.filter((_file, index) => excluded[index] === false);
|
|
1488
1658
|
}
|
|
1489
|
-
async function
|
|
1490
|
-
const
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1659
|
+
async function loadConvergeIdentityCache(cachePath, citationTemplate) {
|
|
1660
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1661
|
+
if (!cachePath) return cache;
|
|
1662
|
+
try {
|
|
1663
|
+
const raw = JSON.parse(await fs15.promises.readFile(cachePath, "utf8"));
|
|
1664
|
+
if (raw.citationTemplate !== citationTemplateFingerprint(citationTemplate)) return cache;
|
|
1665
|
+
for (const candidate of raw.files ?? []) {
|
|
1666
|
+
const entry = candidate;
|
|
1667
|
+
if (typeof entry?.path !== "string" || typeof entry.sha256 !== "string") continue;
|
|
1668
|
+
if (entry.memory !== void 0 && !isReconcileMemoryIdentity(entry.memory)) continue;
|
|
1669
|
+
if (entry.statIdentity !== void 0 && typeof entry.statIdentity !== "string") continue;
|
|
1670
|
+
if (entry.excluded !== void 0 && typeof entry.excluded !== "boolean") continue;
|
|
1671
|
+
if (entry.normalizerVersion !== void 0 && typeof entry.normalizerVersion !== "number") continue;
|
|
1672
|
+
if (entry.identityResolutionVersion !== void 0 && typeof entry.identityResolutionVersion !== "number") {
|
|
1673
|
+
continue;
|
|
1674
|
+
}
|
|
1675
|
+
cache.set(entry.path, {
|
|
1676
|
+
path: entry.path,
|
|
1677
|
+
sha256: entry.sha256,
|
|
1678
|
+
...entry.memory ? { memory: entry.memory } : {},
|
|
1679
|
+
...entry.memory ? {} : {
|
|
1680
|
+
...typeof entry.normalizerVersion === "number" ? { normalizerVersion: entry.normalizerVersion } : {},
|
|
1681
|
+
...typeof entry.identityResolutionVersion === "number" ? { identityResolutionVersion: entry.identityResolutionVersion } : {}
|
|
1682
|
+
},
|
|
1683
|
+
...entry.statIdentity !== void 0 && entry.excluded !== void 0 ? { statIdentity: entry.statIdentity, excluded: entry.excluded } : {}
|
|
1684
|
+
});
|
|
1501
1685
|
}
|
|
1686
|
+
} catch {
|
|
1502
1687
|
}
|
|
1503
|
-
return
|
|
1688
|
+
return cache;
|
|
1504
1689
|
}
|
|
1505
|
-
async function
|
|
1506
|
-
|
|
1507
|
-
const
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
}
|
|
1515
|
-
const
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1690
|
+
async function saveConvergeIdentityCache(cachePath, manifest, citationTemplate, loaded, classifications) {
|
|
1691
|
+
if (!cachePath) return;
|
|
1692
|
+
const files = manifest.files.map((file) => ({
|
|
1693
|
+
path: file.path,
|
|
1694
|
+
sha256: file.sha256,
|
|
1695
|
+
...file.memory !== void 0 ? { memory: file.memory } : file.normalizerVersion !== void 0 && file.identityResolutionVersion !== void 0 ? {
|
|
1696
|
+
normalizerVersion: file.normalizerVersion,
|
|
1697
|
+
identityResolutionVersion: file.identityResolutionVersion
|
|
1698
|
+
} : {}
|
|
1699
|
+
}));
|
|
1700
|
+
for (const entry of files) {
|
|
1701
|
+
const classification = classifications?.get(entry.path);
|
|
1702
|
+
if (classification === void 0) continue;
|
|
1703
|
+
entry.statIdentity = classification.statIdentity;
|
|
1704
|
+
entry.excluded = classification.excluded;
|
|
1705
|
+
}
|
|
1706
|
+
if (loaded !== void 0 && loaded.size === files.length && files.every((entry) => {
|
|
1707
|
+
const previous = loaded.get(entry.path);
|
|
1708
|
+
if (previous === void 0) return false;
|
|
1709
|
+
if (previous.sha256 !== entry.sha256) return false;
|
|
1710
|
+
if (previous.memory !== entry.memory) return false;
|
|
1711
|
+
if ((previous.normalizerVersion ?? void 0) !== (entry.normalizerVersion ?? void 0)) return false;
|
|
1712
|
+
if ((previous.identityResolutionVersion ?? void 0) !== (entry.identityResolutionVersion ?? void 0)) {
|
|
1713
|
+
return false;
|
|
1714
|
+
}
|
|
1715
|
+
if ((previous.statIdentity ?? void 0) !== (entry.statIdentity ?? void 0)) return false;
|
|
1716
|
+
return (previous.excluded ?? void 0) === (entry.excluded ?? void 0);
|
|
1717
|
+
})) {
|
|
1718
|
+
return;
|
|
1521
1719
|
}
|
|
1522
|
-
return storage;
|
|
1523
|
-
}
|
|
1524
|
-
async function createOfflineStorageIo(memoryDir, configuredStorage) {
|
|
1525
|
-
await cleanupOrphanedOfflineDecryptStaging(memoryDir);
|
|
1526
|
-
const { storage, secureStoreKey } = configuredStorage ?? await createConfiguredOfflineStorage(memoryDir);
|
|
1527
|
-
return {
|
|
1528
|
-
excludeFile: createSupportPassportPrivateFileExclusion(storage),
|
|
1529
|
-
readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
|
|
1530
|
-
readDeletionRevisions: () => storage.readDeletionRevisions(),
|
|
1531
|
-
readFileDigest: async ({ filePath }) => {
|
|
1532
|
-
const hash = createHash("sha256");
|
|
1533
|
-
let bytes = 0;
|
|
1534
|
-
for await (const rawChunk of readOfflineSyncFileChunks({
|
|
1535
|
-
filePath,
|
|
1536
|
-
memoryDir,
|
|
1537
|
-
secureStoreKey,
|
|
1538
|
-
chunkSize: OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES
|
|
1539
|
-
})) {
|
|
1540
|
-
const chunk = Buffer.isBuffer(rawChunk) ? rawChunk : Buffer.from(rawChunk);
|
|
1541
|
-
hash.update(chunk);
|
|
1542
|
-
bytes += chunk.length;
|
|
1543
|
-
}
|
|
1544
|
-
return {
|
|
1545
|
-
sha256: hash.digest("hex"),
|
|
1546
|
-
bytes
|
|
1547
|
-
};
|
|
1548
|
-
},
|
|
1549
|
-
readFileChunks: ({ filePath, chunkSize }) => readOfflineSyncFileChunks({
|
|
1550
|
-
filePath,
|
|
1551
|
-
memoryDir,
|
|
1552
|
-
secureStoreKey,
|
|
1553
|
-
chunkSize
|
|
1554
|
-
}),
|
|
1555
|
-
writeFile: async ({ filePath, content }) => storage.writeOfflineSyncFile(filePath, content),
|
|
1556
|
-
writeStagingFile: async ({ filePath, content }) => storage.writeOfflineSyncStagingFile(filePath, content),
|
|
1557
|
-
writeFileChunks: async ({ filePath, chunks }) => storage.writeOfflineSyncFileChunks(filePath, chunks),
|
|
1558
|
-
deleteFile: async ({ filePath, mtimeMs }) => storage.deleteOfflineSyncFile(filePath, mtimeMs ?? null),
|
|
1559
|
-
recordDeletionRevision: async ({ filePath, mtimeMs }) => storage.recordReplicatedDeletionRevision(filePath, mtimeMs)
|
|
1560
|
-
};
|
|
1561
|
-
}
|
|
1562
|
-
var OFFLINE_DECRYPT_STAGING_ORPHAN_MS = 60 * 60 * 1e3;
|
|
1563
|
-
async function cleanupOrphanedOfflineDecryptStaging(memoryDir) {
|
|
1564
|
-
let entries;
|
|
1565
1720
|
try {
|
|
1566
|
-
|
|
1721
|
+
await withCacheWriteLock(cachePath, async () => {
|
|
1722
|
+
const merged = new Map(files.map((entry) => [entry.path, entry]));
|
|
1723
|
+
const dropped = new Set(
|
|
1724
|
+
[...loaded?.keys() ?? []].filter((pathName) => !merged.has(pathName))
|
|
1725
|
+
);
|
|
1726
|
+
try {
|
|
1727
|
+
const raw = JSON.parse(await fs15.promises.readFile(cachePath, "utf8"));
|
|
1728
|
+
if (raw.citationTemplate === citationTemplateFingerprint(citationTemplate)) {
|
|
1729
|
+
for (const candidate of raw.files ?? []) {
|
|
1730
|
+
const entry = candidate;
|
|
1731
|
+
if (typeof entry?.path !== "string" || typeof entry.sha256 !== "string") continue;
|
|
1732
|
+
if (entry.memory !== void 0 && !isReconcileMemoryIdentity(entry.memory)) continue;
|
|
1733
|
+
if (dropped.has(entry.path) || merged.has(entry.path)) continue;
|
|
1734
|
+
merged.set(entry.path, {
|
|
1735
|
+
path: entry.path,
|
|
1736
|
+
sha256: entry.sha256,
|
|
1737
|
+
...entry.memory ? { memory: entry.memory } : {},
|
|
1738
|
+
...entry.memory ? {} : {
|
|
1739
|
+
...typeof entry.normalizerVersion === "number" ? { normalizerVersion: entry.normalizerVersion } : {},
|
|
1740
|
+
...typeof entry.identityResolutionVersion === "number" ? { identityResolutionVersion: entry.identityResolutionVersion } : {}
|
|
1741
|
+
}
|
|
1742
|
+
});
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
} catch {
|
|
1746
|
+
}
|
|
1747
|
+
const tmp = `${cachePath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
1748
|
+
await fs15.promises.mkdir(path.dirname(cachePath), { recursive: true });
|
|
1749
|
+
await fs15.promises.writeFile(
|
|
1750
|
+
tmp,
|
|
1751
|
+
JSON.stringify({
|
|
1752
|
+
citationTemplate: citationTemplateFingerprint(citationTemplate),
|
|
1753
|
+
files: [...merged.values()]
|
|
1754
|
+
})
|
|
1755
|
+
);
|
|
1756
|
+
await fs15.promises.rename(tmp, cachePath);
|
|
1757
|
+
});
|
|
1567
1758
|
} catch {
|
|
1568
|
-
return;
|
|
1569
|
-
}
|
|
1570
|
-
const now = Date.now();
|
|
1571
|
-
for (const name of entries) {
|
|
1572
|
-
if (!name.startsWith(OFFLINE_DECRYPT_STAGING_DIR_PREFIX)) continue;
|
|
1573
|
-
const dir = path.join(memoryDir, name);
|
|
1574
|
-
try {
|
|
1575
|
-
const info = await lstat(dir);
|
|
1576
|
-
if (!info.isDirectory() || info.isSymbolicLink()) continue;
|
|
1577
|
-
if (now - info.mtimeMs < OFFLINE_DECRYPT_STAGING_ORPHAN_MS) continue;
|
|
1578
|
-
await rm(dir, { recursive: true, force: true });
|
|
1579
|
-
} catch {
|
|
1580
|
-
}
|
|
1581
1759
|
}
|
|
1582
1760
|
}
|
|
1583
|
-
async function* readOfflineSyncFileChunks(options) {
|
|
1584
|
-
const header = await readFilePrefix(options.filePath, MAGIC_HEADER_SIZE);
|
|
1585
|
-
if (!isEncryptedFile(header)) {
|
|
1586
|
-
yield* readPlainOfflineFileChunks(options.filePath, options.chunkSize);
|
|
1587
|
-
return;
|
|
1588
|
-
}
|
|
1589
|
-
if (!options.secureStoreKey) {
|
|
1590
|
-
throw new SecureStoreLockedError(
|
|
1591
|
-
`secure-store is locked \u2014 cannot read encrypted file at ${options.filePath}. Run \`remnic secure-store unlock\` to decrypt.`
|
|
1592
|
-
);
|
|
1593
|
-
}
|
|
1594
|
-
yield* readEncryptedOfflineFileChunks({
|
|
1595
|
-
filePath: options.filePath,
|
|
1596
|
-
memoryDir: options.memoryDir,
|
|
1597
|
-
key: options.secureStoreKey,
|
|
1598
|
-
chunkSize: options.chunkSize
|
|
1599
|
-
});
|
|
1600
|
-
}
|
|
1601
|
-
async function readFilePrefix(filePath, length) {
|
|
1602
|
-
const handle = await fs15.promises.open(filePath, "r");
|
|
1603
|
-
try {
|
|
1604
|
-
const out = Buffer.alloc(length);
|
|
1605
|
-
const { bytesRead } = await handle.read(out, 0, length, 0);
|
|
1606
|
-
return out.subarray(0, bytesRead);
|
|
1607
|
-
} finally {
|
|
1608
|
-
await handle.close();
|
|
1609
|
-
}
|
|
1610
|
-
}
|
|
1611
|
-
async function* readPlainOfflineFileChunks(filePath, chunkSize) {
|
|
1612
|
-
const stream = fs15.createReadStream(filePath, { highWaterMark: chunkSize });
|
|
1613
|
-
for await (const chunk of stream) {
|
|
1614
|
-
yield Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1615
|
-
}
|
|
1616
|
-
}
|
|
1617
|
-
async function* readEncryptedOfflineFileChunks(options) {
|
|
1618
|
-
const header = await readFilePrefix(options.filePath, MAGIC_HEADER_SIZE + ENVELOPE_HEADER_SIZE);
|
|
1619
|
-
if (header.length < MAGIC_HEADER_SIZE + ENVELOPE_HEADER_SIZE || !isEncryptedFile(header)) {
|
|
1620
|
-
throw new Error(`secure-store encrypted file is truncated: ${options.filePath}`);
|
|
1621
|
-
}
|
|
1622
|
-
const version = header.readUInt8(MAGIC_BYTES.length);
|
|
1623
|
-
const flags = header.readUInt8(MAGIC_BYTES.length + 1);
|
|
1624
|
-
if (version !== FILE_FORMAT_VERSION) {
|
|
1625
|
-
throw new Error(`secure-store file has unsupported version ${version}: ${options.filePath}`);
|
|
1626
|
-
}
|
|
1627
|
-
if (flags !== FILE_FORMAT_FLAGS) {
|
|
1628
|
-
throw new Error(`secure-store file has unsupported flags 0x${flags.toString(16)}: ${options.filePath}`);
|
|
1629
|
-
}
|
|
1630
|
-
const envelopeHeader = header.subarray(MAGIC_HEADER_SIZE);
|
|
1631
|
-
const envelopeVersion = envelopeHeader.readUInt8(ENVELOPE_LAYOUT.version);
|
|
1632
|
-
if (envelopeVersion !== ENVELOPE_VERSION) {
|
|
1633
|
-
throw new Error(`secure-store envelope has unsupported version ${envelopeVersion}: ${options.filePath}`);
|
|
1634
|
-
}
|
|
1635
|
-
const salt = envelopeHeader.subarray(ENVELOPE_LAYOUT.salt, ENVELOPE_LAYOUT.salt + ENVELOPE_SALT_LENGTH);
|
|
1636
|
-
const iv = envelopeHeader.subarray(ENVELOPE_LAYOUT.iv, ENVELOPE_LAYOUT.iv + IV_LENGTH);
|
|
1637
|
-
const authTag = envelopeHeader.subarray(ENVELOPE_LAYOUT.authTag, ENVELOPE_LAYOUT.authTag + AUTH_TAG_LENGTH);
|
|
1638
|
-
const aadCandidates = offlineFileAadCandidates(options.filePath, options.memoryDir);
|
|
1639
|
-
let lastError;
|
|
1640
|
-
for (const aad of aadCandidates) {
|
|
1641
|
-
const tempDir = await mkdtemp(path.join(options.memoryDir, OFFLINE_DECRYPT_STAGING_DIR_PREFIX));
|
|
1642
|
-
const tempPath = path.join(tempDir, "content");
|
|
1643
|
-
try {
|
|
1644
|
-
const decipher = createDecipheriv("aes-256-gcm", options.key, iv, {
|
|
1645
|
-
authTagLength: AUTH_TAG_LENGTH
|
|
1646
|
-
});
|
|
1647
|
-
decipher.setAuthTag(authTag);
|
|
1648
|
-
decipher.setAAD(Buffer.concat([secureStoreEnvelopeHeaderAad(salt), aad]));
|
|
1649
|
-
const output = fs15.createWriteStream(tempPath, { mode: 384 });
|
|
1650
|
-
try {
|
|
1651
|
-
const stream = fs15.createReadStream(options.filePath, {
|
|
1652
|
-
start: MAGIC_HEADER_SIZE + ENVELOPE_HEADER_SIZE,
|
|
1653
|
-
highWaterMark: options.chunkSize
|
|
1654
|
-
});
|
|
1655
|
-
for await (const encryptedChunk of stream) {
|
|
1656
|
-
const plain = decipher.update(Buffer.isBuffer(encryptedChunk) ? encryptedChunk : Buffer.from(encryptedChunk));
|
|
1657
|
-
if (plain.length > 0 && !output.write(plain)) {
|
|
1658
|
-
await new Promise((resolve2, reject) => {
|
|
1659
|
-
output.once("drain", resolve2);
|
|
1660
|
-
output.once("error", reject);
|
|
1661
|
-
});
|
|
1662
|
-
}
|
|
1663
|
-
}
|
|
1664
|
-
const finalPlain = decipher.final();
|
|
1665
|
-
if (finalPlain.length > 0 && !output.write(finalPlain)) {
|
|
1666
|
-
await new Promise((resolve2, reject) => {
|
|
1667
|
-
output.once("drain", resolve2);
|
|
1668
|
-
output.once("error", reject);
|
|
1669
|
-
});
|
|
1670
|
-
}
|
|
1671
|
-
} finally {
|
|
1672
|
-
await closeWriteStream(output);
|
|
1673
|
-
}
|
|
1674
|
-
yield* readPlainOfflineFileChunks(tempPath, options.chunkSize);
|
|
1675
|
-
return;
|
|
1676
|
-
} catch (error) {
|
|
1677
|
-
lastError = error;
|
|
1678
|
-
} finally {
|
|
1679
|
-
await rm(tempDir, { recursive: true, force: true });
|
|
1680
|
-
}
|
|
1681
|
-
}
|
|
1682
|
-
throw lastError instanceof Error ? lastError : new Error(`secure-store could not decrypt file: ${options.filePath}`);
|
|
1683
|
-
}
|
|
1684
|
-
function offlineFileAadCandidates(filePath, memoryDir) {
|
|
1685
|
-
const candidates = [filePathAad(filePath, memoryDir)];
|
|
1686
|
-
const relative = path.relative(memoryDir, filePath);
|
|
1687
|
-
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) return candidates;
|
|
1688
|
-
const parts = relative.split(path.sep);
|
|
1689
|
-
if (parts[0] === "namespaces" && parts.length >= 3 && parts[1]) {
|
|
1690
|
-
candidates.push(filePathAad(filePath, path.join(memoryDir, "namespaces", parts[1])));
|
|
1691
|
-
}
|
|
1692
|
-
const memoryParts = path.resolve(memoryDir).split(path.sep);
|
|
1693
|
-
if (memoryParts.length >= 3 && memoryParts.at(-2) === "namespaces" && memoryParts.at(-1)) {
|
|
1694
|
-
const topLevelRoot = memoryParts.slice(0, -2).join(path.sep) || path.sep;
|
|
1695
|
-
const topRelative = path.relative(topLevelRoot, filePath);
|
|
1696
|
-
if (topRelative && !topRelative.startsWith("..") && !path.isAbsolute(topRelative) && topRelative.split(path.sep)[0] === "namespaces" && topRelative.split(path.sep)[1] === memoryParts.at(-1)) {
|
|
1697
|
-
candidates.push(filePathAad(filePath, topLevelRoot));
|
|
1698
|
-
}
|
|
1699
|
-
}
|
|
1700
|
-
return candidates;
|
|
1701
|
-
}
|
|
1702
|
-
async function closeWriteStream(stream) {
|
|
1703
|
-
await new Promise((resolve2, reject) => {
|
|
1704
|
-
stream.once("error", reject);
|
|
1705
|
-
stream.end(() => resolve2());
|
|
1706
|
-
});
|
|
1707
|
-
}
|
|
1708
|
-
function secureStoreEnvelopeHeaderAad(salt) {
|
|
1709
|
-
const out = Buffer.alloc(1 + ENVELOPE_SALT_LENGTH);
|
|
1710
|
-
out.writeUInt8(ENVELOPE_VERSION, 0);
|
|
1711
|
-
Buffer.from(salt).copy(out, 1);
|
|
1712
|
-
return out;
|
|
1713
|
-
}
|
|
1714
1761
|
|
|
1715
|
-
// src/converge-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
};
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
return;
|
|
1733
|
-
}
|
|
1734
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
1735
|
-
});
|
|
1736
|
-
}
|
|
1737
|
-
async function convergeWatch(options) {
|
|
1738
|
-
const intervalMs = Math.min(
|
|
1739
|
-
CONVERGE_WATCH_MAX_INTERVAL_MS,
|
|
1740
|
-
Math.max(CONVERGE_WATCH_MIN_INTERVAL_MS, options.intervalMs ?? CONVERGE_WATCH_DEFAULT_INTERVAL_MS)
|
|
1741
|
-
);
|
|
1742
|
-
const { apply, intervalMs: _intervalMs, maxCycles, onCycle, signal, ...applyOptions } = options;
|
|
1743
|
-
const outcome = {
|
|
1744
|
-
cycles: 0,
|
|
1745
|
-
convergedCycles: 0,
|
|
1746
|
-
appliedCycles: 0,
|
|
1747
|
-
failedCycles: 0,
|
|
1748
|
-
lastStatus: "aborted"
|
|
1749
|
-
};
|
|
1750
|
-
while (maxCycles === void 0 || outcome.cycles < maxCycles) {
|
|
1751
|
-
if (signal?.aborted) break;
|
|
1752
|
-
try {
|
|
1753
|
-
const result = await apply(applyOptions);
|
|
1754
|
-
outcome.cycles += 1;
|
|
1755
|
-
if (result.status === "converged") outcome.convergedCycles += 1;
|
|
1756
|
-
else if (result.status === "stopped_unresolved_conflicts" || result.status === "applied" && result.transfers.failed > 0) {
|
|
1757
|
-
outcome.failedCycles += 1;
|
|
1758
|
-
} else outcome.appliedCycles += 1;
|
|
1759
|
-
outcome.lastStatus = result.status;
|
|
1760
|
-
onCycle?.(outcome.cycles, { result });
|
|
1761
|
-
} catch (err) {
|
|
1762
|
-
outcome.cycles += 1;
|
|
1763
|
-
outcome.failedCycles += 1;
|
|
1764
|
-
outcome.lastStatus = "error";
|
|
1765
|
-
onCycle?.(outcome.cycles, { error: err });
|
|
1762
|
+
// src/converge-report.ts
|
|
1763
|
+
function formatConvergeReport(plan) {
|
|
1764
|
+
const lines = [];
|
|
1765
|
+
lines.push(`Convergence Status: ${plan.converged ? "CONVERGED" : "DIVERGED"}`);
|
|
1766
|
+
lines.push("");
|
|
1767
|
+
lines.push("Per-Namespace Summary:");
|
|
1768
|
+
if (plan.byNamespace.length === 0) {
|
|
1769
|
+
lines.push(" (no namespaces evaluated)");
|
|
1770
|
+
} else {
|
|
1771
|
+
for (const report of plan.byNamespace) {
|
|
1772
|
+
lines.push(` [${report.namespace}]`);
|
|
1773
|
+
lines.push(` identical: ${report.identical}`);
|
|
1774
|
+
lines.push(` pull: ${report.pull}`);
|
|
1775
|
+
lines.push(` push: ${report.push}`);
|
|
1776
|
+
lines.push(` conflict: ${report.conflict}`);
|
|
1777
|
+
lines.push(` suppress: ${report.suppress}`);
|
|
1778
|
+
lines.push(` unresolved: ${report.unresolved}`);
|
|
1766
1779
|
}
|
|
1767
|
-
if (maxCycles !== void 0 && outcome.cycles >= maxCycles) break;
|
|
1768
|
-
const slept = await sleepAborted(intervalMs, signal ?? new AbortController().signal);
|
|
1769
|
-
if (!slept) break;
|
|
1770
1780
|
}
|
|
1771
|
-
return
|
|
1772
|
-
}
|
|
1773
|
-
|
|
1774
|
-
// src/converge-token-channel.ts
|
|
1775
|
-
import * as fs16 from "fs";
|
|
1776
|
-
function parseConvergeTokenFileFlag(raw) {
|
|
1777
|
-
return raw !== void 0 && raw.length > 0 ? raw : null;
|
|
1781
|
+
return lines.join("\n");
|
|
1778
1782
|
}
|
|
1779
|
-
function
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
}
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
}
|
|
1794
|
-
return { ok: true, token, tokenFromArgv };
|
|
1795
|
-
} catch (err) {
|
|
1796
|
-
return { ok: false, error: `--token-file ${input.tokenFile} could not be read: ${err}` };
|
|
1797
|
-
}
|
|
1798
|
-
}
|
|
1799
|
-
if (!tokenFromArgv && input.tokenFile === void 0 && env.REMNIC_CONVERGE_PEER_TOKEN !== void 0) {
|
|
1800
|
-
if (env.REMNIC_CONVERGE_PEER_TOKEN.length === 0) {
|
|
1801
|
-
return { ok: false, error: "REMNIC_CONVERGE_PEER_TOKEN is set but empty" };
|
|
1802
|
-
}
|
|
1803
|
-
return { ok: true, token: env.REMNIC_CONVERGE_PEER_TOKEN, tokenFromArgv: false };
|
|
1804
|
-
}
|
|
1805
|
-
return { ok: true, token: input.argvToken, tokenFromArgv };
|
|
1783
|
+
function formatConvergeApplyReport(result) {
|
|
1784
|
+
const lines = [];
|
|
1785
|
+
lines.push(`Convergence Execution Status: ${result.status.toUpperCase()}`);
|
|
1786
|
+
lines.push(`Converged: ${result.converged ? "YES" : "NO"}`);
|
|
1787
|
+
lines.push("");
|
|
1788
|
+
lines.push("Transfers Executed:");
|
|
1789
|
+
lines.push(` pulled: ${result.transfers.pulled}`);
|
|
1790
|
+
lines.push(` pushed: ${result.transfers.pushed}`);
|
|
1791
|
+
lines.push(` conflictsResolved: ${result.transfers.conflictsResolved}`);
|
|
1792
|
+
lines.push(` suppressed: ${result.transfers.suppressed}`);
|
|
1793
|
+
lines.push(` failed: ${result.transfers.failed}`);
|
|
1794
|
+
lines.push("");
|
|
1795
|
+
lines.push(formatConvergeReport(result.plan));
|
|
1796
|
+
return lines.join("\n");
|
|
1806
1797
|
}
|
|
1807
1798
|
|
|
1808
|
-
// src/converge.ts
|
|
1809
|
-
import { resolveAgentAccessAuthToken } from "@remnic/core/resolve-auth-token.js";
|
|
1810
|
-
|
|
1811
1799
|
// src/converge-peer-transport.ts
|
|
1812
|
-
import { createHash
|
|
1800
|
+
import { createHash } from "crypto";
|
|
1813
1801
|
import {
|
|
1814
1802
|
isInternalRemnicStatePath as isInternalRemnicStatePath2,
|
|
1815
1803
|
OFFLINE_SYNC_CHANGESET_FORMAT,
|
|
1816
1804
|
OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES,
|
|
1817
|
-
OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES
|
|
1805
|
+
OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES
|
|
1818
1806
|
} from "@remnic/core";
|
|
1819
1807
|
import { normalizeConvergePeerUrl } from "@remnic/core/reconcile/cursor.js";
|
|
1820
1808
|
|
|
@@ -2101,7 +2089,7 @@ async function streamPeerFileContent(peerUrl, namespace, filePath, onChunk, toke
|
|
|
2101
2089
|
"content-type": "application/json",
|
|
2102
2090
|
...token ? { authorization: `Bearer ${token}` } : {}
|
|
2103
2091
|
};
|
|
2104
|
-
const hash =
|
|
2092
|
+
const hash = createHash("sha256");
|
|
2105
2093
|
let offset = 0;
|
|
2106
2094
|
let expectedBytes;
|
|
2107
2095
|
let expectedSha256;
|
|
@@ -2202,7 +2190,7 @@ async function postPeerFileContent(peerUrl, namespace, filePath, source, token,
|
|
|
2202
2190
|
for (const route of routes) {
|
|
2203
2191
|
let restartedRoute = false;
|
|
2204
2192
|
while (offset < source.bytes || source.bytes === 0 && offset === 0) {
|
|
2205
|
-
const length = Math.min(
|
|
2193
|
+
const length = Math.min(OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES, source.bytes - offset);
|
|
2206
2194
|
let chunk;
|
|
2207
2195
|
try {
|
|
2208
2196
|
chunk = await source.readChunk(offset, length);
|
|
@@ -2241,92 +2229,531 @@ async function postPeerFileContent(peerUrl, namespace, filePath, source, token,
|
|
|
2241
2229
|
restartedRoute = true;
|
|
2242
2230
|
continue;
|
|
2243
2231
|
}
|
|
2244
|
-
previousAttemptFailed = true;
|
|
2245
|
-
break;
|
|
2246
|
-
}
|
|
2247
|
-
const result = await response.json().catch(() => null);
|
|
2248
|
-
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) {
|
|
2249
|
-
return false;
|
|
2250
|
-
}
|
|
2251
|
-
if (result.done) {
|
|
2252
|
-
if (result.skipped) return previousAttemptFailed ? "applied" : "skipped";
|
|
2253
|
-
return result.applied && offset + chunk.length === source.bytes ? "applied" : false;
|
|
2232
|
+
previousAttemptFailed = true;
|
|
2233
|
+
break;
|
|
2234
|
+
}
|
|
2235
|
+
const result = await response.json().catch(() => null);
|
|
2236
|
+
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) {
|
|
2237
|
+
return false;
|
|
2238
|
+
}
|
|
2239
|
+
if (result.done) {
|
|
2240
|
+
if (result.skipped) return previousAttemptFailed ? "applied" : "skipped";
|
|
2241
|
+
return result.applied && offset + chunk.length === source.bytes ? "applied" : false;
|
|
2242
|
+
}
|
|
2243
|
+
if (result.applied || result.skipped || chunk.length === 0) return false;
|
|
2244
|
+
offset += chunk.length;
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
return false;
|
|
2248
|
+
}
|
|
2249
|
+
async function postPeerConvergenceComplete(peerUrl, namespaces, token, fetchImpl = globalThis.fetch, timeoutMs = DEFAULT_PEER_REQUEST_TIMEOUT_MS) {
|
|
2250
|
+
const base = normalizePeerBaseUrl(peerUrl);
|
|
2251
|
+
const query = namespaces.map((namespace) => `namespace=${encodeURIComponent(namespace)}`).join("&");
|
|
2252
|
+
const routes = ["/remnic/v1/offline-sync/convergence-complete", "/engram/v1/offline-sync/convergence-complete"];
|
|
2253
|
+
for (const route of routes) {
|
|
2254
|
+
const response = await fetchPeerRequest(
|
|
2255
|
+
fetchImpl,
|
|
2256
|
+
`${base}${route}?${query}`,
|
|
2257
|
+
{
|
|
2258
|
+
method: "POST",
|
|
2259
|
+
headers: {
|
|
2260
|
+
"x-remnic-source-id": encodeURIComponent("remnic-converge"),
|
|
2261
|
+
...token ? { authorization: `Bearer ${token}` } : {}
|
|
2262
|
+
}
|
|
2263
|
+
},
|
|
2264
|
+
timeoutMs
|
|
2265
|
+
).catch(() => null);
|
|
2266
|
+
if (!response?.ok) continue;
|
|
2267
|
+
const result = await response.json().catch(() => null);
|
|
2268
|
+
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) {
|
|
2269
|
+
return true;
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
return false;
|
|
2273
|
+
}
|
|
2274
|
+
async function postPeerFileDeletion(peerUrl, namespace, filePath, baseSha256, token, fetchImpl = globalThis.fetch, timeoutMs = DEFAULT_PEER_REQUEST_TIMEOUT_MS) {
|
|
2275
|
+
assertTransferablePeerPath(filePath);
|
|
2276
|
+
const base = normalizePeerBaseUrl(peerUrl);
|
|
2277
|
+
const routes = ["/remnic/v1/offline-sync/apply", "/engram/v1/offline-sync/apply"];
|
|
2278
|
+
const headers = {
|
|
2279
|
+
"content-type": "application/json",
|
|
2280
|
+
...token ? { authorization: `Bearer ${token}` } : {}
|
|
2281
|
+
};
|
|
2282
|
+
let previousAttemptFailed = false;
|
|
2283
|
+
for (const route of routes) {
|
|
2284
|
+
try {
|
|
2285
|
+
const response = await fetchPeerRequest(
|
|
2286
|
+
fetchImpl,
|
|
2287
|
+
`${base}${route}`,
|
|
2288
|
+
{
|
|
2289
|
+
method: "POST",
|
|
2290
|
+
headers,
|
|
2291
|
+
body: JSON.stringify({
|
|
2292
|
+
namespace,
|
|
2293
|
+
changeset: {
|
|
2294
|
+
format: OFFLINE_SYNC_CHANGESET_FORMAT,
|
|
2295
|
+
schemaVersion: 1,
|
|
2296
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2297
|
+
sourceId: "remnic-converge",
|
|
2298
|
+
includeTranscripts: false,
|
|
2299
|
+
changes: [{ type: "delete", path: filePath, baseSha256 }]
|
|
2300
|
+
}
|
|
2301
|
+
})
|
|
2302
|
+
},
|
|
2303
|
+
timeoutMs
|
|
2304
|
+
);
|
|
2305
|
+
if (!response.ok) throw new Error(`offline apply request failed: ${response.status}`);
|
|
2306
|
+
const result = await response.json().catch(() => null);
|
|
2307
|
+
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) {
|
|
2308
|
+
return false;
|
|
2309
|
+
}
|
|
2310
|
+
if (result.appliedDeletes === 1) return "applied";
|
|
2311
|
+
if (result.skipped === 1) return previousAttemptFailed ? "applied" : "skipped";
|
|
2312
|
+
return false;
|
|
2313
|
+
} catch {
|
|
2314
|
+
previousAttemptFailed = true;
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
return false;
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
// src/credential-channel.ts
|
|
2321
|
+
import * as fs16 from "fs";
|
|
2322
|
+
function parseTokenFileFlag(raw) {
|
|
2323
|
+
return raw !== void 0 && raw.length > 0 ? raw : null;
|
|
2324
|
+
}
|
|
2325
|
+
function readTokenFileSameInode(tokenFile, afterValidate) {
|
|
2326
|
+
let fd;
|
|
2327
|
+
try {
|
|
2328
|
+
if (process.platform === "win32") {
|
|
2329
|
+
const pre = fs16.lstatSync(tokenFile);
|
|
2330
|
+
if (pre.isSymbolicLink()) {
|
|
2331
|
+
return { ok: false, error: `--token-file ${tokenFile} must be a regular file, not a symlink` };
|
|
2332
|
+
}
|
|
2333
|
+
if (!pre.isFile()) {
|
|
2334
|
+
return { ok: false, error: `--token-file ${tokenFile} must be a regular file` };
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
fd = fs16.openSync(
|
|
2338
|
+
tokenFile,
|
|
2339
|
+
fs16.constants.O_RDONLY | (fs16.constants.O_NOFOLLOW ?? 0) | (fs16.constants.O_NONBLOCK ?? 0)
|
|
2340
|
+
);
|
|
2341
|
+
} catch (err) {
|
|
2342
|
+
const code = err.code;
|
|
2343
|
+
if (code === "ELOOP") {
|
|
2344
|
+
return { ok: false, error: `--token-file ${tokenFile} must be a regular file, not a symlink` };
|
|
2345
|
+
}
|
|
2346
|
+
if (code === "EISDIR" || code === "ENOTDIR" || code === "ENXIO" || code === "EAGAIN" || code === "EWOULDBLOCK" || code === "EOPNOTSUPP" || code === "ENOTSUP") {
|
|
2347
|
+
return { ok: false, error: `--token-file ${tokenFile} must be a regular file` };
|
|
2348
|
+
}
|
|
2349
|
+
return { ok: false, error: `--token-file ${tokenFile} could not be read: ${err}` };
|
|
2350
|
+
}
|
|
2351
|
+
try {
|
|
2352
|
+
const opened = fs16.fstatSync(fd);
|
|
2353
|
+
if (!opened.isFile()) {
|
|
2354
|
+
return { ok: false, error: `--token-file ${tokenFile} must be a regular file` };
|
|
2355
|
+
}
|
|
2356
|
+
if (process.platform !== "win32" && opened.mode & 63) {
|
|
2357
|
+
return { ok: false, error: `--token-file ${tokenFile} must not be group- or world-readable (chmod 600)` };
|
|
2358
|
+
}
|
|
2359
|
+
if (process.platform === "win32" || (fs16.constants.O_NOFOLLOW ?? 0) === 0) {
|
|
2360
|
+
const pathStat = fs16.lstatSync(tokenFile);
|
|
2361
|
+
if (pathStat.isSymbolicLink()) {
|
|
2362
|
+
return { ok: false, error: `--token-file ${tokenFile} must be a regular file, not a symlink` };
|
|
2363
|
+
}
|
|
2364
|
+
if (!pathStat.isFile() || pathStat.dev !== opened.dev || pathStat.ino !== opened.ino) {
|
|
2365
|
+
return { ok: false, error: `--token-file ${tokenFile} must be a regular file` };
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
afterValidate?.();
|
|
2369
|
+
const size = opened.size;
|
|
2370
|
+
const buf = Buffer.alloc(size);
|
|
2371
|
+
const got = size > 0 ? fs16.readSync(fd, buf, 0, size, 0) : 0;
|
|
2372
|
+
const token = buf.subarray(0, got).toString("utf8").trim();
|
|
2373
|
+
const after = fs16.fstatSync(fd);
|
|
2374
|
+
if (opened.dev !== after.dev || opened.ino !== after.ino || !after.isFile()) {
|
|
2375
|
+
return { ok: false, error: `--token-file ${tokenFile} could not be read: file changed during read` };
|
|
2376
|
+
}
|
|
2377
|
+
if (token.length === 0) {
|
|
2378
|
+
return { ok: false, error: `--token-file ${tokenFile} is empty` };
|
|
2379
|
+
}
|
|
2380
|
+
return { ok: true, token, tokenFromArgv: false };
|
|
2381
|
+
} catch (err) {
|
|
2382
|
+
return { ok: false, error: `--token-file ${tokenFile} could not be read: ${err}` };
|
|
2383
|
+
} finally {
|
|
2384
|
+
if (fd !== void 0) {
|
|
2385
|
+
try {
|
|
2386
|
+
fs16.closeSync(fd);
|
|
2387
|
+
} catch {
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
function resolveCredentialChannel(input, env, hooks) {
|
|
2393
|
+
if (input.argvToken !== void 0 && input.argvToken.trim().length === 0) {
|
|
2394
|
+
return { ok: false, error: "--token requires a non-empty value" };
|
|
2395
|
+
}
|
|
2396
|
+
if (input.argvToken !== void 0) {
|
|
2397
|
+
return { ok: true, token: input.argvToken, tokenFromArgv: true };
|
|
2398
|
+
}
|
|
2399
|
+
if (input.tokenFile !== void 0) {
|
|
2400
|
+
if (input.tokenFile.length === 0) {
|
|
2401
|
+
return { ok: false, error: "--token-file requires a non-empty path" };
|
|
2402
|
+
}
|
|
2403
|
+
return readTokenFileSameInode(input.tokenFile, hooks?.afterTokenFileValidated);
|
|
2404
|
+
}
|
|
2405
|
+
for (const name of input.envNames) {
|
|
2406
|
+
const value = env[name];
|
|
2407
|
+
if (value === void 0) continue;
|
|
2408
|
+
if (value.trim().length === 0) {
|
|
2409
|
+
return { ok: false, error: `${name} is set but empty` };
|
|
2410
|
+
}
|
|
2411
|
+
return { ok: true, token: value, tokenFromArgv: false };
|
|
2412
|
+
}
|
|
2413
|
+
return { ok: true, token: void 0, tokenFromArgv: false };
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
// src/converge-watch.ts
|
|
2417
|
+
var CONVERGE_WATCH_MIN_INTERVAL_MS = 1e3;
|
|
2418
|
+
var CONVERGE_WATCH_DEFAULT_INTERVAL_MS = 3e5;
|
|
2419
|
+
var CONVERGE_WATCH_MAX_INTERVAL_MS = 2147483647;
|
|
2420
|
+
function sleepAborted(ms, signal) {
|
|
2421
|
+
return new Promise((resolve2) => {
|
|
2422
|
+
const timer = setTimeout(() => {
|
|
2423
|
+
signal.removeEventListener("abort", onAbort);
|
|
2424
|
+
resolve2(true);
|
|
2425
|
+
}, ms);
|
|
2426
|
+
const onAbort = () => {
|
|
2427
|
+
clearTimeout(timer);
|
|
2428
|
+
resolve2(false);
|
|
2429
|
+
};
|
|
2430
|
+
if (signal.aborted) {
|
|
2431
|
+
clearTimeout(timer);
|
|
2432
|
+
resolve2(false);
|
|
2433
|
+
return;
|
|
2434
|
+
}
|
|
2435
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2436
|
+
});
|
|
2437
|
+
}
|
|
2438
|
+
async function convergeWatch(options) {
|
|
2439
|
+
const intervalMs = Math.min(
|
|
2440
|
+
CONVERGE_WATCH_MAX_INTERVAL_MS,
|
|
2441
|
+
Math.max(CONVERGE_WATCH_MIN_INTERVAL_MS, options.intervalMs ?? CONVERGE_WATCH_DEFAULT_INTERVAL_MS)
|
|
2442
|
+
);
|
|
2443
|
+
const { apply, intervalMs: _intervalMs, maxCycles, onCycle, signal, ...applyOptions } = options;
|
|
2444
|
+
const outcome = {
|
|
2445
|
+
cycles: 0,
|
|
2446
|
+
convergedCycles: 0,
|
|
2447
|
+
appliedCycles: 0,
|
|
2448
|
+
failedCycles: 0,
|
|
2449
|
+
lastStatus: "aborted"
|
|
2450
|
+
};
|
|
2451
|
+
while (maxCycles === void 0 || outcome.cycles < maxCycles) {
|
|
2452
|
+
if (signal?.aborted) break;
|
|
2453
|
+
try {
|
|
2454
|
+
const result = await apply(applyOptions);
|
|
2455
|
+
outcome.cycles += 1;
|
|
2456
|
+
if (result.status === "converged") outcome.convergedCycles += 1;
|
|
2457
|
+
else if (result.status === "stopped_unresolved_conflicts" || result.status === "applied" && result.transfers.failed > 0) {
|
|
2458
|
+
outcome.failedCycles += 1;
|
|
2459
|
+
} else outcome.appliedCycles += 1;
|
|
2460
|
+
outcome.lastStatus = result.status;
|
|
2461
|
+
onCycle?.(outcome.cycles, { result });
|
|
2462
|
+
} catch (err) {
|
|
2463
|
+
outcome.cycles += 1;
|
|
2464
|
+
outcome.failedCycles += 1;
|
|
2465
|
+
outcome.lastStatus = "error";
|
|
2466
|
+
onCycle?.(outcome.cycles, { error: err });
|
|
2467
|
+
}
|
|
2468
|
+
if (maxCycles !== void 0 && outcome.cycles >= maxCycles) break;
|
|
2469
|
+
const slept = await sleepAborted(intervalMs, signal ?? new AbortController().signal);
|
|
2470
|
+
if (!slept) break;
|
|
2471
|
+
}
|
|
2472
|
+
return outcome;
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
// src/offline-storage-io.ts
|
|
2476
|
+
import { createDecipheriv, createHash as createHash2 } from "crypto";
|
|
2477
|
+
import fs17 from "fs";
|
|
2478
|
+
import { lstat, mkdtemp, readdir, rm } from "fs/promises";
|
|
2479
|
+
import path2 from "path";
|
|
2480
|
+
import {
|
|
2481
|
+
OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES as OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES2,
|
|
2482
|
+
StorageManager as StorageManager3,
|
|
2483
|
+
createPersistedSupportPassportPrivateFileExclusion,
|
|
2484
|
+
createSupportPassportPrivateFileExclusion
|
|
2485
|
+
} from "@remnic/core";
|
|
2486
|
+
import { OFFLINE_DECRYPT_STAGING_DIR_PREFIX } from "@remnic/core/offline-sync-exclude-globs";
|
|
2487
|
+
import {
|
|
2488
|
+
AUTH_TAG_LENGTH,
|
|
2489
|
+
ENVELOPE_HEADER_SIZE,
|
|
2490
|
+
ENVELOPE_LAYOUT,
|
|
2491
|
+
ENVELOPE_SALT_LENGTH,
|
|
2492
|
+
ENVELOPE_VERSION,
|
|
2493
|
+
FILE_FORMAT_FLAGS,
|
|
2494
|
+
FILE_FORMAT_VERSION,
|
|
2495
|
+
IV_LENGTH,
|
|
2496
|
+
MAGIC_BYTES,
|
|
2497
|
+
MAGIC_HEADER_SIZE,
|
|
2498
|
+
SecureStoreLockedError,
|
|
2499
|
+
filePathAad,
|
|
2500
|
+
isEncryptedFile,
|
|
2501
|
+
keyring,
|
|
2502
|
+
readHeader,
|
|
2503
|
+
secureStoreDir
|
|
2504
|
+
} from "@remnic/core/secure-store";
|
|
2505
|
+
var OFFLINE_SYNC_EXCLUSION_CONCURRENCY = 16;
|
|
2506
|
+
function resolveOfflineDirectHydrationPath(memoryDir, relPath) {
|
|
2507
|
+
const base = path2.resolve(memoryDir);
|
|
2508
|
+
const target = path2.resolve(base, relPath);
|
|
2509
|
+
const relative = path2.relative(base, target);
|
|
2510
|
+
if (relative === "" || relative === ".." || relative.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative)) {
|
|
2511
|
+
throw new Error(`offline sync direct hydration path escapes memory dir: ${relPath}`);
|
|
2512
|
+
}
|
|
2513
|
+
return target;
|
|
2514
|
+
}
|
|
2515
|
+
async function filterOfflineSyncBaseFiles(memoryDir, files, excludeFile) {
|
|
2516
|
+
const excluded = new Array(files.length);
|
|
2517
|
+
for (let offset = 0; offset < files.length; offset += OFFLINE_SYNC_EXCLUSION_CONCURRENCY) {
|
|
2518
|
+
await Promise.all(
|
|
2519
|
+
files.slice(offset, offset + OFFLINE_SYNC_EXCLUSION_CONCURRENCY).map(async (file, index) => {
|
|
2520
|
+
const filePath = resolveOfflineDirectHydrationPath(memoryDir, file.path);
|
|
2521
|
+
excluded[offset + index] = await excludeFile({ root: memoryDir, path: file.path, filePath });
|
|
2522
|
+
})
|
|
2523
|
+
);
|
|
2524
|
+
}
|
|
2525
|
+
return files.filter((_file, index) => excluded[index] === false);
|
|
2526
|
+
}
|
|
2527
|
+
async function createConfiguredOfflineStorage(memoryDir, secureStoreEncryptOnWrite = true) {
|
|
2528
|
+
const storage = new StorageManager3(memoryDir);
|
|
2529
|
+
const header = await readHeader(memoryDir);
|
|
2530
|
+
let secureStoreKey = null;
|
|
2531
|
+
let secureStoreRequired = false;
|
|
2532
|
+
if (header) {
|
|
2533
|
+
secureStoreRequired = true;
|
|
2534
|
+
storage.setSecureStoreRequired(true);
|
|
2535
|
+
const key = keyring.getKey(secureStoreDir(memoryDir));
|
|
2536
|
+
if (key) {
|
|
2537
|
+
await storage.setSecureStoreKeyAndWait(key, secureStoreEncryptOnWrite);
|
|
2538
|
+
secureStoreKey = key;
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
return { storage, secureStoreKey, secureStoreRequired };
|
|
2542
|
+
}
|
|
2543
|
+
async function createOfflineStorageForPath(memoryDir, filePath, configured, secureStoreEncryptOnWrite) {
|
|
2544
|
+
const memoryRoot = path2.resolve(memoryDir);
|
|
2545
|
+
const stateDir = path2.dirname(filePath);
|
|
2546
|
+
if (path2.basename(stateDir) !== "state" || path2.basename(filePath) !== "memory-lifecycle-ledger.jsonl") {
|
|
2547
|
+
throw new Error(`invalid lifecycle ledger path: ${filePath}`);
|
|
2548
|
+
}
|
|
2549
|
+
const storageRoot = path2.resolve(path2.dirname(stateDir));
|
|
2550
|
+
if (storageRoot !== memoryRoot && !storageRoot.startsWith(`${memoryRoot}${path2.sep}`)) {
|
|
2551
|
+
throw new Error(`lifecycle ledger path is outside the offline memory directory: ${filePath}`);
|
|
2552
|
+
}
|
|
2553
|
+
const storage = new StorageManager3(storageRoot);
|
|
2554
|
+
if (configured.secureStoreRequired) {
|
|
2555
|
+
storage.setSecureStoreRequired(true);
|
|
2556
|
+
}
|
|
2557
|
+
if (configured.secureStoreKey) {
|
|
2558
|
+
await storage.setSecureStoreKeyAndWait(configured.secureStoreKey, secureStoreEncryptOnWrite);
|
|
2559
|
+
}
|
|
2560
|
+
return storage;
|
|
2561
|
+
}
|
|
2562
|
+
async function createOfflineStorageIo(memoryDir, configuredStorage, persistedExclusion) {
|
|
2563
|
+
await cleanupOrphanedOfflineDecryptStaging(memoryDir);
|
|
2564
|
+
const { storage, secureStoreKey } = configuredStorage ?? await createConfiguredOfflineStorage(memoryDir);
|
|
2565
|
+
return {
|
|
2566
|
+
// With persisted classifications the warm path skips the per-file
|
|
2567
|
+
// read+parse the plain exclusion performs for every candidate.
|
|
2568
|
+
excludeFile: persistedExclusion ? createPersistedSupportPassportPrivateFileExclusion(
|
|
2569
|
+
storage,
|
|
2570
|
+
persistedExclusion.persisted,
|
|
2571
|
+
persistedExclusion.updates
|
|
2572
|
+
) : createSupportPassportPrivateFileExclusion(storage),
|
|
2573
|
+
readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
|
|
2574
|
+
readDeletionRevisions: () => storage.readDeletionRevisions(),
|
|
2575
|
+
readFileDigest: async ({ filePath }) => {
|
|
2576
|
+
const hash = createHash2("sha256");
|
|
2577
|
+
let bytes = 0;
|
|
2578
|
+
for await (const rawChunk of readOfflineSyncFileChunks({
|
|
2579
|
+
filePath,
|
|
2580
|
+
memoryDir,
|
|
2581
|
+
secureStoreKey,
|
|
2582
|
+
chunkSize: OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES2
|
|
2583
|
+
})) {
|
|
2584
|
+
const chunk = Buffer.isBuffer(rawChunk) ? rawChunk : Buffer.from(rawChunk);
|
|
2585
|
+
hash.update(chunk);
|
|
2586
|
+
bytes += chunk.length;
|
|
2587
|
+
}
|
|
2588
|
+
return {
|
|
2589
|
+
sha256: hash.digest("hex"),
|
|
2590
|
+
bytes
|
|
2591
|
+
};
|
|
2592
|
+
},
|
|
2593
|
+
readFileChunks: ({ filePath, chunkSize }) => readOfflineSyncFileChunks({
|
|
2594
|
+
filePath,
|
|
2595
|
+
memoryDir,
|
|
2596
|
+
secureStoreKey,
|
|
2597
|
+
chunkSize
|
|
2598
|
+
}),
|
|
2599
|
+
writeFile: async ({ filePath, content }) => storage.writeOfflineSyncFile(filePath, content),
|
|
2600
|
+
writeStagingFile: async ({ filePath, content }) => storage.writeOfflineSyncStagingFile(filePath, content),
|
|
2601
|
+
writeFileChunks: async ({ filePath, chunks }) => storage.writeOfflineSyncFileChunks(filePath, chunks),
|
|
2602
|
+
deleteFile: async ({ filePath, mtimeMs }) => storage.deleteOfflineSyncFile(filePath, mtimeMs ?? null),
|
|
2603
|
+
recordDeletionRevision: async ({ filePath, mtimeMs }) => storage.recordReplicatedDeletionRevision(filePath, mtimeMs)
|
|
2604
|
+
};
|
|
2605
|
+
}
|
|
2606
|
+
var OFFLINE_DECRYPT_STAGING_ORPHAN_MS = 60 * 60 * 1e3;
|
|
2607
|
+
async function cleanupOrphanedOfflineDecryptStaging(memoryDir) {
|
|
2608
|
+
let entries;
|
|
2609
|
+
try {
|
|
2610
|
+
entries = await readdir(memoryDir);
|
|
2611
|
+
} catch {
|
|
2612
|
+
return;
|
|
2613
|
+
}
|
|
2614
|
+
const now = Date.now();
|
|
2615
|
+
for (const name of entries) {
|
|
2616
|
+
if (!name.startsWith(OFFLINE_DECRYPT_STAGING_DIR_PREFIX)) continue;
|
|
2617
|
+
const dir = path2.join(memoryDir, name);
|
|
2618
|
+
try {
|
|
2619
|
+
const info = await lstat(dir);
|
|
2620
|
+
if (!info.isDirectory() || info.isSymbolicLink()) continue;
|
|
2621
|
+
if (now - info.mtimeMs < OFFLINE_DECRYPT_STAGING_ORPHAN_MS) continue;
|
|
2622
|
+
await rm(dir, { recursive: true, force: true });
|
|
2623
|
+
} catch {
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
async function* readOfflineSyncFileChunks(options) {
|
|
2628
|
+
const header = await readFilePrefix(options.filePath, MAGIC_HEADER_SIZE);
|
|
2629
|
+
if (!isEncryptedFile(header)) {
|
|
2630
|
+
yield* readPlainOfflineFileChunks(options.filePath, options.chunkSize);
|
|
2631
|
+
return;
|
|
2632
|
+
}
|
|
2633
|
+
if (!options.secureStoreKey) {
|
|
2634
|
+
throw new SecureStoreLockedError(
|
|
2635
|
+
`secure-store is locked \u2014 cannot read encrypted file at ${options.filePath}. Run \`remnic secure-store unlock\` to decrypt.`
|
|
2636
|
+
);
|
|
2637
|
+
}
|
|
2638
|
+
yield* readEncryptedOfflineFileChunks({
|
|
2639
|
+
filePath: options.filePath,
|
|
2640
|
+
memoryDir: options.memoryDir,
|
|
2641
|
+
key: options.secureStoreKey,
|
|
2642
|
+
chunkSize: options.chunkSize
|
|
2643
|
+
});
|
|
2644
|
+
}
|
|
2645
|
+
async function readFilePrefix(filePath, length) {
|
|
2646
|
+
const handle = await fs17.promises.open(filePath, "r");
|
|
2647
|
+
try {
|
|
2648
|
+
const out = Buffer.alloc(length);
|
|
2649
|
+
const { bytesRead } = await handle.read(out, 0, length, 0);
|
|
2650
|
+
return out.subarray(0, bytesRead);
|
|
2651
|
+
} finally {
|
|
2652
|
+
await handle.close();
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
async function* readPlainOfflineFileChunks(filePath, chunkSize) {
|
|
2656
|
+
const stream = fs17.createReadStream(filePath, { highWaterMark: chunkSize });
|
|
2657
|
+
for await (const chunk of stream) {
|
|
2658
|
+
yield Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
async function* readEncryptedOfflineFileChunks(options) {
|
|
2662
|
+
const header = await readFilePrefix(options.filePath, MAGIC_HEADER_SIZE + ENVELOPE_HEADER_SIZE);
|
|
2663
|
+
if (header.length < MAGIC_HEADER_SIZE + ENVELOPE_HEADER_SIZE || !isEncryptedFile(header)) {
|
|
2664
|
+
throw new Error(`secure-store encrypted file is truncated: ${options.filePath}`);
|
|
2665
|
+
}
|
|
2666
|
+
const version = header.readUInt8(MAGIC_BYTES.length);
|
|
2667
|
+
const flags = header.readUInt8(MAGIC_BYTES.length + 1);
|
|
2668
|
+
if (version !== FILE_FORMAT_VERSION) {
|
|
2669
|
+
throw new Error(`secure-store file has unsupported version ${version}: ${options.filePath}`);
|
|
2670
|
+
}
|
|
2671
|
+
if (flags !== FILE_FORMAT_FLAGS) {
|
|
2672
|
+
throw new Error(`secure-store file has unsupported flags 0x${flags.toString(16)}: ${options.filePath}`);
|
|
2673
|
+
}
|
|
2674
|
+
const envelopeHeader = header.subarray(MAGIC_HEADER_SIZE);
|
|
2675
|
+
const envelopeVersion = envelopeHeader.readUInt8(ENVELOPE_LAYOUT.version);
|
|
2676
|
+
if (envelopeVersion !== ENVELOPE_VERSION) {
|
|
2677
|
+
throw new Error(`secure-store envelope has unsupported version ${envelopeVersion}: ${options.filePath}`);
|
|
2678
|
+
}
|
|
2679
|
+
const salt = envelopeHeader.subarray(ENVELOPE_LAYOUT.salt, ENVELOPE_LAYOUT.salt + ENVELOPE_SALT_LENGTH);
|
|
2680
|
+
const iv = envelopeHeader.subarray(ENVELOPE_LAYOUT.iv, ENVELOPE_LAYOUT.iv + IV_LENGTH);
|
|
2681
|
+
const authTag = envelopeHeader.subarray(ENVELOPE_LAYOUT.authTag, ENVELOPE_LAYOUT.authTag + AUTH_TAG_LENGTH);
|
|
2682
|
+
const aadCandidates = offlineFileAadCandidates(options.filePath, options.memoryDir);
|
|
2683
|
+
let lastError;
|
|
2684
|
+
for (const aad of aadCandidates) {
|
|
2685
|
+
const tempDir = await mkdtemp(path2.join(options.memoryDir, OFFLINE_DECRYPT_STAGING_DIR_PREFIX));
|
|
2686
|
+
const tempPath = path2.join(tempDir, "content");
|
|
2687
|
+
try {
|
|
2688
|
+
const decipher = createDecipheriv("aes-256-gcm", options.key, iv, {
|
|
2689
|
+
authTagLength: AUTH_TAG_LENGTH
|
|
2690
|
+
});
|
|
2691
|
+
decipher.setAuthTag(authTag);
|
|
2692
|
+
decipher.setAAD(Buffer.concat([secureStoreEnvelopeHeaderAad(salt), aad]));
|
|
2693
|
+
const output = fs17.createWriteStream(tempPath, { mode: 384 });
|
|
2694
|
+
try {
|
|
2695
|
+
const stream = fs17.createReadStream(options.filePath, {
|
|
2696
|
+
start: MAGIC_HEADER_SIZE + ENVELOPE_HEADER_SIZE,
|
|
2697
|
+
highWaterMark: options.chunkSize
|
|
2698
|
+
});
|
|
2699
|
+
for await (const encryptedChunk of stream) {
|
|
2700
|
+
const plain = decipher.update(Buffer.isBuffer(encryptedChunk) ? encryptedChunk : Buffer.from(encryptedChunk));
|
|
2701
|
+
if (plain.length > 0 && !output.write(plain)) {
|
|
2702
|
+
await new Promise((resolve2, reject) => {
|
|
2703
|
+
output.once("drain", resolve2);
|
|
2704
|
+
output.once("error", reject);
|
|
2705
|
+
});
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
const finalPlain = decipher.final();
|
|
2709
|
+
if (finalPlain.length > 0 && !output.write(finalPlain)) {
|
|
2710
|
+
await new Promise((resolve2, reject) => {
|
|
2711
|
+
output.once("drain", resolve2);
|
|
2712
|
+
output.once("error", reject);
|
|
2713
|
+
});
|
|
2714
|
+
}
|
|
2715
|
+
} finally {
|
|
2716
|
+
await closeWriteStream(output);
|
|
2254
2717
|
}
|
|
2255
|
-
|
|
2256
|
-
|
|
2718
|
+
yield* readPlainOfflineFileChunks(tempPath, options.chunkSize);
|
|
2719
|
+
return;
|
|
2720
|
+
} catch (error) {
|
|
2721
|
+
lastError = error;
|
|
2722
|
+
} finally {
|
|
2723
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
2257
2724
|
}
|
|
2258
2725
|
}
|
|
2259
|
-
|
|
2726
|
+
throw lastError instanceof Error ? lastError : new Error(`secure-store could not decrypt file: ${options.filePath}`);
|
|
2260
2727
|
}
|
|
2261
|
-
|
|
2262
|
-
const
|
|
2263
|
-
const
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
`${base}${route}?${query}`,
|
|
2269
|
-
{
|
|
2270
|
-
method: "POST",
|
|
2271
|
-
headers: {
|
|
2272
|
-
"x-remnic-source-id": encodeURIComponent("remnic-converge"),
|
|
2273
|
-
...token ? { authorization: `Bearer ${token}` } : {}
|
|
2274
|
-
}
|
|
2275
|
-
},
|
|
2276
|
-
timeoutMs
|
|
2277
|
-
).catch(() => null);
|
|
2278
|
-
if (!response?.ok) continue;
|
|
2279
|
-
const result = await response.json().catch(() => null);
|
|
2280
|
-
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) {
|
|
2281
|
-
return true;
|
|
2282
|
-
}
|
|
2728
|
+
function offlineFileAadCandidates(filePath, memoryDir) {
|
|
2729
|
+
const candidates = [filePathAad(filePath, memoryDir)];
|
|
2730
|
+
const relative = path2.relative(memoryDir, filePath);
|
|
2731
|
+
if (!relative || relative.startsWith("..") || path2.isAbsolute(relative)) return candidates;
|
|
2732
|
+
const parts = relative.split(path2.sep);
|
|
2733
|
+
if (parts[0] === "namespaces" && parts.length >= 3 && parts[1]) {
|
|
2734
|
+
candidates.push(filePathAad(filePath, path2.join(memoryDir, "namespaces", parts[1])));
|
|
2283
2735
|
}
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
const headers = {
|
|
2291
|
-
"content-type": "application/json",
|
|
2292
|
-
...token ? { authorization: `Bearer ${token}` } : {}
|
|
2293
|
-
};
|
|
2294
|
-
let previousAttemptFailed = false;
|
|
2295
|
-
for (const route of routes) {
|
|
2296
|
-
try {
|
|
2297
|
-
const response = await fetchPeerRequest(
|
|
2298
|
-
fetchImpl,
|
|
2299
|
-
`${base}${route}`,
|
|
2300
|
-
{
|
|
2301
|
-
method: "POST",
|
|
2302
|
-
headers,
|
|
2303
|
-
body: JSON.stringify({
|
|
2304
|
-
namespace,
|
|
2305
|
-
changeset: {
|
|
2306
|
-
format: OFFLINE_SYNC_CHANGESET_FORMAT,
|
|
2307
|
-
schemaVersion: 1,
|
|
2308
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2309
|
-
sourceId: "remnic-converge",
|
|
2310
|
-
includeTranscripts: false,
|
|
2311
|
-
changes: [{ type: "delete", path: filePath, baseSha256 }]
|
|
2312
|
-
}
|
|
2313
|
-
})
|
|
2314
|
-
},
|
|
2315
|
-
timeoutMs
|
|
2316
|
-
);
|
|
2317
|
-
if (!response.ok) throw new Error(`offline apply request failed: ${response.status}`);
|
|
2318
|
-
const result = await response.json().catch(() => null);
|
|
2319
|
-
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) {
|
|
2320
|
-
return false;
|
|
2321
|
-
}
|
|
2322
|
-
if (result.appliedDeletes === 1) return "applied";
|
|
2323
|
-
if (result.skipped === 1) return previousAttemptFailed ? "applied" : "skipped";
|
|
2324
|
-
return false;
|
|
2325
|
-
} catch {
|
|
2326
|
-
previousAttemptFailed = true;
|
|
2736
|
+
const memoryParts = path2.resolve(memoryDir).split(path2.sep);
|
|
2737
|
+
if (memoryParts.length >= 3 && memoryParts.at(-2) === "namespaces" && memoryParts.at(-1)) {
|
|
2738
|
+
const topLevelRoot = memoryParts.slice(0, -2).join(path2.sep) || path2.sep;
|
|
2739
|
+
const topRelative = path2.relative(topLevelRoot, filePath);
|
|
2740
|
+
if (topRelative && !topRelative.startsWith("..") && !path2.isAbsolute(topRelative) && topRelative.split(path2.sep)[0] === "namespaces" && topRelative.split(path2.sep)[1] === memoryParts.at(-1)) {
|
|
2741
|
+
candidates.push(filePathAad(filePath, topLevelRoot));
|
|
2327
2742
|
}
|
|
2328
2743
|
}
|
|
2329
|
-
return
|
|
2744
|
+
return candidates;
|
|
2745
|
+
}
|
|
2746
|
+
async function closeWriteStream(stream) {
|
|
2747
|
+
await new Promise((resolve2, reject) => {
|
|
2748
|
+
stream.once("error", reject);
|
|
2749
|
+
stream.end(() => resolve2());
|
|
2750
|
+
});
|
|
2751
|
+
}
|
|
2752
|
+
function secureStoreEnvelopeHeaderAad(salt) {
|
|
2753
|
+
const out = Buffer.alloc(1 + ENVELOPE_SALT_LENGTH);
|
|
2754
|
+
out.writeUInt8(ENVELOPE_VERSION, 0);
|
|
2755
|
+
Buffer.from(salt).copy(out, 1);
|
|
2756
|
+
return out;
|
|
2330
2757
|
}
|
|
2331
2758
|
|
|
2332
2759
|
// src/converge.ts
|
|
@@ -2365,7 +2792,7 @@ async function readLocalTombstoneEvidence(rootDir) {
|
|
|
2365
2792
|
for (const relativePath of TOMBSTONE_PATHS) {
|
|
2366
2793
|
let content;
|
|
2367
2794
|
try {
|
|
2368
|
-
content = await
|
|
2795
|
+
content = await fs18.promises.readFile(path3.join(rootDir, relativePath), "utf-8");
|
|
2369
2796
|
} catch (error) {
|
|
2370
2797
|
if (error.code === "ENOENT") continue;
|
|
2371
2798
|
throw error;
|
|
@@ -2377,20 +2804,20 @@ async function readLocalTombstoneEvidence(rootDir) {
|
|
|
2377
2804
|
return merged;
|
|
2378
2805
|
}
|
|
2379
2806
|
async function discoverCursorNamespaces(memoryDir, peerUrl) {
|
|
2380
|
-
const cursorDir =
|
|
2807
|
+
const cursorDir = path3.join(path3.resolve(memoryDir), ".remnic", "state", "converge-cursors");
|
|
2381
2808
|
let entries;
|
|
2382
2809
|
try {
|
|
2383
|
-
entries = await
|
|
2810
|
+
entries = await fs18.promises.readdir(cursorDir, { withFileTypes: true });
|
|
2384
2811
|
} catch (error) {
|
|
2385
2812
|
if (error.code === "ENOENT") return [];
|
|
2386
2813
|
throw error;
|
|
2387
2814
|
}
|
|
2388
2815
|
const namespaces = /* @__PURE__ */ new Set();
|
|
2389
2816
|
for (const entry of entries) {
|
|
2390
|
-
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
2391
|
-
const cursor = await readConvergeCursor(
|
|
2817
|
+
if (!entry.isFile() || !entry.name.endsWith(".json") || entry.name.startsWith("identity-")) continue;
|
|
2818
|
+
const cursor = await readConvergeCursor(path3.join(cursorDir, entry.name));
|
|
2392
2819
|
if (!cursor) throw new Error(`invalid converge cursor: ${entry.name}`);
|
|
2393
|
-
if (
|
|
2820
|
+
if (path3.basename(defaultConvergeCursorPath(memoryDir, peerUrl, cursor.namespace)) !== entry.name) continue;
|
|
2394
2821
|
namespaces.add(cursor.namespace);
|
|
2395
2822
|
}
|
|
2396
2823
|
return [...namespaces].sort();
|
|
@@ -2480,7 +2907,13 @@ async function computeConvergePlan(options = {}) {
|
|
|
2480
2907
|
for (const rootInfo of roots) {
|
|
2481
2908
|
const ns = rootInfo.namespace;
|
|
2482
2909
|
namespacesToPlan.add(ns);
|
|
2483
|
-
const
|
|
2910
|
+
const identityCachePath = memoryDir ? convergeIdentityCachePath(memoryDir, options.peerUrl ?? "local", ns) : void 0;
|
|
2911
|
+
const identityCache = await loadConvergeIdentityCache(identityCachePath, config.inlineSourceAttributionFormat);
|
|
2912
|
+
const classificationUpdates = /* @__PURE__ */ new Map();
|
|
2913
|
+
const io = await createOfflineStorageIo(rootInfo.rootDir, void 0, {
|
|
2914
|
+
persisted: identityCache,
|
|
2915
|
+
updates: classificationUpdates
|
|
2916
|
+
});
|
|
2484
2917
|
const snapshot = await buildOfflineSyncSnapshotFromBase({
|
|
2485
2918
|
root: rootInfo.rootDir,
|
|
2486
2919
|
sourceId: "local",
|
|
@@ -2502,17 +2935,18 @@ async function computeConvergePlan(options = {}) {
|
|
|
2502
2935
|
files,
|
|
2503
2936
|
parseMemory: parseFrontmatter,
|
|
2504
2937
|
citationTemplate: config.inlineSourceAttributionFormat,
|
|
2938
|
+
...identityCache.size > 0 ? { cachedFiles: [...identityCache.values()] } : {},
|
|
2505
2939
|
readFile: async (file) => {
|
|
2506
2940
|
const readFile3 = io.readFile;
|
|
2507
2941
|
if (!readFile3) {
|
|
2508
2942
|
manifestReadFailed = true;
|
|
2509
|
-
throw new Error("offline storage cannot read
|
|
2943
|
+
throw new Error("offline storage io cannot read manifest files");
|
|
2510
2944
|
}
|
|
2511
2945
|
try {
|
|
2512
2946
|
return await readFile3({
|
|
2513
2947
|
root: rootInfo.rootDir,
|
|
2514
2948
|
path: file.path,
|
|
2515
|
-
filePath:
|
|
2949
|
+
filePath: path3.join(rootInfo.rootDir, file.path)
|
|
2516
2950
|
});
|
|
2517
2951
|
} catch (error) {
|
|
2518
2952
|
manifestReadFailed = true;
|
|
@@ -2525,6 +2959,13 @@ async function computeConvergePlan(options = {}) {
|
|
|
2525
2959
|
}
|
|
2526
2960
|
localManifests.set(ns, manifest);
|
|
2527
2961
|
localTombstones.set(ns, tombstonedFileDigests(evidence, manifest));
|
|
2962
|
+
await saveConvergeIdentityCache(
|
|
2963
|
+
identityCachePath,
|
|
2964
|
+
manifest,
|
|
2965
|
+
config.inlineSourceAttributionFormat,
|
|
2966
|
+
identityCache,
|
|
2967
|
+
classificationUpdates
|
|
2968
|
+
);
|
|
2528
2969
|
}
|
|
2529
2970
|
}
|
|
2530
2971
|
const peerUrl = options.peerUrl;
|
|
@@ -2873,13 +3314,13 @@ async function executeConvergeApply(options = {}) {
|
|
|
2873
3314
|
const rootDir = rootMap.get(entry.namespace);
|
|
2874
3315
|
if (rootDir) {
|
|
2875
3316
|
try {
|
|
2876
|
-
const filePath =
|
|
3317
|
+
const filePath = path3.join(rootDir, localPath);
|
|
2877
3318
|
const io = await createOfflineStorageIo(rootDir);
|
|
2878
3319
|
const current = await io.readFileDigest({ root: rootDir, path: localPath, filePath });
|
|
2879
3320
|
if (current.sha256 !== entry.localSha256) {
|
|
2880
3321
|
throw new Error(`local file changed during push: ${localPath}`);
|
|
2881
3322
|
}
|
|
2882
|
-
const stat2 = await
|
|
3323
|
+
const stat2 = await fs18.promises.stat(filePath);
|
|
2883
3324
|
let chunks;
|
|
2884
3325
|
let chunkOffset = 0;
|
|
2885
3326
|
const resetChunks = async () => {
|
|
@@ -2970,7 +3411,7 @@ async function executeConvergeApply(options = {}) {
|
|
|
2970
3411
|
if (rootDir && entry.localSha256) {
|
|
2971
3412
|
try {
|
|
2972
3413
|
const io = await createOfflineStorageIo(rootDir);
|
|
2973
|
-
const filePath =
|
|
3414
|
+
const filePath = path3.join(rootDir, localPath);
|
|
2974
3415
|
const current = await io.readFileDigest({ root: rootDir, path: localPath, filePath });
|
|
2975
3416
|
if (current.sha256 === entry.localSha256) {
|
|
2976
3417
|
await io.deleteFile({ root: rootDir, path: localPath, filePath });
|
|
@@ -3023,7 +3464,7 @@ async function executeConvergeApply(options = {}) {
|
|
|
3023
3464
|
if (rootDir) {
|
|
3024
3465
|
try {
|
|
3025
3466
|
const io = await createOfflineStorageIo(rootDir);
|
|
3026
|
-
const filePath =
|
|
3467
|
+
const filePath = path3.join(rootDir, localPath);
|
|
3027
3468
|
const current = await io.readFileDigest({ root: rootDir, path: localPath, filePath });
|
|
3028
3469
|
if (current.sha256 === entry.localSha256) {
|
|
3029
3470
|
await io.deleteFile({ root: rootDir, path: localPath, filePath });
|
|
@@ -3117,41 +3558,6 @@ async function updateCursorsForPlan(plan, options) {
|
|
|
3117
3558
|
}
|
|
3118
3559
|
}
|
|
3119
3560
|
}
|
|
3120
|
-
function formatConvergeReport(plan) {
|
|
3121
|
-
const lines = [];
|
|
3122
|
-
lines.push(`Convergence Status: ${plan.converged ? "CONVERGED" : "DIVERGED"}`);
|
|
3123
|
-
lines.push("");
|
|
3124
|
-
lines.push("Per-Namespace Summary:");
|
|
3125
|
-
if (plan.byNamespace.length === 0) {
|
|
3126
|
-
lines.push(" (no namespaces evaluated)");
|
|
3127
|
-
} else {
|
|
3128
|
-
for (const report of plan.byNamespace) {
|
|
3129
|
-
lines.push(` [${report.namespace}]`);
|
|
3130
|
-
lines.push(` identical: ${report.identical}`);
|
|
3131
|
-
lines.push(` pull: ${report.pull}`);
|
|
3132
|
-
lines.push(` push: ${report.push}`);
|
|
3133
|
-
lines.push(` conflict: ${report.conflict}`);
|
|
3134
|
-
lines.push(` suppress: ${report.suppress}`);
|
|
3135
|
-
lines.push(` unresolved: ${report.unresolved}`);
|
|
3136
|
-
}
|
|
3137
|
-
}
|
|
3138
|
-
return lines.join("\n");
|
|
3139
|
-
}
|
|
3140
|
-
function formatConvergeApplyReport(result) {
|
|
3141
|
-
const lines = [];
|
|
3142
|
-
lines.push(`Convergence Execution Status: ${result.status.toUpperCase()}`);
|
|
3143
|
-
lines.push(`Converged: ${result.converged ? "YES" : "NO"}`);
|
|
3144
|
-
lines.push("");
|
|
3145
|
-
lines.push("Transfers Executed:");
|
|
3146
|
-
lines.push(` pulled: ${result.transfers.pulled}`);
|
|
3147
|
-
lines.push(` pushed: ${result.transfers.pushed}`);
|
|
3148
|
-
lines.push(` conflictsResolved: ${result.transfers.conflictsResolved}`);
|
|
3149
|
-
lines.push(` suppressed: ${result.transfers.suppressed}`);
|
|
3150
|
-
lines.push(` failed: ${result.transfers.failed}`);
|
|
3151
|
-
lines.push("");
|
|
3152
|
-
lines.push(formatConvergeReport(result.plan));
|
|
3153
|
-
return lines.join("\n");
|
|
3154
|
-
}
|
|
3155
3561
|
function convergeTimeoutFlagToMs(seconds) {
|
|
3156
3562
|
return normalizeConvergePeerRequestTimeoutMs(Math.round(seconds * 1e3), "--timeout");
|
|
3157
3563
|
}
|
|
@@ -3189,7 +3595,7 @@ Subcommands:
|
|
|
3189
3595
|
for (let i = 0; i < rest.length; i += 1) {
|
|
3190
3596
|
const arg = rest[i];
|
|
3191
3597
|
if (arg === "--token-file") {
|
|
3192
|
-
tokenFile =
|
|
3598
|
+
tokenFile = parseTokenFileFlag(rest[i + 1]);
|
|
3193
3599
|
if (tokenFile === null) {
|
|
3194
3600
|
process.stderr.write("converge: --token-file requires a path.\n");
|
|
3195
3601
|
process.exitCode = 2;
|
|
@@ -3237,8 +3643,8 @@ Subcommands:
|
|
|
3237
3643
|
i += 1;
|
|
3238
3644
|
}
|
|
3239
3645
|
}
|
|
3240
|
-
const tokenChannel =
|
|
3241
|
-
{ argvToken: peerToken, tokenFile: tokenFile ?? void 0 },
|
|
3646
|
+
const tokenChannel = resolveCredentialChannel(
|
|
3647
|
+
{ argvToken: peerToken, tokenFile: tokenFile ?? void 0, envNames: ["REMNIC_CONVERGE_PEER_TOKEN"] },
|
|
3242
3648
|
process.env
|
|
3243
3649
|
);
|
|
3244
3650
|
if (!tokenChannel.ok) {
|
|
@@ -3462,8 +3868,8 @@ function renderReplayResult(result, targetNamespace, format) {
|
|
|
3462
3868
|
}
|
|
3463
3869
|
|
|
3464
3870
|
// src/quarantine-replay.ts
|
|
3465
|
-
import * as
|
|
3466
|
-
import { EngramAccessService, Orchestrator as
|
|
3871
|
+
import * as fs19 from "fs";
|
|
3872
|
+
import { EngramAccessService, Orchestrator as Orchestrator9, initLogger as initLogger3, parseConfig as parseConfig14, resolveRemnicConfigRecord as resolveRemnicConfigRecord13 } from "@remnic/core";
|
|
3467
3873
|
import { WriteQuarantineStore } from "@remnic/core/write-quarantine.js";
|
|
3468
3874
|
function valueFlag(args, flag) {
|
|
3469
3875
|
const occurrences = args.filter((a) => a === flag).length;
|
|
@@ -3511,9 +3917,9 @@ async function runQuarantineReplay(rest, format, resolveConfigPath2) {
|
|
|
3511
3917
|
let orchestrator;
|
|
3512
3918
|
try {
|
|
3513
3919
|
const configPath = resolveConfigPath2();
|
|
3514
|
-
const raw =
|
|
3920
|
+
const raw = fs19.existsSync(configPath) ? JSON.parse(fs19.readFileSync(configPath, "utf8")) : {};
|
|
3515
3921
|
const config = parseConfig14(resolveRemnicConfigRecord13(raw));
|
|
3516
|
-
orchestrator = new
|
|
3922
|
+
orchestrator = new Orchestrator9(config);
|
|
3517
3923
|
await orchestrator.initialize();
|
|
3518
3924
|
await orchestrator.deferredReady;
|
|
3519
3925
|
const service = new EngramAccessService(orchestrator);
|
|
@@ -3543,7 +3949,7 @@ async function runQuarantineReplay(rest, format, resolveConfigPath2) {
|
|
|
3543
3949
|
}
|
|
3544
3950
|
|
|
3545
3951
|
// src/offline-impression-rotation.ts
|
|
3546
|
-
import
|
|
3952
|
+
import fs20 from "fs";
|
|
3547
3953
|
import { parseConfig as parseConfig15, resolveRemnicConfigRecord as resolveRemnicConfigRecord14, drainPendingImpressionsForOfflineSync } from "@remnic/core";
|
|
3548
3954
|
import { LastRecallStore } from "@remnic/core/recall-state";
|
|
3549
3955
|
function parseConfigQuietly(raw) {
|
|
@@ -3578,7 +3984,7 @@ function pickOfflineConfigRecord(raw) {
|
|
|
3578
3984
|
function resolveOfflineImpressionRotation(configPath) {
|
|
3579
3985
|
let raw;
|
|
3580
3986
|
try {
|
|
3581
|
-
raw =
|
|
3987
|
+
raw = fs20.existsSync(configPath) ? JSON.parse(fs20.readFileSync(configPath, "utf8")) : {};
|
|
3582
3988
|
} catch {
|
|
3583
3989
|
throw new Error(
|
|
3584
3990
|
`cannot read recall-impression rotation from ${configPath}: config file could not be read as JSON`
|
|
@@ -3609,20 +4015,20 @@ async function drainOfflineSyncImpressions(memoryDir, rotation) {
|
|
|
3609
4015
|
// src/bench-build-freshness.ts
|
|
3610
4016
|
import {
|
|
3611
4017
|
existsSync as existsSync2,
|
|
3612
|
-
lstatSync,
|
|
4018
|
+
lstatSync as lstatSync2,
|
|
3613
4019
|
readdirSync,
|
|
3614
|
-
readFileSync as
|
|
3615
|
-
statSync
|
|
4020
|
+
readFileSync as readFileSync2,
|
|
4021
|
+
statSync
|
|
3616
4022
|
} from "fs";
|
|
3617
|
-
import
|
|
4023
|
+
import path4 from "path";
|
|
3618
4024
|
import { fileURLToPath } from "url";
|
|
3619
4025
|
var STALE_BUILD_TOLERANCE_MS = 1e3;
|
|
3620
4026
|
function assertLocalBenchBuildFreshForDevelopment(currentModuleUrl) {
|
|
3621
4027
|
if (isTruthyEnv(process.env.REMNIC_BENCH_ALLOW_STALE_DIST)) {
|
|
3622
4028
|
return;
|
|
3623
4029
|
}
|
|
3624
|
-
const currentDir =
|
|
3625
|
-
const benchPackageDir =
|
|
4030
|
+
const currentDir = path4.dirname(fileURLToPath(currentModuleUrl));
|
|
4031
|
+
const benchPackageDir = path4.resolve(currentDir, "../../bench");
|
|
3626
4032
|
const freshness = checkBenchBuildFreshness(benchPackageDir);
|
|
3627
4033
|
if (!freshness.stale) {
|
|
3628
4034
|
return;
|
|
@@ -3639,30 +4045,30 @@ function assertLocalBenchBuildFreshForDevelopment(currentModuleUrl) {
|
|
|
3639
4045
|
);
|
|
3640
4046
|
}
|
|
3641
4047
|
function checkBenchBuildFreshness(benchPackageDir) {
|
|
3642
|
-
const packageJsonPath =
|
|
4048
|
+
const packageJsonPath = path4.join(benchPackageDir, "package.json");
|
|
3643
4049
|
if (!existsSync2(packageJsonPath)) {
|
|
3644
4050
|
return { stale: false };
|
|
3645
4051
|
}
|
|
3646
4052
|
let packageName;
|
|
3647
4053
|
try {
|
|
3648
|
-
packageName = JSON.parse(
|
|
4054
|
+
packageName = JSON.parse(readFileSync2(packageJsonPath, "utf8")).name;
|
|
3649
4055
|
} catch {
|
|
3650
4056
|
return { stale: false };
|
|
3651
4057
|
}
|
|
3652
4058
|
if (packageName !== "@remnic/bench") {
|
|
3653
4059
|
return { stale: false };
|
|
3654
4060
|
}
|
|
3655
|
-
const srcDir =
|
|
4061
|
+
const srcDir = path4.join(benchPackageDir, "src");
|
|
3656
4062
|
if (!isDirectory(srcDir)) {
|
|
3657
4063
|
return { stale: false };
|
|
3658
4064
|
}
|
|
3659
4065
|
const sourceRoots = [
|
|
3660
4066
|
srcDir,
|
|
3661
4067
|
packageJsonPath,
|
|
3662
|
-
|
|
3663
|
-
|
|
4068
|
+
path4.join(benchPackageDir, "tsup.config.ts"),
|
|
4069
|
+
path4.join(benchPackageDir, "tsconfig.json")
|
|
3664
4070
|
];
|
|
3665
|
-
const distPath =
|
|
4071
|
+
const distPath = path4.join(benchPackageDir, "dist", "index.js");
|
|
3666
4072
|
if (!existsSync2(distPath)) {
|
|
3667
4073
|
return {
|
|
3668
4074
|
stale: true,
|
|
@@ -3674,7 +4080,7 @@ function checkBenchBuildFreshness(benchPackageDir) {
|
|
|
3674
4080
|
if (!newestSource) {
|
|
3675
4081
|
return { stale: false };
|
|
3676
4082
|
}
|
|
3677
|
-
const distMtimeMs =
|
|
4083
|
+
const distMtimeMs = statSync(distPath).mtimeMs;
|
|
3678
4084
|
if (newestSource.mtimeMs > distMtimeMs + STALE_BUILD_TOLERANCE_MS) {
|
|
3679
4085
|
return {
|
|
3680
4086
|
stale: true,
|
|
@@ -3699,13 +4105,13 @@ function newestMtime(roots) {
|
|
|
3699
4105
|
if (!existsSync2(entryPath)) {
|
|
3700
4106
|
return;
|
|
3701
4107
|
}
|
|
3702
|
-
const stat2 =
|
|
4108
|
+
const stat2 = lstatSync2(entryPath);
|
|
3703
4109
|
if (stat2.isSymbolicLink()) {
|
|
3704
4110
|
return;
|
|
3705
4111
|
}
|
|
3706
4112
|
if (stat2.isDirectory()) {
|
|
3707
4113
|
for (const child of readdirSync(entryPath)) {
|
|
3708
|
-
visit(
|
|
4114
|
+
visit(path4.join(entryPath, child));
|
|
3709
4115
|
}
|
|
3710
4116
|
return;
|
|
3711
4117
|
}
|
|
@@ -3723,7 +4129,7 @@ function newestMtime(roots) {
|
|
|
3723
4129
|
}
|
|
3724
4130
|
function isDirectory(entryPath) {
|
|
3725
4131
|
try {
|
|
3726
|
-
return
|
|
4132
|
+
return statSync(entryPath).isDirectory();
|
|
3727
4133
|
} catch {
|
|
3728
4134
|
return false;
|
|
3729
4135
|
}
|
|
@@ -3738,18 +4144,18 @@ function isTruthyEnv(value) {
|
|
|
3738
4144
|
|
|
3739
4145
|
// src/optional-bench.ts
|
|
3740
4146
|
import { existsSync as existsSync3 } from "fs";
|
|
3741
|
-
import
|
|
4147
|
+
import path5 from "path";
|
|
3742
4148
|
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
|
|
3743
4149
|
var SPECIFIER2 = "@remnic/bench";
|
|
3744
4150
|
var TSX_ESM_API_SPECIFIER = "tsx/esm/api";
|
|
3745
4151
|
var cached2;
|
|
3746
4152
|
var cachedFromLocalWorkspaceBenchSource = false;
|
|
3747
4153
|
function resolveLocalWorkspaceBenchPaths() {
|
|
3748
|
-
const currentDir =
|
|
3749
|
-
const benchPackageDir =
|
|
4154
|
+
const currentDir = path5.dirname(fileURLToPath2(import.meta.url));
|
|
4155
|
+
const benchPackageDir = path5.resolve(currentDir, "../../bench");
|
|
3750
4156
|
return {
|
|
3751
|
-
distEntry:
|
|
3752
|
-
sourceEntry:
|
|
4157
|
+
distEntry: path5.join(benchPackageDir, "dist", "index.js"),
|
|
4158
|
+
sourceEntry: path5.join(benchPackageDir, "src", "index.ts")
|
|
3753
4159
|
};
|
|
3754
4160
|
}
|
|
3755
4161
|
async function tryImportLocalWorkspaceBenchSource(err) {
|
|
@@ -3836,9 +4242,9 @@ function assertBenchModuleFreshForDevelopment() {
|
|
|
3836
4242
|
}
|
|
3837
4243
|
|
|
3838
4244
|
// src/cmd-security.ts
|
|
3839
|
-
import
|
|
4245
|
+
import fs21 from "fs";
|
|
3840
4246
|
import {
|
|
3841
|
-
Orchestrator as
|
|
4247
|
+
Orchestrator as Orchestrator10,
|
|
3842
4248
|
parseConfig as parseConfig16,
|
|
3843
4249
|
initLogger as initLogger4,
|
|
3844
4250
|
resolveRemnicConfigRecord as resolveRemnicConfigRecord15,
|
|
@@ -3856,9 +4262,9 @@ async function cmdSecurity(rest) {
|
|
|
3856
4262
|
}
|
|
3857
4263
|
initLogger4();
|
|
3858
4264
|
const configPath = resolveConfigPath();
|
|
3859
|
-
const raw =
|
|
4265
|
+
const raw = fs21.existsSync(configPath) ? JSON.parse(fs21.readFileSync(configPath, "utf8")) : {};
|
|
3860
4266
|
const config = parseConfig16(resolveRemnicConfigRecord15(raw));
|
|
3861
|
-
const orchestrator = new
|
|
4267
|
+
const orchestrator = new Orchestrator10(config);
|
|
3862
4268
|
await orchestrator.initialize();
|
|
3863
4269
|
try {
|
|
3864
4270
|
const sinceFlag = rest.indexOf("--since");
|
|
@@ -3881,8 +4287,8 @@ async function cmdSecurity(rest) {
|
|
|
3881
4287
|
}
|
|
3882
4288
|
|
|
3883
4289
|
// src/daemon-service-candidates.ts
|
|
3884
|
-
import
|
|
3885
|
-
import
|
|
4290
|
+
import fs22 from "fs";
|
|
4291
|
+
import path6 from "path";
|
|
3886
4292
|
var LAUNCHD_LABEL = "ai.remnic.daemon";
|
|
3887
4293
|
var LEGACY_REMNIC_SERVER_LAUNCHD_LABEL = "ai.remnic.server";
|
|
3888
4294
|
var LEGACY_LAUNCHD_LABEL = "ai.engram.daemon";
|
|
@@ -3895,15 +4301,15 @@ var SYSTEMD_SERVICE = "remnic.service";
|
|
|
3895
4301
|
var LEGACY_SYSTEMD_SERVICE = "engram.service";
|
|
3896
4302
|
var SYSTEMD_SERVICE_CANDIDATES = [SYSTEMD_SERVICE, LEGACY_SYSTEMD_SERVICE];
|
|
3897
4303
|
function launchdPlistPaths(homeDir) {
|
|
3898
|
-
return LAUNCHD_LABEL_CANDIDATES.map((label) =>
|
|
4304
|
+
return LAUNCHD_LABEL_CANDIDATES.map((label) => path6.join(homeDir, "Library", "LaunchAgents", `${label}.plist`));
|
|
3899
4305
|
}
|
|
3900
4306
|
function systemdUnitPaths(homeDir) {
|
|
3901
|
-
return SYSTEMD_SERVICE_CANDIDATES.map((service) =>
|
|
4307
|
+
return SYSTEMD_SERVICE_CANDIDATES.map((service) => path6.join(homeDir, ".config", "systemd", "user", service));
|
|
3902
4308
|
}
|
|
3903
4309
|
function anyFileExists(paths) {
|
|
3904
4310
|
return paths.some((candidate) => {
|
|
3905
4311
|
try {
|
|
3906
|
-
return
|
|
4312
|
+
return fs22.statSync(candidate).isFile();
|
|
3907
4313
|
} catch {
|
|
3908
4314
|
return false;
|
|
3909
4315
|
}
|
|
@@ -3915,7 +4321,7 @@ function commandNames(command) {
|
|
|
3915
4321
|
}
|
|
3916
4322
|
function isRunnableNodeScript(filePath) {
|
|
3917
4323
|
try {
|
|
3918
|
-
const text =
|
|
4324
|
+
const text = fs22.readFileSync(filePath, "utf8").slice(0, 4096);
|
|
3919
4325
|
const firstLine = text.split(/\r?\n/, 1)[0] ?? "";
|
|
3920
4326
|
if (/^#!.*\bnode\b/.test(firstLine)) return true;
|
|
3921
4327
|
if (firstLine.startsWith("#!")) return false;
|
|
@@ -3928,20 +4334,20 @@ function isRunnableNodeScript(filePath) {
|
|
|
3928
4334
|
function resolveShimNodeScript(filePath) {
|
|
3929
4335
|
let text;
|
|
3930
4336
|
try {
|
|
3931
|
-
text =
|
|
4337
|
+
text = fs22.readFileSync(filePath, "utf8").slice(0, 16384);
|
|
3932
4338
|
} catch {
|
|
3933
4339
|
return void 0;
|
|
3934
4340
|
}
|
|
3935
|
-
const basedir =
|
|
4341
|
+
const basedir = path6.dirname(filePath);
|
|
3936
4342
|
const jsReferencePattern = /"([^"]+\.js)"|'([^']+\.js)'|([^\s"'`]+\.js)/g;
|
|
3937
4343
|
for (const match of text.matchAll(jsReferencePattern)) {
|
|
3938
4344
|
const raw = match[1] ?? match[2] ?? match[3];
|
|
3939
4345
|
if (!raw) continue;
|
|
3940
4346
|
const candidate = raw.replaceAll("${basedir}", basedir).replaceAll("$basedir", basedir).replaceAll("\\ ", " ");
|
|
3941
|
-
const resolved =
|
|
4347
|
+
const resolved = path6.isAbsolute(candidate) ? candidate : path6.resolve(basedir, candidate);
|
|
3942
4348
|
try {
|
|
3943
|
-
if (
|
|
3944
|
-
return
|
|
4349
|
+
if (fs22.statSync(resolved).isFile() && isRunnableNodeScript(resolved)) {
|
|
4350
|
+
return fs22.realpathSync(resolved);
|
|
3945
4351
|
}
|
|
3946
4352
|
} catch {
|
|
3947
4353
|
}
|
|
@@ -3949,19 +4355,19 @@ function resolveShimNodeScript(filePath) {
|
|
|
3949
4355
|
return void 0;
|
|
3950
4356
|
}
|
|
3951
4357
|
function resolveRunnableNodeScript(filePath) {
|
|
3952
|
-
const realPath =
|
|
4358
|
+
const realPath = fs22.realpathSync(filePath);
|
|
3953
4359
|
if (isRunnableNodeScript(realPath)) return realPath;
|
|
3954
4360
|
return resolveShimNodeScript(realPath);
|
|
3955
4361
|
}
|
|
3956
4362
|
function findCommandOnPath(command, pathEnv = process.env.PATH ?? "") {
|
|
3957
|
-
for (const dir of pathEnv.split(
|
|
4363
|
+
for (const dir of pathEnv.split(path6.delimiter)) {
|
|
3958
4364
|
if (!dir) continue;
|
|
3959
4365
|
for (const name of commandNames(command)) {
|
|
3960
|
-
const candidate =
|
|
4366
|
+
const candidate = path6.join(dir, name);
|
|
3961
4367
|
try {
|
|
3962
|
-
const stat2 =
|
|
4368
|
+
const stat2 = fs22.statSync(candidate);
|
|
3963
4369
|
if (!stat2.isFile()) continue;
|
|
3964
|
-
if (process.platform !== "win32")
|
|
4370
|
+
if (process.platform !== "win32") fs22.accessSync(candidate, fs22.constants.X_OK);
|
|
3965
4371
|
const runnable = resolveRunnableNodeScript(candidate);
|
|
3966
4372
|
if (runnable) return runnable;
|
|
3967
4373
|
} catch {
|
|
@@ -3971,11 +4377,11 @@ function findCommandOnPath(command, pathEnv = process.env.PATH ?? "") {
|
|
|
3971
4377
|
return void 0;
|
|
3972
4378
|
}
|
|
3973
4379
|
function serverBinWrapperRequiredPath(candidate) {
|
|
3974
|
-
const filename =
|
|
4380
|
+
const filename = path6.basename(candidate);
|
|
3975
4381
|
if (filename !== "remnic-server.js" && filename !== "engram-server.js") return void 0;
|
|
3976
|
-
const binDir =
|
|
3977
|
-
if (
|
|
3978
|
-
return
|
|
4382
|
+
const binDir = path6.dirname(candidate);
|
|
4383
|
+
if (path6.basename(binDir) !== "bin") return void 0;
|
|
4384
|
+
return path6.join(path6.dirname(binDir), "dist", "index.js");
|
|
3979
4385
|
}
|
|
3980
4386
|
|
|
3981
4387
|
// src/service-candidates.ts
|
|
@@ -3997,7 +4403,7 @@ function firstSuccessfulCandidate(candidates, attempt) {
|
|
|
3997
4403
|
}
|
|
3998
4404
|
|
|
3999
4405
|
// src/bench-args.ts
|
|
4000
|
-
import
|
|
4406
|
+
import path8 from "path";
|
|
4001
4407
|
|
|
4002
4408
|
// src/bench-flags.ts
|
|
4003
4409
|
function readBenchOptionValue(argv, flag) {
|
|
@@ -4362,7 +4768,7 @@ function collectBenchmarks(argv) {
|
|
|
4362
4768
|
}
|
|
4363
4769
|
|
|
4364
4770
|
// src/bench-args-research.ts
|
|
4365
|
-
import
|
|
4771
|
+
import path7 from "path";
|
|
4366
4772
|
function readPositiveInteger(args, flag) {
|
|
4367
4773
|
const raw = readBenchOptionValue(args, flag);
|
|
4368
4774
|
if (raw === void 0) return void 0;
|
|
@@ -4408,7 +4814,7 @@ function parseBenchResearchArgs(action, args) {
|
|
|
4408
4814
|
}
|
|
4409
4815
|
const outRaw = readBenchOptionValue(args, "--out");
|
|
4410
4816
|
if (outRaw !== void 0) {
|
|
4411
|
-
out =
|
|
4817
|
+
out = path7.resolve(expandTilde(outRaw));
|
|
4412
4818
|
}
|
|
4413
4819
|
}
|
|
4414
4820
|
const epochs = readPositiveInteger(args, "--epochs");
|
|
@@ -4422,8 +4828,8 @@ function parseBenchResearchArgs(action, args) {
|
|
|
4422
4828
|
}
|
|
4423
4829
|
return {
|
|
4424
4830
|
runRef,
|
|
4425
|
-
memoryDir: memoryDirRaw ?
|
|
4426
|
-
qmdPath: qmdPathRaw ?
|
|
4831
|
+
memoryDir: memoryDirRaw ? path7.resolve(expandTilde(memoryDirRaw)) : void 0,
|
|
4832
|
+
qmdPath: qmdPathRaw ? path7.resolve(expandTilde(qmdPathRaw)) : void 0,
|
|
4427
4833
|
collection,
|
|
4428
4834
|
users: readPositiveInteger(args, "--users"),
|
|
4429
4835
|
epochs,
|
|
@@ -4643,7 +5049,7 @@ function parseBenchArgs(argv) {
|
|
|
4643
5049
|
}
|
|
4644
5050
|
validateBenchFlags(action, args);
|
|
4645
5051
|
const driftGenPositionals = action === "drift-gen" && driftGenAction === "validate" ? collectBenchmarks(args.slice(1)) : [];
|
|
4646
|
-
const driftGenDir = driftGenPositionals[0] ?
|
|
5052
|
+
const driftGenDir = driftGenPositionals[0] ? path8.resolve(expandTilde(driftGenPositionals[0])) : void 0;
|
|
4647
5053
|
const benchmarkArgs = action === "baseline" || action === "datasets" || action === "providers" || action === "runs" || action === "drift-gen" && (args[0] === "validate" || args[0] === "generate") ? args.slice(1) : args;
|
|
4648
5054
|
const benchmarks = collectBenchmarks(benchmarkArgs);
|
|
4649
5055
|
const datasetDir = readBenchOptionValue(args, "--dataset-dir") ?? readBenchOptionValue(args, "--dataset");
|
|
@@ -5193,13 +5599,13 @@ function parseBenchArgs(argv) {
|
|
|
5193
5599
|
mcpUrl,
|
|
5194
5600
|
mcpToolMap,
|
|
5195
5601
|
mcpDemo,
|
|
5196
|
-
datasetDir: datasetDir ?
|
|
5197
|
-
resultsDir: resultsDir ?
|
|
5198
|
-
baselinesDir: baselinesDir ?
|
|
5602
|
+
datasetDir: datasetDir ? path8.resolve(expandTilde(datasetDir)) : void 0,
|
|
5603
|
+
resultsDir: resultsDir ? path8.resolve(expandTilde(resultsDir)) : void 0,
|
|
5604
|
+
baselinesDir: baselinesDir ? path8.resolve(expandTilde(baselinesDir)) : void 0,
|
|
5199
5605
|
runtimeProfile,
|
|
5200
5606
|
matrixProfiles,
|
|
5201
|
-
remnicConfigPath: remnicConfigRaw ?
|
|
5202
|
-
openclawConfigPath: openclawConfigRaw ?
|
|
5607
|
+
remnicConfigPath: remnicConfigRaw ? path8.resolve(expandTilde(remnicConfigRaw)) : void 0,
|
|
5608
|
+
openclawConfigPath: openclawConfigRaw ? path8.resolve(expandTilde(openclawConfigRaw)) : void 0,
|
|
5203
5609
|
modelSource,
|
|
5204
5610
|
gatewayAgentId,
|
|
5205
5611
|
fastGatewayAgentId,
|
|
@@ -5222,13 +5628,13 @@ function parseBenchArgs(argv) {
|
|
|
5222
5628
|
internalDisableThinking: args.includes("--internal-disable-thinking"),
|
|
5223
5629
|
internalCodexReasoningEffort,
|
|
5224
5630
|
threshold,
|
|
5225
|
-
custom: customRaw ?
|
|
5631
|
+
custom: customRaw ? path8.resolve(expandTilde(customRaw)) : void 0,
|
|
5226
5632
|
baselineAction,
|
|
5227
5633
|
datasetAction,
|
|
5228
5634
|
providerAction,
|
|
5229
5635
|
runAction,
|
|
5230
5636
|
format,
|
|
5231
|
-
output: output ?
|
|
5637
|
+
output: output ? path8.resolve(expandTilde(output)) : void 0,
|
|
5232
5638
|
target,
|
|
5233
5639
|
publishedName,
|
|
5234
5640
|
publishedSeed,
|
|
@@ -5238,24 +5644,24 @@ function parseBenchArgs(argv) {
|
|
|
5238
5644
|
publishedIngestConcurrency,
|
|
5239
5645
|
publishedTaskFilter,
|
|
5240
5646
|
memcorrectAdapter,
|
|
5241
|
-
publishedOut: publishedOutRaw ?
|
|
5647
|
+
publishedOut: publishedOutRaw ? path8.resolve(expandTilde(publishedOutRaw)) : void 0,
|
|
5242
5648
|
publishedDryRun: args.includes("--dry-run"),
|
|
5243
5649
|
requestTimeout,
|
|
5244
5650
|
localJudgeRequestTimeout,
|
|
5245
5651
|
frontierJudgeRequestTimeout,
|
|
5246
|
-
calibrationDir: calibrationDirRaw ?
|
|
5652
|
+
calibrationDir: calibrationDirRaw ? path8.resolve(expandTilde(calibrationDirRaw)) : void 0,
|
|
5247
5653
|
calibrationLocalConfigSha256,
|
|
5248
5654
|
calibrationFrontierConfigSha256,
|
|
5249
5655
|
sourceResultId,
|
|
5250
5656
|
expectedAnswerSetSha256,
|
|
5251
5657
|
expectedQuestionIdListSha256,
|
|
5252
|
-
taskIdsFile: taskIdsFileRaw ?
|
|
5658
|
+
taskIdsFile: taskIdsFileRaw ? path8.resolve(expandTilde(taskIdsFileRaw)) : void 0,
|
|
5253
5659
|
expectedTaskIdListSha256,
|
|
5254
5660
|
drainTimeout,
|
|
5255
5661
|
// Issue #1573 PR1: surface judge-cache flags into the runner options.
|
|
5256
5662
|
noJudgeCache: args.includes("--no-judge-cache"),
|
|
5257
|
-
judgeCacheDir: judgeCacheDirRaw ?
|
|
5258
|
-
localLabManifestPath: localLabManifestRaw ?
|
|
5663
|
+
judgeCacheDir: judgeCacheDirRaw ? path8.resolve(expandTilde(judgeCacheDirRaw)) : void 0,
|
|
5664
|
+
localLabManifestPath: localLabManifestRaw ? path8.resolve(expandTilde(localLabManifestRaw)) : void 0,
|
|
5259
5665
|
max429WaitMs,
|
|
5260
5666
|
disableThinking: args.includes("--disable-thinking"),
|
|
5261
5667
|
amaBenchJudgeProtocol,
|
|
@@ -5293,9 +5699,9 @@ function assertCalibrationProvenanceMatches(binding, state, benchmarkId) {
|
|
|
5293
5699
|
|
|
5294
5700
|
// src/bench-status.ts
|
|
5295
5701
|
import { mkdir, readFile, readdir as readdir2, rename, writeFile } from "fs/promises";
|
|
5296
|
-
import
|
|
5702
|
+
import path9 from "path";
|
|
5297
5703
|
function createBenchStatusPath(resultsDir, pid, startedAtMs = Date.now()) {
|
|
5298
|
-
return
|
|
5704
|
+
return path9.join(resultsDir, `bench-status-${startedAtMs}-${pid}.json`);
|
|
5299
5705
|
}
|
|
5300
5706
|
var BENCH_STATUS_FILENAME = /^bench-status-\d+-\d+\.json$/;
|
|
5301
5707
|
var VALID_BENCH_ENTRY_STATUSES = /* @__PURE__ */ new Set(["pending", "running", "complete", "failed"]);
|
|
@@ -5308,7 +5714,7 @@ async function findLatestBenchStatusFile(resultsDir) {
|
|
|
5308
5714
|
}
|
|
5309
5715
|
const candidates = entries.filter((name) => BENCH_STATUS_FILENAME.test(name)).sort().reverse();
|
|
5310
5716
|
for (const name of candidates) {
|
|
5311
|
-
const filePath =
|
|
5717
|
+
const filePath = path9.join(resultsDir, name);
|
|
5312
5718
|
const status = await readBenchStatus(filePath);
|
|
5313
5719
|
if (status) {
|
|
5314
5720
|
return filePath;
|
|
@@ -5317,7 +5723,7 @@ async function findLatestBenchStatusFile(resultsDir) {
|
|
|
5317
5723
|
return null;
|
|
5318
5724
|
}
|
|
5319
5725
|
async function atomicWriteJSON(filePath, data) {
|
|
5320
|
-
await mkdir(
|
|
5726
|
+
await mkdir(path9.dirname(filePath), { recursive: true });
|
|
5321
5727
|
const tmp = `${filePath}.${Date.now()}-${Math.random().toString(36).slice(2)}.tmp`;
|
|
5322
5728
|
await writeFile(tmp, JSON.stringify(data, null, 2) + "\n");
|
|
5323
5729
|
await rename(tmp, filePath);
|
|
@@ -5433,86 +5839,6 @@ function finalizeBenchStatus(filePath) {
|
|
|
5433
5839
|
});
|
|
5434
5840
|
}
|
|
5435
5841
|
|
|
5436
|
-
// src/bench-fallback.ts
|
|
5437
|
-
import fs22 from "fs";
|
|
5438
|
-
import path9 from "path";
|
|
5439
|
-
var FALLBACK_RESULTS_DIRNAME = "fallback-runs";
|
|
5440
|
-
function buildBenchRunnerArgs(parsed, benchmarkId, outputDir) {
|
|
5441
|
-
const args = ["--benchmark", benchmarkId];
|
|
5442
|
-
if (parsed.quick) {
|
|
5443
|
-
args.push("--lightweight");
|
|
5444
|
-
}
|
|
5445
|
-
if (parsed.publishedLimit !== void 0) {
|
|
5446
|
-
args.push("--limit", String(parsed.publishedLimit));
|
|
5447
|
-
} else if (parsed.quick) {
|
|
5448
|
-
args.push("--limit", "1");
|
|
5449
|
-
}
|
|
5450
|
-
if (parsed.datasetDir) {
|
|
5451
|
-
args.push("--dataset-dir", parsed.datasetDir);
|
|
5452
|
-
}
|
|
5453
|
-
if (outputDir) {
|
|
5454
|
-
args.push("--output-dir", outputDir);
|
|
5455
|
-
}
|
|
5456
|
-
return args;
|
|
5457
|
-
}
|
|
5458
|
-
function findUnsupportedFallbackBenchOptions(parsed) {
|
|
5459
|
-
const unsupported = [];
|
|
5460
|
-
const add = (condition, flag) => {
|
|
5461
|
-
if (condition) unsupported.push(flag);
|
|
5462
|
-
};
|
|
5463
|
-
add(parsed.modelSource !== void 0, "--model-source");
|
|
5464
|
-
add(parsed.gatewayAgentId !== void 0, "--gateway-agent-id");
|
|
5465
|
-
add(parsed.fastGatewayAgentId !== void 0, "--fast-gateway-agent-id");
|
|
5466
|
-
add(parsed.systemProvider !== void 0, "--system-provider/--provider");
|
|
5467
|
-
add(parsed.systemModel !== void 0, "--system-model/--model");
|
|
5468
|
-
add(parsed.systemBaseUrl !== void 0, "--system-base-url/--base-url");
|
|
5469
|
-
add(parsed.systemApiKey !== void 0, "--system-api-key");
|
|
5470
|
-
add(parsed.systemCodexReasoningEffort !== void 0, "--system-codex-reasoning-effort");
|
|
5471
|
-
add(parsed.systemResponderContextBudgetChars !== void 0, "--system-responder-context-budget-chars");
|
|
5472
|
-
add(parsed.systemResponderPromptBudgetChars !== void 0, "--system-responder-prompt-budget-chars");
|
|
5473
|
-
add(parsed.judgeProvider !== void 0, "--judge-provider");
|
|
5474
|
-
add(parsed.judgeModel !== void 0, "--judge-model");
|
|
5475
|
-
add(parsed.judgeBaseUrl !== void 0, "--judge-base-url");
|
|
5476
|
-
add(parsed.judgeApiKey !== void 0, "--judge-api-key");
|
|
5477
|
-
add(parsed.judgeCodexReasoningEffort !== void 0, "--judge-codex-reasoning-effort");
|
|
5478
|
-
add(parsed.internalProvider !== void 0, "--internal-provider");
|
|
5479
|
-
add(parsed.internalModel !== void 0, "--internal-model");
|
|
5480
|
-
add(parsed.internalBaseUrl !== void 0, "--internal-base-url");
|
|
5481
|
-
add(parsed.internalApiKey !== void 0, "--internal-api-key");
|
|
5482
|
-
add(parsed.internalDisableThinking === true, "--internal-disable-thinking");
|
|
5483
|
-
add(parsed.internalCodexReasoningEffort !== void 0, "--internal-codex-reasoning-effort");
|
|
5484
|
-
add(parsed.amaBenchJudgeProtocol !== void 0, "--ama-bench-judge-protocol");
|
|
5485
|
-
add(parsed.amaBenchCrossJudgeProvider !== void 0, "--ama-bench-cross-judge-provider");
|
|
5486
|
-
add(parsed.amaBenchCrossJudgeModel !== void 0, "--ama-bench-cross-judge-model");
|
|
5487
|
-
add(parsed.amaBenchCrossJudgeBaseUrl !== void 0, "--ama-bench-cross-judge-base-url");
|
|
5488
|
-
add(parsed.amaBenchCrossJudgeApiKey !== void 0, "--ama-bench-cross-judge-api-key");
|
|
5489
|
-
add(parsed.amaBenchCrossJudgeCodexReasoningEffort !== void 0, "--ama-bench-cross-judge-codex-reasoning-effort");
|
|
5490
|
-
add(parsed.disableThinking === true, "--disable-thinking");
|
|
5491
|
-
add(parsed.requestTimeout !== void 0, "--request-timeout");
|
|
5492
|
-
add(parsed.drainTimeout !== void 0, "--drain-timeout");
|
|
5493
|
-
add(parsed.max429WaitMs !== void 0, "--max-429-wait");
|
|
5494
|
-
add(parsed.publishedTrialLimit !== void 0, "--trial-limit");
|
|
5495
|
-
add(parsed.publishedTrialConcurrency !== void 0, "--trial-concurrency");
|
|
5496
|
-
add(parsed.publishedIngestConcurrency !== void 0, "--ingest-concurrency");
|
|
5497
|
-
add(parsed.publishedTaskFilter !== void 0, "--task-filter");
|
|
5498
|
-
add(parsed.publishedSeed !== void 0, "--seed");
|
|
5499
|
-
return unsupported;
|
|
5500
|
-
}
|
|
5501
|
-
function createFallbackBenchOutputDir(resultsDir, benchmarkId, pid, startedAtMs = Date.now()) {
|
|
5502
|
-
return path9.join(
|
|
5503
|
-
resultsDir,
|
|
5504
|
-
FALLBACK_RESULTS_DIRNAME,
|
|
5505
|
-
`${benchmarkId}-${startedAtMs}-${pid}`
|
|
5506
|
-
);
|
|
5507
|
-
}
|
|
5508
|
-
function resolveFallbackBenchResultPath(outputDir) {
|
|
5509
|
-
const entries = fs22.readdirSync(outputDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name).sort();
|
|
5510
|
-
if (entries.length === 0) {
|
|
5511
|
-
throw new Error(`Fallback benchmark runner did not write a JSON result artifact in ${outputDir}`);
|
|
5512
|
-
}
|
|
5513
|
-
return path9.join(outputDir, entries[0]);
|
|
5514
|
-
}
|
|
5515
|
-
|
|
5516
5842
|
// src/openclaw-upgrade-swap.ts
|
|
5517
5843
|
import fs23 from "fs";
|
|
5518
5844
|
import path10 from "path";
|
|
@@ -6254,7 +6580,7 @@ function resolveServerBin(options = {}) {
|
|
|
6254
6580
|
return resolveServerBinDetails(options).path;
|
|
6255
6581
|
}
|
|
6256
6582
|
function readVerifiedDaemonPid(options) {
|
|
6257
|
-
const
|
|
6583
|
+
const readFileSync4 = options.readFileSync ?? fs26.readFileSync;
|
|
6258
6584
|
const unlinkSync = options.unlinkSync ?? fs26.unlinkSync;
|
|
6259
6585
|
const processKill = options.processKill ?? process.kill;
|
|
6260
6586
|
const platform = options.platform ?? process.platform;
|
|
@@ -6262,7 +6588,7 @@ function readVerifiedDaemonPid(options) {
|
|
|
6262
6588
|
for (const file of options.pidFiles) {
|
|
6263
6589
|
let pid;
|
|
6264
6590
|
try {
|
|
6265
|
-
pid = parseDaemonPid(
|
|
6591
|
+
pid = parseDaemonPid(readFileSync4(file, "utf8"));
|
|
6266
6592
|
} catch {
|
|
6267
6593
|
continue;
|
|
6268
6594
|
}
|
|
@@ -6356,7 +6682,7 @@ function removePidFileBestEffort(file, unlinkSync) {
|
|
|
6356
6682
|
}
|
|
6357
6683
|
function inspectLaunchdPlist(plistPath, options = {}) {
|
|
6358
6684
|
const existsSync4 = options.existsSync ?? fs26.existsSync;
|
|
6359
|
-
const
|
|
6685
|
+
const readFileSync4 = options.readFileSync ?? fs26.readFileSync;
|
|
6360
6686
|
if (!existsSync4(plistPath)) {
|
|
6361
6687
|
return {
|
|
6362
6688
|
installed: false,
|
|
@@ -6367,7 +6693,7 @@ function inspectLaunchdPlist(plistPath, options = {}) {
|
|
|
6367
6693
|
}
|
|
6368
6694
|
let content;
|
|
6369
6695
|
try {
|
|
6370
|
-
content =
|
|
6696
|
+
content = readFileSync4(plistPath, "utf8");
|
|
6371
6697
|
} catch {
|
|
6372
6698
|
return {
|
|
6373
6699
|
installed: true,
|
|
@@ -6542,7 +6868,7 @@ import {
|
|
|
6542
6868
|
} from "@remnic/core";
|
|
6543
6869
|
|
|
6544
6870
|
// src/import-bundle-detect.ts
|
|
6545
|
-
import { lstatSync as
|
|
6871
|
+
import { lstatSync as lstatSync3, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
6546
6872
|
import path13 from "path";
|
|
6547
6873
|
function detectBundleEntries(bundleDir, options = {}) {
|
|
6548
6874
|
const readdir3 = options.readdirImpl ?? defaultReaddir;
|
|
@@ -6551,7 +6877,7 @@ function detectBundleEntries(bundleDir, options = {}) {
|
|
|
6551
6877
|
const isRegularFile = options.isRegularFileImpl ?? (options.readdirImpl !== void 0 || options.isDirectoryImpl !== void 0 ? (p) => !isDirectory2(p) : defaultIsRegularFile);
|
|
6552
6878
|
if (options.readdirImpl === void 0 && options.isDirectoryImpl === void 0) {
|
|
6553
6879
|
try {
|
|
6554
|
-
const rootStat =
|
|
6880
|
+
const rootStat = lstatSync3(bundleDir);
|
|
6555
6881
|
if (rootStat.isSymbolicLink()) {
|
|
6556
6882
|
throw new Error(
|
|
6557
6883
|
`Bundle directory '${bundleDir}' is a symbolic link. Pass the resolved directory path instead.`
|
|
@@ -6664,11 +6990,11 @@ function defaultReaddir(dir) {
|
|
|
6664
6990
|
return readdirSync2(dir);
|
|
6665
6991
|
}
|
|
6666
6992
|
function defaultReadFile(p) {
|
|
6667
|
-
return
|
|
6993
|
+
return readFileSync3(p, "utf-8");
|
|
6668
6994
|
}
|
|
6669
6995
|
function defaultIsDirectory(p) {
|
|
6670
6996
|
try {
|
|
6671
|
-
const s =
|
|
6997
|
+
const s = lstatSync3(p);
|
|
6672
6998
|
if (s.isSymbolicLink()) return false;
|
|
6673
6999
|
return s.isDirectory();
|
|
6674
7000
|
} catch {
|
|
@@ -6677,7 +7003,7 @@ function defaultIsDirectory(p) {
|
|
|
6677
7003
|
}
|
|
6678
7004
|
function defaultIsRegularFile(p) {
|
|
6679
7005
|
try {
|
|
6680
|
-
const s =
|
|
7006
|
+
const s = lstatSync3(p);
|
|
6681
7007
|
if (s.isSymbolicLink()) return false;
|
|
6682
7008
|
return s.isFile();
|
|
6683
7009
|
} catch {
|
|
@@ -8506,7 +8832,6 @@ var LOG_FILE = path18.join(PID_DIR, "server.log");
|
|
|
8506
8832
|
var LEGACY_LOG_FILE = path18.join(LEGACY_PID_DIR, "server.log");
|
|
8507
8833
|
var CLI_MODULE_DIR = path18.dirname(fileURLToPath5(import.meta.url));
|
|
8508
8834
|
var CLI_REPO_ROOT = path18.resolve(CLI_MODULE_DIR, "../../..");
|
|
8509
|
-
var EVAL_RUNNER_PATH = path18.join(CLI_REPO_ROOT, "evals", "run.ts");
|
|
8510
8835
|
var OPENCLAW_GATEWAY_LABEL = "ai.openclaw.gateway";
|
|
8511
8836
|
var CLI_SUCCESS_EXIT_GRACE_MS = 5e3;
|
|
8512
8837
|
var CLI_OUTPUT_FLUSH_GRACE_MS = 250;
|
|
@@ -8666,15 +8991,13 @@ async function loadBenchDefinitionsFromPackage() {
|
|
|
8666
8991
|
const result = benchModule.listBenchmarks();
|
|
8667
8992
|
return Array.isArray(result) ? result : void 0;
|
|
8668
8993
|
}
|
|
8994
|
+
var MISSING_BENCH_RUNTIME_HINT = "@remnic/bench. Build the workspace packages (or install @remnic/bench) and retry.";
|
|
8669
8995
|
async function resolveAllBenchmarks() {
|
|
8670
8996
|
const packageBenchmarks = await loadBenchDefinitionsFromPackage();
|
|
8671
8997
|
if (packageBenchmarks) {
|
|
8672
8998
|
return packageBenchmarks.filter((entry) => entry.runnerAvailable).map((entry) => entry.id);
|
|
8673
8999
|
}
|
|
8674
|
-
|
|
8675
|
-
return [];
|
|
8676
|
-
}
|
|
8677
|
-
return BENCHMARK_CATALOG.filter((entry) => entry.category !== "ingestion").map((entry) => entry.id);
|
|
9000
|
+
return [];
|
|
8678
9001
|
}
|
|
8679
9002
|
async function resolveKnownBenchmarkIds() {
|
|
8680
9003
|
const knownIds = new Set(BENCHMARK_IDS);
|
|
@@ -8686,65 +9009,6 @@ async function resolveKnownBenchmarkIds() {
|
|
|
8686
9009
|
}
|
|
8687
9010
|
return knownIds;
|
|
8688
9011
|
}
|
|
8689
|
-
async function runBenchViaFallback(parsed, benchmarkId, runtimeProfile) {
|
|
8690
|
-
if (parsed.taskIdsFile) {
|
|
8691
|
-
throw new Error(
|
|
8692
|
-
"Fallback benchmark runner does not support hash-pinned LoCoMo task selection. Build/install @remnic/bench to use --task-ids-file."
|
|
8693
|
-
);
|
|
8694
|
-
}
|
|
8695
|
-
if (runtimeProfile === "real" && parsed.remnicConfigPath) {
|
|
8696
|
-
resolveExistingBenchRemnicConfigPath(parsed.remnicConfigPath);
|
|
8697
|
-
}
|
|
8698
|
-
if (runtimeProfile === "openclaw-chain" && parsed.openclawConfigPath) {
|
|
8699
|
-
resolveExistingBenchOpenclawConfigPath(parsed.openclawConfigPath);
|
|
8700
|
-
}
|
|
8701
|
-
if (runtimeProfile === "real") {
|
|
8702
|
-
throw new Error(
|
|
8703
|
-
'Fallback benchmark runner does not support --runtime-profile "real". Build/install @remnic/bench to use package-backed runtime profiles.'
|
|
8704
|
-
);
|
|
8705
|
-
}
|
|
8706
|
-
if (runtimeProfile === "openclaw-chain") {
|
|
8707
|
-
throw new Error(
|
|
8708
|
-
'Fallback benchmark runner does not support --runtime-profile "openclaw-chain". Build/install @remnic/bench to use package-backed runtime profiles.'
|
|
8709
|
-
);
|
|
8710
|
-
}
|
|
8711
|
-
if (runtimeProfile === "local-lab") {
|
|
8712
|
-
throw new Error(
|
|
8713
|
-
'Fallback benchmark runner does not support --runtime-profile "local-lab". Build/install @remnic/bench to use package-backed runtime profiles with local-lab manifests.'
|
|
8714
|
-
);
|
|
8715
|
-
}
|
|
8716
|
-
const unsupportedOptions = findUnsupportedFallbackBenchOptions(parsed);
|
|
8717
|
-
if (unsupportedOptions.length > 0) {
|
|
8718
|
-
throw new Error(
|
|
8719
|
-
`Fallback benchmark runner does not support provider-backed, gateway, or thinking/timeout flags (${unsupportedOptions.join(", ")}). Build/install @remnic/bench to use those options.`
|
|
8720
|
-
);
|
|
8721
|
-
}
|
|
8722
|
-
if (!fs29.existsSync(EVAL_RUNNER_PATH)) {
|
|
8723
|
-
console.error(
|
|
8724
|
-
"Benchmark runner not found. Expected eval runner at evals/run.ts or a phase-1 @remnic/bench runtime export."
|
|
8725
|
-
);
|
|
8726
|
-
process.exit(1);
|
|
8727
|
-
}
|
|
8728
|
-
const tsxCandidates = [
|
|
8729
|
-
path18.join(CLI_REPO_ROOT, "node_modules", ".bin", "tsx"),
|
|
8730
|
-
path18.join(CLI_REPO_ROOT, "packages", "remnic-cli", "node_modules", ".bin", "tsx")
|
|
8731
|
-
];
|
|
8732
|
-
const tsxCmd = tsxCandidates.find((candidate) => fs29.existsSync(candidate)) ?? "tsx";
|
|
8733
|
-
const fallbackOutputDir = createFallbackBenchOutputDir(
|
|
8734
|
-
parsed.resultsDir ?? resolveBenchOutputDir(),
|
|
8735
|
-
benchmarkId,
|
|
8736
|
-
process.pid
|
|
8737
|
-
);
|
|
8738
|
-
const fallbackArgs = [
|
|
8739
|
-
EVAL_RUNNER_PATH,
|
|
8740
|
-
...buildBenchRunnerArgs(parsed, benchmarkId, fallbackOutputDir)
|
|
8741
|
-
];
|
|
8742
|
-
childProcess2.execFileSync(tsxCmd, fallbackArgs, {
|
|
8743
|
-
stdio: "inherit",
|
|
8744
|
-
env: process.env
|
|
8745
|
-
});
|
|
8746
|
-
return resolveFallbackBenchResultPath(fallbackOutputDir);
|
|
8747
|
-
}
|
|
8748
9012
|
function resolveBenchOutputDir() {
|
|
8749
9013
|
return path18.join(resolveHomeDir(), ".remnic", "bench", "results");
|
|
8750
9014
|
}
|
|
@@ -9099,13 +9363,7 @@ async function launchBenchUi(resultsDir) {
|
|
|
9099
9363
|
});
|
|
9100
9364
|
});
|
|
9101
9365
|
}
|
|
9102
|
-
|
|
9103
|
-
const repoCandidate = path18.join(CLI_REPO_ROOT, "evals", "datasets");
|
|
9104
|
-
if (isRepoCheckout()) {
|
|
9105
|
-
return repoCandidate;
|
|
9106
|
-
}
|
|
9107
|
-
return path18.join(resolveHomeDir(), ".remnic", "bench", "datasets");
|
|
9108
|
-
}
|
|
9366
|
+
var BENCH_DATASET_ROOT = path18.join(resolveHomeDir(), ".remnic", "bench", "datasets");
|
|
9109
9367
|
function listDownloadableBenchmarks() {
|
|
9110
9368
|
return [...DOWNLOADABLE_BENCHMARK_DATASETS];
|
|
9111
9369
|
}
|
|
@@ -9114,10 +9372,7 @@ function resolveDatasetDownloadScriptPath() {
|
|
|
9114
9372
|
if (fs29.existsSync(bundled)) {
|
|
9115
9373
|
return bundled;
|
|
9116
9374
|
}
|
|
9117
|
-
return path18.join(CLI_REPO_ROOT, "
|
|
9118
|
-
}
|
|
9119
|
-
function isRepoCheckout() {
|
|
9120
|
-
return fs29.existsSync(path18.join(CLI_REPO_ROOT, "pnpm-workspace.yaml")) && fs29.existsSync(path18.join(CLI_REPO_ROOT, "evals", "scripts", "download-datasets.sh"));
|
|
9375
|
+
return path18.join(CLI_REPO_ROOT, "packages", "remnic-cli", "assets", "download-datasets.sh");
|
|
9121
9376
|
}
|
|
9122
9377
|
function runDatasetDownloadScript(scriptPath, benchmarkId, datasetRoot, jsonMode) {
|
|
9123
9378
|
const stdio = jsonMode ? ["inherit", process.stderr, "inherit"] : "inherit";
|
|
@@ -9168,7 +9423,7 @@ function resolveBenchDatasetDir(benchmarkId, quick, datasetDirOverride) {
|
|
|
9168
9423
|
if (quick) {
|
|
9169
9424
|
return void 0;
|
|
9170
9425
|
}
|
|
9171
|
-
const datasetDir = path18.join(
|
|
9426
|
+
const datasetDir = path18.join(BENCH_DATASET_ROOT, benchmarkId);
|
|
9172
9427
|
if (isDatasetDownloaded(datasetDir, benchmarkId)) {
|
|
9173
9428
|
return datasetDir;
|
|
9174
9429
|
}
|
|
@@ -9438,7 +9693,7 @@ async function exportBenchPackageResult(parsed) {
|
|
|
9438
9693
|
process.stdout.write(rendered);
|
|
9439
9694
|
}
|
|
9440
9695
|
async function manageBenchDatasets(parsed) {
|
|
9441
|
-
const datasetRoot =
|
|
9696
|
+
const datasetRoot = BENCH_DATASET_ROOT;
|
|
9442
9697
|
const supported = listDownloadableBenchmarks();
|
|
9443
9698
|
if (parsed.datasetAction === "status") {
|
|
9444
9699
|
if (parsed.benchmarks.length > 0 || parsed.all) {
|
|
@@ -10187,7 +10442,7 @@ async function loadPublishedPromotionHelpers() {
|
|
|
10187
10442
|
const benchModule = await loadBenchModule();
|
|
10188
10443
|
return {
|
|
10189
10444
|
async promoteArtifactsToPublished(args) {
|
|
10190
|
-
const { mkdirSync, readFileSync:
|
|
10445
|
+
const { mkdirSync, readFileSync: readFileSync4, writeFileSync } = await import("fs");
|
|
10191
10446
|
const path19 = await import("path");
|
|
10192
10447
|
mkdirSync(args.publishedOutDir, { recursive: true });
|
|
10193
10448
|
if (args.artifactPaths.length === 0) {
|
|
@@ -10197,7 +10452,7 @@ async function loadPublishedPromotionHelpers() {
|
|
|
10197
10452
|
return;
|
|
10198
10453
|
}
|
|
10199
10454
|
for (const artifactPath of args.artifactPaths) {
|
|
10200
|
-
const raw =
|
|
10455
|
+
const raw = readFileSync4(artifactPath, "utf8");
|
|
10201
10456
|
const parsedUnknown = JSON.parse(raw);
|
|
10202
10457
|
const parsedObj = parsedUnknown !== null && typeof parsedUnknown === "object" && !Array.isArray(parsedUnknown) ? parsedUnknown : {};
|
|
10203
10458
|
const gitShaShort = (parsedObj.meta?.gitSha ?? "unknown").slice(0, 7);
|
|
@@ -11596,7 +11851,7 @@ async function cmdQuery(queryText, json, explain) {
|
|
|
11596
11851
|
const raw = fs29.existsSync(configPath) ? JSON.parse(fs29.readFileSync(configPath, "utf8")) : {};
|
|
11597
11852
|
const remnicCfg = resolveRemnicConfigRecord16(raw);
|
|
11598
11853
|
const config = parseConfig17(remnicCfg);
|
|
11599
|
-
const orchestrator = new
|
|
11854
|
+
const orchestrator = new Orchestrator11(config);
|
|
11600
11855
|
await orchestrator.initialize();
|
|
11601
11856
|
const service = new EngramAccessService2(orchestrator);
|
|
11602
11857
|
const recallRequest = buildQueryRecallRequest(queryText);
|
|
@@ -11776,7 +12031,7 @@ async function cmdXray(rest) {
|
|
|
11776
12031
|
const raw = fs29.existsSync(configPath) ? JSON.parse(fs29.readFileSync(configPath, "utf8")) : {};
|
|
11777
12032
|
const remnicCfg = resolveRemnicConfigRecord16(raw);
|
|
11778
12033
|
const config = parseConfig17(remnicCfg);
|
|
11779
|
-
const orchestrator = new
|
|
12034
|
+
const orchestrator = new Orchestrator11(config);
|
|
11780
12035
|
await orchestrator.initialize();
|
|
11781
12036
|
await orchestrator.deferredReady;
|
|
11782
12037
|
const service = new EngramAccessService2(orchestrator);
|
|
@@ -11807,7 +12062,7 @@ async function withLocalService(fn) {
|
|
|
11807
12062
|
initLogger5();
|
|
11808
12063
|
const configPath = resolveConfigPath();
|
|
11809
12064
|
const raw = fs29.existsSync(configPath) ? JSON.parse(fs29.readFileSync(configPath, "utf8")) : {};
|
|
11810
|
-
const orchestrator = new
|
|
12065
|
+
const orchestrator = new Orchestrator11(parseConfig17(resolveRemnicConfigRecord16(raw)));
|
|
11811
12066
|
await orchestrator.initialize();
|
|
11812
12067
|
await orchestrator.deferredReady;
|
|
11813
12068
|
const service = new EngramAccessService2(orchestrator);
|
|
@@ -11985,7 +12240,7 @@ async function cmdEnrich(rest) {
|
|
|
11985
12240
|
pipelineConfig2.providers = [
|
|
11986
12241
|
{ id: "web-search", enabled: true, costTier: "cheap" }
|
|
11987
12242
|
];
|
|
11988
|
-
const orchestrator2 = new
|
|
12243
|
+
const orchestrator2 = new Orchestrator11(config);
|
|
11989
12244
|
await orchestrator2.initialize();
|
|
11990
12245
|
await orchestrator2.deferredReady;
|
|
11991
12246
|
const searchBackend2 = orchestrator2.qmd;
|
|
@@ -12021,7 +12276,7 @@ Registered providers:`);
|
|
|
12021
12276
|
console.error("Usage: remnic enrich <entity-name> | --all | --dry-run | audit | providers");
|
|
12022
12277
|
process.exit(1);
|
|
12023
12278
|
}
|
|
12024
|
-
const orchestrator = new
|
|
12279
|
+
const orchestrator = new Orchestrator11(config);
|
|
12025
12280
|
await orchestrator.initialize();
|
|
12026
12281
|
await orchestrator.deferredReady;
|
|
12027
12282
|
const storage = await orchestrator.getStorage(config.defaultNamespace);
|
|
@@ -12298,7 +12553,7 @@ async function cmdBriefing(rest) {
|
|
|
12298
12553
|
process.exit(1);
|
|
12299
12554
|
}
|
|
12300
12555
|
const format = effectiveFormatFlag === "json" ? "json" : effectiveFormatFlag === "markdown" ? "markdown" : config.briefing.defaultFormat;
|
|
12301
|
-
const orchestrator = new
|
|
12556
|
+
const orchestrator = new Orchestrator11(config);
|
|
12302
12557
|
await orchestrator.initialize();
|
|
12303
12558
|
const storage = await orchestrator.getStorage(config.defaultNamespace);
|
|
12304
12559
|
const calendarSource = config.briefing.calendarSource ? new FileCalendarSource(config.briefing.calendarSource) : void 0;
|
|
@@ -12751,7 +13006,7 @@ async function cmdReview(action, rest) {
|
|
|
12751
13006
|
console.error("Usage: remnic review <approve|dismiss|flag> <id>");
|
|
12752
13007
|
process.exit(1);
|
|
12753
13008
|
}
|
|
12754
|
-
const storage = new
|
|
13009
|
+
const storage = new StorageManager4(memoryDir);
|
|
12755
13010
|
const configPath = resolveConfigPath();
|
|
12756
13011
|
let tombstonesConfig = null;
|
|
12757
13012
|
try {
|
|
@@ -12877,14 +13132,25 @@ function resolveOfflineRemoteUrl(args) {
|
|
|
12877
13132
|
}
|
|
12878
13133
|
return parsed;
|
|
12879
13134
|
}
|
|
13135
|
+
var OFFLINE_TOKEN_ENV_NAMES = ["REMNIC_OFFLINE_TOKEN", "REMNIC_AUTH_TOKEN", "ENGRAM_AUTH_TOKEN"];
|
|
12880
13136
|
function resolveOfflineToken(args) {
|
|
12881
|
-
const
|
|
12882
|
-
|
|
12883
|
-
|
|
12884
|
-
|
|
13137
|
+
const channel = resolveCredentialChannel(
|
|
13138
|
+
{
|
|
13139
|
+
argvToken: resolveRequiredValueFlag(args, "--token"),
|
|
13140
|
+
tokenFile: resolveRequiredValueFlag(args, "--token-file"),
|
|
13141
|
+
envNames: OFFLINE_TOKEN_ENV_NAMES
|
|
13142
|
+
},
|
|
13143
|
+
process.env
|
|
13144
|
+
);
|
|
13145
|
+
if (!channel.ok) {
|
|
13146
|
+
throw new Error(`offline: ${channel.error}`);
|
|
13147
|
+
}
|
|
13148
|
+
if (channel.tokenFromArgv) {
|
|
13149
|
+
process.stderr.write(
|
|
13150
|
+
"offline: note: --token is argv-visible; prefer --token-file or REMNIC_OFFLINE_TOKEN\n"
|
|
12885
13151
|
);
|
|
12886
13152
|
}
|
|
12887
|
-
return token
|
|
13153
|
+
return channel.token?.trim();
|
|
12888
13154
|
}
|
|
12889
13155
|
function offlineEndpoint(remoteUrl, pathname, params = {}) {
|
|
12890
13156
|
const url = new URL(remoteUrl);
|
|
@@ -14600,7 +14866,9 @@ async function cmdOffline(action, rest, json) {
|
|
|
14600
14866
|
|
|
14601
14867
|
Options:
|
|
14602
14868
|
--remote-url <url> Remote Remnic server URL, e.g. http://home:4242 (--remote alias accepted)
|
|
14603
|
-
--token <token> Bearer token for the remote server
|
|
14869
|
+
--token <token> Bearer token for the remote server (argv-visible;
|
|
14870
|
+
prefer --token-file or the env fallbacks)
|
|
14871
|
+
--token-file <path> Read the bearer token from a 0600 regular file
|
|
14604
14872
|
--namespace <name> Namespace to sync
|
|
14605
14873
|
--memory-dir <dir> Local memory dir (defaults to resolved memoryDir)
|
|
14606
14874
|
--state <path> Override offline sync state file
|
|
@@ -14612,7 +14880,9 @@ Options:
|
|
|
14612
14880
|
--json JSON output
|
|
14613
14881
|
|
|
14614
14882
|
Environment fallbacks:
|
|
14615
|
-
REMNIC_OFFLINE_REMOTE_URL, REMNIC_OFFLINE_TOKEN, REMNIC_AUTH_TOKEN
|
|
14883
|
+
REMNIC_OFFLINE_REMOTE_URL, REMNIC_OFFLINE_TOKEN, REMNIC_AUTH_TOKEN,
|
|
14884
|
+
ENGRAM_AUTH_TOKEN (legacy). Token precedence: --token > --token-file >
|
|
14885
|
+
REMNIC_OFFLINE_TOKEN > REMNIC_AUTH_TOKEN > ENGRAM_AUTH_TOKEN.`);
|
|
14616
14886
|
return;
|
|
14617
14887
|
}
|
|
14618
14888
|
const memoryDir = path18.resolve(expandTilde(resolveRequiredValueFlag(rest, "--memory-dir") ?? resolveMemoryDir()));
|
|
@@ -14634,7 +14904,8 @@ Environment fallbacks:
|
|
|
14634
14904
|
const impressionRotation = resolveOfflineImpressionRotation(configPath);
|
|
14635
14905
|
const needsRemote = action === "prepare" || action === "sync" || action === "watch";
|
|
14636
14906
|
const remoteUrl = needsRemote ? resolveOfflineRemoteUrl(rest) : resolveOptionalOfflineRemoteUrl(rest);
|
|
14637
|
-
const
|
|
14907
|
+
const knownAction = needsRemote || action === "status";
|
|
14908
|
+
const token = knownAction ? resolveOfflineToken(rest) : void 0;
|
|
14638
14909
|
const statePath = statePathExplicit ? path18.resolve(expandTilde(stateOverride)) : remoteUrl !== void 0 ? defaultOfflineSyncStatePath(memoryDir, remoteUrl, namespace) : void 0;
|
|
14639
14910
|
if (action === "prepare") {
|
|
14640
14911
|
if (!remoteUrl || !token || !statePath) throw new Error("offline prepare requires remote URL and token");
|
|
@@ -15206,7 +15477,7 @@ async function cmdConnectors(action, rest, json) {
|
|
|
15206
15477
|
const raw = fs29.existsSync(configPath) ? JSON.parse(fs29.readFileSync(configPath, "utf8")) : {};
|
|
15207
15478
|
const remnicCfg = resolveRemnicConfigRecord16(raw);
|
|
15208
15479
|
const config = parseConfig17(remnicCfg);
|
|
15209
|
-
const orchestrator = new
|
|
15480
|
+
const orchestrator = new Orchestrator11(config);
|
|
15210
15481
|
try {
|
|
15211
15482
|
await orchestrator.initialize();
|
|
15212
15483
|
await orchestrator.deferredReady;
|
|
@@ -15562,7 +15833,7 @@ async function cmdLegacyBenchmark(action, rest, json) {
|
|
|
15562
15833
|
const raw = fs29.existsSync(configPath) ? JSON.parse(fs29.readFileSync(configPath, "utf8")) : {};
|
|
15563
15834
|
const remnicCfg = resolveRemnicConfigRecord16(raw);
|
|
15564
15835
|
const config = parseConfig17(remnicCfg);
|
|
15565
|
-
const orchestrator = new
|
|
15836
|
+
const orchestrator = new Orchestrator11(config);
|
|
15566
15837
|
const service = new EngramAccessService2(orchestrator);
|
|
15567
15838
|
const { runBenchSuite, loadBaseline, checkRegression } = await loadBenchModule();
|
|
15568
15839
|
const benchConfig = {
|
|
@@ -15729,6 +16000,10 @@ async function cmdBench(rest) {
|
|
|
15729
16000
|
}
|
|
15730
16001
|
return;
|
|
15731
16002
|
}
|
|
16003
|
+
if (parsed.all && !await tryLoadBenchModule()) {
|
|
16004
|
+
console.error(`ERROR: bench run --all requires ${MISSING_BENCH_RUNTIME_HINT}`);
|
|
16005
|
+
process.exit(1);
|
|
16006
|
+
}
|
|
15732
16007
|
let selectedBenchmarks = parsed.all ? await resolveAllBenchmarks() : parsed.benchmarks;
|
|
15733
16008
|
if (selectedBenchmarks.length === 0) {
|
|
15734
16009
|
console.error(
|
|
@@ -15839,12 +16114,9 @@ async function cmdBench(rest) {
|
|
|
15839
16114
|
} catch {
|
|
15840
16115
|
}
|
|
15841
16116
|
} else {
|
|
15842
|
-
|
|
15843
|
-
|
|
15844
|
-
|
|
15845
|
-
await updateBenchmarkCompleted(benchStatusPath, statusId, fallbackResultPath);
|
|
15846
|
-
} catch {
|
|
15847
|
-
}
|
|
16117
|
+
throw new Error(
|
|
16118
|
+
`Benchmark "${benchmarkId}" requires ${MISSING_BENCH_RUNTIME_HINT}`
|
|
16119
|
+
);
|
|
15848
16120
|
}
|
|
15849
16121
|
} catch (err) {
|
|
15850
16122
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -17783,7 +18055,7 @@ Other:
|
|
|
17783
18055
|
const raw = fs29.existsSync(configPath) ? JSON.parse(fs29.readFileSync(configPath, "utf8")) : {};
|
|
17784
18056
|
const remnicCfg = resolveRemnicConfigRecord16(raw);
|
|
17785
18057
|
const config = parseConfig17(remnicCfg);
|
|
17786
|
-
orchestratorSingleton = new
|
|
18058
|
+
orchestratorSingleton = new Orchestrator11(config);
|
|
17787
18059
|
await orchestratorSingleton.initialize();
|
|
17788
18060
|
await orchestratorSingleton.deferredReady;
|
|
17789
18061
|
}
|