mnemospark 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -0
- package/dist/cli.js +626 -241
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +39 -0
- package/dist/index.js +626 -241
- package/dist/index.js.map +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/skills/mnemospark/references/commands.md +13 -6
- package/skills/mnemospark/references/state-and-logs.md +5 -0
package/dist/cli.js
CHANGED
|
@@ -746,9 +746,14 @@ async function forwardPaymentSettleToBackend(quoteId, walletAddress, options = {
|
|
|
746
746
|
}
|
|
747
747
|
const targetUrl = `${normalizeBaseUrl(backendBaseUrl)}/payment/settle`;
|
|
748
748
|
const requestBody = {
|
|
749
|
-
quote_id: quoteId,
|
|
750
749
|
wallet_address: walletAddress
|
|
751
750
|
};
|
|
751
|
+
if (options.renewal === true && options.objectKey?.trim()) {
|
|
752
|
+
requestBody.renewal = true;
|
|
753
|
+
requestBody.object_key = options.objectKey.trim();
|
|
754
|
+
} else {
|
|
755
|
+
requestBody.quote_id = quoteId;
|
|
756
|
+
}
|
|
752
757
|
if (options.payment) {
|
|
753
758
|
requestBody.payment = options.payment;
|
|
754
759
|
}
|
|
@@ -775,9 +780,14 @@ async function requestPaymentSettleViaProxy(quoteId, walletAddress, options = {}
|
|
|
775
780
|
);
|
|
776
781
|
const targetUrl = `${baseUrl}${PAYMENT_SETTLE_PROXY_PATH}`;
|
|
777
782
|
const requestBody = {
|
|
778
|
-
quote_id: quoteId,
|
|
779
783
|
wallet_address: walletAddress
|
|
780
784
|
};
|
|
785
|
+
if (options.renewal === true && options.objectKey?.trim()) {
|
|
786
|
+
requestBody.renewal = true;
|
|
787
|
+
requestBody.object_key = options.objectKey.trim();
|
|
788
|
+
} else {
|
|
789
|
+
requestBody.quote_id = quoteId;
|
|
790
|
+
}
|
|
781
791
|
if (options.payment) {
|
|
782
792
|
requestBody.payment = options.payment;
|
|
783
793
|
}
|
|
@@ -1199,6 +1209,23 @@ function parseJsonText(text, errorMessage) {
|
|
|
1199
1209
|
}
|
|
1200
1210
|
return record;
|
|
1201
1211
|
}
|
|
1212
|
+
var MAX_LOCAL_FRIENDLY_BASENAME_LEN = 200;
|
|
1213
|
+
function sanitizeFriendlyNameForLocalBasename(raw) {
|
|
1214
|
+
const normalized = raw.replace(/\\/g, "/").trim();
|
|
1215
|
+
if (!normalized) {
|
|
1216
|
+
throw new Error("Friendly name is empty");
|
|
1217
|
+
}
|
|
1218
|
+
const segments = normalized.split("/").filter((segment2) => segment2.length > 0 && segment2 !== "." && segment2 !== "..");
|
|
1219
|
+
const segment = segments[segments.length - 1] ?? "";
|
|
1220
|
+
if (!segment || segment === "." || segment === "..") {
|
|
1221
|
+
throw new Error("Invalid friendly name for local file path");
|
|
1222
|
+
}
|
|
1223
|
+
const noCtrl = segment.replace(/[\x00-\x1f]/g, "").trim();
|
|
1224
|
+
if (!noCtrl || noCtrl === "." || noCtrl === "..") {
|
|
1225
|
+
throw new Error("Invalid friendly name for local file path");
|
|
1226
|
+
}
|
|
1227
|
+
return noCtrl.length > MAX_LOCAL_FRIENDLY_BASENAME_LEN ? noCtrl.slice(0, MAX_LOCAL_FRIENDLY_BASENAME_LEN) : noCtrl;
|
|
1228
|
+
}
|
|
1202
1229
|
function sanitizeObjectKeyToRelativePath(objectKey) {
|
|
1203
1230
|
const normalized = objectKey.replace(/\\/g, "/").trim().replace(/^\/+/, "");
|
|
1204
1231
|
const segments = normalized.split("/").filter((segment) => segment.length > 0 && segment !== "." && segment !== "..");
|
|
@@ -1342,6 +1369,27 @@ function parseStorageObjectRequest(payload) {
|
|
|
1342
1369
|
location
|
|
1343
1370
|
};
|
|
1344
1371
|
}
|
|
1372
|
+
function parseProxyStorageDownloadPayload(payload) {
|
|
1373
|
+
const record = asRecord(payload);
|
|
1374
|
+
if (!record) {
|
|
1375
|
+
return null;
|
|
1376
|
+
}
|
|
1377
|
+
const walletAddress = asNonEmptyString(record.wallet_address);
|
|
1378
|
+
const objectKey = asNonEmptyString(record.object_key);
|
|
1379
|
+
const location = asNonEmptyString(record.location) ?? void 0;
|
|
1380
|
+
if (!walletAddress || !objectKey) {
|
|
1381
|
+
return null;
|
|
1382
|
+
}
|
|
1383
|
+
const localRaw = asNonEmptyString(record.mnemospark_local_filename) ?? void 0;
|
|
1384
|
+
return {
|
|
1385
|
+
request: {
|
|
1386
|
+
wallet_address: walletAddress,
|
|
1387
|
+
object_key: objectKey,
|
|
1388
|
+
...location ? { location } : {}
|
|
1389
|
+
},
|
|
1390
|
+
...localRaw ? { localBasename: localRaw } : {}
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1345
1393
|
function jsonBodyForObjectRequest(request) {
|
|
1346
1394
|
const o = {
|
|
1347
1395
|
wallet_address: request.wallet_address,
|
|
@@ -1352,6 +1400,14 @@ function jsonBodyForObjectRequest(request) {
|
|
|
1352
1400
|
}
|
|
1353
1401
|
return o;
|
|
1354
1402
|
}
|
|
1403
|
+
function jsonBodyForProxyDownloadRequest(request, downloadLocalBasename) {
|
|
1404
|
+
const body = jsonBodyForObjectRequest(request);
|
|
1405
|
+
const trimmed = downloadLocalBasename?.trim();
|
|
1406
|
+
if (trimmed) {
|
|
1407
|
+
body.mnemospark_local_filename = trimmed;
|
|
1408
|
+
}
|
|
1409
|
+
return body;
|
|
1410
|
+
}
|
|
1355
1411
|
function jsonBodyForLsRequest(request) {
|
|
1356
1412
|
const o = { wallet_address: request.wallet_address };
|
|
1357
1413
|
if (request.object_key) {
|
|
@@ -1497,7 +1553,7 @@ async function requestStorageLsViaProxy(request, options = {}) {
|
|
|
1497
1553
|
async function requestStorageDownloadViaProxy(request, options = {}) {
|
|
1498
1554
|
return requestJsonViaProxy(
|
|
1499
1555
|
STORAGE_DOWNLOAD_PROXY_PATH,
|
|
1500
|
-
|
|
1556
|
+
jsonBodyForProxyDownloadRequest(request, options.downloadLocalBasename),
|
|
1501
1557
|
parseStorageDownloadProxyResponse,
|
|
1502
1558
|
options
|
|
1503
1559
|
);
|
|
@@ -1584,7 +1640,8 @@ async function downloadStorageToDisk(request, backendResponse, options = {}) {
|
|
|
1584
1640
|
objectKey = filenameFromHeader;
|
|
1585
1641
|
}
|
|
1586
1642
|
}
|
|
1587
|
-
const
|
|
1643
|
+
const pathKey = options.localOutputBasename?.trim() && options.localOutputBasename.trim().length > 0 ? options.localOutputBasename.trim() : objectKey;
|
|
1644
|
+
const filePath = resolveDownloadPath(outputDir, pathKey);
|
|
1588
1645
|
await mkdir(dirname(filePath), { recursive: true });
|
|
1589
1646
|
await writeFile(filePath, bytes);
|
|
1590
1647
|
return {
|
|
@@ -1949,16 +2006,47 @@ async function startProxy(options) {
|
|
|
1949
2006
|
return;
|
|
1950
2007
|
}
|
|
1951
2008
|
const record = payload && typeof payload === "object" ? payload : null;
|
|
1952
|
-
const quoteId = typeof record?.quote_id === "string" ? record.quote_id.trim() : "";
|
|
1953
2009
|
const walletAddress = typeof record?.wallet_address === "string" ? record.wallet_address.trim() : "";
|
|
2010
|
+
const isRenewal = record?.renewal === true;
|
|
2011
|
+
const objectKey = typeof record?.object_key === "string" ? record.object_key.trim() : "";
|
|
2012
|
+
let quoteId = typeof record?.quote_id === "string" ? record.quote_id.trim() : "";
|
|
1954
2013
|
const inlinePayment = record?.payment;
|
|
1955
2014
|
const inlinePaymentAuthorization = record?.payment_authorization;
|
|
1956
|
-
if (!
|
|
2015
|
+
if (!walletAddress) {
|
|
1957
2016
|
logProxyEvent("warn", "proxy_payment_settle_missing_fields");
|
|
1958
2017
|
emitProxyTerminalFromStatus(correlation, 400, { reason: "missing_fields" });
|
|
1959
2018
|
sendJson(res, 400, {
|
|
1960
2019
|
error: "Bad request",
|
|
1961
|
-
message: "Missing required
|
|
2020
|
+
message: "Missing required field: wallet_address"
|
|
2021
|
+
});
|
|
2022
|
+
return;
|
|
2023
|
+
}
|
|
2024
|
+
if (isRenewal) {
|
|
2025
|
+
if (quoteId) {
|
|
2026
|
+
logProxyEvent("warn", "proxy_payment_settle_renewal_with_quote_id");
|
|
2027
|
+
emitProxyTerminalFromStatus(correlation, 400, { reason: "renewal_with_quote_id" });
|
|
2028
|
+
sendJson(res, 400, {
|
|
2029
|
+
error: "Bad request",
|
|
2030
|
+
message: "renewal requests must not include quote_id"
|
|
2031
|
+
});
|
|
2032
|
+
return;
|
|
2033
|
+
}
|
|
2034
|
+
if (!objectKey) {
|
|
2035
|
+
logProxyEvent("warn", "proxy_payment_settle_missing_fields");
|
|
2036
|
+
emitProxyTerminalFromStatus(correlation, 400, { reason: "missing_fields" });
|
|
2037
|
+
sendJson(res, 400, {
|
|
2038
|
+
error: "Bad request",
|
|
2039
|
+
message: "Missing required field: object_key (renewal mode)"
|
|
2040
|
+
});
|
|
2041
|
+
return;
|
|
2042
|
+
}
|
|
2043
|
+
quoteId = `renewal:${objectKey}`;
|
|
2044
|
+
} else if (!quoteId) {
|
|
2045
|
+
logProxyEvent("warn", "proxy_payment_settle_missing_fields");
|
|
2046
|
+
emitProxyTerminalFromStatus(correlation, 400, { reason: "missing_fields" });
|
|
2047
|
+
sendJson(res, 400, {
|
|
2048
|
+
error: "Bad request",
|
|
2049
|
+
message: "Missing required field: quote_id (or use renewal: true with object_key)"
|
|
1962
2050
|
});
|
|
1963
2051
|
return;
|
|
1964
2052
|
}
|
|
@@ -2017,7 +2105,9 @@ async function startProxy(options) {
|
|
|
2017
2105
|
paymentSignature: readHeaderValue(req.headers["payment-signature"]),
|
|
2018
2106
|
legacyPayment: readHeaderValue(req.headers["x-payment"]),
|
|
2019
2107
|
payment: inlinePayment && typeof inlinePayment === "object" && !Array.isArray(inlinePayment) ? inlinePayment : void 0,
|
|
2020
|
-
paymentAuthorization: typeof inlinePaymentAuthorization === "string" ? inlinePaymentAuthorization.trim() || void 0 : inlinePaymentAuthorization !== void 0 ? inlinePaymentAuthorization : void 0
|
|
2108
|
+
paymentAuthorization: typeof inlinePaymentAuthorization === "string" ? inlinePaymentAuthorization.trim() || void 0 : inlinePaymentAuthorization !== void 0 ? inlinePaymentAuthorization : void 0,
|
|
2109
|
+
renewal: isRenewal,
|
|
2110
|
+
objectKey: isRenewal ? objectKey : void 0
|
|
2021
2111
|
});
|
|
2022
2112
|
logProxyEvent("info", "proxy_payment_settle_backend_response", {
|
|
2023
2113
|
status: backendResponse.status
|
|
@@ -2457,8 +2547,8 @@ async function startProxy(options) {
|
|
|
2457
2547
|
});
|
|
2458
2548
|
return;
|
|
2459
2549
|
}
|
|
2460
|
-
const
|
|
2461
|
-
if (!
|
|
2550
|
+
const parsedDownload = parseProxyStorageDownloadPayload(payload);
|
|
2551
|
+
if (!parsedDownload) {
|
|
2462
2552
|
logProxyEvent("warn", "proxy_download_missing_fields");
|
|
2463
2553
|
emitProxyTerminalFromStatus(correlation, 400, { reason: "missing_fields" });
|
|
2464
2554
|
sendJson(res, 400, {
|
|
@@ -2467,6 +2557,8 @@ async function startProxy(options) {
|
|
|
2467
2557
|
});
|
|
2468
2558
|
return;
|
|
2469
2559
|
}
|
|
2560
|
+
const requestPayload = parsedDownload.request;
|
|
2561
|
+
const downloadLocalBasename = parsedDownload.localBasename;
|
|
2470
2562
|
correlation.wallet_address = requestPayload.wallet_address;
|
|
2471
2563
|
correlation.object_key = requestPayload.object_key;
|
|
2472
2564
|
if (requestPayload.wallet_address.toLowerCase() !== proxyWalletAddressLower) {
|
|
@@ -2528,7 +2620,8 @@ async function startProxy(options) {
|
|
|
2528
2620
|
return;
|
|
2529
2621
|
}
|
|
2530
2622
|
const downloadResult = await downloadStorageToDisk(requestPayload, backendResponse, {
|
|
2531
|
-
outputDir: resolveDownloadOutputDir()
|
|
2623
|
+
outputDir: resolveDownloadOutputDir(),
|
|
2624
|
+
...downloadLocalBasename?.trim() ? { localOutputBasename: downloadLocalBasename.trim() } : {}
|
|
2532
2625
|
});
|
|
2533
2626
|
logProxyEvent("info", "proxy_download_written_to_disk", {
|
|
2534
2627
|
key: downloadResult.key,
|
|
@@ -2881,7 +2974,7 @@ import {
|
|
|
2881
2974
|
randomUUID as randomUUID3
|
|
2882
2975
|
} from "crypto";
|
|
2883
2976
|
import { createReadStream as createReadStream2, statfsSync } from "fs";
|
|
2884
|
-
import {
|
|
2977
|
+
import { lstat, mkdir as mkdir5, readFile as readFile3, readdir as readdir2, rm, stat as stat2, writeFile as writeFile3 } from "fs/promises";
|
|
2885
2978
|
import { homedir as homedir6 } from "os";
|
|
2886
2979
|
import { basename as basename2, dirname as dirname5, join as join8, resolve as resolve2 } from "path";
|
|
2887
2980
|
import { privateKeyToAccount as privateKeyToAccount5 } from "viem/accounts";
|
|
@@ -2988,8 +3081,8 @@ function formatNextCronUtc(schedule, cronStatus, now) {
|
|
|
2988
3081
|
}
|
|
2989
3082
|
function buildLsProseIntro(bucket) {
|
|
2990
3083
|
return [
|
|
2991
|
-
"\u2601\uFE0F mnemospark cloud
|
|
2992
|
-
`
|
|
3084
|
+
"\u2601\uFE0F mnemospark cloud",
|
|
3085
|
+
`Folder: ${bucket}`,
|
|
2993
3086
|
"The columns: CRON JOB, NEXT PAYMENT DATE, AMOUNT DUE, FILE NAME are from this host's mnemospark SQLite catalog",
|
|
2994
3087
|
"mnemospark cloud only stores the OBJECT-KEY for privacy"
|
|
2995
3088
|
];
|
|
@@ -3419,6 +3512,22 @@ async function createCloudDatastore(homeDir) {
|
|
|
3419
3512
|
if (!row) return null;
|
|
3420
3513
|
return { cronId: row.cron_id, objectId: row.object_id };
|
|
3421
3514
|
}, null),
|
|
3515
|
+
findCronJobRowByObjectKey: async (objectKey) => safe(() => {
|
|
3516
|
+
const row = db.prepare(
|
|
3517
|
+
`SELECT cron_id, object_id, object_key, quote_id, schedule, command, status
|
|
3518
|
+
FROM cron_jobs WHERE object_key = ? ORDER BY updated_at DESC LIMIT 1`
|
|
3519
|
+
).get(objectKey);
|
|
3520
|
+
if (!row) return null;
|
|
3521
|
+
return {
|
|
3522
|
+
cron_id: row.cron_id,
|
|
3523
|
+
object_id: row.object_id,
|
|
3524
|
+
object_key: row.object_key,
|
|
3525
|
+
quote_id: row.quote_id,
|
|
3526
|
+
schedule: row.schedule,
|
|
3527
|
+
command: row.command,
|
|
3528
|
+
status: row.status
|
|
3529
|
+
};
|
|
3530
|
+
}, null),
|
|
3422
3531
|
findCronByQuoteId: async (quoteId) => safe(() => {
|
|
3423
3532
|
const row = db.prepare(
|
|
3424
3533
|
`SELECT cron_id, object_id, object_key, quote_id, schedule, command, status
|
|
@@ -3605,6 +3714,16 @@ async function createCloudDatastore(homeDir) {
|
|
|
3605
3714
|
).get(w, obj.object_id);
|
|
3606
3715
|
return byObj?.friendly_name ?? null;
|
|
3607
3716
|
}, null),
|
|
3717
|
+
findLatestFriendlyNameForObjectId: async (objectId) => safe(() => {
|
|
3718
|
+
const row = db.prepare(
|
|
3719
|
+
`SELECT friendly_name
|
|
3720
|
+
FROM friendly_names
|
|
3721
|
+
WHERE object_id = ? AND is_active = 1
|
|
3722
|
+
ORDER BY created_at DESC
|
|
3723
|
+
LIMIT 1`
|
|
3724
|
+
).get(objectId.trim());
|
|
3725
|
+
return row?.friendly_name ?? null;
|
|
3726
|
+
}, null),
|
|
3608
3727
|
findCronAndPaymentForObjectKey: async (walletAddress, objectKey) => safe(() => {
|
|
3609
3728
|
const w = normalizeWalletAddress(walletAddress);
|
|
3610
3729
|
const cron = db.prepare(
|
|
@@ -3641,20 +3760,19 @@ async function createCloudDatastore(homeDir) {
|
|
|
3641
3760
|
var SUPPORTED_BACKUP_PLATFORMS = /* @__PURE__ */ new Set(["darwin", "linux"]);
|
|
3642
3761
|
var BACKUP_DIR_SUBPATH = join8(".openclaw", "mnemospark", "backup");
|
|
3643
3762
|
var DEFAULT_BACKUP_DIR = join8(homedir6(), BACKUP_DIR_SUBPATH);
|
|
3644
|
-
var CRON_TABLE_SUBPATH = join8(".openclaw", "mnemospark", "crontab.txt");
|
|
3645
3763
|
var BLOCKRUN_WALLET_KEY_SUBPATH = join8(".openclaw", "blockrun", "wallet.key");
|
|
3646
3764
|
var MNEMOSPARK_WALLET_KEY_SUBPATH = join8(".openclaw", "mnemospark", "wallet", "wallet.key");
|
|
3647
3765
|
var INLINE_UPLOAD_MAX_BYTES = 45e5;
|
|
3648
|
-
var PAYMENT_REMINDER_INTERVAL_DAYS = 30;
|
|
3649
|
-
var PAYMENT_DELETE_DEADLINE_DAYS = 32;
|
|
3650
3766
|
var PAYMENT_CRON_SCHEDULE = "0 0 1 * *";
|
|
3651
3767
|
var TAR_OVERHEAD_BYTES = 10 * 1024 * 1024;
|
|
3652
3768
|
var QUOTE_VALIDITY_USER_NOTE = "Quotes are valid for one hour. Please run price-storage again if you need a new quote.";
|
|
3653
3769
|
var MNEMOSPARK_SUPPORT_EMAIL = "pluggedin@mnemospark.ai";
|
|
3654
|
-
var CLOUD_HELP_FOOTER_STATE = "Local state: mnemospark records quotes, objects, payments, cron jobs, friendly names, and operation metadata in ~/.openclaw/mnemospark/state.db (SQLite). For troubleshooting and correlation, commands and the HTTP proxy append structured JSON lines to ~/.openclaw/mnemospark/events.jsonl. Monthly storage billing jobs are listed in ~/.openclaw/
|
|
3770
|
+
var CLOUD_HELP_FOOTER_STATE = "Local state: mnemospark records quotes, objects, payments, cron jobs, friendly names, and operation metadata in ~/.openclaw/mnemospark/state.db (SQLite). For troubleshooting and correlation, commands and the HTTP proxy append structured JSON lines to ~/.openclaw/mnemospark/events.jsonl. Monthly storage billing jobs are listed in ~/.openclaw/cron/jobs.json for OpenClaw scheduling.";
|
|
3655
3771
|
var REQUIRED_PRICE_STORAGE = "--wallet-address, --object-id, --object-id-hash, --gb, --provider, --region";
|
|
3656
3772
|
var REQUIRED_UPLOAD = "--quote-id, --wallet-address, --object-id, --object-id-hash";
|
|
3657
|
-
var
|
|
3773
|
+
var REQUIRED_BACKUP = "<file|directory> and --name <friendly-name>";
|
|
3774
|
+
var REQUIRED_PAYMENT_SETTLE = "--wallet-address and (--quote-id | --renewal with --object-key)";
|
|
3775
|
+
var PAYMENT_SETTLE_BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["renewal"]);
|
|
3658
3776
|
var REQUIRED_STORAGE_OBJECT = "--wallet-address and one of (--object-key | --name [--latest|--at])";
|
|
3659
3777
|
var REQUIRED_LS = "--wallet-address (for one object add --object-key or --name [--latest|--at]; omit both to list the bucket)";
|
|
3660
3778
|
var PAYMENT_SETTLE_FLAG_NAMES = /* @__PURE__ */ new Set([
|
|
@@ -3662,7 +3780,8 @@ var PAYMENT_SETTLE_FLAG_NAMES = /* @__PURE__ */ new Set([
|
|
|
3662
3780
|
"wallet-address",
|
|
3663
3781
|
"object-id",
|
|
3664
3782
|
"object-key",
|
|
3665
|
-
"storage-price"
|
|
3783
|
+
"storage-price",
|
|
3784
|
+
"renewal"
|
|
3666
3785
|
]);
|
|
3667
3786
|
var BOOLEAN_SELECTOR_FLAGS = /* @__PURE__ */ new Set(["latest"]);
|
|
3668
3787
|
var BOOLEAN_ASYNC_FLAGS = /* @__PURE__ */ new Set(["async"]);
|
|
@@ -3686,9 +3805,9 @@ var CLOUD_HELP_TEXT = [
|
|
|
3686
3805
|
"",
|
|
3687
3806
|
"\u2022 `/mnemospark_cloud` or `/mnemospark_cloud help` \u2014 show this message",
|
|
3688
3807
|
"",
|
|
3689
|
-
"\u2022 `/mnemospark_cloud backup <file|directory>
|
|
3690
|
-
" Purpose: create a local tar+gzip archive under ~/.openclaw/mnemospark/backup and record metadata in SQLite for later price-storage and upload.",
|
|
3691
|
-
" Required:
|
|
3808
|
+
"\u2022 `/mnemospark_cloud backup <file|directory> --name <friendly-name> [--async] [--orchestrator <inline|subagent>] [--timeout-seconds <n>]`",
|
|
3809
|
+
" Purpose: create a local tar+gzip archive under ~/.openclaw/mnemospark/backup (filename from sanitized friendly name) and record metadata in SQLite for later price-storage and upload.",
|
|
3810
|
+
" Required: " + REQUIRED_BACKUP,
|
|
3692
3811
|
"",
|
|
3693
3812
|
"\u2022 `/mnemospark_cloud price-storage --wallet-address <addr> --object-id <id> --object-id-hash <hash> --gb <gb> --provider <provider> --region <region>`",
|
|
3694
3813
|
" Purpose: request a storage quote before upload.",
|
|
@@ -3698,9 +3817,9 @@ var CLOUD_HELP_TEXT = [
|
|
|
3698
3817
|
" Purpose: upload an encrypted object using a valid quote-id.",
|
|
3699
3818
|
" Required: " + REQUIRED_UPLOAD,
|
|
3700
3819
|
"",
|
|
3701
|
-
"\u2022 `/mnemospark_cloud payment-settle --quote-id <quote-id> --
|
|
3702
|
-
" Purpose: settle storage payment
|
|
3703
|
-
" Required:
|
|
3820
|
+
"\u2022 `/mnemospark_cloud payment-settle (--quote-id <quote-id> | --renewal --object-key <key>) --wallet-address <addr> [--object-id <id>] [--storage-price <n>]`",
|
|
3821
|
+
" Purpose: settle storage payment before upload (quote) or on the monthly cron (renewal, no new quote). Uses the same proxy + x402 path as upload pre-settlement.",
|
|
3822
|
+
" Required: " + REQUIRED_PAYMENT_SETTLE + " (wallet private key must match the address).",
|
|
3704
3823
|
"",
|
|
3705
3824
|
"\u2022 `/mnemospark_cloud ls --wallet-address <addr> [--object-key <key> | --name <friendly-name> | omit both to list bucket] [--latest|--at <timestamp>]`",
|
|
3706
3825
|
" Purpose: stat one object or list all keys in the wallet bucket (S3).",
|
|
@@ -3738,7 +3857,7 @@ var CLOUD_HELP_TEXT = [
|
|
|
3738
3857
|
"",
|
|
3739
3858
|
CLOUD_HELP_FOOTER_STATE,
|
|
3740
3859
|
"",
|
|
3741
|
-
"Commands price-storage, upload, ls, download, delete, and payment-settle require
|
|
3860
|
+
"Backup uses your configured mnemospark wallet key (no `--wallet-address` flag). Commands price-storage, upload, ls, download, delete, and payment-settle require `--wallet-address` on the command line (must match that wallet)."
|
|
3742
3861
|
].join("\n");
|
|
3743
3862
|
var UnsupportedBackupPlatformError = class extends Error {
|
|
3744
3863
|
constructor(platform) {
|
|
@@ -3937,10 +4056,14 @@ function parseCloudArgs(args) {
|
|
|
3937
4056
|
if (!asyncArgs) {
|
|
3938
4057
|
return { mode: "backup-invalid-async" };
|
|
3939
4058
|
}
|
|
4059
|
+
const friendlyName = flags.name?.trim();
|
|
4060
|
+
if (!friendlyName) {
|
|
4061
|
+
return { mode: "backup-invalid-name" };
|
|
4062
|
+
}
|
|
3940
4063
|
return {
|
|
3941
4064
|
mode: "backup",
|
|
3942
4065
|
backupTarget,
|
|
3943
|
-
friendlyName
|
|
4066
|
+
friendlyName,
|
|
3944
4067
|
...asyncArgs
|
|
3945
4068
|
};
|
|
3946
4069
|
}
|
|
@@ -3992,7 +4115,7 @@ function parseCloudArgs(args) {
|
|
|
3992
4115
|
};
|
|
3993
4116
|
}
|
|
3994
4117
|
if (subcommand === "payment-settle") {
|
|
3995
|
-
const flags = parseNamedFlags(rest);
|
|
4118
|
+
const flags = parseNamedFlags(rest, PAYMENT_SETTLE_BOOLEAN_FLAGS);
|
|
3996
4119
|
if (!flags) {
|
|
3997
4120
|
return { mode: "payment-settle-invalid" };
|
|
3998
4121
|
}
|
|
@@ -4001,9 +4124,21 @@ function parseCloudArgs(args) {
|
|
|
4001
4124
|
return { mode: "payment-settle-invalid" };
|
|
4002
4125
|
}
|
|
4003
4126
|
}
|
|
4004
|
-
const quoteId = flags["quote-id"]?.trim();
|
|
4005
4127
|
const walletAddress = flags["wallet-address"]?.trim();
|
|
4006
|
-
if (!
|
|
4128
|
+
if (!walletAddress) {
|
|
4129
|
+
return { mode: "payment-settle-invalid" };
|
|
4130
|
+
}
|
|
4131
|
+
const isRenewal = flags.renewal === "true";
|
|
4132
|
+
const quoteId = flags["quote-id"]?.trim();
|
|
4133
|
+
const objectKey = flags["object-key"]?.trim();
|
|
4134
|
+
if (isRenewal) {
|
|
4135
|
+
if (quoteId) {
|
|
4136
|
+
return { mode: "payment-settle-invalid" };
|
|
4137
|
+
}
|
|
4138
|
+
if (!objectKey) {
|
|
4139
|
+
return { mode: "payment-settle-invalid" };
|
|
4140
|
+
}
|
|
4141
|
+
} else if (!quoteId) {
|
|
4007
4142
|
return { mode: "payment-settle-invalid" };
|
|
4008
4143
|
}
|
|
4009
4144
|
let storagePrice;
|
|
@@ -4018,10 +4153,11 @@ function parseCloudArgs(args) {
|
|
|
4018
4153
|
return {
|
|
4019
4154
|
mode: "payment-settle",
|
|
4020
4155
|
paymentSettleRequest: {
|
|
4021
|
-
quote_id: quoteId,
|
|
4022
4156
|
wallet_address: walletAddress,
|
|
4157
|
+
renewal: isRenewal || void 0,
|
|
4158
|
+
quote_id: quoteId || void 0,
|
|
4023
4159
|
object_id: flags["object-id"]?.trim() || void 0,
|
|
4024
|
-
object_key:
|
|
4160
|
+
object_key: objectKey || void 0,
|
|
4025
4161
|
storage_price: storagePrice
|
|
4026
4162
|
}
|
|
4027
4163
|
};
|
|
@@ -4111,9 +4247,6 @@ function parseCloudArgs(args) {
|
|
|
4111
4247
|
}
|
|
4112
4248
|
return { mode: "unknown" };
|
|
4113
4249
|
}
|
|
4114
|
-
function resolveCronTablePath(homeDir) {
|
|
4115
|
-
return join8(homeDir ?? homedir6(), CRON_TABLE_SUBPATH);
|
|
4116
|
-
}
|
|
4117
4250
|
async function calculateInputSizeBytes(targetPath) {
|
|
4118
4251
|
const targetStats = await lstat(targetPath);
|
|
4119
4252
|
if (targetStats.isFile() || targetStats.isSymbolicLink()) {
|
|
@@ -4167,6 +4300,38 @@ async function sha256File(filePath) {
|
|
|
4167
4300
|
});
|
|
4168
4301
|
return hash.digest("hex");
|
|
4169
4302
|
}
|
|
4303
|
+
async function resolveLocalUploadArchivePath(backupDir, objectId, friendlyName) {
|
|
4304
|
+
if (friendlyName?.trim()) {
|
|
4305
|
+
try {
|
|
4306
|
+
const sanitized = sanitizeFriendlyNameForLocalBasename(friendlyName);
|
|
4307
|
+
const candidate = join8(backupDir, sanitized);
|
|
4308
|
+
try {
|
|
4309
|
+
const st = await stat2(candidate);
|
|
4310
|
+
if (st.isFile()) {
|
|
4311
|
+
return { ok: true, archivePath: candidate };
|
|
4312
|
+
}
|
|
4313
|
+
} catch {
|
|
4314
|
+
}
|
|
4315
|
+
} catch {
|
|
4316
|
+
}
|
|
4317
|
+
}
|
|
4318
|
+
const legacyPath = join8(backupDir, objectId);
|
|
4319
|
+
try {
|
|
4320
|
+
const legacyStats = await stat2(legacyPath);
|
|
4321
|
+
if (!legacyStats.isFile()) {
|
|
4322
|
+
return {
|
|
4323
|
+
ok: false,
|
|
4324
|
+
message: `Cannot upload storage object: local archive path is not a file (${legacyPath}).`
|
|
4325
|
+
};
|
|
4326
|
+
}
|
|
4327
|
+
return { ok: true, archivePath: legacyPath };
|
|
4328
|
+
} catch {
|
|
4329
|
+
return {
|
|
4330
|
+
ok: false,
|
|
4331
|
+
message: `Cannot upload storage object: local archive not found. Run /mnemospark_cloud backup with --name (canonical layout) or restore the legacy file at ${legacyPath}.`
|
|
4332
|
+
};
|
|
4333
|
+
}
|
|
4334
|
+
}
|
|
4170
4335
|
function createObjectId(options) {
|
|
4171
4336
|
const nowFn = options.now ?? Date.now;
|
|
4172
4337
|
const randomFn = options.randomBytes ?? randomBytesNode;
|
|
@@ -4204,7 +4369,25 @@ async function buildBackupObject(targetPathArg, options = {}) {
|
|
|
4204
4369
|
throw new Error("Insufficient disk space for backup object");
|
|
4205
4370
|
}
|
|
4206
4371
|
const objectId = createObjectId(options);
|
|
4207
|
-
const
|
|
4372
|
+
const archiveBaseSegment = options.archiveBasename?.trim() || objectId;
|
|
4373
|
+
const archivePath = join8(tmpDir, archiveBaseSegment);
|
|
4374
|
+
if (options.archiveBasename?.trim()) {
|
|
4375
|
+
try {
|
|
4376
|
+
const existing = await stat2(archivePath);
|
|
4377
|
+
if (existing.isFile() || existing.isDirectory()) {
|
|
4378
|
+
throw new Error(
|
|
4379
|
+
`Backup archive path already exists: ${archivePath}. Choose a different --name.`
|
|
4380
|
+
);
|
|
4381
|
+
}
|
|
4382
|
+
} catch (err) {
|
|
4383
|
+
if (err instanceof Error && err.message.includes("already exists")) {
|
|
4384
|
+
throw err;
|
|
4385
|
+
}
|
|
4386
|
+
if (err.code !== "ENOENT") {
|
|
4387
|
+
throw err;
|
|
4388
|
+
}
|
|
4389
|
+
}
|
|
4390
|
+
}
|
|
4208
4391
|
try {
|
|
4209
4392
|
await runTarGzip(archivePath, targetPath);
|
|
4210
4393
|
const archiveStats = await stat2(archivePath);
|
|
@@ -4221,84 +4404,126 @@ async function buildBackupObject(targetPathArg, options = {}) {
|
|
|
4221
4404
|
throw error;
|
|
4222
4405
|
}
|
|
4223
4406
|
}
|
|
4224
|
-
function
|
|
4225
|
-
|
|
4226
|
-
return [
|
|
4227
|
-
date.getFullYear().toString(),
|
|
4228
|
-
"-",
|
|
4229
|
-
pad(date.getMonth() + 1),
|
|
4230
|
-
"-",
|
|
4231
|
-
pad(date.getDate()),
|
|
4232
|
-
" ",
|
|
4233
|
-
pad(date.getHours()),
|
|
4234
|
-
":",
|
|
4235
|
-
pad(date.getMinutes()),
|
|
4236
|
-
":",
|
|
4237
|
-
pad(date.getSeconds())
|
|
4238
|
-
].join("");
|
|
4239
|
-
}
|
|
4240
|
-
function parseStoragePaymentCronJobLine(line) {
|
|
4241
|
-
const trimmed = line.trim();
|
|
4242
|
-
if (!trimmed) {
|
|
4407
|
+
function normalizeOpenClawCronJobForLookup(value) {
|
|
4408
|
+
if (!value || typeof value !== "object") {
|
|
4243
4409
|
return null;
|
|
4244
4410
|
}
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4411
|
+
const record = value;
|
|
4412
|
+
const jobIdRaw = typeof record.jobId === "string" ? record.jobId : record.id;
|
|
4413
|
+
const jobId = typeof jobIdRaw === "string" ? jobIdRaw.trim() : "";
|
|
4414
|
+
const payloadRaw = record.payload;
|
|
4415
|
+
if (!jobId || !payloadRaw || typeof payloadRaw !== "object") {
|
|
4249
4416
|
return null;
|
|
4250
4417
|
}
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
const cronId = typeof record.cronId === "string" ? record.cronId.trim() : "";
|
|
4256
|
-
const createdAt = typeof record.createdAt === "string" ? record.createdAt.trim() : "";
|
|
4257
|
-
const schedule = typeof record.schedule === "string" ? record.schedule.trim() : "";
|
|
4258
|
-
const command = typeof record.command === "string" ? record.command.trim() : "";
|
|
4259
|
-
const quoteId = typeof record.quoteId === "string" ? record.quoteId.trim() : "";
|
|
4260
|
-
const storagePrice = typeof record.storagePrice === "number" ? record.storagePrice : Number.NaN;
|
|
4261
|
-
const walletAddress = typeof record.walletAddress === "string" ? record.walletAddress.trim() : "";
|
|
4262
|
-
const objectId = typeof record.objectId === "string" ? record.objectId.trim() : "";
|
|
4263
|
-
const objectKey = typeof record.objectKey === "string" ? record.objectKey.trim() : "";
|
|
4264
|
-
const provider = typeof record.provider === "string" ? record.provider.trim() : "";
|
|
4265
|
-
const bucketName = typeof record.bucketName === "string" ? record.bucketName.trim() : "";
|
|
4266
|
-
const location = typeof record.location === "string" ? record.location.trim() : "";
|
|
4267
|
-
if (!cronId || !createdAt || !schedule || !command || !quoteId || !Number.isFinite(storagePrice) || storagePrice <= 0 || !walletAddress || !objectId || !objectKey || !provider || !bucketName || !location) {
|
|
4418
|
+
const payloadRecord = payloadRaw;
|
|
4419
|
+
const payloadKind = payloadRecord.kind;
|
|
4420
|
+
const payloadMessage = typeof payloadRecord.message === "string" ? payloadRecord.message.trim() : "";
|
|
4421
|
+
if (payloadKind !== "agentTurn" || !payloadMessage) {
|
|
4268
4422
|
return null;
|
|
4269
4423
|
}
|
|
4270
4424
|
return {
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
schedule,
|
|
4274
|
-
command,
|
|
4275
|
-
quoteId,
|
|
4276
|
-
storagePrice,
|
|
4277
|
-
walletAddress,
|
|
4278
|
-
objectId,
|
|
4279
|
-
objectKey,
|
|
4280
|
-
provider,
|
|
4281
|
-
bucketName,
|
|
4282
|
-
location
|
|
4425
|
+
jobId,
|
|
4426
|
+
message: payloadMessage
|
|
4283
4427
|
};
|
|
4284
4428
|
}
|
|
4285
|
-
async function
|
|
4286
|
-
|
|
4287
|
-
|
|
4429
|
+
async function runOpenClawCli(args, homeDir) {
|
|
4430
|
+
return await new Promise((resolvePromise, rejectPromise) => {
|
|
4431
|
+
let stdout = "";
|
|
4432
|
+
let stderr = "";
|
|
4433
|
+
const child = spawn("openclaw", args, {
|
|
4434
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
4435
|
+
env: {
|
|
4436
|
+
...process.env,
|
|
4437
|
+
HOME: homeDir ?? process.env.HOME
|
|
4438
|
+
}
|
|
4439
|
+
});
|
|
4440
|
+
child.stdout.on("data", (chunk) => {
|
|
4441
|
+
stdout += chunk.toString();
|
|
4442
|
+
});
|
|
4443
|
+
child.stderr.on("data", (chunk) => {
|
|
4444
|
+
stderr += chunk.toString();
|
|
4445
|
+
});
|
|
4446
|
+
child.on("error", rejectPromise);
|
|
4447
|
+
child.on("close", (code) => {
|
|
4448
|
+
if (code === 0) {
|
|
4449
|
+
resolvePromise({ stdout, stderr });
|
|
4450
|
+
return;
|
|
4451
|
+
}
|
|
4452
|
+
rejectPromise(
|
|
4453
|
+
new Error(
|
|
4454
|
+
stderr.trim() || stdout.trim() || `openclaw ${args.join(" ")} exited with code ${code ?? "unknown"}`
|
|
4455
|
+
)
|
|
4456
|
+
);
|
|
4457
|
+
});
|
|
4458
|
+
});
|
|
4459
|
+
}
|
|
4460
|
+
function parseOpenClawCliJson(stdout, commandLabel) {
|
|
4461
|
+
const trimmed = stdout.trim();
|
|
4462
|
+
if (!trimmed) {
|
|
4463
|
+
throw new Error(`openclaw ${commandLabel} returned empty JSON output`);
|
|
4464
|
+
}
|
|
4288
4465
|
try {
|
|
4289
|
-
|
|
4290
|
-
} catch
|
|
4291
|
-
|
|
4292
|
-
return null;
|
|
4293
|
-
}
|
|
4294
|
-
throw error;
|
|
4466
|
+
return JSON.parse(trimmed);
|
|
4467
|
+
} catch {
|
|
4468
|
+
throw new Error(`openclaw ${commandLabel} returned invalid JSON output`);
|
|
4295
4469
|
}
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4470
|
+
}
|
|
4471
|
+
function createOpenClawCliCronAdapter(homeDir) {
|
|
4472
|
+
return {
|
|
4473
|
+
add: async (job) => {
|
|
4474
|
+
const { stdout } = await runOpenClawCli(
|
|
4475
|
+
[
|
|
4476
|
+
"cron",
|
|
4477
|
+
"add",
|
|
4478
|
+
"--name",
|
|
4479
|
+
job.name,
|
|
4480
|
+
"--cron",
|
|
4481
|
+
job.schedule.expr,
|
|
4482
|
+
"--tz",
|
|
4483
|
+
job.schedule.tz,
|
|
4484
|
+
"--session",
|
|
4485
|
+
job.sessionTarget,
|
|
4486
|
+
"--message",
|
|
4487
|
+
job.payload.message,
|
|
4488
|
+
"--announce",
|
|
4489
|
+
"--description",
|
|
4490
|
+
job.delivery.text,
|
|
4491
|
+
"--json"
|
|
4492
|
+
],
|
|
4493
|
+
homeDir
|
|
4494
|
+
);
|
|
4495
|
+
const payload = parseOpenClawCliJson(stdout, "cron add");
|
|
4496
|
+
const createdIdRaw = typeof payload.id === "string" ? payload.id : payload.jobId;
|
|
4497
|
+
const createdId = typeof createdIdRaw === "string" ? createdIdRaw.trim() : "";
|
|
4498
|
+
if (!createdId) {
|
|
4499
|
+
throw new Error("openclaw cron add did not return a job id");
|
|
4500
|
+
}
|
|
4501
|
+
return { jobId: createdId };
|
|
4502
|
+
},
|
|
4503
|
+
remove: async (jobId) => {
|
|
4504
|
+
const { stdout } = await runOpenClawCli(["cron", "rm", jobId, "--json"], homeDir);
|
|
4505
|
+
const payload = parseOpenClawCliJson(stdout, "cron rm");
|
|
4506
|
+
if (typeof payload.removed === "boolean") {
|
|
4507
|
+
return payload.removed;
|
|
4508
|
+
}
|
|
4509
|
+
return true;
|
|
4510
|
+
},
|
|
4511
|
+
list: async () => {
|
|
4512
|
+
const { stdout } = await runOpenClawCli(["cron", "list", "--all", "--json"], homeDir);
|
|
4513
|
+
const payload = parseOpenClawCliJson(stdout, "cron list");
|
|
4514
|
+
const jobsRaw = Array.isArray(payload.jobs) ? payload.jobs : [];
|
|
4515
|
+
return jobsRaw.map((entry) => normalizeOpenClawCronJobForLookup(entry)).filter((entry) => entry !== null);
|
|
4516
|
+
}
|
|
4517
|
+
};
|
|
4518
|
+
}
|
|
4519
|
+
async function findCronJobInOpenClawCronJobsByObjectKey(objectKey, adapter) {
|
|
4520
|
+
const cronJobs = await adapter.list();
|
|
4521
|
+
for (let idx = cronJobs.length - 1; idx >= 0; idx -= 1) {
|
|
4522
|
+
const cronJob = cronJobs[idx];
|
|
4523
|
+
const parsed = parseStoragePaymentCronCommand(cronJob.message);
|
|
4299
4524
|
if (parsed && parsed.objectKey === objectKey) {
|
|
4300
4525
|
return {
|
|
4301
|
-
cronId:
|
|
4526
|
+
cronId: cronJob.jobId,
|
|
4302
4527
|
objectId: parsed.objectId,
|
|
4303
4528
|
objectKey: parsed.objectKey
|
|
4304
4529
|
};
|
|
@@ -4313,8 +4538,7 @@ function buildStoragePaymentCronCommand(job) {
|
|
|
4313
4538
|
return [
|
|
4314
4539
|
"/mnemospark_cloud",
|
|
4315
4540
|
"payment-settle",
|
|
4316
|
-
"--
|
|
4317
|
-
quoteCronArgument(job.quoteId),
|
|
4541
|
+
"--renewal",
|
|
4318
4542
|
"--wallet-address",
|
|
4319
4543
|
quoteCronArgument(job.walletAddress),
|
|
4320
4544
|
"--object-id",
|
|
@@ -4325,57 +4549,67 @@ function buildStoragePaymentCronCommand(job) {
|
|
|
4325
4549
|
quoteCronArgument(job.storagePrice)
|
|
4326
4550
|
].join(" ");
|
|
4327
4551
|
}
|
|
4328
|
-
|
|
4329
|
-
const
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
return cronTablePath;
|
|
4334
|
-
}
|
|
4335
|
-
async function removeStoragePaymentCronJob(cronId, homeDir) {
|
|
4336
|
-
const cronTablePath = resolveCronTablePath(homeDir);
|
|
4337
|
-
let content;
|
|
4338
|
-
try {
|
|
4339
|
-
content = await readFile3(cronTablePath, "utf-8");
|
|
4340
|
-
} catch (error) {
|
|
4341
|
-
if (error.code === "ENOENT") {
|
|
4342
|
-
return false;
|
|
4343
|
-
}
|
|
4344
|
-
throw error;
|
|
4552
|
+
function parseStoragePaymentCronCommand(command) {
|
|
4553
|
+
const objectIdMatch = command.match(/--object-id\s+("([^"\\]|\\.)*"|'([^'\\]|\\.)*'|\S+)/);
|
|
4554
|
+
const objectKeyMatch = command.match(/--object-key\s+("([^"\\]|\\.)*"|'([^'\\]|\\.)*'|\S+)/);
|
|
4555
|
+
if (!objectIdMatch || !objectKeyMatch) {
|
|
4556
|
+
return null;
|
|
4345
4557
|
}
|
|
4346
|
-
const
|
|
4347
|
-
|
|
4348
|
-
const keptLines = [];
|
|
4349
|
-
for (const line of lines) {
|
|
4350
|
-
const trimmed = line.trim();
|
|
4558
|
+
const parseToken = (token) => {
|
|
4559
|
+
const trimmed = token.trim();
|
|
4351
4560
|
if (!trimmed) {
|
|
4352
|
-
|
|
4561
|
+
return null;
|
|
4353
4562
|
}
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4563
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
4564
|
+
try {
|
|
4565
|
+
return JSON.parse(trimmed);
|
|
4566
|
+
} catch {
|
|
4567
|
+
return trimmed.slice(1, -1);
|
|
4568
|
+
}
|
|
4358
4569
|
}
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
return
|
|
4570
|
+
if (trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
4571
|
+
return trimmed.slice(1, -1);
|
|
4572
|
+
}
|
|
4573
|
+
return trimmed;
|
|
4574
|
+
};
|
|
4575
|
+
const objectId = parseToken(objectIdMatch[1] ?? "");
|
|
4576
|
+
const objectKey = parseToken(objectKeyMatch[1] ?? "");
|
|
4577
|
+
if (!objectId || !objectKey) {
|
|
4578
|
+
return null;
|
|
4363
4579
|
}
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4580
|
+
return { objectId, objectKey };
|
|
4581
|
+
}
|
|
4582
|
+
async function appendStoragePaymentCronJob(cronJob, adapter) {
|
|
4583
|
+
const openClawJob = {
|
|
4584
|
+
jobId: cronJob.cronId,
|
|
4585
|
+
name: "Mnemospark Monthly Renewal",
|
|
4586
|
+
schedule: {
|
|
4587
|
+
kind: "cron",
|
|
4588
|
+
expr: PAYMENT_CRON_SCHEDULE,
|
|
4589
|
+
tz: "UTC"
|
|
4590
|
+
},
|
|
4591
|
+
payload: {
|
|
4592
|
+
kind: "agentTurn",
|
|
4593
|
+
message: cronJob.command
|
|
4594
|
+
},
|
|
4595
|
+
sessionTarget: "isolated",
|
|
4596
|
+
delivery: {
|
|
4597
|
+
mode: "announce",
|
|
4598
|
+
text: "Thank you for using mnemospark cloud storage. Your renewal has been processed."
|
|
4599
|
+
}
|
|
4600
|
+
};
|
|
4601
|
+
return adapter.add(openClawJob);
|
|
4369
4602
|
}
|
|
4370
|
-
async function
|
|
4371
|
-
|
|
4372
|
-
|
|
4603
|
+
async function removeStoragePaymentCronJob(cronId, adapter) {
|
|
4604
|
+
return adapter.remove(cronId);
|
|
4605
|
+
}
|
|
4606
|
+
async function createStoragePaymentCronJob(upload, storagePrice, openClawCronAdapter, nowDateFn = () => /* @__PURE__ */ new Date()) {
|
|
4607
|
+
const provisionalCronId = randomUUID3();
|
|
4373
4608
|
const cronJob = {
|
|
4374
|
-
cronId,
|
|
4375
|
-
createdAt,
|
|
4609
|
+
cronId: provisionalCronId,
|
|
4610
|
+
createdAt: nowDateFn().toISOString(),
|
|
4376
4611
|
schedule: PAYMENT_CRON_SCHEDULE,
|
|
4377
4612
|
command: buildStoragePaymentCronCommand({
|
|
4378
|
-
quoteId: upload.quote_id,
|
|
4379
4613
|
walletAddress: upload.addr,
|
|
4380
4614
|
objectId: upload.object_id,
|
|
4381
4615
|
objectKey: upload.object_key,
|
|
@@ -4390,7 +4624,10 @@ async function createStoragePaymentCronJob(upload, storagePrice, homeDir, nowDat
|
|
|
4390
4624
|
bucketName: upload.bucket_name,
|
|
4391
4625
|
location: upload.location
|
|
4392
4626
|
};
|
|
4393
|
-
await appendStoragePaymentCronJob(cronJob,
|
|
4627
|
+
const created = await appendStoragePaymentCronJob(cronJob, openClawCronAdapter);
|
|
4628
|
+
if (created.jobId?.trim()) {
|
|
4629
|
+
cronJob.cronId = created.jobId.trim();
|
|
4630
|
+
}
|
|
4394
4631
|
return cronJob;
|
|
4395
4632
|
}
|
|
4396
4633
|
async function readWalletKeyIfPresent(walletPath) {
|
|
@@ -4520,13 +4757,32 @@ async function uploadPresignedObjectIfNeeded(uploadResponse, uploadMode, encrypt
|
|
|
4520
4757
|
`Presigned upload failed with status ${firstAttempt.status}${details ? `: ${details}` : ""}`
|
|
4521
4758
|
);
|
|
4522
4759
|
}
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4760
|
+
function envMeansExplicitRemoveOrKeep(value) {
|
|
4761
|
+
if (value === void 0) {
|
|
4762
|
+
return null;
|
|
4763
|
+
}
|
|
4764
|
+
const trimmed = value.trim();
|
|
4765
|
+
if (!trimmed) {
|
|
4766
|
+
return null;
|
|
4527
4767
|
}
|
|
4528
|
-
const
|
|
4529
|
-
if (
|
|
4768
|
+
const n = trimmed.toLowerCase();
|
|
4769
|
+
if (n === "0" || n === "false" || n === "no" || n === "n") {
|
|
4770
|
+
return false;
|
|
4771
|
+
}
|
|
4772
|
+
if (n === "1" || n === "true" || n === "yes" || n === "y") {
|
|
4773
|
+
return true;
|
|
4774
|
+
}
|
|
4775
|
+
return null;
|
|
4776
|
+
}
|
|
4777
|
+
function shouldRemoveLocalBackupAfterUpload() {
|
|
4778
|
+
const parsed = envMeansExplicitRemoveOrKeep(process.env.MNEMOSPARK_REMOVE_BACKUP_FILE);
|
|
4779
|
+
if (parsed !== null) {
|
|
4780
|
+
return parsed;
|
|
4781
|
+
}
|
|
4782
|
+
return true;
|
|
4783
|
+
}
|
|
4784
|
+
async function maybeCleanupLocalBackupArchive(archivePath) {
|
|
4785
|
+
if (!shouldRemoveLocalBackupAfterUpload()) {
|
|
4530
4786
|
return;
|
|
4531
4787
|
}
|
|
4532
4788
|
try {
|
|
@@ -4537,9 +4793,9 @@ async function maybeCleanupLocalBackupArchive(archivePath) {
|
|
|
4537
4793
|
function formatStorageUploadUserMessage(upload, cronJobId) {
|
|
4538
4794
|
const lsLine = `/mnemospark_cloud ls --wallet-address \`${upload.addr}\``;
|
|
4539
4795
|
return [
|
|
4540
|
-
`Your file \`${upload.object_id}\` with key \`${upload.object_key}\` has been stored using \`${upload.provider}\` in \`${upload.bucket_name}\` \`${upload.location}\``,
|
|
4796
|
+
`Your file \`${upload.object_id}\` with key \`${upload.object_key}\` has been stored using \`${upload.provider}\` in folder \`${upload.bucket_name}\` in region \`${upload.location}\``,
|
|
4541
4797
|
"",
|
|
4542
|
-
`A cron job \`${cronJobId}\` has been configured to send payment monthly (
|
|
4798
|
+
`A cron job \`${cronJobId}\` has been configured to send renewal payment monthly (1st of the month, UTC), matching backend calendar billing. The object is skipped for deletion in its first UTC calendar month after upload; after that, if renewal is missing for a month, housekeeping may remove the object shortly after the 3rd (UTC).`,
|
|
4543
4799
|
"",
|
|
4544
4800
|
"To view your cloud storage run the command:",
|
|
4545
4801
|
"",
|
|
@@ -4592,32 +4848,40 @@ function extractLsErrorMessage(error) {
|
|
|
4592
4848
|
}
|
|
4593
4849
|
return null;
|
|
4594
4850
|
}
|
|
4595
|
-
function formatPriceStorageUserMessage(quote) {
|
|
4851
|
+
function formatPriceStorageUserMessage(quote, localArchiveHint) {
|
|
4596
4852
|
const uploadLine = `/mnemospark_cloud upload --quote-id \`${quote.quote_id}\` --wallet-address \`${quote.addr}\` --object-id \`${quote.object_id}\` --object-id-hash \`${quote.object_id_hash}\``;
|
|
4597
|
-
|
|
4598
|
-
`Your storage quote \`${quote.quote_id}\`: storage price
|
|
4853
|
+
const lines = [
|
|
4854
|
+
`Your storage quote \`${quote.quote_id}\`: storage price \`$${quote.storage_price}\` for file \`${quote.object_id}\` with file size \`${quote.object_size_gb}\` in \`${quote.provider}\` \`${quote.location}\`.`,
|
|
4599
4855
|
"",
|
|
4600
4856
|
"If you accept this quote, run:",
|
|
4601
4857
|
"",
|
|
4602
4858
|
uploadLine,
|
|
4603
|
-
""
|
|
4604
|
-
|
|
4605
|
-
|
|
4859
|
+
""
|
|
4860
|
+
];
|
|
4861
|
+
if (localArchiveHint?.trim()) {
|
|
4862
|
+
lines.push(
|
|
4863
|
+
`Local backup archive uses friendly name \`${localArchiveHint.trim()}\` (on-disk basename is sanitized).`,
|
|
4864
|
+
""
|
|
4865
|
+
);
|
|
4866
|
+
}
|
|
4867
|
+
lines.push(QUOTE_VALIDITY_USER_NOTE);
|
|
4868
|
+
return lines.join("\n");
|
|
4606
4869
|
}
|
|
4607
4870
|
function quoteLookupMatchesPriceStorageResponse(lookup, quote) {
|
|
4608
4871
|
return lookup.quoteId === quote.quote_id && lookup.walletAddress.trim().toLowerCase() === quote.addr.trim().toLowerCase() && lookup.objectId === quote.object_id && lookup.objectIdHash.toLowerCase() === quote.object_id_hash.toLowerCase() && lookup.storagePrice === quote.storage_price && lookup.provider === quote.provider && lookup.location === quote.location;
|
|
4609
4872
|
}
|
|
4610
|
-
function formatBackupSuccessUserMessage(result) {
|
|
4873
|
+
function formatBackupSuccessUserMessage(result, walletAddress, friendlyName) {
|
|
4611
4874
|
const hash = result.objectIdHash.replace(/\s/g, "");
|
|
4612
|
-
const priceStorageLine = `/mnemospark_cloud price-storage --wallet-address
|
|
4875
|
+
const priceStorageLine = `/mnemospark_cloud price-storage --wallet-address \`${walletAddress}\` --object-id \`${result.objectId}\` --object-id-hash \`${hash}\` --gb \`${result.objectSizeGb}\` --provider <provider> --region <region>`;
|
|
4613
4876
|
return [
|
|
4614
4877
|
`Backup archive: \`${result.archivePath}\``,
|
|
4615
4878
|
"",
|
|
4879
|
+
`friendly-name: ${friendlyName}`,
|
|
4616
4880
|
`object-id: ${result.objectId}`,
|
|
4617
4881
|
`object-id-hash: ${hash}`,
|
|
4618
4882
|
`object-size: ${result.objectSizeGb}`,
|
|
4619
4883
|
"",
|
|
4620
|
-
"Next, request a storage quote. Replace `<
|
|
4884
|
+
"Next, request a storage quote. Replace `<provider>` and `<region>` (one line):",
|
|
4621
4885
|
"",
|
|
4622
4886
|
priceStorageLine,
|
|
4623
4887
|
"",
|
|
@@ -4753,6 +5017,9 @@ function createCloudCommand(options = {}) {
|
|
|
4753
5017
|
requestStorageDeleteFn: options.requestStorageDeleteFn ?? requestStorageDeleteViaProxy,
|
|
4754
5018
|
requestPaymentSettleViaProxyFn: options.requestPaymentSettleViaProxyFn ?? requestPaymentSettleViaProxy,
|
|
4755
5019
|
mnemosparkHomeDir: options.mnemosparkHomeDir ?? options.backupOptions?.homeDir,
|
|
5020
|
+
openClawCronAdapter: options.openClawCronAdapter ?? createOpenClawCliCronAdapter(
|
|
5021
|
+
options.mnemosparkHomeDir ?? options.backupOptions?.homeDir
|
|
5022
|
+
),
|
|
4756
5023
|
backupOptions: options.backupOptions,
|
|
4757
5024
|
proxyQuoteOptions: options.proxyQuoteOptions,
|
|
4758
5025
|
proxyUploadOptions: options.proxyUploadOptions,
|
|
@@ -4905,10 +5172,26 @@ function parseTransIdFromPaymentSettleBody(bodyText) {
|
|
|
4905
5172
|
return null;
|
|
4906
5173
|
}
|
|
4907
5174
|
}
|
|
5175
|
+
function parseQuoteIdFromPaymentSettleBody(bodyText) {
|
|
5176
|
+
const trimmed = bodyText.trim();
|
|
5177
|
+
if (!trimmed.startsWith("{")) {
|
|
5178
|
+
return null;
|
|
5179
|
+
}
|
|
5180
|
+
try {
|
|
5181
|
+
const parsed = JSON.parse(trimmed);
|
|
5182
|
+
const q = parsed.quote_id;
|
|
5183
|
+
return typeof q === "string" && q.trim() ? q.trim() : null;
|
|
5184
|
+
} catch {
|
|
5185
|
+
return null;
|
|
5186
|
+
}
|
|
5187
|
+
}
|
|
4908
5188
|
async function resolveAmountForPaymentSettle(quoteId, storagePriceFromFlag, datastore) {
|
|
4909
5189
|
if (storagePriceFromFlag !== void 0 && Number.isFinite(storagePriceFromFlag)) {
|
|
4910
5190
|
return storagePriceFromFlag;
|
|
4911
5191
|
}
|
|
5192
|
+
if (!quoteId) {
|
|
5193
|
+
return 0;
|
|
5194
|
+
}
|
|
4912
5195
|
const quoteLookup = await datastore.findQuoteById(quoteId);
|
|
4913
5196
|
if (quoteLookup && Number.isFinite(quoteLookup.storagePrice)) {
|
|
4914
5197
|
return quoteLookup.storagePrice;
|
|
@@ -4919,6 +5202,20 @@ async function resolveAmountForPaymentSettle(quoteId, storagePriceFromFlag, data
|
|
|
4919
5202
|
}
|
|
4920
5203
|
return 0;
|
|
4921
5204
|
}
|
|
5205
|
+
async function resolveCronRowForPaymentSettle(req, datastore) {
|
|
5206
|
+
if (req.renewal) {
|
|
5207
|
+
const key = req.object_key?.trim();
|
|
5208
|
+
if (!key) {
|
|
5209
|
+
return null;
|
|
5210
|
+
}
|
|
5211
|
+
return datastore.findCronJobRowByObjectKey(key);
|
|
5212
|
+
}
|
|
5213
|
+
const qid = req.quote_id?.trim();
|
|
5214
|
+
if (!qid) {
|
|
5215
|
+
return null;
|
|
5216
|
+
}
|
|
5217
|
+
return datastore.findCronByQuoteId(qid);
|
|
5218
|
+
}
|
|
4922
5219
|
async function emitPaymentSettleClientObservationBestEffort(params) {
|
|
4923
5220
|
try {
|
|
4924
5221
|
const {
|
|
@@ -5018,6 +5315,7 @@ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
|
|
|
5018
5315
|
const requestStorageDownload = options.requestStorageDownloadFn;
|
|
5019
5316
|
const requestStorageDelete = options.requestStorageDeleteFn;
|
|
5020
5317
|
const requestPaymentSettleViaProxy2 = options.requestPaymentSettleViaProxyFn;
|
|
5318
|
+
const openClawCronAdapter = options.openClawCronAdapter;
|
|
5021
5319
|
const subagentOrchestrator = options.subagentOrchestrator;
|
|
5022
5320
|
if (parsed.mode === "help" || parsed.mode === "unknown") {
|
|
5023
5321
|
return {
|
|
@@ -5043,6 +5341,12 @@ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
|
|
|
5043
5341
|
isError: true
|
|
5044
5342
|
};
|
|
5045
5343
|
}
|
|
5344
|
+
if (parsed.mode === "backup-invalid-name") {
|
|
5345
|
+
return {
|
|
5346
|
+
text: `Cannot build storage object: required arguments are ${REQUIRED_BACKUP}.`,
|
|
5347
|
+
isError: true
|
|
5348
|
+
};
|
|
5349
|
+
}
|
|
5046
5350
|
if (parsed.mode === "upload-invalid") {
|
|
5047
5351
|
return {
|
|
5048
5352
|
text: `Cannot upload storage object: required arguments are ${REQUIRED_UPLOAD}.`,
|
|
@@ -5057,7 +5361,7 @@ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
|
|
|
5057
5361
|
}
|
|
5058
5362
|
if (parsed.mode === "payment-settle-invalid") {
|
|
5059
5363
|
return {
|
|
5060
|
-
text: `Cannot settle payment: required arguments are ${REQUIRED_PAYMENT_SETTLE}. Optional: --object-id, --
|
|
5364
|
+
text: `Cannot settle payment: required arguments are ${REQUIRED_PAYMENT_SETTLE}. Optional: --object-id, --storage-price.`,
|
|
5061
5365
|
isError: true
|
|
5062
5366
|
};
|
|
5063
5367
|
}
|
|
@@ -5225,10 +5529,11 @@ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
|
|
|
5225
5529
|
const settleFetch = createPayment(walletKey).fetch;
|
|
5226
5530
|
const objectId = req.object_id;
|
|
5227
5531
|
const objectKey = req.object_key;
|
|
5532
|
+
const logQuoteId = req.quote_id?.trim() || (req.renewal && objectKey?.trim() ? `renewal:${objectKey.trim()}` : "");
|
|
5228
5533
|
await emitPaymentSettleClientObservationBestEffort({
|
|
5229
5534
|
phase: "start",
|
|
5230
5535
|
correlation,
|
|
5231
|
-
quoteId:
|
|
5536
|
+
quoteId: logQuoteId,
|
|
5232
5537
|
walletAddress: req.wallet_address,
|
|
5233
5538
|
objectId,
|
|
5234
5539
|
objectKey,
|
|
@@ -5236,10 +5541,12 @@ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
|
|
|
5236
5541
|
});
|
|
5237
5542
|
let settleResult;
|
|
5238
5543
|
try {
|
|
5239
|
-
settleResult = await requestPaymentSettleViaProxy2(req.quote_id, req.wallet_address, {
|
|
5544
|
+
settleResult = await requestPaymentSettleViaProxy2(req.quote_id ?? "", req.wallet_address, {
|
|
5240
5545
|
...options.proxySettleOptions,
|
|
5241
5546
|
correlation,
|
|
5242
|
-
fetchImpl: (input, init) => settleFetch(input, init)
|
|
5547
|
+
fetchImpl: (input, init) => settleFetch(input, init),
|
|
5548
|
+
renewal: req.renewal === true,
|
|
5549
|
+
objectKey: req.object_key
|
|
5243
5550
|
});
|
|
5244
5551
|
} catch (err) {
|
|
5245
5552
|
const amountErr = await resolveAmountForPaymentSettle(
|
|
@@ -5248,7 +5555,7 @@ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
|
|
|
5248
5555
|
datastore
|
|
5249
5556
|
);
|
|
5250
5557
|
await datastore.upsertPayment({
|
|
5251
|
-
quote_id: req.quote_id,
|
|
5558
|
+
quote_id: logQuoteId || req.quote_id?.trim() || "payment-settle-error",
|
|
5252
5559
|
wallet_address: req.wallet_address,
|
|
5253
5560
|
trans_id: null,
|
|
5254
5561
|
amount: amountErr,
|
|
@@ -5256,7 +5563,7 @@ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
|
|
|
5256
5563
|
status: "settle_failed",
|
|
5257
5564
|
settled_at: null
|
|
5258
5565
|
});
|
|
5259
|
-
const cronErr = await
|
|
5566
|
+
const cronErr = await resolveCronRowForPaymentSettle(req, datastore);
|
|
5260
5567
|
if (cronErr) {
|
|
5261
5568
|
await datastore.upsertCronJob({ ...cronErr, status: "active" });
|
|
5262
5569
|
}
|
|
@@ -5264,7 +5571,7 @@ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
|
|
|
5264
5571
|
await emitPaymentSettleClientObservationBestEffort({
|
|
5265
5572
|
phase: "result",
|
|
5266
5573
|
correlation,
|
|
5267
|
-
quoteId:
|
|
5574
|
+
quoteId: logQuoteId,
|
|
5268
5575
|
walletAddress: req.wallet_address,
|
|
5269
5576
|
objectId,
|
|
5270
5577
|
objectKey,
|
|
@@ -5273,11 +5580,17 @@ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
|
|
|
5273
5580
|
});
|
|
5274
5581
|
return { text: `Payment settle failed: ${msg}`, isError: true };
|
|
5275
5582
|
}
|
|
5276
|
-
const
|
|
5583
|
+
const ledgerQuoteIdFromBody = parseQuoteIdFromPaymentSettleBody(settleResult.bodyText ?? "");
|
|
5584
|
+
const ledgerQuoteId = ledgerQuoteIdFromBody || req.quote_id?.trim() || logQuoteId;
|
|
5585
|
+
const amount = await resolveAmountForPaymentSettle(
|
|
5586
|
+
req.renewal ? void 0 : req.quote_id,
|
|
5587
|
+
req.storage_price,
|
|
5588
|
+
datastore
|
|
5589
|
+
);
|
|
5277
5590
|
const transId = settleResult.status === 200 ? parseTransIdFromPaymentSettleBody(settleResult.bodyText ?? "") : null;
|
|
5278
5591
|
if (settleResult.status === 200) {
|
|
5279
5592
|
await datastore.upsertPayment({
|
|
5280
|
-
quote_id:
|
|
5593
|
+
quote_id: ledgerQuoteId,
|
|
5281
5594
|
wallet_address: req.wallet_address,
|
|
5282
5595
|
trans_id: transId,
|
|
5283
5596
|
amount,
|
|
@@ -5285,14 +5598,14 @@ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
|
|
|
5285
5598
|
status: "settled",
|
|
5286
5599
|
settled_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
5287
5600
|
});
|
|
5288
|
-
const cronRow = await
|
|
5601
|
+
const cronRow = await resolveCronRowForPaymentSettle(req, datastore);
|
|
5289
5602
|
if (cronRow) {
|
|
5290
5603
|
await datastore.upsertCronJob({ ...cronRow, status: "active" });
|
|
5291
5604
|
}
|
|
5292
5605
|
await emitPaymentSettleClientObservationBestEffort({
|
|
5293
5606
|
phase: "result",
|
|
5294
5607
|
correlation,
|
|
5295
|
-
quoteId:
|
|
5608
|
+
quoteId: ledgerQuoteId,
|
|
5296
5609
|
walletAddress: req.wallet_address,
|
|
5297
5610
|
objectId,
|
|
5298
5611
|
objectKey,
|
|
@@ -5300,12 +5613,13 @@ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
|
|
|
5300
5613
|
outcomeStatus: "succeeded",
|
|
5301
5614
|
homeDir: mnemosparkHomeDir
|
|
5302
5615
|
});
|
|
5616
|
+
const label = req.renewal ? `object ${req.object_key}` : `quote ${req.quote_id}`;
|
|
5303
5617
|
return {
|
|
5304
|
-
text: transId ? `Payment settled for
|
|
5618
|
+
text: transId ? `Payment settled for ${label} (trans_id: ${transId}).` : `Payment settled for ${label}.`
|
|
5305
5619
|
};
|
|
5306
5620
|
}
|
|
5307
5621
|
await datastore.upsertPayment({
|
|
5308
|
-
quote_id:
|
|
5622
|
+
quote_id: ledgerQuoteId,
|
|
5309
5623
|
wallet_address: req.wallet_address,
|
|
5310
5624
|
trans_id: transId,
|
|
5311
5625
|
amount,
|
|
@@ -5313,14 +5627,14 @@ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
|
|
|
5313
5627
|
status: "settle_failed",
|
|
5314
5628
|
settled_at: null
|
|
5315
5629
|
});
|
|
5316
|
-
const cronRowFailed = await
|
|
5630
|
+
const cronRowFailed = await resolveCronRowForPaymentSettle(req, datastore);
|
|
5317
5631
|
if (cronRowFailed) {
|
|
5318
5632
|
await datastore.upsertCronJob({ ...cronRowFailed, status: "active" });
|
|
5319
5633
|
}
|
|
5320
5634
|
await emitPaymentSettleClientObservationBestEffort({
|
|
5321
5635
|
phase: "result",
|
|
5322
5636
|
correlation,
|
|
5323
|
-
quoteId:
|
|
5637
|
+
quoteId: ledgerQuoteId,
|
|
5324
5638
|
walletAddress: req.wallet_address,
|
|
5325
5639
|
objectId,
|
|
5326
5640
|
objectKey,
|
|
@@ -5669,8 +5983,38 @@ operation-id: ${operationId}`,
|
|
|
5669
5983
|
};
|
|
5670
5984
|
}
|
|
5671
5985
|
if (parsed.mode === "backup") {
|
|
5986
|
+
const backupPlatform = options.backupOptions?.platform ?? process.platform;
|
|
5987
|
+
if (!SUPPORTED_BACKUP_PLATFORMS.has(backupPlatform)) {
|
|
5988
|
+
return {
|
|
5989
|
+
text: "Cloud backup is only supported on macOS and Linux.",
|
|
5990
|
+
isError: true
|
|
5991
|
+
};
|
|
5992
|
+
}
|
|
5672
5993
|
try {
|
|
5673
|
-
|
|
5994
|
+
let walletAddress;
|
|
5995
|
+
try {
|
|
5996
|
+
const walletKey = await resolveWalletKey(mnemosparkHomeDir);
|
|
5997
|
+
walletAddress = privateKeyToAccount5(walletKey).address;
|
|
5998
|
+
} catch (err) {
|
|
5999
|
+
const message = err instanceof Error ? err.message : typeof err === "string" ? err : String(err);
|
|
6000
|
+
return {
|
|
6001
|
+
text: message.trim() || "No mnemospark wallet found.",
|
|
6002
|
+
isError: true
|
|
6003
|
+
};
|
|
6004
|
+
}
|
|
6005
|
+
let archiveBasename;
|
|
6006
|
+
try {
|
|
6007
|
+
archiveBasename = sanitizeFriendlyNameForLocalBasename(parsed.friendlyName);
|
|
6008
|
+
} catch {
|
|
6009
|
+
return {
|
|
6010
|
+
text: "Cannot build storage object: invalid --name for local file path (use a non-empty name without reserved path segments).",
|
|
6011
|
+
isError: true
|
|
6012
|
+
};
|
|
6013
|
+
}
|
|
6014
|
+
const result = await backupBuilder(parsed.backupTarget, {
|
|
6015
|
+
...options.backupOptions,
|
|
6016
|
+
archiveBasename
|
|
6017
|
+
});
|
|
5674
6018
|
await emitCloudEventBestEffort(
|
|
5675
6019
|
"backup.completed",
|
|
5676
6020
|
{
|
|
@@ -5678,7 +6022,7 @@ operation-id: ${operationId}`,
|
|
|
5678
6022
|
object_id: result.objectId,
|
|
5679
6023
|
status: "succeeded",
|
|
5680
6024
|
details: {
|
|
5681
|
-
friendly_name: parsed.friendlyName
|
|
6025
|
+
friendly_name: parsed.friendlyName,
|
|
5682
6026
|
archive_path: result.archivePath,
|
|
5683
6027
|
object_id_hash: result.objectIdHash.replace(/\s/g, ""),
|
|
5684
6028
|
object_size_gb: result.objectSizeGb
|
|
@@ -5689,7 +6033,7 @@ operation-id: ${operationId}`,
|
|
|
5689
6033
|
await datastore.upsertObject({
|
|
5690
6034
|
object_id: result.objectId,
|
|
5691
6035
|
object_key: null,
|
|
5692
|
-
wallet_address:
|
|
6036
|
+
wallet_address: walletAddress,
|
|
5693
6037
|
quote_id: null,
|
|
5694
6038
|
provider: null,
|
|
5695
6039
|
bucket_name: null,
|
|
@@ -5697,23 +6041,20 @@ operation-id: ${operationId}`,
|
|
|
5697
6041
|
sha256: result.objectIdHash,
|
|
5698
6042
|
status: "backed_up"
|
|
5699
6043
|
});
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
wallet_address: "unknown"
|
|
5708
|
-
});
|
|
5709
|
-
}
|
|
6044
|
+
await datastore.upsertFriendlyName({
|
|
6045
|
+
friendly_name: parsed.friendlyName,
|
|
6046
|
+
object_id: result.objectId,
|
|
6047
|
+
object_key: null,
|
|
6048
|
+
quote_id: null,
|
|
6049
|
+
wallet_address: walletAddress
|
|
6050
|
+
});
|
|
5710
6051
|
return {
|
|
5711
|
-
text: formatBackupSuccessUserMessage(result)
|
|
6052
|
+
text: formatBackupSuccessUserMessage(result, walletAddress, parsed.friendlyName)
|
|
5712
6053
|
};
|
|
5713
6054
|
} catch (err) {
|
|
5714
|
-
if (err instanceof
|
|
6055
|
+
if (err instanceof Error && err.message.includes("already exists")) {
|
|
5715
6056
|
return {
|
|
5716
|
-
text:
|
|
6057
|
+
text: err.message,
|
|
5717
6058
|
isError: true
|
|
5718
6059
|
};
|
|
5719
6060
|
}
|
|
@@ -5769,8 +6110,14 @@ operation-id: ${operationId}`,
|
|
|
5769
6110
|
},
|
|
5770
6111
|
mnemosparkHomeDir
|
|
5771
6112
|
);
|
|
6113
|
+
let friendlyForQuote = null;
|
|
6114
|
+
try {
|
|
6115
|
+
friendlyForQuote = await datastore.findLatestFriendlyNameForObjectId(quote.object_id);
|
|
6116
|
+
} catch {
|
|
6117
|
+
friendlyForQuote = null;
|
|
6118
|
+
}
|
|
5772
6119
|
return {
|
|
5773
|
-
text: formatPriceStorageUserMessage(quote)
|
|
6120
|
+
text: formatPriceStorageUserMessage(quote, friendlyForQuote)
|
|
5774
6121
|
};
|
|
5775
6122
|
} catch (err) {
|
|
5776
6123
|
await emitCloudEventBestEffort(
|
|
@@ -5810,10 +6157,33 @@ operation-id: ${operationId}`,
|
|
|
5810
6157
|
isError: true
|
|
5811
6158
|
};
|
|
5812
6159
|
}
|
|
5813
|
-
const
|
|
5814
|
-
|
|
6160
|
+
const backupDir = options.backupOptions?.tmpDir ?? DEFAULT_BACKUP_DIR;
|
|
6161
|
+
const dbFriendly = await datastore.findLatestFriendlyNameForObjectId(
|
|
5815
6162
|
parsed.uploadRequest.object_id
|
|
5816
6163
|
);
|
|
6164
|
+
if (!dbFriendly?.trim()) {
|
|
6165
|
+
return {
|
|
6166
|
+
text: "Cannot upload storage object: no friendly name in local SQLite for this object-id. Run /mnemospark_cloud backup with --name first.",
|
|
6167
|
+
isError: true
|
|
6168
|
+
};
|
|
6169
|
+
}
|
|
6170
|
+
if (parsed.friendlyName?.trim()) {
|
|
6171
|
+
if (parsed.friendlyName.trim() !== dbFriendly.trim()) {
|
|
6172
|
+
return {
|
|
6173
|
+
text: "Cannot upload storage object: --name does not match the friendly name stored in local SQLite for this object-id.",
|
|
6174
|
+
isError: true
|
|
6175
|
+
};
|
|
6176
|
+
}
|
|
6177
|
+
}
|
|
6178
|
+
const resolvedArchive = await resolveLocalUploadArchivePath(
|
|
6179
|
+
backupDir,
|
|
6180
|
+
parsed.uploadRequest.object_id,
|
|
6181
|
+
dbFriendly
|
|
6182
|
+
);
|
|
6183
|
+
if (!resolvedArchive.ok) {
|
|
6184
|
+
return { text: resolvedArchive.message, isError: true };
|
|
6185
|
+
}
|
|
6186
|
+
const archivePath = resolvedArchive.archivePath;
|
|
5817
6187
|
let archiveStats;
|
|
5818
6188
|
try {
|
|
5819
6189
|
archiveStats = await stat2(archivePath);
|
|
@@ -5918,7 +6288,7 @@ operation-id: ${operationId}`,
|
|
|
5918
6288
|
const cronJob = await createStoragePaymentCronJob(
|
|
5919
6289
|
finalizedUploadResponse,
|
|
5920
6290
|
cronStoragePrice,
|
|
5921
|
-
|
|
6291
|
+
openClawCronAdapter,
|
|
5922
6292
|
nowDateFn
|
|
5923
6293
|
);
|
|
5924
6294
|
await datastore.upsertObject({
|
|
@@ -5950,45 +6320,43 @@ operation-id: ${operationId}`,
|
|
|
5950
6320
|
command: cronJob.command,
|
|
5951
6321
|
status: "active"
|
|
5952
6322
|
});
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
6323
|
+
const normalizedFriendlyName = dbFriendly.trim();
|
|
6324
|
+
await datastore.upsertFriendlyName({
|
|
6325
|
+
friendly_name: normalizedFriendlyName,
|
|
6326
|
+
object_id: finalizedUploadResponse.object_id,
|
|
6327
|
+
object_key: finalizedUploadResponse.object_key,
|
|
6328
|
+
quote_id: finalizedUploadResponse.quote_id,
|
|
6329
|
+
wallet_address: finalizedUploadResponse.addr
|
|
6330
|
+
});
|
|
6331
|
+
let friendlyNameVerified = false;
|
|
6332
|
+
try {
|
|
6333
|
+
const readBack = await datastore.resolveFriendlyName({
|
|
6334
|
+
walletAddress: finalizedUploadResponse.addr,
|
|
6335
|
+
friendlyName: normalizedFriendlyName,
|
|
6336
|
+
latest: true
|
|
5961
6337
|
});
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
friendly_name: normalizedFriendlyName,
|
|
5985
|
-
warning
|
|
5986
|
-
},
|
|
5987
|
-
mnemosparkHomeDir
|
|
5988
|
-
);
|
|
5989
|
-
if (process.env.MNEMOSPARK_SQLITE_STRICT === "1") {
|
|
5990
|
-
throw new Error(warning);
|
|
5991
|
-
}
|
|
6338
|
+
friendlyNameVerified = Boolean(readBack?.objectKey) && readBack?.objectKey === finalizedUploadResponse.object_key;
|
|
6339
|
+
} catch {
|
|
6340
|
+
friendlyNameVerified = false;
|
|
6341
|
+
}
|
|
6342
|
+
if (!friendlyNameVerified) {
|
|
6343
|
+
const warning = "SQLite friendly-name write verification failed; --name lookups may not resolve until SQLite is healthy.";
|
|
6344
|
+
await emitCloudEventBestEffort(
|
|
6345
|
+
"friendly_name.write_verification_failed",
|
|
6346
|
+
{
|
|
6347
|
+
operation_id: uploadCorrelation.operationId,
|
|
6348
|
+
trace_id: uploadCorrelation.traceId,
|
|
6349
|
+
wallet_address: finalizedUploadResponse.addr,
|
|
6350
|
+
object_id: finalizedUploadResponse.object_id,
|
|
6351
|
+
object_key: finalizedUploadResponse.object_key,
|
|
6352
|
+
quote_id: finalizedUploadResponse.quote_id,
|
|
6353
|
+
friendly_name: normalizedFriendlyName,
|
|
6354
|
+
warning
|
|
6355
|
+
},
|
|
6356
|
+
mnemosparkHomeDir
|
|
6357
|
+
);
|
|
6358
|
+
if (process.env.MNEMOSPARK_SQLITE_STRICT === "1") {
|
|
6359
|
+
throw new Error(warning);
|
|
5992
6360
|
}
|
|
5993
6361
|
}
|
|
5994
6362
|
await emitCloudEventBestEffort(
|
|
@@ -6151,10 +6519,27 @@ operation-id: ${operationId}`,
|
|
|
6151
6519
|
error_code: null,
|
|
6152
6520
|
error_message: null
|
|
6153
6521
|
});
|
|
6522
|
+
let downloadLocalBasename;
|
|
6523
|
+
try {
|
|
6524
|
+
const friendly = await datastore.findLatestFriendlyNameForObjectKey(
|
|
6525
|
+
resolvedRequest.wallet_address,
|
|
6526
|
+
resolvedRequest.object_key
|
|
6527
|
+
);
|
|
6528
|
+
if (friendly?.trim()) {
|
|
6529
|
+
try {
|
|
6530
|
+
downloadLocalBasename = sanitizeFriendlyNameForLocalBasename(friendly);
|
|
6531
|
+
} catch {
|
|
6532
|
+
downloadLocalBasename = void 0;
|
|
6533
|
+
}
|
|
6534
|
+
}
|
|
6535
|
+
} catch {
|
|
6536
|
+
downloadLocalBasename = void 0;
|
|
6537
|
+
}
|
|
6154
6538
|
try {
|
|
6155
6539
|
const downloadResult = await requestStorageDownload(resolvedRequest, {
|
|
6156
6540
|
...options.proxyStorageOptions,
|
|
6157
|
-
correlation
|
|
6541
|
+
correlation,
|
|
6542
|
+
...downloadLocalBasename ? { downloadLocalBasename } : {}
|
|
6158
6543
|
});
|
|
6159
6544
|
if (!downloadResult.success) {
|
|
6160
6545
|
throw new Error("download failed");
|
|
@@ -6267,15 +6652,15 @@ operation-id: ${operationId}`,
|
|
|
6267
6652
|
};
|
|
6268
6653
|
}
|
|
6269
6654
|
if (!cronEntry) {
|
|
6270
|
-
cronEntry = await
|
|
6655
|
+
cronEntry = await findCronJobInOpenClawCronJobsByObjectKey(
|
|
6271
6656
|
resolvedRequest.object_key,
|
|
6272
|
-
|
|
6657
|
+
openClawCronAdapter
|
|
6273
6658
|
);
|
|
6274
6659
|
}
|
|
6275
6660
|
if (cronEntry) {
|
|
6276
6661
|
const fileCronDeleted = await removeStoragePaymentCronJob(
|
|
6277
6662
|
cronEntry.cronId,
|
|
6278
|
-
|
|
6663
|
+
openClawCronAdapter
|
|
6279
6664
|
);
|
|
6280
6665
|
const dbCronDeleted = await datastore.removeCronJob(cronEntry.cronId);
|
|
6281
6666
|
cronDeleted = fileCronDeleted || dbCronDeleted;
|