mnemospark 0.3.0 → 0.4.0

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
@@ -2784,6 +2784,7 @@ async function createCloudDatastore(homeDir) {
2784
2784
  const nextDb = new DatabaseSyncCtor(dbPath);
2785
2785
  nextDb.exec("PRAGMA journal_mode=WAL;");
2786
2786
  nextDb.exec("PRAGMA foreign_keys=ON;");
2787
+ nextDb.exec("PRAGMA busy_timeout=5000;");
2787
2788
  nextDb.exec(`
2788
2789
  CREATE TABLE IF NOT EXISTS schema_migrations (
2789
2790
  version INTEGER PRIMARY KEY,
@@ -2832,6 +2833,7 @@ async function createCloudDatastore(homeDir) {
2832
2833
  updated_at TEXT NOT NULL
2833
2834
  );
2834
2835
  CREATE INDEX IF NOT EXISTS idx_cron_jobs_object_key ON cron_jobs(object_key);
2836
+ CREATE INDEX IF NOT EXISTS idx_cron_jobs_quote_id ON cron_jobs(quote_id);
2835
2837
 
2836
2838
  CREATE TABLE IF NOT EXISTS operations (
2837
2839
  operation_id TEXT PRIMARY KEY,
@@ -3032,6 +3034,38 @@ async function createCloudDatastore(homeDir) {
3032
3034
  if (!row) return null;
3033
3035
  return { cronId: row.cron_id, objectId: row.object_id };
3034
3036
  }, null),
3037
+ findCronByQuoteId: async (quoteId) => safe(() => {
3038
+ const row = db.prepare(
3039
+ `SELECT cron_id, object_id, object_key, quote_id, schedule, command, status
3040
+ FROM cron_jobs WHERE quote_id = ? ORDER BY updated_at DESC LIMIT 1`
3041
+ ).get(quoteId);
3042
+ if (!row) return null;
3043
+ return {
3044
+ cron_id: row.cron_id,
3045
+ object_id: row.object_id,
3046
+ object_key: row.object_key,
3047
+ quote_id: row.quote_id,
3048
+ schedule: row.schedule,
3049
+ command: row.command,
3050
+ status: row.status
3051
+ };
3052
+ }, null),
3053
+ findPaymentByQuoteId: async (quoteId) => safe(() => {
3054
+ const row = db.prepare(
3055
+ `SELECT quote_id, wallet_address, trans_id, amount, network, status, settled_at
3056
+ FROM payments WHERE quote_id = ? LIMIT 1`
3057
+ ).get(quoteId);
3058
+ if (!row) return null;
3059
+ return {
3060
+ quote_id: row.quote_id,
3061
+ wallet_address: row.wallet_address,
3062
+ trans_id: row.trans_id,
3063
+ amount: row.amount,
3064
+ network: row.network,
3065
+ status: row.status,
3066
+ settled_at: row.settled_at
3067
+ };
3068
+ }, null),
3035
3069
  upsertOperation: async (row) => {
3036
3070
  await safe(() => {
3037
3071
  const ts = nowIso();
@@ -3178,7 +3212,15 @@ var CRON_LOG_ROW_PREFIX = "cron";
3178
3212
  var TAR_OVERHEAD_BYTES = 10 * 1024 * 1024;
3179
3213
  var REQUIRED_PRICE_STORAGE = "--wallet-address, --object-id, --object-id-hash, --gb, --provider, --region";
3180
3214
  var REQUIRED_UPLOAD = "--quote-id, --wallet-address, --object-id, --object-id-hash";
3215
+ var REQUIRED_PAYMENT_SETTLE = "--quote-id and --wallet-address";
3181
3216
  var REQUIRED_STORAGE_OBJECT = "--wallet-address and one of (--object-key | --name [--latest|--at])";
3217
+ var PAYMENT_SETTLE_FLAG_NAMES = /* @__PURE__ */ new Set([
3218
+ "quote-id",
3219
+ "wallet-address",
3220
+ "object-id",
3221
+ "object-key",
3222
+ "storage-price"
3223
+ ]);
3182
3224
  var BOOLEAN_SELECTOR_FLAGS = /* @__PURE__ */ new Set(["latest"]);
3183
3225
  var BOOLEAN_ASYNC_FLAGS = /* @__PURE__ */ new Set(["async"]);
3184
3226
  var BOOLEAN_OP_STATUS_FLAGS = /* @__PURE__ */ new Set(["cancel"]);
@@ -3211,6 +3253,10 @@ var CLOUD_HELP_TEXT = [
3211
3253
  " Purpose: upload an encrypted object using a valid quote-id.",
3212
3254
  " Required: " + REQUIRED_UPLOAD,
3213
3255
  "",
3256
+ "\u2022 `/mnemospark_cloud payment-settle --quote-id <quote-id> --wallet-address <addr> [--object-id <id>] [--object-key <key>] [--storage-price <n>]`",
3257
+ " Purpose: settle storage payment for a quote (e.g. monthly cron). Uses the same proxy + x402 path as upload pre-settlement.",
3258
+ " Required: --quote-id, --wallet-address (wallet private key must match the address).",
3259
+ "",
3214
3260
  "\u2022 `/mnemospark_cloud ls --wallet-address <addr> [--object-key <object-key> | --name <friendly-name>] [--latest|--at <timestamp>]`",
3215
3261
  " Purpose: look up remote object metadata.",
3216
3262
  " Required: " + REQUIRED_STORAGE_OBJECT,
@@ -3245,7 +3291,7 @@ var CLOUD_HELP_TEXT = [
3245
3291
  "\u2022 `/mnemospark_cloud op-status --operation-id <id>`",
3246
3292
  "\u2022 `/mnemospark_cloud op-status --operation-id <id> --cancel`",
3247
3293
  "",
3248
- "Backup creates a tar+gzip object in ~/.openclaw/mnemospark/backup and appends object metadata to ~/.openclaw/mnemospark/object.log. Upload appends storage rows and cron-tracking rows to object.log, and keeps job entries in ~/.openclaw/mnemospark/crontab.txt. All storage commands (price-storage, upload, ls, download, delete) require --wallet-address."
3294
+ "Backup creates a tar+gzip object in ~/.openclaw/mnemospark/backup and appends object metadata to ~/.openclaw/mnemospark/object.log. Upload appends storage rows and cron-tracking rows to object.log, and keeps job entries in ~/.openclaw/mnemospark/crontab.txt. Commands price-storage, upload, ls, download, delete, and payment-settle require --wallet-address."
3249
3295
  ].join("\n");
3250
3296
  var UnsupportedBackupPlatformError = class extends Error {
3251
3297
  constructor(platform) {
@@ -3486,6 +3532,41 @@ function parseCloudArgs(args) {
3486
3532
  }
3487
3533
  };
3488
3534
  }
3535
+ if (subcommand === "payment-settle") {
3536
+ const flags = parseNamedFlags(rest);
3537
+ if (!flags) {
3538
+ return { mode: "payment-settle-invalid" };
3539
+ }
3540
+ for (const key of Object.keys(flags)) {
3541
+ if (!PAYMENT_SETTLE_FLAG_NAMES.has(key)) {
3542
+ return { mode: "payment-settle-invalid" };
3543
+ }
3544
+ }
3545
+ const quoteId = flags["quote-id"]?.trim();
3546
+ const walletAddress = flags["wallet-address"]?.trim();
3547
+ if (!quoteId || !walletAddress) {
3548
+ return { mode: "payment-settle-invalid" };
3549
+ }
3550
+ let storagePrice;
3551
+ if (flags["storage-price"] !== void 0 && flags["storage-price"] !== "") {
3552
+ const raw = flags["storage-price"]?.trim() ?? "";
3553
+ const n = Number.parseFloat(raw);
3554
+ if (!Number.isFinite(n) || n < 0) {
3555
+ return { mode: "payment-settle-invalid" };
3556
+ }
3557
+ storagePrice = n;
3558
+ }
3559
+ return {
3560
+ mode: "payment-settle",
3561
+ paymentSettleRequest: {
3562
+ quote_id: quoteId,
3563
+ wallet_address: walletAddress,
3564
+ object_id: flags["object-id"]?.trim() || void 0,
3565
+ object_key: flags["object-key"]?.trim() || void 0,
3566
+ storage_price: storagePrice
3567
+ }
3568
+ };
3569
+ }
3489
3570
  if (subcommand === "ls") {
3490
3571
  const flags = parseNamedFlags(rest, BOOLEAN_SELECTOR_FLAGS);
3491
3572
  if (!flags) {
@@ -3846,7 +3927,8 @@ function quoteCronArgument(value) {
3846
3927
  }
3847
3928
  function buildStoragePaymentCronCommand(job) {
3848
3929
  return [
3849
- "mnemospark-pay-storage",
3930
+ "/mnemospark_cloud",
3931
+ "payment-settle",
3850
3932
  "--quote-id",
3851
3933
  quoteCronArgument(job.quoteId),
3852
3934
  "--wallet-address",
@@ -4268,10 +4350,12 @@ function createCloudCommand(options = {}) {
4268
4350
  requestStorageLsFn: options.requestStorageLsFn ?? requestStorageLsViaProxy,
4269
4351
  requestStorageDownloadFn: options.requestStorageDownloadFn ?? requestStorageDownloadViaProxy,
4270
4352
  requestStorageDeleteFn: options.requestStorageDeleteFn ?? requestStorageDeleteViaProxy,
4353
+ requestPaymentSettleViaProxyFn: options.requestPaymentSettleViaProxyFn ?? requestPaymentSettleViaProxy,
4271
4354
  objectLogHomeDir: options.objectLogHomeDir ?? options.backupOptions?.homeDir,
4272
4355
  backupOptions: options.backupOptions,
4273
4356
  proxyQuoteOptions: options.proxyQuoteOptions,
4274
4357
  proxyUploadOptions: options.proxyUploadOptions,
4358
+ proxySettleOptions: options.proxySettleOptions,
4275
4359
  proxyUploadConfirmOptions: options.proxyUploadConfirmOptions,
4276
4360
  subagentOrchestrator,
4277
4361
  proxyStorageOptions: options.proxyStorageOptions
@@ -4435,6 +4519,117 @@ function buildRequestCorrelation(forcedOperationId, forcedTraceId) {
4435
4519
  const traceId = forcedTraceId?.trim() || randomUUID3();
4436
4520
  return { operationId, traceId };
4437
4521
  }
4522
+ function parseTransIdFromPaymentSettleBody(bodyText) {
4523
+ const trimmed = bodyText.trim();
4524
+ if (!trimmed.startsWith("{")) {
4525
+ return null;
4526
+ }
4527
+ try {
4528
+ const parsed = JSON.parse(trimmed);
4529
+ const tid = parsed.trans_id;
4530
+ return typeof tid === "string" && tid.trim() ? tid.trim() : null;
4531
+ } catch {
4532
+ return null;
4533
+ }
4534
+ }
4535
+ async function resolveAmountForPaymentSettle(quoteId, storagePriceFromFlag, datastore, homeDir) {
4536
+ if (storagePriceFromFlag !== void 0 && Number.isFinite(storagePriceFromFlag)) {
4537
+ return storagePriceFromFlag;
4538
+ }
4539
+ const quoteLookup = await datastore.findQuoteById(quoteId);
4540
+ if (quoteLookup && Number.isFinite(quoteLookup.storagePrice)) {
4541
+ return quoteLookup.storagePrice;
4542
+ }
4543
+ const logged = await findLoggedPriceStorageQuote(quoteId, homeDir);
4544
+ if (logged && Number.isFinite(logged.storagePrice)) {
4545
+ return logged.storagePrice;
4546
+ }
4547
+ const payment = await datastore.findPaymentByQuoteId(quoteId);
4548
+ if (payment && Number.isFinite(payment.amount)) {
4549
+ return payment.amount;
4550
+ }
4551
+ return 0;
4552
+ }
4553
+ async function emitPaymentSettleClientObservationBestEffort(params) {
4554
+ try {
4555
+ const {
4556
+ phase,
4557
+ correlation,
4558
+ quoteId,
4559
+ walletAddress,
4560
+ objectId,
4561
+ objectKey,
4562
+ httpStatus,
4563
+ outcomeStatus,
4564
+ homeDir
4565
+ } = params;
4566
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
4567
+ if (phase === "start") {
4568
+ await emitCloudEventBestEffort(
4569
+ "payment-settle.started",
4570
+ {
4571
+ operation_id: correlation.operationId,
4572
+ trace_id: correlation.traceId,
4573
+ quote_id: quoteId,
4574
+ wallet_address: walletAddress,
4575
+ object_id: objectId,
4576
+ object_key: objectKey,
4577
+ status: "running"
4578
+ },
4579
+ homeDir
4580
+ );
4581
+ await appendJsonlEvent(
4582
+ "proxy-events.jsonl",
4583
+ {
4584
+ ts,
4585
+ event_type: "payment.settle",
4586
+ status: "start",
4587
+ trace_id: correlation.traceId,
4588
+ operation_id: correlation.operationId,
4589
+ quote_id: quoteId,
4590
+ wallet_address: walletAddress,
4591
+ object_id: objectId ?? null,
4592
+ object_key: objectKey ?? null,
4593
+ details: { source: "client" }
4594
+ },
4595
+ homeDir
4596
+ );
4597
+ return;
4598
+ }
4599
+ const terminal = outcomeStatus ?? "failed";
4600
+ await emitCloudEventBestEffort(
4601
+ "payment-settle.completed",
4602
+ {
4603
+ operation_id: correlation.operationId,
4604
+ trace_id: correlation.traceId,
4605
+ quote_id: quoteId,
4606
+ wallet_address: walletAddress,
4607
+ object_id: objectId,
4608
+ object_key: objectKey,
4609
+ status: terminal === "succeeded" ? "succeeded" : "failed",
4610
+ http_status: httpStatus
4611
+ },
4612
+ homeDir
4613
+ );
4614
+ await appendJsonlEvent(
4615
+ "proxy-events.jsonl",
4616
+ {
4617
+ ts,
4618
+ event_type: "payment.settle",
4619
+ status: "result",
4620
+ trace_id: correlation.traceId,
4621
+ operation_id: correlation.operationId,
4622
+ quote_id: quoteId,
4623
+ wallet_address: walletAddress,
4624
+ object_id: objectId ?? null,
4625
+ object_key: objectKey ?? null,
4626
+ details: { http_status: httpStatus ?? null, source: "client" }
4627
+ },
4628
+ homeDir
4629
+ );
4630
+ } catch {
4631
+ }
4632
+ }
4438
4633
  async function runCloudCommandHandler(ctx, options, executionContext = {}) {
4439
4634
  const parsed = parseCloudArgs(ctx.args);
4440
4635
  const objectLogHomeDir = options.objectLogHomeDir;
@@ -4450,6 +4645,7 @@ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
4450
4645
  const requestStorageLs = options.requestStorageLsFn;
4451
4646
  const requestStorageDownload = options.requestStorageDownloadFn;
4452
4647
  const requestStorageDelete = options.requestStorageDeleteFn;
4648
+ const requestPaymentSettleViaProxy2 = options.requestPaymentSettleViaProxyFn;
4453
4649
  const subagentOrchestrator = options.subagentOrchestrator;
4454
4650
  if (parsed.mode === "help" || parsed.mode === "unknown") {
4455
4651
  return {
@@ -4487,6 +4683,12 @@ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
4487
4683
  isError: true
4488
4684
  };
4489
4685
  }
4686
+ if (parsed.mode === "payment-settle-invalid") {
4687
+ return {
4688
+ text: `Cannot settle payment: required arguments are ${REQUIRED_PAYMENT_SETTLE}. Optional: --object-id, --object-key, --storage-price.`,
4689
+ isError: true
4690
+ };
4691
+ }
4490
4692
  if (parsed.mode === "ls-invalid") {
4491
4693
  return {
4492
4694
  text: `Cannot list storage object: required arguments are ${REQUIRED_STORAGE_OBJECT}.`,
@@ -4631,6 +4833,142 @@ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
4631
4833
  }
4632
4834
  return formatOperationStatus(operation);
4633
4835
  }
4836
+ if (parsed.mode === "payment-settle") {
4837
+ const req = parsed.paymentSettleRequest;
4838
+ let walletKey;
4839
+ try {
4840
+ walletKey = await resolveWalletKey(objectLogHomeDir);
4841
+ } catch (err) {
4842
+ const message = err instanceof Error ? err.message : String(err);
4843
+ return { text: message.trim() || "Cannot resolve wallet key.", isError: true };
4844
+ }
4845
+ const walletAccount = privateKeyToAccount5(walletKey);
4846
+ if (walletAccount.address.toLowerCase() !== req.wallet_address.toLowerCase()) {
4847
+ return {
4848
+ text: `Cannot settle payment: wallet key address ${walletAccount.address} does not match --wallet-address ${req.wallet_address}.`,
4849
+ isError: true
4850
+ };
4851
+ }
4852
+ const correlation = buildRequestCorrelation();
4853
+ const settleFetch = createPayment(walletKey).fetch;
4854
+ const objectId = req.object_id;
4855
+ const objectKey = req.object_key;
4856
+ await emitPaymentSettleClientObservationBestEffort({
4857
+ phase: "start",
4858
+ correlation,
4859
+ quoteId: req.quote_id,
4860
+ walletAddress: req.wallet_address,
4861
+ objectId,
4862
+ objectKey,
4863
+ homeDir: objectLogHomeDir
4864
+ });
4865
+ let settleResult;
4866
+ try {
4867
+ settleResult = await requestPaymentSettleViaProxy2(req.quote_id, req.wallet_address, {
4868
+ ...options.proxySettleOptions,
4869
+ correlation,
4870
+ fetchImpl: (input, init) => settleFetch(input, init)
4871
+ });
4872
+ } catch (err) {
4873
+ const amountErr = await resolveAmountForPaymentSettle(
4874
+ req.quote_id,
4875
+ req.storage_price,
4876
+ datastore,
4877
+ objectLogHomeDir
4878
+ );
4879
+ await datastore.upsertPayment({
4880
+ quote_id: req.quote_id,
4881
+ wallet_address: req.wallet_address,
4882
+ trans_id: null,
4883
+ amount: amountErr,
4884
+ network: null,
4885
+ status: "settle_failed",
4886
+ settled_at: null
4887
+ });
4888
+ const cronErr = await datastore.findCronByQuoteId(req.quote_id);
4889
+ if (cronErr) {
4890
+ await datastore.upsertCronJob({ ...cronErr, status: "active" });
4891
+ }
4892
+ const msg = err instanceof Error ? err.message : String(err);
4893
+ await emitPaymentSettleClientObservationBestEffort({
4894
+ phase: "result",
4895
+ correlation,
4896
+ quoteId: req.quote_id,
4897
+ walletAddress: req.wallet_address,
4898
+ objectId,
4899
+ objectKey,
4900
+ outcomeStatus: "failed",
4901
+ homeDir: objectLogHomeDir
4902
+ });
4903
+ return { text: `Payment settle failed: ${msg}`, isError: true };
4904
+ }
4905
+ const amount = await resolveAmountForPaymentSettle(
4906
+ req.quote_id,
4907
+ req.storage_price,
4908
+ datastore,
4909
+ objectLogHomeDir
4910
+ );
4911
+ const transId = settleResult.status === 200 ? parseTransIdFromPaymentSettleBody(settleResult.bodyText ?? "") : null;
4912
+ if (settleResult.status === 200) {
4913
+ await datastore.upsertPayment({
4914
+ quote_id: req.quote_id,
4915
+ wallet_address: req.wallet_address,
4916
+ trans_id: transId,
4917
+ amount,
4918
+ network: null,
4919
+ status: "settled",
4920
+ settled_at: (/* @__PURE__ */ new Date()).toISOString()
4921
+ });
4922
+ const cronRow = await datastore.findCronByQuoteId(req.quote_id);
4923
+ if (cronRow) {
4924
+ await datastore.upsertCronJob({ ...cronRow, status: "active" });
4925
+ }
4926
+ await emitPaymentSettleClientObservationBestEffort({
4927
+ phase: "result",
4928
+ correlation,
4929
+ quoteId: req.quote_id,
4930
+ walletAddress: req.wallet_address,
4931
+ objectId,
4932
+ objectKey,
4933
+ httpStatus: settleResult.status,
4934
+ outcomeStatus: "succeeded",
4935
+ homeDir: objectLogHomeDir
4936
+ });
4937
+ return {
4938
+ text: transId ? `Payment settled for quote ${req.quote_id} (trans_id: ${transId}).` : `Payment settled for quote ${req.quote_id}.`
4939
+ };
4940
+ }
4941
+ await datastore.upsertPayment({
4942
+ quote_id: req.quote_id,
4943
+ wallet_address: req.wallet_address,
4944
+ trans_id: transId,
4945
+ amount,
4946
+ network: null,
4947
+ status: "settle_failed",
4948
+ settled_at: null
4949
+ });
4950
+ const cronRowFailed = await datastore.findCronByQuoteId(req.quote_id);
4951
+ if (cronRowFailed) {
4952
+ await datastore.upsertCronJob({ ...cronRowFailed, status: "active" });
4953
+ }
4954
+ await emitPaymentSettleClientObservationBestEffort({
4955
+ phase: "result",
4956
+ correlation,
4957
+ quoteId: req.quote_id,
4958
+ walletAddress: req.wallet_address,
4959
+ objectId,
4960
+ objectKey,
4961
+ httpStatus: settleResult.status,
4962
+ outcomeStatus: "failed",
4963
+ homeDir: objectLogHomeDir
4964
+ });
4965
+ const bodySnippet = settleResult.bodyText?.trim();
4966
+ const detail = bodySnippet && bodySnippet.length > 500 ? `${bodySnippet.slice(0, 500)}\u2026` : bodySnippet;
4967
+ return {
4968
+ text: detail ? `Payment settle failed (HTTP ${settleResult.status}): ${detail}` : `Payment settle failed with HTTP ${settleResult.status}.`,
4969
+ isError: true
4970
+ };
4971
+ }
4634
4972
  if ((parsed.mode === "backup" || parsed.mode === "upload" || parsed.mode === "download") && parsed.async) {
4635
4973
  const asyncCorrelation = buildRequestCorrelation();
4636
4974
  const operationId = asyncCorrelation.operationId;
@@ -5141,7 +5479,7 @@ operation-id: ${operationId}`,
5141
5479
  const shouldSettleBeforeUpload = requestStorageUpload !== requestStorageUploadViaProxy;
5142
5480
  if (shouldSettleBeforeUpload) {
5143
5481
  const paymentFetch = createPayment(walletKey).fetch;
5144
- const settleResult = await requestPaymentSettleViaProxy(
5482
+ const settleResult = await requestPaymentSettleViaProxy2(
5145
5483
  parsed.uploadRequest.quote_id,
5146
5484
  parsed.uploadRequest.wallet_address,
5147
5485
  {