@memnexus-ai/cli 1.7.218 → 1.7.220
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/commands/memories.d.ts +2 -14
- package/dist/commands/memories.d.ts.map +1 -1
- package/dist/commands/memories.js +132 -106
- package/dist/commands/memories.js.map +1 -1
- package/dist/lib/retry.d.ts +98 -0
- package/dist/lib/retry.d.ts.map +1 -0
- package/dist/lib/retry.js +183 -0
- package/dist/lib/retry.js.map +1 -0
- package/package.json +2 -2
|
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.StdinTooLargeError = exports.BATCH_CREATE_ALLOWED_KEYS = exports.BATCH_CREATE_MAX_LINE_BYTES = exports.BATCH_CREATE_MAX_STDIN_BYTES = exports.BATCH_CREATE_MAX_RETRIES = exports.BATCH_CREATE_CONCURRENCY_MAX = exports.BATCH_CREATE_CONCURRENCY_MIN = exports.BATCH_CREATE_DEFAULT_CONCURRENCY = exports.BULK_DELETE_LIST_PAGE_SIZE = exports.BULK_DELETE_MAX_RETRIES = exports.BULK_DELETE_PAGE_PAUSE_MS = exports.BULK_DELETE_MAX_IDS = exports.BULK_DELETE_GUARD_WINDOW_MS = exports.BULK_DELETE_GUARD_THRESHOLD = void 0;
|
|
39
|
+
exports.StdinTooLargeError = exports.BATCH_CREATE_ALLOWED_KEYS = exports.BATCH_CREATE_MAX_LINE_BYTES = exports.BATCH_CREATE_MAX_STDIN_BYTES = exports.BATCH_CREATE_MAX_RETRIES = exports.BATCH_CREATE_CONCURRENCY_MAX = exports.BATCH_CREATE_CONCURRENCY_MIN = exports.BATCH_CREATE_DEFAULT_CONCURRENCY = exports.sleep = exports.getRetryAfterMs = exports.isQuotaError = exports.isServiceUnavailableError = exports.isRateLimitError = exports.BULK_DELETE_LIST_PAGE_SIZE = exports.BULK_DELETE_MAX_RETRIES = exports.BULK_DELETE_PAGE_PAUSE_MS = exports.BULK_DELETE_MAX_IDS = exports.BULK_DELETE_GUARD_WINDOW_MS = exports.BULK_DELETE_GUARD_THRESHOLD = void 0;
|
|
40
40
|
exports.getBulkDeleteGuardPath = getBulkDeleteGuardPath;
|
|
41
41
|
exports.readDeleteGuardTimestamps = readDeleteGuardTimestamps;
|
|
42
42
|
exports.recordDeletion = recordDeletion;
|
|
@@ -45,15 +45,10 @@ exports.parseCodeContextFlag = parseCodeContextFlag;
|
|
|
45
45
|
exports.parseBatchIds = parseBatchIds;
|
|
46
46
|
exports.isValidIsoDate = isValidIsoDate;
|
|
47
47
|
exports.chunkIds = chunkIds;
|
|
48
|
-
exports.isRateLimitError = isRateLimitError;
|
|
49
|
-
exports.getRetryAfterMs = getRetryAfterMs;
|
|
50
|
-
exports.sleep = sleep;
|
|
51
48
|
exports.bulkDeleteWithRetry = bulkDeleteWithRetry;
|
|
52
49
|
exports.runBulkDeletePaged = runBulkDeletePaged;
|
|
53
50
|
exports.collectAllMemoryIds = collectAllMemoryIds;
|
|
54
|
-
exports.isServiceUnavailableError = isServiceUnavailableError;
|
|
55
51
|
exports.isRetryableCreateError = isRetryableCreateError;
|
|
56
|
-
exports.isQuotaError = isQuotaError;
|
|
57
52
|
exports.parseJsonlCreateLine = parseJsonlCreateLine;
|
|
58
53
|
exports.resolveCreateConcurrency = resolveCreateConcurrency;
|
|
59
54
|
exports.batchCreateWithRetry = batchCreateWithRetry;
|
|
@@ -74,6 +69,12 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
74
69
|
const api_client_1 = require("../lib/api-client");
|
|
75
70
|
const config_1 = require("../lib/config");
|
|
76
71
|
const errors_1 = require("../lib/errors");
|
|
72
|
+
const retry_1 = require("../lib/retry");
|
|
73
|
+
Object.defineProperty(exports, "isRateLimitError", { enumerable: true, get: function () { return retry_1.isRateLimitError; } });
|
|
74
|
+
Object.defineProperty(exports, "isServiceUnavailableError", { enumerable: true, get: function () { return retry_1.isServiceUnavailableError; } });
|
|
75
|
+
Object.defineProperty(exports, "isQuotaError", { enumerable: true, get: function () { return retry_1.isQuotaError; } });
|
|
76
|
+
Object.defineProperty(exports, "getRetryAfterMs", { enumerable: true, get: function () { return retry_1.getRetryAfterMs; } });
|
|
77
|
+
Object.defineProperty(exports, "sleep", { enumerable: true, get: function () { return retry_1.sleep; } });
|
|
77
78
|
const formatters_1 = require("../lib/formatters");
|
|
78
79
|
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
79
80
|
const spinner_1 = require("../lib/spinner");
|
|
@@ -270,11 +271,11 @@ async function resolveShortIds(ids, opts = {}) {
|
|
|
270
271
|
}
|
|
271
272
|
else {
|
|
272
273
|
// Resolve short ID
|
|
273
|
-
const listResponse = await (0, api_client_1.getSdk)().memories.listMemories({
|
|
274
|
+
const listResponse = await retryRead(() => (0, api_client_1.getSdk)().memories.listMemories({
|
|
274
275
|
limit: 10,
|
|
275
276
|
offset: 0,
|
|
276
277
|
idPrefix: id,
|
|
277
|
-
});
|
|
278
|
+
}));
|
|
278
279
|
const matches = listResponse.data?.data || [];
|
|
279
280
|
if (matches.length === 0) {
|
|
280
281
|
warn(`Warning: No memory found with prefix "${id}"`);
|
|
@@ -392,10 +393,10 @@ async function handleBatchMemoryGet(ids, options) {
|
|
|
392
393
|
}
|
|
393
394
|
spinner.text = `Fetching ${resolvedIds.length} memories...`;
|
|
394
395
|
const batchDetail = options.detail || 'minimal';
|
|
395
|
-
const response = await (0, api_client_1.getSdk)().memories.getMemoriesBatch({
|
|
396
|
+
const response = await retryRead(() => (0, api_client_1.getSdk)().memories.getMemoriesBatch({
|
|
396
397
|
ids: resolvedIds,
|
|
397
398
|
detail: batchDetail,
|
|
398
|
-
});
|
|
399
|
+
}));
|
|
399
400
|
const result = response.data;
|
|
400
401
|
if (result && result.meta && result.meta.missing && result.meta.missing.length > 0) {
|
|
401
402
|
spinner.warn(`Found ${result.meta.found} of ${result.meta.requested} memories`);
|
|
@@ -506,42 +507,46 @@ function chunkIds(ids, size = exports.BULK_DELETE_MAX_IDS) {
|
|
|
506
507
|
}
|
|
507
508
|
return chunks;
|
|
508
509
|
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
510
|
+
// ── Single-shot retry wiring (PO CLI-latency directive, item 3) ──────────────
|
|
511
|
+
// These thin wrappers apply the shared withSingleShotRetry helper at the plain
|
|
512
|
+
// single-request SDK call sites. The idempotency flag is passed PER CALL SITE —
|
|
513
|
+
// that is the crux of the retry matrix, and it cannot be inferred centrally
|
|
514
|
+
// because the same SDK method (createMemory) serves BOTH single create (retry)
|
|
515
|
+
// and the batch --stdin-jsonl path (already retries via batchCreateWithRetry).
|
|
516
|
+
// Wrapping per single-shot site is therefore the correct, non-double-wrapping
|
|
517
|
+
// chokepoint; the batch helpers are left untouched.
|
|
518
|
+
/** STDERR-only retry notice. Never touches stdout, so `--format json` stays pure. */
|
|
519
|
+
function emitRetryNotice(info) {
|
|
520
|
+
const reason = info.status === 429 ? 'rate limited' : `transient error ${info.status ?? ''}`.trim();
|
|
521
|
+
console.error(chalk_1.default.dim(`⏳ ${reason}; retry ${info.attempt}/${info.maxRetries} in ${(info.waitMs / 1000).toFixed(1)}s`));
|
|
513
522
|
}
|
|
514
523
|
/**
|
|
515
|
-
*
|
|
516
|
-
*
|
|
517
|
-
*
|
|
524
|
+
* Wrap an idempotent single-shot read (GET search/get/list/recall) or a
|
|
525
|
+
* naturally idempotent write (DELETE — deleting an already-deleted id is a
|
|
526
|
+
* no-op). Retries 429 and transient 5xx (502/503/504/408). Do NOT use this for
|
|
527
|
+
* named-memory PUT: it is a version bump that is only idempotent when
|
|
528
|
+
* expectedHeadId is supplied, and the CLI/SDK never supply it — use retryWrite.
|
|
518
529
|
*/
|
|
519
|
-
function
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
let headerVal;
|
|
527
|
-
if (headers) {
|
|
528
|
-
if (typeof headers.get === 'function') {
|
|
529
|
-
headerVal = headers.get('retry-after') ?? headers.get('Retry-After');
|
|
530
|
-
}
|
|
531
|
-
else {
|
|
532
|
-
headerVal = headers['retry-after'] ?? headers['Retry-After'];
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
if (headerVal) {
|
|
536
|
-
const n = parseInt(String(headerVal), 10);
|
|
537
|
-
if (Number.isFinite(n) && n >= 0)
|
|
538
|
-
return n * 1000;
|
|
539
|
-
}
|
|
540
|
-
return null;
|
|
530
|
+
function retryRead(fn) {
|
|
531
|
+
return (0, retry_1.withSingleShotRetry)(fn, {
|
|
532
|
+
idempotent: true,
|
|
533
|
+
sleep: retry_1.sleep,
|
|
534
|
+
maxRetries: retry_1.SINGLE_SHOT_MAX_RETRIES,
|
|
535
|
+
onRetry: emitRetryNotice,
|
|
536
|
+
});
|
|
541
537
|
}
|
|
542
|
-
/**
|
|
543
|
-
|
|
544
|
-
|
|
538
|
+
/**
|
|
539
|
+
* Wrap a non-idempotent single-shot write (plain POST create, non-CAS update).
|
|
540
|
+
* Retries ONLY 429 (server rejected before processing — no duplicate risk);
|
|
541
|
+
* a transient 5xx is NOT auto-retried because the write may already have landed.
|
|
542
|
+
*/
|
|
543
|
+
function retryWrite(fn) {
|
|
544
|
+
return (0, retry_1.withSingleShotRetry)(fn, {
|
|
545
|
+
idempotent: false,
|
|
546
|
+
sleep: retry_1.sleep,
|
|
547
|
+
maxRetries: retry_1.SINGLE_SHOT_MAX_RETRIES,
|
|
548
|
+
onRetry: emitRetryNotice,
|
|
549
|
+
});
|
|
545
550
|
}
|
|
546
551
|
/**
|
|
547
552
|
* Delete a single page, retrying on 429 up to `maxRetries` times. Honors the
|
|
@@ -556,8 +561,8 @@ async function bulkDeleteWithRetry(ids, deps) {
|
|
|
556
561
|
return await deps.deleteFn(ids);
|
|
557
562
|
}
|
|
558
563
|
catch (error) {
|
|
559
|
-
if (isRateLimitError(error) && attempt < maxRetries) {
|
|
560
|
-
const waitMs = getRetryAfterMs(error) ?? defaultRetryMs * (attempt + 1);
|
|
564
|
+
if ((0, retry_1.isRateLimitError)(error) && attempt < maxRetries) {
|
|
565
|
+
const waitMs = (0, retry_1.getRetryAfterMs)(error) ?? defaultRetryMs * (attempt + 1);
|
|
561
566
|
deps.onRateLimited?.(waitMs);
|
|
562
567
|
await deps.sleep(waitMs);
|
|
563
568
|
attempt++;
|
|
@@ -737,7 +742,7 @@ async function handleBatchDelete(rawIds, options, mode) {
|
|
|
737
742
|
try {
|
|
738
743
|
const result = await runBulkDeletePaged(resolvedIds, {
|
|
739
744
|
deleteFn: bulkDeleteViaSdk,
|
|
740
|
-
sleep,
|
|
745
|
+
sleep: retry_1.sleep,
|
|
741
746
|
onPage: (i, total) => {
|
|
742
747
|
if (total > 1 && !jsonMode) {
|
|
743
748
|
spinner.text = `Deleting page ${i + 1} of ${total}...`;
|
|
@@ -841,19 +846,9 @@ exports.BATCH_CREATE_ALLOWED_KEYS = new Set([
|
|
|
841
846
|
'outcome',
|
|
842
847
|
'distill',
|
|
843
848
|
]);
|
|
844
|
-
/** True when an error is an HTTP 503 (service unavailable — transient). */
|
|
845
|
-
function isServiceUnavailableError(error) {
|
|
846
|
-
const status = error?.status ?? error?.statusCode ?? error?.response?.status;
|
|
847
|
-
return status === 503;
|
|
848
|
-
}
|
|
849
849
|
/** True when a create should be retried: 429 (rate limited) or 503 (transient). */
|
|
850
850
|
function isRetryableCreateError(error) {
|
|
851
|
-
return isRateLimitError(error) || isServiceUnavailableError(error);
|
|
852
|
-
}
|
|
853
|
-
/** True when an error is an HTTP 402 (quota / payment required). */
|
|
854
|
-
function isQuotaError(error) {
|
|
855
|
-
const status = error?.status ?? error?.statusCode ?? error?.response?.status;
|
|
856
|
-
return status === 402;
|
|
851
|
+
return (0, retry_1.isRateLimitError)(error) || (0, retry_1.isServiceUnavailableError)(error);
|
|
857
852
|
}
|
|
858
853
|
/**
|
|
859
854
|
* Parse and validate one JSONL line into a createMemory body.
|
|
@@ -990,10 +985,10 @@ async function batchCreateWithRetry(data, deps) {
|
|
|
990
985
|
return await deps.createFn(data);
|
|
991
986
|
}
|
|
992
987
|
catch (error) {
|
|
993
|
-
if (isQuotaError(error))
|
|
988
|
+
if ((0, retry_1.isQuotaError)(error))
|
|
994
989
|
throw error;
|
|
995
990
|
if (isRetryableCreateError(error) && attempt < maxRetries) {
|
|
996
|
-
const waitMs = getRetryAfterMs(error) ?? defaultRetryMs * (attempt + 1);
|
|
991
|
+
const waitMs = (0, retry_1.getRetryAfterMs)(error) ?? defaultRetryMs * (attempt + 1);
|
|
997
992
|
deps.onRateLimited?.(waitMs);
|
|
998
993
|
await deps.sleep(waitMs);
|
|
999
994
|
attempt++;
|
|
@@ -1087,7 +1082,7 @@ async function runBatchCreate(items, deps) {
|
|
|
1087
1082
|
}
|
|
1088
1083
|
catch (error) {
|
|
1089
1084
|
summary.failed++;
|
|
1090
|
-
if (isQuotaError(error)) {
|
|
1085
|
+
if ((0, retry_1.isQuotaError)(error)) {
|
|
1091
1086
|
// Quota exhausted — pointless to keep hammering. Stop pulling new
|
|
1092
1087
|
// lines; in-flight workers finish, remaining lines are not-attempted.
|
|
1093
1088
|
stopped = true;
|
|
@@ -1253,7 +1248,7 @@ async function handleStreamingCreate(maxStdinBytes = exports.BATCH_CREATE_MAX_ST
|
|
|
1253
1248
|
const claudeSessionId = claudeSession?.sessionId || undefined;
|
|
1254
1249
|
const summary = await runBatchCreate(items, {
|
|
1255
1250
|
createFn: (data) => createOneViaSdk(claudeSessionId ? { ...data, claudeSessionId } : data),
|
|
1256
|
-
sleep,
|
|
1251
|
+
sleep: retry_1.sleep,
|
|
1257
1252
|
concurrency,
|
|
1258
1253
|
onResult: (r) => {
|
|
1259
1254
|
// Pure JSONL on stdout — the ONLY stdout write on this path. A broken pipe
|
|
@@ -1319,7 +1314,7 @@ function registerMemoriesCommands(program) {
|
|
|
1319
1314
|
// Named memories filter — fetch and filter client-side
|
|
1320
1315
|
if (options.named) {
|
|
1321
1316
|
const spinner = (0, spinner_1.createSpinner)('Fetching named memories...', options.format).start();
|
|
1322
|
-
const namedResponse = await (0, api_client_1.getSdk)().memories.listMemories({ limit: 100, offset: 0 });
|
|
1317
|
+
const namedResponse = await retryRead(() => (0, api_client_1.getSdk)().memories.listMemories({ limit: 100, offset: 0 }));
|
|
1323
1318
|
const namedMemories = (namedResponse.data?.data || []).filter((m) => m.name);
|
|
1324
1319
|
spinner.succeed(`Found ${namedMemories.length} named memories`);
|
|
1325
1320
|
if (options.format === 'table' || !options.format) {
|
|
@@ -1420,7 +1415,7 @@ function registerMemoriesCommands(program) {
|
|
|
1420
1415
|
.filter((t) => t.length > 0)
|
|
1421
1416
|
.join(',');
|
|
1422
1417
|
}
|
|
1423
|
-
const response = await (0, api_client_1.getSdk)().memories.listMemories(listParams);
|
|
1418
|
+
const response = await retryRead(() => (0, api_client_1.getSdk)().memories.listMemories(listParams));
|
|
1424
1419
|
const result = response.data;
|
|
1425
1420
|
spinner.succeed(`Found ${result?.data?.length || 0} memories`);
|
|
1426
1421
|
// Format for table display
|
|
@@ -1479,9 +1474,9 @@ function registerMemoriesCommands(program) {
|
|
|
1479
1474
|
// Named memory lookup — short-circuits the entire ID-based flow
|
|
1480
1475
|
if (options.name) {
|
|
1481
1476
|
const spinner = (0, spinner_1.createSpinner)('Fetching named memory...', options.format).start();
|
|
1482
|
-
const response = await (0, api_client_1.getSdk)().memories.getMemoryByName(options.name, {
|
|
1477
|
+
const response = await retryRead(() => (0, api_client_1.getSdk)().memories.getMemoryByName(options.name, {
|
|
1483
1478
|
detail: options.detail || 'standard',
|
|
1484
|
-
});
|
|
1479
|
+
}));
|
|
1485
1480
|
const memory = response.data?.data;
|
|
1486
1481
|
if (!memory) {
|
|
1487
1482
|
spinner.fail(`No memory found with name '${options.name}'`);
|
|
@@ -1521,9 +1516,9 @@ function registerMemoriesCommands(program) {
|
|
|
1521
1516
|
if (isNamedKey) {
|
|
1522
1517
|
const spinner = (0, spinner_1.createSpinner)('Fetching named memory...', options.format).start();
|
|
1523
1518
|
try {
|
|
1524
|
-
const response = await (0, api_client_1.getSdk)().memories.getMemoryByName(singleId, {
|
|
1519
|
+
const response = await retryRead(() => (0, api_client_1.getSdk)().memories.getMemoryByName(singleId, {
|
|
1525
1520
|
detail: options.detail || 'standard',
|
|
1526
|
-
});
|
|
1521
|
+
}));
|
|
1527
1522
|
const memory = response.data?.data;
|
|
1528
1523
|
if (!memory) {
|
|
1529
1524
|
spinner.fail(`No memory found with name '${singleId}'`);
|
|
@@ -1552,11 +1547,11 @@ function registerMemoriesCommands(program) {
|
|
|
1552
1547
|
let resolvedMemory = null;
|
|
1553
1548
|
if (isShortId) {
|
|
1554
1549
|
const spinner = (0, spinner_1.createSpinner)('Searching for matching memory...', options.format).start();
|
|
1555
|
-
const listResponse = await (0, api_client_1.getSdk)().memories.listMemories({
|
|
1550
|
+
const listResponse = await retryRead(() => (0, api_client_1.getSdk)().memories.listMemories({
|
|
1556
1551
|
limit: 10,
|
|
1557
1552
|
offset: 0,
|
|
1558
1553
|
idPrefix: singleId,
|
|
1559
|
-
});
|
|
1554
|
+
}));
|
|
1560
1555
|
const matches = listResponse.data?.data || [];
|
|
1561
1556
|
if (matches.length === 0) {
|
|
1562
1557
|
spinner.fail(`No memory found with ID prefix: ${singleId}`);
|
|
@@ -1597,9 +1592,9 @@ function registerMemoriesCommands(program) {
|
|
|
1597
1592
|
});
|
|
1598
1593
|
}
|
|
1599
1594
|
else {
|
|
1600
|
-
const response = await (0, api_client_1.getSdk)().memories.getMemoryById(memoryId, {
|
|
1595
|
+
const response = await retryRead(() => (0, api_client_1.getSdk)().memories.getMemoryById(memoryId, {
|
|
1601
1596
|
detail,
|
|
1602
|
-
});
|
|
1597
|
+
}));
|
|
1603
1598
|
const memory = response.data?.data;
|
|
1604
1599
|
printMemoryWithDetail(memory, options.format, {
|
|
1605
1600
|
includeVectors: !!options.includeVectors,
|
|
@@ -1789,7 +1784,12 @@ function registerMemoriesCommands(program) {
|
|
|
1789
1784
|
let response;
|
|
1790
1785
|
let upserted = false;
|
|
1791
1786
|
try {
|
|
1792
|
-
|
|
1787
|
+
// Single create is non-idempotent → retryWrite (429-only: a rejected
|
|
1788
|
+
// 429 never reached processing, so a retry cannot duplicate the create;
|
|
1789
|
+
// a transient 5xx is NOT retried here to avoid a duplicate). A 409
|
|
1790
|
+
// (named already exists) is not retryable and falls through to the
|
|
1791
|
+
// auto-upsert branch below unchanged.
|
|
1792
|
+
response = await retryWrite(() => (0, api_client_1.getSdk)().memories.createMemory(memoryData));
|
|
1793
1793
|
}
|
|
1794
1794
|
catch (createError) {
|
|
1795
1795
|
// 409 = named memory already exists → auto-upsert via updateNamedMemory
|
|
@@ -1811,7 +1811,15 @@ function registerMemoriesCommands(program) {
|
|
|
1811
1811
|
// intentionally NOT forwarded here: it is applied uniformly for both the
|
|
1812
1812
|
// create and upsert paths by the createMemoryRelationship call below, so
|
|
1813
1813
|
// forwarding it in the body too would create a duplicate SUPERSEDES edge.
|
|
1814
|
-
|
|
1814
|
+
// Named update is NOT idempotent on this path: the CLI/SDK never
|
|
1815
|
+
// supply expectedHeadId, so PUT /named/:name does an UNCONDITIONAL
|
|
1816
|
+
// version bump (buildNamedVersionBumpCypher). retryWrite → 429-only:
|
|
1817
|
+
// the named-write gate acquires the permit BEFORE this.write, so a
|
|
1818
|
+
// 429 is pre-write and safe to retry; a 5xx is ambiguous (the bump
|
|
1819
|
+
// may already have committed — e.g. Kong 504 after Neo4j commits)
|
|
1820
|
+
// and MUST NOT be retried, or it silently creates a second identical
|
|
1821
|
+
// version (memory-history corruption).
|
|
1822
|
+
response = await retryWrite(() => (0, api_client_1.getSdk)().memories.updateNamedMemory(memoryData.name, {
|
|
1815
1823
|
content: memoryData.content,
|
|
1816
1824
|
...(options.memoryType && { memoryType: options.memoryType }),
|
|
1817
1825
|
...(memoryData.topics && { topics: memoryData.topics }),
|
|
@@ -1825,7 +1833,7 @@ function registerMemoriesCommands(program) {
|
|
|
1825
1833
|
options.conversationId.toUpperCase() !== 'NEW' && {
|
|
1826
1834
|
conversationId: options.conversationId,
|
|
1827
1835
|
}),
|
|
1828
|
-
});
|
|
1836
|
+
}));
|
|
1829
1837
|
upserted = true;
|
|
1830
1838
|
}
|
|
1831
1839
|
else {
|
|
@@ -1839,7 +1847,9 @@ function registerMemoriesCommands(program) {
|
|
|
1839
1847
|
const version = meta?.version || 'new';
|
|
1840
1848
|
spinner.succeed(`Named memory '${memoryData.name}' updated (v${version}) via auto-upsert. ID: ${memory?.id}`);
|
|
1841
1849
|
if (meta?.previousVersionId) {
|
|
1842
|
-
|
|
1850
|
+
if (options.format !== 'json' && options.format !== 'yaml') {
|
|
1851
|
+
console.log(chalk_1.default.gray(`Previous version: ${meta.previousVersionId}`));
|
|
1852
|
+
}
|
|
1843
1853
|
}
|
|
1844
1854
|
}
|
|
1845
1855
|
else {
|
|
@@ -1848,7 +1858,9 @@ function registerMemoriesCommands(program) {
|
|
|
1848
1858
|
: 'Memory created with';
|
|
1849
1859
|
if (isNewConversation && meta?.conversationCreated) {
|
|
1850
1860
|
spinner.succeed(`New conversation created: ${meta.conversationId}\n${nameLabel} ID: ${memory?.id}`);
|
|
1851
|
-
|
|
1861
|
+
if (options.format !== 'json' && options.format !== 'yaml') {
|
|
1862
|
+
console.log(chalk_1.default.gray(`\nSession ID: ${meta.sessionId}`));
|
|
1863
|
+
}
|
|
1852
1864
|
}
|
|
1853
1865
|
else {
|
|
1854
1866
|
spinner.succeed(`${nameLabel} ID: ${memory?.id}`);
|
|
@@ -1858,18 +1870,20 @@ function registerMemoriesCommands(program) {
|
|
|
1858
1870
|
if (options.supersedes && memory?.id) {
|
|
1859
1871
|
const relSpinner = (0, spinner_1.createSpinner)('Creating SUPERSEDES relationship...', options.format).start();
|
|
1860
1872
|
try {
|
|
1861
|
-
await (0, api_client_1.getSdk)().memories.createMemoryRelationship(memory.id, {
|
|
1873
|
+
await retryWrite(() => (0, api_client_1.getSdk)().memories.createMemoryRelationship(memory.id, {
|
|
1862
1874
|
targetMemoryId: options.supersedes,
|
|
1863
1875
|
type: 'SUPERSEDES',
|
|
1864
1876
|
confidence: 1.0,
|
|
1865
1877
|
reason: 'Created via --supersedes flag',
|
|
1866
|
-
});
|
|
1878
|
+
}));
|
|
1867
1879
|
relSpinner.succeed(`Relationship created: ${memory.id.substring(0, 8)}... SUPERSEDES ${options.supersedes.substring(0, 8)}...`);
|
|
1868
1880
|
}
|
|
1869
1881
|
catch (relError) {
|
|
1870
1882
|
relSpinner.fail(`Failed to create relationship: ${relError.message || 'Unknown error'}`);
|
|
1871
|
-
|
|
1872
|
-
|
|
1883
|
+
if (options.format !== 'json' && options.format !== 'yaml') {
|
|
1884
|
+
console.log(chalk_1.default.yellow('Memory was created successfully. You can manually create the relationship with:'));
|
|
1885
|
+
console.log(chalk_1.default.cyan(` mx memories relationships create ${memory.id} --target ${options.supersedes} --type SUPERSEDES`));
|
|
1886
|
+
}
|
|
1873
1887
|
}
|
|
1874
1888
|
}
|
|
1875
1889
|
(0, formatters_1.printOutput)(memory, options.format);
|
|
@@ -1928,7 +1942,7 @@ function registerMemoriesCommands(program) {
|
|
|
1928
1942
|
}
|
|
1929
1943
|
updates.name = options.assignName;
|
|
1930
1944
|
const spinner = (0, spinner_1.createSpinner)('Assigning name...', options.format).start();
|
|
1931
|
-
const response = await (0, api_client_1.getSdk)().memories.updateMemory(id, updates);
|
|
1945
|
+
const response = await retryWrite(() => (0, api_client_1.getSdk)().memories.updateMemory(id, updates));
|
|
1932
1946
|
const memory = response.data?.data;
|
|
1933
1947
|
spinner.succeed(`Memory ${id} named '${options.assignName}'`);
|
|
1934
1948
|
(0, formatters_1.printOutput)(memory, options.format);
|
|
@@ -1939,15 +1953,15 @@ function registerMemoriesCommands(program) {
|
|
|
1939
1953
|
if (options.name) {
|
|
1940
1954
|
// Look up current memory by name, then update its name
|
|
1941
1955
|
const spinner = (0, spinner_1.createSpinner)('Renaming...', options.format).start();
|
|
1942
|
-
const getResponse = await (0, api_client_1.getSdk)().memories.getMemoryByName(options.name);
|
|
1956
|
+
const getResponse = await retryRead(() => (0, api_client_1.getSdk)().memories.getMemoryByName(options.name));
|
|
1943
1957
|
const currentMemory = getResponse.data?.data;
|
|
1944
1958
|
if (!currentMemory?.id) {
|
|
1945
1959
|
spinner.fail(`Named memory '${options.name}' not found`);
|
|
1946
1960
|
return;
|
|
1947
1961
|
}
|
|
1948
|
-
const response = await (0, api_client_1.getSdk)().memories.updateMemory(currentMemory.id, {
|
|
1962
|
+
const response = await retryWrite(() => (0, api_client_1.getSdk)().memories.updateMemory(currentMemory.id, {
|
|
1949
1963
|
name: options.rename,
|
|
1950
|
-
});
|
|
1964
|
+
}));
|
|
1951
1965
|
const memory = response.data?.data;
|
|
1952
1966
|
spinner.succeed(`Named memory '${options.name}' renamed to '${options.rename}'`);
|
|
1953
1967
|
(0, formatters_1.printOutput)(memory, options.format);
|
|
@@ -1956,7 +1970,7 @@ function registerMemoriesCommands(program) {
|
|
|
1956
1970
|
if (id) {
|
|
1957
1971
|
updates.name = options.rename;
|
|
1958
1972
|
const spinner = (0, spinner_1.createSpinner)('Renaming...', options.format).start();
|
|
1959
|
-
const response = await (0, api_client_1.getSdk)().memories.updateMemory(id, updates);
|
|
1973
|
+
const response = await retryWrite(() => (0, api_client_1.getSdk)().memories.updateMemory(id, updates));
|
|
1960
1974
|
const memory = response.data?.data;
|
|
1961
1975
|
spinner.succeed(`Memory ${id} renamed to '${options.rename}'`);
|
|
1962
1976
|
(0, formatters_1.printOutput)(memory, options.format);
|
|
@@ -1972,18 +1986,26 @@ function registerMemoriesCommands(program) {
|
|
|
1972
1986
|
// Named memory update — creates new version
|
|
1973
1987
|
if (options.name) {
|
|
1974
1988
|
const spinner = (0, spinner_1.createSpinner)('Creating new version...', options.format).start();
|
|
1975
|
-
|
|
1989
|
+
// Named update is NOT idempotent without expectedHeadId (which the
|
|
1990
|
+
// CLI/SDK never supply): PUT /named/:name unconditionally bumps the
|
|
1991
|
+
// version. retryWrite → 429-only: the gate takes the permit before the
|
|
1992
|
+
// write, so a 429 is pre-write and safe to retry; a 5xx is ambiguous
|
|
1993
|
+
// (the bump may already have committed) and MUST NOT be retried, or it
|
|
1994
|
+
// creates a second identical version (silent history corruption).
|
|
1995
|
+
const response = await retryWrite(() => (0, api_client_1.getSdk)().memories.updateNamedMemory(options.name, updates));
|
|
1976
1996
|
const memory = response.data?.data;
|
|
1977
1997
|
const meta = response.data?.meta;
|
|
1978
1998
|
spinner.succeed(`Named memory '${options.name}' updated to v${meta?.version}`);
|
|
1979
1999
|
if (meta?.previousVersionId) {
|
|
1980
|
-
|
|
2000
|
+
if (options.format !== 'json' && options.format !== 'yaml') {
|
|
2001
|
+
console.log(chalk_1.default.gray(`Previous version: ${meta.previousVersionId}`));
|
|
2002
|
+
}
|
|
1981
2003
|
}
|
|
1982
2004
|
(0, formatters_1.printOutput)(memory, options.format);
|
|
1983
2005
|
return;
|
|
1984
2006
|
}
|
|
1985
2007
|
const spinner = (0, spinner_1.createSpinner)('Updating memory...', options.format).start();
|
|
1986
|
-
const response = await (0, api_client_1.getSdk)().memories.updateMemory(id, updates);
|
|
2008
|
+
const response = await retryWrite(() => (0, api_client_1.getSdk)().memories.updateMemory(id, updates));
|
|
1987
2009
|
const memory = response.data?.data;
|
|
1988
2010
|
spinner.succeed(`Memory ${id} updated successfully`);
|
|
1989
2011
|
(0, formatters_1.printOutput)(memory, options.format);
|
|
@@ -2004,7 +2026,7 @@ function registerMemoriesCommands(program) {
|
|
|
2004
2026
|
.action(async (name, options) => {
|
|
2005
2027
|
try {
|
|
2006
2028
|
const spinner = (0, spinner_1.createSpinner)('Fetching version history...', options.format).start();
|
|
2007
|
-
const response = await (0, api_client_1.getSdk)().memories.getNamedMemoryHistory(name);
|
|
2029
|
+
const response = await retryRead(() => (0, api_client_1.getSdk)().memories.getNamedMemoryHistory(name));
|
|
2008
2030
|
const data = response.data;
|
|
2009
2031
|
if (!data?.versions?.length) {
|
|
2010
2032
|
spinner.fail(`No memory found with name '${name}'`);
|
|
@@ -2205,7 +2227,9 @@ function registerMemoriesCommands(program) {
|
|
|
2205
2227
|
}
|
|
2206
2228
|
}
|
|
2207
2229
|
const spinner = (0, spinner_1.createSpinner)('Deleting memory...', 'table').start();
|
|
2208
|
-
|
|
2230
|
+
// DELETE is idempotent (deleting an already-deleted id is a no-op), so a
|
|
2231
|
+
// transient 5xx is retry-safe as well as a 429.
|
|
2232
|
+
await retryRead(() => (0, api_client_1.getSdk)().memories.deleteMemory(id));
|
|
2209
2233
|
// Record this deletion in the cross-process guard file so the next
|
|
2210
2234
|
// invocation's threshold check reflects it.
|
|
2211
2235
|
if (isNonInteractive) {
|
|
@@ -2426,7 +2450,7 @@ function registerMemoriesCommands(program) {
|
|
|
2426
2450
|
searchRequest.order = options.order;
|
|
2427
2451
|
}
|
|
2428
2452
|
// Use GET endpoint for better performance (Issue #79)
|
|
2429
|
-
const response = await (0, api_client_1.getSdk)().memories.searchMemoriesGet(searchRequest);
|
|
2453
|
+
const response = await retryRead(() => (0, api_client_1.getSdk)().memories.searchMemoriesGet(searchRequest));
|
|
2430
2454
|
const searchData = response.data;
|
|
2431
2455
|
// Normalize: API may return flat objects or {memory, score} wrappers.
|
|
2432
2456
|
// Ensure consistent {memory, score, matchedVia} shape for display code.
|
|
@@ -2844,19 +2868,19 @@ async function handleMemoryDiscovery(memoryId, options) {
|
|
|
2844
2868
|
const sdk = (0, api_client_1.getSdk)();
|
|
2845
2869
|
if (options.similar) {
|
|
2846
2870
|
discoveryType = 'similar';
|
|
2847
|
-
const response = await sdk.memories.getSimilarMemories(memoryId, { limit: options.limit });
|
|
2871
|
+
const response = await retryRead(() => sdk.memories.getSimilarMemories(memoryId, { limit: options.limit }));
|
|
2848
2872
|
result = response.data;
|
|
2849
2873
|
}
|
|
2850
2874
|
else if (options.conversation) {
|
|
2851
2875
|
discoveryType = 'conversation';
|
|
2852
|
-
const response = await sdk.memories.getConversationMemories(memoryId, {
|
|
2876
|
+
const response = await retryRead(() => sdk.memories.getConversationMemories(memoryId, {
|
|
2853
2877
|
limit: options.limit,
|
|
2854
|
-
});
|
|
2878
|
+
}));
|
|
2855
2879
|
result = response.data;
|
|
2856
2880
|
}
|
|
2857
2881
|
else if (options.related) {
|
|
2858
2882
|
discoveryType = 'related';
|
|
2859
|
-
const response = await sdk.memories.getRelatedMemories(memoryId, { limit: options.limit });
|
|
2883
|
+
const response = await retryRead(() => sdk.memories.getRelatedMemories(memoryId, { limit: options.limit }));
|
|
2860
2884
|
result = response.data;
|
|
2861
2885
|
}
|
|
2862
2886
|
else {
|
|
@@ -2957,9 +2981,9 @@ function registerMemoryRelationshipCommands(memories) {
|
|
|
2957
2981
|
return;
|
|
2958
2982
|
}
|
|
2959
2983
|
const spinner = (0, spinner_1.createSpinner)('Fetching relationships...', options.format).start();
|
|
2960
|
-
const response = await (0, api_client_1.getSdk)().memories.getMemoryRelationships(id, {
|
|
2984
|
+
const response = await retryRead(() => (0, api_client_1.getSdk)().memories.getMemoryRelationships(id, {
|
|
2961
2985
|
direction: options.direction,
|
|
2962
|
-
});
|
|
2986
|
+
}));
|
|
2963
2987
|
const result = response.data;
|
|
2964
2988
|
spinner.succeed(`Found ${result?.data?.length || 0} relationships`);
|
|
2965
2989
|
if (options.format === 'table' || !options.format) {
|
|
@@ -3065,7 +3089,7 @@ function registerMemoryRelationshipCommands(memories) {
|
|
|
3065
3089
|
};
|
|
3066
3090
|
}
|
|
3067
3091
|
const spinner = (0, spinner_1.createSpinner)('Creating relationship...', options.format).start();
|
|
3068
|
-
const response = await (0, api_client_1.getSdk)().memories.createMemoryRelationship(id, relationshipData);
|
|
3092
|
+
const response = await retryWrite(() => (0, api_client_1.getSdk)().memories.createMemoryRelationship(id, relationshipData));
|
|
3069
3093
|
const relationship = response.data?.data;
|
|
3070
3094
|
spinner.succeed(`Relationship created: ${id} ${relationship?.type} ${relationshipData.targetMemoryId}`);
|
|
3071
3095
|
(0, formatters_1.printOutput)(relationship, options.format);
|
|
@@ -3108,7 +3132,7 @@ function registerMemoryRelationshipCommands(memories) {
|
|
|
3108
3132
|
}
|
|
3109
3133
|
// Non-interactive without --force: auto-confirm single delete
|
|
3110
3134
|
const spinner = (0, spinner_1.createSpinner)('Deleting relationship...', 'table').start();
|
|
3111
|
-
await (0, api_client_1.getSdk)().memories.deleteMemoryRelationship(id, relationshipId);
|
|
3135
|
+
await retryRead(() => (0, api_client_1.getSdk)().memories.deleteMemoryRelationship(id, relationshipId));
|
|
3112
3136
|
spinner.succeed(`Relationship ${relationshipId} deleted successfully`);
|
|
3113
3137
|
}
|
|
3114
3138
|
catch (error) {
|
|
@@ -3136,7 +3160,7 @@ function registerMemoryTimelineCommand(memories) {
|
|
|
3136
3160
|
const before = parseInt(options.before, 10);
|
|
3137
3161
|
const after = parseInt(options.after, 10);
|
|
3138
3162
|
const spinner = (0, spinner_1.createSpinner)('Fetching timeline context...', options.format).start();
|
|
3139
|
-
const response = await (0, api_client_1.getSdk)().memories.getMemoryTimeline(id, { before, after });
|
|
3163
|
+
const response = await retryRead(() => (0, api_client_1.getSdk)().memories.getMemoryTimeline(id, { before, after }));
|
|
3140
3164
|
const result = response.data;
|
|
3141
3165
|
spinner.succeed('Timeline context retrieved');
|
|
3142
3166
|
if (options.format === 'table' || !options.format) {
|
|
@@ -3199,12 +3223,14 @@ function registerDetectRelationshipsCommand(memories) {
|
|
|
3199
3223
|
const spinner = (0, spinner_1.createSpinner)(options.autoCreate
|
|
3200
3224
|
? 'Detecting and creating relationships...'
|
|
3201
3225
|
: 'Detecting relationships...', options.format).start();
|
|
3202
|
-
|
|
3226
|
+
// detect with --auto-create writes relationship edges, so it is
|
|
3227
|
+
// non-idempotent → retryWrite (429-only) to avoid duplicate edges on 5xx.
|
|
3228
|
+
const response = await retryWrite(() => (0, api_client_1.getSdk)().memories.detectMemoryRelationships(id, {
|
|
3203
3229
|
body: {
|
|
3204
3230
|
autoCreate: options.autoCreate || false,
|
|
3205
3231
|
minConfidence,
|
|
3206
3232
|
},
|
|
3207
|
-
});
|
|
3233
|
+
}));
|
|
3208
3234
|
const result = response.data;
|
|
3209
3235
|
const suggestions = result?.data || [];
|
|
3210
3236
|
spinner.succeed(`Found ${suggestions.length} potential relationships`);
|
|
@@ -3344,7 +3370,7 @@ function registerDigestCommand(memories) {
|
|
|
3344
3370
|
sdk.config.timeoutMs = RECALL_MAX_MS;
|
|
3345
3371
|
let response;
|
|
3346
3372
|
try {
|
|
3347
|
-
response = await sdk.memories.generateMemoryDigest(body);
|
|
3373
|
+
response = await retryRead(() => sdk.memories.generateMemoryDigest(body));
|
|
3348
3374
|
}
|
|
3349
3375
|
finally {
|
|
3350
3376
|
if (sdk.config)
|
|
@@ -3433,7 +3459,7 @@ function registerStatusCommand(memories) {
|
|
|
3433
3459
|
sdk.config.timeoutMs = 180000;
|
|
3434
3460
|
let response;
|
|
3435
3461
|
try {
|
|
3436
|
-
response = await sdk.memories.generateMemoryDigest(body);
|
|
3462
|
+
response = await retryRead(() => sdk.memories.generateMemoryDigest(body));
|
|
3437
3463
|
}
|
|
3438
3464
|
finally {
|
|
3439
3465
|
if (sdk.config)
|
|
@@ -3579,7 +3605,7 @@ function registerRecapCommand(memories) {
|
|
|
3579
3605
|
};
|
|
3580
3606
|
if (excludeTopicsParam)
|
|
3581
3607
|
searchRequest.excludeTopics = excludeTopicsParam;
|
|
3582
|
-
const response = await (0, api_client_1.getSdk)().memories.searchMemoriesGet(searchRequest);
|
|
3608
|
+
const response = await retryRead(() => (0, api_client_1.getSdk)().memories.searchMemoriesGet(searchRequest));
|
|
3583
3609
|
// Normalize: API may return flat objects or {memory, score} wrappers.
|
|
3584
3610
|
results = (response.data?.data || []).map((r) => r.memory ? r : { memory: r, score: r.score, matchedVia: r.matchedVia });
|
|
3585
3611
|
}
|
|
@@ -3595,7 +3621,7 @@ function registerRecapCommand(memories) {
|
|
|
3595
3621
|
};
|
|
3596
3622
|
if (excludeTopicsParam)
|
|
3597
3623
|
listRequest.excludeTopics = excludeTopicsParam;
|
|
3598
|
-
const response = await (0, api_client_1.getSdk)().memories.listMemories(listRequest);
|
|
3624
|
+
const response = await retryRead(() => (0, api_client_1.getSdk)().memories.listMemories(listRequest));
|
|
3599
3625
|
// Wrap in search-result shape expected by groupByConversation / displayRecap
|
|
3600
3626
|
results = (response.data?.data || []).map((m) => ({ memory: m }));
|
|
3601
3627
|
}
|
|
@@ -3833,7 +3859,7 @@ function registerBuildContextCommand(memories) {
|
|
|
3833
3859
|
body.files = options.files;
|
|
3834
3860
|
}
|
|
3835
3861
|
const sdk = (0, api_client_1.getSdk)();
|
|
3836
|
-
const response = await sdk.memories.buildContext(body);
|
|
3862
|
+
const response = await retryRead(() => sdk.memories.buildContext(body));
|
|
3837
3863
|
const data = response?.data || response;
|
|
3838
3864
|
if (!data) {
|
|
3839
3865
|
spinner.fail('No response data received');
|