@remnic/cli 9.41.0 → 9.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1638 -897
- package/package.json +29 -29
package/dist/index.js
CHANGED
|
@@ -18,15 +18,15 @@ async function persistEnrichmentCandidate(storage, entityName, candidate) {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
// src/index.ts
|
|
21
|
-
import
|
|
21
|
+
import fs12 from "fs";
|
|
22
22
|
import os from "os";
|
|
23
|
-
import
|
|
24
|
-
import { createHash as
|
|
23
|
+
import path15 from "path";
|
|
24
|
+
import { createHash as createHash3 } from "crypto";
|
|
25
25
|
import * as childProcess2 from "child_process";
|
|
26
26
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
27
27
|
import { gzipSync } from "zlib";
|
|
28
28
|
import {
|
|
29
|
-
parseConfig as
|
|
29
|
+
parseConfig as parseConfig5,
|
|
30
30
|
isOpenaiApiKeyDisabled,
|
|
31
31
|
resolveEnvVars,
|
|
32
32
|
resolveRemnicConfigRecord as resolveRemnicConfigRecord4,
|
|
@@ -110,15 +110,15 @@ import {
|
|
|
110
110
|
parseXrayCliOptions,
|
|
111
111
|
renderXray,
|
|
112
112
|
OFFLINE_SYNC_APPLY_MAX_BODY_BYTES,
|
|
113
|
-
OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES,
|
|
114
|
-
OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES as
|
|
113
|
+
OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES as OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES2,
|
|
114
|
+
OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES as OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES3,
|
|
115
115
|
OFFLINE_SYNC_SNAPSHOT_BASE_MAX_BODY_BYTES,
|
|
116
|
-
applyOfflineSyncFileContentChunk,
|
|
116
|
+
applyOfflineSyncFileContentChunk as applyOfflineSyncFileContentChunk2,
|
|
117
117
|
applyOfflineSyncSnapshot,
|
|
118
118
|
buildOfflineSyncChangesetFromSnapshot,
|
|
119
119
|
drainPendingLifecycleForOfflineSync,
|
|
120
120
|
compileOfflineSyncExcludeGlobs,
|
|
121
|
-
buildOfflineSyncSnapshotFromBase,
|
|
121
|
+
buildOfflineSyncSnapshotFromBase as buildOfflineSyncSnapshotFromBase2,
|
|
122
122
|
defaultOfflineSyncStatePath,
|
|
123
123
|
normalizeOfflineSyncSnapshot,
|
|
124
124
|
offlineSyncStateFromSnapshot,
|
|
@@ -238,121 +238,1115 @@ async function loadWecloneExportModule() {
|
|
|
238
238
|
return cached;
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
// src/
|
|
242
|
-
import
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
241
|
+
// src/converge.ts
|
|
242
|
+
import * as fs3 from "fs";
|
|
243
|
+
import { createHash as createHash2 } from "crypto";
|
|
244
|
+
import * as path2 from "path";
|
|
245
|
+
import {
|
|
246
|
+
parseConfig as parseConfig2,
|
|
247
|
+
buildOfflineSyncSnapshotFromBase,
|
|
248
|
+
OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES,
|
|
249
|
+
OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES as OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES2,
|
|
250
|
+
applyOfflineSyncFileContentChunk
|
|
251
|
+
} from "@remnic/core";
|
|
252
|
+
import { resolveCorpusNamespaceRoots } from "@remnic/core/corpus-watermark.js";
|
|
253
|
+
import { listNamespaces } from "@remnic/core/namespaces/migrate.js";
|
|
254
|
+
import {
|
|
255
|
+
planReconciliation
|
|
256
|
+
} from "@remnic/core/reconcile/plan.js";
|
|
257
|
+
import {
|
|
258
|
+
defaultConvergeCursorPath,
|
|
259
|
+
readConvergeCursor,
|
|
260
|
+
writeConvergeCursor
|
|
261
|
+
} from "@remnic/core/reconcile/cursor.js";
|
|
262
|
+
|
|
263
|
+
// src/offline-storage-io.ts
|
|
264
|
+
import { mkdtemp, readdir, lstat, rm } from "fs/promises";
|
|
265
|
+
import fs2 from "fs";
|
|
266
|
+
import path from "path";
|
|
267
|
+
import { createHash, createDecipheriv } from "crypto";
|
|
268
|
+
import {
|
|
269
|
+
OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES,
|
|
270
|
+
StorageManager
|
|
271
|
+
} from "@remnic/core";
|
|
272
|
+
import { OFFLINE_DECRYPT_STAGING_DIR_PREFIX } from "@remnic/core/offline-sync-exclude-globs";
|
|
273
|
+
import {
|
|
274
|
+
AUTH_TAG_LENGTH,
|
|
275
|
+
ENVELOPE_HEADER_SIZE,
|
|
276
|
+
ENVELOPE_LAYOUT,
|
|
277
|
+
ENVELOPE_SALT_LENGTH,
|
|
278
|
+
ENVELOPE_VERSION,
|
|
279
|
+
FILE_FORMAT_FLAGS,
|
|
280
|
+
FILE_FORMAT_VERSION,
|
|
281
|
+
IV_LENGTH,
|
|
282
|
+
MAGIC_BYTES,
|
|
283
|
+
MAGIC_HEADER_SIZE,
|
|
284
|
+
SecureStoreLockedError,
|
|
285
|
+
filePathAad,
|
|
286
|
+
isEncryptedFile,
|
|
287
|
+
keyring,
|
|
288
|
+
readHeader,
|
|
289
|
+
secureStoreDir
|
|
290
|
+
} from "@remnic/core/secure-store";
|
|
291
|
+
async function createConfiguredOfflineStorage(memoryDir, secureStoreEncryptOnWrite = true) {
|
|
292
|
+
const storage = new StorageManager(memoryDir);
|
|
293
|
+
const header = await readHeader(memoryDir);
|
|
294
|
+
let secureStoreKey = null;
|
|
295
|
+
let secureStoreRequired = false;
|
|
296
|
+
if (header) {
|
|
297
|
+
secureStoreRequired = true;
|
|
298
|
+
storage.setSecureStoreRequired(true);
|
|
299
|
+
const key = keyring.getKey(secureStoreDir(memoryDir));
|
|
300
|
+
if (key) {
|
|
301
|
+
await storage.setSecureStoreKeyAndWait(key, secureStoreEncryptOnWrite);
|
|
302
|
+
secureStoreKey = key;
|
|
303
|
+
}
|
|
248
304
|
}
|
|
249
|
-
return {
|
|
305
|
+
return { storage, secureStoreKey, secureStoreRequired };
|
|
250
306
|
}
|
|
251
|
-
function
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
detail: "config `namespace` is set but is not a non-empty string",
|
|
257
|
-
remediation: "Set `namespace` to a non-empty string (a namespacePolicies name or the default namespace), or remove it."
|
|
258
|
-
};
|
|
307
|
+
async function createOfflineStorageForPath(memoryDir, filePath, configured, secureStoreEncryptOnWrite) {
|
|
308
|
+
const memoryRoot = path.resolve(memoryDir);
|
|
309
|
+
const stateDir = path.dirname(filePath);
|
|
310
|
+
if (path.basename(stateDir) !== "state" || path.basename(filePath) !== "memory-lifecycle-ledger.jsonl") {
|
|
311
|
+
throw new Error(`invalid lifecycle ledger path: ${filePath}`);
|
|
259
312
|
}
|
|
260
|
-
|
|
261
|
-
|
|
313
|
+
const storageRoot = path.resolve(path.dirname(stateDir));
|
|
314
|
+
if (storageRoot !== memoryRoot && !storageRoot.startsWith(`${memoryRoot}${path.sep}`)) {
|
|
315
|
+
throw new Error(`lifecycle ledger path is outside the offline memory directory: ${filePath}`);
|
|
316
|
+
}
|
|
317
|
+
const storage = new StorageManager(storageRoot);
|
|
318
|
+
if (configured.secureStoreRequired) {
|
|
319
|
+
storage.setSecureStoreRequired(true);
|
|
320
|
+
}
|
|
321
|
+
if (configured.secureStoreKey) {
|
|
322
|
+
await storage.setSecureStoreKeyAndWait(configured.secureStoreKey, secureStoreEncryptOnWrite);
|
|
323
|
+
}
|
|
324
|
+
return storage;
|
|
325
|
+
}
|
|
326
|
+
async function createOfflineStorageIo(memoryDir, configuredStorage) {
|
|
327
|
+
await cleanupOrphanedOfflineDecryptStaging(memoryDir);
|
|
328
|
+
const { storage, secureStoreKey } = configuredStorage ?? await createConfiguredOfflineStorage(memoryDir);
|
|
262
329
|
return {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
330
|
+
readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
|
|
331
|
+
readFileDigest: async ({ filePath }) => {
|
|
332
|
+
const hash = createHash("sha256");
|
|
333
|
+
let bytes = 0;
|
|
334
|
+
for await (const rawChunk of readOfflineSyncFileChunks({
|
|
335
|
+
filePath,
|
|
336
|
+
memoryDir,
|
|
337
|
+
secureStoreKey,
|
|
338
|
+
chunkSize: OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES
|
|
339
|
+
})) {
|
|
340
|
+
const chunk = Buffer.isBuffer(rawChunk) ? rawChunk : Buffer.from(rawChunk);
|
|
341
|
+
hash.update(chunk);
|
|
342
|
+
bytes += chunk.length;
|
|
343
|
+
}
|
|
344
|
+
return {
|
|
345
|
+
sha256: hash.digest("hex"),
|
|
346
|
+
bytes
|
|
347
|
+
};
|
|
348
|
+
},
|
|
349
|
+
readFileChunks: ({ filePath, chunkSize }) => readOfflineSyncFileChunks({
|
|
350
|
+
filePath,
|
|
351
|
+
memoryDir,
|
|
352
|
+
secureStoreKey,
|
|
353
|
+
chunkSize
|
|
354
|
+
}),
|
|
355
|
+
writeFile: async ({ filePath, content }) => storage.writeOfflineSyncFile(filePath, content),
|
|
356
|
+
writeStagingFile: async ({ filePath, content }) => storage.writeOfflineSyncStagingFile(filePath, content),
|
|
357
|
+
writeFileChunks: async ({ filePath, chunks }) => storage.writeOfflineSyncFileChunks(filePath, chunks),
|
|
358
|
+
deleteFile: async ({ filePath }) => storage.deleteOfflineSyncFile(filePath)
|
|
268
359
|
};
|
|
269
360
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
if (format === "json") {
|
|
278
|
-
const summary = records.map((record) => ({
|
|
279
|
-
timestamp: record.timestamp,
|
|
280
|
-
operation: record.operation,
|
|
281
|
-
principal: record.principal,
|
|
282
|
-
attemptedNamespace: record.attemptedNamespace
|
|
283
|
-
}));
|
|
284
|
-
return JSON.stringify(summary, null, 2);
|
|
361
|
+
var OFFLINE_DECRYPT_STAGING_ORPHAN_MS = 60 * 60 * 1e3;
|
|
362
|
+
async function cleanupOrphanedOfflineDecryptStaging(memoryDir) {
|
|
363
|
+
let entries;
|
|
364
|
+
try {
|
|
365
|
+
entries = await readdir(memoryDir);
|
|
366
|
+
} catch {
|
|
367
|
+
return;
|
|
285
368
|
}
|
|
286
|
-
|
|
287
|
-
|
|
369
|
+
const now = Date.now();
|
|
370
|
+
for (const name of entries) {
|
|
371
|
+
if (!name.startsWith(OFFLINE_DECRYPT_STAGING_DIR_PREFIX)) continue;
|
|
372
|
+
const dir = path.join(memoryDir, name);
|
|
373
|
+
try {
|
|
374
|
+
const info = await lstat(dir);
|
|
375
|
+
if (!info.isDirectory() || info.isSymbolicLink()) continue;
|
|
376
|
+
if (now - info.mtimeMs < OFFLINE_DECRYPT_STAGING_ORPHAN_MS) continue;
|
|
377
|
+
await rm(dir, { recursive: true, force: true });
|
|
378
|
+
} catch {
|
|
379
|
+
}
|
|
288
380
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
381
|
+
}
|
|
382
|
+
async function* readOfflineSyncFileChunks(options) {
|
|
383
|
+
const header = await readFilePrefix(options.filePath, MAGIC_HEADER_SIZE);
|
|
384
|
+
if (!isEncryptedFile(header)) {
|
|
385
|
+
yield* readPlainOfflineFileChunks(options.filePath, options.chunkSize);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if (!options.secureStoreKey) {
|
|
389
|
+
throw new SecureStoreLockedError(
|
|
390
|
+
`secure-store is locked \u2014 cannot read encrypted file at ${options.filePath}. Run \`remnic secure-store unlock\` to decrypt.`
|
|
294
391
|
);
|
|
295
392
|
}
|
|
296
|
-
|
|
393
|
+
yield* readEncryptedOfflineFileChunks({
|
|
394
|
+
filePath: options.filePath,
|
|
395
|
+
memoryDir: options.memoryDir,
|
|
396
|
+
key: options.secureStoreKey,
|
|
397
|
+
chunkSize: options.chunkSize
|
|
398
|
+
});
|
|
297
399
|
}
|
|
298
|
-
async function
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
const
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
...basePayload,
|
|
307
|
-
namespace: opts.targetNamespace,
|
|
308
|
-
suppressQuarantine: true,
|
|
309
|
-
idempotencyKey,
|
|
310
|
-
...principal ? { authenticatedPrincipal: principal } : {}
|
|
311
|
-
};
|
|
312
|
-
try {
|
|
313
|
-
await opts.submit(record.operation, request);
|
|
314
|
-
} catch (err) {
|
|
315
|
-
result.failures.push({
|
|
316
|
-
operation: record.operation,
|
|
317
|
-
attemptedNamespace: opts.targetNamespace,
|
|
318
|
-
error: err instanceof Error ? err.message : String(err)
|
|
319
|
-
});
|
|
320
|
-
continue;
|
|
321
|
-
}
|
|
322
|
-
try {
|
|
323
|
-
const removed = await opts.store.removeEntry(entry.path);
|
|
324
|
-
if (removed) {
|
|
325
|
-
result.replayed += 1;
|
|
326
|
-
} else {
|
|
327
|
-
result.deleteFailures.push({
|
|
328
|
-
path: entry.path,
|
|
329
|
-
error: "entry not removed (outside quarantine root or already absent)"
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
} catch (err) {
|
|
333
|
-
result.deleteFailures.push({
|
|
334
|
-
path: entry.path,
|
|
335
|
-
error: err instanceof Error ? err.message : String(err)
|
|
336
|
-
});
|
|
337
|
-
}
|
|
400
|
+
async function readFilePrefix(filePath, length) {
|
|
401
|
+
const handle = await fs2.promises.open(filePath, "r");
|
|
402
|
+
try {
|
|
403
|
+
const out = Buffer.alloc(length);
|
|
404
|
+
const { bytesRead } = await handle.read(out, 0, length, 0);
|
|
405
|
+
return out.subarray(0, bytesRead);
|
|
406
|
+
} finally {
|
|
407
|
+
await handle.close();
|
|
338
408
|
}
|
|
339
|
-
return result;
|
|
340
409
|
}
|
|
341
|
-
function
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
targetNamespace,
|
|
346
|
-
replayed: result.replayed,
|
|
347
|
-
failures: result.failures,
|
|
348
|
-
deleteFailures: result.deleteFailures
|
|
349
|
-
},
|
|
350
|
-
null,
|
|
351
|
-
2
|
|
352
|
-
);
|
|
410
|
+
async function* readPlainOfflineFileChunks(filePath, chunkSize) {
|
|
411
|
+
const stream = fs2.createReadStream(filePath, { highWaterMark: chunkSize });
|
|
412
|
+
for await (const chunk of stream) {
|
|
413
|
+
yield Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
353
414
|
}
|
|
354
|
-
|
|
355
|
-
|
|
415
|
+
}
|
|
416
|
+
async function* readEncryptedOfflineFileChunks(options) {
|
|
417
|
+
const header = await readFilePrefix(options.filePath, MAGIC_HEADER_SIZE + ENVELOPE_HEADER_SIZE);
|
|
418
|
+
if (header.length < MAGIC_HEADER_SIZE + ENVELOPE_HEADER_SIZE || !isEncryptedFile(header)) {
|
|
419
|
+
throw new Error(`secure-store encrypted file is truncated: ${options.filePath}`);
|
|
420
|
+
}
|
|
421
|
+
const version = header.readUInt8(MAGIC_BYTES.length);
|
|
422
|
+
const flags = header.readUInt8(MAGIC_BYTES.length + 1);
|
|
423
|
+
if (version !== FILE_FORMAT_VERSION) {
|
|
424
|
+
throw new Error(`secure-store file has unsupported version ${version}: ${options.filePath}`);
|
|
425
|
+
}
|
|
426
|
+
if (flags !== FILE_FORMAT_FLAGS) {
|
|
427
|
+
throw new Error(`secure-store file has unsupported flags 0x${flags.toString(16)}: ${options.filePath}`);
|
|
428
|
+
}
|
|
429
|
+
const envelopeHeader = header.subarray(MAGIC_HEADER_SIZE);
|
|
430
|
+
const envelopeVersion = envelopeHeader.readUInt8(ENVELOPE_LAYOUT.version);
|
|
431
|
+
if (envelopeVersion !== ENVELOPE_VERSION) {
|
|
432
|
+
throw new Error(`secure-store envelope has unsupported version ${envelopeVersion}: ${options.filePath}`);
|
|
433
|
+
}
|
|
434
|
+
const salt = envelopeHeader.subarray(
|
|
435
|
+
ENVELOPE_LAYOUT.salt,
|
|
436
|
+
ENVELOPE_LAYOUT.salt + ENVELOPE_SALT_LENGTH
|
|
437
|
+
);
|
|
438
|
+
const iv = envelopeHeader.subarray(ENVELOPE_LAYOUT.iv, ENVELOPE_LAYOUT.iv + IV_LENGTH);
|
|
439
|
+
const authTag = envelopeHeader.subarray(
|
|
440
|
+
ENVELOPE_LAYOUT.authTag,
|
|
441
|
+
ENVELOPE_LAYOUT.authTag + AUTH_TAG_LENGTH
|
|
442
|
+
);
|
|
443
|
+
const aadCandidates = offlineFileAadCandidates(options.filePath, options.memoryDir);
|
|
444
|
+
let lastError;
|
|
445
|
+
for (const aad of aadCandidates) {
|
|
446
|
+
const tempDir = await mkdtemp(path.join(options.memoryDir, OFFLINE_DECRYPT_STAGING_DIR_PREFIX));
|
|
447
|
+
const tempPath = path.join(tempDir, "content");
|
|
448
|
+
try {
|
|
449
|
+
const decipher = createDecipheriv("aes-256-gcm", options.key, iv, {
|
|
450
|
+
authTagLength: AUTH_TAG_LENGTH
|
|
451
|
+
});
|
|
452
|
+
decipher.setAuthTag(authTag);
|
|
453
|
+
decipher.setAAD(Buffer.concat([secureStoreEnvelopeHeaderAad(salt), aad]));
|
|
454
|
+
const output = fs2.createWriteStream(tempPath, { mode: 384 });
|
|
455
|
+
try {
|
|
456
|
+
const stream = fs2.createReadStream(options.filePath, {
|
|
457
|
+
start: MAGIC_HEADER_SIZE + ENVELOPE_HEADER_SIZE,
|
|
458
|
+
highWaterMark: options.chunkSize
|
|
459
|
+
});
|
|
460
|
+
for await (const encryptedChunk of stream) {
|
|
461
|
+
const plain = decipher.update(
|
|
462
|
+
Buffer.isBuffer(encryptedChunk) ? encryptedChunk : Buffer.from(encryptedChunk)
|
|
463
|
+
);
|
|
464
|
+
if (plain.length > 0 && !output.write(plain)) {
|
|
465
|
+
await new Promise((resolve, reject) => {
|
|
466
|
+
output.once("drain", resolve);
|
|
467
|
+
output.once("error", reject);
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
const finalPlain = decipher.final();
|
|
472
|
+
if (finalPlain.length > 0 && !output.write(finalPlain)) {
|
|
473
|
+
await new Promise((resolve, reject) => {
|
|
474
|
+
output.once("drain", resolve);
|
|
475
|
+
output.once("error", reject);
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
} finally {
|
|
479
|
+
await closeWriteStream(output);
|
|
480
|
+
}
|
|
481
|
+
yield* readPlainOfflineFileChunks(tempPath, options.chunkSize);
|
|
482
|
+
return;
|
|
483
|
+
} catch (error) {
|
|
484
|
+
lastError = error;
|
|
485
|
+
} finally {
|
|
486
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
throw lastError instanceof Error ? lastError : new Error(`secure-store could not decrypt file: ${options.filePath}`);
|
|
490
|
+
}
|
|
491
|
+
function offlineFileAadCandidates(filePath, memoryDir) {
|
|
492
|
+
const candidates = [filePathAad(filePath, memoryDir)];
|
|
493
|
+
const relative = path.relative(memoryDir, filePath);
|
|
494
|
+
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) return candidates;
|
|
495
|
+
const parts = relative.split(path.sep);
|
|
496
|
+
if (parts[0] === "namespaces" && parts.length >= 3 && parts[1]) {
|
|
497
|
+
candidates.push(filePathAad(filePath, path.join(memoryDir, "namespaces", parts[1])));
|
|
498
|
+
}
|
|
499
|
+
const memoryParts = path.resolve(memoryDir).split(path.sep);
|
|
500
|
+
if (memoryParts.length >= 3 && memoryParts.at(-2) === "namespaces" && memoryParts.at(-1)) {
|
|
501
|
+
const topLevelRoot = memoryParts.slice(0, -2).join(path.sep) || path.sep;
|
|
502
|
+
const topRelative = path.relative(topLevelRoot, filePath);
|
|
503
|
+
if (topRelative && !topRelative.startsWith("..") && !path.isAbsolute(topRelative) && topRelative.split(path.sep)[0] === "namespaces" && topRelative.split(path.sep)[1] === memoryParts.at(-1)) {
|
|
504
|
+
candidates.push(filePathAad(filePath, topLevelRoot));
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return candidates;
|
|
508
|
+
}
|
|
509
|
+
async function closeWriteStream(stream) {
|
|
510
|
+
await new Promise((resolve, reject) => {
|
|
511
|
+
stream.once("error", reject);
|
|
512
|
+
stream.end(() => resolve());
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
function secureStoreEnvelopeHeaderAad(salt) {
|
|
516
|
+
const out = Buffer.alloc(1 + ENVELOPE_SALT_LENGTH);
|
|
517
|
+
out.writeUInt8(ENVELOPE_VERSION, 0);
|
|
518
|
+
Buffer.from(salt).copy(out, 1);
|
|
519
|
+
return out;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// src/converge.ts
|
|
523
|
+
import { resolveAgentAccessAuthToken } from "@remnic/core/resolve-auth-token.js";
|
|
524
|
+
async function readLocalTombstones(rootDir) {
|
|
525
|
+
const shaSet = /* @__PURE__ */ new Set();
|
|
526
|
+
const candidates = [
|
|
527
|
+
path2.join(rootDir, "state", "tombstones.jsonl"),
|
|
528
|
+
path2.join(rootDir, "tombstones.jsonl")
|
|
529
|
+
];
|
|
530
|
+
for (const tombPath of candidates) {
|
|
531
|
+
try {
|
|
532
|
+
const content = await fs3.promises.readFile(tombPath, "utf-8");
|
|
533
|
+
for (const line of content.split("\n")) {
|
|
534
|
+
const trimmed = line.trim();
|
|
535
|
+
if (!trimmed) continue;
|
|
536
|
+
try {
|
|
537
|
+
const record = JSON.parse(trimmed);
|
|
538
|
+
if (typeof record.contentHash === "string" && /^[0-9a-f]{64}$/i.test(record.contentHash)) {
|
|
539
|
+
shaSet.add(record.contentHash.toLowerCase());
|
|
540
|
+
}
|
|
541
|
+
if (typeof record.fileSha256 === "string" && /^[0-9a-f]{64}$/i.test(record.fileSha256)) {
|
|
542
|
+
shaSet.add(record.fileSha256.toLowerCase());
|
|
543
|
+
}
|
|
544
|
+
} catch {
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
} catch {
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return shaSet;
|
|
551
|
+
}
|
|
552
|
+
async function fetchPeerSnapshot(peerUrl, namespace, token, fetchImpl = globalThis.fetch) {
|
|
553
|
+
let base = peerUrl;
|
|
554
|
+
while (base.endsWith("/")) {
|
|
555
|
+
base = base.slice(0, -1);
|
|
556
|
+
}
|
|
557
|
+
const routes = [
|
|
558
|
+
`/remnic/v1/offline-sync/snapshot?namespace=${encodeURIComponent(namespace)}&content=false`,
|
|
559
|
+
`/engram/v1/offline-sync/snapshot?namespace=${encodeURIComponent(namespace)}&content=false`
|
|
560
|
+
];
|
|
561
|
+
const headers = token ? { authorization: `Bearer ${token}` } : {};
|
|
562
|
+
let lastFailure = "no snapshot route responded";
|
|
563
|
+
for (const route of routes) {
|
|
564
|
+
let response;
|
|
565
|
+
try {
|
|
566
|
+
response = await fetchImpl(`${base}${route}`, { headers });
|
|
567
|
+
} catch (error) {
|
|
568
|
+
lastFailure = error instanceof Error ? error.message : String(error);
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
if (!response.ok) {
|
|
572
|
+
lastFailure = `HTTP ${response.status}`;
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
let data;
|
|
576
|
+
try {
|
|
577
|
+
data = await response.json();
|
|
578
|
+
} catch {
|
|
579
|
+
throw new Error(`invalid peer snapshot for namespace ${namespace}: response was not JSON`);
|
|
580
|
+
}
|
|
581
|
+
if (!data || typeof data !== "object" || !("files" in data) || !Array.isArray(data.files)) {
|
|
582
|
+
throw new Error(`invalid peer snapshot for namespace ${namespace}: files must be an array`);
|
|
583
|
+
}
|
|
584
|
+
const files = data.files.map((item, index) => {
|
|
585
|
+
if (!item || typeof item !== "object" || !("path" in item) || typeof item.path !== "string" || !("sha256" in item) || typeof item.sha256 !== "string") {
|
|
586
|
+
throw new Error(`invalid peer snapshot for namespace ${namespace}: malformed file at index ${index}`);
|
|
587
|
+
}
|
|
588
|
+
return {
|
|
589
|
+
path: item.path,
|
|
590
|
+
sha256: item.sha256,
|
|
591
|
+
mtimeMs: "mtimeMs" in item && typeof item.mtimeMs === "number" ? item.mtimeMs : void 0,
|
|
592
|
+
bytes: "bytes" in item && typeof item.bytes === "number" ? item.bytes : void 0
|
|
593
|
+
};
|
|
594
|
+
});
|
|
595
|
+
const rawTombstones = "tombstones" in data ? data.tombstones : void 0;
|
|
596
|
+
if (rawTombstones !== void 0 && !Array.isArray(rawTombstones)) {
|
|
597
|
+
throw new Error(`invalid peer snapshot for namespace ${namespace}: tombstones must be an array`);
|
|
598
|
+
}
|
|
599
|
+
const tombstones = /* @__PURE__ */ new Set();
|
|
600
|
+
for (const tombstone of rawTombstones ?? []) {
|
|
601
|
+
if (typeof tombstone !== "string" || !/^[0-9a-f]{64}$/i.test(tombstone)) {
|
|
602
|
+
throw new Error(`invalid peer snapshot for namespace ${namespace}: malformed tombstone`);
|
|
603
|
+
}
|
|
604
|
+
tombstones.add(tombstone.toLowerCase());
|
|
605
|
+
}
|
|
606
|
+
return { files, tombstones };
|
|
607
|
+
}
|
|
608
|
+
throw new Error(`failed to fetch peer snapshot for namespace ${namespace}: ${lastFailure}`);
|
|
609
|
+
}
|
|
610
|
+
function requiredResponseNumber(response, name) {
|
|
611
|
+
const raw = response.headers.get(name);
|
|
612
|
+
const value = raw === null ? Number.NaN : Number(raw);
|
|
613
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
614
|
+
throw new Error(`offline file content response had invalid ${name}`);
|
|
615
|
+
}
|
|
616
|
+
return value;
|
|
617
|
+
}
|
|
618
|
+
async function fetchPeerFileContent(peerUrl, namespace, filePath, token, fetchImpl = globalThis.fetch) {
|
|
619
|
+
const base = peerUrl.replace(/\/+$/, "");
|
|
620
|
+
const routes = [
|
|
621
|
+
"/remnic/v1/offline-sync/file-content",
|
|
622
|
+
"/engram/v1/offline-sync/file-content"
|
|
623
|
+
];
|
|
624
|
+
const headers = {
|
|
625
|
+
"content-type": "application/json",
|
|
626
|
+
...token ? { authorization: `Bearer ${token}` } : {}
|
|
627
|
+
};
|
|
628
|
+
for (const route of routes) {
|
|
629
|
+
try {
|
|
630
|
+
const chunks = [];
|
|
631
|
+
const hash = createHash2("sha256");
|
|
632
|
+
let offset = 0;
|
|
633
|
+
let expectedBytes;
|
|
634
|
+
let expectedSha256;
|
|
635
|
+
let mtimeMs;
|
|
636
|
+
do {
|
|
637
|
+
const response = await fetchImpl(`${base}${route}`, {
|
|
638
|
+
method: "POST",
|
|
639
|
+
headers,
|
|
640
|
+
body: JSON.stringify({
|
|
641
|
+
namespace,
|
|
642
|
+
includeTranscripts: false,
|
|
643
|
+
path: filePath,
|
|
644
|
+
offset,
|
|
645
|
+
length: OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES
|
|
646
|
+
})
|
|
647
|
+
});
|
|
648
|
+
if (!response.ok) throw new Error(`offline file content request failed: ${response.status}`);
|
|
649
|
+
const content = Buffer.from(await response.arrayBuffer());
|
|
650
|
+
const chunkOffset = requiredResponseNumber(response, "x-remnic-chunk-offset");
|
|
651
|
+
const chunkBytes = requiredResponseNumber(response, "x-remnic-chunk-bytes");
|
|
652
|
+
const totalBytes = requiredResponseNumber(response, "x-remnic-file-bytes");
|
|
653
|
+
const responseMtimeMs = requiredResponseNumber(response, "x-remnic-file-mtime-ms");
|
|
654
|
+
const sha256 = response.headers.get("x-remnic-file-sha256");
|
|
655
|
+
const encodedPath = response.headers.get("x-remnic-file-path");
|
|
656
|
+
if (!sha256 || chunkOffset !== offset || chunkBytes !== content.length || encodedPath !== null && decodeURIComponent(encodedPath) !== filePath || expectedBytes !== void 0 && expectedBytes !== totalBytes || expectedSha256 !== void 0 && expectedSha256 !== sha256) {
|
|
657
|
+
throw new Error(`offline file content response changed during transfer: ${filePath}`);
|
|
658
|
+
}
|
|
659
|
+
if (content.length === 0 && offset < totalBytes) {
|
|
660
|
+
throw new Error(`offline file content chunk was empty before EOF: ${filePath}`);
|
|
661
|
+
}
|
|
662
|
+
expectedBytes = totalBytes;
|
|
663
|
+
expectedSha256 = sha256;
|
|
664
|
+
mtimeMs = responseMtimeMs;
|
|
665
|
+
chunks.push(content);
|
|
666
|
+
hash.update(content);
|
|
667
|
+
offset += content.length;
|
|
668
|
+
} while (expectedBytes === void 0 || offset < expectedBytes);
|
|
669
|
+
if (expectedBytes === void 0 || expectedSha256 === void 0 || mtimeMs === void 0 || offset !== expectedBytes || hash.digest("hex") !== expectedSha256) {
|
|
670
|
+
throw new Error(`offline file content checksum mismatch: ${filePath}`);
|
|
671
|
+
}
|
|
672
|
+
return {
|
|
673
|
+
content: Buffer.concat(chunks, expectedBytes),
|
|
674
|
+
sha256: expectedSha256,
|
|
675
|
+
bytes: expectedBytes,
|
|
676
|
+
mtimeMs
|
|
677
|
+
};
|
|
678
|
+
} catch {
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
return null;
|
|
682
|
+
}
|
|
683
|
+
async function postPeerFileContent(peerUrl, namespace, filePath, content, metadata, token, fetchImpl = globalThis.fetch) {
|
|
684
|
+
const base = peerUrl.replace(/\/+$/, "");
|
|
685
|
+
const routes = [
|
|
686
|
+
`/remnic/v1/offline-sync/apply-file-content?namespace=${encodeURIComponent(namespace)}`,
|
|
687
|
+
`/engram/v1/offline-sync/apply-file-content?namespace=${encodeURIComponent(namespace)}`
|
|
688
|
+
];
|
|
689
|
+
for (const route of routes) {
|
|
690
|
+
try {
|
|
691
|
+
let offset = 0;
|
|
692
|
+
do {
|
|
693
|
+
const chunk = content.subarray(
|
|
694
|
+
offset,
|
|
695
|
+
Math.min(content.length, offset + OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES2)
|
|
696
|
+
);
|
|
697
|
+
const headers = {
|
|
698
|
+
"content-type": "application/octet-stream",
|
|
699
|
+
"x-remnic-include-transcripts": "false",
|
|
700
|
+
"x-remnic-source-id": encodeURIComponent("remnic-converge"),
|
|
701
|
+
"x-remnic-file-path": encodeURIComponent(filePath),
|
|
702
|
+
"x-remnic-file-sha256": metadata.sha256,
|
|
703
|
+
"x-remnic-file-bytes": String(content.length),
|
|
704
|
+
"x-remnic-file-mtime-ms": String(metadata.mtimeMs),
|
|
705
|
+
"x-remnic-chunk-offset": String(offset),
|
|
706
|
+
...metadata.baseSha256 ? { "x-remnic-base-sha256": metadata.baseSha256 } : {},
|
|
707
|
+
...token ? { authorization: `Bearer ${token}` } : {}
|
|
708
|
+
};
|
|
709
|
+
const response = await fetchImpl(`${base}${route}`, {
|
|
710
|
+
method: "POST",
|
|
711
|
+
headers,
|
|
712
|
+
body: new Uint8Array(chunk)
|
|
713
|
+
});
|
|
714
|
+
if (!response.ok) throw new Error(`offline apply-file-content request failed: ${response.status}`);
|
|
715
|
+
const result = await response.json().catch(() => null);
|
|
716
|
+
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) {
|
|
717
|
+
return false;
|
|
718
|
+
}
|
|
719
|
+
if (result.done) {
|
|
720
|
+
return result.skipped || result.applied && offset + chunk.length === content.length;
|
|
721
|
+
}
|
|
722
|
+
if (result.applied || result.skipped || chunk.length === 0) {
|
|
723
|
+
return false;
|
|
724
|
+
}
|
|
725
|
+
offset += chunk.length;
|
|
726
|
+
} while (offset < content.length);
|
|
727
|
+
return false;
|
|
728
|
+
} catch {
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
return false;
|
|
732
|
+
}
|
|
733
|
+
async function computeConvergePlan(options = {}) {
|
|
734
|
+
const baseMap = /* @__PURE__ */ new Map();
|
|
735
|
+
const namespacesToPlan = /* @__PURE__ */ new Set();
|
|
736
|
+
const localMap = /* @__PURE__ */ new Map();
|
|
737
|
+
const localTombstones = /* @__PURE__ */ new Map();
|
|
738
|
+
const peerMap = /* @__PURE__ */ new Map();
|
|
739
|
+
const peerTombstones = /* @__PURE__ */ new Map();
|
|
740
|
+
if (options.baseFilesByNamespace) {
|
|
741
|
+
for (const [ns, files] of options.baseFilesByNamespace) {
|
|
742
|
+
namespacesToPlan.add(ns);
|
|
743
|
+
baseMap.set(ns, files);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
if (options.localFilesByNamespace) {
|
|
747
|
+
for (const [ns, files] of options.localFilesByNamespace) {
|
|
748
|
+
namespacesToPlan.add(ns);
|
|
749
|
+
localMap.set(ns, files);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
if (options.localTombstonesByNamespace) {
|
|
753
|
+
for (const [ns, tombstones] of options.localTombstonesByNamespace) {
|
|
754
|
+
localTombstones.set(ns, new Set(tombstones));
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
if (options.peerFilesByNamespace) {
|
|
758
|
+
for (const [ns, files] of options.peerFilesByNamespace) {
|
|
759
|
+
namespacesToPlan.add(ns);
|
|
760
|
+
peerMap.set(ns, files);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
if (options.peerTombstonesByNamespace) {
|
|
764
|
+
for (const [ns, tombstones] of options.peerTombstonesByNamespace) {
|
|
765
|
+
peerTombstones.set(ns, new Set(tombstones));
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
let config = options.config;
|
|
769
|
+
if (!config) {
|
|
770
|
+
try {
|
|
771
|
+
config = parseConfig2({});
|
|
772
|
+
} catch {
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
if (!options.localFilesByNamespace && config) {
|
|
776
|
+
const roots = await resolveCorpusNamespaceRoots({ config });
|
|
777
|
+
const discovered = await listNamespaces({ config });
|
|
778
|
+
for (const entry of discovered) {
|
|
779
|
+
namespacesToPlan.add(entry.namespace);
|
|
780
|
+
}
|
|
781
|
+
for (const rootInfo of roots) {
|
|
782
|
+
const ns = rootInfo.namespace;
|
|
783
|
+
namespacesToPlan.add(ns);
|
|
784
|
+
try {
|
|
785
|
+
const snapshot = await buildOfflineSyncSnapshotFromBase({
|
|
786
|
+
root: rootInfo.rootDir,
|
|
787
|
+
sourceId: "local",
|
|
788
|
+
includeContent: false
|
|
789
|
+
});
|
|
790
|
+
const files = snapshot.files.map((record) => ({
|
|
791
|
+
path: record.path,
|
|
792
|
+
sha256: record.sha256,
|
|
793
|
+
mtimeMs: record.mtimeMs,
|
|
794
|
+
bytes: record.bytes
|
|
795
|
+
}));
|
|
796
|
+
localMap.set(ns, files);
|
|
797
|
+
const tombstones = await readLocalTombstones(rootInfo.rootDir);
|
|
798
|
+
localTombstones.set(ns, tombstones);
|
|
799
|
+
} catch {
|
|
800
|
+
localMap.set(ns, []);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
if (!options.peerFilesByNamespace && options.peerUrl) {
|
|
805
|
+
let resolvedToken;
|
|
806
|
+
if (options.peerToken) {
|
|
807
|
+
try {
|
|
808
|
+
resolvedToken = await resolveAgentAccessAuthToken(options.peerToken, {
|
|
809
|
+
resolveSecretRef: options.resolveSecretRef
|
|
810
|
+
});
|
|
811
|
+
} catch {
|
|
812
|
+
resolvedToken = options.peerToken;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
const fetchFn = options.fetchImpl ?? globalThis.fetch;
|
|
816
|
+
for (const ns of namespacesToPlan) {
|
|
817
|
+
const peerData = await fetchPeerSnapshot(options.peerUrl, ns, resolvedToken, fetchFn);
|
|
818
|
+
peerMap.set(ns, peerData.files);
|
|
819
|
+
peerTombstones.set(ns, peerData.tombstones);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
const memoryDir = options.cursorDir ?? config?.memoryDir;
|
|
823
|
+
if (!options.baseFilesByNamespace && memoryDir && options.peerUrl) {
|
|
824
|
+
for (const ns of namespacesToPlan) {
|
|
825
|
+
const cursorPath = defaultConvergeCursorPath(memoryDir, options.peerUrl, ns);
|
|
826
|
+
const cursor = await readConvergeCursor(cursorPath);
|
|
827
|
+
if (cursor?.baseFiles && cursor.baseFiles.length > 0) {
|
|
828
|
+
baseMap.set(ns, cursor.baseFiles);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
const inputs = [];
|
|
833
|
+
for (const ns of [...namespacesToPlan].sort()) {
|
|
834
|
+
inputs.push({
|
|
835
|
+
namespace: ns,
|
|
836
|
+
local: localMap.get(ns) ?? [],
|
|
837
|
+
peer: peerMap.get(ns) ?? [],
|
|
838
|
+
base: baseMap.get(ns),
|
|
839
|
+
tombstonedFileSha256: localTombstones.get(ns) ?? [],
|
|
840
|
+
peerTombstonedFileSha256: peerTombstones.get(ns) ?? []
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
return planReconciliation(inputs, { conflictPolicy: options.conflictPolicy });
|
|
844
|
+
}
|
|
845
|
+
async function executeConvergeApply(options = {}) {
|
|
846
|
+
const conflictPolicy = options.conflictPolicy ?? "manual";
|
|
847
|
+
const plan = await computeConvergePlan({ ...options, conflictPolicy });
|
|
848
|
+
if (plan.converged && !options.dryRun) {
|
|
849
|
+
await updateCursorsForPlan(plan, options);
|
|
850
|
+
return {
|
|
851
|
+
converged: true,
|
|
852
|
+
status: "converged",
|
|
853
|
+
plan,
|
|
854
|
+
transfers: { pulled: 0, pushed: 0, conflictsResolved: 0, suppressed: 0, failed: 0 },
|
|
855
|
+
cursorUpdated: true
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
const unresolvedCount = plan.byNamespace.reduce((acc, report) => acc + report.unresolved, 0);
|
|
859
|
+
if (unresolvedCount > 0 && conflictPolicy === "manual") {
|
|
860
|
+
return {
|
|
861
|
+
converged: false,
|
|
862
|
+
status: "stopped_unresolved_conflicts",
|
|
863
|
+
plan,
|
|
864
|
+
transfers: { pulled: 0, pushed: 0, conflictsResolved: 0, suppressed: 0, failed: 0 },
|
|
865
|
+
cursorUpdated: false
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
const plannedTransfers = {
|
|
869
|
+
pulled: 0,
|
|
870
|
+
pushed: 0,
|
|
871
|
+
conflictsResolved: 0,
|
|
872
|
+
suppressed: 0,
|
|
873
|
+
failed: 0
|
|
874
|
+
};
|
|
875
|
+
for (const entry of plan.entries) {
|
|
876
|
+
if (entry.action === "pull") plannedTransfers.pulled += 1;
|
|
877
|
+
else if (entry.action === "push") plannedTransfers.pushed += 1;
|
|
878
|
+
else if (entry.action === "conflict") plannedTransfers.conflictsResolved += 1;
|
|
879
|
+
else if (entry.action === "suppress") plannedTransfers.suppressed += 1;
|
|
880
|
+
}
|
|
881
|
+
if (options.dryRun) {
|
|
882
|
+
return {
|
|
883
|
+
converged: false,
|
|
884
|
+
status: "dry_run",
|
|
885
|
+
plan,
|
|
886
|
+
transfers: plannedTransfers,
|
|
887
|
+
cursorUpdated: false
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
const actualTransfers = {
|
|
891
|
+
pulled: 0,
|
|
892
|
+
pushed: 0,
|
|
893
|
+
conflictsResolved: 0,
|
|
894
|
+
suppressed: 0,
|
|
895
|
+
failed: 0
|
|
896
|
+
};
|
|
897
|
+
let resolvedToken;
|
|
898
|
+
if (options.peerToken) {
|
|
899
|
+
try {
|
|
900
|
+
resolvedToken = await resolveAgentAccessAuthToken(options.peerToken, {
|
|
901
|
+
resolveSecretRef: options.resolveSecretRef
|
|
902
|
+
});
|
|
903
|
+
} catch {
|
|
904
|
+
resolvedToken = options.peerToken;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
const fetchFn = options.fetchImpl ?? globalThis.fetch;
|
|
908
|
+
let config = options.config;
|
|
909
|
+
if (!config) {
|
|
910
|
+
try {
|
|
911
|
+
config = parseConfig2({});
|
|
912
|
+
} catch {
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
const rootMap = /* @__PURE__ */ new Map();
|
|
916
|
+
if (config) {
|
|
917
|
+
try {
|
|
918
|
+
const roots = await resolveCorpusNamespaceRoots({ config });
|
|
919
|
+
for (const r of roots) {
|
|
920
|
+
rootMap.set(r.namespace, r.rootDir);
|
|
921
|
+
}
|
|
922
|
+
} catch {
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
for (const entry of plan.entries) {
|
|
926
|
+
if (entry.action === "identical") continue;
|
|
927
|
+
let transferType = "none";
|
|
928
|
+
if (entry.action === "pull") {
|
|
929
|
+
transferType = "pull";
|
|
930
|
+
} else if (entry.action === "push") {
|
|
931
|
+
transferType = "push";
|
|
932
|
+
} else if (entry.action === "suppress") {
|
|
933
|
+
transferType = "suppress";
|
|
934
|
+
} else if (entry.action === "conflict") {
|
|
935
|
+
if (entry.resolution === "peer-wins") {
|
|
936
|
+
transferType = "pull";
|
|
937
|
+
} else if (entry.resolution === "local-wins") {
|
|
938
|
+
transferType = "push";
|
|
939
|
+
} else if (entry.resolution === "supersede-link") {
|
|
940
|
+
if (entry.newerSide === "peer") transferType = "pull";
|
|
941
|
+
else if (entry.newerSide === "local") transferType = "push";
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
if (transferType === "pull") {
|
|
945
|
+
let remoteFile = null;
|
|
946
|
+
const buffered = options.peerFileBuffers?.get(entry.namespace)?.get(entry.path);
|
|
947
|
+
if (buffered) {
|
|
948
|
+
const state = options.peerFilesByNamespace?.get(entry.namespace)?.find((file) => file.path === entry.path);
|
|
949
|
+
remoteFile = {
|
|
950
|
+
content: buffered,
|
|
951
|
+
sha256: state?.sha256 ?? entry.peerSha256 ?? createHash2("sha256").update(buffered).digest("hex"),
|
|
952
|
+
bytes: buffered.length,
|
|
953
|
+
mtimeMs: state?.mtimeMs ?? 0
|
|
954
|
+
};
|
|
955
|
+
} else if (options.peerUrl) {
|
|
956
|
+
remoteFile = await fetchPeerFileContent(
|
|
957
|
+
options.peerUrl,
|
|
958
|
+
entry.namespace,
|
|
959
|
+
entry.path,
|
|
960
|
+
resolvedToken,
|
|
961
|
+
fetchFn
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
if (remoteFile !== null && (!entry.peerSha256 || remoteFile.sha256 === entry.peerSha256)) {
|
|
965
|
+
if (options.localFileBuffers) {
|
|
966
|
+
let nsMap = options.localFileBuffers.get(entry.namespace);
|
|
967
|
+
if (!nsMap) {
|
|
968
|
+
nsMap = /* @__PURE__ */ new Map();
|
|
969
|
+
options.localFileBuffers.set(entry.namespace, nsMap);
|
|
970
|
+
}
|
|
971
|
+
nsMap.set(entry.path, remoteFile.content);
|
|
972
|
+
if (entry.action === "conflict") actualTransfers.conflictsResolved += 1;
|
|
973
|
+
else actualTransfers.pulled += 1;
|
|
974
|
+
} else {
|
|
975
|
+
const rootDir = rootMap.get(entry.namespace);
|
|
976
|
+
if (rootDir) {
|
|
977
|
+
const io = await createOfflineStorageIo(rootDir);
|
|
978
|
+
const expectedLocalSha256 = entry.action === "conflict" ? entry.localSha256 : entry.baseSha256;
|
|
979
|
+
let offset = 0;
|
|
980
|
+
let transferComplete = false;
|
|
981
|
+
do {
|
|
982
|
+
const chunk = remoteFile.content.subarray(
|
|
983
|
+
offset,
|
|
984
|
+
Math.min(
|
|
985
|
+
remoteFile.content.length,
|
|
986
|
+
offset + OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES2
|
|
987
|
+
)
|
|
988
|
+
);
|
|
989
|
+
const chunkResult = await applyOfflineSyncFileContentChunk({
|
|
990
|
+
root: rootDir,
|
|
991
|
+
sourceId: "remnic-converge",
|
|
992
|
+
path: entry.path,
|
|
993
|
+
sha256: remoteFile.sha256,
|
|
994
|
+
bytes: remoteFile.bytes,
|
|
995
|
+
mtimeMs: remoteFile.mtimeMs,
|
|
996
|
+
offset,
|
|
997
|
+
content: chunk,
|
|
998
|
+
...expectedLocalSha256 ? { baseSha256: expectedLocalSha256 } : {},
|
|
999
|
+
readFile: io.readFile,
|
|
1000
|
+
readFileDigest: io.readFileDigest,
|
|
1001
|
+
writeFile: io.writeFile,
|
|
1002
|
+
writeStagingFile: io.writeStagingFile,
|
|
1003
|
+
writeFileChunks: io.writeFileChunks
|
|
1004
|
+
});
|
|
1005
|
+
if (chunkResult.conflict) {
|
|
1006
|
+
break;
|
|
1007
|
+
}
|
|
1008
|
+
if (chunkResult.done) {
|
|
1009
|
+
transferComplete = chunkResult.applied || chunkResult.skipped;
|
|
1010
|
+
break;
|
|
1011
|
+
}
|
|
1012
|
+
if (chunkResult.applied || chunkResult.skipped || chunk.length === 0) {
|
|
1013
|
+
break;
|
|
1014
|
+
}
|
|
1015
|
+
offset += chunk.length;
|
|
1016
|
+
} while (offset < remoteFile.content.length);
|
|
1017
|
+
if (transferComplete) {
|
|
1018
|
+
if (entry.action === "conflict") actualTransfers.conflictsResolved += 1;
|
|
1019
|
+
else actualTransfers.pulled += 1;
|
|
1020
|
+
} else {
|
|
1021
|
+
actualTransfers.failed += 1;
|
|
1022
|
+
}
|
|
1023
|
+
} else {
|
|
1024
|
+
actualTransfers.failed += 1;
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
} else {
|
|
1028
|
+
actualTransfers.failed += 1;
|
|
1029
|
+
}
|
|
1030
|
+
} else if (transferType === "push") {
|
|
1031
|
+
let content = null;
|
|
1032
|
+
let mtimeMs = options.localFilesByNamespace?.get(entry.namespace)?.find((file) => file.path === entry.path)?.mtimeMs;
|
|
1033
|
+
if (options.localFileBuffers?.get(entry.namespace)?.has(entry.path)) {
|
|
1034
|
+
content = options.localFileBuffers.get(entry.namespace).get(entry.path);
|
|
1035
|
+
} else {
|
|
1036
|
+
const rootDir = rootMap.get(entry.namespace);
|
|
1037
|
+
if (rootDir) {
|
|
1038
|
+
const filePath = path2.join(rootDir, entry.path);
|
|
1039
|
+
try {
|
|
1040
|
+
const io = await createOfflineStorageIo(rootDir);
|
|
1041
|
+
content = await io.readFile({ root: rootDir, path: entry.path, filePath });
|
|
1042
|
+
mtimeMs ??= (await fs3.promises.stat(filePath)).mtimeMs;
|
|
1043
|
+
} catch {
|
|
1044
|
+
content = null;
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
if (content !== null) {
|
|
1049
|
+
if (options.peerFileBuffers) {
|
|
1050
|
+
let nsMap = options.peerFileBuffers.get(entry.namespace);
|
|
1051
|
+
if (!nsMap) {
|
|
1052
|
+
nsMap = /* @__PURE__ */ new Map();
|
|
1053
|
+
options.peerFileBuffers.set(entry.namespace, nsMap);
|
|
1054
|
+
}
|
|
1055
|
+
nsMap.set(entry.path, content);
|
|
1056
|
+
if (entry.action === "conflict") actualTransfers.conflictsResolved += 1;
|
|
1057
|
+
else actualTransfers.pushed += 1;
|
|
1058
|
+
} else if (options.peerUrl && entry.localSha256) {
|
|
1059
|
+
const expectedPeerSha256 = entry.action === "conflict" ? entry.peerSha256 : entry.baseSha256;
|
|
1060
|
+
const ok = await postPeerFileContent(
|
|
1061
|
+
options.peerUrl,
|
|
1062
|
+
entry.namespace,
|
|
1063
|
+
entry.path,
|
|
1064
|
+
content,
|
|
1065
|
+
{
|
|
1066
|
+
sha256: entry.localSha256,
|
|
1067
|
+
mtimeMs: mtimeMs ?? 0,
|
|
1068
|
+
...expectedPeerSha256 ? { baseSha256: expectedPeerSha256 } : {}
|
|
1069
|
+
},
|
|
1070
|
+
resolvedToken,
|
|
1071
|
+
fetchFn
|
|
1072
|
+
);
|
|
1073
|
+
if (ok) {
|
|
1074
|
+
if (entry.action === "conflict") actualTransfers.conflictsResolved += 1;
|
|
1075
|
+
else actualTransfers.pushed += 1;
|
|
1076
|
+
} else {
|
|
1077
|
+
actualTransfers.failed += 1;
|
|
1078
|
+
}
|
|
1079
|
+
} else {
|
|
1080
|
+
actualTransfers.failed += 1;
|
|
1081
|
+
}
|
|
1082
|
+
} else {
|
|
1083
|
+
actualTransfers.failed += 1;
|
|
1084
|
+
}
|
|
1085
|
+
} else if (transferType === "suppress") {
|
|
1086
|
+
actualTransfers.suppressed += 1;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
let cursorUpdated = false;
|
|
1090
|
+
if (actualTransfers.failed === 0) {
|
|
1091
|
+
await updateCursorsForPlan(plan, options);
|
|
1092
|
+
cursorUpdated = true;
|
|
1093
|
+
}
|
|
1094
|
+
return {
|
|
1095
|
+
converged: actualTransfers.failed === 0,
|
|
1096
|
+
status: "applied",
|
|
1097
|
+
plan,
|
|
1098
|
+
transfers: actualTransfers,
|
|
1099
|
+
cursorUpdated
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
async function updateCursorsForPlan(plan, options) {
|
|
1103
|
+
const peerUrl = options.peerUrl ?? "local";
|
|
1104
|
+
let memoryDir;
|
|
1105
|
+
if (options.cursorDir) {
|
|
1106
|
+
memoryDir = options.cursorDir;
|
|
1107
|
+
} else if (options.config) {
|
|
1108
|
+
memoryDir = options.config.memoryDir;
|
|
1109
|
+
}
|
|
1110
|
+
if (!memoryDir) return;
|
|
1111
|
+
const namespaces = new Set(plan.byNamespace.map((n) => n.namespace));
|
|
1112
|
+
for (const ns of namespaces) {
|
|
1113
|
+
const cursorPath = defaultConvergeCursorPath(memoryDir, peerUrl, ns);
|
|
1114
|
+
const nsEntries = plan.entries.filter((e) => e.namespace === ns);
|
|
1115
|
+
const baseFiles = nsEntries.map((e) => ({
|
|
1116
|
+
path: e.path,
|
|
1117
|
+
sha256: e.localSha256 ?? e.peerSha256 ?? "unknown"
|
|
1118
|
+
}));
|
|
1119
|
+
const cursorState = {
|
|
1120
|
+
version: 1,
|
|
1121
|
+
peerUrl,
|
|
1122
|
+
namespace: ns,
|
|
1123
|
+
lastConvergedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1124
|
+
baseFiles
|
|
1125
|
+
};
|
|
1126
|
+
try {
|
|
1127
|
+
await writeConvergeCursor(cursorPath, cursorState);
|
|
1128
|
+
} catch {
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
function formatConvergeReport(plan) {
|
|
1133
|
+
const lines = [];
|
|
1134
|
+
lines.push(`Convergence Status: ${plan.converged ? "CONVERGED" : "DIVERGED"}`);
|
|
1135
|
+
lines.push("");
|
|
1136
|
+
lines.push("Per-Namespace Summary:");
|
|
1137
|
+
if (plan.byNamespace.length === 0) {
|
|
1138
|
+
lines.push(" (no namespaces evaluated)");
|
|
1139
|
+
} else {
|
|
1140
|
+
for (const report of plan.byNamespace) {
|
|
1141
|
+
lines.push(` [${report.namespace}]`);
|
|
1142
|
+
lines.push(` identical: ${report.identical}`);
|
|
1143
|
+
lines.push(` pull: ${report.pull}`);
|
|
1144
|
+
lines.push(` push: ${report.push}`);
|
|
1145
|
+
lines.push(` conflict: ${report.conflict}`);
|
|
1146
|
+
lines.push(` suppress: ${report.suppress}`);
|
|
1147
|
+
lines.push(` unresolved: ${report.unresolved}`);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
return lines.join("\n");
|
|
1151
|
+
}
|
|
1152
|
+
function formatConvergeApplyReport(result) {
|
|
1153
|
+
const lines = [];
|
|
1154
|
+
lines.push(`Convergence Execution Status: ${result.status.toUpperCase()}`);
|
|
1155
|
+
lines.push(`Converged: ${result.converged ? "YES" : "NO"}`);
|
|
1156
|
+
lines.push("");
|
|
1157
|
+
lines.push("Transfers Executed:");
|
|
1158
|
+
lines.push(` pulled: ${result.transfers.pulled}`);
|
|
1159
|
+
lines.push(` pushed: ${result.transfers.pushed}`);
|
|
1160
|
+
lines.push(` conflictsResolved: ${result.transfers.conflictsResolved}`);
|
|
1161
|
+
lines.push(` suppressed: ${result.transfers.suppressed}`);
|
|
1162
|
+
lines.push(` failed: ${result.transfers.failed}`);
|
|
1163
|
+
lines.push("");
|
|
1164
|
+
lines.push(formatConvergeReport(result.plan));
|
|
1165
|
+
return lines.join("\n");
|
|
1166
|
+
}
|
|
1167
|
+
async function cmdConverge(action, rest, json) {
|
|
1168
|
+
if (action === "help" || action === "--help" || action === "-h" || rest.includes("--help") || rest.includes("-h")) {
|
|
1169
|
+
console.log(`Usage: remnic converge <plan|apply> [options]
|
|
1170
|
+
|
|
1171
|
+
Subcommands:
|
|
1172
|
+
plan Compute and display reconciliation plan
|
|
1173
|
+
apply Execute bidirectional converge transport (alias: transport, sync)
|
|
1174
|
+
|
|
1175
|
+
Options:
|
|
1176
|
+
--peer <url> Peer server URL (or --remote-url / --remote)
|
|
1177
|
+
--token <token> Bearer token or SecretRef for peer authentication
|
|
1178
|
+
--conflict-policy <policy>
|
|
1179
|
+
Conflict resolution policy (manual|newest-wins|keep-both)
|
|
1180
|
+
--dry-run Simulate transfers without mutating disk or remote peer
|
|
1181
|
+
--json Output detailed JSON plan report
|
|
1182
|
+
`);
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
if (action !== "plan" && action !== "apply" && action !== "transport" && action !== "sync") {
|
|
1186
|
+
process.stderr.write(`converge: unknown action "${action}". Use: plan or apply [options].
|
|
1187
|
+
`);
|
|
1188
|
+
process.exitCode = 2;
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
let peerUrl;
|
|
1192
|
+
let peerToken;
|
|
1193
|
+
let dryRun = false;
|
|
1194
|
+
let conflictPolicy;
|
|
1195
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
1196
|
+
const arg = rest[i];
|
|
1197
|
+
if ((arg === "--peer" || arg === "--remote-url" || arg === "--remote") && rest[i + 1]) {
|
|
1198
|
+
peerUrl = rest[i + 1];
|
|
1199
|
+
i += 1;
|
|
1200
|
+
} else if (arg === "--token" && rest[i + 1]) {
|
|
1201
|
+
peerToken = rest[i + 1];
|
|
1202
|
+
i += 1;
|
|
1203
|
+
} else if (arg === "--dry-run") {
|
|
1204
|
+
dryRun = true;
|
|
1205
|
+
} else if (arg === "--conflict-policy" && rest[i + 1]) {
|
|
1206
|
+
const pol = rest[i + 1];
|
|
1207
|
+
if (pol === "manual" || pol === "newest-wins" || pol === "keep-both") {
|
|
1208
|
+
conflictPolicy = pol;
|
|
1209
|
+
}
|
|
1210
|
+
i += 1;
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
if (action === "plan") {
|
|
1214
|
+
const plan = await computeConvergePlan({ peerUrl, peerToken, conflictPolicy });
|
|
1215
|
+
if (json) {
|
|
1216
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
1217
|
+
} else {
|
|
1218
|
+
console.log(formatConvergeReport(plan));
|
|
1219
|
+
}
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
const result = await executeConvergeApply({
|
|
1223
|
+
peerUrl,
|
|
1224
|
+
peerToken,
|
|
1225
|
+
dryRun,
|
|
1226
|
+
conflictPolicy
|
|
1227
|
+
});
|
|
1228
|
+
if (json) {
|
|
1229
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1230
|
+
} else {
|
|
1231
|
+
console.log(formatConvergeApplyReport(result));
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
// src/doctor-namespace-lint.ts
|
|
1236
|
+
import { isNamespacePolicyCovered } from "@remnic/core";
|
|
1237
|
+
function readConfiguredNamespace(remnicCfg) {
|
|
1238
|
+
if (!("namespace" in remnicCfg)) return { invalid: false };
|
|
1239
|
+
const value = remnicCfg.namespace;
|
|
1240
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
1241
|
+
return { configuredNamespace: value.trim(), invalid: false };
|
|
1242
|
+
}
|
|
1243
|
+
return { invalid: true };
|
|
1244
|
+
}
|
|
1245
|
+
function buildNamespacePolicyCheck(args) {
|
|
1246
|
+
if (args.invalid) {
|
|
1247
|
+
return {
|
|
1248
|
+
name: "Namespace policy",
|
|
1249
|
+
ok: false,
|
|
1250
|
+
detail: "config `namespace` is set but is not a non-empty string",
|
|
1251
|
+
remediation: "Set `namespace` to a non-empty string (a namespacePolicies name or the default namespace), or remove it."
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
if (!args.config || !args.configuredNamespace) return void 0;
|
|
1255
|
+
const covered = isNamespacePolicyCovered(args.configuredNamespace, args.config);
|
|
1256
|
+
return {
|
|
1257
|
+
name: "Namespace policy",
|
|
1258
|
+
ok: covered,
|
|
1259
|
+
warn: !covered,
|
|
1260
|
+
detail: covered ? `configured namespace "${args.configuredNamespace}" is writable` : `configured namespace "${args.configuredNamespace}" is writable by no one \u2014 its namespacePolicies entry grants no writer, or it has no entry and is not the default namespace`,
|
|
1261
|
+
remediation: covered ? void 0 : `Give "${args.configuredNamespace}" a namespacePolicies entry with a non-blank writePrincipals value, or set namespace to a writable one \u2014 otherwise every write is rejected and dead-lettered.`
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
// src/index.ts
|
|
1266
|
+
import { WriteQuarantineStore as WriteQuarantineStore2 } from "@remnic/core/write-quarantine.js";
|
|
1267
|
+
|
|
1268
|
+
// src/quarantine-cli.ts
|
|
1269
|
+
import { basename } from "path";
|
|
1270
|
+
function renderQuarantineList(records, format) {
|
|
1271
|
+
if (format === "json") {
|
|
1272
|
+
const summary = records.map((record) => ({
|
|
1273
|
+
timestamp: record.timestamp,
|
|
1274
|
+
operation: record.operation,
|
|
1275
|
+
principal: record.principal,
|
|
1276
|
+
attemptedNamespace: record.attemptedNamespace
|
|
1277
|
+
}));
|
|
1278
|
+
return JSON.stringify(summary, null, 2);
|
|
1279
|
+
}
|
|
1280
|
+
if (format !== "text") {
|
|
1281
|
+
throw new Error(`Unsupported quarantine format: ${String(format)}`);
|
|
1282
|
+
}
|
|
1283
|
+
if (records.length === 0) return "No quarantined writes.";
|
|
1284
|
+
const lines = [`Quarantined writes (${records.length}):`, ""];
|
|
1285
|
+
for (const record of records) {
|
|
1286
|
+
lines.push(
|
|
1287
|
+
` ${record.timestamp} ${record.operation} principal=${record.principal ?? "-"} attemptedNamespace=${record.attemptedNamespace}`
|
|
1288
|
+
);
|
|
1289
|
+
}
|
|
1290
|
+
return lines.join("\n");
|
|
1291
|
+
}
|
|
1292
|
+
async function replayQuarantine(opts) {
|
|
1293
|
+
const result = { replayed: 0, failures: [], deleteFailures: [] };
|
|
1294
|
+
for (const entry of await opts.store.entries()) {
|
|
1295
|
+
const { record } = entry;
|
|
1296
|
+
const basePayload = record.payload;
|
|
1297
|
+
const principal = opts.principal ?? record.principal ?? void 0;
|
|
1298
|
+
const idempotencyKey = typeof basePayload.idempotencyKey === "string" && basePayload.idempotencyKey.length > 0 ? basePayload.idempotencyKey : `quarantine-replay:${basename(entry.path)}`;
|
|
1299
|
+
const request = {
|
|
1300
|
+
...basePayload,
|
|
1301
|
+
namespace: opts.targetNamespace,
|
|
1302
|
+
suppressQuarantine: true,
|
|
1303
|
+
idempotencyKey,
|
|
1304
|
+
...principal ? { authenticatedPrincipal: principal } : {}
|
|
1305
|
+
};
|
|
1306
|
+
try {
|
|
1307
|
+
await opts.submit(record.operation, request);
|
|
1308
|
+
} catch (err) {
|
|
1309
|
+
result.failures.push({
|
|
1310
|
+
operation: record.operation,
|
|
1311
|
+
attemptedNamespace: opts.targetNamespace,
|
|
1312
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1313
|
+
});
|
|
1314
|
+
continue;
|
|
1315
|
+
}
|
|
1316
|
+
try {
|
|
1317
|
+
const removed = await opts.store.removeEntry(entry.path);
|
|
1318
|
+
if (removed) {
|
|
1319
|
+
result.replayed += 1;
|
|
1320
|
+
} else {
|
|
1321
|
+
result.deleteFailures.push({
|
|
1322
|
+
path: entry.path,
|
|
1323
|
+
error: "entry not removed (outside quarantine root or already absent)"
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
} catch (err) {
|
|
1327
|
+
result.deleteFailures.push({
|
|
1328
|
+
path: entry.path,
|
|
1329
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
return result;
|
|
1334
|
+
}
|
|
1335
|
+
function renderReplayResult(result, targetNamespace, format) {
|
|
1336
|
+
if (format === "json") {
|
|
1337
|
+
return JSON.stringify(
|
|
1338
|
+
{
|
|
1339
|
+
targetNamespace,
|
|
1340
|
+
replayed: result.replayed,
|
|
1341
|
+
failures: result.failures,
|
|
1342
|
+
deleteFailures: result.deleteFailures
|
|
1343
|
+
},
|
|
1344
|
+
null,
|
|
1345
|
+
2
|
|
1346
|
+
);
|
|
1347
|
+
}
|
|
1348
|
+
if (format !== "text") {
|
|
1349
|
+
throw new Error(`Unsupported quarantine format: ${String(format)}`);
|
|
356
1350
|
}
|
|
357
1351
|
const lines = [`Replayed ${result.replayed} quarantined write(s) into namespace ${targetNamespace}.`];
|
|
358
1352
|
if (result.failures.length > 0) {
|
|
@@ -371,8 +1365,8 @@ function renderReplayResult(result, targetNamespace, format) {
|
|
|
371
1365
|
}
|
|
372
1366
|
|
|
373
1367
|
// src/quarantine-replay.ts
|
|
374
|
-
import * as
|
|
375
|
-
import { EngramAccessService, Orchestrator as Orchestrator2, initLogger, parseConfig as
|
|
1368
|
+
import * as fs4 from "fs";
|
|
1369
|
+
import { EngramAccessService, Orchestrator as Orchestrator2, initLogger, parseConfig as parseConfig3, resolveRemnicConfigRecord as resolveRemnicConfigRecord2 } from "@remnic/core";
|
|
376
1370
|
import { WriteQuarantineStore } from "@remnic/core/write-quarantine.js";
|
|
377
1371
|
function valueFlag(args, flag) {
|
|
378
1372
|
const occurrences = args.filter((a) => a === flag).length;
|
|
@@ -394,384 +1388,125 @@ async function runQuarantineReplay(rest, format, resolveConfigPath2) {
|
|
|
394
1388
|
let principal;
|
|
395
1389
|
try {
|
|
396
1390
|
targetNamespace = valueFlag(rest, "--namespace");
|
|
397
|
-
principal = valueFlag(rest, "--principal");
|
|
398
|
-
} catch (err) {
|
|
399
|
-
process.stderr.write(`quarantine replay: ${err instanceof Error ? err.message : String(err)}
|
|
400
|
-
`);
|
|
401
|
-
process.exitCode = 2;
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
if (!targetNamespace || targetNamespace.trim().length === 0) {
|
|
405
|
-
process.stderr.write("quarantine replay: --namespace <ns> is required.\n");
|
|
406
|
-
process.exitCode = 2;
|
|
407
|
-
return;
|
|
408
|
-
}
|
|
409
|
-
const valued = /* @__PURE__ */ new Set(["--namespace", "--principal"]);
|
|
410
|
-
const bad = rest.filter((a, i) => a.startsWith("--") ? a !== "--json" && !valued.has(a) : !valued.has(rest[i - 1]));
|
|
411
|
-
if (bad.length > 0) {
|
|
412
|
-
process.stderr.write(
|
|
413
|
-
`quarantine replay: unexpected argument(s): ${bad.join(", ")}. Use: replay --namespace <ns> [--principal <p>] [--json].
|
|
414
|
-
`
|
|
415
|
-
);
|
|
416
|
-
process.exitCode = 2;
|
|
417
|
-
return;
|
|
418
|
-
}
|
|
419
|
-
initLogger();
|
|
420
|
-
let orchestrator;
|
|
421
|
-
try {
|
|
422
|
-
const configPath = resolveConfigPath2();
|
|
423
|
-
const raw = fs2.existsSync(configPath) ? JSON.parse(fs2.readFileSync(configPath, "utf8")) : {};
|
|
424
|
-
const config = parseConfig2(resolveRemnicConfigRecord2(raw));
|
|
425
|
-
orchestrator = new Orchestrator2(config);
|
|
426
|
-
await orchestrator.initialize();
|
|
427
|
-
await orchestrator.deferredReady;
|
|
428
|
-
const service = new EngramAccessService(orchestrator);
|
|
429
|
-
const store = new WriteQuarantineStore(config.memoryDir);
|
|
430
|
-
const result = await replayQuarantine({
|
|
431
|
-
store,
|
|
432
|
-
targetNamespace,
|
|
433
|
-
principal,
|
|
434
|
-
submit: async (operation, request) => {
|
|
435
|
-
if (operation === "observe") {
|
|
436
|
-
await service.observe(request);
|
|
437
|
-
} else if (operation === "memory_store") {
|
|
438
|
-
await service.memoryStore(request);
|
|
439
|
-
} else {
|
|
440
|
-
await service.suggestionSubmit(request);
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
});
|
|
444
|
-
console.log(renderReplayResult(result, targetNamespace, format));
|
|
445
|
-
if (result.failures.length > 0 || result.deleteFailures.length > 0) process.exitCode = 1;
|
|
446
|
-
} catch {
|
|
447
|
-
process.stderr.write("quarantine replay: unable to replay quarantine store\n");
|
|
448
|
-
process.exitCode = 2;
|
|
449
|
-
} finally {
|
|
450
|
-
if (orchestrator) await orchestrator.destroy();
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
// src/offline-impression-rotation.ts
|
|
455
|
-
import fs3 from "fs";
|
|
456
|
-
import { parseConfig as parseConfig3, resolveRemnicConfigRecord as resolveRemnicConfigRecord3, drainPendingImpressionsForOfflineSync } from "@remnic/core";
|
|
457
|
-
import { LastRecallStore } from "@remnic/core/recall-state";
|
|
458
|
-
function parseConfigQuietly(raw) {
|
|
459
|
-
const originalWarn = console.warn;
|
|
460
|
-
console.warn = () => {
|
|
461
|
-
};
|
|
462
|
-
try {
|
|
463
|
-
return parseConfig3(resolveRemnicConfigRecord3(raw));
|
|
464
|
-
} finally {
|
|
465
|
-
console.warn = originalWarn;
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
var OFFLINE_CONFIG_KEYS = [
|
|
469
|
-
"offlineSyncExcludes",
|
|
470
|
-
"secureStoreEncryptOnWrite",
|
|
471
|
-
"recallImpressionsRotateBytes",
|
|
472
|
-
"recallImpressionsRotateKeep"
|
|
473
|
-
];
|
|
474
|
-
function pickOfflineConfigRecord(raw) {
|
|
475
|
-
let resolved;
|
|
476
|
-
try {
|
|
477
|
-
resolved = resolveRemnicConfigRecord3(raw);
|
|
478
|
-
} catch {
|
|
479
|
-
resolved = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
480
|
-
}
|
|
481
|
-
const picked = {};
|
|
482
|
-
for (const key of OFFLINE_CONFIG_KEYS) {
|
|
483
|
-
if (key in resolved) picked[key] = resolved[key];
|
|
484
|
-
}
|
|
485
|
-
return picked;
|
|
486
|
-
}
|
|
487
|
-
function resolveOfflineImpressionRotation(configPath) {
|
|
488
|
-
let raw;
|
|
489
|
-
try {
|
|
490
|
-
raw = fs3.existsSync(configPath) ? JSON.parse(fs3.readFileSync(configPath, "utf8")) : {};
|
|
491
|
-
} catch {
|
|
492
|
-
throw new Error(
|
|
493
|
-
`cannot read recall-impression rotation from ${configPath}: config file could not be read as JSON`
|
|
494
|
-
);
|
|
495
|
-
}
|
|
496
|
-
let config;
|
|
497
|
-
try {
|
|
498
|
-
config = parseConfigQuietly(pickOfflineConfigRecord(raw));
|
|
499
|
-
} catch {
|
|
500
|
-
throw new Error(
|
|
501
|
-
`cannot read recall-impression rotation from ${configPath}: config failed validation`
|
|
502
|
-
);
|
|
503
|
-
}
|
|
504
|
-
return {
|
|
505
|
-
impressionsRotateBytes: config.recallImpressionsRotateBytes,
|
|
506
|
-
impressionsRotateKeep: config.recallImpressionsRotateKeep
|
|
507
|
-
};
|
|
508
|
-
}
|
|
509
|
-
async function drainOfflineSyncImpressions(memoryDir, rotation) {
|
|
510
|
-
await drainPendingImpressionsForOfflineSync(
|
|
511
|
-
() => new LastRecallStore(memoryDir, {
|
|
512
|
-
impressionsRotateBytes: rotation.impressionsRotateBytes,
|
|
513
|
-
impressionsRotateKeep: rotation.impressionsRotateKeep
|
|
514
|
-
}).drainPendingImpressions()
|
|
515
|
-
);
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
// src/offline-storage-io.ts
|
|
519
|
-
import { mkdtemp, readdir, lstat, rm } from "fs/promises";
|
|
520
|
-
import fs4 from "fs";
|
|
521
|
-
import path from "path";
|
|
522
|
-
import { createHash, createDecipheriv } from "crypto";
|
|
523
|
-
import {
|
|
524
|
-
OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES,
|
|
525
|
-
StorageManager
|
|
526
|
-
} from "@remnic/core";
|
|
527
|
-
import { OFFLINE_DECRYPT_STAGING_DIR_PREFIX } from "@remnic/core/offline-sync-exclude-globs";
|
|
528
|
-
import {
|
|
529
|
-
AUTH_TAG_LENGTH,
|
|
530
|
-
ENVELOPE_HEADER_SIZE,
|
|
531
|
-
ENVELOPE_LAYOUT,
|
|
532
|
-
ENVELOPE_SALT_LENGTH,
|
|
533
|
-
ENVELOPE_VERSION,
|
|
534
|
-
FILE_FORMAT_FLAGS,
|
|
535
|
-
FILE_FORMAT_VERSION,
|
|
536
|
-
IV_LENGTH,
|
|
537
|
-
MAGIC_BYTES,
|
|
538
|
-
MAGIC_HEADER_SIZE,
|
|
539
|
-
SecureStoreLockedError,
|
|
540
|
-
filePathAad,
|
|
541
|
-
isEncryptedFile,
|
|
542
|
-
keyring,
|
|
543
|
-
readHeader,
|
|
544
|
-
secureStoreDir
|
|
545
|
-
} from "@remnic/core/secure-store";
|
|
546
|
-
async function createConfiguredOfflineStorage(memoryDir, secureStoreEncryptOnWrite = true) {
|
|
547
|
-
const storage = new StorageManager(memoryDir);
|
|
548
|
-
const header = await readHeader(memoryDir);
|
|
549
|
-
let secureStoreKey = null;
|
|
550
|
-
let secureStoreRequired = false;
|
|
551
|
-
if (header) {
|
|
552
|
-
secureStoreRequired = true;
|
|
553
|
-
storage.setSecureStoreRequired(true);
|
|
554
|
-
const key = keyring.getKey(secureStoreDir(memoryDir));
|
|
555
|
-
if (key) {
|
|
556
|
-
await storage.setSecureStoreKeyAndWait(key, secureStoreEncryptOnWrite);
|
|
557
|
-
secureStoreKey = key;
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
return { storage, secureStoreKey, secureStoreRequired };
|
|
561
|
-
}
|
|
562
|
-
async function createOfflineStorageForPath(memoryDir, filePath, configured, secureStoreEncryptOnWrite) {
|
|
563
|
-
const memoryRoot = path.resolve(memoryDir);
|
|
564
|
-
const stateDir = path.dirname(filePath);
|
|
565
|
-
if (path.basename(stateDir) !== "state" || path.basename(filePath) !== "memory-lifecycle-ledger.jsonl") {
|
|
566
|
-
throw new Error(`invalid lifecycle ledger path: ${filePath}`);
|
|
567
|
-
}
|
|
568
|
-
const storageRoot = path.resolve(path.dirname(stateDir));
|
|
569
|
-
if (storageRoot !== memoryRoot && !storageRoot.startsWith(`${memoryRoot}${path.sep}`)) {
|
|
570
|
-
throw new Error(`lifecycle ledger path is outside the offline memory directory: ${filePath}`);
|
|
571
|
-
}
|
|
572
|
-
const storage = new StorageManager(storageRoot);
|
|
573
|
-
if (configured.secureStoreRequired) {
|
|
574
|
-
storage.setSecureStoreRequired(true);
|
|
575
|
-
}
|
|
576
|
-
if (configured.secureStoreKey) {
|
|
577
|
-
await storage.setSecureStoreKeyAndWait(configured.secureStoreKey, secureStoreEncryptOnWrite);
|
|
578
|
-
}
|
|
579
|
-
return storage;
|
|
580
|
-
}
|
|
581
|
-
async function createOfflineStorageIo(memoryDir, configuredStorage) {
|
|
582
|
-
await cleanupOrphanedOfflineDecryptStaging(memoryDir);
|
|
583
|
-
const { storage, secureStoreKey } = configuredStorage ?? await createConfiguredOfflineStorage(memoryDir);
|
|
584
|
-
return {
|
|
585
|
-
readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
|
|
586
|
-
readFileDigest: async ({ filePath }) => {
|
|
587
|
-
const hash = createHash("sha256");
|
|
588
|
-
let bytes = 0;
|
|
589
|
-
for await (const rawChunk of readOfflineSyncFileChunks({
|
|
590
|
-
filePath,
|
|
591
|
-
memoryDir,
|
|
592
|
-
secureStoreKey,
|
|
593
|
-
chunkSize: OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES
|
|
594
|
-
})) {
|
|
595
|
-
const chunk = Buffer.isBuffer(rawChunk) ? rawChunk : Buffer.from(rawChunk);
|
|
596
|
-
hash.update(chunk);
|
|
597
|
-
bytes += chunk.length;
|
|
598
|
-
}
|
|
599
|
-
return {
|
|
600
|
-
sha256: hash.digest("hex"),
|
|
601
|
-
bytes
|
|
602
|
-
};
|
|
603
|
-
},
|
|
604
|
-
readFileChunks: ({ filePath, chunkSize }) => readOfflineSyncFileChunks({
|
|
605
|
-
filePath,
|
|
606
|
-
memoryDir,
|
|
607
|
-
secureStoreKey,
|
|
608
|
-
chunkSize
|
|
609
|
-
}),
|
|
610
|
-
writeFile: async ({ filePath, content }) => storage.writeOfflineSyncFile(filePath, content),
|
|
611
|
-
writeStagingFile: async ({ filePath, content }) => storage.writeOfflineSyncStagingFile(filePath, content),
|
|
612
|
-
writeFileChunks: async ({ filePath, chunks }) => storage.writeOfflineSyncFileChunks(filePath, chunks),
|
|
613
|
-
deleteFile: async ({ filePath }) => storage.deleteOfflineSyncFile(filePath)
|
|
614
|
-
};
|
|
615
|
-
}
|
|
616
|
-
var OFFLINE_DECRYPT_STAGING_ORPHAN_MS = 60 * 60 * 1e3;
|
|
617
|
-
async function cleanupOrphanedOfflineDecryptStaging(memoryDir) {
|
|
618
|
-
let entries;
|
|
619
|
-
try {
|
|
620
|
-
entries = await readdir(memoryDir);
|
|
621
|
-
} catch {
|
|
622
|
-
return;
|
|
623
|
-
}
|
|
624
|
-
const now = Date.now();
|
|
625
|
-
for (const name of entries) {
|
|
626
|
-
if (!name.startsWith(OFFLINE_DECRYPT_STAGING_DIR_PREFIX)) continue;
|
|
627
|
-
const dir = path.join(memoryDir, name);
|
|
628
|
-
try {
|
|
629
|
-
const info = await lstat(dir);
|
|
630
|
-
if (!info.isDirectory() || info.isSymbolicLink()) continue;
|
|
631
|
-
if (now - info.mtimeMs < OFFLINE_DECRYPT_STAGING_ORPHAN_MS) continue;
|
|
632
|
-
await rm(dir, { recursive: true, force: true });
|
|
633
|
-
} catch {
|
|
634
|
-
}
|
|
1391
|
+
principal = valueFlag(rest, "--principal");
|
|
1392
|
+
} catch (err) {
|
|
1393
|
+
process.stderr.write(`quarantine replay: ${err instanceof Error ? err.message : String(err)}
|
|
1394
|
+
`);
|
|
1395
|
+
process.exitCode = 2;
|
|
1396
|
+
return;
|
|
635
1397
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
if (!isEncryptedFile(header)) {
|
|
640
|
-
yield* readPlainOfflineFileChunks(options.filePath, options.chunkSize);
|
|
1398
|
+
if (!targetNamespace || targetNamespace.trim().length === 0) {
|
|
1399
|
+
process.stderr.write("quarantine replay: --namespace <ns> is required.\n");
|
|
1400
|
+
process.exitCode = 2;
|
|
641
1401
|
return;
|
|
642
1402
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
1403
|
+
const valued = /* @__PURE__ */ new Set(["--namespace", "--principal"]);
|
|
1404
|
+
const bad = rest.filter((a, i) => a.startsWith("--") ? a !== "--json" && !valued.has(a) : !valued.has(rest[i - 1]));
|
|
1405
|
+
if (bad.length > 0) {
|
|
1406
|
+
process.stderr.write(
|
|
1407
|
+
`quarantine replay: unexpected argument(s): ${bad.join(", ")}. Use: replay --namespace <ns> [--principal <p>] [--json].
|
|
1408
|
+
`
|
|
646
1409
|
);
|
|
1410
|
+
process.exitCode = 2;
|
|
1411
|
+
return;
|
|
647
1412
|
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
memoryDir: options.memoryDir,
|
|
651
|
-
key: options.secureStoreKey,
|
|
652
|
-
chunkSize: options.chunkSize
|
|
653
|
-
});
|
|
654
|
-
}
|
|
655
|
-
async function readFilePrefix(filePath, length) {
|
|
656
|
-
const handle = await fs4.promises.open(filePath, "r");
|
|
1413
|
+
initLogger();
|
|
1414
|
+
let orchestrator;
|
|
657
1415
|
try {
|
|
658
|
-
const
|
|
659
|
-
const
|
|
660
|
-
|
|
1416
|
+
const configPath = resolveConfigPath2();
|
|
1417
|
+
const raw = fs4.existsSync(configPath) ? JSON.parse(fs4.readFileSync(configPath, "utf8")) : {};
|
|
1418
|
+
const config = parseConfig3(resolveRemnicConfigRecord2(raw));
|
|
1419
|
+
orchestrator = new Orchestrator2(config);
|
|
1420
|
+
await orchestrator.initialize();
|
|
1421
|
+
await orchestrator.deferredReady;
|
|
1422
|
+
const service = new EngramAccessService(orchestrator);
|
|
1423
|
+
const store = new WriteQuarantineStore(config.memoryDir);
|
|
1424
|
+
const result = await replayQuarantine({
|
|
1425
|
+
store,
|
|
1426
|
+
targetNamespace,
|
|
1427
|
+
principal,
|
|
1428
|
+
submit: async (operation, request) => {
|
|
1429
|
+
if (operation === "observe") {
|
|
1430
|
+
await service.observe(request);
|
|
1431
|
+
} else if (operation === "memory_store") {
|
|
1432
|
+
await service.memoryStore(request);
|
|
1433
|
+
} else {
|
|
1434
|
+
await service.suggestionSubmit(request);
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
});
|
|
1438
|
+
console.log(renderReplayResult(result, targetNamespace, format));
|
|
1439
|
+
if (result.failures.length > 0 || result.deleteFailures.length > 0) process.exitCode = 1;
|
|
1440
|
+
} catch {
|
|
1441
|
+
process.stderr.write("quarantine replay: unable to replay quarantine store\n");
|
|
1442
|
+
process.exitCode = 2;
|
|
661
1443
|
} finally {
|
|
662
|
-
await
|
|
1444
|
+
if (orchestrator) await orchestrator.destroy();
|
|
663
1445
|
}
|
|
664
1446
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
1447
|
+
|
|
1448
|
+
// src/offline-impression-rotation.ts
|
|
1449
|
+
import fs5 from "fs";
|
|
1450
|
+
import { parseConfig as parseConfig4, resolveRemnicConfigRecord as resolveRemnicConfigRecord3, drainPendingImpressionsForOfflineSync } from "@remnic/core";
|
|
1451
|
+
import { LastRecallStore } from "@remnic/core/recall-state";
|
|
1452
|
+
function parseConfigQuietly(raw) {
|
|
1453
|
+
const originalWarn = console.warn;
|
|
1454
|
+
console.warn = () => {
|
|
1455
|
+
};
|
|
1456
|
+
try {
|
|
1457
|
+
return parseConfig4(resolveRemnicConfigRecord3(raw));
|
|
1458
|
+
} finally {
|
|
1459
|
+
console.warn = originalWarn;
|
|
669
1460
|
}
|
|
670
1461
|
}
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
}
|
|
684
|
-
const envelopeHeader = header.subarray(MAGIC_HEADER_SIZE);
|
|
685
|
-
const envelopeVersion = envelopeHeader.readUInt8(ENVELOPE_LAYOUT.version);
|
|
686
|
-
if (envelopeVersion !== ENVELOPE_VERSION) {
|
|
687
|
-
throw new Error(`secure-store envelope has unsupported version ${envelopeVersion}: ${options.filePath}`);
|
|
1462
|
+
var OFFLINE_CONFIG_KEYS = [
|
|
1463
|
+
"offlineSyncExcludes",
|
|
1464
|
+
"secureStoreEncryptOnWrite",
|
|
1465
|
+
"recallImpressionsRotateBytes",
|
|
1466
|
+
"recallImpressionsRotateKeep"
|
|
1467
|
+
];
|
|
1468
|
+
function pickOfflineConfigRecord(raw) {
|
|
1469
|
+
let resolved;
|
|
1470
|
+
try {
|
|
1471
|
+
resolved = resolveRemnicConfigRecord3(raw);
|
|
1472
|
+
} catch {
|
|
1473
|
+
resolved = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
688
1474
|
}
|
|
689
|
-
const
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
);
|
|
693
|
-
const iv = envelopeHeader.subarray(ENVELOPE_LAYOUT.iv, ENVELOPE_LAYOUT.iv + IV_LENGTH);
|
|
694
|
-
const authTag = envelopeHeader.subarray(
|
|
695
|
-
ENVELOPE_LAYOUT.authTag,
|
|
696
|
-
ENVELOPE_LAYOUT.authTag + AUTH_TAG_LENGTH
|
|
697
|
-
);
|
|
698
|
-
const aadCandidates = offlineFileAadCandidates(options.filePath, options.memoryDir);
|
|
699
|
-
let lastError;
|
|
700
|
-
for (const aad of aadCandidates) {
|
|
701
|
-
const tempDir = await mkdtemp(path.join(options.memoryDir, OFFLINE_DECRYPT_STAGING_DIR_PREFIX));
|
|
702
|
-
const tempPath = path.join(tempDir, "content");
|
|
703
|
-
try {
|
|
704
|
-
const decipher = createDecipheriv("aes-256-gcm", options.key, iv, {
|
|
705
|
-
authTagLength: AUTH_TAG_LENGTH
|
|
706
|
-
});
|
|
707
|
-
decipher.setAuthTag(authTag);
|
|
708
|
-
decipher.setAAD(Buffer.concat([secureStoreEnvelopeHeaderAad(salt), aad]));
|
|
709
|
-
const output = fs4.createWriteStream(tempPath, { mode: 384 });
|
|
710
|
-
try {
|
|
711
|
-
const stream = fs4.createReadStream(options.filePath, {
|
|
712
|
-
start: MAGIC_HEADER_SIZE + ENVELOPE_HEADER_SIZE,
|
|
713
|
-
highWaterMark: options.chunkSize
|
|
714
|
-
});
|
|
715
|
-
for await (const encryptedChunk of stream) {
|
|
716
|
-
const plain = decipher.update(
|
|
717
|
-
Buffer.isBuffer(encryptedChunk) ? encryptedChunk : Buffer.from(encryptedChunk)
|
|
718
|
-
);
|
|
719
|
-
if (plain.length > 0 && !output.write(plain)) {
|
|
720
|
-
await new Promise((resolve, reject) => {
|
|
721
|
-
output.once("drain", resolve);
|
|
722
|
-
output.once("error", reject);
|
|
723
|
-
});
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
const finalPlain = decipher.final();
|
|
727
|
-
if (finalPlain.length > 0 && !output.write(finalPlain)) {
|
|
728
|
-
await new Promise((resolve, reject) => {
|
|
729
|
-
output.once("drain", resolve);
|
|
730
|
-
output.once("error", reject);
|
|
731
|
-
});
|
|
732
|
-
}
|
|
733
|
-
} finally {
|
|
734
|
-
await closeWriteStream(output);
|
|
735
|
-
}
|
|
736
|
-
yield* readPlainOfflineFileChunks(tempPath, options.chunkSize);
|
|
737
|
-
return;
|
|
738
|
-
} catch (error) {
|
|
739
|
-
lastError = error;
|
|
740
|
-
} finally {
|
|
741
|
-
await rm(tempDir, { recursive: true, force: true });
|
|
742
|
-
}
|
|
1475
|
+
const picked = {};
|
|
1476
|
+
for (const key of OFFLINE_CONFIG_KEYS) {
|
|
1477
|
+
if (key in resolved) picked[key] = resolved[key];
|
|
743
1478
|
}
|
|
744
|
-
|
|
1479
|
+
return picked;
|
|
745
1480
|
}
|
|
746
|
-
function
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
1481
|
+
function resolveOfflineImpressionRotation(configPath) {
|
|
1482
|
+
let raw;
|
|
1483
|
+
try {
|
|
1484
|
+
raw = fs5.existsSync(configPath) ? JSON.parse(fs5.readFileSync(configPath, "utf8")) : {};
|
|
1485
|
+
} catch {
|
|
1486
|
+
throw new Error(
|
|
1487
|
+
`cannot read recall-impression rotation from ${configPath}: config file could not be read as JSON`
|
|
1488
|
+
);
|
|
753
1489
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
1490
|
+
let config;
|
|
1491
|
+
try {
|
|
1492
|
+
config = parseConfigQuietly(pickOfflineConfigRecord(raw));
|
|
1493
|
+
} catch {
|
|
1494
|
+
throw new Error(
|
|
1495
|
+
`cannot read recall-impression rotation from ${configPath}: config failed validation`
|
|
1496
|
+
);
|
|
761
1497
|
}
|
|
762
|
-
return
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
stream.once("error", reject);
|
|
767
|
-
stream.end(() => resolve());
|
|
768
|
-
});
|
|
1498
|
+
return {
|
|
1499
|
+
impressionsRotateBytes: config.recallImpressionsRotateBytes,
|
|
1500
|
+
impressionsRotateKeep: config.recallImpressionsRotateKeep
|
|
1501
|
+
};
|
|
769
1502
|
}
|
|
770
|
-
function
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
1503
|
+
async function drainOfflineSyncImpressions(memoryDir, rotation) {
|
|
1504
|
+
await drainPendingImpressionsForOfflineSync(
|
|
1505
|
+
() => new LastRecallStore(memoryDir, {
|
|
1506
|
+
impressionsRotateBytes: rotation.impressionsRotateBytes,
|
|
1507
|
+
impressionsRotateKeep: rotation.impressionsRotateKeep
|
|
1508
|
+
}).drainPendingImpressions()
|
|
1509
|
+
);
|
|
775
1510
|
}
|
|
776
1511
|
|
|
777
1512
|
// src/bench-build-freshness.ts
|
|
@@ -782,15 +1517,15 @@ import {
|
|
|
782
1517
|
readFileSync as readFileSync2,
|
|
783
1518
|
statSync
|
|
784
1519
|
} from "fs";
|
|
785
|
-
import
|
|
1520
|
+
import path3 from "path";
|
|
786
1521
|
import { fileURLToPath } from "url";
|
|
787
1522
|
var STALE_BUILD_TOLERANCE_MS = 1e3;
|
|
788
1523
|
function assertLocalBenchBuildFreshForDevelopment(currentModuleUrl) {
|
|
789
1524
|
if (isTruthyEnv(process.env.REMNIC_BENCH_ALLOW_STALE_DIST)) {
|
|
790
1525
|
return;
|
|
791
1526
|
}
|
|
792
|
-
const currentDir =
|
|
793
|
-
const benchPackageDir =
|
|
1527
|
+
const currentDir = path3.dirname(fileURLToPath(currentModuleUrl));
|
|
1528
|
+
const benchPackageDir = path3.resolve(currentDir, "../../bench");
|
|
794
1529
|
const freshness = checkBenchBuildFreshness(benchPackageDir);
|
|
795
1530
|
if (!freshness.stale) {
|
|
796
1531
|
return;
|
|
@@ -807,7 +1542,7 @@ function assertLocalBenchBuildFreshForDevelopment(currentModuleUrl) {
|
|
|
807
1542
|
);
|
|
808
1543
|
}
|
|
809
1544
|
function checkBenchBuildFreshness(benchPackageDir) {
|
|
810
|
-
const packageJsonPath =
|
|
1545
|
+
const packageJsonPath = path3.join(benchPackageDir, "package.json");
|
|
811
1546
|
if (!existsSync2(packageJsonPath)) {
|
|
812
1547
|
return { stale: false };
|
|
813
1548
|
}
|
|
@@ -820,17 +1555,17 @@ function checkBenchBuildFreshness(benchPackageDir) {
|
|
|
820
1555
|
if (packageName !== "@remnic/bench") {
|
|
821
1556
|
return { stale: false };
|
|
822
1557
|
}
|
|
823
|
-
const srcDir =
|
|
1558
|
+
const srcDir = path3.join(benchPackageDir, "src");
|
|
824
1559
|
if (!isDirectory(srcDir)) {
|
|
825
1560
|
return { stale: false };
|
|
826
1561
|
}
|
|
827
1562
|
const sourceRoots = [
|
|
828
1563
|
srcDir,
|
|
829
1564
|
packageJsonPath,
|
|
830
|
-
|
|
831
|
-
|
|
1565
|
+
path3.join(benchPackageDir, "tsup.config.ts"),
|
|
1566
|
+
path3.join(benchPackageDir, "tsconfig.json")
|
|
832
1567
|
];
|
|
833
|
-
const distPath =
|
|
1568
|
+
const distPath = path3.join(benchPackageDir, "dist", "index.js");
|
|
834
1569
|
if (!existsSync2(distPath)) {
|
|
835
1570
|
return {
|
|
836
1571
|
stale: true,
|
|
@@ -873,7 +1608,7 @@ function newestMtime(roots) {
|
|
|
873
1608
|
}
|
|
874
1609
|
if (stat.isDirectory()) {
|
|
875
1610
|
for (const child of readdirSync(entryPath)) {
|
|
876
|
-
visit(
|
|
1611
|
+
visit(path3.join(entryPath, child));
|
|
877
1612
|
}
|
|
878
1613
|
return;
|
|
879
1614
|
}
|
|
@@ -906,18 +1641,18 @@ function isTruthyEnv(value) {
|
|
|
906
1641
|
|
|
907
1642
|
// src/optional-bench.ts
|
|
908
1643
|
import { existsSync as existsSync3 } from "fs";
|
|
909
|
-
import
|
|
1644
|
+
import path4 from "path";
|
|
910
1645
|
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
|
|
911
1646
|
var SPECIFIER2 = "@remnic/bench";
|
|
912
1647
|
var TSX_ESM_API_SPECIFIER = "tsx/esm/api";
|
|
913
1648
|
var cached2;
|
|
914
1649
|
var cachedFromLocalWorkspaceBenchSource = false;
|
|
915
1650
|
function resolveLocalWorkspaceBenchPaths() {
|
|
916
|
-
const currentDir =
|
|
917
|
-
const benchPackageDir =
|
|
1651
|
+
const currentDir = path4.dirname(fileURLToPath2(import.meta.url));
|
|
1652
|
+
const benchPackageDir = path4.resolve(currentDir, "../../bench");
|
|
918
1653
|
return {
|
|
919
|
-
distEntry:
|
|
920
|
-
sourceEntry:
|
|
1654
|
+
distEntry: path4.join(benchPackageDir, "dist", "index.js"),
|
|
1655
|
+
sourceEntry: path4.join(benchPackageDir, "src", "index.ts")
|
|
921
1656
|
};
|
|
922
1657
|
}
|
|
923
1658
|
async function tryImportLocalWorkspaceBenchSource(err) {
|
|
@@ -1004,8 +1739,8 @@ function assertBenchModuleFreshForDevelopment() {
|
|
|
1004
1739
|
}
|
|
1005
1740
|
|
|
1006
1741
|
// src/daemon-service-candidates.ts
|
|
1007
|
-
import
|
|
1008
|
-
import
|
|
1742
|
+
import fs6 from "fs";
|
|
1743
|
+
import path5 from "path";
|
|
1009
1744
|
var LAUNCHD_LABEL = "ai.remnic.daemon";
|
|
1010
1745
|
var LEGACY_REMNIC_SERVER_LAUNCHD_LABEL = "ai.remnic.server";
|
|
1011
1746
|
var LEGACY_LAUNCHD_LABEL = "ai.engram.daemon";
|
|
@@ -1018,15 +1753,15 @@ var SYSTEMD_SERVICE = "remnic.service";
|
|
|
1018
1753
|
var LEGACY_SYSTEMD_SERVICE = "engram.service";
|
|
1019
1754
|
var SYSTEMD_SERVICE_CANDIDATES = [SYSTEMD_SERVICE, LEGACY_SYSTEMD_SERVICE];
|
|
1020
1755
|
function launchdPlistPaths(homeDir) {
|
|
1021
|
-
return LAUNCHD_LABEL_CANDIDATES.map((label) =>
|
|
1756
|
+
return LAUNCHD_LABEL_CANDIDATES.map((label) => path5.join(homeDir, "Library", "LaunchAgents", `${label}.plist`));
|
|
1022
1757
|
}
|
|
1023
1758
|
function systemdUnitPaths(homeDir) {
|
|
1024
|
-
return SYSTEMD_SERVICE_CANDIDATES.map((service) =>
|
|
1759
|
+
return SYSTEMD_SERVICE_CANDIDATES.map((service) => path5.join(homeDir, ".config", "systemd", "user", service));
|
|
1025
1760
|
}
|
|
1026
1761
|
function anyFileExists(paths) {
|
|
1027
1762
|
return paths.some((candidate) => {
|
|
1028
1763
|
try {
|
|
1029
|
-
return
|
|
1764
|
+
return fs6.statSync(candidate).isFile();
|
|
1030
1765
|
} catch {
|
|
1031
1766
|
return false;
|
|
1032
1767
|
}
|
|
@@ -1038,7 +1773,7 @@ function commandNames(command) {
|
|
|
1038
1773
|
}
|
|
1039
1774
|
function isRunnableNodeScript(filePath) {
|
|
1040
1775
|
try {
|
|
1041
|
-
const text =
|
|
1776
|
+
const text = fs6.readFileSync(filePath, "utf8").slice(0, 4096);
|
|
1042
1777
|
const firstLine = text.split(/\r?\n/, 1)[0] ?? "";
|
|
1043
1778
|
if (/^#!.*\bnode\b/.test(firstLine)) return true;
|
|
1044
1779
|
if (firstLine.startsWith("#!")) return false;
|
|
@@ -1051,20 +1786,20 @@ function isRunnableNodeScript(filePath) {
|
|
|
1051
1786
|
function resolveShimNodeScript(filePath) {
|
|
1052
1787
|
let text;
|
|
1053
1788
|
try {
|
|
1054
|
-
text =
|
|
1789
|
+
text = fs6.readFileSync(filePath, "utf8").slice(0, 16384);
|
|
1055
1790
|
} catch {
|
|
1056
1791
|
return void 0;
|
|
1057
1792
|
}
|
|
1058
|
-
const basedir =
|
|
1793
|
+
const basedir = path5.dirname(filePath);
|
|
1059
1794
|
const jsReferencePattern = /"([^"]+\.js)"|'([^']+\.js)'|([^\s"'`]+\.js)/g;
|
|
1060
1795
|
for (const match of text.matchAll(jsReferencePattern)) {
|
|
1061
1796
|
const raw = match[1] ?? match[2] ?? match[3];
|
|
1062
1797
|
if (!raw) continue;
|
|
1063
1798
|
const candidate = raw.replaceAll("${basedir}", basedir).replaceAll("$basedir", basedir).replaceAll("\\ ", " ");
|
|
1064
|
-
const resolved =
|
|
1799
|
+
const resolved = path5.isAbsolute(candidate) ? candidate : path5.resolve(basedir, candidate);
|
|
1065
1800
|
try {
|
|
1066
|
-
if (
|
|
1067
|
-
return
|
|
1801
|
+
if (fs6.statSync(resolved).isFile() && isRunnableNodeScript(resolved)) {
|
|
1802
|
+
return fs6.realpathSync(resolved);
|
|
1068
1803
|
}
|
|
1069
1804
|
} catch {
|
|
1070
1805
|
}
|
|
@@ -1072,19 +1807,19 @@ function resolveShimNodeScript(filePath) {
|
|
|
1072
1807
|
return void 0;
|
|
1073
1808
|
}
|
|
1074
1809
|
function resolveRunnableNodeScript(filePath) {
|
|
1075
|
-
const realPath =
|
|
1810
|
+
const realPath = fs6.realpathSync(filePath);
|
|
1076
1811
|
if (isRunnableNodeScript(realPath)) return realPath;
|
|
1077
1812
|
return resolveShimNodeScript(realPath);
|
|
1078
1813
|
}
|
|
1079
1814
|
function findCommandOnPath(command, pathEnv = process.env.PATH ?? "") {
|
|
1080
|
-
for (const dir of pathEnv.split(
|
|
1815
|
+
for (const dir of pathEnv.split(path5.delimiter)) {
|
|
1081
1816
|
if (!dir) continue;
|
|
1082
1817
|
for (const name of commandNames(command)) {
|
|
1083
|
-
const candidate =
|
|
1818
|
+
const candidate = path5.join(dir, name);
|
|
1084
1819
|
try {
|
|
1085
|
-
const stat =
|
|
1820
|
+
const stat = fs6.statSync(candidate);
|
|
1086
1821
|
if (!stat.isFile()) continue;
|
|
1087
|
-
if (process.platform !== "win32")
|
|
1822
|
+
if (process.platform !== "win32") fs6.accessSync(candidate, fs6.constants.X_OK);
|
|
1088
1823
|
const runnable = resolveRunnableNodeScript(candidate);
|
|
1089
1824
|
if (runnable) return runnable;
|
|
1090
1825
|
} catch {
|
|
@@ -1094,11 +1829,11 @@ function findCommandOnPath(command, pathEnv = process.env.PATH ?? "") {
|
|
|
1094
1829
|
return void 0;
|
|
1095
1830
|
}
|
|
1096
1831
|
function serverBinWrapperRequiredPath(candidate) {
|
|
1097
|
-
const filename =
|
|
1832
|
+
const filename = path5.basename(candidate);
|
|
1098
1833
|
if (filename !== "remnic-server.js" && filename !== "engram-server.js") return void 0;
|
|
1099
|
-
const binDir =
|
|
1100
|
-
if (
|
|
1101
|
-
return
|
|
1834
|
+
const binDir = path5.dirname(candidate);
|
|
1835
|
+
if (path5.basename(binDir) !== "bin") return void 0;
|
|
1836
|
+
return path5.join(path5.dirname(binDir), "dist", "index.js");
|
|
1102
1837
|
}
|
|
1103
1838
|
|
|
1104
1839
|
// src/service-candidates.ts
|
|
@@ -1120,7 +1855,7 @@ function firstSuccessfulCandidate(candidates, attempt) {
|
|
|
1120
1855
|
}
|
|
1121
1856
|
|
|
1122
1857
|
// src/bench-args.ts
|
|
1123
|
-
import
|
|
1858
|
+
import path7 from "path";
|
|
1124
1859
|
|
|
1125
1860
|
// src/bench-flags.ts
|
|
1126
1861
|
function readBenchOptionValue(argv, flag) {
|
|
@@ -1498,7 +2233,7 @@ function expandTilde(p) {
|
|
|
1498
2233
|
}
|
|
1499
2234
|
|
|
1500
2235
|
// src/bench-args-research.ts
|
|
1501
|
-
import
|
|
2236
|
+
import path6 from "path";
|
|
1502
2237
|
function readPositiveInteger(args, flag) {
|
|
1503
2238
|
const raw = readBenchOptionValue(args, flag);
|
|
1504
2239
|
if (raw === void 0) return void 0;
|
|
@@ -1536,7 +2271,7 @@ function parseBenchResearchArgs(action, args) {
|
|
|
1536
2271
|
}
|
|
1537
2272
|
const outRaw = readBenchOptionValue(args, "--out");
|
|
1538
2273
|
if (outRaw !== void 0) {
|
|
1539
|
-
out =
|
|
2274
|
+
out = path6.resolve(expandTilde(outRaw));
|
|
1540
2275
|
}
|
|
1541
2276
|
}
|
|
1542
2277
|
const epochs = readPositiveInteger(args, "--epochs");
|
|
@@ -1550,7 +2285,7 @@ function parseBenchResearchArgs(action, args) {
|
|
|
1550
2285
|
}
|
|
1551
2286
|
return {
|
|
1552
2287
|
runRef,
|
|
1553
|
-
memoryDir: memoryDirRaw ?
|
|
2288
|
+
memoryDir: memoryDirRaw ? path6.resolve(expandTilde(memoryDirRaw)) : void 0,
|
|
1554
2289
|
users: readPositiveInteger(args, "--users"),
|
|
1555
2290
|
epochs,
|
|
1556
2291
|
seed,
|
|
@@ -1769,7 +2504,7 @@ function parseBenchArgs(argv) {
|
|
|
1769
2504
|
}
|
|
1770
2505
|
validateBenchFlags(action, args);
|
|
1771
2506
|
const driftGenPositionals = action === "drift-gen" && driftGenAction === "validate" ? collectBenchmarks(args.slice(1)) : [];
|
|
1772
|
-
const driftGenDir = driftGenPositionals[0] ?
|
|
2507
|
+
const driftGenDir = driftGenPositionals[0] ? path7.resolve(expandTilde(driftGenPositionals[0])) : void 0;
|
|
1773
2508
|
const benchmarkArgs = action === "baseline" || action === "datasets" || action === "providers" || action === "runs" || action === "drift-gen" && (args[0] === "validate" || args[0] === "generate") ? args.slice(1) : args;
|
|
1774
2509
|
const benchmarks = collectBenchmarks(benchmarkArgs);
|
|
1775
2510
|
const datasetDir = readBenchOptionValue(args, "--dataset-dir") ?? readBenchOptionValue(args, "--dataset");
|
|
@@ -2319,13 +3054,13 @@ function parseBenchArgs(argv) {
|
|
|
2319
3054
|
mcpUrl,
|
|
2320
3055
|
mcpToolMap,
|
|
2321
3056
|
mcpDemo,
|
|
2322
|
-
datasetDir: datasetDir ?
|
|
2323
|
-
resultsDir: resultsDir ?
|
|
2324
|
-
baselinesDir: baselinesDir ?
|
|
3057
|
+
datasetDir: datasetDir ? path7.resolve(expandTilde(datasetDir)) : void 0,
|
|
3058
|
+
resultsDir: resultsDir ? path7.resolve(expandTilde(resultsDir)) : void 0,
|
|
3059
|
+
baselinesDir: baselinesDir ? path7.resolve(expandTilde(baselinesDir)) : void 0,
|
|
2325
3060
|
runtimeProfile,
|
|
2326
3061
|
matrixProfiles,
|
|
2327
|
-
remnicConfigPath: remnicConfigRaw ?
|
|
2328
|
-
openclawConfigPath: openclawConfigRaw ?
|
|
3062
|
+
remnicConfigPath: remnicConfigRaw ? path7.resolve(expandTilde(remnicConfigRaw)) : void 0,
|
|
3063
|
+
openclawConfigPath: openclawConfigRaw ? path7.resolve(expandTilde(openclawConfigRaw)) : void 0,
|
|
2329
3064
|
modelSource,
|
|
2330
3065
|
gatewayAgentId,
|
|
2331
3066
|
fastGatewayAgentId,
|
|
@@ -2348,13 +3083,13 @@ function parseBenchArgs(argv) {
|
|
|
2348
3083
|
internalDisableThinking: args.includes("--internal-disable-thinking"),
|
|
2349
3084
|
internalCodexReasoningEffort,
|
|
2350
3085
|
threshold,
|
|
2351
|
-
custom: customRaw ?
|
|
3086
|
+
custom: customRaw ? path7.resolve(expandTilde(customRaw)) : void 0,
|
|
2352
3087
|
baselineAction,
|
|
2353
3088
|
datasetAction,
|
|
2354
3089
|
providerAction,
|
|
2355
3090
|
runAction,
|
|
2356
3091
|
format,
|
|
2357
|
-
output: output ?
|
|
3092
|
+
output: output ? path7.resolve(expandTilde(output)) : void 0,
|
|
2358
3093
|
target,
|
|
2359
3094
|
publishedName,
|
|
2360
3095
|
publishedSeed,
|
|
@@ -2364,24 +3099,24 @@ function parseBenchArgs(argv) {
|
|
|
2364
3099
|
publishedIngestConcurrency,
|
|
2365
3100
|
publishedTaskFilter,
|
|
2366
3101
|
memcorrectAdapter,
|
|
2367
|
-
publishedOut: publishedOutRaw ?
|
|
3102
|
+
publishedOut: publishedOutRaw ? path7.resolve(expandTilde(publishedOutRaw)) : void 0,
|
|
2368
3103
|
publishedDryRun: args.includes("--dry-run"),
|
|
2369
3104
|
requestTimeout,
|
|
2370
3105
|
localJudgeRequestTimeout,
|
|
2371
3106
|
frontierJudgeRequestTimeout,
|
|
2372
|
-
calibrationDir: calibrationDirRaw ?
|
|
3107
|
+
calibrationDir: calibrationDirRaw ? path7.resolve(expandTilde(calibrationDirRaw)) : void 0,
|
|
2373
3108
|
calibrationLocalConfigSha256,
|
|
2374
3109
|
calibrationFrontierConfigSha256,
|
|
2375
3110
|
sourceResultId,
|
|
2376
3111
|
expectedAnswerSetSha256,
|
|
2377
3112
|
expectedQuestionIdListSha256,
|
|
2378
|
-
taskIdsFile: taskIdsFileRaw ?
|
|
3113
|
+
taskIdsFile: taskIdsFileRaw ? path7.resolve(expandTilde(taskIdsFileRaw)) : void 0,
|
|
2379
3114
|
expectedTaskIdListSha256,
|
|
2380
3115
|
drainTimeout,
|
|
2381
3116
|
// Issue #1573 PR1: surface judge-cache flags into the runner options.
|
|
2382
3117
|
noJudgeCache: args.includes("--no-judge-cache"),
|
|
2383
|
-
judgeCacheDir: judgeCacheDirRaw ?
|
|
2384
|
-
localLabManifestPath: localLabManifestRaw ?
|
|
3118
|
+
judgeCacheDir: judgeCacheDirRaw ? path7.resolve(expandTilde(judgeCacheDirRaw)) : void 0,
|
|
3119
|
+
localLabManifestPath: localLabManifestRaw ? path7.resolve(expandTilde(localLabManifestRaw)) : void 0,
|
|
2385
3120
|
max429WaitMs,
|
|
2386
3121
|
disableThinking: args.includes("--disable-thinking"),
|
|
2387
3122
|
amaBenchJudgeProtocol,
|
|
@@ -2400,9 +3135,9 @@ function parseBenchArgs(argv) {
|
|
|
2400
3135
|
|
|
2401
3136
|
// src/bench-status.ts
|
|
2402
3137
|
import { mkdir, readFile, readdir as readdir2, rename, writeFile } from "fs/promises";
|
|
2403
|
-
import
|
|
3138
|
+
import path8 from "path";
|
|
2404
3139
|
function createBenchStatusPath(resultsDir, pid, startedAtMs = Date.now()) {
|
|
2405
|
-
return
|
|
3140
|
+
return path8.join(resultsDir, `bench-status-${startedAtMs}-${pid}.json`);
|
|
2406
3141
|
}
|
|
2407
3142
|
var BENCH_STATUS_FILENAME = /^bench-status-\d+-\d+\.json$/;
|
|
2408
3143
|
var VALID_BENCH_ENTRY_STATUSES = /* @__PURE__ */ new Set(["pending", "running", "complete", "failed"]);
|
|
@@ -2415,7 +3150,7 @@ async function findLatestBenchStatusFile(resultsDir) {
|
|
|
2415
3150
|
}
|
|
2416
3151
|
const candidates = entries.filter((name) => BENCH_STATUS_FILENAME.test(name)).sort().reverse();
|
|
2417
3152
|
for (const name of candidates) {
|
|
2418
|
-
const filePath =
|
|
3153
|
+
const filePath = path8.join(resultsDir, name);
|
|
2419
3154
|
const status = await readBenchStatus(filePath);
|
|
2420
3155
|
if (status) {
|
|
2421
3156
|
return filePath;
|
|
@@ -2424,7 +3159,7 @@ async function findLatestBenchStatusFile(resultsDir) {
|
|
|
2424
3159
|
return null;
|
|
2425
3160
|
}
|
|
2426
3161
|
async function atomicWriteJSON(filePath, data) {
|
|
2427
|
-
await mkdir(
|
|
3162
|
+
await mkdir(path8.dirname(filePath), { recursive: true });
|
|
2428
3163
|
const tmp = `${filePath}.${Date.now()}-${Math.random().toString(36).slice(2)}.tmp`;
|
|
2429
3164
|
await writeFile(tmp, JSON.stringify(data, null, 2) + "\n");
|
|
2430
3165
|
await rename(tmp, filePath);
|
|
@@ -2541,8 +3276,8 @@ function finalizeBenchStatus(filePath) {
|
|
|
2541
3276
|
}
|
|
2542
3277
|
|
|
2543
3278
|
// src/bench-fallback.ts
|
|
2544
|
-
import
|
|
2545
|
-
import
|
|
3279
|
+
import fs7 from "fs";
|
|
3280
|
+
import path9 from "path";
|
|
2546
3281
|
var FALLBACK_RESULTS_DIRNAME = "fallback-runs";
|
|
2547
3282
|
function buildBenchRunnerArgs(parsed, benchmarkId, outputDir) {
|
|
2548
3283
|
const args = ["--benchmark", benchmarkId];
|
|
@@ -2606,34 +3341,34 @@ function findUnsupportedFallbackBenchOptions(parsed) {
|
|
|
2606
3341
|
return unsupported;
|
|
2607
3342
|
}
|
|
2608
3343
|
function createFallbackBenchOutputDir(resultsDir, benchmarkId, pid, startedAtMs = Date.now()) {
|
|
2609
|
-
return
|
|
3344
|
+
return path9.join(
|
|
2610
3345
|
resultsDir,
|
|
2611
3346
|
FALLBACK_RESULTS_DIRNAME,
|
|
2612
3347
|
`${benchmarkId}-${startedAtMs}-${pid}`
|
|
2613
3348
|
);
|
|
2614
3349
|
}
|
|
2615
3350
|
function resolveFallbackBenchResultPath(outputDir) {
|
|
2616
|
-
const entries =
|
|
3351
|
+
const entries = fs7.readdirSync(outputDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name).sort();
|
|
2617
3352
|
if (entries.length === 0) {
|
|
2618
3353
|
throw new Error(`Fallback benchmark runner did not write a JSON result artifact in ${outputDir}`);
|
|
2619
3354
|
}
|
|
2620
|
-
return
|
|
3355
|
+
return path9.join(outputDir, entries[0]);
|
|
2621
3356
|
}
|
|
2622
3357
|
|
|
2623
3358
|
// src/openclaw-upgrade-swap.ts
|
|
2624
|
-
import
|
|
2625
|
-
import
|
|
3359
|
+
import fs8 from "fs";
|
|
3360
|
+
import path10 from "path";
|
|
2626
3361
|
function describeError(error) {
|
|
2627
3362
|
return error instanceof Error ? error.message : String(error);
|
|
2628
3363
|
}
|
|
2629
3364
|
function createSiblingTempFilePath(targetPath, label) {
|
|
2630
3365
|
const nonce = `${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2, 10)}`;
|
|
2631
|
-
return
|
|
3366
|
+
return path10.join(path10.dirname(targetPath), `.${path10.basename(targetPath)}.${label}.${nonce}.tmp`);
|
|
2632
3367
|
}
|
|
2633
3368
|
function resolveAtomicWriteMode(targetPath, explicitMode) {
|
|
2634
3369
|
if (explicitMode !== void 0) return explicitMode;
|
|
2635
3370
|
try {
|
|
2636
|
-
return
|
|
3371
|
+
return fs8.statSync(targetPath).mode & 4095;
|
|
2637
3372
|
} catch (error) {
|
|
2638
3373
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
2639
3374
|
return 384;
|
|
@@ -2643,8 +3378,8 @@ function resolveAtomicWriteMode(targetPath, explicitMode) {
|
|
|
2643
3378
|
}
|
|
2644
3379
|
function resolveAtomicReplacementPath(targetPath) {
|
|
2645
3380
|
try {
|
|
2646
|
-
if (
|
|
2647
|
-
return
|
|
3381
|
+
if (fs8.lstatSync(targetPath).isSymbolicLink()) {
|
|
3382
|
+
return fs8.realpathSync(targetPath);
|
|
2648
3383
|
}
|
|
2649
3384
|
} catch (error) {
|
|
2650
3385
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
@@ -2656,12 +3391,12 @@ function resolveAtomicReplacementPath(targetPath) {
|
|
|
2656
3391
|
}
|
|
2657
3392
|
function createSiblingSwapPath(targetDir, label) {
|
|
2658
3393
|
const nonce = `${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2, 10)}`;
|
|
2659
|
-
return
|
|
3394
|
+
return path10.join(path10.dirname(targetDir), `.${path10.basename(targetDir)}.${label}.${nonce}`);
|
|
2660
3395
|
}
|
|
2661
3396
|
function cleanupDisplacedDirectoryBestEffort(displacedDir, context) {
|
|
2662
3397
|
if (!displacedDir) return void 0;
|
|
2663
3398
|
try {
|
|
2664
|
-
|
|
3399
|
+
fs8.rmSync(displacedDir, { recursive: true, force: true });
|
|
2665
3400
|
return void 0;
|
|
2666
3401
|
} catch (error) {
|
|
2667
3402
|
return `Warning: ${context}, but failed to remove the displaced plugin copy at ${displacedDir}: ${describeError(error)}`;
|
|
@@ -2669,55 +3404,55 @@ function cleanupDisplacedDirectoryBestEffort(displacedDir, context) {
|
|
|
2669
3404
|
}
|
|
2670
3405
|
function atomicWriteFileSync(targetPath, data, options = {}) {
|
|
2671
3406
|
const resolvedTargetPath = resolveAtomicReplacementPath(targetPath);
|
|
2672
|
-
|
|
3407
|
+
fs8.mkdirSync(path10.dirname(resolvedTargetPath), { recursive: true });
|
|
2673
3408
|
const tempPath = createSiblingTempFilePath(resolvedTargetPath, "write");
|
|
2674
3409
|
const mode = resolveAtomicWriteMode(resolvedTargetPath, options.mode);
|
|
2675
3410
|
try {
|
|
2676
3411
|
if (options.hooks?.writeTempFileSync) {
|
|
2677
3412
|
options.hooks.writeTempFileSync(tempPath);
|
|
2678
3413
|
} else {
|
|
2679
|
-
|
|
3414
|
+
fs8.writeFileSync(tempPath, data, { mode });
|
|
2680
3415
|
}
|
|
2681
|
-
|
|
2682
|
-
const renameTempFileSync = options.hooks?.renameTempFileSync ??
|
|
3416
|
+
fs8.chmodSync(tempPath, mode);
|
|
3417
|
+
const renameTempFileSync = options.hooks?.renameTempFileSync ?? fs8.renameSync;
|
|
2683
3418
|
renameTempFileSync(tempPath, resolvedTargetPath);
|
|
2684
3419
|
} catch (error) {
|
|
2685
|
-
|
|
3420
|
+
fs8.rmSync(tempPath, { force: true });
|
|
2686
3421
|
throw error;
|
|
2687
3422
|
}
|
|
2688
3423
|
}
|
|
2689
3424
|
function atomicCopyFileSync(sourcePath, targetPath, options = {}) {
|
|
2690
|
-
if (!
|
|
3425
|
+
if (!fs8.existsSync(sourcePath)) return;
|
|
2691
3426
|
const resolvedTargetPath = resolveAtomicReplacementPath(targetPath);
|
|
2692
|
-
|
|
3427
|
+
fs8.mkdirSync(path10.dirname(resolvedTargetPath), { recursive: true });
|
|
2693
3428
|
const tempPath = createSiblingTempFilePath(resolvedTargetPath, "copy");
|
|
2694
|
-
const mode =
|
|
3429
|
+
const mode = fs8.statSync(sourcePath).mode & 4095;
|
|
2695
3430
|
try {
|
|
2696
|
-
const copyTempFileSync = options.hooks?.copyTempFileSync ??
|
|
3431
|
+
const copyTempFileSync = options.hooks?.copyTempFileSync ?? fs8.copyFileSync;
|
|
2697
3432
|
copyTempFileSync(sourcePath, tempPath);
|
|
2698
|
-
|
|
2699
|
-
const renameTempFileSync = options.hooks?.renameTempFileSync ??
|
|
3433
|
+
fs8.chmodSync(tempPath, mode);
|
|
3434
|
+
const renameTempFileSync = options.hooks?.renameTempFileSync ?? fs8.renameSync;
|
|
2700
3435
|
renameTempFileSync(tempPath, resolvedTargetPath);
|
|
2701
3436
|
} catch (error) {
|
|
2702
|
-
|
|
3437
|
+
fs8.rmSync(tempPath, { force: true });
|
|
2703
3438
|
throw error;
|
|
2704
3439
|
}
|
|
2705
3440
|
}
|
|
2706
3441
|
function swapDirectoryWithRollback(stagedDir, targetDir, rollbackDir) {
|
|
2707
3442
|
let hasRollbackCopy = false;
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
if (
|
|
2711
|
-
|
|
3443
|
+
fs8.mkdirSync(path10.dirname(targetDir), { recursive: true });
|
|
3444
|
+
fs8.rmSync(rollbackDir, { recursive: true, force: true });
|
|
3445
|
+
if (fs8.existsSync(targetDir)) {
|
|
3446
|
+
fs8.renameSync(targetDir, rollbackDir);
|
|
2712
3447
|
hasRollbackCopy = true;
|
|
2713
3448
|
}
|
|
2714
3449
|
try {
|
|
2715
|
-
|
|
3450
|
+
fs8.renameSync(stagedDir, targetDir);
|
|
2716
3451
|
} catch (swapError) {
|
|
2717
|
-
|
|
2718
|
-
if (hasRollbackCopy &&
|
|
3452
|
+
fs8.rmSync(targetDir, { recursive: true, force: true });
|
|
3453
|
+
if (hasRollbackCopy && fs8.existsSync(rollbackDir)) {
|
|
2719
3454
|
try {
|
|
2720
|
-
|
|
3455
|
+
fs8.renameSync(rollbackDir, targetDir);
|
|
2721
3456
|
hasRollbackCopy = false;
|
|
2722
3457
|
} catch (restoreError) {
|
|
2723
3458
|
throw new AggregateError(
|
|
@@ -2732,7 +3467,7 @@ function swapDirectoryWithRollback(stagedDir, targetDir, rollbackDir) {
|
|
|
2732
3467
|
}
|
|
2733
3468
|
function cleanupRollbackDirectory(rollbackDir) {
|
|
2734
3469
|
if (!rollbackDir) return;
|
|
2735
|
-
|
|
3470
|
+
fs8.rmSync(rollbackDir, { recursive: true, force: true });
|
|
2736
3471
|
}
|
|
2737
3472
|
function cleanupRollbackDirectoryBestEffort(rollbackDir) {
|
|
2738
3473
|
if (!rollbackDir) return void 0;
|
|
@@ -2744,20 +3479,20 @@ function cleanupRollbackDirectoryBestEffort(rollbackDir) {
|
|
|
2744
3479
|
}
|
|
2745
3480
|
}
|
|
2746
3481
|
function restoreDirectoryFromRollback(targetDir, rollbackDir) {
|
|
2747
|
-
if (!
|
|
3482
|
+
if (!fs8.existsSync(rollbackDir)) {
|
|
2748
3483
|
throw new Error(`Rollback directory is missing: ${rollbackDir}`);
|
|
2749
3484
|
}
|
|
2750
|
-
|
|
2751
|
-
const displacedDir =
|
|
3485
|
+
fs8.mkdirSync(path10.dirname(targetDir), { recursive: true });
|
|
3486
|
+
const displacedDir = fs8.existsSync(targetDir) ? createSiblingSwapPath(targetDir, "rollback-restore") : void 0;
|
|
2752
3487
|
if (displacedDir) {
|
|
2753
|
-
|
|
3488
|
+
fs8.renameSync(targetDir, displacedDir);
|
|
2754
3489
|
}
|
|
2755
3490
|
try {
|
|
2756
|
-
|
|
3491
|
+
fs8.renameSync(rollbackDir, targetDir);
|
|
2757
3492
|
} catch (restoreError) {
|
|
2758
|
-
if (displacedDir &&
|
|
3493
|
+
if (displacedDir && fs8.existsSync(displacedDir)) {
|
|
2759
3494
|
try {
|
|
2760
|
-
|
|
3495
|
+
fs8.renameSync(displacedDir, targetDir);
|
|
2761
3496
|
} catch (revertError) {
|
|
2762
3497
|
throw new AggregateError(
|
|
2763
3498
|
[restoreError, revertError],
|
|
@@ -2776,23 +3511,23 @@ function restoreDirectoryFromRollback(targetDir, rollbackDir) {
|
|
|
2776
3511
|
);
|
|
2777
3512
|
}
|
|
2778
3513
|
function restoreDirectoryFromBackup(targetDir, backupDir) {
|
|
2779
|
-
if (!
|
|
3514
|
+
if (!fs8.existsSync(backupDir)) {
|
|
2780
3515
|
throw new Error(`Plugin backup directory is missing: ${backupDir}`);
|
|
2781
3516
|
}
|
|
2782
|
-
|
|
3517
|
+
fs8.mkdirSync(path10.dirname(targetDir), { recursive: true });
|
|
2783
3518
|
const stagedDir = createSiblingSwapPath(targetDir, "backup-restore");
|
|
2784
|
-
const displacedDir =
|
|
2785
|
-
|
|
3519
|
+
const displacedDir = fs8.existsSync(targetDir) ? createSiblingSwapPath(targetDir, "pre-backup-restore") : void 0;
|
|
3520
|
+
fs8.cpSync(backupDir, stagedDir, { recursive: true });
|
|
2786
3521
|
if (displacedDir) {
|
|
2787
|
-
|
|
3522
|
+
fs8.renameSync(targetDir, displacedDir);
|
|
2788
3523
|
}
|
|
2789
3524
|
try {
|
|
2790
|
-
|
|
3525
|
+
fs8.renameSync(stagedDir, targetDir);
|
|
2791
3526
|
} catch (restoreError) {
|
|
2792
|
-
|
|
2793
|
-
if (displacedDir &&
|
|
3527
|
+
fs8.rmSync(targetDir, { recursive: true, force: true });
|
|
3528
|
+
if (displacedDir && fs8.existsSync(displacedDir)) {
|
|
2794
3529
|
try {
|
|
2795
|
-
|
|
3530
|
+
fs8.renameSync(displacedDir, targetDir);
|
|
2796
3531
|
} catch (revertError) {
|
|
2797
3532
|
throw new AggregateError(
|
|
2798
3533
|
[restoreError, revertError],
|
|
@@ -2800,7 +3535,7 @@ function restoreDirectoryFromBackup(targetDir, backupDir) {
|
|
|
2800
3535
|
);
|
|
2801
3536
|
}
|
|
2802
3537
|
}
|
|
2803
|
-
|
|
3538
|
+
fs8.rmSync(stagedDir, { recursive: true, force: true });
|
|
2804
3539
|
throw new Error(
|
|
2805
3540
|
`Failed to restore the plugin backup into ${targetDir}. The durable backup remains preserved at ${backupDir}.`,
|
|
2806
3541
|
{ cause: restoreError }
|
|
@@ -2826,7 +3561,7 @@ function rollbackOpenclawUpgrade({
|
|
|
2826
3561
|
let rollbackRestoreError;
|
|
2827
3562
|
let pluginRestored = false;
|
|
2828
3563
|
try {
|
|
2829
|
-
if (rollbackDir &&
|
|
3564
|
+
if (rollbackDir && fs8.existsSync(rollbackDir)) {
|
|
2830
3565
|
const cleanupWarning = restoreDirectoryFromRollback(pluginDir, rollbackDir);
|
|
2831
3566
|
notes.push(`Restored previous plugin from rollback copy at ${rollbackDir}`);
|
|
2832
3567
|
if (cleanupWarning) notes.push(cleanupWarning);
|
|
@@ -2836,7 +3571,7 @@ function rollbackOpenclawUpgrade({
|
|
|
2836
3571
|
rollbackRestoreError = error instanceof Error ? error.message : String(error);
|
|
2837
3572
|
}
|
|
2838
3573
|
try {
|
|
2839
|
-
if (!pluginRestored && pluginBackupDir &&
|
|
3574
|
+
if (!pluginRestored && pluginBackupDir && fs8.existsSync(pluginBackupDir)) {
|
|
2840
3575
|
const cleanupWarning = restoreDirectoryFromBackup(pluginDir, pluginBackupDir);
|
|
2841
3576
|
if (rollbackRestoreError) {
|
|
2842
3577
|
notes.push(
|
|
@@ -2863,7 +3598,7 @@ function rollbackOpenclawUpgrade({
|
|
|
2863
3598
|
notes.push("No previous plugin copy was available for automatic restore");
|
|
2864
3599
|
}
|
|
2865
3600
|
try {
|
|
2866
|
-
if (configBackupPath &&
|
|
3601
|
+
if (configBackupPath && fs8.existsSync(configBackupPath)) {
|
|
2867
3602
|
restoreFileFromBackup(configPath, configBackupPath);
|
|
2868
3603
|
notes.push(`Restored OpenClaw config from backup at ${configBackupPath}`);
|
|
2869
3604
|
}
|
|
@@ -2903,11 +3638,11 @@ Run this manually when you're ready:
|
|
|
2903
3638
|
}
|
|
2904
3639
|
|
|
2905
3640
|
// src/daemon-service.ts
|
|
2906
|
-
import
|
|
2907
|
-
import
|
|
3641
|
+
import fs9 from "fs";
|
|
3642
|
+
import path11 from "path";
|
|
2908
3643
|
import * as childProcess from "child_process";
|
|
2909
3644
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2910
|
-
var thisModuleDir =
|
|
3645
|
+
var thisModuleDir = path11.dirname(fileURLToPath3(import.meta.url));
|
|
2911
3646
|
function launchdLoadPlist(plistPath, processApi = childProcess) {
|
|
2912
3647
|
processApi.execFileSync("launchctl", ["load", "-w", plistPath], { stdio: "pipe" });
|
|
2913
3648
|
}
|
|
@@ -2915,7 +3650,7 @@ function launchdUnloadPlist(plistPath, processApi = childProcess) {
|
|
|
2915
3650
|
processApi.execFileSync("launchctl", ["unload", plistPath], { stdio: "pipe" });
|
|
2916
3651
|
}
|
|
2917
3652
|
function resolveServerBinDetails(options = {}) {
|
|
2918
|
-
const existsSync4 = options.existsSync ??
|
|
3653
|
+
const existsSync4 = options.existsSync ?? fs9.existsSync;
|
|
2919
3654
|
const findCommandOnPath2 = options.findCommandOnPath ?? findCommandOnPath;
|
|
2920
3655
|
const moduleDir = options.moduleDir ?? thisModuleDir;
|
|
2921
3656
|
const packageResolve = options.packageResolve ?? resolveImportSpecifier;
|
|
@@ -2929,8 +3664,8 @@ function resolveServerBinDetails(options = {}) {
|
|
|
2929
3664
|
});
|
|
2930
3665
|
} catch {
|
|
2931
3666
|
}
|
|
2932
|
-
const workspaceServerBin =
|
|
2933
|
-
const workspaceDistIndex =
|
|
3667
|
+
const workspaceServerBin = path11.resolve(moduleDir, "../../remnic-server/bin/remnic-server.js");
|
|
3668
|
+
const workspaceDistIndex = path11.resolve(moduleDir, "../../remnic-server/dist/index.js");
|
|
2934
3669
|
candidates.push(
|
|
2935
3670
|
{
|
|
2936
3671
|
path: workspaceServerBin,
|
|
@@ -2951,11 +3686,11 @@ function resolveServerBinDetails(options = {}) {
|
|
|
2951
3686
|
});
|
|
2952
3687
|
}
|
|
2953
3688
|
candidates.push({
|
|
2954
|
-
path:
|
|
3689
|
+
path: path11.resolve(moduleDir, "../../remnic-server/src/index.ts"),
|
|
2955
3690
|
source: "workspace-source"
|
|
2956
3691
|
});
|
|
2957
3692
|
const selected = candidates.find((candidate) => isCandidateReady(candidate, existsSync4)) ?? candidates.find((candidate) => existsSync4(candidate.path)) ?? candidates[0] ?? {
|
|
2958
|
-
path:
|
|
3693
|
+
path: path11.resolve(moduleDir, "../../remnic-server/dist/index.js"),
|
|
2959
3694
|
source: "workspace-dist"
|
|
2960
3695
|
};
|
|
2961
3696
|
const exists = existsSync4(selected.path);
|
|
@@ -2974,8 +3709,8 @@ function resolveServerBin(options = {}) {
|
|
|
2974
3709
|
return resolveServerBinDetails(options).path;
|
|
2975
3710
|
}
|
|
2976
3711
|
function readVerifiedDaemonPid(options) {
|
|
2977
|
-
const readFileSync4 = options.readFileSync ??
|
|
2978
|
-
const unlinkSync = options.unlinkSync ??
|
|
3712
|
+
const readFileSync4 = options.readFileSync ?? fs9.readFileSync;
|
|
3713
|
+
const unlinkSync = options.unlinkSync ?? fs9.unlinkSync;
|
|
2979
3714
|
const processKill = options.processKill ?? process.kill;
|
|
2980
3715
|
const platform = options.platform ?? process.platform;
|
|
2981
3716
|
const execFileSync3 = options.execFileSync ?? ((command, args, execOptions) => childProcess.execFileSync(command, args, execOptions));
|
|
@@ -3010,7 +3745,7 @@ function readVerifiedDaemonPid(options) {
|
|
|
3010
3745
|
}
|
|
3011
3746
|
function doesProcessCommandLookLikeRemnicDaemon(command, expectedServerBin) {
|
|
3012
3747
|
const normalizedCommand = command.trim();
|
|
3013
|
-
const normalizedExpected =
|
|
3748
|
+
const normalizedExpected = path11.resolve(expandTilde(expectedServerBin));
|
|
3014
3749
|
return normalizedCommand.includes(normalizedExpected) || /(?:^|\s|[/\\])(?:remnic-server|engram-server)(?:\.js)?(?:\s|$)/.test(normalizedCommand) || /@remnic[/\\]server[/\\]/.test(normalizedCommand) || /packages[/\\]remnic-server[/\\](?:bin[/\\]remnic-server\.js|dist[/\\]index\.js|src[/\\]index\.ts)/.test(normalizedCommand);
|
|
3015
3750
|
}
|
|
3016
3751
|
function parseDaemonPid(raw) {
|
|
@@ -3075,8 +3810,8 @@ function removePidFileBestEffort(file, unlinkSync) {
|
|
|
3075
3810
|
}
|
|
3076
3811
|
}
|
|
3077
3812
|
function inspectLaunchdPlist(plistPath, options = {}) {
|
|
3078
|
-
const existsSync4 = options.existsSync ??
|
|
3079
|
-
const readFileSync4 = options.readFileSync ??
|
|
3813
|
+
const existsSync4 = options.existsSync ?? fs9.existsSync;
|
|
3814
|
+
const readFileSync4 = options.readFileSync ?? fs9.readFileSync;
|
|
3080
3815
|
if (!existsSync4(plistPath)) {
|
|
3081
3816
|
return {
|
|
3082
3817
|
installed: false,
|
|
@@ -3115,7 +3850,7 @@ function inspectLaunchdPlist(plistPath, options = {}) {
|
|
|
3115
3850
|
};
|
|
3116
3851
|
}
|
|
3117
3852
|
const expandedServerArg = expandTilde(serverArg);
|
|
3118
|
-
if (!
|
|
3853
|
+
if (!path11.isAbsolute(expandedServerArg)) {
|
|
3119
3854
|
return {
|
|
3120
3855
|
installed: true,
|
|
3121
3856
|
ok: false,
|
|
@@ -3187,8 +3922,8 @@ function normalizeResolvedPath(resolved) {
|
|
|
3187
3922
|
return resolved;
|
|
3188
3923
|
}
|
|
3189
3924
|
function packageServerBinFromEntry(packageEntry) {
|
|
3190
|
-
if (
|
|
3191
|
-
return
|
|
3925
|
+
if (path11.basename(packageEntry) === "index.js" && path11.basename(path11.dirname(packageEntry)) === "dist") {
|
|
3926
|
+
return path11.join(path11.dirname(path11.dirname(packageEntry)), "bin", "remnic-server.js");
|
|
3192
3927
|
}
|
|
3193
3928
|
return packageEntry;
|
|
3194
3929
|
}
|
|
@@ -3310,7 +4045,7 @@ function stripConfigArgv(args) {
|
|
|
3310
4045
|
}
|
|
3311
4046
|
|
|
3312
4047
|
// src/import-dispatch.ts
|
|
3313
|
-
import
|
|
4048
|
+
import fs10 from "fs";
|
|
3314
4049
|
import {
|
|
3315
4050
|
runImporter,
|
|
3316
4051
|
validateImportBatchSize,
|
|
@@ -3319,7 +4054,7 @@ import {
|
|
|
3319
4054
|
|
|
3320
4055
|
// src/import-bundle-detect.ts
|
|
3321
4056
|
import { lstatSync as lstatSync2, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
3322
|
-
import
|
|
4057
|
+
import path12 from "path";
|
|
3323
4058
|
function detectBundleEntries(bundleDir, options = {}) {
|
|
3324
4059
|
const readdir3 = options.readdirImpl ?? defaultReaddir;
|
|
3325
4060
|
const readFileImpl = options.readFileImpl ?? defaultReadFile;
|
|
@@ -3350,7 +4085,7 @@ function detectBundleEntries(bundleDir, options = {}) {
|
|
|
3350
4085
|
for (const filePath of roots) {
|
|
3351
4086
|
if (seenFiles.has(filePath)) continue;
|
|
3352
4087
|
seenFiles.add(filePath);
|
|
3353
|
-
const name =
|
|
4088
|
+
const name = path12.basename(filePath);
|
|
3354
4089
|
const match = classifyFile(name, filePath, readFileImpl);
|
|
3355
4090
|
if (match) entries.push(match);
|
|
3356
4091
|
}
|
|
@@ -3388,7 +4123,7 @@ function collectCandidatePaths(root, readdir3, isDirectory2, isRegularFile) {
|
|
|
3388
4123
|
return;
|
|
3389
4124
|
}
|
|
3390
4125
|
for (const entry of entries) {
|
|
3391
|
-
const full =
|
|
4126
|
+
const full = path12.join(dir, entry);
|
|
3392
4127
|
if (isDirectory2(full)) {
|
|
3393
4128
|
walk(full, depth + 1);
|
|
3394
4129
|
} else if (isRegularFile(full)) {
|
|
@@ -3824,7 +4559,7 @@ async function cmdImport(rest, targetFactory, disposeTarget, ioOverrides = {}) {
|
|
|
3824
4559
|
let materializedTarget;
|
|
3825
4560
|
let materializePromise;
|
|
3826
4561
|
const io = {
|
|
3827
|
-
readFile: ioOverrides.readFile ?? (async (p) =>
|
|
4562
|
+
readFile: ioOverrides.readFile ?? (async (p) => fs10.promises.readFile(p, "utf-8")),
|
|
3828
4563
|
loadAdapter: ioOverrides.loadAdapter ?? (async (name) => (await loadImporterModule(name)).adapter),
|
|
3829
4564
|
runImporter: ioOverrides.runImporter ?? runImporter,
|
|
3830
4565
|
getWriteTarget: async () => {
|
|
@@ -3937,8 +4672,8 @@ async function cmdCapture(rest, io) {
|
|
|
3937
4672
|
}
|
|
3938
4673
|
|
|
3939
4674
|
// src/import-lossless-claw-cmd.ts
|
|
3940
|
-
import
|
|
3941
|
-
import
|
|
4675
|
+
import fs11 from "fs";
|
|
4676
|
+
import path13 from "path";
|
|
3942
4677
|
import {
|
|
3943
4678
|
applyLcmSchema,
|
|
3944
4679
|
ensureLcmStateDir,
|
|
@@ -4049,15 +4784,15 @@ async function loadImportLosslessClawModule() {
|
|
|
4049
4784
|
|
|
4050
4785
|
// src/import-lossless-claw-cmd.ts
|
|
4051
4786
|
function assertDirectoryOrAbsent(p, label) {
|
|
4052
|
-
if (
|
|
4787
|
+
if (fs11.existsSync(p) && !fs11.statSync(p).isDirectory()) {
|
|
4053
4788
|
throw new Error(`${label} is not a directory: ${p}`);
|
|
4054
4789
|
}
|
|
4055
4790
|
}
|
|
4056
4791
|
function assertFile(p, label) {
|
|
4057
|
-
if (!
|
|
4792
|
+
if (!fs11.existsSync(p)) {
|
|
4058
4793
|
throw new Error(`${label} does not exist: ${p}`);
|
|
4059
4794
|
}
|
|
4060
|
-
if (!
|
|
4795
|
+
if (!fs11.statSync(p).isFile()) {
|
|
4061
4796
|
throw new Error(`${label} is not a file: ${p}`);
|
|
4062
4797
|
}
|
|
4063
4798
|
}
|
|
@@ -4088,8 +4823,8 @@ async function cmdImportLosslessClaw(argv, io, deps = {}) {
|
|
|
4088
4823
|
let destDb;
|
|
4089
4824
|
try {
|
|
4090
4825
|
if (parsed.dryRun) {
|
|
4091
|
-
const lcmPath =
|
|
4092
|
-
if (
|
|
4826
|
+
const lcmPath = path13.join(memoryDir, "state", "lcm.sqlite");
|
|
4827
|
+
if (fs11.existsSync(lcmPath)) {
|
|
4093
4828
|
destDb = mod.openExistingLcmDatabaseReadOnly(lcmPath);
|
|
4094
4829
|
} else {
|
|
4095
4830
|
destDb = mod.openInMemoryDestinationDatabase();
|
|
@@ -4207,7 +4942,7 @@ function printBenchComparisonSummary(comparison, baseline, candidate) {
|
|
|
4207
4942
|
}
|
|
4208
4943
|
|
|
4209
4944
|
// src/bench-research-commands.ts
|
|
4210
|
-
import
|
|
4945
|
+
import path14 from "path";
|
|
4211
4946
|
function emit(result) {
|
|
4212
4947
|
if (result.output) {
|
|
4213
4948
|
console.log(result.output);
|
|
@@ -4225,7 +4960,7 @@ async function runBenchResearchCommand(parsed) {
|
|
|
4225
4960
|
emit(
|
|
4226
4961
|
await runAttributeCliCommand({
|
|
4227
4962
|
runRef: parsed.runRef,
|
|
4228
|
-
resultsDir: parsed.resultsDir ??
|
|
4963
|
+
resultsDir: parsed.resultsDir ?? path14.join(resolveHomeDir(), ".remnic", "bench", "results"),
|
|
4229
4964
|
memoryDir: parsed.memoryDir,
|
|
4230
4965
|
threshold: parsed.threshold,
|
|
4231
4966
|
json: parsed.json
|
|
@@ -4480,15 +5215,15 @@ registerPublisher("omp", () => new LazyPluginPiPublisher("omp", (mod) => mod.Omp
|
|
|
4480
5215
|
function readCompatEnv(primary, legacy) {
|
|
4481
5216
|
return process.env[primary] ?? process.env[legacy];
|
|
4482
5217
|
}
|
|
4483
|
-
var PID_DIR =
|
|
4484
|
-
var LEGACY_PID_DIR =
|
|
4485
|
-
var PID_FILE =
|
|
4486
|
-
var LEGACY_PID_FILE =
|
|
4487
|
-
var LOG_FILE =
|
|
4488
|
-
var LEGACY_LOG_FILE =
|
|
4489
|
-
var CLI_MODULE_DIR =
|
|
4490
|
-
var CLI_REPO_ROOT =
|
|
4491
|
-
var EVAL_RUNNER_PATH =
|
|
5218
|
+
var PID_DIR = path15.join(resolveHomeDir(), ".remnic");
|
|
5219
|
+
var LEGACY_PID_DIR = path15.join(resolveHomeDir(), ".engram");
|
|
5220
|
+
var PID_FILE = path15.join(PID_DIR, "server.pid");
|
|
5221
|
+
var LEGACY_PID_FILE = path15.join(LEGACY_PID_DIR, "server.pid");
|
|
5222
|
+
var LOG_FILE = path15.join(PID_DIR, "server.log");
|
|
5223
|
+
var LEGACY_LOG_FILE = path15.join(LEGACY_PID_DIR, "server.log");
|
|
5224
|
+
var CLI_MODULE_DIR = path15.dirname(fileURLToPath4(import.meta.url));
|
|
5225
|
+
var CLI_REPO_ROOT = path15.resolve(CLI_MODULE_DIR, "../../..");
|
|
5226
|
+
var EVAL_RUNNER_PATH = path15.join(CLI_REPO_ROOT, "evals", "run.ts");
|
|
4492
5227
|
var OPENCLAW_GATEWAY_LABEL = "ai.openclaw.gateway";
|
|
4493
5228
|
var CLI_SUCCESS_EXIT_GRACE_MS = 5e3;
|
|
4494
5229
|
var CLI_OUTPUT_FLUSH_GRACE_MS = 250;
|
|
@@ -4653,7 +5388,7 @@ async function resolveAllBenchmarks() {
|
|
|
4653
5388
|
if (packageBenchmarks) {
|
|
4654
5389
|
return packageBenchmarks.filter((entry) => entry.runnerAvailable).map((entry) => entry.id);
|
|
4655
5390
|
}
|
|
4656
|
-
if (!
|
|
5391
|
+
if (!fs12.existsSync(EVAL_RUNNER_PATH)) {
|
|
4657
5392
|
return [];
|
|
4658
5393
|
}
|
|
4659
5394
|
return BENCHMARK_CATALOG.filter((entry) => entry.category !== "ingestion").map((entry) => entry.id);
|
|
@@ -4701,17 +5436,17 @@ async function runBenchViaFallback(parsed, benchmarkId, runtimeProfile) {
|
|
|
4701
5436
|
`Fallback benchmark runner does not support provider-backed, gateway, or thinking/timeout flags (${unsupportedOptions.join(", ")}). Build/install @remnic/bench to use those options.`
|
|
4702
5437
|
);
|
|
4703
5438
|
}
|
|
4704
|
-
if (!
|
|
5439
|
+
if (!fs12.existsSync(EVAL_RUNNER_PATH)) {
|
|
4705
5440
|
console.error(
|
|
4706
5441
|
"Benchmark runner not found. Expected eval runner at evals/run.ts or a phase-1 @remnic/bench runtime export."
|
|
4707
5442
|
);
|
|
4708
5443
|
process.exit(1);
|
|
4709
5444
|
}
|
|
4710
5445
|
const tsxCandidates = [
|
|
4711
|
-
|
|
4712
|
-
|
|
5446
|
+
path15.join(CLI_REPO_ROOT, "node_modules", ".bin", "tsx"),
|
|
5447
|
+
path15.join(CLI_REPO_ROOT, "packages", "remnic-cli", "node_modules", ".bin", "tsx")
|
|
4713
5448
|
];
|
|
4714
|
-
const tsxCmd = tsxCandidates.find((candidate) =>
|
|
5449
|
+
const tsxCmd = tsxCandidates.find((candidate) => fs12.existsSync(candidate)) ?? "tsx";
|
|
4715
5450
|
const fallbackOutputDir = createFallbackBenchOutputDir(
|
|
4716
5451
|
parsed.resultsDir ?? resolveBenchOutputDir(),
|
|
4717
5452
|
benchmarkId,
|
|
@@ -4728,7 +5463,7 @@ async function runBenchViaFallback(parsed, benchmarkId, runtimeProfile) {
|
|
|
4728
5463
|
return resolveFallbackBenchResultPath(fallbackOutputDir);
|
|
4729
5464
|
}
|
|
4730
5465
|
function resolveBenchOutputDir() {
|
|
4731
|
-
return
|
|
5466
|
+
return path15.join(resolveHomeDir(), ".remnic", "bench", "results");
|
|
4732
5467
|
}
|
|
4733
5468
|
var DOWNLOADABLE_BENCHMARK_DATASETS = [
|
|
4734
5469
|
"ama-bench",
|
|
@@ -4773,8 +5508,8 @@ var MEMORY_AGENT_BENCH_SPLIT_FILENAMES = [
|
|
|
4773
5508
|
];
|
|
4774
5509
|
var MEMORY_AGENT_BENCH_ENTITY_MAPPING_CANDIDATES = [
|
|
4775
5510
|
"entity2id.json",
|
|
4776
|
-
|
|
4777
|
-
|
|
5511
|
+
path15.join("processed_data", "Recsys_Redial", "entity2id.json"),
|
|
5512
|
+
path15.join("Recsys_Redial", "entity2id.json")
|
|
4778
5513
|
];
|
|
4779
5514
|
var DOWNLOADED_DATASET_MARKERS = {
|
|
4780
5515
|
"ama-bench": { anyOf: ["open_end_qa_set.jsonl"] },
|
|
@@ -4849,18 +5584,18 @@ var PERSONAMEM_DATASET_FILE_CANDIDATES = [
|
|
|
4849
5584
|
"benchmark/benchmark.csv",
|
|
4850
5585
|
"benchmark.csv"
|
|
4851
5586
|
];
|
|
4852
|
-
var PERSONAMEM_COMPLETION_MARKER =
|
|
5587
|
+
var PERSONAMEM_COMPLETION_MARKER = path15.join(
|
|
4853
5588
|
"data",
|
|
4854
5589
|
"chat_history_32k",
|
|
4855
5590
|
".download-complete"
|
|
4856
5591
|
);
|
|
4857
5592
|
function resolveRealpathWithinDataset(datasetPath, relativePath) {
|
|
4858
5593
|
try {
|
|
4859
|
-
const datasetRoot =
|
|
4860
|
-
const candidatePath =
|
|
4861
|
-
const candidateRealPath =
|
|
4862
|
-
const relativeToRoot =
|
|
4863
|
-
if (relativeToRoot.startsWith("..") ||
|
|
5594
|
+
const datasetRoot = fs12.realpathSync(datasetPath);
|
|
5595
|
+
const candidatePath = path15.resolve(datasetRoot, relativePath);
|
|
5596
|
+
const candidateRealPath = fs12.realpathSync(candidatePath);
|
|
5597
|
+
const relativeToRoot = path15.relative(datasetRoot, candidateRealPath);
|
|
5598
|
+
if (relativeToRoot.startsWith("..") || path15.isAbsolute(relativeToRoot)) {
|
|
4864
5599
|
return null;
|
|
4865
5600
|
}
|
|
4866
5601
|
return candidateRealPath;
|
|
@@ -4916,15 +5651,15 @@ function parseCsvRows(raw) {
|
|
|
4916
5651
|
}
|
|
4917
5652
|
function isPersonaMemDatasetComplete(datasetPath) {
|
|
4918
5653
|
try {
|
|
4919
|
-
const completionMarkerPath =
|
|
4920
|
-
if (
|
|
5654
|
+
const completionMarkerPath = path15.join(datasetPath, PERSONAMEM_COMPLETION_MARKER);
|
|
5655
|
+
if (fs12.statSync(completionMarkerPath).isFile()) {
|
|
4921
5656
|
return true;
|
|
4922
5657
|
}
|
|
4923
5658
|
} catch {
|
|
4924
5659
|
}
|
|
4925
5660
|
const datasetFile = PERSONAMEM_DATASET_FILE_CANDIDATES.find((candidate) => {
|
|
4926
5661
|
try {
|
|
4927
|
-
return
|
|
5662
|
+
return fs12.statSync(path15.join(datasetPath, candidate)).isFile();
|
|
4928
5663
|
} catch {
|
|
4929
5664
|
return false;
|
|
4930
5665
|
}
|
|
@@ -4933,7 +5668,7 @@ function isPersonaMemDatasetComplete(datasetPath) {
|
|
|
4933
5668
|
return false;
|
|
4934
5669
|
}
|
|
4935
5670
|
try {
|
|
4936
|
-
const rows = parseCsvRows(
|
|
5671
|
+
const rows = parseCsvRows(fs12.readFileSync(path15.join(datasetPath, datasetFile), "utf8"));
|
|
4937
5672
|
if (rows.length < 2) {
|
|
4938
5673
|
return false;
|
|
4939
5674
|
}
|
|
@@ -4948,7 +5683,7 @@ function isPersonaMemDatasetComplete(datasetPath) {
|
|
|
4948
5683
|
}
|
|
4949
5684
|
return historyPaths.every((relativePath) => {
|
|
4950
5685
|
const resolvedPath = resolveRealpathWithinDataset(datasetPath, relativePath);
|
|
4951
|
-
return resolvedPath !== null &&
|
|
5686
|
+
return resolvedPath !== null && fs12.statSync(resolvedPath).isFile();
|
|
4952
5687
|
});
|
|
4953
5688
|
} catch {
|
|
4954
5689
|
return false;
|
|
@@ -4956,14 +5691,14 @@ function isPersonaMemDatasetComplete(datasetPath) {
|
|
|
4956
5691
|
}
|
|
4957
5692
|
function hasDatasetFile(datasetPath, relativePath) {
|
|
4958
5693
|
try {
|
|
4959
|
-
return
|
|
5694
|
+
return fs12.statSync(path15.join(datasetPath, relativePath)).isFile();
|
|
4960
5695
|
} catch {
|
|
4961
5696
|
return false;
|
|
4962
5697
|
}
|
|
4963
5698
|
}
|
|
4964
5699
|
function hasMemoryAgentBenchEntityMapping(datasetPath) {
|
|
4965
|
-
const absoluteDatasetPath =
|
|
4966
|
-
const roots = [absoluteDatasetPath,
|
|
5700
|
+
const absoluteDatasetPath = path15.resolve(datasetPath);
|
|
5701
|
+
const roots = [absoluteDatasetPath, path15.dirname(absoluteDatasetPath)];
|
|
4967
5702
|
return hasDatasetFile(absoluteDatasetPath, "entity2id.json") || roots.some(
|
|
4968
5703
|
(root) => MEMORY_AGENT_BENCH_ENTITY_MAPPING_CANDIDATES.filter((relativePath) => relativePath !== "entity2id.json").some((relativePath) => hasDatasetFile(root, relativePath))
|
|
4969
5704
|
);
|
|
@@ -4974,12 +5709,12 @@ function memoryAgentBenchDatasetHasRecSysSamples(datasetPath) {
|
|
|
4974
5709
|
...MEMORY_AGENT_BENCH_SPLIT_FILENAMES
|
|
4975
5710
|
];
|
|
4976
5711
|
return candidateFilenames.some((filename) => {
|
|
4977
|
-
const filePath =
|
|
5712
|
+
const filePath = path15.join(datasetPath, filename);
|
|
4978
5713
|
try {
|
|
4979
|
-
if (!
|
|
5714
|
+
if (!fs12.statSync(filePath).isFile()) {
|
|
4980
5715
|
return false;
|
|
4981
5716
|
}
|
|
4982
|
-
const raw =
|
|
5717
|
+
const raw = fs12.readFileSync(filePath, "utf8");
|
|
4983
5718
|
return /"source"\s*:\s*"recsys[_-]/i.test(raw);
|
|
4984
5719
|
} catch {
|
|
4985
5720
|
return false;
|
|
@@ -4995,7 +5730,7 @@ function isMemoryAgentBenchDatasetComplete(datasetPath) {
|
|
|
4995
5730
|
function isDatasetDownloaded(datasetPath, benchmarkId) {
|
|
4996
5731
|
let stats;
|
|
4997
5732
|
try {
|
|
4998
|
-
stats =
|
|
5733
|
+
stats = fs12.statSync(datasetPath);
|
|
4999
5734
|
} catch {
|
|
5000
5735
|
return false;
|
|
5001
5736
|
}
|
|
@@ -5005,7 +5740,7 @@ function isDatasetDownloaded(datasetPath, benchmarkId) {
|
|
|
5005
5740
|
const marker = DOWNLOADED_DATASET_MARKERS[benchmarkId];
|
|
5006
5741
|
if (!marker) {
|
|
5007
5742
|
try {
|
|
5008
|
-
return
|
|
5743
|
+
return fs12.readdirSync(datasetPath).length > 0;
|
|
5009
5744
|
} catch {
|
|
5010
5745
|
return false;
|
|
5011
5746
|
}
|
|
@@ -5013,7 +5748,7 @@ function isDatasetDownloaded(datasetPath, benchmarkId) {
|
|
|
5013
5748
|
if (marker.allOf) {
|
|
5014
5749
|
const hasAllRequiredFiles = marker.allOf.every((name) => {
|
|
5015
5750
|
try {
|
|
5016
|
-
return
|
|
5751
|
+
return fs12.statSync(path15.join(datasetPath, name)).isFile();
|
|
5017
5752
|
} catch {
|
|
5018
5753
|
return false;
|
|
5019
5754
|
}
|
|
@@ -5025,7 +5760,7 @@ function isDatasetDownloaded(datasetPath, benchmarkId) {
|
|
|
5025
5760
|
if (marker.anyOf) {
|
|
5026
5761
|
const hasMarkerFile = marker.anyOf.some((name) => {
|
|
5027
5762
|
try {
|
|
5028
|
-
return
|
|
5763
|
+
return fs12.statSync(path15.join(datasetPath, name)).isFile();
|
|
5029
5764
|
} catch {
|
|
5030
5765
|
return false;
|
|
5031
5766
|
}
|
|
@@ -5043,7 +5778,7 @@ function isDatasetDownloaded(datasetPath, benchmarkId) {
|
|
|
5043
5778
|
}
|
|
5044
5779
|
if (marker.ext) {
|
|
5045
5780
|
try {
|
|
5046
|
-
return
|
|
5781
|
+
return fs12.readdirSync(datasetPath).some(
|
|
5047
5782
|
(name) => name.endsWith(marker.ext) && !marker.exclude?.includes(name)
|
|
5048
5783
|
);
|
|
5049
5784
|
} catch {
|
|
@@ -5053,9 +5788,9 @@ function isDatasetDownloaded(datasetPath, benchmarkId) {
|
|
|
5053
5788
|
return false;
|
|
5054
5789
|
}
|
|
5055
5790
|
async function launchBenchUi(resultsDir) {
|
|
5056
|
-
const benchUiDir =
|
|
5791
|
+
const benchUiDir = path15.join(CLI_REPO_ROOT, "packages", "bench-ui");
|
|
5057
5792
|
const pnpmCmd = process.platform === "win32" ? "pnpm.cmd" : "pnpm";
|
|
5058
|
-
if (!
|
|
5793
|
+
if (!fs12.existsSync(path15.join(benchUiDir, "package.json"))) {
|
|
5059
5794
|
console.error("ERROR: @remnic/bench-ui is not available in this checkout.");
|
|
5060
5795
|
process.exit(1);
|
|
5061
5796
|
}
|
|
@@ -5082,24 +5817,24 @@ async function launchBenchUi(resultsDir) {
|
|
|
5082
5817
|
});
|
|
5083
5818
|
}
|
|
5084
5819
|
function resolveRepoDatasetRoot() {
|
|
5085
|
-
const repoCandidate =
|
|
5820
|
+
const repoCandidate = path15.join(CLI_REPO_ROOT, "evals", "datasets");
|
|
5086
5821
|
if (isRepoCheckout()) {
|
|
5087
5822
|
return repoCandidate;
|
|
5088
5823
|
}
|
|
5089
|
-
return
|
|
5824
|
+
return path15.join(resolveHomeDir(), ".remnic", "bench", "datasets");
|
|
5090
5825
|
}
|
|
5091
5826
|
function listDownloadableBenchmarks() {
|
|
5092
5827
|
return [...DOWNLOADABLE_BENCHMARK_DATASETS];
|
|
5093
5828
|
}
|
|
5094
5829
|
function resolveDatasetDownloadScriptPath() {
|
|
5095
|
-
const bundled =
|
|
5096
|
-
if (
|
|
5830
|
+
const bundled = path15.join(CLI_MODULE_DIR, "assets", "download-datasets.sh");
|
|
5831
|
+
if (fs12.existsSync(bundled)) {
|
|
5097
5832
|
return bundled;
|
|
5098
5833
|
}
|
|
5099
|
-
return
|
|
5834
|
+
return path15.join(CLI_REPO_ROOT, "evals", "scripts", "download-datasets.sh");
|
|
5100
5835
|
}
|
|
5101
5836
|
function isRepoCheckout() {
|
|
5102
|
-
return
|
|
5837
|
+
return fs12.existsSync(path15.join(CLI_REPO_ROOT, "pnpm-workspace.yaml")) && fs12.existsSync(path15.join(CLI_REPO_ROOT, "evals", "scripts", "download-datasets.sh"));
|
|
5103
5838
|
}
|
|
5104
5839
|
function runDatasetDownloadScript(scriptPath, benchmarkId, datasetRoot, jsonMode) {
|
|
5105
5840
|
const stdio = jsonMode ? ["inherit", process.stderr, "inherit"] : "inherit";
|
|
@@ -5150,7 +5885,7 @@ function resolveBenchDatasetDir(benchmarkId, quick, datasetDirOverride) {
|
|
|
5150
5885
|
if (quick) {
|
|
5151
5886
|
return void 0;
|
|
5152
5887
|
}
|
|
5153
|
-
const datasetDir =
|
|
5888
|
+
const datasetDir = path15.join(resolveRepoDatasetRoot(), benchmarkId);
|
|
5154
5889
|
if (isDatasetDownloaded(datasetDir, benchmarkId)) {
|
|
5155
5890
|
return datasetDir;
|
|
5156
5891
|
}
|
|
@@ -5407,13 +6142,13 @@ async function exportBenchPackageResult(parsed) {
|
|
|
5407
6142
|
process.exit(1);
|
|
5408
6143
|
}
|
|
5409
6144
|
const result = await loadBenchmarkResult(summary.path);
|
|
5410
|
-
const reportCardProvenance = parsed.format === "html" ? await loadBenchmarkReportCardProvenance(
|
|
6145
|
+
const reportCardProvenance = parsed.format === "html" ? await loadBenchmarkReportCardProvenance(path15.dirname(summary.path), result.meta.id) : void 0;
|
|
5411
6146
|
const rendered = renderBenchmarkResultExport(result, parsed.format, {
|
|
5412
6147
|
...reportCardProvenance ? { reportCardProvenance } : {}
|
|
5413
6148
|
});
|
|
5414
6149
|
if (parsed.output) {
|
|
5415
|
-
|
|
5416
|
-
|
|
6150
|
+
fs12.mkdirSync(path15.dirname(parsed.output), { recursive: true });
|
|
6151
|
+
fs12.writeFileSync(parsed.output, rendered);
|
|
5417
6152
|
console.log(`Exported ${summary.id} as ${parsed.format} to ${parsed.output}`);
|
|
5418
6153
|
return;
|
|
5419
6154
|
}
|
|
@@ -5430,7 +6165,7 @@ async function manageBenchDatasets(parsed) {
|
|
|
5430
6165
|
process.exit(1);
|
|
5431
6166
|
}
|
|
5432
6167
|
const status = supported.map((benchmarkId) => {
|
|
5433
|
-
const datasetPath =
|
|
6168
|
+
const datasetPath = path15.join(datasetRoot, benchmarkId);
|
|
5434
6169
|
return {
|
|
5435
6170
|
benchmark: benchmarkId,
|
|
5436
6171
|
downloaded: isDatasetDownloaded(datasetPath, benchmarkId),
|
|
@@ -5458,7 +6193,7 @@ async function manageBenchDatasets(parsed) {
|
|
|
5458
6193
|
process.exit(1);
|
|
5459
6194
|
}
|
|
5460
6195
|
const scriptPath = resolveDatasetDownloadScriptPath();
|
|
5461
|
-
if (!
|
|
6196
|
+
if (!fs12.existsSync(scriptPath)) {
|
|
5462
6197
|
console.error(`ERROR: dataset download script not found: ${scriptPath}`);
|
|
5463
6198
|
process.exit(1);
|
|
5464
6199
|
}
|
|
@@ -5468,7 +6203,7 @@ async function manageBenchDatasets(parsed) {
|
|
|
5468
6203
|
runDatasetDownloadScript(scriptPath, benchmarkId, datasetRoot, parsed.json === true);
|
|
5469
6204
|
downloaded.push({
|
|
5470
6205
|
benchmark: benchmarkId,
|
|
5471
|
-
path:
|
|
6206
|
+
path: path15.join(datasetRoot, benchmarkId)
|
|
5472
6207
|
});
|
|
5473
6208
|
}
|
|
5474
6209
|
if (parsed.json) {
|
|
@@ -5607,10 +6342,10 @@ async function calibrateBenchJudges(parsed, rawArgs) {
|
|
|
5607
6342
|
}
|
|
5608
6343
|
const bench = await loadBenchModule();
|
|
5609
6344
|
const resultsDir = expandTilde(
|
|
5610
|
-
parsed.resultsDir ??
|
|
6345
|
+
parsed.resultsDir ?? path15.join(resolveHomeDir(), ".remnic", "bench", "results")
|
|
5611
6346
|
);
|
|
5612
6347
|
const calibrationDir = expandTilde(
|
|
5613
|
-
parsed.calibrationDir ??
|
|
6348
|
+
parsed.calibrationDir ?? path15.join(resolveHomeDir(), ".remnic", "bench", "calibration")
|
|
5614
6349
|
);
|
|
5615
6350
|
const stored = await bench.listBenchmarkResults(resultsDir);
|
|
5616
6351
|
const allForBenchmark = stored.filter((entry) => entry.benchmark === benchmarkId);
|
|
@@ -5658,7 +6393,7 @@ async function calibrateBenchJudges(parsed, rawArgs) {
|
|
|
5658
6393
|
);
|
|
5659
6394
|
process.exit(1);
|
|
5660
6395
|
}
|
|
5661
|
-
const sourceResultSha256 =
|
|
6396
|
+
const sourceResultSha256 = createHash3("sha256").update(fs12.readFileSync(latest.path)).digest("hex");
|
|
5662
6397
|
const expandedManifestPath = expandTilde(manifestPath);
|
|
5663
6398
|
if (!bench.resolveLocalLabJudgeProviderConfig) {
|
|
5664
6399
|
console.error(
|
|
@@ -6073,7 +6808,7 @@ function loadPinnedLoCoMoTaskSelector(parsed) {
|
|
|
6073
6808
|
}
|
|
6074
6809
|
let decoded;
|
|
6075
6810
|
try {
|
|
6076
|
-
decoded = JSON.parse(
|
|
6811
|
+
decoded = JSON.parse(fs12.readFileSync(parsed.taskIdsFile, "utf8"));
|
|
6077
6812
|
} catch (error) {
|
|
6078
6813
|
throw new Error(
|
|
6079
6814
|
`Unable to read --task-ids-file ${parsed.taskIdsFile}: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -6170,7 +6905,7 @@ async function loadPublishedPromotionHelpers() {
|
|
|
6170
6905
|
return {
|
|
6171
6906
|
async promoteArtifactsToPublished(args) {
|
|
6172
6907
|
const { mkdirSync, readFileSync: readFileSync4, writeFileSync } = await import("fs");
|
|
6173
|
-
const
|
|
6908
|
+
const path16 = await import("path");
|
|
6174
6909
|
mkdirSync(args.publishedOutDir, { recursive: true });
|
|
6175
6910
|
if (args.artifactPaths.length === 0) {
|
|
6176
6911
|
console.warn(
|
|
@@ -6187,13 +6922,13 @@ async function loadPublishedPromotionHelpers() {
|
|
|
6187
6922
|
const modelSlug = args.model.replace(/[^a-zA-Z0-9_.-]/g, "-");
|
|
6188
6923
|
const rawProfile = parsedObj.config?.runtimeProfile;
|
|
6189
6924
|
const profileSlug = typeof rawProfile === "string" && rawProfile.length > 0 ? `-${rawProfile.replace(/[^a-zA-Z0-9_.-]/g, "-")}` : "";
|
|
6190
|
-
const target =
|
|
6925
|
+
const target = path16.join(
|
|
6191
6926
|
args.publishedOutDir,
|
|
6192
6927
|
`${today}-${args.benchmarkId}-${modelSlug}${profileSlug}-${gitShaShort}.json`
|
|
6193
6928
|
);
|
|
6194
6929
|
writeFileSync(target, raw, "utf8");
|
|
6195
6930
|
console.log(
|
|
6196
|
-
`[bench published] Promoted ${
|
|
6931
|
+
`[bench published] Promoted ${path16.basename(artifactPath)} \u2192 ${target}`
|
|
6197
6932
|
);
|
|
6198
6933
|
}
|
|
6199
6934
|
void benchModule;
|
|
@@ -6300,7 +7035,7 @@ async function runBenchViaPackage(parsed, benchmarkId, runtimeProfile, benchStat
|
|
|
6300
7035
|
const previousCodexDiagnosticsDir = process.env[CODEX_CLI_BENCH_DIAGNOSTICS_DIR_ENV];
|
|
6301
7036
|
const previousCodexDiagnosticsMode = process.env[CODEX_CLI_BENCH_DIAGNOSTICS_MODE_ENV];
|
|
6302
7037
|
if (!previousCodexDiagnosticsDir) {
|
|
6303
|
-
process.env[CODEX_CLI_BENCH_DIAGNOSTICS_DIR_ENV] =
|
|
7038
|
+
process.env[CODEX_CLI_BENCH_DIAGNOSTICS_DIR_ENV] = path15.join(
|
|
6304
7039
|
outputDir,
|
|
6305
7040
|
"codex-cli-diagnostics"
|
|
6306
7041
|
);
|
|
@@ -6448,7 +7183,7 @@ async function preparePersistedJudgeCalibrationAttachment(benchModule, benchmark
|
|
|
6448
7183
|
);
|
|
6449
7184
|
}
|
|
6450
7185
|
const calibrationDir = expandTilde(
|
|
6451
|
-
calibrationBinding.calibrationDir ??
|
|
7186
|
+
calibrationBinding.calibrationDir ?? path15.join(resolveHomeDir(), ".remnic", "bench", "calibration")
|
|
6452
7187
|
);
|
|
6453
7188
|
const state = await benchModule.loadJudgeCalibrationState?.(benchmarkId, calibrationDir);
|
|
6454
7189
|
if (!state) {
|
|
@@ -6522,7 +7257,7 @@ function attachPreparedJudgeCalibration(result, judgeCalibration) {
|
|
|
6522
7257
|
function hashCalibrationProviderConfig(config) {
|
|
6523
7258
|
const canonicalize = (value, key = "") => {
|
|
6524
7259
|
if (typeof value === "string" && /(?:api.?key|authorization|token|secret)/i.test(key)) {
|
|
6525
|
-
return { secretSha256:
|
|
7260
|
+
return { secretSha256: createHash3("sha256").update(value).digest("hex") };
|
|
6526
7261
|
}
|
|
6527
7262
|
if (Array.isArray(value)) return value.map((item) => canonicalize(item));
|
|
6528
7263
|
if (value && typeof value === "object") {
|
|
@@ -6533,7 +7268,7 @@ function hashCalibrationProviderConfig(config) {
|
|
|
6533
7268
|
}
|
|
6534
7269
|
return value;
|
|
6535
7270
|
};
|
|
6536
|
-
return
|
|
7271
|
+
return createHash3("sha256").update(JSON.stringify(canonicalize(config))).digest("hex");
|
|
6537
7272
|
}
|
|
6538
7273
|
function restoreOptionalEnv(key, previousValue) {
|
|
6539
7274
|
if (previousValue === void 0) {
|
|
@@ -6745,7 +7480,7 @@ function resolveBenchReproDatasetDir(datasetDir) {
|
|
|
6745
7480
|
return void 0;
|
|
6746
7481
|
}
|
|
6747
7482
|
try {
|
|
6748
|
-
return
|
|
7483
|
+
return fs12.realpathSync(datasetDir);
|
|
6749
7484
|
} catch {
|
|
6750
7485
|
return datasetDir;
|
|
6751
7486
|
}
|
|
@@ -6798,23 +7533,23 @@ async function writeBenchReproManifestForPackageRun(args) {
|
|
|
6798
7533
|
}
|
|
6799
7534
|
}
|
|
6800
7535
|
function resolveConfigPath(cliPath) {
|
|
6801
|
-
if (cliPath) return
|
|
7536
|
+
if (cliPath) return path15.resolve(expandTilde(cliPath));
|
|
6802
7537
|
const envPath = readCompatEnv("REMNIC_CONFIG_PATH", "ENGRAM_CONFIG_PATH");
|
|
6803
|
-
if (envPath) return
|
|
7538
|
+
if (envPath) return path15.resolve(expandTilde(envPath));
|
|
6804
7539
|
const candidates = [
|
|
6805
|
-
|
|
6806
|
-
|
|
6807
|
-
|
|
6808
|
-
|
|
7540
|
+
path15.join(process.cwd(), "remnic.config.json"),
|
|
7541
|
+
path15.join(process.cwd(), "engram.config.json"),
|
|
7542
|
+
path15.join(resolveHomeDir(), ".config", "remnic", "config.json"),
|
|
7543
|
+
path15.join(resolveHomeDir(), ".config", "engram", "config.json")
|
|
6809
7544
|
];
|
|
6810
7545
|
for (const candidate of candidates) {
|
|
6811
|
-
if (
|
|
7546
|
+
if (fs12.existsSync(candidate)) return candidate;
|
|
6812
7547
|
}
|
|
6813
|
-
return
|
|
7548
|
+
return path15.join(resolveHomeDir(), ".config", "remnic", "config.json");
|
|
6814
7549
|
}
|
|
6815
7550
|
function resolveExistingBenchRemnicConfigPath(cliPath) {
|
|
6816
7551
|
const configPath = resolveConfigPath(cliPath);
|
|
6817
|
-
if (
|
|
7552
|
+
if (fs12.existsSync(configPath)) {
|
|
6818
7553
|
return configPath;
|
|
6819
7554
|
}
|
|
6820
7555
|
if (cliPath) {
|
|
@@ -6824,7 +7559,7 @@ function resolveExistingBenchRemnicConfigPath(cliPath) {
|
|
|
6824
7559
|
}
|
|
6825
7560
|
function resolveExistingBenchOpenclawConfigPath(cliPath) {
|
|
6826
7561
|
const configPath = resolveOpenclawConfigPath(cliPath);
|
|
6827
|
-
if (
|
|
7562
|
+
if (fs12.existsSync(configPath)) {
|
|
6828
7563
|
return configPath;
|
|
6829
7564
|
}
|
|
6830
7565
|
if (cliPath) {
|
|
@@ -6924,34 +7659,34 @@ async function resolvePackageBenchRuntime(benchModule, parsed, runtimeProfile) {
|
|
|
6924
7659
|
);
|
|
6925
7660
|
}
|
|
6926
7661
|
function normalizeMemoryDirPath(memoryDir) {
|
|
6927
|
-
return
|
|
7662
|
+
return path15.resolve(expandTilde(memoryDir));
|
|
6928
7663
|
}
|
|
6929
7664
|
function resolveMemoryDir() {
|
|
6930
7665
|
const configMemoryDir = (() => {
|
|
6931
7666
|
const envMemoryDir = readCompatEnv("REMNIC_MEMORY_DIR", "ENGRAM_MEMORY_DIR");
|
|
6932
7667
|
if (envMemoryDir) return normalizeMemoryDirPath(envMemoryDir);
|
|
6933
7668
|
const configPath = resolveConfigPath();
|
|
6934
|
-
const raw =
|
|
7669
|
+
const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
|
|
6935
7670
|
const remnicCfg = resolveRemnicConfigRecord4(raw);
|
|
6936
7671
|
if (typeof remnicCfg.memoryDir === "string" && remnicCfg.memoryDir.length > 0) {
|
|
6937
7672
|
return normalizeMemoryDirPath(remnicCfg.memoryDir);
|
|
6938
7673
|
}
|
|
6939
7674
|
const home = resolveHomeDir();
|
|
6940
|
-
const standalonePath =
|
|
6941
|
-
const legacyStandalonePath =
|
|
6942
|
-
const openclawPath =
|
|
6943
|
-
if (
|
|
6944
|
-
if (
|
|
7675
|
+
const standalonePath = path15.join(home, ".remnic", "memory");
|
|
7676
|
+
const legacyStandalonePath = path15.join(home, ".engram", "memory");
|
|
7677
|
+
const openclawPath = path15.join(home, ".openclaw", "workspace", "memory", "local");
|
|
7678
|
+
if (fs12.existsSync(standalonePath)) return standalonePath;
|
|
7679
|
+
if (fs12.existsSync(legacyStandalonePath)) return legacyStandalonePath;
|
|
6945
7680
|
return openclawPath;
|
|
6946
7681
|
})();
|
|
6947
7682
|
const manifestPath = getManifestPath();
|
|
6948
|
-
if (
|
|
7683
|
+
if (fs12.existsSync(manifestPath)) {
|
|
6949
7684
|
try {
|
|
6950
7685
|
const active = getActiveSpace();
|
|
6951
7686
|
if (active?.memoryDir) {
|
|
6952
7687
|
const activeMemoryDir = normalizeMemoryDirPath(active.memoryDir);
|
|
6953
|
-
if (!
|
|
6954
|
-
|
|
7688
|
+
if (!fs12.existsSync(activeMemoryDir)) {
|
|
7689
|
+
fs12.mkdirSync(activeMemoryDir, { recursive: true });
|
|
6955
7690
|
}
|
|
6956
7691
|
return activeMemoryDir;
|
|
6957
7692
|
}
|
|
@@ -6990,20 +7725,20 @@ var REMNIC_OPENCLAW_LEGACY_PLUGIN_ID = "openclaw-engram";
|
|
|
6990
7725
|
var DEFAULT_OPENCLAW_CONFIG_PATHS_FOR_DOCTOR = [
|
|
6991
7726
|
process.env.OPENCLAW_CONFIG_PATH,
|
|
6992
7727
|
process.env.OPENCLAW_ENGRAM_CONFIG_PATH,
|
|
6993
|
-
|
|
7728
|
+
path15.join(resolveHomeDir(), ".openclaw", "openclaw.json")
|
|
6994
7729
|
].filter(Boolean);
|
|
6995
7730
|
function resolveOpenclawConfigPath(cliPath) {
|
|
6996
|
-
if (cliPath) return
|
|
7731
|
+
if (cliPath) return path15.resolve(expandTilde(cliPath));
|
|
6997
7732
|
const envPath = process.env.OPENCLAW_CONFIG_PATH || process.env.OPENCLAW_ENGRAM_CONFIG_PATH;
|
|
6998
|
-
if (envPath) return
|
|
7733
|
+
if (envPath) return path15.resolve(expandTilde(envPath));
|
|
6999
7734
|
for (const candidate of DEFAULT_OPENCLAW_CONFIG_PATHS_FOR_DOCTOR) {
|
|
7000
|
-
if (
|
|
7735
|
+
if (fs12.existsSync(candidate)) return candidate;
|
|
7001
7736
|
}
|
|
7002
|
-
return
|
|
7737
|
+
return path15.join(resolveHomeDir(), ".openclaw", "openclaw.json");
|
|
7003
7738
|
}
|
|
7004
7739
|
function readOpenclawConfig(configPath) {
|
|
7005
|
-
if (!
|
|
7006
|
-
const raw =
|
|
7740
|
+
if (!fs12.existsSync(configPath)) return {};
|
|
7741
|
+
const raw = fs12.readFileSync(configPath, "utf-8");
|
|
7007
7742
|
let parsed;
|
|
7008
7743
|
try {
|
|
7009
7744
|
parsed = JSON.parse(raw);
|
|
@@ -7058,10 +7793,10 @@ function buildRemnicOpenclawHooksPolicy(legacyHooks, existingHooks) {
|
|
|
7058
7793
|
function resolveOpenclawInstallMemoryDir(args) {
|
|
7059
7794
|
const existingMemoryDir = (typeof args.existingNewEntryConfig.memoryDir === "string" ? args.existingNewEntryConfig.memoryDir : void 0) || (args.migrateLegacy && typeof args.legacyConfigToMerge.memoryDir === "string" ? args.legacyConfigToMerge.memoryDir : void 0);
|
|
7060
7795
|
if (args.requestedMemoryDir) {
|
|
7061
|
-
return
|
|
7796
|
+
return path15.resolve(expandTilde(args.requestedMemoryDir));
|
|
7062
7797
|
}
|
|
7063
7798
|
if (existingMemoryDir) {
|
|
7064
|
-
return
|
|
7799
|
+
return path15.resolve(expandTilde(existingMemoryDir));
|
|
7065
7800
|
}
|
|
7066
7801
|
return args.fallbackMemoryDir;
|
|
7067
7802
|
}
|
|
@@ -7079,18 +7814,18 @@ function resolveCurrentOpenclawMemoryDir(entries, slots, fallbackMemoryDir) {
|
|
|
7079
7814
|
if (!config || typeof config !== "object" || Array.isArray(config)) continue;
|
|
7080
7815
|
const memoryDir = config.memoryDir;
|
|
7081
7816
|
if (typeof memoryDir === "string" && memoryDir.trim().length > 0) {
|
|
7082
|
-
return
|
|
7817
|
+
return path15.resolve(expandTilde(memoryDir));
|
|
7083
7818
|
}
|
|
7084
7819
|
}
|
|
7085
7820
|
return fallbackMemoryDir;
|
|
7086
7821
|
}
|
|
7087
7822
|
function resolveOpenclawPluginDir(cliPath) {
|
|
7088
|
-
if (cliPath) return
|
|
7089
|
-
return
|
|
7823
|
+
if (cliPath) return path15.resolve(expandTilde(cliPath));
|
|
7824
|
+
return path15.join(resolveHomeDir(), ".openclaw", "extensions", REMNIC_OPENCLAW_PLUGIN_ID);
|
|
7090
7825
|
}
|
|
7091
7826
|
function resolveOpenclawLegacyPluginDir(cliPath) {
|
|
7092
|
-
if (cliPath) return
|
|
7093
|
-
return
|
|
7827
|
+
if (cliPath) return path15.resolve(expandTilde(cliPath));
|
|
7828
|
+
return path15.join(resolveHomeDir(), ".openclaw", "extensions", REMNIC_OPENCLAW_LEGACY_PLUGIN_ID);
|
|
7094
7829
|
}
|
|
7095
7830
|
function formatOpenclawUpgradeStamp(now = /* @__PURE__ */ new Date()) {
|
|
7096
7831
|
const yyyy = now.getFullYear().toString();
|
|
@@ -7102,14 +7837,14 @@ function formatOpenclawUpgradeStamp(now = /* @__PURE__ */ new Date()) {
|
|
|
7102
7837
|
return `${yyyy}${mm}${dd}-${hh}${min}${ss}`;
|
|
7103
7838
|
}
|
|
7104
7839
|
function backupPathIfPresent(sourcePath, backupPath) {
|
|
7105
|
-
if (!
|
|
7106
|
-
|
|
7107
|
-
|
|
7840
|
+
if (!fs12.existsSync(sourcePath)) return false;
|
|
7841
|
+
fs12.mkdirSync(path15.dirname(backupPath), { recursive: true });
|
|
7842
|
+
fs12.cpSync(sourcePath, backupPath, { recursive: true });
|
|
7108
7843
|
return true;
|
|
7109
7844
|
}
|
|
7110
7845
|
function assertDirectoryPathOrMissing(targetPath, label) {
|
|
7111
|
-
if (!
|
|
7112
|
-
const stat =
|
|
7846
|
+
if (!fs12.existsSync(targetPath)) return;
|
|
7847
|
+
const stat = fs12.statSync(targetPath);
|
|
7113
7848
|
if (!stat.isDirectory()) {
|
|
7114
7849
|
throw new Error(`${label} must be a directory when it already exists: ${targetPath}`);
|
|
7115
7850
|
}
|
|
@@ -7134,7 +7869,7 @@ var PublishedOpenclawPluginInstallError = class extends Error {
|
|
|
7134
7869
|
}
|
|
7135
7870
|
};
|
|
7136
7871
|
function installPublishedOpenclawPlugin(spec, pluginDir) {
|
|
7137
|
-
const tempRoot =
|
|
7872
|
+
const tempRoot = fs12.mkdtempSync(path15.join(os.tmpdir(), "remnic-openclaw-upgrade-"));
|
|
7138
7873
|
const stagedDir = `${pluginDir}.next-${process.pid}-${Date.now()}`;
|
|
7139
7874
|
const rollbackDir = `${pluginDir}.rollback-${process.pid}-${Date.now()}`;
|
|
7140
7875
|
let swapRollbackDir;
|
|
@@ -7149,17 +7884,17 @@ function installPublishedOpenclawPlugin(spec, pluginDir) {
|
|
|
7149
7884
|
if (!tarballName) {
|
|
7150
7885
|
throw new Error(`npm pack ${spec} did not return a tarball name`);
|
|
7151
7886
|
}
|
|
7152
|
-
const unpackDir =
|
|
7153
|
-
|
|
7154
|
-
childProcess2.execFileSync("tar", ["-xzf",
|
|
7887
|
+
const unpackDir = path15.join(tempRoot, "unpacked");
|
|
7888
|
+
fs12.mkdirSync(unpackDir, { recursive: true });
|
|
7889
|
+
childProcess2.execFileSync("tar", ["-xzf", path15.join(tempRoot, tarballName), "-C", unpackDir], {
|
|
7155
7890
|
stdio: ["ignore", "pipe", "pipe"]
|
|
7156
7891
|
});
|
|
7157
|
-
const packagedDir =
|
|
7158
|
-
if (!
|
|
7892
|
+
const packagedDir = path15.join(unpackDir, "package");
|
|
7893
|
+
if (!fs12.existsSync(packagedDir)) {
|
|
7159
7894
|
throw new Error(`npm pack ${spec} did not contain a package/ directory`);
|
|
7160
7895
|
}
|
|
7161
|
-
|
|
7162
|
-
|
|
7896
|
+
fs12.rmSync(stagedDir, { recursive: true, force: true });
|
|
7897
|
+
fs12.cpSync(packagedDir, stagedDir, { recursive: true });
|
|
7163
7898
|
childProcess2.execFileSync("npm", ["install", "--omit=dev"], {
|
|
7164
7899
|
cwd: stagedDir,
|
|
7165
7900
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -7174,8 +7909,8 @@ function installPublishedOpenclawPlugin(spec, pluginDir) {
|
|
|
7174
7909
|
}
|
|
7175
7910
|
})();
|
|
7176
7911
|
swapRollbackDir = swapResult.rollbackDir;
|
|
7177
|
-
const installedPackageJsonPath =
|
|
7178
|
-
const installedPackage =
|
|
7912
|
+
const installedPackageJsonPath = path15.join(pluginDir, "package.json");
|
|
7913
|
+
const installedPackage = fs12.existsSync(installedPackageJsonPath) ? JSON.parse(fs12.readFileSync(installedPackageJsonPath, "utf8")) : {};
|
|
7179
7914
|
return {
|
|
7180
7915
|
rollbackDir: swapRollbackDir,
|
|
7181
7916
|
version: typeof installedPackage.version === "string" ? installedPackage.version : void 0
|
|
@@ -7190,8 +7925,8 @@ function installPublishedOpenclawPlugin(spec, pluginDir) {
|
|
|
7190
7925
|
}
|
|
7191
7926
|
);
|
|
7192
7927
|
} finally {
|
|
7193
|
-
|
|
7194
|
-
|
|
7928
|
+
fs12.rmSync(stagedDir, { recursive: true, force: true });
|
|
7929
|
+
fs12.rmSync(tempRoot, { recursive: true, force: true });
|
|
7195
7930
|
}
|
|
7196
7931
|
}
|
|
7197
7932
|
function restartOpenclawGateway() {
|
|
@@ -7209,15 +7944,15 @@ function restartOpenclawGateway() {
|
|
|
7209
7944
|
});
|
|
7210
7945
|
}
|
|
7211
7946
|
function cmdInit() {
|
|
7212
|
-
const configPath =
|
|
7213
|
-
if (
|
|
7947
|
+
const configPath = path15.join(process.cwd(), "remnic.config.json");
|
|
7948
|
+
if (fs12.existsSync(configPath)) {
|
|
7214
7949
|
console.log(`Config already exists: ${configPath}`);
|
|
7215
7950
|
return;
|
|
7216
7951
|
}
|
|
7217
7952
|
const template = {
|
|
7218
7953
|
remnic: {
|
|
7219
7954
|
openaiApiKey: "${OPENAI_API_KEY}",
|
|
7220
|
-
memoryDir:
|
|
7955
|
+
memoryDir: path15.join(process.cwd(), ".remnic", "memory"),
|
|
7221
7956
|
memoryOsPreset: "balanced"
|
|
7222
7957
|
},
|
|
7223
7958
|
server: {
|
|
@@ -7226,7 +7961,7 @@ function cmdInit() {
|
|
|
7226
7961
|
authToken: "${REMNIC_AUTH_TOKEN}"
|
|
7227
7962
|
}
|
|
7228
7963
|
};
|
|
7229
|
-
|
|
7964
|
+
fs12.writeFileSync(configPath, JSON.stringify(template, null, 2) + "\n");
|
|
7230
7965
|
console.log(`Created ${configPath}`);
|
|
7231
7966
|
console.log("\nSet these environment variables:");
|
|
7232
7967
|
console.log(" export OPENAI_API_KEY=sk-...");
|
|
@@ -7296,7 +8031,7 @@ async function cmdStatus(json) {
|
|
|
7296
8031
|
}
|
|
7297
8032
|
function oauthReadConfigRecord(configPath) {
|
|
7298
8033
|
try {
|
|
7299
|
-
const parsed = JSON.parse(
|
|
8034
|
+
const parsed = JSON.parse(fs12.readFileSync(configPath, "utf8"));
|
|
7300
8035
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
7301
8036
|
return parsed;
|
|
7302
8037
|
}
|
|
@@ -7354,7 +8089,7 @@ function oauthResolveOperatorToken() {
|
|
|
7354
8089
|
}
|
|
7355
8090
|
return void 0;
|
|
7356
8091
|
}
|
|
7357
|
-
async function oauthFetch(method,
|
|
8092
|
+
async function oauthFetch(method, path16, token, body) {
|
|
7358
8093
|
const controller = new AbortController();
|
|
7359
8094
|
const timeoutId = setTimeout(() => controller.abort(), 5e3);
|
|
7360
8095
|
try {
|
|
@@ -7373,7 +8108,7 @@ async function oauthFetch(method, path15, token, body) {
|
|
|
7373
8108
|
if (body !== void 0) {
|
|
7374
8109
|
init.body = JSON.stringify(body);
|
|
7375
8110
|
}
|
|
7376
|
-
const response = await fetch(`${oauthResolveBaseUrl()}${
|
|
8111
|
+
const response = await fetch(`${oauthResolveBaseUrl()}${path16}`, init);
|
|
7377
8112
|
if (response.status === 401) {
|
|
7378
8113
|
throw new Error(
|
|
7379
8114
|
"operator token rejected by remnic-server (HTTP 401). Update `server.authToken` or `REMNIC_AUTH_TOKEN` to match the running daemon."
|
|
@@ -7712,9 +8447,9 @@ async function cmdQuery(queryText, json, explain) {
|
|
|
7712
8447
|
}
|
|
7713
8448
|
initLogger2();
|
|
7714
8449
|
const configPath = resolveConfigPath();
|
|
7715
|
-
const raw =
|
|
8450
|
+
const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
|
|
7716
8451
|
const remnicCfg = resolveRemnicConfigRecord4(raw);
|
|
7717
|
-
const config =
|
|
8452
|
+
const config = parseConfig5(remnicCfg);
|
|
7718
8453
|
const orchestrator = new Orchestrator3(config);
|
|
7719
8454
|
await orchestrator.initialize();
|
|
7720
8455
|
const service = new EngramAccessService2(orchestrator);
|
|
@@ -7883,9 +8618,9 @@ async function cmdXray(rest) {
|
|
|
7883
8618
|
parseXrayCliOptions(rawQuery, options);
|
|
7884
8619
|
initLogger2();
|
|
7885
8620
|
const configPath = resolveConfigPath();
|
|
7886
|
-
const raw =
|
|
8621
|
+
const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
|
|
7887
8622
|
const remnicCfg = resolveRemnicConfigRecord4(raw);
|
|
7888
|
-
const config =
|
|
8623
|
+
const config = parseConfig5(remnicCfg);
|
|
7889
8624
|
const orchestrator = new Orchestrator3(config);
|
|
7890
8625
|
await orchestrator.initialize();
|
|
7891
8626
|
await orchestrator.deferredReady;
|
|
@@ -7906,9 +8641,9 @@ async function cmdXray(rest) {
|
|
|
7906
8641
|
async function cmdVersions(rest) {
|
|
7907
8642
|
initLogger2();
|
|
7908
8643
|
const configPath = resolveConfigPath();
|
|
7909
|
-
const raw =
|
|
8644
|
+
const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
|
|
7910
8645
|
const remnicCfg = resolveRemnicConfigRecord4(raw);
|
|
7911
|
-
const config =
|
|
8646
|
+
const config = parseConfig5(remnicCfg);
|
|
7912
8647
|
if (!config.versioningEnabled) {
|
|
7913
8648
|
console.error("Page versioning is disabled (versioningEnabled = false).");
|
|
7914
8649
|
process.exit(1);
|
|
@@ -7928,7 +8663,7 @@ async function cmdVersions(rest) {
|
|
|
7928
8663
|
console.error("Usage: remnic versions list <page-path>");
|
|
7929
8664
|
process.exit(1);
|
|
7930
8665
|
}
|
|
7931
|
-
const absPath =
|
|
8666
|
+
const absPath = path15.resolve(pagePath);
|
|
7932
8667
|
const history = await listVersions(absPath, versioningConfig, memDir);
|
|
7933
8668
|
if (json) {
|
|
7934
8669
|
console.log(JSON.stringify(history, null, 2));
|
|
@@ -7953,7 +8688,7 @@ async function cmdVersions(rest) {
|
|
|
7953
8688
|
console.error("Usage: remnic versions show <page-path> <version-id>");
|
|
7954
8689
|
process.exit(1);
|
|
7955
8690
|
}
|
|
7956
|
-
const absPath =
|
|
8691
|
+
const absPath = path15.resolve(pagePath);
|
|
7957
8692
|
try {
|
|
7958
8693
|
const content = await getVersion(absPath, versionId, versioningConfig, memDir);
|
|
7959
8694
|
console.log(content);
|
|
@@ -7971,7 +8706,7 @@ async function cmdVersions(rest) {
|
|
|
7971
8706
|
console.error("Usage: remnic versions diff <page-path> <v1> <v2>");
|
|
7972
8707
|
process.exit(1);
|
|
7973
8708
|
}
|
|
7974
|
-
const absPath =
|
|
8709
|
+
const absPath = path15.resolve(pagePath);
|
|
7975
8710
|
try {
|
|
7976
8711
|
const diffOutput = await diffVersions(absPath, v1, v2, versioningConfig, memDir);
|
|
7977
8712
|
console.log(diffOutput);
|
|
@@ -7988,7 +8723,7 @@ async function cmdVersions(rest) {
|
|
|
7988
8723
|
console.error("Usage: remnic versions revert <page-path> <version-id>");
|
|
7989
8724
|
process.exit(1);
|
|
7990
8725
|
}
|
|
7991
|
-
const absPath =
|
|
8726
|
+
const absPath = path15.resolve(pagePath);
|
|
7992
8727
|
try {
|
|
7993
8728
|
const version = await revertToVersion(absPath, versionId, versioningConfig, void 0, memDir);
|
|
7994
8729
|
if (json) {
|
|
@@ -8022,13 +8757,13 @@ Options:
|
|
|
8022
8757
|
async function cmdEnrich(rest) {
|
|
8023
8758
|
initLogger2();
|
|
8024
8759
|
const configPath = resolveConfigPath();
|
|
8025
|
-
const raw =
|
|
8760
|
+
const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
|
|
8026
8761
|
const remnicCfg = resolveRemnicConfigRecord4(raw);
|
|
8027
|
-
const config =
|
|
8762
|
+
const config = parseConfig5(remnicCfg);
|
|
8028
8763
|
const subcommand = rest[0];
|
|
8029
8764
|
if (subcommand === "audit") {
|
|
8030
8765
|
const memoryDir2 = expandTilde(config.memoryDir);
|
|
8031
|
-
const auditDir2 =
|
|
8766
|
+
const auditDir2 = path15.join(memoryDir2, "enrichment");
|
|
8032
8767
|
const sinceFlag = resolveFlag(rest.slice(1), "--since");
|
|
8033
8768
|
const entries = await readAuditLog(auditDir2, sinceFlag ?? void 0);
|
|
8034
8769
|
if (entries.length === 0) {
|
|
@@ -8153,7 +8888,7 @@ Registered providers:`);
|
|
|
8153
8888
|
return;
|
|
8154
8889
|
}
|
|
8155
8890
|
const memoryDir = expandTilde(config.memoryDir);
|
|
8156
|
-
const auditDir =
|
|
8891
|
+
const auditDir = path15.join(memoryDir, "enrichment");
|
|
8157
8892
|
let totalPersisted = 0;
|
|
8158
8893
|
for (const result of results) {
|
|
8159
8894
|
for (const candidate of result.acceptedCandidates) {
|
|
@@ -8267,9 +9002,9 @@ Shared with:
|
|
|
8267
9002
|
process.exit(1);
|
|
8268
9003
|
}
|
|
8269
9004
|
const configPath = resolveConfigPath();
|
|
8270
|
-
const raw =
|
|
9005
|
+
const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
|
|
8271
9006
|
const remnicCfg = resolveRemnicConfigRecord4(raw);
|
|
8272
|
-
const config =
|
|
9007
|
+
const config = parseConfig5(remnicCfg);
|
|
8273
9008
|
const memoryDir = expandTilde(
|
|
8274
9009
|
typeof memoryDirOverride === "string" && memoryDirOverride.length > 0 ? memoryDirOverride : config.memoryDir ?? resolveMemoryDir()
|
|
8275
9010
|
);
|
|
@@ -8284,9 +9019,9 @@ Shared with:
|
|
|
8284
9019
|
async function cmdExtensions(action, rest) {
|
|
8285
9020
|
initLogger2();
|
|
8286
9021
|
const configPath = resolveConfigPath();
|
|
8287
|
-
const raw =
|
|
9022
|
+
const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
|
|
8288
9023
|
const remnicCfg = resolveRemnicConfigRecord4(raw);
|
|
8289
|
-
const config =
|
|
9024
|
+
const config = parseConfig5(remnicCfg);
|
|
8290
9025
|
const root = resolveExtensionsRoot(config);
|
|
8291
9026
|
const noopLog = { warn: () => {
|
|
8292
9027
|
}, debug: () => {
|
|
@@ -8335,7 +9070,7 @@ Root: ${root}`);
|
|
|
8335
9070
|
const extensions = await discoverMemoryExtensions(root, warnLog);
|
|
8336
9071
|
let entries = [];
|
|
8337
9072
|
try {
|
|
8338
|
-
entries =
|
|
9073
|
+
entries = fs12.readdirSync(root);
|
|
8339
9074
|
} catch {
|
|
8340
9075
|
console.log(`Extensions root does not exist: ${root}`);
|
|
8341
9076
|
process.exitCode = 0;
|
|
@@ -8344,9 +9079,9 @@ Root: ${root}`);
|
|
|
8344
9079
|
const validNames = new Set(extensions.map((e) => e.name));
|
|
8345
9080
|
let errors = 0;
|
|
8346
9081
|
for (const entry of entries) {
|
|
8347
|
-
const entryPath =
|
|
9082
|
+
const entryPath = path15.join(root, entry);
|
|
8348
9083
|
try {
|
|
8349
|
-
if (!
|
|
9084
|
+
if (!fs12.statSync(entryPath).isDirectory()) continue;
|
|
8350
9085
|
} catch {
|
|
8351
9086
|
continue;
|
|
8352
9087
|
}
|
|
@@ -8378,9 +9113,9 @@ Root: ${root}`);
|
|
|
8378
9113
|
async function cmdBriefing(rest) {
|
|
8379
9114
|
initLogger2();
|
|
8380
9115
|
const configPath = resolveConfigPath();
|
|
8381
|
-
const raw =
|
|
9116
|
+
const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
|
|
8382
9117
|
const remnicCfg = resolveRemnicConfigRecord4(raw);
|
|
8383
|
-
const config =
|
|
9118
|
+
const config = parseConfig5(remnicCfg);
|
|
8384
9119
|
if (!config.briefing.enabled) {
|
|
8385
9120
|
console.error("Briefing is disabled in config (briefing.enabled = false).");
|
|
8386
9121
|
process.exit(1);
|
|
@@ -8458,10 +9193,10 @@ async function cmdBriefing(rest) {
|
|
|
8458
9193
|
if (save) {
|
|
8459
9194
|
try {
|
|
8460
9195
|
const saveDir = resolveBriefingSaveDir(config.briefing.saveDir);
|
|
8461
|
-
|
|
9196
|
+
fs12.mkdirSync(saveDir, { recursive: true });
|
|
8462
9197
|
const filename = briefingFilename(new Date(result.window.to), format);
|
|
8463
|
-
const filePath =
|
|
8464
|
-
|
|
9198
|
+
const filePath = path15.join(saveDir, filename);
|
|
9199
|
+
fs12.writeFileSync(filePath, payload + (payload.endsWith("\n") ? "" : "\n"));
|
|
8465
9200
|
console.error(`Saved briefing: ${filePath}`);
|
|
8466
9201
|
} catch (err) {
|
|
8467
9202
|
console.error(`Failed to save briefing: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -8479,7 +9214,7 @@ async function cmdDoctor() {
|
|
|
8479
9214
|
detail: `${nodeVersion} (requires >= 22.12.0)`
|
|
8480
9215
|
});
|
|
8481
9216
|
const configPath = resolveConfigPath();
|
|
8482
|
-
const configExists =
|
|
9217
|
+
const configExists = fs12.existsSync(configPath);
|
|
8483
9218
|
checks.push({ name: "Config file", ok: configExists, detail: configPath });
|
|
8484
9219
|
let standaloneConfig;
|
|
8485
9220
|
let standaloneConfigError;
|
|
@@ -8487,11 +9222,11 @@ async function cmdDoctor() {
|
|
|
8487
9222
|
let configuredNs = { invalid: false };
|
|
8488
9223
|
if (configExists) {
|
|
8489
9224
|
try {
|
|
8490
|
-
const raw = JSON.parse(
|
|
9225
|
+
const raw = JSON.parse(fs12.readFileSync(configPath, "utf8"));
|
|
8491
9226
|
const remnicCfg = resolveRemnicConfigRecord4(raw);
|
|
8492
9227
|
standaloneOpenaiApiKeyExplicitlyFalse = isOpenaiApiKeyDisabled(remnicCfg.openaiApiKey);
|
|
8493
9228
|
configuredNs = readConfiguredNamespace(remnicCfg);
|
|
8494
|
-
standaloneConfig =
|
|
9229
|
+
standaloneConfig = parseConfig5(remnicCfg);
|
|
8495
9230
|
} catch (err) {
|
|
8496
9231
|
standaloneConfigError = err instanceof Error ? err.message : String(err);
|
|
8497
9232
|
}
|
|
@@ -8500,10 +9235,10 @@ async function cmdDoctor() {
|
|
|
8500
9235
|
try {
|
|
8501
9236
|
memoryDir = resolveMemoryDir();
|
|
8502
9237
|
} catch {
|
|
8503
|
-
memoryDir =
|
|
9238
|
+
memoryDir = parseConfig5({}).memoryDir;
|
|
8504
9239
|
}
|
|
8505
9240
|
try {
|
|
8506
|
-
|
|
9241
|
+
fs12.mkdirSync(memoryDir, { recursive: true });
|
|
8507
9242
|
checks.push({ name: "Memory directory", ok: true, detail: memoryDir });
|
|
8508
9243
|
} catch {
|
|
8509
9244
|
checks.push({ name: "Memory directory", ok: false, detail: `cannot create ${memoryDir}` });
|
|
@@ -8532,7 +9267,7 @@ async function cmdDoctor() {
|
|
|
8532
9267
|
});
|
|
8533
9268
|
if (nsPolicyCheck) checks.push(nsPolicyCheck);
|
|
8534
9269
|
const openclawConfigPath = resolveOpenclawConfigPath();
|
|
8535
|
-
const openclawConfigExists =
|
|
9270
|
+
const openclawConfigExists = fs12.existsSync(openclawConfigPath);
|
|
8536
9271
|
let openclawConfig = {};
|
|
8537
9272
|
let openclawConfigValid = false;
|
|
8538
9273
|
let openclawPluginModeConfigured = false;
|
|
@@ -8540,7 +9275,7 @@ async function cmdDoctor() {
|
|
|
8540
9275
|
let activeOpenclawEntryConfig = null;
|
|
8541
9276
|
if (openclawConfigExists) {
|
|
8542
9277
|
try {
|
|
8543
|
-
const parsed = JSON.parse(
|
|
9278
|
+
const parsed = JSON.parse(fs12.readFileSync(openclawConfigPath, "utf-8"));
|
|
8544
9279
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
8545
9280
|
openclawConfig = parsed;
|
|
8546
9281
|
openclawConfigValid = true;
|
|
@@ -8616,13 +9351,13 @@ async function cmdDoctor() {
|
|
|
8616
9351
|
const rawMemoryDir = entryConfig?.memoryDir;
|
|
8617
9352
|
const configuredMemoryDir = typeof rawMemoryDir === "string" ? rawMemoryDir : void 0;
|
|
8618
9353
|
if (configuredMemoryDir) {
|
|
8619
|
-
const resolvedMemDir =
|
|
9354
|
+
const resolvedMemDir = path15.resolve(expandTilde(configuredMemoryDir));
|
|
8620
9355
|
let memDirOk = false;
|
|
8621
9356
|
let memDirDetail = `${resolvedMemDir} (not found)`;
|
|
8622
9357
|
let memDirRemediation = `Run \`remnic openclaw install --memory-dir "${resolvedMemDir}"\` to create the directory.`;
|
|
8623
|
-
if (
|
|
9358
|
+
if (fs12.existsSync(resolvedMemDir)) {
|
|
8624
9359
|
try {
|
|
8625
|
-
const stat =
|
|
9360
|
+
const stat = fs12.statSync(resolvedMemDir);
|
|
8626
9361
|
if (stat.isDirectory()) {
|
|
8627
9362
|
memDirOk = true;
|
|
8628
9363
|
memDirDetail = resolvedMemDir;
|
|
@@ -8764,12 +9499,12 @@ async function cmdDoctor() {
|
|
|
8764
9499
|
}
|
|
8765
9500
|
function cmdConfig() {
|
|
8766
9501
|
const configPath = resolveConfigPath();
|
|
8767
|
-
if (!
|
|
9502
|
+
if (!fs12.existsSync(configPath)) {
|
|
8768
9503
|
console.log("No config file found. Run `remnic init` to create one.");
|
|
8769
9504
|
return;
|
|
8770
9505
|
}
|
|
8771
9506
|
console.log(`Config: ${configPath}`);
|
|
8772
|
-
const rawConfig =
|
|
9507
|
+
const rawConfig = fs12.readFileSync(configPath, "utf8");
|
|
8773
9508
|
const redacted = rawConfig.replace(
|
|
8774
9509
|
/("(?:openaiApiKey|localLlmApiKey|authToken|apiKey|remoteSearchApiKey|meilisearchApiKey|opikApiKey)"\s*:\s*")([^"]*)(")/g,
|
|
8775
9510
|
"$1[REDACTED]$3"
|
|
@@ -8816,7 +9551,7 @@ async function cmdMigrate(json, rollback) {
|
|
|
8816
9551
|
console.log(` Rollback: ${result.rollbackCommand}`);
|
|
8817
9552
|
}
|
|
8818
9553
|
function cmdOnboard(dirPath, json) {
|
|
8819
|
-
const directory =
|
|
9554
|
+
const directory = path15.resolve(dirPath || process.cwd());
|
|
8820
9555
|
const result = onboard({ directory });
|
|
8821
9556
|
if (json) {
|
|
8822
9557
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -8835,7 +9570,7 @@ Suggested namespace: ${result.plan.suggestedNamespace}`);
|
|
|
8835
9570
|
async function cmdCurate(targetPath, json) {
|
|
8836
9571
|
const memoryDir = resolveMemoryDir();
|
|
8837
9572
|
const result = await curate({
|
|
8838
|
-
targetPath:
|
|
9573
|
+
targetPath: path15.resolve(targetPath),
|
|
8839
9574
|
memoryDir,
|
|
8840
9575
|
source: "curation",
|
|
8841
9576
|
checkDuplicates: true,
|
|
@@ -8877,9 +9612,9 @@ async function cmdReview(action, rest) {
|
|
|
8877
9612
|
const configPath = resolveConfigPath();
|
|
8878
9613
|
let tombstonesConfig = null;
|
|
8879
9614
|
try {
|
|
8880
|
-
const rawCfg =
|
|
9615
|
+
const rawCfg = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
|
|
8881
9616
|
const remnicCfg = resolveRemnicConfigRecord4(rawCfg);
|
|
8882
|
-
const config =
|
|
9617
|
+
const config = parseConfig5(remnicCfg);
|
|
8883
9618
|
tombstonesConfig = {
|
|
8884
9619
|
enabled: config.tombstonesEnabled,
|
|
8885
9620
|
semanticMatch: config.tombstonesSemanticMatch,
|
|
@@ -8965,7 +9700,7 @@ async function cmdSync(action, rest, json) {
|
|
|
8965
9700
|
}
|
|
8966
9701
|
function localOfflineSourceId(memoryDir) {
|
|
8967
9702
|
const host = os.hostname() || "unknown-host";
|
|
8968
|
-
const dirHash =
|
|
9703
|
+
const dirHash = createHash3("sha256").update(path15.resolve(memoryDir)).digest("hex").slice(0, 16);
|
|
8969
9704
|
return `remnic-local:${host}:${dirHash}`;
|
|
8970
9705
|
}
|
|
8971
9706
|
function normalizeOfflineRemoteUrl(raw) {
|
|
@@ -9363,10 +10098,10 @@ var OFFLINE_SYNC_CONTENT_MISSING_RETRY_MAX = 3;
|
|
|
9363
10098
|
var OFFLINE_SYNC_CONTENT_MISSING_RETRY_DELAY_MS = 250;
|
|
9364
10099
|
var OfflineRemoteFileChangedError = class extends Error {
|
|
9365
10100
|
path;
|
|
9366
|
-
constructor(
|
|
9367
|
-
super(`remote file changed while fetching offline content: ${
|
|
10101
|
+
constructor(path16) {
|
|
10102
|
+
super(`remote file changed while fetching offline content: ${path16}`);
|
|
9368
10103
|
this.name = "OfflineRemoteFileChangedError";
|
|
9369
|
-
this.path =
|
|
10104
|
+
this.path = path16;
|
|
9370
10105
|
}
|
|
9371
10106
|
};
|
|
9372
10107
|
function isOfflineRemoteFileChangedError(error) {
|
|
@@ -9557,15 +10292,15 @@ function offlineDirectPushFiles(options) {
|
|
|
9557
10292
|
}).sort((left, right) => right.bytes - left.bytes || left.path.localeCompare(right.path));
|
|
9558
10293
|
}
|
|
9559
10294
|
function resolveOfflineDirectHydrationPath(memoryDir, relPath) {
|
|
9560
|
-
const base =
|
|
9561
|
-
const target =
|
|
9562
|
-
const relative =
|
|
9563
|
-
if (relative === "" || relative === ".." || relative.startsWith(`..${
|
|
10295
|
+
const base = path15.resolve(memoryDir);
|
|
10296
|
+
const target = path15.resolve(base, relPath);
|
|
10297
|
+
const relative = path15.relative(base, target);
|
|
10298
|
+
if (relative === "" || relative === ".." || relative.startsWith(`..${path15.sep}`) || path15.isAbsolute(relative)) {
|
|
9564
10299
|
throw new Error(`offline sync direct hydration path escapes memory dir: ${relPath}`);
|
|
9565
10300
|
}
|
|
9566
10301
|
return target;
|
|
9567
10302
|
}
|
|
9568
|
-
var OFFLINE_SYNC_FILE_CONTENT_UPLOAD_CHUNK_BYTES =
|
|
10303
|
+
var OFFLINE_SYNC_FILE_CONTENT_UPLOAD_CHUNK_BYTES = OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES3;
|
|
9569
10304
|
async function pushOfflineFileContent(args) {
|
|
9570
10305
|
if (args.readFileChunks) {
|
|
9571
10306
|
return pushOfflineFileContentFromChunkReader(args);
|
|
@@ -9573,7 +10308,7 @@ async function pushOfflineFileContent(args) {
|
|
|
9573
10308
|
let offset = 0;
|
|
9574
10309
|
let finalResult = null;
|
|
9575
10310
|
let remoteSatisfiedResult = null;
|
|
9576
|
-
const hash =
|
|
10311
|
+
const hash = createHash3("sha256");
|
|
9577
10312
|
let bytes = 0;
|
|
9578
10313
|
while (offset < args.file.bytes || args.file.bytes === 0 && offset === 0) {
|
|
9579
10314
|
const chunk = await readOfflineSyncFileContentChunk({
|
|
@@ -9630,13 +10365,13 @@ async function pushOfflineFileContent(args) {
|
|
|
9630
10365
|
}
|
|
9631
10366
|
async function pushOfflineFileContentFromChunkReader(args) {
|
|
9632
10367
|
const filePath = resolveOfflineDirectHydrationPath(args.memoryDir, args.file.path);
|
|
9633
|
-
const stat =
|
|
10368
|
+
const stat = fs12.statSync(filePath);
|
|
9634
10369
|
if (stat.mtimeMs !== args.file.mtimeMs) {
|
|
9635
10370
|
throw new Error(`local file changed while pushing offline content: ${args.file.path}`);
|
|
9636
10371
|
}
|
|
9637
|
-
const hash =
|
|
10372
|
+
const hash = createHash3("sha256");
|
|
9638
10373
|
const chunks = args.readFileChunks({
|
|
9639
|
-
root:
|
|
10374
|
+
root: path15.resolve(args.memoryDir),
|
|
9640
10375
|
path: args.file.path,
|
|
9641
10376
|
filePath,
|
|
9642
10377
|
chunkSize: OFFLINE_SYNC_FILE_CONTENT_UPLOAD_CHUNK_BYTES
|
|
@@ -9648,7 +10383,7 @@ async function pushOfflineFileContentFromChunkReader(args) {
|
|
|
9648
10383
|
for await (const rawChunk of chunks) {
|
|
9649
10384
|
const chunk = Buffer.from(rawChunk);
|
|
9650
10385
|
if (chunk.length === 0) continue;
|
|
9651
|
-
if (chunk.length >
|
|
10386
|
+
if (chunk.length > OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES2) {
|
|
9652
10387
|
throw new Error(`local offline content chunk exceeds max size: ${args.file.path}`);
|
|
9653
10388
|
}
|
|
9654
10389
|
if (pending) {
|
|
@@ -9733,7 +10468,7 @@ async function hydrateOfflineFileContent(args) {
|
|
|
9733
10468
|
path: args.expected.path,
|
|
9734
10469
|
offset,
|
|
9735
10470
|
length: Math.min(
|
|
9736
|
-
|
|
10471
|
+
OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES2,
|
|
9737
10472
|
Math.max(1, args.expected.bytes - offset)
|
|
9738
10473
|
)
|
|
9739
10474
|
});
|
|
@@ -9744,7 +10479,7 @@ async function hydrateOfflineFileContent(args) {
|
|
|
9744
10479
|
throw new Error(`remote offline content chunk was empty before EOF: ${args.expected.path}`);
|
|
9745
10480
|
}
|
|
9746
10481
|
try {
|
|
9747
|
-
finalResult = await
|
|
10482
|
+
finalResult = await applyOfflineSyncFileContentChunk2({
|
|
9748
10483
|
root: args.memoryDir,
|
|
9749
10484
|
sourceId: args.sourceId,
|
|
9750
10485
|
path: args.expected.path,
|
|
@@ -10121,7 +10856,7 @@ function advanceOfflineBaseFilesForSuccessfulPush(options) {
|
|
|
10121
10856
|
return [...next.values()].sort((left, right) => left.path.localeCompare(right.path));
|
|
10122
10857
|
}
|
|
10123
10858
|
async function runOfflineSyncOnce(options) {
|
|
10124
|
-
|
|
10859
|
+
fs12.mkdirSync(options.memoryDir, { recursive: true });
|
|
10125
10860
|
let activeStatePath = options.statePath;
|
|
10126
10861
|
let priorState = await readOfflineSyncState(activeStatePath);
|
|
10127
10862
|
let syncNamespace = options.namespace ?? priorState?.namespace;
|
|
@@ -10175,7 +10910,7 @@ async function runOfflineSyncOnce(options) {
|
|
|
10175
10910
|
options.secureStoreEncryptOnWrite ?? true
|
|
10176
10911
|
)).drainPendingMemoryLifecycleEventsForSyncAt(ledgerPath)
|
|
10177
10912
|
);
|
|
10178
|
-
const currentSnapshotForPush = await
|
|
10913
|
+
const currentSnapshotForPush = await buildOfflineSyncSnapshotFromBase2({
|
|
10179
10914
|
root: options.memoryDir,
|
|
10180
10915
|
sourceId: localSourceId,
|
|
10181
10916
|
baseFiles,
|
|
@@ -10324,7 +11059,7 @@ async function runOfflineSyncOnce(options) {
|
|
|
10324
11059
|
if (pushed) return writePartialPushState(error);
|
|
10325
11060
|
throw error;
|
|
10326
11061
|
}
|
|
10327
|
-
let currentSnapshotForChangeset = directPushedPaths.size > 0 ? await
|
|
11062
|
+
let currentSnapshotForChangeset = directPushedPaths.size > 0 ? await buildOfflineSyncSnapshotFromBase2({
|
|
10328
11063
|
root: options.memoryDir,
|
|
10329
11064
|
sourceId: localSourceId,
|
|
10330
11065
|
baseFiles,
|
|
@@ -10377,7 +11112,7 @@ async function runOfflineSyncOnce(options) {
|
|
|
10377
11112
|
}
|
|
10378
11113
|
changesetRetryCount += 1;
|
|
10379
11114
|
directPushDeferredPaths.add(changedPath);
|
|
10380
|
-
currentSnapshotForChangeset = await
|
|
11115
|
+
currentSnapshotForChangeset = await buildOfflineSyncSnapshotFromBase2({
|
|
10381
11116
|
root: options.memoryDir,
|
|
10382
11117
|
sourceId: localSourceId,
|
|
10383
11118
|
baseFiles,
|
|
@@ -10432,7 +11167,7 @@ async function runOfflineSyncOnce(options) {
|
|
|
10432
11167
|
}
|
|
10433
11168
|
let currentSnapshot;
|
|
10434
11169
|
try {
|
|
10435
|
-
currentSnapshot = await
|
|
11170
|
+
currentSnapshot = await buildOfflineSyncSnapshotFromBase2({
|
|
10436
11171
|
root: options.memoryDir,
|
|
10437
11172
|
sourceId: localSourceId,
|
|
10438
11173
|
baseFiles,
|
|
@@ -10495,7 +11230,7 @@ async function runOfflineSyncOnce(options) {
|
|
|
10495
11230
|
resolvedNamespace: resolvedOfflineSnapshotNamespace(remoteSnapshotMetadata, syncNamespace),
|
|
10496
11231
|
remoteFileCount: remoteSnapshotMetadata.files.length
|
|
10497
11232
|
};
|
|
10498
|
-
const buildCurrentSnapshotForApply = async () =>
|
|
11233
|
+
const buildCurrentSnapshotForApply = async () => buildOfflineSyncSnapshotFromBase2({
|
|
10499
11234
|
root: options.memoryDir,
|
|
10500
11235
|
sourceId: localSourceId,
|
|
10501
11236
|
baseFiles,
|
|
@@ -10739,7 +11474,7 @@ Environment fallbacks:
|
|
|
10739
11474
|
REMNIC_OFFLINE_REMOTE_URL, REMNIC_OFFLINE_TOKEN, REMNIC_AUTH_TOKEN`);
|
|
10740
11475
|
return;
|
|
10741
11476
|
}
|
|
10742
|
-
const memoryDir =
|
|
11477
|
+
const memoryDir = path15.resolve(expandTilde(resolveRequiredValueFlag(rest, "--memory-dir") ?? resolveMemoryDir()));
|
|
10743
11478
|
const namespace = resolveRequiredValueFlag(rest, "--namespace");
|
|
10744
11479
|
const includeTranscripts = !hasFlag(rest, "--no-transcripts");
|
|
10745
11480
|
const stateOverride = resolveRequiredValueFlag(rest, "--state");
|
|
@@ -10747,7 +11482,7 @@ Environment fallbacks:
|
|
|
10747
11482
|
const configPath = resolveConfigPath();
|
|
10748
11483
|
let config;
|
|
10749
11484
|
try {
|
|
10750
|
-
const rawConfig =
|
|
11485
|
+
const rawConfig = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
|
|
10751
11486
|
config = parseConfigQuietly(pickOfflineConfigRecord(rawConfig));
|
|
10752
11487
|
} catch {
|
|
10753
11488
|
throw new Error(
|
|
@@ -10759,10 +11494,10 @@ Environment fallbacks:
|
|
|
10759
11494
|
const needsRemote = action === "prepare" || action === "sync" || action === "watch";
|
|
10760
11495
|
const remoteUrl = needsRemote ? resolveOfflineRemoteUrl(rest) : resolveOptionalOfflineRemoteUrl(rest);
|
|
10761
11496
|
const token = needsRemote ? resolveOfflineToken(rest) : void 0;
|
|
10762
|
-
const statePath = statePathExplicit ?
|
|
11497
|
+
const statePath = statePathExplicit ? path15.resolve(expandTilde(stateOverride)) : remoteUrl !== void 0 ? defaultOfflineSyncStatePath(memoryDir, remoteUrl, namespace) : void 0;
|
|
10763
11498
|
if (action === "prepare") {
|
|
10764
11499
|
if (!remoteUrl || !token || !statePath) throw new Error("offline prepare requires remote URL and token");
|
|
10765
|
-
|
|
11500
|
+
fs12.mkdirSync(memoryDir, { recursive: true });
|
|
10766
11501
|
const remoteSnapshot = await fetchOfflineSnapshot({
|
|
10767
11502
|
remoteUrl,
|
|
10768
11503
|
token,
|
|
@@ -10860,7 +11595,7 @@ Environment fallbacks:
|
|
|
10860
11595
|
return;
|
|
10861
11596
|
}
|
|
10862
11597
|
if (action === "status") {
|
|
10863
|
-
|
|
11598
|
+
fs12.mkdirSync(memoryDir, { recursive: true });
|
|
10864
11599
|
const state = statePath ? await readOfflineSyncState(statePath) : null;
|
|
10865
11600
|
if (state && remoteUrl && statePath) {
|
|
10866
11601
|
assertOfflineStateMatches({
|
|
@@ -10940,11 +11675,11 @@ Environment fallbacks:
|
|
|
10940
11675
|
failures: result.largeFilePushFailures
|
|
10941
11676
|
});
|
|
10942
11677
|
largeFileFailureCounts = advanced.counts;
|
|
10943
|
-
for (const
|
|
10944
|
-
if (skippedLargeFiles.has(
|
|
10945
|
-
skippedLargeFiles.add(
|
|
11678
|
+
for (const path16 of advanced.newlySkipped) {
|
|
11679
|
+
if (skippedLargeFiles.has(path16)) continue;
|
|
11680
|
+
skippedLargeFiles.add(path16);
|
|
10946
11681
|
console.warn(
|
|
10947
|
-
`offline sync: permanently skipping ${
|
|
11682
|
+
`offline sync: permanently skipping ${path16} after ${OFFLINE_LARGE_FILE_SKIP_AFTER_FAILURES} failed large-file pushes for this watcher process (see issue #1786; use --exclude or offlineSyncExcludes to silence permanently)`
|
|
10948
11683
|
);
|
|
10949
11684
|
}
|
|
10950
11685
|
const pulled = result.pull ? result.pull.upserted + result.pull.deleted : 0;
|
|
@@ -10959,11 +11694,11 @@ Environment fallbacks:
|
|
|
10959
11694
|
failures: error.failures
|
|
10960
11695
|
});
|
|
10961
11696
|
largeFileFailureCounts = advanced.counts;
|
|
10962
|
-
for (const
|
|
10963
|
-
if (skippedLargeFiles.has(
|
|
10964
|
-
skippedLargeFiles.add(
|
|
11697
|
+
for (const path16 of advanced.newlySkipped) {
|
|
11698
|
+
if (skippedLargeFiles.has(path16)) continue;
|
|
11699
|
+
skippedLargeFiles.add(path16);
|
|
10965
11700
|
console.warn(
|
|
10966
|
-
`offline sync: permanently skipping ${
|
|
11701
|
+
`offline sync: permanently skipping ${path16} after ${OFFLINE_LARGE_FILE_SKIP_AFTER_FAILURES} failed large-file pushes for this watcher process (see issue #1786; use --exclude or offlineSyncExcludes to silence permanently)`
|
|
10967
11702
|
);
|
|
10968
11703
|
}
|
|
10969
11704
|
}
|
|
@@ -10998,7 +11733,7 @@ function cmdDedup(json) {
|
|
|
10998
11733
|
function readInstalledConnectorConfig(configPath, fallback) {
|
|
10999
11734
|
if (!configPath) return fallback;
|
|
11000
11735
|
try {
|
|
11001
|
-
const parsed = JSON.parse(
|
|
11736
|
+
const parsed = JSON.parse(fs12.readFileSync(configPath, "utf8"));
|
|
11002
11737
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return fallback;
|
|
11003
11738
|
const { token: _token, ...config } = parsed;
|
|
11004
11739
|
return config;
|
|
@@ -11104,7 +11839,7 @@ async function cmdConnectors(action, rest, json) {
|
|
|
11104
11839
|
const connectorDaemonUrl = typeof effectiveConnectorConfig.remnicDaemonUrl === "string" && effectiveConnectorConfig.remnicDaemonUrl.trim().length > 0 ? effectiveConnectorConfig.remnicDaemonUrl.trim() : void 0;
|
|
11105
11840
|
const pubResult = await pub.publish({
|
|
11106
11841
|
config: { memoryDir, namespace: connectorNamespace, daemonUrl: connectorDaemonUrl },
|
|
11107
|
-
skillsRoot:
|
|
11842
|
+
skillsRoot: path15.join(memoryDir, "skills"),
|
|
11108
11843
|
rollbackTokenEntry: preInstallTokenEntry,
|
|
11109
11844
|
log: { info: console.log, warn: console.warn, error: console.error }
|
|
11110
11845
|
});
|
|
@@ -11176,7 +11911,7 @@ async function cmdConnectors(action, rest, json) {
|
|
|
11176
11911
|
const pub = factory();
|
|
11177
11912
|
const available = await pub.isHostAvailable();
|
|
11178
11913
|
const extRoot = available ? await pub.resolveExtensionRoot() : "(host not installed)";
|
|
11179
|
-
const extensionExists = available && extRoot ?
|
|
11914
|
+
const extensionExists = available && extRoot ? fs12.existsSync(extRoot) : false;
|
|
11180
11915
|
publisherChecks.push({
|
|
11181
11916
|
name: `Publisher: ${targetHostId}`,
|
|
11182
11917
|
ok: !available || extensionExists,
|
|
@@ -11250,7 +11985,7 @@ async function cmdConnectors(action, rest, json) {
|
|
|
11250
11985
|
let connectorsCfg;
|
|
11251
11986
|
const configPath = resolveConfigPath();
|
|
11252
11987
|
try {
|
|
11253
|
-
const raw =
|
|
11988
|
+
const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
|
|
11254
11989
|
connectorsCfg = parseConfigQuietly(raw).connectors;
|
|
11255
11990
|
} catch {
|
|
11256
11991
|
process.stderr.write(
|
|
@@ -11326,9 +12061,9 @@ async function cmdConnectors(action, rest, json) {
|
|
|
11326
12061
|
}
|
|
11327
12062
|
initLogger2();
|
|
11328
12063
|
const configPath = resolveConfigPath();
|
|
11329
|
-
const raw =
|
|
12064
|
+
const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
|
|
11330
12065
|
const remnicCfg = resolveRemnicConfigRecord4(raw);
|
|
11331
|
-
const config =
|
|
12066
|
+
const config = parseConfig5(remnicCfg);
|
|
11332
12067
|
const orchestrator = new Orchestrator3(config);
|
|
11333
12068
|
try {
|
|
11334
12069
|
await orchestrator.initialize();
|
|
@@ -11451,9 +12186,9 @@ async function cmdConnectorsMarketplace(subAction, rest, json) {
|
|
|
11451
12186
|
console.error(`connectors marketplace: ${err instanceof Error ? err.message : String(err)}`);
|
|
11452
12187
|
process.exit(1);
|
|
11453
12188
|
}
|
|
11454
|
-
const rawConfig =
|
|
12189
|
+
const rawConfig = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
|
|
11455
12190
|
const pluginConfig = resolveRemnicConfigRecord4(rawConfig);
|
|
11456
|
-
const config =
|
|
12191
|
+
const config = parseConfig5(pluginConfig);
|
|
11457
12192
|
if (subAction === "generate") {
|
|
11458
12193
|
let outputDir;
|
|
11459
12194
|
try {
|
|
@@ -11464,22 +12199,22 @@ async function cmdConnectorsMarketplace(subAction, rest, json) {
|
|
|
11464
12199
|
}
|
|
11465
12200
|
const manifest = generateMarketplaceManifest();
|
|
11466
12201
|
await writeMarketplaceManifest(outputDir, manifest);
|
|
11467
|
-
const outPath =
|
|
12202
|
+
const outPath = path15.join(outputDir, "marketplace.json");
|
|
11468
12203
|
if (json) {
|
|
11469
12204
|
console.log(JSON.stringify({ status: "generated", path: outPath }, null, 2));
|
|
11470
12205
|
} else {
|
|
11471
12206
|
console.log(`Generated marketplace.json at ${outPath}`);
|
|
11472
12207
|
}
|
|
11473
12208
|
} else if (subAction === "validate") {
|
|
11474
|
-
const targetPath = rest.filter((a) => !a.startsWith("--"))[0] ??
|
|
11475
|
-
const resolved =
|
|
11476
|
-
if (!
|
|
12209
|
+
const targetPath = rest.filter((a) => !a.startsWith("--"))[0] ?? path15.join(process.cwd(), "marketplace.json");
|
|
12210
|
+
const resolved = path15.resolve(targetPath);
|
|
12211
|
+
if (!fs12.existsSync(resolved)) {
|
|
11477
12212
|
console.error(`File not found: ${resolved}`);
|
|
11478
12213
|
process.exit(1);
|
|
11479
12214
|
}
|
|
11480
12215
|
let parsed;
|
|
11481
12216
|
try {
|
|
11482
|
-
parsed = JSON.parse(
|
|
12217
|
+
parsed = JSON.parse(fs12.readFileSync(resolved, "utf8"));
|
|
11483
12218
|
} catch {
|
|
11484
12219
|
console.error(`Invalid JSON in ${resolved}`);
|
|
11485
12220
|
process.exit(1);
|
|
@@ -11680,9 +12415,9 @@ async function cmdSpace(action, rest, json) {
|
|
|
11680
12415
|
async function cmdLegacyBenchmark(action, rest, json) {
|
|
11681
12416
|
initLogger2();
|
|
11682
12417
|
const configPath = resolveConfigPath();
|
|
11683
|
-
const raw =
|
|
12418
|
+
const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
|
|
11684
12419
|
const remnicCfg = resolveRemnicConfigRecord4(raw);
|
|
11685
|
-
const config =
|
|
12420
|
+
const config = parseConfig5(remnicCfg);
|
|
11686
12421
|
const orchestrator = new Orchestrator3(config);
|
|
11687
12422
|
const service = new EngramAccessService2(orchestrator);
|
|
11688
12423
|
const { runBenchSuite, loadBaseline, checkRegression } = await loadBenchModule();
|
|
@@ -11880,7 +12615,7 @@ async function cmdBench(rest) {
|
|
|
11880
12615
|
}
|
|
11881
12616
|
const completeCount = prevStatus.benchmarks.filter((b) => b.status === "complete").length;
|
|
11882
12617
|
const failedCount = prevStatus.benchmarks.filter((b) => b.status === "failed").length;
|
|
11883
|
-
printBenchStatusLine(parsed.json, `Resuming from: ${
|
|
12618
|
+
printBenchStatusLine(parsed.json, `Resuming from: ${path15.basename(latestStatusPath)}`);
|
|
11884
12619
|
printBenchStatusLine(parsed.json, ` Previous run: ${prevStatus.startedAt}`);
|
|
11885
12620
|
printBenchStatusLine(parsed.json, ` Benchmarks: ${prevStatus.benchmarks.length} total, ${completeCount} complete, ${failedCount} failed`);
|
|
11886
12621
|
const before = selectedBenchmarks.length;
|
|
@@ -12048,9 +12783,9 @@ Options:
|
|
|
12048
12783
|
);
|
|
12049
12784
|
process.exit(1);
|
|
12050
12785
|
} else {
|
|
12051
|
-
fixturePath =
|
|
12786
|
+
fixturePath = path15.resolve(expandTilde(fixturePathRaw));
|
|
12052
12787
|
}
|
|
12053
|
-
const outPath =
|
|
12788
|
+
const outPath = path15.resolve(expandTilde(outPathRaw));
|
|
12054
12789
|
const benchModule = await loadBenchModule();
|
|
12055
12790
|
const runner = benchModule.runProceduralAblationCli;
|
|
12056
12791
|
if (typeof runner !== "function") {
|
|
@@ -12069,7 +12804,7 @@ Options:
|
|
|
12069
12804
|
);
|
|
12070
12805
|
console.log(`wrote ${outPath}`);
|
|
12071
12806
|
}
|
|
12072
|
-
var LOGS_DIR =
|
|
12807
|
+
var LOGS_DIR = path15.join(PID_DIR, "logs");
|
|
12073
12808
|
var LAUNCHD_PLIST_PATHS = launchdPlistPaths(resolveHomeDir());
|
|
12074
12809
|
var [LAUNCHD_PLIST_PATH] = LAUNCHD_PLIST_PATHS;
|
|
12075
12810
|
var SYSTEMD_UNIT_PATHS = systemdUnitPaths(resolveHomeDir());
|
|
@@ -12083,7 +12818,7 @@ function readPid() {
|
|
|
12083
12818
|
function inferPort() {
|
|
12084
12819
|
try {
|
|
12085
12820
|
const configPath = resolveConfigPath();
|
|
12086
|
-
const raw = JSON.parse(
|
|
12821
|
+
const raw = JSON.parse(fs12.readFileSync(configPath, "utf8"));
|
|
12087
12822
|
return raw.server?.port ?? 4318;
|
|
12088
12823
|
} catch {
|
|
12089
12824
|
return 4318;
|
|
@@ -12146,7 +12881,7 @@ function selectLaunchdInspection(openclawPluginModeConfigured) {
|
|
|
12146
12881
|
for (const plistPath of LAUNCHD_PLIST_PATHS.slice(1)) {
|
|
12147
12882
|
const legacy = inspectLaunchdPlist(plistPath);
|
|
12148
12883
|
if (!legacy.installed) continue;
|
|
12149
|
-
const label =
|
|
12884
|
+
const label = path15.basename(plistPath, ".plist");
|
|
12150
12885
|
return legacy.ok ? {
|
|
12151
12886
|
...legacy,
|
|
12152
12887
|
warn: true,
|
|
@@ -12178,13 +12913,13 @@ function daemonInstall() {
|
|
|
12178
12913
|
process.exit(1);
|
|
12179
12914
|
}
|
|
12180
12915
|
const vars = { HOME: home, NODE_PATH: nodePath, REMNIC_SERVER_BIN: serverBin };
|
|
12181
|
-
|
|
12916
|
+
fs12.mkdirSync(LOGS_DIR, { recursive: true });
|
|
12182
12917
|
if (isMacOS()) {
|
|
12183
|
-
const templatePath =
|
|
12184
|
-
const template =
|
|
12918
|
+
const templatePath = path15.resolve(import.meta.dirname, "../templates/launchd/ai.remnic.daemon.plist");
|
|
12919
|
+
const template = fs12.readFileSync(templatePath, "utf8");
|
|
12185
12920
|
const plist = renderTemplate(template, vars);
|
|
12186
|
-
|
|
12187
|
-
|
|
12921
|
+
fs12.mkdirSync(path15.dirname(LAUNCHD_PLIST_PATH), { recursive: true });
|
|
12922
|
+
fs12.writeFileSync(LAUNCHD_PLIST_PATH, plist);
|
|
12188
12923
|
try {
|
|
12189
12924
|
launchdLoadPlist(LAUNCHD_PLIST_PATH);
|
|
12190
12925
|
} catch (err) {
|
|
@@ -12200,11 +12935,11 @@ function daemonInstall() {
|
|
|
12200
12935
|
console.log(` RunAtLoad: true, KeepAlive: true`);
|
|
12201
12936
|
console.log(` Logs: ${LOGS_DIR}/daemon.log`);
|
|
12202
12937
|
} else if (isLinux()) {
|
|
12203
|
-
const templatePath =
|
|
12204
|
-
const template =
|
|
12938
|
+
const templatePath = path15.resolve(import.meta.dirname, "../templates/systemd/remnic.service");
|
|
12939
|
+
const template = fs12.readFileSync(templatePath, "utf8");
|
|
12205
12940
|
const unit = renderTemplate(template, vars);
|
|
12206
|
-
|
|
12207
|
-
|
|
12941
|
+
fs12.mkdirSync(path15.dirname(SYSTEMD_UNIT_PATH), { recursive: true });
|
|
12942
|
+
fs12.writeFileSync(SYSTEMD_UNIT_PATH, unit);
|
|
12208
12943
|
try {
|
|
12209
12944
|
childProcess2.execSync("systemctl --user daemon-reload", { stdio: "pipe" });
|
|
12210
12945
|
} catch (err) {
|
|
@@ -12240,7 +12975,7 @@ function daemonUninstall() {
|
|
|
12240
12975
|
} catch {
|
|
12241
12976
|
}
|
|
12242
12977
|
try {
|
|
12243
|
-
|
|
12978
|
+
fs12.unlinkSync(plistPath);
|
|
12244
12979
|
removed = true;
|
|
12245
12980
|
console.log(`Removed launchd service: ${plistPath}`);
|
|
12246
12981
|
} catch {
|
|
@@ -12260,7 +12995,7 @@ function daemonUninstall() {
|
|
|
12260
12995
|
let removed = false;
|
|
12261
12996
|
for (const unitPath of SYSTEMD_UNIT_PATHS) {
|
|
12262
12997
|
try {
|
|
12263
|
-
|
|
12998
|
+
fs12.unlinkSync(unitPath);
|
|
12264
12999
|
removed = true;
|
|
12265
13000
|
console.log(`Removed systemd service: ${unitPath}`);
|
|
12266
13001
|
} catch {
|
|
@@ -12327,13 +13062,13 @@ async function daemonStatus() {
|
|
|
12327
13062
|
console.log(` Port: ${port}`);
|
|
12328
13063
|
console.log(` Service: ${serviceInstalled ? "installed" : "not installed"}`);
|
|
12329
13064
|
console.log(` Platform: ${process.platform}`);
|
|
12330
|
-
console.log(` PID file: ${
|
|
12331
|
-
console.log(` Log file: ${
|
|
13065
|
+
console.log(` PID file: ${fs12.existsSync(PID_FILE) ? PID_FILE : LEGACY_PID_FILE}`);
|
|
13066
|
+
console.log(` Log file: ${fs12.existsSync(LOG_FILE) ? LOG_FILE : LEGACY_LOG_FILE}`);
|
|
12332
13067
|
try {
|
|
12333
13068
|
const configPath = resolveConfigPath();
|
|
12334
|
-
const raw =
|
|
13069
|
+
const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
|
|
12335
13070
|
const remnicCfg = resolveRemnicConfigRecord4(raw);
|
|
12336
|
-
const config =
|
|
13071
|
+
const config = parseConfig5(remnicCfg);
|
|
12337
13072
|
const extRoot = resolveExtensionsRoot(config);
|
|
12338
13073
|
const noopLog = { warn: () => {
|
|
12339
13074
|
}, debug: () => {
|
|
@@ -12372,9 +13107,9 @@ function daemonStart() {
|
|
|
12372
13107
|
return;
|
|
12373
13108
|
}
|
|
12374
13109
|
}
|
|
12375
|
-
|
|
12376
|
-
|
|
12377
|
-
const logStream =
|
|
13110
|
+
fs12.mkdirSync(PID_DIR, { recursive: true });
|
|
13111
|
+
fs12.mkdirSync(LOGS_DIR, { recursive: true });
|
|
13112
|
+
const logStream = fs12.openSync(LOG_FILE, "a");
|
|
12378
13113
|
const serverBin = resolveServerBin();
|
|
12379
13114
|
const isSource = serverBin.endsWith(".ts");
|
|
12380
13115
|
let cmd;
|
|
@@ -12396,7 +13131,7 @@ function daemonStart() {
|
|
|
12396
13131
|
}
|
|
12397
13132
|
});
|
|
12398
13133
|
child.unref();
|
|
12399
|
-
|
|
13134
|
+
fs12.writeFileSync(PID_FILE, String(child.pid));
|
|
12400
13135
|
console.log(`Started remnic server (pid ${child.pid})`);
|
|
12401
13136
|
console.log(` Log: ${LOG_FILE}`);
|
|
12402
13137
|
}
|
|
@@ -12430,11 +13165,11 @@ function daemonStop() {
|
|
|
12430
13165
|
console.log("Process not found (cleaning up PID file)");
|
|
12431
13166
|
}
|
|
12432
13167
|
try {
|
|
12433
|
-
|
|
13168
|
+
fs12.unlinkSync(PID_FILE);
|
|
12434
13169
|
} catch {
|
|
12435
13170
|
}
|
|
12436
13171
|
try {
|
|
12437
|
-
|
|
13172
|
+
fs12.unlinkSync(LEGACY_PID_FILE);
|
|
12438
13173
|
} catch {
|
|
12439
13174
|
}
|
|
12440
13175
|
}
|
|
@@ -12562,9 +13297,9 @@ async function promptYesNo(question, defaultYes = true) {
|
|
|
12562
13297
|
async function cmdBinary(rest) {
|
|
12563
13298
|
initLogger2();
|
|
12564
13299
|
const configPath = resolveConfigPath();
|
|
12565
|
-
const raw =
|
|
13300
|
+
const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
|
|
12566
13301
|
const remnicCfg = resolveRemnicConfigRecord4(raw);
|
|
12567
|
-
const config =
|
|
13302
|
+
const config = parseConfig5(remnicCfg);
|
|
12568
13303
|
const memoryDir = resolveMemoryDir();
|
|
12569
13304
|
const blConfig = {
|
|
12570
13305
|
enabled: config.binaryLifecycleEnabled,
|
|
@@ -12682,7 +13417,7 @@ Clean complete: cleaned=${result.cleaned}`
|
|
|
12682
13417
|
}
|
|
12683
13418
|
async function cmdOpenclawInstall(opts) {
|
|
12684
13419
|
const configPath = resolveOpenclawConfigPath(opts.configPath);
|
|
12685
|
-
const fallbackMemoryDir =
|
|
13420
|
+
const fallbackMemoryDir = path15.join(resolveHomeDir(), ".openclaw", "workspace", "memory", "local");
|
|
12686
13421
|
console.log(`OpenClaw config: ${configPath}`);
|
|
12687
13422
|
const existingConfig = readOpenclawConfig(configPath);
|
|
12688
13423
|
const { plugins, entries, slots } = parseOpenclawPluginState(existingConfig, configPath);
|
|
@@ -12753,7 +13488,7 @@ async function cmdOpenclawInstall(opts) {
|
|
|
12753
13488
|
} else if (slotIsActiveLegacy) {
|
|
12754
13489
|
changes.push(` Slot left as "${REMNIC_OPENCLAW_LEGACY_PLUGIN_ID}" \u2014 re-run with --yes to activate the new entry`);
|
|
12755
13490
|
}
|
|
12756
|
-
if (!
|
|
13491
|
+
if (!fs12.existsSync(memoryDir)) changes.push(`+ Will create memory directory: ${memoryDir}`);
|
|
12757
13492
|
if (hasLegacy && migrateLegacy) {
|
|
12758
13493
|
changes.push(`~ Legacy '${REMNIC_OPENCLAW_LEGACY_PLUGIN_ID}' entry retained (safe to remove after verifying hooks fire)`);
|
|
12759
13494
|
}
|
|
@@ -12773,8 +13508,8 @@ async function cmdOpenclawInstall(opts) {
|
|
|
12773
13508
|
Resulting plugins.slots.memory: ${dryRunPlugins.slots?.memory ?? "(unset)"}`);
|
|
12774
13509
|
return;
|
|
12775
13510
|
}
|
|
12776
|
-
if (
|
|
12777
|
-
const st =
|
|
13511
|
+
if (fs12.existsSync(memoryDir)) {
|
|
13512
|
+
const st = fs12.statSync(memoryDir);
|
|
12778
13513
|
if (!st.isDirectory()) {
|
|
12779
13514
|
throw new Error(
|
|
12780
13515
|
`Cannot use ${memoryDir} as the memory directory \u2014 a file already exists at that path.
|
|
@@ -12782,12 +13517,12 @@ Remove it first and re-run, or choose a different path with --memory-dir.`
|
|
|
12782
13517
|
);
|
|
12783
13518
|
}
|
|
12784
13519
|
} else {
|
|
12785
|
-
|
|
13520
|
+
fs12.mkdirSync(memoryDir, { recursive: true });
|
|
12786
13521
|
console.log(`Created memory directory: ${memoryDir}`);
|
|
12787
13522
|
}
|
|
12788
|
-
const configDir =
|
|
12789
|
-
if (!
|
|
12790
|
-
|
|
13523
|
+
const configDir = path15.dirname(configPath);
|
|
13524
|
+
if (!fs12.existsSync(configDir)) {
|
|
13525
|
+
fs12.mkdirSync(configDir, { recursive: true });
|
|
12791
13526
|
}
|
|
12792
13527
|
atomicWriteFileSync(configPath, JSON.stringify(updatedConfig, null, 2) + "\n");
|
|
12793
13528
|
console.log("\nDone! Summary of changes:");
|
|
@@ -12813,11 +13548,11 @@ async function cmdOpenclawUpgrade(opts) {
|
|
|
12813
13548
|
const configPath = resolveOpenclawConfigPath(opts.configPath);
|
|
12814
13549
|
const pluginDir = resolveOpenclawPluginDir(opts.pluginDir);
|
|
12815
13550
|
const legacyPluginDirForBackup = opts.legacyPluginDirForBackup ? resolveOpenclawLegacyPluginDir(opts.legacyPluginDirForBackup) : void 0;
|
|
12816
|
-
const fallbackMemoryDir =
|
|
13551
|
+
const fallbackMemoryDir = path15.join(resolveHomeDir(), ".openclaw", "workspace", "memory", "local");
|
|
12817
13552
|
const packageSpec = `@remnic/plugin-openclaw@${opts.version ?? "latest"}`;
|
|
12818
13553
|
const existingConfig = readOpenclawConfig(configPath);
|
|
12819
13554
|
const { entries, slots } = parseOpenclawPluginState(existingConfig, configPath);
|
|
12820
|
-
const preservedMemoryDir = opts.memoryDir ?
|
|
13555
|
+
const preservedMemoryDir = opts.memoryDir ? path15.resolve(expandTilde(opts.memoryDir)) : resolveCurrentOpenclawMemoryDir(entries, slots, fallbackMemoryDir);
|
|
12821
13556
|
assertDirectoryPathOrMissing(pluginDir, "OpenClaw plugin dir");
|
|
12822
13557
|
if (legacyPluginDirForBackup) {
|
|
12823
13558
|
assertDirectoryPathOrMissing(legacyPluginDirForBackup, "Legacy OpenClaw plugin dir");
|
|
@@ -12829,7 +13564,7 @@ async function cmdOpenclawUpgrade(opts) {
|
|
|
12829
13564
|
}
|
|
12830
13565
|
console.log(`Memory dir: ${preservedMemoryDir}`);
|
|
12831
13566
|
console.log(`Package spec: ${packageSpec}`);
|
|
12832
|
-
console.log(`Backup root: ${
|
|
13567
|
+
console.log(`Backup root: ${path15.join(resolveHomeDir(), ".openclaw", "backups")}`);
|
|
12833
13568
|
const plannedActions = [
|
|
12834
13569
|
`backup openclaw.json and the existing ${REMNIC_OPENCLAW_PLUGIN_ID} extension`,
|
|
12835
13570
|
...legacyPluginDirForBackup ? [`backup the existing ${REMNIC_OPENCLAW_LEGACY_PLUGIN_ID} extension without modifying it`] : [],
|
|
@@ -12855,9 +13590,9 @@ async function cmdOpenclawUpgrade(opts) {
|
|
|
12855
13590
|
}
|
|
12856
13591
|
}
|
|
12857
13592
|
const backupDir = createOpenclawUpgradeBackupDir();
|
|
12858
|
-
const configBackupPath =
|
|
12859
|
-
const pluginBackupDir =
|
|
12860
|
-
const legacyPluginBackupDir = legacyPluginDirForBackup ?
|
|
13593
|
+
const configBackupPath = path15.join(backupDir, "openclaw.json");
|
|
13594
|
+
const pluginBackupDir = path15.join(backupDir, "extensions", REMNIC_OPENCLAW_PLUGIN_ID);
|
|
13595
|
+
const legacyPluginBackupDir = legacyPluginDirForBackup ? path15.join(backupDir, "extensions", REMNIC_OPENCLAW_LEGACY_PLUGIN_ID) : void 0;
|
|
12861
13596
|
const backupNotes = [];
|
|
12862
13597
|
if (backupPathIfPresent(configPath, configBackupPath)) {
|
|
12863
13598
|
backupNotes.push(`+ Backed up config to ${configBackupPath}`);
|
|
@@ -12958,16 +13693,16 @@ async function cmdOpenclawMigrateEngram(opts) {
|
|
|
12958
13693
|
console.log(" - Re-apply any local source patches to the new package only after verifying the published build.");
|
|
12959
13694
|
}
|
|
12960
13695
|
function createOpenclawUpgradeBackupDir() {
|
|
12961
|
-
const backupsRoot =
|
|
12962
|
-
|
|
12963
|
-
return
|
|
13696
|
+
const backupsRoot = path15.join(resolveHomeDir(), ".openclaw", "backups");
|
|
13697
|
+
fs12.mkdirSync(backupsRoot, { recursive: true });
|
|
13698
|
+
return fs12.mkdtempSync(path15.join(backupsRoot, `remnic-openclaw-upgrade-${formatOpenclawUpgradeStamp()}-`));
|
|
12964
13699
|
}
|
|
12965
13700
|
async function cmdTaxonomy(rest) {
|
|
12966
13701
|
initLogger2();
|
|
12967
13702
|
const configPath = resolveConfigPath();
|
|
12968
|
-
const raw =
|
|
13703
|
+
const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
|
|
12969
13704
|
const remnicCfg = resolveRemnicConfigRecord4(raw);
|
|
12970
|
-
const config =
|
|
13705
|
+
const config = parseConfig5(remnicCfg);
|
|
12971
13706
|
if (!config.taxonomyEnabled) {
|
|
12972
13707
|
console.error(
|
|
12973
13708
|
"Taxonomy is disabled in config (taxonomyEnabled = false). Enable it to use taxonomy commands."
|
|
@@ -13002,9 +13737,9 @@ async function cmdTaxonomy(rest) {
|
|
|
13002
13737
|
const doc = generateResolverDocument(taxonomy);
|
|
13003
13738
|
console.log(doc);
|
|
13004
13739
|
if (config.taxonomyAutoGenResolver) {
|
|
13005
|
-
const resolverPath =
|
|
13006
|
-
|
|
13007
|
-
|
|
13740
|
+
const resolverPath = path15.join(config.memoryDir, ".taxonomy", "RESOLVER.md");
|
|
13741
|
+
fs12.mkdirSync(path15.dirname(resolverPath), { recursive: true });
|
|
13742
|
+
fs12.writeFileSync(resolverPath, doc);
|
|
13008
13743
|
console.error(`Written: ${resolverPath}`);
|
|
13009
13744
|
}
|
|
13010
13745
|
break;
|
|
@@ -13049,8 +13784,8 @@ async function cmdTaxonomy(rest) {
|
|
|
13049
13784
|
console.log(`Added category "${id}" (${name}).`);
|
|
13050
13785
|
if (config.taxonomyAutoGenResolver) {
|
|
13051
13786
|
const doc = generateResolverDocument(taxonomy);
|
|
13052
|
-
const resolverPath =
|
|
13053
|
-
|
|
13787
|
+
const resolverPath = path15.join(config.memoryDir, ".taxonomy", "RESOLVER.md");
|
|
13788
|
+
fs12.writeFileSync(resolverPath, doc);
|
|
13054
13789
|
console.error(`Regenerated: ${resolverPath}`);
|
|
13055
13790
|
}
|
|
13056
13791
|
break;
|
|
@@ -13080,8 +13815,8 @@ async function cmdTaxonomy(rest) {
|
|
|
13080
13815
|
console.log(`Removed category "${id}".`);
|
|
13081
13816
|
if (config.taxonomyAutoGenResolver) {
|
|
13082
13817
|
const doc = generateResolverDocument(taxonomy);
|
|
13083
|
-
const resolverPath =
|
|
13084
|
-
|
|
13818
|
+
const resolverPath = path15.join(config.memoryDir, ".taxonomy", "RESOLVER.md");
|
|
13819
|
+
fs12.writeFileSync(resolverPath, doc);
|
|
13085
13820
|
console.error(`Regenerated: ${resolverPath}`);
|
|
13086
13821
|
}
|
|
13087
13822
|
break;
|
|
@@ -13272,12 +14007,12 @@ async function runTrainingExport(args, stdout = process.stdout) {
|
|
|
13272
14007
|
`Unknown training-export format "${args.format}". ${validList}`
|
|
13273
14008
|
);
|
|
13274
14009
|
}
|
|
13275
|
-
if (!
|
|
14010
|
+
if (!fs12.existsSync(args.memoryDir)) {
|
|
13276
14011
|
throw new Error(
|
|
13277
14012
|
`--memory-dir "${args.memoryDir}" does not exist. Provide the path to an existing memory directory.`
|
|
13278
14013
|
);
|
|
13279
14014
|
}
|
|
13280
|
-
if (!
|
|
14015
|
+
if (!fs12.statSync(args.memoryDir).isDirectory()) {
|
|
13281
14016
|
throw new Error(
|
|
13282
14017
|
`--memory-dir "${args.memoryDir}" is not a directory. Provide the path to a memory directory, not a file.`
|
|
13283
14018
|
);
|
|
@@ -13362,11 +14097,11 @@ async function runTrainingExport(args, stdout = process.stdout) {
|
|
|
13362
14097
|
);
|
|
13363
14098
|
}
|
|
13364
14099
|
const formatted = adapter.formatRecords(records);
|
|
13365
|
-
const outDir =
|
|
13366
|
-
|
|
14100
|
+
const outDir = path15.dirname(args.output);
|
|
14101
|
+
fs12.mkdirSync(outDir, { recursive: true });
|
|
13367
14102
|
const tmpPath = `${args.output}.tmp-${process.pid}-${Date.now()}`;
|
|
13368
|
-
|
|
13369
|
-
|
|
14103
|
+
fs12.writeFileSync(tmpPath, formatted, "utf-8");
|
|
14104
|
+
fs12.renameSync(tmpPath, args.output);
|
|
13370
14105
|
stdout.write(
|
|
13371
14106
|
`Exported ${records.length} records to ${args.output} (${adapter.name} format)
|
|
13372
14107
|
`
|
|
@@ -13470,7 +14205,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
13470
14205
|
case "tree": {
|
|
13471
14206
|
const subAction = rest[0];
|
|
13472
14207
|
const json = rest.includes("--json");
|
|
13473
|
-
const outputDir = resolveFlag(rest, "--output") ??
|
|
14208
|
+
const outputDir = resolveFlag(rest, "--output") ?? path15.join(process.cwd(), ".remnic", "context-tree");
|
|
13474
14209
|
const categoriesFlag = resolveFlag(rest, "--categories");
|
|
13475
14210
|
const categories = categoriesFlag ? categoriesFlag.split(",") : void 0;
|
|
13476
14211
|
const maxPerCategoryRaw = resolveFlag(rest, "--max-per-category");
|
|
@@ -13535,7 +14270,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
13535
14270
|
}
|
|
13536
14271
|
}, 500);
|
|
13537
14272
|
};
|
|
13538
|
-
|
|
14273
|
+
fs12.watch(memoryDir, { recursive: true }, (_event, filename) => {
|
|
13539
14274
|
if (filename && filename.startsWith(".")) return;
|
|
13540
14275
|
rebuild();
|
|
13541
14276
|
});
|
|
@@ -13543,12 +14278,12 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
13543
14278
|
});
|
|
13544
14279
|
} else if (subAction === "validate") {
|
|
13545
14280
|
const treeDir = outputDir;
|
|
13546
|
-
if (!
|
|
14281
|
+
if (!fs12.existsSync(treeDir)) {
|
|
13547
14282
|
console.error(`Context tree not found at ${treeDir}. Run 'remnic tree generate' first.`);
|
|
13548
14283
|
process.exit(1);
|
|
13549
14284
|
}
|
|
13550
|
-
const indexPath =
|
|
13551
|
-
if (!
|
|
14285
|
+
const indexPath = path15.join(treeDir, "INDEX.md");
|
|
14286
|
+
if (!fs12.existsSync(indexPath)) {
|
|
13552
14287
|
console.error(`INDEX.md missing in ${treeDir}. Tree may be corrupt \u2014 regenerate.`);
|
|
13553
14288
|
process.exit(1);
|
|
13554
14289
|
}
|
|
@@ -13602,6 +14337,12 @@ Options:
|
|
|
13602
14337
|
await cmdOffline(action, rest.slice(1), json);
|
|
13603
14338
|
break;
|
|
13604
14339
|
}
|
|
14340
|
+
case "converge": {
|
|
14341
|
+
const action = rest[0] ?? "plan";
|
|
14342
|
+
const json = rest.includes("--json");
|
|
14343
|
+
await cmdConverge(action, rest.slice(1), json);
|
|
14344
|
+
break;
|
|
14345
|
+
}
|
|
13605
14346
|
case "oauth": {
|
|
13606
14347
|
await cmdOAuth(rest);
|
|
13607
14348
|
break;
|
|
@@ -13718,9 +14459,9 @@ Other:
|
|
|
13718
14459
|
let wearablesService;
|
|
13719
14460
|
try {
|
|
13720
14461
|
const configPath = resolveConfigPath();
|
|
13721
|
-
const raw =
|
|
14462
|
+
const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
|
|
13722
14463
|
const remnicCfg = resolveRemnicConfigRecord4(raw);
|
|
13723
|
-
const config =
|
|
14464
|
+
const config = parseConfig5(remnicCfg);
|
|
13724
14465
|
wearablesOrchestrator = new Orchestrator3(config);
|
|
13725
14466
|
await wearablesOrchestrator.initialize();
|
|
13726
14467
|
await wearablesOrchestrator.deferredReady;
|
|
@@ -13769,9 +14510,9 @@ Other:
|
|
|
13769
14510
|
const targetFactory = async () => {
|
|
13770
14511
|
if (!orchestratorSingleton) {
|
|
13771
14512
|
const configPath = resolveConfigPath();
|
|
13772
|
-
const raw =
|
|
14513
|
+
const raw = fs12.existsSync(configPath) ? JSON.parse(fs12.readFileSync(configPath, "utf8")) : {};
|
|
13773
14514
|
const remnicCfg = resolveRemnicConfigRecord4(raw);
|
|
13774
|
-
const config =
|
|
14515
|
+
const config = parseConfig5(remnicCfg);
|
|
13775
14516
|
orchestratorSingleton = new Orchestrator3(config);
|
|
13776
14517
|
await orchestratorSingleton.initialize();
|
|
13777
14518
|
await orchestratorSingleton.deferredReady;
|