mnemospark 0.1.22 → 0.2.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
@@ -1721,7 +1721,7 @@ async function startProxy(options) {
1721
1721
  emitProxyTerminalFromStatus(correlation, 400, { reason: "invalid_json" });
1722
1722
  sendJson(res, 400, {
1723
1723
  error: "Bad request",
1724
- message: "Invalid JSON body for /mnemospark-cloud price-storage"
1724
+ message: "Invalid JSON body for /mnemospark_cloud price-storage"
1725
1725
  });
1726
1726
  return;
1727
1727
  }
@@ -1789,7 +1789,7 @@ async function startProxy(options) {
1789
1789
  });
1790
1790
  sendJson(res, 502, {
1791
1791
  error: "proxy_error",
1792
- message: `Failed to forward /mnemospark-cloud price-storage: ${err instanceof Error ? err.message : String(err)}`
1792
+ message: `Failed to forward /mnemospark_cloud price-storage: ${err instanceof Error ? err.message : String(err)}`
1793
1793
  });
1794
1794
  }
1795
1795
  return;
@@ -1935,7 +1935,7 @@ async function startProxy(options) {
1935
1935
  emitProxyTerminalFromStatus(correlation, 400, { reason: "invalid_json" });
1936
1936
  sendJson(res, 400, {
1937
1937
  error: "Bad request",
1938
- message: "Invalid JSON body for /mnemospark-cloud upload"
1938
+ message: "Invalid JSON body for /mnemospark_cloud upload"
1939
1939
  });
1940
1940
  return;
1941
1941
  }
@@ -1998,7 +1998,7 @@ async function startProxy(options) {
1998
1998
  error: "insufficient_balance",
1999
1999
  message: `Insufficient USDC balance. Current: ${sufficiency.info.balanceUSD}, Required: ${requiredUSD}`,
2000
2000
  wallet: requestPayload.wallet_address,
2001
- help: `Fund wallet ${requestPayload.wallet_address} on Base before running /mnemospark-cloud upload`
2001
+ help: `Fund wallet ${requestPayload.wallet_address} on Base before running /mnemospark_cloud upload`
2002
2002
  });
2003
2003
  return;
2004
2004
  }
@@ -2106,7 +2106,7 @@ async function startProxy(options) {
2106
2106
  });
2107
2107
  sendJson(res, 502, {
2108
2108
  error: "proxy_error",
2109
- message: `Failed to forward /mnemospark-cloud upload: ${err instanceof Error ? err.message : String(err)}`
2109
+ message: `Failed to forward /mnemospark_cloud upload: ${err instanceof Error ? err.message : String(err)}`
2110
2110
  });
2111
2111
  }
2112
2112
  return;
@@ -2124,7 +2124,7 @@ async function startProxy(options) {
2124
2124
  emitProxyTerminalFromStatus(correlation, 400, { reason: "invalid_json" });
2125
2125
  sendJson(res, 400, {
2126
2126
  error: "Bad request",
2127
- message: "Invalid JSON body for /mnemospark-cloud upload/confirm"
2127
+ message: "Invalid JSON body for /mnemospark_cloud upload/confirm"
2128
2128
  });
2129
2129
  return;
2130
2130
  }
@@ -2205,7 +2205,7 @@ async function startProxy(options) {
2205
2205
  });
2206
2206
  sendJson(res, 502, {
2207
2207
  error: "proxy_error",
2208
- message: `Failed to forward /mnemospark-cloud upload/confirm: ${err instanceof Error ? err.message : String(err)}`
2208
+ message: `Failed to forward /mnemospark_cloud upload/confirm: ${err instanceof Error ? err.message : String(err)}`
2209
2209
  });
2210
2210
  }
2211
2211
  return;
@@ -2223,7 +2223,7 @@ async function startProxy(options) {
2223
2223
  emitProxyTerminalFromStatus(correlation, 400, { reason: "invalid_json" });
2224
2224
  sendJson(res, 400, {
2225
2225
  error: "Bad request",
2226
- message: "Invalid JSON body for /mnemospark-cloud ls"
2226
+ message: "Invalid JSON body for /mnemospark_cloud ls"
2227
2227
  });
2228
2228
  return;
2229
2229
  }
@@ -2296,7 +2296,7 @@ async function startProxy(options) {
2296
2296
  });
2297
2297
  sendJson(res, 502, {
2298
2298
  error: "proxy_error",
2299
- message: `Failed to forward /mnemospark-cloud ls: ${err instanceof Error ? err.message : String(err)}`
2299
+ message: `Failed to forward /mnemospark_cloud ls: ${err instanceof Error ? err.message : String(err)}`
2300
2300
  });
2301
2301
  }
2302
2302
  return;
@@ -2316,7 +2316,7 @@ async function startProxy(options) {
2316
2316
  emitProxyTerminalFromStatus(correlation, 400, { reason: "invalid_json" });
2317
2317
  sendJson(res, 400, {
2318
2318
  error: "Bad request",
2319
- message: "Invalid JSON body for /mnemospark-cloud download"
2319
+ message: "Invalid JSON body for /mnemospark_cloud download"
2320
2320
  });
2321
2321
  return;
2322
2322
  }
@@ -2414,7 +2414,7 @@ async function startProxy(options) {
2414
2414
  });
2415
2415
  sendJson(res, 502, {
2416
2416
  error: "proxy_error",
2417
- message: `Failed to forward /mnemospark-cloud download: ${err instanceof Error ? err.message : String(err)}`
2417
+ message: `Failed to forward /mnemospark_cloud download: ${err instanceof Error ? err.message : String(err)}`
2418
2418
  });
2419
2419
  }
2420
2420
  return;
@@ -2432,7 +2432,7 @@ async function startProxy(options) {
2432
2432
  emitProxyTerminalFromStatus(correlation, 400, { reason: "invalid_json" });
2433
2433
  sendJson(res, 400, {
2434
2434
  error: "Bad request",
2435
- message: "Invalid JSON body for /mnemospark-cloud delete"
2435
+ message: "Invalid JSON body for /mnemospark_cloud delete"
2436
2436
  });
2437
2437
  return;
2438
2438
  }
@@ -2509,7 +2509,7 @@ async function startProxy(options) {
2509
2509
  });
2510
2510
  sendJson(res, 502, {
2511
2511
  error: "proxy_error",
2512
- message: `Failed to forward /mnemospark-cloud delete: ${err instanceof Error ? err.message : String(err)}`
2512
+ message: `Failed to forward /mnemospark_cloud delete: ${err instanceof Error ? err.message : String(err)}`
2513
2513
  });
2514
2514
  }
2515
2515
  return;
@@ -2755,7 +2755,7 @@ import { mkdir as mkdir4 } from "fs/promises";
2755
2755
  import { homedir as homedir5 } from "os";
2756
2756
  import { dirname as dirname4, join as join7 } from "path";
2757
2757
  var DB_SUBPATH = join7(".openclaw", "mnemospark", "state.db");
2758
- var SCHEMA_VERSION = 2;
2758
+ var SCHEMA_VERSION = 3;
2759
2759
  function resolveDbPath(homeDir) {
2760
2760
  return join7(homeDir ?? homedir5(), DB_SUBPATH);
2761
2761
  }
