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/index.js
CHANGED
|
@@ -791,9 +791,14 @@ async function forwardPaymentSettleToBackend(quoteId, walletAddress, options = {
|
|
|
791
791
|
}
|
|
792
792
|
const targetUrl = `${normalizeBaseUrl(backendBaseUrl)}/payment/settle`;
|
|
793
793
|
const requestBody = {
|
|
794
|
-
quote_id: quoteId,
|
|
795
794
|
wallet_address: walletAddress
|
|
796
795
|
};
|
|
796
|
+
if (options.renewal === true && options.objectKey?.trim()) {
|
|
797
|
+
requestBody.renewal = true;
|
|
798
|
+
requestBody.object_key = options.objectKey.trim();
|
|
799
|
+
} else {
|
|
800
|
+
requestBody.quote_id = quoteId;
|
|
801
|
+
}
|
|
797
802
|
if (options.payment) {
|
|
798
803
|
requestBody.payment = options.payment;
|
|
799
804
|
}
|
|
@@ -820,9 +825,14 @@ async function requestPaymentSettleViaProxy(quoteId, walletAddress, options = {}
|
|
|
820
825
|
);
|
|
821
826
|
const targetUrl = `${baseUrl}${PAYMENT_SETTLE_PROXY_PATH}`;
|
|
822
827
|
const requestBody = {
|
|
823
|
-
quote_id: quoteId,
|
|
824
828
|
wallet_address: walletAddress
|
|
825
829
|
};
|
|
830
|
+
if (options.renewal === true && options.objectKey?.trim()) {
|
|
831
|
+
requestBody.renewal = true;
|
|
832
|
+
requestBody.object_key = options.objectKey.trim();
|
|
833
|
+
} else {
|
|
834
|
+
requestBody.quote_id = quoteId;
|
|
835
|
+
}
|
|
826
836
|
if (options.payment) {
|
|
827
837
|
requestBody.payment = options.payment;
|
|
828
838
|
}
|
|
@@ -1244,6 +1254,23 @@ function parseJsonText(text, errorMessage) {
|
|
|
1244
1254
|
}
|
|
1245
1255
|
return record;
|
|
1246
1256
|
}
|
|
1257
|
+
var MAX_LOCAL_FRIENDLY_BASENAME_LEN = 200;
|
|
1258
|
+
function sanitizeFriendlyNameForLocalBasename(raw) {
|
|
1259
|
+
const normalized = raw.replace(/\\/g, "/").trim();
|
|
1260
|
+
if (!normalized) {
|
|
1261
|
+
throw new Error("Friendly name is empty");
|
|
1262
|
+
}
|
|
1263
|
+
const segments = normalized.split("/").filter((segment2) => segment2.length > 0 && segment2 !== "." && segment2 !== "..");
|
|
1264
|
+
const segment = segments[segments.length - 1] ?? "";
|
|
1265
|
+
if (!segment || segment === "." || segment === "..") {
|
|
1266
|
+
throw new Error("Invalid friendly name for local file path");
|
|
1267
|
+
}
|
|
1268
|
+
const noCtrl = segment.replace(/[\x00-\x1f]/g, "").trim();
|
|
1269
|
+
if (!noCtrl || noCtrl === "." || noCtrl === "..") {
|
|
1270
|
+
throw new Error("Invalid friendly name for local file path");
|
|
1271
|
+
}
|
|
1272
|
+
return noCtrl.length > MAX_LOCAL_FRIENDLY_BASENAME_LEN ? noCtrl.slice(0, MAX_LOCAL_FRIENDLY_BASENAME_LEN) : noCtrl;
|
|
1273
|
+
}
|
|
1247
1274
|
function sanitizeObjectKeyToRelativePath(objectKey) {
|
|
1248
1275
|
const normalized = objectKey.replace(/\\/g, "/").trim().replace(/^\/+/, "");
|
|
1249
1276
|
const segments = normalized.split("/").filter((segment) => segment.length > 0 && segment !== "." && segment !== "..");
|
|
@@ -1387,6 +1414,27 @@ function parseStorageObjectRequest(payload) {
|
|
|
1387
1414
|
location
|
|
1388
1415
|
};
|
|
1389
1416
|
}
|
|
1417
|
+
function parseProxyStorageDownloadPayload(payload) {
|
|
1418
|
+
const record = asRecord(payload);
|
|
1419
|
+
if (!record) {
|
|
1420
|
+
return null;
|
|
1421
|
+
}
|
|
1422
|
+
const walletAddress = asNonEmptyString(record.wallet_address);
|
|
1423
|
+
const objectKey = asNonEmptyString(record.object_key);
|
|
1424
|
+
const location = asNonEmptyString(record.location) ?? void 0;
|
|
1425
|
+
if (!walletAddress || !objectKey) {
|
|
1426
|
+
return null;
|
|
1427
|
+
}
|
|
1428
|
+
const localRaw = asNonEmptyString(record.mnemospark_local_filename) ?? void 0;
|
|
1429
|
+
return {
|
|
1430
|
+
request: {
|
|
1431
|
+
wallet_address: walletAddress,
|
|
1432
|
+
object_key: objectKey,
|
|
1433
|
+
...location ? { location } : {}
|
|
1434
|
+
},
|
|
1435
|
+
...localRaw ? { localBasename: localRaw } : {}
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1390
1438
|
function jsonBodyForObjectRequest(request) {
|
|
1391
1439
|
const o = {
|
|
1392
1440
|
wallet_address: request.wallet_address,
|
|
@@ -1397,6 +1445,14 @@ function jsonBodyForObjectRequest(request) {
|
|
|
1397
1445
|
}
|
|
1398
1446
|
return o;
|
|
1399
1447
|
}
|
|
1448
|
+
function jsonBodyForProxyDownloadRequest(request, downloadLocalBasename) {
|
|
1449
|
+
const body = jsonBodyForObjectRequest(request);
|
|
1450
|
+
const trimmed = downloadLocalBasename?.trim();
|
|
1451
|
+
if (trimmed) {
|
|
1452
|
+
body.mnemospark_local_filename = trimmed;
|
|
1453
|
+
}
|
|
1454
|
+
return body;
|
|
1455
|
+
}
|
|
1400
1456
|
function jsonBodyForLsRequest(request) {
|
|
1401
1457
|
const o = { wallet_address: request.wallet_address };
|
|
1402
1458
|
if (request.object_key) {
|
|
@@ -1542,7 +1598,7 @@ async function requestStorageLsViaProxy(request, options = {}) {
|
|
|
1542
1598
|
async function requestStorageDownloadViaProxy(request, options = {}) {
|
|
1543
1599
|
return requestJsonViaProxy(
|
|
1544
1600
|
STORAGE_DOWNLOAD_PROXY_PATH,
|
|
1545
|
-
|
|
1601
|
+
jsonBodyForProxyDownloadRequest(request, options.downloadLocalBasename),
|
|
1546
1602
|
parseStorageDownloadProxyResponse,
|
|
1547
1603
|
options
|
|
1548
1604
|
);
|
|
@@ -1629,7 +1685,8 @@ async function downloadStorageToDisk(request, backendResponse, options = {}) {
|
|
|
1629
1685
|
objectKey = filenameFromHeader;
|
|
1630
1686
|
}
|
|
1631
1687
|
}
|
|
1632
|
-
const
|
|
1688
|
+
const pathKey = options.localOutputBasename?.trim() && options.localOutputBasename.trim().length > 0 ? options.localOutputBasename.trim() : objectKey;
|
|
1689
|
+
const filePath = resolveDownloadPath(outputDir, pathKey);
|
|
1633
1690
|
await mkdir(dirname(filePath), { recursive: true });
|
|
1634
1691
|
await writeFile(filePath, bytes);
|
|
1635
1692
|
return {
|
|
@@ -1994,16 +2051,47 @@ async function startProxy(options) {
|
|
|
1994
2051
|
return;
|
|
1995
2052
|
}
|
|
1996
2053
|
const record = payload && typeof payload === "object" ? payload : null;
|
|
1997
|
-
const quoteId = typeof record?.quote_id === "string" ? record.quote_id.trim() : "";
|
|
1998
2054
|
const walletAddress = typeof record?.wallet_address === "string" ? record.wallet_address.trim() : "";
|
|
2055
|
+
const isRenewal = record?.renewal === true;
|
|
2056
|
+
const objectKey = typeof record?.object_key === "string" ? record.object_key.trim() : "";
|
|
2057
|
+
let quoteId = typeof record?.quote_id === "string" ? record.quote_id.trim() : "";
|
|
1999
2058
|
const inlinePayment = record?.payment;
|
|
2000
2059
|
const inlinePaymentAuthorization = record?.payment_authorization;
|
|
2001
|
-
if (!
|
|
2060
|
+
if (!walletAddress) {
|
|
2002
2061
|
logProxyEvent("warn", "proxy_payment_settle_missing_fields");
|
|
2003
2062
|
emitProxyTerminalFromStatus(correlation, 400, { reason: "missing_fields" });
|
|
2004
2063
|
sendJson(res, 400, {
|
|
2005
2064
|
error: "Bad request",
|
|
2006
|
-
message: "Missing required
|
|
2065
|
+
message: "Missing required field: wallet_address"
|
|
2066
|
+
});
|
|
2067
|
+
return;
|
|
2068
|
+
}
|
|
2069
|
+
if (isRenewal) {
|
|
2070
|
+
if (quoteId) {
|
|
2071
|
+
logProxyEvent("warn", "proxy_payment_settle_renewal_with_quote_id");
|
|
2072
|
+
emitProxyTerminalFromStatus(correlation, 400, { reason: "renewal_with_quote_id" });
|
|
2073
|
+
sendJson(res, 400, {
|
|
2074
|
+
error: "Bad request",
|
|
2075
|
+
message: "renewal requests must not include quote_id"
|
|
2076
|
+
});
|
|
2077
|
+
return;
|
|
2078
|
+
}
|
|
2079
|
+
if (!objectKey) {
|
|
2080
|
+
logProxyEvent("warn", "proxy_payment_settle_missing_fields");
|
|
2081
|
+
emitProxyTerminalFromStatus(correlation, 400, { reason: "missing_fields" });
|
|
2082
|
+
sendJson(res, 400, {
|
|
2083
|
+
error: "Bad request",
|
|
2084
|
+
message: "Missing required field: object_key (renewal mode)"
|
|
2085
|
+
});
|
|
2086
|
+
return;
|
|
2087
|
+
}
|
|
2088
|
+
quoteId = `renewal:${objectKey}`;
|
|
2089
|
+
} else if (!quoteId) {
|
|
2090
|
+
logProxyEvent("warn", "proxy_payment_settle_missing_fields");
|
|
2091
|
+
emitProxyTerminalFromStatus(correlation, 400, { reason: "missing_fields" });
|
|
2092
|
+
sendJson(res, 400, {
|
|
2093
|
+
error: "Bad request",
|
|
2094
|
+
message: "Missing required field: quote_id (or use renewal: true with object_key)"
|
|
2007
2095
|
});
|
|
2008
2096
|
return;
|
|
2009
2097
|
}
|
|
@@ -2062,7 +2150,9 @@ async function startProxy(options) {
|
|
|
2062
2150
|
paymentSignature: readHeaderValue(req.headers["payment-signature"]),
|
|
2063
2151
|
legacyPayment: readHeaderValue(req.headers["x-payment"]),
|
|
2064
2152
|
payment: inlinePayment && typeof inlinePayment === "object" && !Array.isArray(inlinePayment) ? inlinePayment : void 0,
|
|
2065
|
-
paymentAuthorization: typeof inlinePaymentAuthorization === "string" ? inlinePaymentAuthorization.trim() || void 0 : inlinePaymentAuthorization !== void 0 ? inlinePaymentAuthorization : void 0
|
|
2153
|
+
paymentAuthorization: typeof inlinePaymentAuthorization === "string" ? inlinePaymentAuthorization.trim() || void 0 : inlinePaymentAuthorization !== void 0 ? inlinePaymentAuthorization : void 0,
|
|
2154
|
+
renewal: isRenewal,
|
|
2155
|
+
objectKey: isRenewal ? objectKey : void 0
|
|
2066
2156
|
});
|
|
2067
2157
|
logProxyEvent("info", "proxy_payment_settle_backend_response", {
|
|
2068
2158
|
status: backendResponse.status
|
|
@@ -2502,8 +2592,8 @@ async function startProxy(options) {
|
|
|
2502
2592
|
});
|
|
2503
2593
|
return;
|
|
2504
2594
|
}
|
|
2505
|
-
const
|
|
2506
|
-
if (!
|
|
2595
|
+
const parsedDownload = parseProxyStorageDownloadPayload(payload);
|
|
2596
|
+
if (!parsedDownload) {
|
|
2507
2597
|
logProxyEvent("warn", "proxy_download_missing_fields");
|
|
2508
2598
|
emitProxyTerminalFromStatus(correlation, 400, { reason: "missing_fields" });
|
|
2509
2599
|
sendJson(res, 400, {
|
|
@@ -2512,6 +2602,8 @@ async function startProxy(options) {
|
|
|
2512
2602
|
});
|
|
2513
2603
|
return;
|
|
2514
2604
|
}
|
|
2605
|
+
const requestPayload = parsedDownload.request;
|
|
2606
|
+
const downloadLocalBasename = parsedDownload.localBasename;
|
|
2515
2607
|
correlation.wallet_address = requestPayload.wallet_address;
|
|
2516
2608
|
correlation.object_key = requestPayload.object_key;
|
|
2517
2609
|
if (requestPayload.wallet_address.toLowerCase() !== proxyWalletAddressLower) {
|
|
@@ -2573,7 +2665,8 @@ async function startProxy(options) {
|
|
|
2573
2665
|
return;
|
|
2574
2666
|
}
|
|
2575
2667
|
const downloadResult = await downloadStorageToDisk(requestPayload, backendResponse, {
|
|
2576
|
-
outputDir: resolveDownloadOutputDir()
|
|
2668
|
+
outputDir: resolveDownloadOutputDir(),
|
|
2669
|
+
...downloadLocalBasename?.trim() ? { localOutputBasename: downloadLocalBasename.trim() } : {}
|
|
2577
2670
|
});
|
|
2578
2671
|
logProxyEvent("info", "proxy_download_written_to_disk", {
|
|
2579
2672
|
key: downloadResult.key,
|
|
@@ -2932,7 +3025,7 @@ import {
|
|
|
2932
3025
|
randomUUID as randomUUID3
|
|
2933
3026
|
} from "crypto";
|
|
2934
3027
|
import { createReadStream as createReadStream2, statfsSync } from "fs";
|
|
2935
|
-
import {
|
|
3028
|
+
import { lstat, mkdir as mkdir5, readFile as readFile3, readdir as readdir2, rm, stat as stat2, writeFile as writeFile3 } from "fs/promises";
|
|
2936
3029
|
import { homedir as homedir6 } from "os";
|
|
2937
3030
|
import { basename as basename2, dirname as dirname5, join as join8, resolve as resolve2 } from "path";
|
|
2938
3031
|
import { privateKeyToAccount as privateKeyToAccount5 } from "viem/accounts";
|
|
@@ -3039,8 +3132,8 @@ function formatNextCronUtc(schedule, cronStatus, now) {
|
|
|
3039
3132
|
}
|
|
3040
3133
|
function buildLsProseIntro(bucket) {
|
|
3041
3134
|
return [
|
|
3042
|
-
"\u2601\uFE0F mnemospark cloud
|
|
3043
|
-
`
|
|
3135
|
+
"\u2601\uFE0F mnemospark cloud",
|
|
3136
|
+
`Folder: ${bucket}`,
|
|
3044
3137
|
"The columns: CRON JOB, NEXT PAYMENT DATE, AMOUNT DUE, FILE NAME are from this host's mnemospark SQLite catalog",
|
|
3045
3138
|
"mnemospark cloud only stores the OBJECT-KEY for privacy"
|
|
3046
3139
|
];
|
|
@@ -3470,6 +3563,22 @@ async function createCloudDatastore(homeDir) {
|
|
|
3470
3563
|
if (!row) return null;
|
|
3471
3564
|
return { cronId: row.cron_id, objectId: row.object_id };
|
|
3472
3565
|
}, null),
|
|
3566
|
+
findCronJobRowByObjectKey: async (objectKey) => safe(() => {
|
|
3567
|
+
const row = db.prepare(
|
|
3568
|
+
`SELECT cron_id, object_id, object_key, quote_id, schedule, command, status
|
|
3569
|
+
FROM cron_jobs WHERE object_key = ? ORDER BY updated_at DESC LIMIT 1`
|
|
3570
|
+
).get(objectKey);
|
|
3571
|
+
if (!row) return null;
|
|
3572
|
+
return {
|
|
3573
|
+
cron_id: row.cron_id,
|
|
3574
|
+
object_id: row.object_id,
|
|
3575
|
+
object_key: row.object_key,
|
|
3576
|
+
quote_id: row.quote_id,
|
|
3577
|
+
schedule: row.schedule,
|
|
3578
|
+
command: row.command,
|
|
3579
|
+
status: row.status
|
|
3580
|
+
};
|
|
3581
|
+
}, null),
|
|
3473
3582
|
findCronByQuoteId: async (quoteId) => safe(() => {
|
|
3474
3583
|
const row = db.prepare(
|
|
3475
3584
|
`SELECT cron_id, object_id, object_key, quote_id, schedule, command, status
|
|
@@ -3656,6 +3765,16 @@ async function createCloudDatastore(homeDir) {
|
|
|
3656
3765
|
).get(w, obj.object_id);
|
|
3657
3766
|
return byObj?.friendly_name ?? null;
|
|
3658
3767
|
}, null),
|
|
3768
|
+
findLatestFriendlyNameForObjectId: async (objectId) => safe(() => {
|
|
3769
|
+
const row = db.prepare(
|
|
3770
|
+
`SELECT friendly_name
|
|
3771
|
+
FROM friendly_names
|
|
3772
|
+
WHERE object_id = ? AND is_active = 1
|
|
3773
|
+
ORDER BY created_at DESC
|
|
3774
|
+
LIMIT 1`
|
|
3775
|
+
).get(objectId.trim());
|
|
3776
|
+
return row?.friendly_name ?? null;
|
|
3777
|
+
}, null),
|
|
3659
3778
|
findCronAndPaymentForObjectKey: async (walletAddress, objectKey) => safe(() => {
|
|
3660
3779
|
const w = normalizeWalletAddress(walletAddress);
|
|
3661
3780
|
const cron = db.prepare(
|
|
@@ -3692,20 +3811,19 @@ async function createCloudDatastore(homeDir) {
|
|
|
3692
3811
|
var SUPPORTED_BACKUP_PLATFORMS = /* @__PURE__ */ new Set(["darwin", "linux"]);
|
|
3693
3812
|
var BACKUP_DIR_SUBPATH = join8(".openclaw", "mnemospark", "backup");
|
|
3694
3813
|
var DEFAULT_BACKUP_DIR = join8(homedir6(), BACKUP_DIR_SUBPATH);
|
|
3695
|
-
var CRON_TABLE_SUBPATH = join8(".openclaw", "mnemospark", "crontab.txt");
|
|
3696
3814
|
var BLOCKRUN_WALLET_KEY_SUBPATH = join8(".openclaw", "blockrun", "wallet.key");
|
|
3697
3815
|
var MNEMOSPARK_WALLET_KEY_SUBPATH = join8(".openclaw", "mnemospark", "wallet", "wallet.key");
|
|
3698
3816
|
var INLINE_UPLOAD_MAX_BYTES = 45e5;
|
|
3699
|
-
var PAYMENT_REMINDER_INTERVAL_DAYS = 30;
|
|
3700
|
-
var PAYMENT_DELETE_DEADLINE_DAYS = 32;
|
|
3701
3817
|
var PAYMENT_CRON_SCHEDULE = "0 0 1 * *";
|
|
3702
3818
|
var TAR_OVERHEAD_BYTES = 10 * 1024 * 1024;
|
|
3703
3819
|
var QUOTE_VALIDITY_USER_NOTE = "Quotes are valid for one hour. Please run price-storage again if you need a new quote.";
|
|
3704
3820
|
var MNEMOSPARK_SUPPORT_EMAIL = "pluggedin@mnemospark.ai";
|
|
3705
|
-
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/
|
|
3821
|
+
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.";
|
|
3706
3822
|
var REQUIRED_PRICE_STORAGE = "--wallet-address, --object-id, --object-id-hash, --gb, --provider, --region";
|
|
3707
3823
|
var REQUIRED_UPLOAD = "--quote-id, --wallet-address, --object-id, --object-id-hash";
|
|
3708
|
-
var
|
|
3824
|
+
var REQUIRED_BACKUP = "<file|directory> and --name <friendly-name>";
|
|
3825
|
+
var REQUIRED_PAYMENT_SETTLE = "--wallet-address and (--quote-id | --renewal with --object-key)";
|
|
3826
|
+
var PAYMENT_SETTLE_BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["renewal"]);
|
|
3709
3827
|
var REQUIRED_STORAGE_OBJECT = "--wallet-address and one of (--object-key | --name [--latest|--at])";
|
|
3710
3828
|
var REQUIRED_LS = "--wallet-address (for one object add --object-key or --name [--latest|--at]; omit both to list the bucket)";
|
|
3711
3829
|
var PAYMENT_SETTLE_FLAG_NAMES = /* @__PURE__ */ new Set([
|
|
@@ -3713,7 +3831,8 @@ var PAYMENT_SETTLE_FLAG_NAMES = /* @__PURE__ */ new Set([
|
|
|
3713
3831
|
"wallet-address",
|
|
3714
3832
|
"object-id",
|
|
3715
3833
|
"object-key",
|
|
3716
|
-
"storage-price"
|
|
3834
|
+
"storage-price",
|
|
3835
|
+
"renewal"
|
|
3717
3836
|
]);
|
|
3718
3837
|
var BOOLEAN_SELECTOR_FLAGS = /* @__PURE__ */ new Set(["latest"]);
|
|
3719
3838
|
var BOOLEAN_ASYNC_FLAGS = /* @__PURE__ */ new Set(["async"]);
|
|
@@ -3737,9 +3856,9 @@ var CLOUD_HELP_TEXT = [
|
|
|
3737
3856
|
"",
|
|
3738
3857
|
"\u2022 `/mnemospark_cloud` or `/mnemospark_cloud help` \u2014 show this message",
|
|
3739
3858
|
"",
|
|
3740
|
-
"\u2022 `/mnemospark_cloud backup <file|directory>
|
|
3741
|
-
" Purpose: create a local tar+gzip archive under ~/.openclaw/mnemospark/backup and record metadata in SQLite for later price-storage and upload.",
|
|
3742
|
-
" Required:
|
|
3859
|
+
"\u2022 `/mnemospark_cloud backup <file|directory> --name <friendly-name> [--async] [--orchestrator <inline|subagent>] [--timeout-seconds <n>]`",
|
|
3860
|
+
" 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.",
|
|
3861
|
+
" Required: " + REQUIRED_BACKUP,
|
|
3743
3862
|
"",
|
|
3744
3863
|
"\u2022 `/mnemospark_cloud price-storage --wallet-address <addr> --object-id <id> --object-id-hash <hash> --gb <gb> --provider <provider> --region <region>`",
|
|
3745
3864
|
" Purpose: request a storage quote before upload.",
|
|
@@ -3749,9 +3868,9 @@ var CLOUD_HELP_TEXT = [
|
|
|
3749
3868
|
" Purpose: upload an encrypted object using a valid quote-id.",
|
|
3750
3869
|
" Required: " + REQUIRED_UPLOAD,
|
|
3751
3870
|
"",
|
|
3752
|
-
"\u2022 `/mnemospark_cloud payment-settle --quote-id <quote-id> --
|
|
3753
|
-
" Purpose: settle storage payment
|
|
3754
|
-
" Required:
|
|
3871
|
+
"\u2022 `/mnemospark_cloud payment-settle (--quote-id <quote-id> | --renewal --object-key <key>) --wallet-address <addr> [--object-id <id>] [--storage-price <n>]`",
|
|
3872
|
+
" 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.",
|
|
3873
|
+
" Required: " + REQUIRED_PAYMENT_SETTLE + " (wallet private key must match the address).",
|
|
3755
3874
|
"",
|
|
3756
3875
|
"\u2022 `/mnemospark_cloud ls --wallet-address <addr> [--object-key <key> | --name <friendly-name> | omit both to list bucket] [--latest|--at <timestamp>]`",
|
|
3757
3876
|
" Purpose: stat one object or list all keys in the wallet bucket (S3).",
|
|
@@ -3789,7 +3908,7 @@ var CLOUD_HELP_TEXT = [
|
|
|
3789
3908
|
"",
|
|
3790
3909
|
CLOUD_HELP_FOOTER_STATE,
|
|
3791
3910
|
"",
|
|
3792
|
-
"Commands price-storage, upload, ls, download, delete, and payment-settle require
|
|
3911
|
+
"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)."
|
|
3793
3912
|
].join("\n");
|
|
3794
3913
|
var UnsupportedBackupPlatformError = class extends Error {
|
|
3795
3914
|
constructor(platform) {
|
|
@@ -3988,10 +4107,14 @@ function parseCloudArgs(args) {
|
|
|
3988
4107
|
if (!asyncArgs) {
|
|
3989
4108
|
return { mode: "backup-invalid-async" };
|
|
3990
4109
|
}
|
|
4110
|
+
const friendlyName = flags.name?.trim();
|
|
4111
|
+
if (!friendlyName) {
|
|
4112
|
+
return { mode: "backup-invalid-name" };
|
|
4113
|
+
}
|
|
3991
4114
|
return {
|
|
3992
4115
|
mode: "backup",
|
|
3993
4116
|
backupTarget,
|
|
3994
|
-
friendlyName
|
|
4117
|
+
friendlyName,
|
|
3995
4118
|
...asyncArgs
|
|
3996
4119
|
};
|
|
3997
4120
|
}
|
|
@@ -4043,7 +4166,7 @@ function parseCloudArgs(args) {
|
|
|
4043
4166
|
};
|
|
4044
4167
|
}
|
|
4045
4168
|
if (subcommand === "payment-settle") {
|
|
4046
|
-
const flags = parseNamedFlags(rest);
|
|
4169
|
+
const flags = parseNamedFlags(rest, PAYMENT_SETTLE_BOOLEAN_FLAGS);
|
|
4047
4170
|
if (!flags) {
|
|
4048
4171
|
return { mode: "payment-settle-invalid" };
|
|
4049
4172
|
}
|
|
@@ -4052,9 +4175,21 @@ function parseCloudArgs(args) {
|
|
|
4052
4175
|
return { mode: "payment-settle-invalid" };
|
|
4053
4176
|
}
|
|
4054
4177
|
}
|
|
4055
|
-
const quoteId = flags["quote-id"]?.trim();
|
|
4056
4178
|
const walletAddress = flags["wallet-address"]?.trim();
|
|
4057
|
-
if (!
|
|
4179
|
+
if (!walletAddress) {
|
|
4180
|
+
return { mode: "payment-settle-invalid" };
|
|
4181
|
+
}
|
|
4182
|
+
const isRenewal = flags.renewal === "true";
|
|
4183
|
+
const quoteId = flags["quote-id"]?.trim();
|
|
4184
|
+
const objectKey = flags["object-key"]?.trim();
|
|
4185
|
+
if (isRenewal) {
|
|
4186
|
+
if (quoteId) {
|
|
4187
|
+
return { mode: "payment-settle-invalid" };
|
|
4188
|
+
}
|
|
4189
|
+
if (!objectKey) {
|
|
4190
|
+
return { mode: "payment-settle-invalid" };
|
|
4191
|
+
}
|
|
4192
|
+
} else if (!quoteId) {
|
|
4058
4193
|
return { mode: "payment-settle-invalid" };
|
|
4059
4194
|
}
|
|
4060
4195
|
let storagePrice;
|
|
@@ -4069,10 +4204,11 @@ function parseCloudArgs(args) {
|
|
|
4069
4204
|
return {
|
|
4070
4205
|
mode: "payment-settle",
|
|
4071
4206
|
paymentSettleRequest: {
|
|
4072
|
-
quote_id: quoteId,
|
|
4073
4207
|
wallet_address: walletAddress,
|
|
4208
|
+
renewal: isRenewal || void 0,
|
|
4209
|
+
quote_id: quoteId || void 0,
|
|
4074
4210
|
object_id: flags["object-id"]?.trim() || void 0,
|
|
4075
|
-
object_key:
|
|
4211
|
+
object_key: objectKey || void 0,
|
|
4076
4212
|
storage_price: storagePrice
|
|
4077
4213
|
}
|
|
4078
4214
|
};
|
|
@@ -4162,9 +4298,6 @@ function parseCloudArgs(args) {
|
|
|
4162
4298
|
}
|
|
4163
4299
|
return { mode: "unknown" };
|
|
4164
4300
|
}
|
|
4165
|
-
function resolveCronTablePath(homeDir) {
|
|
4166
|
-
return join8(homeDir ?? homedir6(), CRON_TABLE_SUBPATH);
|
|
4167
|
-
}
|
|
4168
4301
|
async function calculateInputSizeBytes(targetPath) {
|
|
4169
4302
|
const targetStats = await lstat(targetPath);
|
|
4170
4303
|
if (targetStats.isFile() || targetStats.isSymbolicLink()) {
|
|
@@ -4218,6 +4351,38 @@ async function sha256File(filePath) {
|
|
|
4218
4351
|
});
|
|
4219
4352
|
return hash.digest("hex");
|
|
4220
4353
|
}
|
|
4354
|
+
async function resolveLocalUploadArchivePath(backupDir, objectId, friendlyName) {
|
|
4355
|
+
if (friendlyName?.trim()) {
|
|
4356
|
+
try {
|
|
4357
|
+
const sanitized = sanitizeFriendlyNameForLocalBasename(friendlyName);
|
|
4358
|
+
const candidate = join8(backupDir, sanitized);
|
|
4359
|
+
try {
|
|
4360
|
+
const st = await stat2(candidate);
|
|
4361
|
+
if (st.isFile()) {
|
|
4362
|
+
return { ok: true, archivePath: candidate };
|
|
4363
|
+
}
|
|
4364
|
+
} catch {
|
|
4365
|
+
}
|
|
4366
|
+
} catch {
|
|
4367
|
+
}
|
|
4368
|
+
}
|
|
4369
|
+
const legacyPath = join8(backupDir, objectId);
|
|
4370
|
+
try {
|
|
4371
|
+
const legacyStats = await stat2(legacyPath);
|
|
4372
|
+
if (!legacyStats.isFile()) {
|
|
4373
|
+
return {
|
|
4374
|
+
ok: false,
|
|
4375
|
+
message: `Cannot upload storage object: local archive path is not a file (${legacyPath}).`
|
|
4376
|
+
};
|
|
4377
|
+
}
|
|
4378
|
+
return { ok: true, archivePath: legacyPath };
|
|
4379
|
+
} catch {
|
|
4380
|
+
return {
|
|
4381
|
+
ok: false,
|
|
4382
|
+
message: `Cannot upload storage object: local archive not found. Run /mnemospark_cloud backup with --name (canonical layout) or restore the legacy file at ${legacyPath}.`
|
|
4383
|
+
};
|
|
4384
|
+
}
|
|
4385
|
+
}
|
|
4221
4386
|
function createObjectId(options) {
|
|
4222
4387
|
const nowFn = options.now ?? Date.now;
|
|
4223
4388
|
const randomFn = options.randomBytes ?? randomBytesNode;
|
|
@@ -4255,7 +4420,25 @@ async function buildBackupObject(targetPathArg, options = {}) {
|
|
|
4255
4420
|
throw new Error("Insufficient disk space for backup object");
|
|
4256
4421
|
}
|
|
4257
4422
|
const objectId = createObjectId(options);
|
|
4258
|
-
const
|
|
4423
|
+
const archiveBaseSegment = options.archiveBasename?.trim() || objectId;
|
|
4424
|
+
const archivePath = join8(tmpDir, archiveBaseSegment);
|
|
4425
|
+
if (options.archiveBasename?.trim()) {
|
|
4426
|
+
try {
|
|
4427
|
+
const existing = await stat2(archivePath);
|
|
4428
|
+
if (existing.isFile() || existing.isDirectory()) {
|
|
4429
|
+
throw new Error(
|
|
4430
|
+
`Backup archive path already exists: ${archivePath}. Choose a different --name.`
|
|
4431
|
+
);
|
|
4432
|
+
}
|
|
4433
|
+
} catch (err) {
|
|
4434
|
+
if (err instanceof Error && err.message.includes("already exists")) {
|
|
4435
|
+
throw err;
|
|
4436
|
+
}
|
|
4437
|
+
if (err.code !== "ENOENT") {
|
|
4438
|
+
throw err;
|
|
4439
|
+
}
|
|
4440
|
+
}
|
|
4441
|
+
}
|
|
4259
4442
|
try {
|
|
4260
4443
|
await runTarGzip(archivePath, targetPath);
|
|
4261
4444
|
const archiveStats = await stat2(archivePath);
|
|
@@ -4272,84 +4455,126 @@ async function buildBackupObject(targetPathArg, options = {}) {
|
|
|
4272
4455
|
throw error;
|
|
4273
4456
|
}
|
|
4274
4457
|
}
|
|
4275
|
-
function
|
|
4276
|
-
|
|
4277
|
-
return [
|
|
4278
|
-
date.getFullYear().toString(),
|
|
4279
|
-
"-",
|
|
4280
|
-
pad(date.getMonth() + 1),
|
|
4281
|
-
"-",
|
|
4282
|
-
pad(date.getDate()),
|
|
4283
|
-
" ",
|
|
4284
|
-
pad(date.getHours()),
|
|
4285
|
-
":",
|
|
4286
|
-
pad(date.getMinutes()),
|
|
4287
|
-
":",
|
|
4288
|
-
pad(date.getSeconds())
|
|
4289
|
-
].join("");
|
|
4290
|
-
}
|
|
4291
|
-
function parseStoragePaymentCronJobLine(line) {
|
|
4292
|
-
const trimmed = line.trim();
|
|
4293
|
-
if (!trimmed) {
|
|
4458
|
+
function normalizeOpenClawCronJobForLookup(value) {
|
|
4459
|
+
if (!value || typeof value !== "object") {
|
|
4294
4460
|
return null;
|
|
4295
4461
|
}
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4462
|
+
const record = value;
|
|
4463
|
+
const jobIdRaw = typeof record.jobId === "string" ? record.jobId : record.id;
|
|
4464
|
+
const jobId = typeof jobIdRaw === "string" ? jobIdRaw.trim() : "";
|
|
4465
|
+
const payloadRaw = record.payload;
|
|
4466
|
+
if (!jobId || !payloadRaw || typeof payloadRaw !== "object") {
|
|
4300
4467
|
return null;
|
|
4301
4468
|
}
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
const cronId = typeof record.cronId === "string" ? record.cronId.trim() : "";
|
|
4307
|
-
const createdAt = typeof record.createdAt === "string" ? record.createdAt.trim() : "";
|
|
4308
|
-
const schedule = typeof record.schedule === "string" ? record.schedule.trim() : "";
|
|
4309
|
-
const command = typeof record.command === "string" ? record.command.trim() : "";
|
|
4310
|
-
const quoteId = typeof record.quoteId === "string" ? record.quoteId.trim() : "";
|
|
4311
|
-
const storagePrice = typeof record.storagePrice === "number" ? record.storagePrice : Number.NaN;
|
|
4312
|
-
const walletAddress = typeof record.walletAddress === "string" ? record.walletAddress.trim() : "";
|
|
4313
|
-
const objectId = typeof record.objectId === "string" ? record.objectId.trim() : "";
|
|
4314
|
-
const objectKey = typeof record.objectKey === "string" ? record.objectKey.trim() : "";
|
|
4315
|
-
const provider = typeof record.provider === "string" ? record.provider.trim() : "";
|
|
4316
|
-
const bucketName = typeof record.bucketName === "string" ? record.bucketName.trim() : "";
|
|
4317
|
-
const location = typeof record.location === "string" ? record.location.trim() : "";
|
|
4318
|
-
if (!cronId || !createdAt || !schedule || !command || !quoteId || !Number.isFinite(storagePrice) || storagePrice <= 0 || !walletAddress || !objectId || !objectKey || !provider || !bucketName || !location) {
|
|
4469
|
+
const payloadRecord = payloadRaw;
|
|
4470
|
+
const payloadKind = payloadRecord.kind;
|
|
4471
|
+
const payloadMessage = typeof payloadRecord.message === "string" ? payloadRecord.message.trim() : "";
|
|
4472
|
+
if (payloadKind !== "agentTurn" || !payloadMessage) {
|
|
4319
4473
|
return null;
|
|
4320
4474
|
}
|
|
4321
4475
|
return {
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
schedule,
|
|
4325
|
-
command,
|
|
4326
|
-
quoteId,
|
|
4327
|
-
storagePrice,
|
|
4328
|
-
walletAddress,
|
|
4329
|
-
objectId,
|
|
4330
|
-
objectKey,
|
|
4331
|
-
provider,
|
|
4332
|
-
bucketName,
|
|
4333
|
-
location
|
|
4476
|
+
jobId,
|
|
4477
|
+
message: payloadMessage
|
|
4334
4478
|
};
|
|
4335
4479
|
}
|
|
4336
|
-
async function
|
|
4337
|
-
|
|
4338
|
-
|
|
4480
|
+
async function runOpenClawCli(args, homeDir) {
|
|
4481
|
+
return await new Promise((resolvePromise, rejectPromise) => {
|
|
4482
|
+
let stdout = "";
|
|
4483
|
+
let stderr = "";
|
|
4484
|
+
const child = spawn("openclaw", args, {
|
|
4485
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
4486
|
+
env: {
|
|
4487
|
+
...process.env,
|
|
4488
|
+
HOME: homeDir ?? process.env.HOME
|
|
4489
|
+
}
|
|
4490
|
+
});
|
|
4491
|
+
child.stdout.on("data", (chunk) => {
|
|
4492
|
+
stdout += chunk.toString();
|
|
4493
|
+
});
|
|
4494
|
+
child.stderr.on("data", (chunk) => {
|
|
4495
|
+
stderr += chunk.toString();
|
|
4496
|
+
});
|
|
4497
|
+
child.on("error", rejectPromise);
|
|
4498
|
+
child.on("close", (code) => {
|
|
4499
|
+
if (code === 0) {
|
|
4500
|
+
resolvePromise({ stdout, stderr });
|
|
4501
|
+
return;
|
|
4502
|
+
}
|
|
4503
|
+
rejectPromise(
|
|
4504
|
+
new Error(
|
|
4505
|
+
stderr.trim() || stdout.trim() || `openclaw ${args.join(" ")} exited with code ${code ?? "unknown"}`
|
|
4506
|
+
)
|
|
4507
|
+
);
|
|
4508
|
+
});
|
|
4509
|
+
});
|
|
4510
|
+
}
|
|
4511
|
+
function parseOpenClawCliJson(stdout, commandLabel) {
|
|
4512
|
+
const trimmed = stdout.trim();
|
|
4513
|
+
if (!trimmed) {
|
|
4514
|
+
throw new Error(`openclaw ${commandLabel} returned empty JSON output`);
|
|
4515
|
+
}
|
|
4339
4516
|
try {
|
|
4340
|
-
|
|
4341
|
-
} catch
|
|
4342
|
-
|
|
4343
|
-
return null;
|
|
4344
|
-
}
|
|
4345
|
-
throw error;
|
|
4517
|
+
return JSON.parse(trimmed);
|
|
4518
|
+
} catch {
|
|
4519
|
+
throw new Error(`openclaw ${commandLabel} returned invalid JSON output`);
|
|
4346
4520
|
}
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4521
|
+
}
|
|
4522
|
+
function createOpenClawCliCronAdapter(homeDir) {
|
|
4523
|
+
return {
|
|
4524
|
+
add: async (job) => {
|
|
4525
|
+
const { stdout } = await runOpenClawCli(
|
|
4526
|
+
[
|
|
4527
|
+
"cron",
|
|
4528
|
+
"add",
|
|
4529
|
+
"--name",
|
|
4530
|
+
job.name,
|
|
4531
|
+
"--cron",
|
|
4532
|
+
job.schedule.expr,
|
|
4533
|
+
"--tz",
|
|
4534
|
+
job.schedule.tz,
|
|
4535
|
+
"--session",
|
|
4536
|
+
job.sessionTarget,
|
|
4537
|
+
"--message",
|
|
4538
|
+
job.payload.message,
|
|
4539
|
+
"--announce",
|
|
4540
|
+
"--description",
|
|
4541
|
+
job.delivery.text,
|
|
4542
|
+
"--json"
|
|
4543
|
+
],
|
|
4544
|
+
homeDir
|
|
4545
|
+
);
|
|
4546
|
+
const payload = parseOpenClawCliJson(stdout, "cron add");
|
|
4547
|
+
const createdIdRaw = typeof payload.id === "string" ? payload.id : payload.jobId;
|
|
4548
|
+
const createdId = typeof createdIdRaw === "string" ? createdIdRaw.trim() : "";
|
|
4549
|
+
if (!createdId) {
|
|
4550
|
+
throw new Error("openclaw cron add did not return a job id");
|
|
4551
|
+
}
|
|
4552
|
+
return { jobId: createdId };
|
|
4553
|
+
},
|
|
4554
|
+
remove: async (jobId) => {
|
|
4555
|
+
const { stdout } = await runOpenClawCli(["cron", "rm", jobId, "--json"], homeDir);
|
|
4556
|
+
const payload = parseOpenClawCliJson(stdout, "cron rm");
|
|
4557
|
+
if (typeof payload.removed === "boolean") {
|
|
4558
|
+
return payload.removed;
|
|
4559
|
+
}
|
|
4560
|
+
return true;
|
|
4561
|
+
},
|
|
4562
|
+
list: async () => {
|
|
4563
|
+
const { stdout } = await runOpenClawCli(["cron", "list", "--all", "--json"], homeDir);
|
|
4564
|
+
const payload = parseOpenClawCliJson(stdout, "cron list");
|
|
4565
|
+
const jobsRaw = Array.isArray(payload.jobs) ? payload.jobs : [];
|
|
4566
|
+
return jobsRaw.map((entry) => normalizeOpenClawCronJobForLookup(entry)).filter((entry) => entry !== null);
|
|
4567
|
+
}
|
|
4568
|
+
};
|
|
4569
|
+
}
|
|
4570
|
+
async function findCronJobInOpenClawCronJobsByObjectKey(objectKey, adapter) {
|
|
4571
|
+
const cronJobs = await adapter.list();
|
|
4572
|
+
for (let idx = cronJobs.length - 1; idx >= 0; idx -= 1) {
|
|
4573
|
+
const cronJob = cronJobs[idx];
|
|
4574
|
+
const parsed = parseStoragePaymentCronCommand(cronJob.message);
|
|
4350
4575
|
if (parsed && parsed.objectKey === objectKey) {
|
|
4351
4576
|
return {
|
|
4352
|
-
cronId:
|
|
4577
|
+
cronId: cronJob.jobId,
|
|
4353
4578
|
objectId: parsed.objectId,
|
|
4354
4579
|
objectKey: parsed.objectKey
|
|
4355
4580
|
};
|
|
@@ -4364,8 +4589,7 @@ function buildStoragePaymentCronCommand(job) {
|
|
|
4364
4589
|
return [
|
|
4365
4590
|
"/mnemospark_cloud",
|
|
4366
4591
|
"payment-settle",
|
|
4367
|
-
"--
|
|
4368
|
-
quoteCronArgument(job.quoteId),
|
|
4592
|
+
"--renewal",
|
|
4369
4593
|
"--wallet-address",
|
|
4370
4594
|
quoteCronArgument(job.walletAddress),
|
|
4371
4595
|
"--object-id",
|
|
@@ -4376,57 +4600,67 @@ function buildStoragePaymentCronCommand(job) {
|
|
|
4376
4600
|
quoteCronArgument(job.storagePrice)
|
|
4377
4601
|
].join(" ");
|
|
4378
4602
|
}
|
|
4379
|
-
|
|
4380
|
-
const
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
return cronTablePath;
|
|
4385
|
-
}
|
|
4386
|
-
async function removeStoragePaymentCronJob(cronId, homeDir) {
|
|
4387
|
-
const cronTablePath = resolveCronTablePath(homeDir);
|
|
4388
|
-
let content;
|
|
4389
|
-
try {
|
|
4390
|
-
content = await readFile3(cronTablePath, "utf-8");
|
|
4391
|
-
} catch (error) {
|
|
4392
|
-
if (error.code === "ENOENT") {
|
|
4393
|
-
return false;
|
|
4394
|
-
}
|
|
4395
|
-
throw error;
|
|
4603
|
+
function parseStoragePaymentCronCommand(command) {
|
|
4604
|
+
const objectIdMatch = command.match(/--object-id\s+("([^"\\]|\\.)*"|'([^'\\]|\\.)*'|\S+)/);
|
|
4605
|
+
const objectKeyMatch = command.match(/--object-key\s+("([^"\\]|\\.)*"|'([^'\\]|\\.)*'|\S+)/);
|
|
4606
|
+
if (!objectIdMatch || !objectKeyMatch) {
|
|
4607
|
+
return null;
|
|
4396
4608
|
}
|
|
4397
|
-
const
|
|
4398
|
-
|
|
4399
|
-
const keptLines = [];
|
|
4400
|
-
for (const line of lines) {
|
|
4401
|
-
const trimmed = line.trim();
|
|
4609
|
+
const parseToken = (token) => {
|
|
4610
|
+
const trimmed = token.trim();
|
|
4402
4611
|
if (!trimmed) {
|
|
4403
|
-
|
|
4612
|
+
return null;
|
|
4404
4613
|
}
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4614
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
4615
|
+
try {
|
|
4616
|
+
return JSON.parse(trimmed);
|
|
4617
|
+
} catch {
|
|
4618
|
+
return trimmed.slice(1, -1);
|
|
4619
|
+
}
|
|
4409
4620
|
}
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
return
|
|
4621
|
+
if (trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
4622
|
+
return trimmed.slice(1, -1);
|
|
4623
|
+
}
|
|
4624
|
+
return trimmed;
|
|
4625
|
+
};
|
|
4626
|
+
const objectId = parseToken(objectIdMatch[1] ?? "");
|
|
4627
|
+
const objectKey = parseToken(objectKeyMatch[1] ?? "");
|
|
4628
|
+
if (!objectId || !objectKey) {
|
|
4629
|
+
return null;
|
|
4414
4630
|
}
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4631
|
+
return { objectId, objectKey };
|
|
4632
|
+
}
|
|
4633
|
+
async function appendStoragePaymentCronJob(cronJob, adapter) {
|
|
4634
|
+
const openClawJob = {
|
|
4635
|
+
jobId: cronJob.cronId,
|
|
4636
|
+
name: "Mnemospark Monthly Renewal",
|
|
4637
|
+
schedule: {
|
|
4638
|
+
kind: "cron",
|
|
4639
|
+
expr: PAYMENT_CRON_SCHEDULE,
|
|
4640
|
+
tz: "UTC"
|
|
4641
|
+
},
|
|
4642
|
+
payload: {
|
|
4643
|
+
kind: "agentTurn",
|
|
4644
|
+
message: cronJob.command
|
|
4645
|
+
},
|
|
4646
|
+
sessionTarget: "isolated",
|
|
4647
|
+
delivery: {
|
|
4648
|
+
mode: "announce",
|
|
4649
|
+
text: "Thank you for using mnemospark cloud storage. Your renewal has been processed."
|
|
4650
|
+
}
|
|
4651
|
+
};
|
|
4652
|
+
return adapter.add(openClawJob);
|
|
4420
4653
|
}
|
|
4421
|
-
async function
|
|
4422
|
-
|
|
4423
|
-
|
|
4654
|
+
async function removeStoragePaymentCronJob(cronId, adapter) {
|
|
4655
|
+
return adapter.remove(cronId);
|
|
4656
|
+
}
|
|
4657
|
+
async function createStoragePaymentCronJob(upload, storagePrice, openClawCronAdapter, nowDateFn = () => /* @__PURE__ */ new Date()) {
|
|
4658
|
+
const provisionalCronId = randomUUID3();
|
|
4424
4659
|
const cronJob = {
|
|
4425
|
-
cronId,
|
|
4426
|
-
createdAt,
|
|
4660
|
+
cronId: provisionalCronId,
|
|
4661
|
+
createdAt: nowDateFn().toISOString(),
|
|
4427
4662
|
schedule: PAYMENT_CRON_SCHEDULE,
|
|
4428
4663
|
command: buildStoragePaymentCronCommand({
|
|
4429
|
-
quoteId: upload.quote_id,
|
|
4430
4664
|
walletAddress: upload.addr,
|
|
4431
4665
|
objectId: upload.object_id,
|
|
4432
4666
|
objectKey: upload.object_key,
|
|
@@ -4441,7 +4675,10 @@ async function createStoragePaymentCronJob(upload, storagePrice, homeDir, nowDat
|
|
|
4441
4675
|
bucketName: upload.bucket_name,
|
|
4442
4676
|
location: upload.location
|
|
4443
4677
|
};
|
|
4444
|
-
await appendStoragePaymentCronJob(cronJob,
|
|
4678
|
+
const created = await appendStoragePaymentCronJob(cronJob, openClawCronAdapter);
|
|
4679
|
+
if (created.jobId?.trim()) {
|
|
4680
|
+
cronJob.cronId = created.jobId.trim();
|
|
4681
|
+
}
|
|
4445
4682
|
return cronJob;
|
|
4446
4683
|
}
|
|
4447
4684
|
async function readWalletKeyIfPresent(walletPath) {
|
|
@@ -4571,13 +4808,32 @@ async function uploadPresignedObjectIfNeeded(uploadResponse, uploadMode, encrypt
|
|
|
4571
4808
|
`Presigned upload failed with status ${firstAttempt.status}${details ? `: ${details}` : ""}`
|
|
4572
4809
|
);
|
|
4573
4810
|
}
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4811
|
+
function envMeansExplicitRemoveOrKeep(value) {
|
|
4812
|
+
if (value === void 0) {
|
|
4813
|
+
return null;
|
|
4814
|
+
}
|
|
4815
|
+
const trimmed = value.trim();
|
|
4816
|
+
if (!trimmed) {
|
|
4817
|
+
return null;
|
|
4578
4818
|
}
|
|
4579
|
-
const
|
|
4580
|
-
if (
|
|
4819
|
+
const n = trimmed.toLowerCase();
|
|
4820
|
+
if (n === "0" || n === "false" || n === "no" || n === "n") {
|
|
4821
|
+
return false;
|
|
4822
|
+
}
|
|
4823
|
+
if (n === "1" || n === "true" || n === "yes" || n === "y") {
|
|
4824
|
+
return true;
|
|
4825
|
+
}
|
|
4826
|
+
return null;
|
|
4827
|
+
}
|
|
4828
|
+
function shouldRemoveLocalBackupAfterUpload() {
|
|
4829
|
+
const parsed = envMeansExplicitRemoveOrKeep(process.env.MNEMOSPARK_REMOVE_BACKUP_FILE);
|
|
4830
|
+
if (parsed !== null) {
|
|
4831
|
+
return parsed;
|
|
4832
|
+
}
|
|
4833
|
+
return true;
|
|
4834
|
+
}
|
|
4835
|
+
async function maybeCleanupLocalBackupArchive(archivePath) {
|
|
4836
|
+
if (!shouldRemoveLocalBackupAfterUpload()) {
|
|
4581
4837
|
return;
|
|
4582
4838
|
}
|
|
4583
4839
|
try {
|
|
@@ -4588,9 +4844,9 @@ async function maybeCleanupLocalBackupArchive(archivePath) {
|
|
|
4588
4844
|
function formatStorageUploadUserMessage(upload, cronJobId) {
|
|
4589
4845
|
const lsLine = `/mnemospark_cloud ls --wallet-address \`${upload.addr}\``;
|
|
4590
4846
|
return [
|
|
4591
|
-
`Your file \`${upload.object_id}\` with key \`${upload.object_key}\` has been stored using \`${upload.provider}\` in \`${upload.bucket_name}\` \`${upload.location}\``,
|
|
4847
|
+
`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}\``,
|
|
4592
4848
|
"",
|
|
4593
|
-
`A cron job \`${cronJobId}\` has been configured to send payment monthly (
|
|
4849
|
+
`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).`,
|
|
4594
4850
|
"",
|
|
4595
4851
|
"To view your cloud storage run the command:",
|
|
4596
4852
|
"",
|
|
@@ -4643,32 +4899,40 @@ function extractLsErrorMessage(error) {
|
|
|
4643
4899
|
}
|
|
4644
4900
|
return null;
|
|
4645
4901
|
}
|
|
4646
|
-
function formatPriceStorageUserMessage(quote) {
|
|
4902
|
+
function formatPriceStorageUserMessage(quote, localArchiveHint) {
|
|
4647
4903
|
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}\``;
|
|
4648
|
-
|
|
4649
|
-
`Your storage quote \`${quote.quote_id}\`: storage price
|
|
4904
|
+
const lines = [
|
|
4905
|
+
`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}\`.`,
|
|
4650
4906
|
"",
|
|
4651
4907
|
"If you accept this quote, run:",
|
|
4652
4908
|
"",
|
|
4653
4909
|
uploadLine,
|
|
4654
|
-
""
|
|
4655
|
-
|
|
4656
|
-
|
|
4910
|
+
""
|
|
4911
|
+
];
|
|
4912
|
+
if (localArchiveHint?.trim()) {
|
|
4913
|
+
lines.push(
|
|
4914
|
+
`Local backup archive uses friendly name \`${localArchiveHint.trim()}\` (on-disk basename is sanitized).`,
|
|
4915
|
+
""
|
|
4916
|
+
);
|
|
4917
|
+
}
|
|
4918
|
+
lines.push(QUOTE_VALIDITY_USER_NOTE);
|
|
4919
|
+
return lines.join("\n");
|
|
4657
4920
|
}
|
|
4658
4921
|
function quoteLookupMatchesPriceStorageResponse(lookup, quote) {
|
|
4659
4922
|
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;
|
|
4660
4923
|
}
|
|
4661
|
-
function formatBackupSuccessUserMessage(result) {
|
|
4924
|
+
function formatBackupSuccessUserMessage(result, walletAddress, friendlyName) {
|
|
4662
4925
|
const hash = result.objectIdHash.replace(/\s/g, "");
|
|
4663
|
-
const priceStorageLine = `/mnemospark_cloud price-storage --wallet-address
|
|
4926
|
+
const priceStorageLine = `/mnemospark_cloud price-storage --wallet-address \`${walletAddress}\` --object-id \`${result.objectId}\` --object-id-hash \`${hash}\` --gb \`${result.objectSizeGb}\` --provider <provider> --region <region>`;
|
|
4664
4927
|
return [
|
|
4665
4928
|
`Backup archive: \`${result.archivePath}\``,
|
|
4666
4929
|
"",
|
|
4930
|
+
`friendly-name: ${friendlyName}`,
|
|
4667
4931
|
`object-id: ${result.objectId}`,
|
|
4668
4932
|
`object-id-hash: ${hash}`,
|
|
4669
4933
|
`object-size: ${result.objectSizeGb}`,
|
|
4670
4934
|
"",
|
|
4671
|
-
"Next, request a storage quote. Replace `<
|
|
4935
|
+
"Next, request a storage quote. Replace `<provider>` and `<region>` (one line):",
|
|
4672
4936
|
"",
|
|
4673
4937
|
priceStorageLine,
|
|
4674
4938
|
"",
|
|
@@ -4804,6 +5068,9 @@ function createCloudCommand(options = {}) {
|
|
|
4804
5068
|
requestStorageDeleteFn: options.requestStorageDeleteFn ?? requestStorageDeleteViaProxy,
|
|
4805
5069
|
requestPaymentSettleViaProxyFn: options.requestPaymentSettleViaProxyFn ?? requestPaymentSettleViaProxy,
|
|
4806
5070
|
mnemosparkHomeDir: options.mnemosparkHomeDir ?? options.backupOptions?.homeDir,
|
|
5071
|
+
openClawCronAdapter: options.openClawCronAdapter ?? createOpenClawCliCronAdapter(
|
|
5072
|
+
options.mnemosparkHomeDir ?? options.backupOptions?.homeDir
|
|
5073
|
+
),
|
|
4807
5074
|
backupOptions: options.backupOptions,
|
|
4808
5075
|
proxyQuoteOptions: options.proxyQuoteOptions,
|
|
4809
5076
|
proxyUploadOptions: options.proxyUploadOptions,
|
|
@@ -4956,10 +5223,26 @@ function parseTransIdFromPaymentSettleBody(bodyText) {
|
|
|
4956
5223
|
return null;
|
|
4957
5224
|
}
|
|
4958
5225
|
}
|
|
5226
|
+
function parseQuoteIdFromPaymentSettleBody(bodyText) {
|
|
5227
|
+
const trimmed = bodyText.trim();
|
|
5228
|
+
if (!trimmed.startsWith("{")) {
|
|
5229
|
+
return null;
|
|
5230
|
+
}
|
|
5231
|
+
try {
|
|
5232
|
+
const parsed = JSON.parse(trimmed);
|
|
5233
|
+
const q = parsed.quote_id;
|
|
5234
|
+
return typeof q === "string" && q.trim() ? q.trim() : null;
|
|
5235
|
+
} catch {
|
|
5236
|
+
return null;
|
|
5237
|
+
}
|
|
5238
|
+
}
|
|
4959
5239
|
async function resolveAmountForPaymentSettle(quoteId, storagePriceFromFlag, datastore) {
|
|
4960
5240
|
if (storagePriceFromFlag !== void 0 && Number.isFinite(storagePriceFromFlag)) {
|
|
4961
5241
|
return storagePriceFromFlag;
|
|
4962
5242
|
}
|
|
5243
|
+
if (!quoteId) {
|
|
5244
|
+
return 0;
|
|
5245
|
+
}
|
|
4963
5246
|
const quoteLookup = await datastore.findQuoteById(quoteId);
|
|
4964
5247
|
if (quoteLookup && Number.isFinite(quoteLookup.storagePrice)) {
|
|
4965
5248
|
return quoteLookup.storagePrice;
|
|
@@ -4970,6 +5253,20 @@ async function resolveAmountForPaymentSettle(quoteId, storagePriceFromFlag, data
|
|
|
4970
5253
|
}
|
|
4971
5254
|
return 0;
|
|
4972
5255
|
}
|
|
5256
|
+
async function resolveCronRowForPaymentSettle(req, datastore) {
|
|
5257
|
+
if (req.renewal) {
|
|
5258
|
+
const key = req.object_key?.trim();
|
|
5259
|
+
if (!key) {
|
|
5260
|
+
return null;
|
|
5261
|
+
}
|
|
5262
|
+
return datastore.findCronJobRowByObjectKey(key);
|
|
5263
|
+
}
|
|
5264
|
+
const qid = req.quote_id?.trim();
|
|
5265
|
+
if (!qid) {
|
|
5266
|
+
return null;
|
|
5267
|
+
}
|
|
5268
|
+
return datastore.findCronByQuoteId(qid);
|
|
5269
|
+
}
|
|
4973
5270
|
async function emitPaymentSettleClientObservationBestEffort(params) {
|
|
4974
5271
|
try {
|
|
4975
5272
|
const {
|
|
@@ -5069,6 +5366,7 @@ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
|
|
|
5069
5366
|
const requestStorageDownload = options.requestStorageDownloadFn;
|
|
5070
5367
|
const requestStorageDelete = options.requestStorageDeleteFn;
|
|
5071
5368
|
const requestPaymentSettleViaProxy2 = options.requestPaymentSettleViaProxyFn;
|
|
5369
|
+
const openClawCronAdapter = options.openClawCronAdapter;
|
|
5072
5370
|
const subagentOrchestrator = options.subagentOrchestrator;
|
|
5073
5371
|
if (parsed.mode === "help" || parsed.mode === "unknown") {
|
|
5074
5372
|
return {
|
|
@@ -5094,6 +5392,12 @@ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
|
|
|
5094
5392
|
isError: true
|
|
5095
5393
|
};
|
|
5096
5394
|
}
|
|
5395
|
+
if (parsed.mode === "backup-invalid-name") {
|
|
5396
|
+
return {
|
|
5397
|
+
text: `Cannot build storage object: required arguments are ${REQUIRED_BACKUP}.`,
|
|
5398
|
+
isError: true
|
|
5399
|
+
};
|
|
5400
|
+
}
|
|
5097
5401
|
if (parsed.mode === "upload-invalid") {
|
|
5098
5402
|
return {
|
|
5099
5403
|
text: `Cannot upload storage object: required arguments are ${REQUIRED_UPLOAD}.`,
|
|
@@ -5108,7 +5412,7 @@ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
|
|
|
5108
5412
|
}
|
|
5109
5413
|
if (parsed.mode === "payment-settle-invalid") {
|
|
5110
5414
|
return {
|
|
5111
|
-
text: `Cannot settle payment: required arguments are ${REQUIRED_PAYMENT_SETTLE}. Optional: --object-id, --
|
|
5415
|
+
text: `Cannot settle payment: required arguments are ${REQUIRED_PAYMENT_SETTLE}. Optional: --object-id, --storage-price.`,
|
|
5112
5416
|
isError: true
|
|
5113
5417
|
};
|
|
5114
5418
|
}
|
|
@@ -5276,10 +5580,11 @@ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
|
|
|
5276
5580
|
const settleFetch = createPayment(walletKey).fetch;
|
|
5277
5581
|
const objectId = req.object_id;
|
|
5278
5582
|
const objectKey = req.object_key;
|
|
5583
|
+
const logQuoteId = req.quote_id?.trim() || (req.renewal && objectKey?.trim() ? `renewal:${objectKey.trim()}` : "");
|
|
5279
5584
|
await emitPaymentSettleClientObservationBestEffort({
|
|
5280
5585
|
phase: "start",
|
|
5281
5586
|
correlation,
|
|
5282
|
-
quoteId:
|
|
5587
|
+
quoteId: logQuoteId,
|
|
5283
5588
|
walletAddress: req.wallet_address,
|
|
5284
5589
|
objectId,
|
|
5285
5590
|
objectKey,
|
|
@@ -5287,10 +5592,12 @@ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
|
|
|
5287
5592
|
});
|
|
5288
5593
|
let settleResult;
|
|
5289
5594
|
try {
|
|
5290
|
-
settleResult = await requestPaymentSettleViaProxy2(req.quote_id, req.wallet_address, {
|
|
5595
|
+
settleResult = await requestPaymentSettleViaProxy2(req.quote_id ?? "", req.wallet_address, {
|
|
5291
5596
|
...options.proxySettleOptions,
|
|
5292
5597
|
correlation,
|
|
5293
|
-
fetchImpl: (input, init) => settleFetch(input, init)
|
|
5598
|
+
fetchImpl: (input, init) => settleFetch(input, init),
|
|
5599
|
+
renewal: req.renewal === true,
|
|
5600
|
+
objectKey: req.object_key
|
|
5294
5601
|
});
|
|
5295
5602
|
} catch (err) {
|
|
5296
5603
|
const amountErr = await resolveAmountForPaymentSettle(
|
|
@@ -5299,7 +5606,7 @@ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
|
|
|
5299
5606
|
datastore
|
|
5300
5607
|
);
|
|
5301
5608
|
await datastore.upsertPayment({
|
|
5302
|
-
quote_id: req.quote_id,
|
|
5609
|
+
quote_id: logQuoteId || req.quote_id?.trim() || "payment-settle-error",
|
|
5303
5610
|
wallet_address: req.wallet_address,
|
|
5304
5611
|
trans_id: null,
|
|
5305
5612
|
amount: amountErr,
|
|
@@ -5307,7 +5614,7 @@ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
|
|
|
5307
5614
|
status: "settle_failed",
|
|
5308
5615
|
settled_at: null
|
|
5309
5616
|
});
|
|
5310
|
-
const cronErr = await
|
|
5617
|
+
const cronErr = await resolveCronRowForPaymentSettle(req, datastore);
|
|
5311
5618
|
if (cronErr) {
|
|
5312
5619
|
await datastore.upsertCronJob({ ...cronErr, status: "active" });
|
|
5313
5620
|
}
|
|
@@ -5315,7 +5622,7 @@ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
|
|
|
5315
5622
|
await emitPaymentSettleClientObservationBestEffort({
|
|
5316
5623
|
phase: "result",
|
|
5317
5624
|
correlation,
|
|
5318
|
-
quoteId:
|
|
5625
|
+
quoteId: logQuoteId,
|
|
5319
5626
|
walletAddress: req.wallet_address,
|
|
5320
5627
|
objectId,
|
|
5321
5628
|
objectKey,
|
|
@@ -5324,11 +5631,17 @@ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
|
|
|
5324
5631
|
});
|
|
5325
5632
|
return { text: `Payment settle failed: ${msg}`, isError: true };
|
|
5326
5633
|
}
|
|
5327
|
-
const
|
|
5634
|
+
const ledgerQuoteIdFromBody = parseQuoteIdFromPaymentSettleBody(settleResult.bodyText ?? "");
|
|
5635
|
+
const ledgerQuoteId = ledgerQuoteIdFromBody || req.quote_id?.trim() || logQuoteId;
|
|
5636
|
+
const amount = await resolveAmountForPaymentSettle(
|
|
5637
|
+
req.renewal ? void 0 : req.quote_id,
|
|
5638
|
+
req.storage_price,
|
|
5639
|
+
datastore
|
|
5640
|
+
);
|
|
5328
5641
|
const transId = settleResult.status === 200 ? parseTransIdFromPaymentSettleBody(settleResult.bodyText ?? "") : null;
|
|
5329
5642
|
if (settleResult.status === 200) {
|
|
5330
5643
|
await datastore.upsertPayment({
|
|
5331
|
-
quote_id:
|
|
5644
|
+
quote_id: ledgerQuoteId,
|
|
5332
5645
|
wallet_address: req.wallet_address,
|
|
5333
5646
|
trans_id: transId,
|
|
5334
5647
|
amount,
|
|
@@ -5336,14 +5649,14 @@ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
|
|
|
5336
5649
|
status: "settled",
|
|
5337
5650
|
settled_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
5338
5651
|
});
|
|
5339
|
-
const cronRow = await
|
|
5652
|
+
const cronRow = await resolveCronRowForPaymentSettle(req, datastore);
|
|
5340
5653
|
if (cronRow) {
|
|
5341
5654
|
await datastore.upsertCronJob({ ...cronRow, status: "active" });
|
|
5342
5655
|
}
|
|
5343
5656
|
await emitPaymentSettleClientObservationBestEffort({
|
|
5344
5657
|
phase: "result",
|
|
5345
5658
|
correlation,
|
|
5346
|
-
quoteId:
|
|
5659
|
+
quoteId: ledgerQuoteId,
|
|
5347
5660
|
walletAddress: req.wallet_address,
|
|
5348
5661
|
objectId,
|
|
5349
5662
|
objectKey,
|
|
@@ -5351,12 +5664,13 @@ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
|
|
|
5351
5664
|
outcomeStatus: "succeeded",
|
|
5352
5665
|
homeDir: mnemosparkHomeDir
|
|
5353
5666
|
});
|
|
5667
|
+
const label = req.renewal ? `object ${req.object_key}` : `quote ${req.quote_id}`;
|
|
5354
5668
|
return {
|
|
5355
|
-
text: transId ? `Payment settled for
|
|
5669
|
+
text: transId ? `Payment settled for ${label} (trans_id: ${transId}).` : `Payment settled for ${label}.`
|
|
5356
5670
|
};
|
|
5357
5671
|
}
|
|
5358
5672
|
await datastore.upsertPayment({
|
|
5359
|
-
quote_id:
|
|
5673
|
+
quote_id: ledgerQuoteId,
|
|
5360
5674
|
wallet_address: req.wallet_address,
|
|
5361
5675
|
trans_id: transId,
|
|
5362
5676
|
amount,
|
|
@@ -5364,14 +5678,14 @@ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
|
|
|
5364
5678
|
status: "settle_failed",
|
|
5365
5679
|
settled_at: null
|
|
5366
5680
|
});
|
|
5367
|
-
const cronRowFailed = await
|
|
5681
|
+
const cronRowFailed = await resolveCronRowForPaymentSettle(req, datastore);
|
|
5368
5682
|
if (cronRowFailed) {
|
|
5369
5683
|
await datastore.upsertCronJob({ ...cronRowFailed, status: "active" });
|
|
5370
5684
|
}
|
|
5371
5685
|
await emitPaymentSettleClientObservationBestEffort({
|
|
5372
5686
|
phase: "result",
|
|
5373
5687
|
correlation,
|
|
5374
|
-
quoteId:
|
|
5688
|
+
quoteId: ledgerQuoteId,
|
|
5375
5689
|
walletAddress: req.wallet_address,
|
|
5376
5690
|
objectId,
|
|
5377
5691
|
objectKey,
|
|
@@ -5720,8 +6034,38 @@ operation-id: ${operationId}`,
|
|
|
5720
6034
|
};
|
|
5721
6035
|
}
|
|
5722
6036
|
if (parsed.mode === "backup") {
|
|
6037
|
+
const backupPlatform = options.backupOptions?.platform ?? process.platform;
|
|
6038
|
+
if (!SUPPORTED_BACKUP_PLATFORMS.has(backupPlatform)) {
|
|
6039
|
+
return {
|
|
6040
|
+
text: "Cloud backup is only supported on macOS and Linux.",
|
|
6041
|
+
isError: true
|
|
6042
|
+
};
|
|
6043
|
+
}
|
|
5723
6044
|
try {
|
|
5724
|
-
|
|
6045
|
+
let walletAddress;
|
|
6046
|
+
try {
|
|
6047
|
+
const walletKey = await resolveWalletKey(mnemosparkHomeDir);
|
|
6048
|
+
walletAddress = privateKeyToAccount5(walletKey).address;
|
|
6049
|
+
} catch (err) {
|
|
6050
|
+
const message = err instanceof Error ? err.message : typeof err === "string" ? err : String(err);
|
|
6051
|
+
return {
|
|
6052
|
+
text: message.trim() || "No mnemospark wallet found.",
|
|
6053
|
+
isError: true
|
|
6054
|
+
};
|
|
6055
|
+
}
|
|
6056
|
+
let archiveBasename;
|
|
6057
|
+
try {
|
|
6058
|
+
archiveBasename = sanitizeFriendlyNameForLocalBasename(parsed.friendlyName);
|
|
6059
|
+
} catch {
|
|
6060
|
+
return {
|
|
6061
|
+
text: "Cannot build storage object: invalid --name for local file path (use a non-empty name without reserved path segments).",
|
|
6062
|
+
isError: true
|
|
6063
|
+
};
|
|
6064
|
+
}
|
|
6065
|
+
const result = await backupBuilder(parsed.backupTarget, {
|
|
6066
|
+
...options.backupOptions,
|
|
6067
|
+
archiveBasename
|
|
6068
|
+
});
|
|
5725
6069
|
await emitCloudEventBestEffort(
|
|
5726
6070
|
"backup.completed",
|
|
5727
6071
|
{
|
|
@@ -5729,7 +6073,7 @@ operation-id: ${operationId}`,
|
|
|
5729
6073
|
object_id: result.objectId,
|
|
5730
6074
|
status: "succeeded",
|
|
5731
6075
|
details: {
|
|
5732
|
-
friendly_name: parsed.friendlyName
|
|
6076
|
+
friendly_name: parsed.friendlyName,
|
|
5733
6077
|
archive_path: result.archivePath,
|
|
5734
6078
|
object_id_hash: result.objectIdHash.replace(/\s/g, ""),
|
|
5735
6079
|
object_size_gb: result.objectSizeGb
|
|
@@ -5740,7 +6084,7 @@ operation-id: ${operationId}`,
|
|
|
5740
6084
|
await datastore.upsertObject({
|
|
5741
6085
|
object_id: result.objectId,
|
|
5742
6086
|
object_key: null,
|
|
5743
|
-
wallet_address:
|
|
6087
|
+
wallet_address: walletAddress,
|
|
5744
6088
|
quote_id: null,
|
|
5745
6089
|
provider: null,
|
|
5746
6090
|
bucket_name: null,
|
|
@@ -5748,23 +6092,20 @@ operation-id: ${operationId}`,
|
|
|
5748
6092
|
sha256: result.objectIdHash,
|
|
5749
6093
|
status: "backed_up"
|
|
5750
6094
|
});
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
wallet_address: "unknown"
|
|
5759
|
-
});
|
|
5760
|
-
}
|
|
6095
|
+
await datastore.upsertFriendlyName({
|
|
6096
|
+
friendly_name: parsed.friendlyName,
|
|
6097
|
+
object_id: result.objectId,
|
|
6098
|
+
object_key: null,
|
|
6099
|
+
quote_id: null,
|
|
6100
|
+
wallet_address: walletAddress
|
|
6101
|
+
});
|
|
5761
6102
|
return {
|
|
5762
|
-
text: formatBackupSuccessUserMessage(result)
|
|
6103
|
+
text: formatBackupSuccessUserMessage(result, walletAddress, parsed.friendlyName)
|
|
5763
6104
|
};
|
|
5764
6105
|
} catch (err) {
|
|
5765
|
-
if (err instanceof
|
|
6106
|
+
if (err instanceof Error && err.message.includes("already exists")) {
|
|
5766
6107
|
return {
|
|
5767
|
-
text:
|
|
6108
|
+
text: err.message,
|
|
5768
6109
|
isError: true
|
|
5769
6110
|
};
|
|
5770
6111
|
}
|
|
@@ -5820,8 +6161,14 @@ operation-id: ${operationId}`,
|
|
|
5820
6161
|
},
|
|
5821
6162
|
mnemosparkHomeDir
|
|
5822
6163
|
);
|
|
6164
|
+
let friendlyForQuote = null;
|
|
6165
|
+
try {
|
|
6166
|
+
friendlyForQuote = await datastore.findLatestFriendlyNameForObjectId(quote.object_id);
|
|
6167
|
+
} catch {
|
|
6168
|
+
friendlyForQuote = null;
|
|
6169
|
+
}
|
|
5823
6170
|
return {
|
|
5824
|
-
text: formatPriceStorageUserMessage(quote)
|
|
6171
|
+
text: formatPriceStorageUserMessage(quote, friendlyForQuote)
|
|
5825
6172
|
};
|
|
5826
6173
|
} catch (err) {
|
|
5827
6174
|
await emitCloudEventBestEffort(
|
|
@@ -5861,10 +6208,33 @@ operation-id: ${operationId}`,
|
|
|
5861
6208
|
isError: true
|
|
5862
6209
|
};
|
|
5863
6210
|
}
|
|
5864
|
-
const
|
|
5865
|
-
|
|
6211
|
+
const backupDir = options.backupOptions?.tmpDir ?? DEFAULT_BACKUP_DIR;
|
|
6212
|
+
const dbFriendly = await datastore.findLatestFriendlyNameForObjectId(
|
|
5866
6213
|
parsed.uploadRequest.object_id
|
|
5867
6214
|
);
|
|
6215
|
+
if (!dbFriendly?.trim()) {
|
|
6216
|
+
return {
|
|
6217
|
+
text: "Cannot upload storage object: no friendly name in local SQLite for this object-id. Run /mnemospark_cloud backup with --name first.",
|
|
6218
|
+
isError: true
|
|
6219
|
+
};
|
|
6220
|
+
}
|
|
6221
|
+
if (parsed.friendlyName?.trim()) {
|
|
6222
|
+
if (parsed.friendlyName.trim() !== dbFriendly.trim()) {
|
|
6223
|
+
return {
|
|
6224
|
+
text: "Cannot upload storage object: --name does not match the friendly name stored in local SQLite for this object-id.",
|
|
6225
|
+
isError: true
|
|
6226
|
+
};
|
|
6227
|
+
}
|
|
6228
|
+
}
|
|
6229
|
+
const resolvedArchive = await resolveLocalUploadArchivePath(
|
|
6230
|
+
backupDir,
|
|
6231
|
+
parsed.uploadRequest.object_id,
|
|
6232
|
+
dbFriendly
|
|
6233
|
+
);
|
|
6234
|
+
if (!resolvedArchive.ok) {
|
|
6235
|
+
return { text: resolvedArchive.message, isError: true };
|
|
6236
|
+
}
|
|
6237
|
+
const archivePath = resolvedArchive.archivePath;
|
|
5868
6238
|
let archiveStats;
|
|
5869
6239
|
try {
|
|
5870
6240
|
archiveStats = await stat2(archivePath);
|
|
@@ -5969,7 +6339,7 @@ operation-id: ${operationId}`,
|
|
|
5969
6339
|
const cronJob = await createStoragePaymentCronJob(
|
|
5970
6340
|
finalizedUploadResponse,
|
|
5971
6341
|
cronStoragePrice,
|
|
5972
|
-
|
|
6342
|
+
openClawCronAdapter,
|
|
5973
6343
|
nowDateFn
|
|
5974
6344
|
);
|
|
5975
6345
|
await datastore.upsertObject({
|
|
@@ -6001,45 +6371,43 @@ operation-id: ${operationId}`,
|
|
|
6001
6371
|
command: cronJob.command,
|
|
6002
6372
|
status: "active"
|
|
6003
6373
|
});
|
|
6004
|
-
|
|
6005
|
-
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6374
|
+
const normalizedFriendlyName = dbFriendly.trim();
|
|
6375
|
+
await datastore.upsertFriendlyName({
|
|
6376
|
+
friendly_name: normalizedFriendlyName,
|
|
6377
|
+
object_id: finalizedUploadResponse.object_id,
|
|
6378
|
+
object_key: finalizedUploadResponse.object_key,
|
|
6379
|
+
quote_id: finalizedUploadResponse.quote_id,
|
|
6380
|
+
wallet_address: finalizedUploadResponse.addr
|
|
6381
|
+
});
|
|
6382
|
+
let friendlyNameVerified = false;
|
|
6383
|
+
try {
|
|
6384
|
+
const readBack = await datastore.resolveFriendlyName({
|
|
6385
|
+
walletAddress: finalizedUploadResponse.addr,
|
|
6386
|
+
friendlyName: normalizedFriendlyName,
|
|
6387
|
+
latest: true
|
|
6012
6388
|
});
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
friendly_name: normalizedFriendlyName,
|
|
6036
|
-
warning
|
|
6037
|
-
},
|
|
6038
|
-
mnemosparkHomeDir
|
|
6039
|
-
);
|
|
6040
|
-
if (process.env.MNEMOSPARK_SQLITE_STRICT === "1") {
|
|
6041
|
-
throw new Error(warning);
|
|
6042
|
-
}
|
|
6389
|
+
friendlyNameVerified = Boolean(readBack?.objectKey) && readBack?.objectKey === finalizedUploadResponse.object_key;
|
|
6390
|
+
} catch {
|
|
6391
|
+
friendlyNameVerified = false;
|
|
6392
|
+
}
|
|
6393
|
+
if (!friendlyNameVerified) {
|
|
6394
|
+
const warning = "SQLite friendly-name write verification failed; --name lookups may not resolve until SQLite is healthy.";
|
|
6395
|
+
await emitCloudEventBestEffort(
|
|
6396
|
+
"friendly_name.write_verification_failed",
|
|
6397
|
+
{
|
|
6398
|
+
operation_id: uploadCorrelation.operationId,
|
|
6399
|
+
trace_id: uploadCorrelation.traceId,
|
|
6400
|
+
wallet_address: finalizedUploadResponse.addr,
|
|
6401
|
+
object_id: finalizedUploadResponse.object_id,
|
|
6402
|
+
object_key: finalizedUploadResponse.object_key,
|
|
6403
|
+
quote_id: finalizedUploadResponse.quote_id,
|
|
6404
|
+
friendly_name: normalizedFriendlyName,
|
|
6405
|
+
warning
|
|
6406
|
+
},
|
|
6407
|
+
mnemosparkHomeDir
|
|
6408
|
+
);
|
|
6409
|
+
if (process.env.MNEMOSPARK_SQLITE_STRICT === "1") {
|
|
6410
|
+
throw new Error(warning);
|
|
6043
6411
|
}
|
|
6044
6412
|
}
|
|
6045
6413
|
await emitCloudEventBestEffort(
|
|
@@ -6202,10 +6570,27 @@ operation-id: ${operationId}`,
|
|
|
6202
6570
|
error_code: null,
|
|
6203
6571
|
error_message: null
|
|
6204
6572
|
});
|
|
6573
|
+
let downloadLocalBasename;
|
|
6574
|
+
try {
|
|
6575
|
+
const friendly = await datastore.findLatestFriendlyNameForObjectKey(
|
|
6576
|
+
resolvedRequest.wallet_address,
|
|
6577
|
+
resolvedRequest.object_key
|
|
6578
|
+
);
|
|
6579
|
+
if (friendly?.trim()) {
|
|
6580
|
+
try {
|
|
6581
|
+
downloadLocalBasename = sanitizeFriendlyNameForLocalBasename(friendly);
|
|
6582
|
+
} catch {
|
|
6583
|
+
downloadLocalBasename = void 0;
|
|
6584
|
+
}
|
|
6585
|
+
}
|
|
6586
|
+
} catch {
|
|
6587
|
+
downloadLocalBasename = void 0;
|
|
6588
|
+
}
|
|
6205
6589
|
try {
|
|
6206
6590
|
const downloadResult = await requestStorageDownload(resolvedRequest, {
|
|
6207
6591
|
...options.proxyStorageOptions,
|
|
6208
|
-
correlation
|
|
6592
|
+
correlation,
|
|
6593
|
+
...downloadLocalBasename ? { downloadLocalBasename } : {}
|
|
6209
6594
|
});
|
|
6210
6595
|
if (!downloadResult.success) {
|
|
6211
6596
|
throw new Error("download failed");
|
|
@@ -6318,15 +6703,15 @@ operation-id: ${operationId}`,
|
|
|
6318
6703
|
};
|
|
6319
6704
|
}
|
|
6320
6705
|
if (!cronEntry) {
|
|
6321
|
-
cronEntry = await
|
|
6706
|
+
cronEntry = await findCronJobInOpenClawCronJobsByObjectKey(
|
|
6322
6707
|
resolvedRequest.object_key,
|
|
6323
|
-
|
|
6708
|
+
openClawCronAdapter
|
|
6324
6709
|
);
|
|
6325
6710
|
}
|
|
6326
6711
|
if (cronEntry) {
|
|
6327
6712
|
const fileCronDeleted = await removeStoragePaymentCronJob(
|
|
6328
6713
|
cronEntry.cronId,
|
|
6329
|
-
|
|
6714
|
+
openClawCronAdapter
|
|
6330
6715
|
);
|
|
6331
6716
|
const dbCronDeleted = await datastore.removeCronJob(cronEntry.cronId);
|
|
6332
6717
|
cronDeleted = fileCronDeleted || dbCronDeleted;
|