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/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
- jsonBodyForObjectRequest(request),
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 filePath = resolveDownloadPath(outputDir, objectKey);
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 (!quoteId || !walletAddress) {
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 fields: quote_id, wallet_address"
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 requestPayload = parseStorageObjectRequest(payload);
2506
- if (!requestPayload) {
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 { appendFile as appendFile2, lstat, mkdir as mkdir5, readFile as readFile3, readdir as readdir2, rm, stat as stat2, writeFile as writeFile3 } from "fs/promises";
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 files",
3043
- `S3 bucket: ${bucket}`,
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/mnemospark/crontab.txt for your system scheduler.";
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 REQUIRED_PAYMENT_SETTLE = "--quote-id and --wallet-address";
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> [--name <friendly-name>] [--async] [--orchestrator <inline|subagent>] [--timeout-seconds <n>]`",
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: <file|directory>",
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> --wallet-address <addr> [--object-id <id>] [--object-key <key>] [--storage-price <n>]`",
3753
- " Purpose: settle storage payment for a quote (e.g. monthly cron). Uses the same proxy + x402 path as upload pre-settlement.",
3754
- " Required: --quote-id, --wallet-address (wallet private key must match the address).",
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 --wallet-address."
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: flags.name?.trim() || void 0,
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 (!quoteId || !walletAddress) {
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: flags["object-key"]?.trim() || void 0,
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 archivePath = join8(tmpDir, objectId);
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 formatTimestamp(date) {
4276
- const pad = (value) => value.toString().padStart(2, "0");
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
- let payload;
4297
- try {
4298
- payload = JSON.parse(trimmed);
4299
- } catch {
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
- if (!payload || typeof payload !== "object") {
4303
- return null;
4304
- }
4305
- const record = payload;
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
- cronId,
4323
- createdAt,
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 findCronJobInCrontabByObjectKey(objectKey, homeDir) {
4337
- const cronTablePath = resolveCronTablePath(homeDir);
4338
- let content;
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
- content = await readFile3(cronTablePath, "utf-8");
4341
- } catch (error) {
4342
- if (error.code === "ENOENT") {
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
- const lines = content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
4348
- for (let idx = lines.length - 1; idx >= 0; idx -= 1) {
4349
- const parsed = parseStoragePaymentCronJobLine(lines[idx]);
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: parsed.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
- "--quote-id",
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
- async function appendStoragePaymentCronJob(cronJob, homeDir) {
4380
- const cronTablePath = resolveCronTablePath(homeDir);
4381
- await mkdir5(dirname5(cronTablePath), { recursive: true });
4382
- await appendFile2(cronTablePath, `${JSON.stringify(cronJob)}
4383
- `, "utf-8");
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 lines = content.split(/\r?\n/);
4398
- let removed = false;
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
- continue;
4612
+ return null;
4404
4613
  }
4405
- const parsed = parseStoragePaymentCronJobLine(trimmed);
4406
- if (parsed && parsed.cronId === cronId) {
4407
- removed = true;
4408
- continue;
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
- keptLines.push(trimmed);
4411
- }
4412
- if (!removed) {
4413
- return false;
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
- await mkdir5(dirname5(cronTablePath), { recursive: true });
4416
- const nextContent = keptLines.length > 0 ? `${keptLines.join("\n")}
4417
- ` : "";
4418
- await writeFile3(cronTablePath, nextContent, "utf-8");
4419
- return true;
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 createStoragePaymentCronJob(upload, storagePrice, homeDir, nowDateFn = () => /* @__PURE__ */ new Date()) {
4422
- const cronId = randomUUID3();
4423
- const createdAt = formatTimestamp(nowDateFn());
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, homeDir);
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
- async function maybeCleanupLocalBackupArchive(archivePath) {
4575
- const flag = process.env.MNEMOSPARK_DELETE_BACKUP_AFTER_UPLOAD;
4576
- if (!flag) {
4577
- return;
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 normalized = flag.trim().toLowerCase();
4580
- if (normalized !== "1" && normalized !== "true" && normalized !== "yes" && normalized !== "y") {
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 (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).`,
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
- return [
4649
- `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}\`.`,
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
- QUOTE_VALIDITY_USER_NOTE
4656
- ].join("\n");
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 <wallet-address> --object-id \`${result.objectId}\` --object-id-hash \`${hash}\` --gb \`${result.objectSizeGb}\` --provider <provider> --region <region>`;
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 `<wallet-address>`, `<provider>`, and `<region>` (one line):",
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, --object-key, --storage-price.`,
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: req.quote_id,
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 datastore.findCronByQuoteId(req.quote_id);
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: req.quote_id,
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 amount = await resolveAmountForPaymentSettle(req.quote_id, req.storage_price, datastore);
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: req.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 datastore.findCronByQuoteId(req.quote_id);
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: req.quote_id,
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 quote ${req.quote_id} (trans_id: ${transId}).` : `Payment settled for quote ${req.quote_id}.`
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: req.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 datastore.findCronByQuoteId(req.quote_id);
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: req.quote_id,
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
- const result = await backupBuilder(parsed.backupTarget, options.backupOptions);
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 ?? basename2(parsed.backupTarget),
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: "unknown",
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
- const friendlyName = parsed.friendlyName?.trim() || basename2(parsed.backupTarget);
5752
- if (friendlyName) {
5753
- await datastore.upsertFriendlyName({
5754
- friendly_name: friendlyName,
5755
- object_id: result.objectId,
5756
- object_key: null,
5757
- quote_id: null,
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 UnsupportedBackupPlatformError) {
6106
+ if (err instanceof Error && err.message.includes("already exists")) {
5766
6107
  return {
5767
- text: "Cloud backup is only supported on macOS and Linux.",
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 archivePath = join8(
5865
- options.backupOptions?.tmpDir ?? DEFAULT_BACKUP_DIR,
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
- mnemosparkHomeDir,
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
- if (parsed.friendlyName?.trim()) {
6005
- const normalizedFriendlyName = parsed.friendlyName.trim();
6006
- await datastore.upsertFriendlyName({
6007
- friendly_name: normalizedFriendlyName,
6008
- object_id: finalizedUploadResponse.object_id,
6009
- object_key: finalizedUploadResponse.object_key,
6010
- quote_id: finalizedUploadResponse.quote_id,
6011
- wallet_address: finalizedUploadResponse.addr
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
- let friendlyNameVerified = false;
6014
- try {
6015
- const readBack = await datastore.resolveFriendlyName({
6016
- walletAddress: finalizedUploadResponse.addr,
6017
- friendlyName: normalizedFriendlyName,
6018
- latest: true
6019
- });
6020
- friendlyNameVerified = Boolean(readBack?.objectKey) && readBack?.objectKey === finalizedUploadResponse.object_key;
6021
- } catch {
6022
- friendlyNameVerified = false;
6023
- }
6024
- if (!friendlyNameVerified) {
6025
- const warning = "SQLite friendly-name write verification failed; --name lookups may not resolve until SQLite is healthy.";
6026
- await emitCloudEventBestEffort(
6027
- "friendly_name.write_verification_failed",
6028
- {
6029
- operation_id: uploadCorrelation.operationId,
6030
- trace_id: uploadCorrelation.traceId,
6031
- wallet_address: finalizedUploadResponse.addr,
6032
- object_id: finalizedUploadResponse.object_id,
6033
- object_key: finalizedUploadResponse.object_key,
6034
- quote_id: finalizedUploadResponse.quote_id,
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 findCronJobInCrontabByObjectKey(
6706
+ cronEntry = await findCronJobInOpenClawCronJobsByObjectKey(
6322
6707
  resolvedRequest.object_key,
6323
- mnemosparkHomeDir
6708
+ openClawCronAdapter
6324
6709
  );
6325
6710
  }
6326
6711
  if (cronEntry) {
6327
6712
  const fileCronDeleted = await removeStoragePaymentCronJob(
6328
6713
  cronEntry.cronId,
6329
- mnemosparkHomeDir
6714
+ openClawCronAdapter
6330
6715
  );
6331
6716
  const dbCronDeleted = await datastore.removeCronJob(cronEntry.cronId);
6332
6717
  cronDeleted = fileCronDeleted || dbCronDeleted;