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/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
- jsonBodyForObjectRequest(request),
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 filePath = resolveDownloadPath(outputDir, objectKey);
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 (!quoteId || !walletAddress) {
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 fields: quote_id, wallet_address"
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 requestPayload = parseStorageObjectRequest(payload);
2461
- if (!requestPayload) {
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 { appendFile as appendFile2, lstat, mkdir as mkdir5, readFile as readFile3, readdir as readdir2, rm, stat as stat2, writeFile as writeFile3 } from "fs/promises";
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 files",
2992
- `S3 bucket: ${bucket}`,
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/mnemospark/crontab.txt for your system scheduler.";
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 REQUIRED_PAYMENT_SETTLE = "--quote-id and --wallet-address";
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> [--name <friendly-name>] [--async] [--orchestrator <inline|subagent>] [--timeout-seconds <n>]`",
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: <file|directory>",
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> --wallet-address <addr> [--object-id <id>] [--object-key <key>] [--storage-price <n>]`",
3702
- " Purpose: settle storage payment for a quote (e.g. monthly cron). Uses the same proxy + x402 path as upload pre-settlement.",
3703
- " Required: --quote-id, --wallet-address (wallet private key must match the address).",
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 --wallet-address."
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: flags.name?.trim() || void 0,
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 (!quoteId || !walletAddress) {
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: flags["object-key"]?.trim() || void 0,
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 archivePath = join8(tmpDir, objectId);
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 formatTimestamp(date) {
4225
- const pad = (value) => value.toString().padStart(2, "0");
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
- let payload;
4246
- try {
4247
- payload = JSON.parse(trimmed);
4248
- } catch {
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
- if (!payload || typeof payload !== "object") {
4252
- return null;
4253
- }
4254
- const record = payload;
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
- cronId,
4272
- createdAt,
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 findCronJobInCrontabByObjectKey(objectKey, homeDir) {
4286
- const cronTablePath = resolveCronTablePath(homeDir);
4287
- let content;
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
- content = await readFile3(cronTablePath, "utf-8");
4290
- } catch (error) {
4291
- if (error.code === "ENOENT") {
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
- const lines = content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
4297
- for (let idx = lines.length - 1; idx >= 0; idx -= 1) {
4298
- const parsed = parseStoragePaymentCronJobLine(lines[idx]);
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: parsed.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
- "--quote-id",
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
- async function appendStoragePaymentCronJob(cronJob, homeDir) {
4329
- const cronTablePath = resolveCronTablePath(homeDir);
4330
- await mkdir5(dirname5(cronTablePath), { recursive: true });
4331
- await appendFile2(cronTablePath, `${JSON.stringify(cronJob)}
4332
- `, "utf-8");
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 lines = content.split(/\r?\n/);
4347
- let removed = false;
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
- continue;
4561
+ return null;
4353
4562
  }
4354
- const parsed = parseStoragePaymentCronJobLine(trimmed);
4355
- if (parsed && parsed.cronId === cronId) {
4356
- removed = true;
4357
- continue;
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
- keptLines.push(trimmed);
4360
- }
4361
- if (!removed) {
4362
- return false;
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
- await mkdir5(dirname5(cronTablePath), { recursive: true });
4365
- const nextContent = keptLines.length > 0 ? `${keptLines.join("\n")}
4366
- ` : "";
4367
- await writeFile3(cronTablePath, nextContent, "utf-8");
4368
- return true;
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 createStoragePaymentCronJob(upload, storagePrice, homeDir, nowDateFn = () => /* @__PURE__ */ new Date()) {
4371
- const cronId = randomUUID3();
4372
- const createdAt = formatTimestamp(nowDateFn());
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, homeDir);
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
- async function maybeCleanupLocalBackupArchive(archivePath) {
4524
- const flag = process.env.MNEMOSPARK_DELETE_BACKUP_AFTER_UPLOAD;
4525
- if (!flag) {
4526
- return;
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 normalized = flag.trim().toLowerCase();
4529
- if (normalized !== "1" && normalized !== "true" && normalized !== "yes" && normalized !== "y") {
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 (on the 1st) for storage services. If payment is not sent, your \`${upload.object_id}\` will be deleted after the ${PAYMENT_DELETE_DEADLINE_DAYS}-day deadline (${PAYMENT_REMINDER_INTERVAL_DAYS}-day billing interval + 2-day grace period).`,
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
- return [
4598
- `Your storage quote \`${quote.quote_id}\`: storage price \`${quote.storage_price}\` for \`${quote.object_id}\` with file size \`${quote.object_size_gb}\` in \`${quote.provider}\` \`${quote.location}\`.`,
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
- QUOTE_VALIDITY_USER_NOTE
4605
- ].join("\n");
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 <wallet-address> --object-id \`${result.objectId}\` --object-id-hash \`${hash}\` --gb \`${result.objectSizeGb}\` --provider <provider> --region <region>`;
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 `<wallet-address>`, `<provider>`, and `<region>` (one line):",
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, --object-key, --storage-price.`,
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: req.quote_id,
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 datastore.findCronByQuoteId(req.quote_id);
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: req.quote_id,
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 amount = await resolveAmountForPaymentSettle(req.quote_id, req.storage_price, datastore);
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: req.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 datastore.findCronByQuoteId(req.quote_id);
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: req.quote_id,
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 quote ${req.quote_id} (trans_id: ${transId}).` : `Payment settled for quote ${req.quote_id}.`
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: req.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 datastore.findCronByQuoteId(req.quote_id);
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: req.quote_id,
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
- const result = await backupBuilder(parsed.backupTarget, options.backupOptions);
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 ?? basename2(parsed.backupTarget),
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: "unknown",
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
- const friendlyName = parsed.friendlyName?.trim() || basename2(parsed.backupTarget);
5701
- if (friendlyName) {
5702
- await datastore.upsertFriendlyName({
5703
- friendly_name: friendlyName,
5704
- object_id: result.objectId,
5705
- object_key: null,
5706
- quote_id: null,
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 UnsupportedBackupPlatformError) {
6055
+ if (err instanceof Error && err.message.includes("already exists")) {
5715
6056
  return {
5716
- text: "Cloud backup is only supported on macOS and Linux.",
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 archivePath = join8(
5814
- options.backupOptions?.tmpDir ?? DEFAULT_BACKUP_DIR,
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
- mnemosparkHomeDir,
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
- if (parsed.friendlyName?.trim()) {
5954
- const normalizedFriendlyName = parsed.friendlyName.trim();
5955
- await datastore.upsertFriendlyName({
5956
- friendly_name: normalizedFriendlyName,
5957
- object_id: finalizedUploadResponse.object_id,
5958
- object_key: finalizedUploadResponse.object_key,
5959
- quote_id: finalizedUploadResponse.quote_id,
5960
- wallet_address: finalizedUploadResponse.addr
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
- let friendlyNameVerified = false;
5963
- try {
5964
- const readBack = await datastore.resolveFriendlyName({
5965
- walletAddress: finalizedUploadResponse.addr,
5966
- friendlyName: normalizedFriendlyName,
5967
- latest: true
5968
- });
5969
- friendlyNameVerified = Boolean(readBack?.objectKey) && readBack?.objectKey === finalizedUploadResponse.object_key;
5970
- } catch {
5971
- friendlyNameVerified = false;
5972
- }
5973
- if (!friendlyNameVerified) {
5974
- const warning = "SQLite friendly-name write verification failed; --name lookups may not resolve until SQLite is healthy.";
5975
- await emitCloudEventBestEffort(
5976
- "friendly_name.write_verification_failed",
5977
- {
5978
- operation_id: uploadCorrelation.operationId,
5979
- trace_id: uploadCorrelation.traceId,
5980
- wallet_address: finalizedUploadResponse.addr,
5981
- object_id: finalizedUploadResponse.object_id,
5982
- object_key: finalizedUploadResponse.object_key,
5983
- quote_id: finalizedUploadResponse.quote_id,
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 findCronJobInCrontabByObjectKey(
6655
+ cronEntry = await findCronJobInOpenClawCronJobsByObjectKey(
6271
6656
  resolvedRequest.object_key,
6272
- mnemosparkHomeDir
6657
+ openClawCronAdapter
6273
6658
  );
6274
6659
  }
6275
6660
  if (cronEntry) {
6276
6661
  const fileCronDeleted = await removeStoragePaymentCronJob(
6277
6662
  cronEntry.cronId,
6278
- mnemosparkHomeDir
6663
+ openClawCronAdapter
6279
6664
  );
6280
6665
  const dbCronDeleted = await datastore.removeCronJob(cronEntry.cronId);
6281
6666
  cronDeleted = fileCronDeleted || dbCronDeleted;