@@ -2861,6 +2861,21 @@ async function createCloudDatastore(homeDir) {
2861
2861
  CREATE INDEX IF NOT EXISTS idx_friendly_names_wallet ON friendly_names(wallet_address);
2862
2862
  CREATE INDEX IF NOT EXISTS idx_friendly_names_created_at ON friendly_names(created_at);
2863
2863
  `);
2864
+ const addOperationsColumn = (columnName, sqlType) => {
2865
+ try {
2866
+ nextDb.exec(`ALTER TABLE operations ADD COLUMN ${columnName} ${sqlType}`);
2867
+ } catch (error) {
2868
+ const message = error instanceof Error ? error.message.toLowerCase() : String(error);
2869
+ if (!message.includes("duplicate column name")) {
2870
+ throw error;
2871
+ }
2872
+ }
2873
+ };
2874
+ addOperationsColumn("trace_id", "TEXT");
2875
+ addOperationsColumn("orchestrator", "TEXT");
2876
+ addOperationsColumn("subagent_session_id", "TEXT");
2877
+ addOperationsColumn("timeout_seconds", "INTEGER");
2878
+ addOperationsColumn("cancel_requested_at", "TEXT");
2864
2879
  nextDb.prepare(
2865
2880
  `INSERT INTO schema_migrations(version, applied_at)
2866
2881
  VALUES(?, ?)
@@ -2872,7 +2887,10 @@ async function createCloudDatastore(homeDir) {
2872
2887
  try {
2873
2888
  await ensureReady();
2874
2889
  return fn();
2875
- } catch {
2890
+ } catch (error) {
2891
+ if (process.env.MNEMOSPARK_SQLITE_STRICT === "1") {
2892
+ throw error;
2893
+ }
2876
2894
  return fallback;
2877
2895
  }
2878
2896
  };
@@ -2997,13 +3015,19 @@ async function createCloudDatastore(homeDir) {
2997
3015
  upsertOperation: async (row) => {
2998
3016
  await safe(() => {
2999
3017
  const ts = nowIso();
3018
+ const terminalStatuses = /* @__PURE__ */ new Set(["succeeded", "failed", "cancelled", "timed_out"]);
3000
3019
  db.prepare(
3001
- `INSERT INTO operations(operation_id, type, object_id, quote_id, status, error_code, error_message, started_at, finished_at, created_at, updated_at)
3002
- VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
3020
+ `INSERT INTO operations(operation_id, type, object_id, quote_id, trace_id, orchestrator, subagent_session_id, timeout_seconds, cancel_requested_at, status, error_code, error_message, started_at, finished_at, created_at, updated_at)
3021
+ VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
3003
3022
  ON CONFLICT(operation_id) DO UPDATE SET
3004
3023
  type=excluded.type,
3005
3024
  object_id=COALESCE(excluded.object_id, operations.object_id),
3006
- quote_id=excluded.quote_id,
3025
+ quote_id=COALESCE(excluded.quote_id, operations.quote_id),
3026
+ trace_id=COALESCE(excluded.trace_id, operations.trace_id),
3027
+ orchestrator=COALESCE(excluded.orchestrator, operations.orchestrator),
3028
+ subagent_session_id=COALESCE(excluded.subagent_session_id, operations.subagent_session_id),
3029
+ timeout_seconds=COALESCE(excluded.timeout_seconds, operations.timeout_seconds),
3030
+ cancel_requested_at=COALESCE(excluded.cancel_requested_at, operations.cancel_requested_at),
3007
3031
  status=excluded.status,
3008
3032
  error_code=excluded.error_code,
3009
3033
  error_message=excluded.error_message,
@@ -3015,11 +3039,16 @@ async function createCloudDatastore(homeDir) {
3015
3039
  row.type,
3016
3040
  row.object_id,
3017
3041
  row.quote_id,
3042
+ row.trace_id ?? null,
3043
+ row.orchestrator ?? null,
3044
+ row.subagent_session_id ?? null,
3045
+ row.timeout_seconds ?? null,
3046
+ row.cancel_requested_at ?? null,
3018
3047
  row.status,
3019
3048
  row.error_code,
3020
3049
  row.error_message,
3021
3050
  row.status === "started" ? ts : null,
3022
- row.status === "succeeded" || row.status === "failed" ? ts : null,
3051
+ terminalStatuses.has(row.status) ? ts : null,
3023
3052
  ts,
3024
3053
  ts
3025
3054
  );
@@ -3027,7 +3056,7 @@ async function createCloudDatastore(homeDir) {
3027
3056
  },
3028
3057
  findOperationById: async (operationId) => safe(() => {
3029
3058
  const row = db.prepare(
3030
- `SELECT operation_id, type, status, error_code, error_message, started_at, finished_at, updated_at
3059
+ `SELECT operation_id, type, object_id, quote_id, trace_id, orchestrator, subagent_session_id, timeout_seconds, cancel_requested_at, status, error_code, error_message, started_at, finished_at, updated_at
3031
3060
  FROM operations
3032
3061
  WHERE operation_id = ?
3033
3062
  LIMIT 1`
@@ -3129,7 +3158,9 @@ var REQUIRED_UPLOAD = "--quote-id, --wallet-address, --object-id, --object-id-ha
3129
3158
  var REQUIRED_STORAGE_OBJECT = "--wallet-address and one of (--object-key | --name [--latest|--at])";
3130
3159
  var BOOLEAN_SELECTOR_FLAGS = /* @__PURE__ */ new Set(["latest"]);
3131
3160
  var BOOLEAN_ASYNC_FLAGS = /* @__PURE__ */ new Set(["async"]);
3161
+ var BOOLEAN_OP_STATUS_FLAGS = /* @__PURE__ */ new Set(["cancel"]);
3132
3162
  var BOOLEAN_SELECTOR_AND_ASYNC_FLAGS = /* @__PURE__ */ new Set(["latest", "async"]);
3163
+ var ORCHESTRATOR_MODES = /* @__PURE__ */ new Set(["inline", "subagent"]);
3133
3164
  function expandTilde(path) {
3134
3165
  const trimmed = path.trim();
3135
3166
  if (trimmed === "~") {
@@ -3143,29 +3174,54 @@ function expandTilde(path) {
3143
3174
  var CLOUD_HELP_TEXT = [
3144
3175
  "\u2601\uFE0F **mnemospark Cloud Commands**",
3145
3176
  "",
3146
- "\u2022 `/mnemospark-cloud` or `/mnemospark-cloud help` \u2014 show this message",
3177
+ "\u2022 `/mnemospark_cloud` or `/mnemospark_cloud help` \u2014 show this message",
3147
3178
  "",
3148
- "\u2022 `/mnemospark-cloud backup <file>` or `/mnemospark-cloud backup <directory> [--name <friendly-name>]`",
3149
- " Required: <file> or <directory> (path to back up)",
3179
+ "\u2022 `/mnemospark_cloud backup <file|directory> [--name <friendly-name>] [--async] [--orchestrator <inline|subagent>] [--timeout-seconds <n>]`",
3180
+ " Purpose: create a local tar+gzip backup object and index it for later upload.",
3181
+ " Required: <file|directory>",
3150
3182
  "",
3151
- "\u2022 `/mnemospark-cloud price-storage --wallet-address <addr> --object-id <id> --object-id-hash <hash> --gb <gb> --provider <provider> --region <region>`",
3183
+ "\u2022 `/mnemospark_cloud price-storage --wallet-address <addr> --object-id <id> --object-id-hash <hash> --gb <gb> --provider <provider> --region <region>`",
3184
+ " Purpose: request a storage quote before upload.",
3152
3185
  " Required: " + REQUIRED_PRICE_STORAGE,
3153
3186
  "",
3154
- "\u2022 `/mnemospark-cloud upload --quote-id <quote-id> --wallet-address <addr> --object-id <id> --object-id-hash <hash> [--name <friendly-name>] [--async]`",
3187
+ "\u2022 `/mnemospark_cloud upload --quote-id <quote-id> --wallet-address <addr> --object-id <id> --object-id-hash <hash> [--name <friendly-name>] [--async] [--orchestrator <inline|subagent>] [--timeout-seconds <n>]`",
3188
+ " Purpose: upload an encrypted object using a valid quote-id.",
3155
3189
  " Required: " + REQUIRED_UPLOAD,
3156
3190
  "",
3157
- "\u2022 `/mnemospark-cloud ls --wallet-address <addr> [--object-key <object-key> | --name <friendly-name>] [--latest|--at <timestamp>]`",
3191
+ "\u2022 `/mnemospark_cloud ls --wallet-address <addr> [--object-key <object-key> | --name <friendly-name>] [--latest|--at <timestamp>]`",
3192
+ " Purpose: look up remote object metadata.",
3158
3193
  " Required: " + REQUIRED_STORAGE_OBJECT,
3159
3194
  "",
3160
- "\u2022 `/mnemospark-cloud download --wallet-address <addr> [--object-key <object-key> | --name <friendly-name>] [--latest|--at <timestamp>] [--async]`",
3195
+ "\u2022 `/mnemospark_cloud download --wallet-address <addr> [--object-key <object-key> | --name <friendly-name>] [--latest|--at <timestamp>] [--async] [--orchestrator <inline|subagent>] [--timeout-seconds <n>]`",
3196
+ " Purpose: fetch an object to local disk.",
3161
3197
  " Required: " + REQUIRED_STORAGE_OBJECT,
3162
3198
  "",
3163
- "\u2022 `/mnemospark-cloud delete --wallet-address <addr> [--object-key <object-key> | --name <friendly-name>] [--latest|--at <timestamp>]`",
3199
+ "\u2022 `/mnemospark_cloud delete --wallet-address <addr> [--object-key <object-key> | --name <friendly-name>] [--latest|--at <timestamp>]`",
3200
+ " Purpose: remove a remote object and local cron tracking when present.",
3164
3201
  " Required: " + REQUIRED_STORAGE_OBJECT,
3165
3202
  "",
3166
- "\u2022 `/mnemospark-cloud op-status --operation-id <id>`",
3203
+ "\u2022 `/mnemospark_cloud op-status --operation-id <id> [--cancel]`",
3204
+ " Purpose: inspect async operation status, or request cancellation for subagent runs.",
3167
3205
  " Required: --operation-id",
3168
3206
  "",
3207
+ "Async orchestration flags (`backup`, `upload`, `download` only):",
3208
+ "\u2022 `--async`",
3209
+ " Start operation in background and return quickly with operation-id.",
3210
+ "\u2022 `--orchestrator <inline|subagent>`",
3211
+ " Choose async engine. Default when omitted is `inline`.",
3212
+ " Use `subagent` for explicit subagent session tracking and cancellation.",
3213
+ "\u2022 `--timeout-seconds <n>`",
3214
+ " Optional per-operation timeout. Valid only with `--async --orchestrator subagent`.",
3215
+ " `n` must be a positive integer (seconds).",
3216
+ "\u2022 `op-status --cancel`",
3217
+ " Cancel a subagent-orchestrated operation by operation-id (idempotent).",
3218
+ "",
3219
+ "Examples:",
3220
+ "\u2022 `/mnemospark_cloud upload ... --async --orchestrator subagent`",
3221
+ "\u2022 `/mnemospark_cloud download ... --async --orchestrator subagent --timeout-seconds 900`",
3222
+ "\u2022 `/mnemospark_cloud op-status --operation-id <id>`",
3223
+ "\u2022 `/mnemospark_cloud op-status --operation-id <id> --cancel`",
3224
+ "",
3169
3225
  "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."
3170
3226
  ].join("\n");
3171
3227
  var UnsupportedBackupPlatformError = class extends Error {
@@ -3255,9 +3311,73 @@ function parseStorageObjectRequestInput(flags, selector) {
3255
3311
  location
3256
3312
  });
3257
3313
  }
3258
- function stripAsyncFlag(args) {
3314
+ function parseOrchestratorMode(value) {
3315
+ if (!value) {
3316
+ return void 0;
3317
+ }
3318
+ const normalized = value.trim().toLowerCase();
3319
+ if (!normalized) {
3320
+ return null;
3321
+ }
3322
+ if (!ORCHESTRATOR_MODES.has(normalized)) {
3323
+ return null;
3324
+ }
3325
+ return normalized;
3326
+ }
3327
+ function parseTimeoutSeconds(value) {
3328
+ if (!value) {
3329
+ return void 0;
3330
+ }
3331
+ const trimmed = value.trim();
3332
+ if (!/^\d+$/.test(trimmed)) {
3333
+ return null;
3334
+ }
3335
+ const parsed = Number.parseInt(trimmed, 10);
3336
+ if (!Number.isFinite(parsed) || parsed <= 0) {
3337
+ return null;
3338
+ }
3339
+ return parsed;
3340
+ }
3341
+ function parseAsyncOperationArgs(flags) {
3342
+ const asyncRequested = flags.async === "true";
3343
+ const hasOrchestratorFlag = typeof flags.orchestrator === "string";
3344
+ const hasTimeoutFlag = typeof flags["timeout-seconds"] === "string";
3345
+ if (!asyncRequested && (hasOrchestratorFlag || hasTimeoutFlag)) {
3346
+ return null;
3347
+ }
3348
+ const parsedOrchestrator = parseOrchestratorMode(flags.orchestrator);
3349
+ if (parsedOrchestrator === null) {
3350
+ return null;
3351
+ }
3352
+ const parsedTimeoutSeconds = parseTimeoutSeconds(flags["timeout-seconds"]);
3353
+ if (parsedTimeoutSeconds === null) {
3354
+ return null;
3355
+ }
3356
+ if (typeof parsedTimeoutSeconds === "number" && (parsedOrchestrator ?? "inline") !== "subagent") {
3357
+ return null;
3358
+ }
3359
+ return {
3360
+ async: asyncRequested,
3361
+ orchestrator: parsedOrchestrator === void 0 ? void 0 : parsedOrchestrator,
3362
+ timeoutSeconds: parsedTimeoutSeconds === void 0 ? void 0 : parsedTimeoutSeconds
3363
+ };
3364
+ }
3365
+ var INVALID_ASYNC_FLAGS_MESSAGE = "invalid async flags. `--orchestrator`/`--timeout-seconds` require `--async`, and `--timeout-seconds` is only valid with `--orchestrator subagent`.";
3366
+ function stripAsyncControlFlags(args) {
3259
3367
  const tokens = tokenizeArgsRaw(args ?? "");
3260
- const filtered = tokens.filter((token) => token.toLowerCase() !== "--async");
3368
+ const filtered = [];
3369
+ for (let idx = 0; idx < tokens.length; idx += 1) {
3370
+ const token = tokens[idx];
3371
+ const lowerToken = token.toLowerCase();
3372
+ if (lowerToken === "--async") {
3373
+ continue;
3374
+ }
3375
+ if (lowerToken === "--orchestrator" || lowerToken === "--timeout-seconds") {
3376
+ idx += 1;
3377
+ continue;
3378
+ }
3379
+ filtered.push(token);
3380
+ }
3261
3381
  return filtered.join(" ");
3262
3382
  }
3263
3383
  function parseCloudArgs(args) {
@@ -3280,8 +3400,21 @@ function parseCloudArgs(args) {
3280
3400
  if (!backupTarget) {
3281
3401
  return { mode: "unknown" };
3282
3402
  }
3283
- const flags = parseNamedFlagsTokens(tokens.slice(1));
3284
- return { mode: "backup", backupTarget, friendlyName: flags?.name?.trim() || void 0 };
3403
+ const remainingTokens = tokens.slice(1);
3404
+ const flags = remainingTokens.length === 0 ? {} : parseNamedFlagsTokens(remainingTokens, BOOLEAN_ASYNC_FLAGS);
3405
+ if (!flags) {
3406
+ return { mode: "backup-invalid" };
3407
+ }
3408
+ const asyncArgs = parseAsyncOperationArgs(flags);
3409
+ if (!asyncArgs) {
3410
+ return { mode: "backup-invalid-async" };
3411
+ }
3412
+ return {
3413
+ mode: "backup",
3414
+ backupTarget,
3415
+ friendlyName: flags.name?.trim() || void 0,
3416
+ ...asyncArgs
3417
+ };
3285
3418
  }
3286
3419
  if (subcommand === "price-storage") {
3287
3420
  const flags = parseNamedFlags(rest);
@@ -3307,6 +3440,10 @@ function parseCloudArgs(args) {
3307
3440
  if (!flags) {
3308
3441
  return { mode: "upload-invalid" };
3309
3442
  }
3443
+ const asyncArgs = parseAsyncOperationArgs(flags);
3444
+ if (!asyncArgs) {
3445
+ return { mode: "upload-invalid-async" };
3446
+ }
3310
3447
  const quoteId = flags["quote-id"]?.trim();
3311
3448
  const walletAddress = flags["wallet-address"]?.trim();
3312
3449
  const objectId = flags["object-id"]?.trim();
@@ -3317,7 +3454,7 @@ function parseCloudArgs(args) {
3317
3454
  return {
3318
3455
  mode: "upload",
3319
3456
  friendlyName: flags.name?.trim() || void 0,
3320
- async: flags.async === "true",
3457
+ ...asyncArgs,
3321
3458
  uploadRequest: {
3322
3459
  quote_id: quoteId,
3323
3460
  wallet_address: walletAddress,
@@ -3346,6 +3483,10 @@ function parseCloudArgs(args) {
3346
3483
  if (!flags) {
3347
3484
  return { mode: "download-invalid" };
3348
3485
  }
3486
+ const asyncArgs = parseAsyncOperationArgs(flags);
3487
+ if (!asyncArgs) {
3488
+ return { mode: "download-invalid-async" };
3489
+ }
3349
3490
  const selector = parseObjectSelector(flags);
3350
3491
  if (!selector) {
3351
3492
  return { mode: "download-invalid" };
@@ -3358,7 +3499,7 @@ function parseCloudArgs(args) {
3358
3499
  mode: "download",
3359
3500
  storageObjectRequest: request,
3360
3501
  nameSelector: selector.nameSelector,
3361
- async: flags.async === "true"
3502
+ ...asyncArgs
3362
3503
  };
3363
3504
  }
3364
3505
  if (subcommand === "delete") {
@@ -3377,12 +3518,12 @@ function parseCloudArgs(args) {
3377
3518
  return { mode: "delete", storageObjectRequest: request, nameSelector: selector.nameSelector };
3378
3519
  }
3379
3520
  if (subcommand === "op-status") {
3380
- const flags = parseNamedFlags(rest);
3521
+ const flags = parseNamedFlags(rest, BOOLEAN_OP_STATUS_FLAGS);
3381
3522
  const operationId = flags?.["operation-id"]?.trim();
3382
3523
  if (!operationId) {
3383
3524
  return { mode: "op-status-invalid" };
3384
3525
  }
3385
- return { mode: "op-status", operationId };
3526
+ return { mode: "op-status", operationId, cancel: flags?.cancel === "true" };
3386
3527
  }
3387
3528
  return { mode: "unknown" };
3388
3529
  }
@@ -3873,17 +4014,37 @@ async function uploadPresignedObjectIfNeeded(uploadResponse, uploadMode, encrypt
3873
4014
  if (!headers.has("content-type")) {
3874
4015
  headers.set("content-type", "application/octet-stream");
3875
4016
  }
3876
- const response = await fetchImpl(uploadResponse.upload_url, {
4017
+ const putBody = new Uint8Array(encryptedContent);
4018
+ const firstAttempt = await fetchImpl(uploadResponse.upload_url, {
3877
4019
  method: "PUT",
3878
4020
  headers,
3879
- body: new Uint8Array(encryptedContent)
4021
+ body: putBody,
4022
+ redirect: "manual"
3880
4023
  });
3881
- if (!response.ok) {
3882
- const details = (await response.text()).trim();
3883
- throw new Error(
3884
- `Presigned upload failed with status ${response.status}${details ? `: ${details}` : ""}`
3885
- );
4024
+ if (firstAttempt.ok) {
4025
+ return;
3886
4026
  }
4027
+ if ((firstAttempt.status === 307 || firstAttempt.status === 308) && firstAttempt.headers.has("location")) {
4028
+ const location = firstAttempt.headers.get("location")?.trim();
4029
+ if (location) {
4030
+ const redirectedAttempt = await fetchImpl(location, {
4031
+ method: "PUT",
4032
+ headers,
4033
+ body: putBody
4034
+ });
4035
+ if (redirectedAttempt.ok) {
4036
+ return;
4037
+ }
4038
+ const redirectedDetails = (await redirectedAttempt.text()).trim();
4039
+ throw new Error(
4040
+ `Presigned upload failed after redirect with status ${redirectedAttempt.status}${redirectedDetails ? `: ${redirectedDetails}` : ""}`
4041
+ );
4042
+ }
4043
+ }
4044
+ const details = (await firstAttempt.text()).trim();
4045
+ throw new Error(
4046
+ `Presigned upload failed with status ${firstAttempt.status}${details ? `: ${details}` : ""}`
4047
+ );
3887
4048
  }
3888
4049
  async function appendStorageUploadLog(upload, homeDir, nowDateFn = () => /* @__PURE__ */ new Date()) {
3889
4050
  return appendObjectLogLine(
@@ -3954,16 +4115,118 @@ function extractUploadErrorMessage(error) {
3954
4115
  function formatPriceStorageUserMessage(quote) {
3955
4116
  return [
3956
4117
  `Your storage quote \`${quote.quote_id}\` is valid for 1 hour, the storage price is \`${quote.storage_price}\` for \`${quote.object_id}\` with file size of \`${quote.object_size_gb}\` in \`${quote.provider}\` \`${quote.location}\``,
3957
- `If you accept this quote run the command /mnemospark-cloud upload --quote-id \`${quote.quote_id}\` --wallet-address \`${quote.addr}\` --object-id \`${quote.object_id}\` --object-id-hash \`${quote.object_id_hash}\``
4118
+ `If you accept this quote run the command /mnemospark_cloud upload --quote-id \`${quote.quote_id}\` --wallet-address \`${quote.addr}\` --object-id \`${quote.object_id}\` --object-id-hash \`${quote.object_id_hash}\``
3958
4119
  ].join("\n");
3959
4120
  }
3960
4121
  function formatStorageLsUserMessage(result, requestedObjectKey) {
3961
4122
  const objectId = result.object_id ?? result.key;
3962
4123
  return `${objectId} with ${requestedObjectKey} is ${result.size_bytes} in ${result.bucket}`;
3963
4124
  }
4125
+ function createInProcessSubagentOrchestrator() {
4126
+ const sessions = /* @__PURE__ */ new Map();
4127
+ const completeSession = async (sessionId, handler) => {
4128
+ const session = sessions.get(sessionId);
4129
+ if (!session || session.terminal) {
4130
+ return false;
4131
+ }
4132
+ session.terminal = true;
4133
+ if (session.timeoutHandle) {
4134
+ clearTimeout(session.timeoutHandle);
4135
+ }
4136
+ sessions.delete(sessionId);
4137
+ await handler(session.hooks);
4138
+ return true;
4139
+ };
4140
+ return {
4141
+ dispatch: async (input) => {
4142
+ const sessionId = `agent:mnemospark:subagent:${randomUUID3()}`;
4143
+ const state = {
4144
+ terminal: false,
4145
+ cancelRequested: false,
4146
+ hooks: input.hooks
4147
+ };
4148
+ sessions.set(sessionId, state);
4149
+ if (typeof input.timeoutSeconds === "number" && input.timeoutSeconds > 0) {
4150
+ state.timeoutHandle = setTimeout(() => {
4151
+ void completeSession(sessionId, async (hooks) => {
4152
+ await hooks?.onTimedOut?.(sessionId);
4153
+ });
4154
+ }, input.timeoutSeconds * 1e3);
4155
+ }
4156
+ setTimeout(() => {
4157
+ void (async () => {
4158
+ try {
4159
+ await input.hooks?.onRunning?.(sessionId);
4160
+ await input.hooks?.onProgress?.(sessionId, "subagent execution started");
4161
+ const result = await input.runTask();
4162
+ const session = sessions.get(sessionId);
4163
+ if (!session || session.terminal) {
4164
+ return;
4165
+ }
4166
+ if (session.cancelRequested) {
4167
+ await completeSession(sessionId, async (hooks) => {
4168
+ await hooks?.onCancelled?.(sessionId, "cancel requested");
4169
+ });
4170
+ return;
4171
+ }
4172
+ if (result.isError) {
4173
+ await completeSession(sessionId, async (hooks) => {
4174
+ await hooks?.onFailed?.(sessionId, {
4175
+ code: "ASYNC_FAILED",
4176
+ message: result.text
4177
+ });
4178
+ });
4179
+ return;
4180
+ }
4181
+ await completeSession(sessionId, async (hooks) => {
4182
+ await hooks?.onCompleted?.(sessionId, result);
4183
+ });
4184
+ } catch (error) {
4185
+ const message = error instanceof Error ? error.message : String(error);
4186
+ const session = sessions.get(sessionId);
4187
+ if (!session || session.terminal) {
4188
+ return;
4189
+ }
4190
+ if (session.cancelRequested) {
4191
+ await completeSession(sessionId, async (hooks) => {
4192
+ await hooks?.onCancelled?.(sessionId, "cancel requested");
4193
+ });
4194
+ return;
4195
+ }
4196
+ await completeSession(sessionId, async (hooks) => {
4197
+ await hooks?.onFailed?.(sessionId, {
4198
+ code: "ASYNC_EXCEPTION",
4199
+ message
4200
+ });
4201
+ });
4202
+ }
4203
+ })();
4204
+ }, 0);
4205
+ return { sessionId };
4206
+ },
4207
+ cancel: async (sessionId, reason) => {
4208
+ const session = sessions.get(sessionId);
4209
+ if (!session) {
4210
+ return { accepted: false };
4211
+ }
4212
+ if (session.terminal) {
4213
+ return { accepted: false, alreadyTerminal: true };
4214
+ }
4215
+ session.cancelRequested = true;
4216
+ await completeSession(sessionId, async (hooks) => {
4217
+ await hooks?.onCancelled?.(sessionId, reason ?? "cancel requested");
4218
+ });
4219
+ return { accepted: true };
4220
+ }
4221
+ };
4222
+ }
3964
4223
  function createCloudCommand(options = {}) {
4224
+ const subagentOrchestrator = options.subagentOrchestrator ?? createInProcessSubagentOrchestrator();
3965
4225
  return {
3966
- name: "mnemospark-cloud",
4226
+ name: "mnemospark_cloud",
4227
+ nativeNames: {
4228
+ default: "mnemospark_cloud"
4229
+ },
3967
4230
  description: "Manage mnemospark cloud storage workflow commands",
3968
4231
  acceptsArgs: true,
3969
4232
  requireAuth: true,
@@ -3987,6 +4250,7 @@ function createCloudCommand(options = {}) {
3987
4250
  proxyQuoteOptions: options.proxyQuoteOptions,
3988
4251
  proxyUploadOptions: options.proxyUploadOptions,
3989
4252
  proxyUploadConfirmOptions: options.proxyUploadConfirmOptions,
4253
+ subagentOrchestrator,
3990
4254
  proxyStorageOptions: options.proxyStorageOptions
3991
4255
  });
3992
4256
  } catch (outerError) {
@@ -3996,7 +4260,42 @@ function createCloudCommand(options = {}) {
3996
4260
  }
3997
4261
  };
3998
4262
  }
3999
- async function resolveNameSelectorIfNeeded(datastore, request, selector) {
4263
+ async function resolveFriendlyNameFromManifest(params, homeDir) {
4264
+ const manifestPath = join8(homeDir ?? homedir6(), ".openclaw", "mnemospark", "manifest.jsonl");
4265
+ let manifestRaw;
4266
+ try {
4267
+ manifestRaw = await readFile3(manifestPath, "utf-8");
4268
+ } catch {
4269
+ return { objectKey: null, matchCount: 0 };
4270
+ }
4271
+ const wallet = params.walletAddress.trim();
4272
+ const name = params.friendlyName.trim();
4273
+ const atMs = params.at ? Date.parse(params.at) : Number.NaN;
4274
+ const hasAt = Number.isFinite(atMs);
4275
+ const rows = manifestRaw.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => {
4276
+ try {
4277
+ return JSON.parse(line);
4278
+ } catch {
4279
+ return null;
4280
+ }
4281
+ }).filter((row) => Boolean(row)).filter((row) => {
4282
+ if (!row.object_key || !row.friendly_name || !row.wallet_address || !row.created_at)
4283
+ return false;
4284
+ if (row.friendly_name !== name) return false;
4285
+ if (row.wallet_address.trim() !== wallet) return false;
4286
+ if (params.latest || !hasAt) return true;
4287
+ const createdAtMs = Date.parse(row.created_at);
4288
+ return Number.isFinite(createdAtMs) && createdAtMs <= atMs;
4289
+ }).sort((a, b) => Date.parse(b.created_at ?? "") - Date.parse(a.created_at ?? ""));
4290
+ if (rows.length === 0) {
4291
+ return { objectKey: null, matchCount: 0 };
4292
+ }
4293
+ if (!params.latest && !hasAt && rows.length > 1) {
4294
+ return { objectKey: null, matchCount: rows.length };
4295
+ }
4296
+ return { objectKey: rows[0].object_key ?? null, matchCount: rows.length };
4297
+ }
4298
+ async function resolveNameSelectorIfNeeded(datastore, request, selector, homeDir) {
4000
4299
  if (!selector) {
4001
4300
  const parsedRequest2 = parseStorageObjectRequest(request);
4002
4301
  if (!parsedRequest2) {
@@ -4004,6 +4303,12 @@ async function resolveNameSelectorIfNeeded(datastore, request, selector) {
4004
4303
  }
4005
4304
  return { request: parsedRequest2 };
4006
4305
  }
4306
+ let sqliteUnavailable = false;
4307
+ try {
4308
+ await datastore.ensureReady();
4309
+ } catch {
4310
+ sqliteUnavailable = true;
4311
+ }
4007
4312
  const matches = await datastore.countFriendlyNameMatches(request.wallet_address, selector.name);
4008
4313
  if (matches > 1 && !selector.latest && !selector.at) {
4009
4314
  return {
@@ -4016,18 +4321,41 @@ async function resolveNameSelectorIfNeeded(datastore, request, selector) {
4016
4321
  latest: selector.latest,
4017
4322
  at: selector.at
4018
4323
  });
4019
- if (!resolved || !resolved.objectKey) {
4324
+ let resolvedObjectKey = resolved?.objectKey ?? null;
4325
+ let degradedWarning;
4326
+ if (!resolvedObjectKey && sqliteUnavailable) {
4327
+ const manifestResolved = await resolveFriendlyNameFromManifest(
4328
+ {
4329
+ walletAddress: request.wallet_address,
4330
+ friendlyName: selector.name,
4331
+ latest: selector.latest,
4332
+ at: selector.at
4333
+ },
4334
+ homeDir
4335
+ );
4336
+ if (manifestResolved.matchCount > 1 && !selector.latest && !selector.at) {
4337
+ return {
4338
+ error: `Multiple objects match --name ${selector.name}. Add --latest or --at <timestamp>.`
4339
+ };
4340
+ }
4341
+ resolvedObjectKey = manifestResolved.objectKey;
4342
+ if (resolvedObjectKey) {
4343
+ degradedWarning = "SQLite friendly-name index unavailable; resolved --name via manifest.jsonl fallback.";
4344
+ }
4345
+ }
4346
+ if (!resolvedObjectKey) {
4020
4347
  return { error: `No object found for --name ${selector.name}.` };
4021
4348
  }
4022
4349
  const parsedRequest = parseStorageObjectRequest({
4023
4350
  ...request,
4024
- object_key: resolved.objectKey
4351
+ object_key: resolvedObjectKey
4025
4352
  });
4026
4353
  if (!parsedRequest) {
4027
4354
  return { error: "Cannot resolve storage object request." };
4028
4355
  }
4029
4356
  return {
4030
- request: parsedRequest
4357
+ request: parsedRequest,
4358
+ degradedWarning
4031
4359
  };
4032
4360
  }
4033
4361
  async function emitCloudEvent(eventType, details, homeDir) {
@@ -4047,6 +4375,38 @@ async function emitCloudEventBestEffort(eventType, details, homeDir) {
4047
4375
  } catch {
4048
4376
  }
4049
4377
  }
4378
+ function toOperationEventPayload(eventType, context) {
4379
+ return {
4380
+ operation_id: context.operationId,
4381
+ trace_id: context.traceId,
4382
+ event_type: eventType,
4383
+ status: context.status,
4384
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
4385
+ wallet_address: context.walletAddress ?? void 0,
4386
+ object_id: context.objectId ?? void 0,
4387
+ object_key: context.objectKey ?? void 0,
4388
+ quote_id: context.quoteId ?? void 0,
4389
+ orchestrator: context.orchestrator ?? void 0,
4390
+ "subagent-session-id": context.subagentSessionId ?? void 0,
4391
+ "timeout-seconds": context.timeoutSeconds ?? void 0,
4392
+ "error-code": context.errorCode ?? void 0,
4393
+ "error-message": context.errorMessage ?? void 0,
4394
+ progress: context.progressMessage ?? void 0
4395
+ };
4396
+ }
4397
+ async function emitOperationEvent(eventType, context, homeDir) {
4398
+ const payload = toOperationEventPayload(eventType, context);
4399
+ await Promise.all([
4400
+ appendJsonlEvent("events.jsonl", payload, homeDir),
4401
+ appendJsonlEvent("proxy-events.jsonl", payload, homeDir)
4402
+ ]);
4403
+ }
4404
+ async function emitOperationEventBestEffort(eventType, context, homeDir) {
4405
+ try {
4406
+ await emitOperationEvent(eventType, context, homeDir);
4407
+ } catch {
4408
+ }
4409
+ }
4050
4410
  function buildRequestCorrelation(forcedOperationId, forcedTraceId) {
4051
4411
  const operationId = forcedOperationId?.trim() || randomUUID3();
4052
4412
  const traceId = forcedTraceId?.trim() || randomUUID3();
@@ -4067,6 +4427,7 @@ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
4067
4427
  const requestStorageLs = options.requestStorageLsFn;
4068
4428
  const requestStorageDownload = options.requestStorageDownloadFn;
4069
4429
  const requestStorageDelete = options.requestStorageDeleteFn;
4430
+ const subagentOrchestrator = options.subagentOrchestrator;
4070
4431
  if (parsed.mode === "help" || parsed.mode === "unknown") {
4071
4432
  return {
4072
4433
  text: CLOUD_HELP_TEXT,
@@ -4079,12 +4440,30 @@ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
4079
4440
  isError: true
4080
4441
  };
4081
4442
  }
4443
+ if (parsed.mode === "backup-invalid") {
4444
+ return {
4445
+ text: "Cannot build storage object",
4446
+ isError: true
4447
+ };
4448
+ }
4449
+ if (parsed.mode === "backup-invalid-async") {
4450
+ return {
4451
+ text: `Cannot build storage object: ${INVALID_ASYNC_FLAGS_MESSAGE}`,
4452
+ isError: true
4453
+ };
4454
+ }
4082
4455
  if (parsed.mode === "upload-invalid") {
4083
4456
  return {
4084
4457
  text: `Cannot upload storage object: required arguments are ${REQUIRED_UPLOAD}.`,
4085
4458
  isError: true
4086
4459
  };
4087
4460
  }
4461
+ if (parsed.mode === "upload-invalid-async") {
4462
+ return {
4463
+ text: `Cannot upload storage object: ${INVALID_ASYNC_FLAGS_MESSAGE}`,
4464
+ isError: true
4465
+ };
4466
+ }
4088
4467
  if (parsed.mode === "ls-invalid") {
4089
4468
  return {
4090
4469
  text: `Cannot list storage object: required arguments are ${REQUIRED_STORAGE_OBJECT}.`,
@@ -4097,6 +4476,12 @@ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
4097
4476
  isError: true
4098
4477
  };
4099
4478
  }
4479
+ if (parsed.mode === "download-invalid-async") {
4480
+ return {
4481
+ text: `Cannot download file: ${INVALID_ASYNC_FLAGS_MESSAGE}`,
4482
+ isError: true
4483
+ };
4484
+ }
4100
4485
  if (parsed.mode === "delete-invalid") {
4101
4486
  return {
4102
4487
  text: `Cannot delete file: required arguments are ${REQUIRED_STORAGE_OBJECT}.`,
@@ -4110,70 +4495,450 @@ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
4110
4495
  };
4111
4496
  }
4112
4497
  const datastore = await createCloudDatastore(objectLogHomeDir);
4498
+ const terminalOperationStatuses = /* @__PURE__ */ new Set(["succeeded", "failed", "cancelled", "timed_out"]);
4499
+ const isTerminalOperationStatus = (status) => terminalOperationStatuses.has(status);
4500
+ const formatOperationStatus = (operation) => ({
4501
+ text: [
4502
+ `operation-id: ${operation.operation_id}`,
4503
+ `type: ${operation.type}`,
4504
+ `status: ${operation.status}`,
4505
+ `started-at: ${operation.started_at ?? "n/a"}`,
4506
+ `finished-at: ${operation.finished_at ?? "n/a"}`,
4507
+ operation.orchestrator ? `orchestrator: ${operation.orchestrator}` : null,
4508
+ operation.subagent_session_id ? `subagent-session-id: ${operation.subagent_session_id}` : null,
4509
+ operation.timeout_seconds ? `timeout-seconds: ${operation.timeout_seconds}` : null,
4510
+ operation.error_code ? `error-code: ${operation.error_code}` : null,
4511
+ operation.error_message ? `error-message: ${operation.error_message}` : null
4512
+ ].filter((v) => Boolean(v)).join("\n"),
4513
+ isError: operation.status === "failed" || operation.status === "cancelled" || operation.status === "timed_out"
4514
+ });
4113
4515
  if (parsed.mode === "op-status") {
4114
- const operation = await datastore.findOperationById(parsed.operationId);
4516
+ let operation = await datastore.findOperationById(parsed.operationId);
4115
4517
  if (!operation) {
4116
4518
  return {
4117
4519
  text: `Operation not found: ${parsed.operationId}`,
4118
4520
  isError: true
4119
4521
  };
4120
4522
  }
4121
- return {
4122
- text: [
4123
- `operation-id: ${operation.operation_id}`,
4124
- `type: ${operation.type}`,
4125
- `status: ${operation.status}`,
4126
- `started-at: ${operation.started_at ?? "n/a"}`,
4127
- `finished-at: ${operation.finished_at ?? "n/a"}`,
4128
- operation.error_code ? `error-code: ${operation.error_code}` : null,
4129
- operation.error_message ? `error-message: ${operation.error_message}` : null
4130
- ].filter((v) => Boolean(v)).join("\n"),
4131
- isError: operation.status === "failed"
4132
- };
4523
+ if (parsed.cancel) {
4524
+ if (operation.orchestrator !== "subagent" || !operation.subagent_session_id) {
4525
+ return {
4526
+ text: "Cancellation is only supported for subagent-orchestrated operations.",
4527
+ isError: true
4528
+ };
4529
+ }
4530
+ if (!isTerminalOperationStatus(operation.status)) {
4531
+ const traceId = operation.trace_id ?? randomUUID3();
4532
+ const cancelRequestedAt = (/* @__PURE__ */ new Date()).toISOString();
4533
+ await datastore.upsertOperation({
4534
+ operation_id: operation.operation_id,
4535
+ type: operation.type,
4536
+ object_id: operation.object_id,
4537
+ quote_id: operation.quote_id,
4538
+ trace_id: traceId,
4539
+ orchestrator: "subagent",
4540
+ subagent_session_id: operation.subagent_session_id,
4541
+ timeout_seconds: operation.timeout_seconds,
4542
+ cancel_requested_at: cancelRequestedAt,
4543
+ status: "running",
4544
+ error_code: null,
4545
+ error_message: null
4546
+ });
4547
+ await emitOperationEventBestEffort(
4548
+ "operation.cancel.requested",
4549
+ {
4550
+ operationId: operation.operation_id,
4551
+ traceId,
4552
+ status: "running",
4553
+ objectId: operation.object_id,
4554
+ quoteId: operation.quote_id,
4555
+ orchestrator: "subagent",
4556
+ subagentSessionId: operation.subagent_session_id,
4557
+ timeoutSeconds: operation.timeout_seconds
4558
+ },
4559
+ objectLogHomeDir
4560
+ );
4561
+ const cancelResult = await subagentOrchestrator.cancel(
4562
+ operation.subagent_session_id,
4563
+ "cancel requested by op-status"
4564
+ );
4565
+ if (cancelResult.accepted || cancelResult.alreadyTerminal) {
4566
+ const afterCancel = await datastore.findOperationById(parsed.operationId);
4567
+ if (afterCancel && !isTerminalOperationStatus(afterCancel.status)) {
4568
+ await datastore.upsertOperation({
4569
+ operation_id: operation.operation_id,
4570
+ type: operation.type,
4571
+ object_id: operation.object_id,
4572
+ quote_id: operation.quote_id,
4573
+ trace_id: traceId,
4574
+ orchestrator: "subagent",
4575
+ subagent_session_id: operation.subagent_session_id,
4576
+ timeout_seconds: operation.timeout_seconds,
4577
+ cancel_requested_at: cancelRequestedAt,
4578
+ status: "cancelled",
4579
+ error_code: "ASYNC_CANCELLED",
4580
+ error_message: "Operation cancelled by user request."
4581
+ });
4582
+ await emitOperationEventBestEffort(
4583
+ "operation.cancelled",
4584
+ {
4585
+ operationId: operation.operation_id,
4586
+ traceId,
4587
+ status: "cancelled",
4588
+ objectId: operation.object_id,
4589
+ quoteId: operation.quote_id,
4590
+ orchestrator: "subagent",
4591
+ subagentSessionId: operation.subagent_session_id,
4592
+ timeoutSeconds: operation.timeout_seconds,
4593
+ errorCode: "ASYNC_CANCELLED",
4594
+ errorMessage: "Operation cancelled by user request."
4595
+ },
4596
+ objectLogHomeDir
4597
+ );
4598
+ }
4599
+ }
4600
+ }
4601
+ operation = await datastore.findOperationById(parsed.operationId);
4602
+ if (!operation) {
4603
+ return {
4604
+ text: `Operation not found: ${parsed.operationId}`,
4605
+ isError: true
4606
+ };
4607
+ }
4608
+ }
4609
+ return formatOperationStatus(operation);
4133
4610
  }
4134
- if ((parsed.mode === "upload" || parsed.mode === "download") && parsed.async) {
4611
+ if ((parsed.mode === "backup" || parsed.mode === "upload" || parsed.mode === "download") && parsed.async) {
4135
4612
  const asyncCorrelation = buildRequestCorrelation();
4136
4613
  const operationId = asyncCorrelation.operationId;
4137
4614
  const opType = parsed.mode;
4138
4615
  const opObject = parsed.mode === "upload" ? parsed.uploadRequest.object_id : null;
4139
4616
  const opQuote = parsed.mode === "upload" ? parsed.uploadRequest.quote_id : null;
4617
+ const orchestratorMode = parsed.orchestrator ?? "inline";
4618
+ const timeoutSeconds = orchestratorMode === "subagent" ? parsed.timeoutSeconds ?? null : null;
4619
+ const eventContextBase = {
4620
+ operationId,
4621
+ traceId: asyncCorrelation.traceId,
4622
+ walletAddress: parsed.mode === "upload" ? parsed.uploadRequest.wallet_address : parsed.mode === "download" ? parsed.storageObjectRequest.wallet_address : null,
4623
+ objectId: opObject,
4624
+ objectKey: parsed.mode === "download" ? parsed.storageObjectRequest.object_key ?? null : null,
4625
+ quoteId: opQuote,
4626
+ orchestrator: orchestratorMode,
4627
+ timeoutSeconds
4628
+ };
4140
4629
  await datastore.upsertOperation({
4141
4630
  operation_id: operationId,
4142
4631
  type: opType,
4143
4632
  object_id: opObject,
4144
4633
  quote_id: opQuote,
4634
+ trace_id: asyncCorrelation.traceId,
4635
+ orchestrator: orchestratorMode,
4636
+ timeout_seconds: timeoutSeconds,
4145
4637
  status: "started",
4146
4638
  error_code: null,
4147
4639
  error_message: null
4148
4640
  });
4149
- const syncArgs = stripAsyncFlag(ctx.args);
4150
- void runCloudCommandHandler({ args: syncArgs }, options, {
4151
- forcedOperationId: asyncCorrelation.operationId,
4152
- forcedTraceId: asyncCorrelation.traceId
4153
- }).then(async (result) => {
4641
+ await emitOperationEventBestEffort(
4642
+ "operation.dispatched",
4643
+ { ...eventContextBase, status: "started" },
4644
+ objectLogHomeDir
4645
+ );
4646
+ const syncArgs = stripAsyncControlFlags(ctx.args);
4647
+ if (orchestratorMode === "subagent") {
4648
+ const subagentTask = {
4649
+ schema: "mnemospark.subagent-task.v1",
4650
+ operationId,
4651
+ traceId: asyncCorrelation.traceId,
4652
+ command: parsed.mode,
4653
+ args: syncArgs,
4654
+ timeoutSeconds: parsed.timeoutSeconds,
4655
+ requestedBy: {
4656
+ pluginCommand: "mnemospark_cloud",
4657
+ chatId: ctx.channel,
4658
+ senderId: ctx.senderId
4659
+ }
4660
+ };
4661
+ try {
4662
+ const dispatchResult = await subagentOrchestrator.dispatch({
4663
+ task: subagentTask,
4664
+ timeoutSeconds: parsed.timeoutSeconds,
4665
+ runTask: async () => runCloudCommandHandler(
4666
+ { args: syncArgs, channel: ctx.channel, senderId: ctx.senderId },
4667
+ options,
4668
+ {
4669
+ forcedOperationId: asyncCorrelation.operationId,
4670
+ forcedTraceId: asyncCorrelation.traceId
4671
+ }
4672
+ ),
4673
+ hooks: {
4674
+ onRunning: async (sessionId) => {
4675
+ await datastore.upsertOperation({
4676
+ operation_id: operationId,
4677
+ type: opType,
4678
+ object_id: opObject,
4679
+ quote_id: opQuote,
4680
+ trace_id: asyncCorrelation.traceId,
4681
+ orchestrator: "subagent",
4682
+ subagent_session_id: sessionId,
4683
+ timeout_seconds: timeoutSeconds,
4684
+ status: "running",
4685
+ error_code: null,
4686
+ error_message: null
4687
+ });
4688
+ await emitOperationEventBestEffort(
4689
+ "operation.progress",
4690
+ {
4691
+ ...eventContextBase,
4692
+ status: "running",
4693
+ subagentSessionId: sessionId,
4694
+ progressMessage: "subagent running"
4695
+ },
4696
+ objectLogHomeDir
4697
+ );
4698
+ },
4699
+ onProgress: async (sessionId, message) => {
4700
+ await emitOperationEventBestEffort(
4701
+ "operation.progress",
4702
+ {
4703
+ ...eventContextBase,
4704
+ status: "running",
4705
+ subagentSessionId: sessionId,
4706
+ progressMessage: message
4707
+ },
4708
+ objectLogHomeDir
4709
+ );
4710
+ },
4711
+ onCompleted: async (sessionId) => {
4712
+ await datastore.upsertOperation({
4713
+ operation_id: operationId,
4714
+ type: opType,
4715
+ object_id: opObject,
4716
+ quote_id: opQuote,
4717
+ trace_id: asyncCorrelation.traceId,
4718
+ orchestrator: "subagent",
4719
+ subagent_session_id: sessionId,
4720
+ timeout_seconds: timeoutSeconds,
4721
+ status: "succeeded",
4722
+ error_code: null,
4723
+ error_message: null
4724
+ });
4725
+ await emitOperationEventBestEffort(
4726
+ "operation.completed",
4727
+ {
4728
+ ...eventContextBase,
4729
+ status: "succeeded",
4730
+ subagentSessionId: sessionId
4731
+ },
4732
+ objectLogHomeDir
4733
+ );
4734
+ },
4735
+ onFailed: async (sessionId, details) => {
4736
+ await datastore.upsertOperation({
4737
+ operation_id: operationId,
4738
+ type: opType,
4739
+ object_id: opObject,
4740
+ quote_id: opQuote,
4741
+ trace_id: asyncCorrelation.traceId,
4742
+ orchestrator: "subagent",
4743
+ subagent_session_id: sessionId,
4744
+ timeout_seconds: timeoutSeconds,
4745
+ status: "failed",
4746
+ error_code: details.code,
4747
+ error_message: details.message
4748
+ });
4749
+ await emitOperationEventBestEffort(
4750
+ "operation.completed",
4751
+ {
4752
+ ...eventContextBase,
4753
+ status: "failed",
4754
+ subagentSessionId: sessionId,
4755
+ errorCode: details.code,
4756
+ errorMessage: details.message
4757
+ },
4758
+ objectLogHomeDir
4759
+ );
4760
+ },
4761
+ onCancelled: async (sessionId, reason) => {
4762
+ await datastore.upsertOperation({
4763
+ operation_id: operationId,
4764
+ type: opType,
4765
+ object_id: opObject,
4766
+ quote_id: opQuote,
4767
+ trace_id: asyncCorrelation.traceId,
4768
+ orchestrator: "subagent",
4769
+ subagent_session_id: sessionId,
4770
+ timeout_seconds: timeoutSeconds,
4771
+ cancel_requested_at: (/* @__PURE__ */ new Date()).toISOString(),
4772
+ status: "cancelled",
4773
+ error_code: "ASYNC_CANCELLED",
4774
+ error_message: reason ?? "Operation cancelled."
4775
+ });
4776
+ await emitOperationEventBestEffort(
4777
+ "operation.cancelled",
4778
+ {
4779
+ ...eventContextBase,
4780
+ status: "cancelled",
4781
+ subagentSessionId: sessionId,
4782
+ errorCode: "ASYNC_CANCELLED",
4783
+ errorMessage: reason ?? "Operation cancelled."
4784
+ },
4785
+ objectLogHomeDir
4786
+ );
4787
+ },
4788
+ onTimedOut: async (sessionId) => {
4789
+ await datastore.upsertOperation({
4790
+ operation_id: operationId,
4791
+ type: opType,
4792
+ object_id: opObject,
4793
+ quote_id: opQuote,
4794
+ trace_id: asyncCorrelation.traceId,
4795
+ orchestrator: "subagent",
4796
+ subagent_session_id: sessionId,
4797
+ timeout_seconds: timeoutSeconds,
4798
+ status: "timed_out",
4799
+ error_code: "ASYNC_TIMEOUT",
4800
+ error_message: "Operation timed out."
4801
+ });
4802
+ await emitOperationEventBestEffort(
4803
+ "operation.timed_out",
4804
+ {
4805
+ ...eventContextBase,
4806
+ status: "timed_out",
4807
+ subagentSessionId: sessionId,
4808
+ errorCode: "ASYNC_TIMEOUT",
4809
+ errorMessage: "Operation timed out."
4810
+ },
4811
+ objectLogHomeDir
4812
+ );
4813
+ }
4814
+ }
4815
+ });
4816
+ const operationAfterDispatch = await datastore.findOperationById(operationId);
4817
+ if (operationAfterDispatch?.subagent_session_id !== dispatchResult.sessionId) {
4818
+ await datastore.upsertOperation({
4819
+ operation_id: operationId,
4820
+ type: opType,
4821
+ object_id: opObject,
4822
+ quote_id: opQuote,
4823
+ trace_id: asyncCorrelation.traceId,
4824
+ orchestrator: "subagent",
4825
+ subagent_session_id: dispatchResult.sessionId,
4826
+ timeout_seconds: timeoutSeconds,
4827
+ status: operationAfterDispatch?.status ?? "started",
4828
+ error_code: operationAfterDispatch?.error_code ?? null,
4829
+ error_message: operationAfterDispatch?.error_message ?? null
4830
+ });
4831
+ }
4832
+ return {
4833
+ text: [
4834
+ `Operation started in background. operation-id: ${operationId}`,
4835
+ `orchestrator: subagent`,
4836
+ `subagent-session-id: ${dispatchResult.sessionId}`,
4837
+ timeoutSeconds ? `timeout-seconds: ${timeoutSeconds}` : null,
4838
+ `Use /mnemospark_cloud op-status --operation-id ${operationId}`
4839
+ ].filter((line) => Boolean(line)).join("\n")
4840
+ };
4841
+ } catch (dispatchError) {
4842
+ const dispatchMessage = dispatchError instanceof Error ? dispatchError.message : String(dispatchError);
4843
+ await datastore.upsertOperation({
4844
+ operation_id: operationId,
4845
+ type: opType,
4846
+ object_id: opObject,
4847
+ quote_id: opQuote,
4848
+ trace_id: asyncCorrelation.traceId,
4849
+ orchestrator: "subagent",
4850
+ timeout_seconds: timeoutSeconds,
4851
+ status: "failed",
4852
+ error_code: "ASYNC_DISPATCH_FAILED",
4853
+ error_message: dispatchMessage
4854
+ });
4855
+ await emitOperationEventBestEffort(
4856
+ "operation.completed",
4857
+ {
4858
+ ...eventContextBase,
4859
+ status: "failed",
4860
+ errorCode: "ASYNC_DISPATCH_FAILED",
4861
+ errorMessage: dispatchMessage
4862
+ },
4863
+ objectLogHomeDir
4864
+ );
4865
+ return {
4866
+ text: `Cannot dispatch subagent operation: ${dispatchMessage}
4867
+ operation-id: ${operationId}`,
4868
+ isError: true
4869
+ };
4870
+ }
4871
+ }
4872
+ await datastore.upsertOperation({
4873
+ operation_id: operationId,
4874
+ type: opType,
4875
+ object_id: opObject,
4876
+ quote_id: opQuote,
4877
+ trace_id: asyncCorrelation.traceId,
4878
+ orchestrator: "inline",
4879
+ status: "running",
4880
+ error_code: null,
4881
+ error_message: null
4882
+ });
4883
+ void runCloudCommandHandler(
4884
+ { args: syncArgs, channel: ctx.channel, senderId: ctx.senderId },
4885
+ options,
4886
+ {
4887
+ forcedOperationId: asyncCorrelation.operationId,
4888
+ forcedTraceId: asyncCorrelation.traceId
4889
+ }
4890
+ ).then(async (result) => {
4154
4891
  await datastore.upsertOperation({
4155
4892
  operation_id: operationId,
4156
4893
  type: opType,
4157
4894
  object_id: opObject,
4158
4895
  quote_id: opQuote,
4896
+ trace_id: asyncCorrelation.traceId,
4897
+ orchestrator: "inline",
4159
4898
  status: result.isError ? "failed" : "succeeded",
4160
4899
  error_code: result.isError ? "ASYNC_FAILED" : null,
4161
4900
  error_message: result.isError ? result.text : null
4162
4901
  });
4902
+ await emitOperationEventBestEffort(
4903
+ "operation.completed",
4904
+ {
4905
+ ...eventContextBase,
4906
+ status: result.isError ? "failed" : "succeeded",
4907
+ errorCode: result.isError ? "ASYNC_FAILED" : null,
4908
+ errorMessage: result.isError ? result.text : null
4909
+ },
4910
+ objectLogHomeDir
4911
+ );
4163
4912
  }).catch(async (err) => {
4913
+ const errorMessage = err instanceof Error ? err.message : String(err);
4164
4914
  await datastore.upsertOperation({
4165
4915
  operation_id: operationId,
4166
4916
  type: opType,
4167
4917
  object_id: opObject,
4168
4918
  quote_id: opQuote,
4919
+ trace_id: asyncCorrelation.traceId,
4920
+ orchestrator: "inline",
4169
4921
  status: "failed",
4170
4922
  error_code: "ASYNC_EXCEPTION",
4171
- error_message: err instanceof Error ? err.message : String(err)
4923
+ error_message: errorMessage
4172
4924
  });
4925
+ await emitOperationEventBestEffort(
4926
+ "operation.completed",
4927
+ {
4928
+ ...eventContextBase,
4929
+ status: "failed",
4930
+ errorCode: "ASYNC_EXCEPTION",
4931
+ errorMessage
4932
+ },
4933
+ objectLogHomeDir
4934
+ );
4173
4935
  });
4174
4936
  return {
4175
- text: `Operation started in background. operation-id: ${operationId}
4176
- Use /mnemospark-cloud op-status --operation-id ${operationId}`
4937
+ text: [
4938
+ `Operation started in background. operation-id: ${operationId}`,
4939
+ `orchestrator: inline`,
4940
+ `Use /mnemospark_cloud op-status --operation-id ${operationId}`
4941
+ ].join("\n")
4177
4942
  };
4178
4943
  }
4179
4944
  if (parsed.mode === "backup") {
@@ -4300,7 +5065,7 @@ Use /mnemospark-cloud op-status --operation-id ${operationId}`
4300
5065
  const loggedQuote = await datastore.findQuoteById(parsed.uploadRequest.quote_id) ?? await findLoggedPriceStorageQuote(parsed.uploadRequest.quote_id, objectLogHomeDir);
4301
5066
  if (!loggedQuote) {
4302
5067
  return {
4303
- text: "Cannot upload storage object: quote-id not found in object.log. Run /mnemospark-cloud price-storage first.",
5068
+ text: "Cannot upload storage object: quote-id not found in object.log. Run /mnemospark_cloud price-storage first.",
4304
5069
  isError: true
4305
5070
  };
4306
5071
  }
@@ -4319,7 +5084,7 @@ Use /mnemospark-cloud op-status --operation-id ${operationId}`
4319
5084
  archiveStats = await stat2(archivePath);
4320
5085
  } catch {
4321
5086
  return {
4322
- text: `Cannot upload storage object: local archive not found at ${archivePath}. Run /mnemospark-cloud backup first.`,
5087
+ text: `Cannot upload storage object: local archive not found at ${archivePath}. Run /mnemospark_cloud backup first.`,
4323
5088
  isError: true
4324
5089
  };
4325
5090
  }
@@ -4452,18 +5217,50 @@ Use /mnemospark-cloud op-status --operation-id ${operationId}`
4452
5217
  status: "active"
4453
5218
  });
4454
5219
  if (parsed.friendlyName?.trim()) {
5220
+ const normalizedFriendlyName = parsed.friendlyName.trim();
4455
5221
  await datastore.upsertFriendlyName({
4456
- friendly_name: parsed.friendlyName.trim(),
5222
+ friendly_name: normalizedFriendlyName,
4457
5223
  object_id: finalizedUploadResponse.object_id,
4458
5224
  object_key: finalizedUploadResponse.object_key,
4459
5225
  quote_id: finalizedUploadResponse.quote_id,
4460
5226
  wallet_address: finalizedUploadResponse.addr
4461
5227
  });
5228
+ let friendlyNameVerified = false;
5229
+ try {
5230
+ const readBack = await datastore.resolveFriendlyName({
5231
+ walletAddress: finalizedUploadResponse.addr,
5232
+ friendlyName: normalizedFriendlyName,
5233
+ latest: true
5234
+ });
5235
+ friendlyNameVerified = Boolean(readBack?.objectKey) && readBack?.objectKey === finalizedUploadResponse.object_key;
5236
+ } catch {
5237
+ friendlyNameVerified = false;
5238
+ }
5239
+ if (!friendlyNameVerified) {
5240
+ const warning = "SQLite friendly-name write verification failed; manifest fallback may be required for --name lookups.";
5241
+ await emitCloudEventBestEffort(
5242
+ "friendly_name.write_verification_failed",
5243
+ {
5244
+ operation_id: uploadCorrelation.operationId,
5245
+ trace_id: uploadCorrelation.traceId,
5246
+ wallet_address: finalizedUploadResponse.addr,
5247
+ object_id: finalizedUploadResponse.object_id,
5248
+ object_key: finalizedUploadResponse.object_key,
5249
+ quote_id: finalizedUploadResponse.quote_id,
5250
+ friendly_name: normalizedFriendlyName,
5251
+ warning
5252
+ },
5253
+ objectLogHomeDir
5254
+ );
5255
+ if (process.env.MNEMOSPARK_SQLITE_STRICT === "1") {
5256
+ throw new Error(warning);
5257
+ }
5258
+ }
4462
5259
  try {
4463
5260
  await appendJsonlEvent(
4464
5261
  "manifest.jsonl",
4465
5262
  {
4466
- friendly_name: parsed.friendlyName.trim(),
5263
+ friendly_name: normalizedFriendlyName,
4467
5264
  object_id: finalizedUploadResponse.object_id,
4468
5265
  object_key: finalizedUploadResponse.object_key,
4469
5266
  quote_id: finalizedUploadResponse.quote_id,
@@ -4516,12 +5313,24 @@ Use /mnemospark-cloud op-status --operation-id ${operationId}`
4516
5313
  const resolved = await resolveNameSelectorIfNeeded(
4517
5314
  datastore,
4518
5315
  parsed.storageObjectRequest,
4519
- parsed.nameSelector
5316
+ parsed.nameSelector,
5317
+ objectLogHomeDir
4520
5318
  );
4521
5319
  if (resolved.error || !resolved.request) {
4522
5320
  return { text: resolved.error ?? "Cannot resolve storage object request.", isError: true };
4523
5321
  }
4524
5322
  const resolvedRequest = resolved.request;
5323
+ if (resolved.degradedWarning) {
5324
+ await emitCloudEventBestEffort(
5325
+ "name_resolution.degraded",
5326
+ {
5327
+ wallet_address: resolvedRequest.wallet_address,
5328
+ object_key: resolvedRequest.object_key,
5329
+ warning: resolved.degradedWarning
5330
+ },
5331
+ objectLogHomeDir
5332
+ );
5333
+ }
4525
5334
  const correlation = buildRequestCorrelation();
4526
5335
  const operationId = correlation.operationId;
4527
5336
  const knownObject = await datastore.findObjectByObjectKey(resolvedRequest.object_key);
@@ -4563,8 +5372,10 @@ Use /mnemospark-cloud op-status --operation-id ${operationId}`
4563
5372
  },
4564
5373
  objectLogHomeDir
4565
5374
  );
5375
+ const lsText = formatStorageLsUserMessage(lsResult, resolvedRequest.object_key);
4566
5376
  return {
4567
- text: formatStorageLsUserMessage(lsResult, resolvedRequest.object_key)
5377
+ text: resolved.degradedWarning ? `${resolved.degradedWarning}
5378
+ ${lsText}` : lsText
4568
5379
  };
4569
5380
  } catch {
4570
5381
  await datastore.upsertOperation({
@@ -4597,12 +5408,24 @@ Use /mnemospark-cloud op-status --operation-id ${operationId}`
4597
5408
  const resolved = await resolveNameSelectorIfNeeded(
4598
5409
  datastore,
4599
5410
  parsed.storageObjectRequest,
4600
- parsed.nameSelector
5411
+ parsed.nameSelector,
5412
+ objectLogHomeDir
4601
5413
  );
4602
5414
  if (resolved.error || !resolved.request) {
4603
5415
  return { text: resolved.error ?? "Cannot resolve storage object request.", isError: true };
4604
5416
  }
4605
5417
  const resolvedRequest = resolved.request;
5418
+ if (resolved.degradedWarning) {
5419
+ await emitCloudEventBestEffort(
5420
+ "name_resolution.degraded",
5421
+ {
5422
+ wallet_address: resolvedRequest.wallet_address,
5423
+ object_key: resolvedRequest.object_key,
5424
+ warning: resolved.degradedWarning
5425
+ },
5426
+ objectLogHomeDir
5427
+ );
5428
+ }
4606
5429
  const correlation = buildRequestCorrelation(
4607
5430
  executionContext.forcedOperationId,
4608
5431
  executionContext.forcedTraceId
@@ -4647,8 +5470,10 @@ Use /mnemospark-cloud op-status --operation-id ${operationId}`
4647
5470
  },
4648
5471
  objectLogHomeDir
4649
5472
  );
5473
+ const downloadText = `File ${resolvedRequest.object_key} downloaded to ${downloadResult.file_path}`;
4650
5474
  return {
4651
- text: `File ${resolvedRequest.object_key} downloaded to ${downloadResult.file_path}`
5475
+ text: resolved.degradedWarning ? `${resolved.degradedWarning}
5476
+ ${downloadText}` : downloadText
4652
5477
  };
4653
5478
  } catch {
4654
5479
  await datastore.upsertOperation({
@@ -4681,12 +5506,24 @@ Use /mnemospark-cloud op-status --operation-id ${operationId}`
4681
5506
  const resolved = await resolveNameSelectorIfNeeded(
4682
5507
  datastore,
4683
5508
  parsed.storageObjectRequest,
4684
- parsed.nameSelector
5509
+ parsed.nameSelector,
5510
+ objectLogHomeDir
4685
5511
  );
4686
5512
  if (resolved.error || !resolved.request) {
4687
5513
  return { text: resolved.error ?? "Cannot resolve storage object request.", isError: true };
4688
5514
  }
4689
5515
  const resolvedRequest = resolved.request;
5516
+ if (resolved.degradedWarning) {
5517
+ await emitCloudEventBestEffort(
5518
+ "name_resolution.degraded",
5519
+ {
5520
+ wallet_address: resolvedRequest.wallet_address,
5521
+ object_key: resolvedRequest.object_key,
5522
+ warning: resolved.degradedWarning
5523
+ },
5524
+ objectLogHomeDir
5525
+ );
5526
+ }
4690
5527
  const correlation = buildRequestCorrelation();
4691
5528
  const operationId = correlation.operationId;
4692
5529
  const existingObjectByKey = await datastore.findObjectByObjectKey(resolvedRequest.object_key);
@@ -4768,12 +5605,14 @@ Use /mnemospark-cloud op-status --operation-id ${operationId}`
4768
5605
  },
4769
5606
  objectLogHomeDir
4770
5607
  );
5608
+ const deleteText = formatStorageDeleteUserMessage(
5609
+ resolvedRequest.object_key,
5610
+ cronEntry?.cronId ?? null,
5611
+ cronDeleted
5612
+ );
4771
5613
  return {
4772
- text: formatStorageDeleteUserMessage(
4773
- resolvedRequest.object_key,
4774
- cronEntry?.cronId ?? null,
4775
- cronDeleted
4776
- )
5614
+ text: resolved.degradedWarning ? `${resolved.degradedWarning}
5615
+ ${deleteText}` : deleteText
4777
5616
  };
4778
5617
  }
4779
5618
  return {