mnemospark 0.1.21 → 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
@@ -282,6 +282,21 @@ async function createWalletSignatureHeaderValue(method, path, walletAddress, wal
282
282
  return encodeBase64Json(headerEnvelope);
283
283
  }
284
284
 
285
+ // src/cloud-correlation.ts
286
+ var MNEMOSPARK_TRACE_ID_HEADER = "X-Mnemospark-Trace-Id";
287
+ var MNEMOSPARK_OPERATION_ID_HEADER = "X-Mnemospark-Operation-Id";
288
+ function applyCorrelationHeaders(headers, correlation) {
289
+ const traceId = correlation?.traceId?.trim();
290
+ if (traceId) {
291
+ headers[MNEMOSPARK_TRACE_ID_HEADER] = traceId;
292
+ }
293
+ const operationId = correlation?.operationId?.trim();
294
+ if (operationId) {
295
+ headers[MNEMOSPARK_OPERATION_ID_HEADER] = operationId;
296
+ }
297
+ return headers;
298
+ }
299
+
285
300
  // src/cloud-utils.ts
286
301
  function normalizeBaseUrl(baseUrl) {
287
302
  return baseUrl.replace(/\/+$/, "");
@@ -511,9 +526,12 @@ async function requestPriceStorageViaProxy(request, options = {}) {
511
526
  );
512
527
  const response = await fetchImpl(`${baseUrl}${PRICE_STORAGE_PROXY_PATH}`, {
513
528
  method: "POST",
514
- headers: {
515
- "Content-Type": "application/json"
516
- },
529
+ headers: applyCorrelationHeaders(
530
+ {
531
+ "Content-Type": "application/json"
532
+ },
533
+ options.correlation
534
+ ),
517
535
  body: JSON.stringify(request)
518
536
  });
519
537
  const responseBody = await response.text();
@@ -538,6 +556,7 @@ async function requestStorageUploadViaProxy(request, options = {}) {
538
556
  const requestHeaders = {
539
557
  "Content-Type": "application/json"
540
558
  };
559
+ applyCorrelationHeaders(requestHeaders, options.correlation);
541
560
  if (options.idempotencyKey && options.idempotencyKey.trim().length > 0) {
542
561
  requestHeaders["Idempotency-Key"] = options.idempotencyKey.trim();
543
562
  }
@@ -619,9 +638,12 @@ async function requestStorageUploadConfirmViaProxy(request, options = {}) {
619
638
  );
620
639
  const response = await fetchImpl(`${baseUrl}${UPLOAD_CONFIRM_PROXY_PATH}`, {
621
640
  method: "POST",
622
- headers: {
623
- "Content-Type": "application/json"
624
- },
641
+ headers: applyCorrelationHeaders(
642
+ {
643
+ "Content-Type": "application/json"
644
+ },
645
+ options.correlation
646
+ ),
625
647
  body: JSON.stringify(request)
626
648
  });
627
649
  const responseBody = await response.text();
@@ -736,7 +758,7 @@ async function requestPaymentSettleViaProxy(quoteId, walletAddress, options = {}
736
758
  }
737
759
  const response = await fetchImpl(targetUrl, {
738
760
  method: "POST",
739
- headers: { "Content-Type": "application/json" },
761
+ headers: applyCorrelationHeaders({ "Content-Type": "application/json" }, options.correlation),
740
762
  body: JSON.stringify(requestBody)
741
763
  });
742
764
  return {
@@ -1220,9 +1242,12 @@ async function requestJsonViaProxy(proxyPath, request, parser, options = {}) {
1220
1242
  );
1221
1243
  const response = await fetchImpl(`${baseUrl}${proxyPath}`, {
1222
1244
  method: "POST",
1223
- headers: {
1224
- "Content-Type": "application/json"
1225
- },
1245
+ headers: applyCorrelationHeaders(
1246
+ {
1247
+ "Content-Type": "application/json"
1248
+ },
1249
+ options.correlation
1250
+ ),
1226
1251
  body: JSON.stringify(request)
1227
1252
  });
1228
1253
  const bodyText = await response.text();
@@ -1546,6 +1571,20 @@ function emitProxyEvent(eventType, status, correlation, details = {}) {
1546
1571
  details
1547
1572
  }).catch(() => void 0);
1548
1573
  }
1574
+ function createProxyCorrelation(headers) {
1575
+ const traceId = readHeaderValue(headers[MNEMOSPARK_TRACE_ID_HEADER.toLowerCase()]) ?? randomUUID();
1576
+ const operationId = readHeaderValue(
1577
+ headers[MNEMOSPARK_OPERATION_ID_HEADER.toLowerCase()] ?? headers["idempotency-key"]
1578
+ ) ?? randomUUID();
1579
+ return { trace_id: traceId, operation_id: operationId };
1580
+ }
1581
+ function emitProxyTerminalFromStatus(correlation, statusCode, details = {}) {
1582
+ if (statusCode >= 200 && statusCode < 300) {
1583
+ emitProxyEvent("terminal.success", "success", correlation, { status: statusCode, ...details });
1584
+ return;
1585
+ }
1586
+ emitProxyEvent("terminal.failure", "failure", correlation, { status: statusCode, ...details });
1587
+ }
1549
1588
  function isAlreadySettledConflict(status, bodyText) {
1550
1589
  if (status !== 409) {
1551
1590
  return false;
@@ -1670,10 +1709,7 @@ async function startProxy(options) {
1670
1709
  console.error(`[mnemospark] Response stream error: ${err.message}`);
1671
1710
  });
1672
1711
  if (req.method === "POST" && matchesProxyPath(req.url, PRICE_STORAGE_PROXY_PATH)) {
1673
- const correlation = {
1674
- trace_id: randomUUID(),
1675
- operation_id: randomUUID()
1676
- };
1712
+ const correlation = createProxyCorrelation(req.headers);
1677
1713
  logProxyEvent("info", "proxy_price_storage_received");
1678
1714
  emitProxyEvent("request.received", "start", correlation, { path: PRICE_STORAGE_PROXY_PATH });
1679
1715
  try {
@@ -1682,15 +1718,17 @@ async function startProxy(options) {
1682
1718
  payload = await readProxyJsonBody(req);
1683
1719
  } catch {
1684
1720
  logProxyEvent("warn", "proxy_price_storage_invalid_json");
1721
+ emitProxyTerminalFromStatus(correlation, 400, { reason: "invalid_json" });
1685
1722
  sendJson(res, 400, {
1686
1723
  error: "Bad request",
1687
- message: "Invalid JSON body for /mnemospark-cloud price-storage"
1724
+ message: "Invalid JSON body for /mnemospark_cloud price-storage"
1688
1725
  });
1689
1726
  return;
1690
1727
  }
1691
1728
  const requestPayload = parsePriceStorageQuoteRequest(payload);
1692
1729
  if (!requestPayload) {
1693
1730
  logProxyEvent("warn", "proxy_price_storage_missing_fields");
1731
+ emitProxyTerminalFromStatus(correlation, 400, { reason: "missing_fields" });
1694
1732
  sendJson(res, 400, {
1695
1733
  error: "Bad request",
1696
1734
  message: "Missing required fields: wallet_address, object_id, object_id_hash, gb, provider, region"
@@ -1709,6 +1747,7 @@ async function startProxy(options) {
1709
1747
  logProxyEvent("warn", "proxy_price_storage_wallet_signature_missing");
1710
1748
  res.writeHead(400, { "Content-Type": "application/json" });
1711
1749
  res.end(createWalletRequiredBody());
1750
+ emitProxyTerminalFromStatus(correlation, 400, { reason: "wallet_signature_missing" });
1712
1751
  return;
1713
1752
  }
1714
1753
  const backendResponse = await forwardPriceStorageToBackend(requestPayload, {
@@ -1734,14 +1773,13 @@ async function startProxy(options) {
1734
1773
  });
1735
1774
  res.writeHead(authFailure.status, responseHeaders2);
1736
1775
  res.end(authFailure.bodyText);
1776
+ emitProxyTerminalFromStatus(correlation, authFailure.status, { reason: "auth_failure" });
1737
1777
  return;
1738
1778
  }
1739
1779
  const responseHeaders = createBackendForwardHeaders(backendResponse);
1740
1780
  res.writeHead(backendResponse.status, responseHeaders);
1741
1781
  res.end(backendResponse.bodyText);
1742
- emitProxyEvent("terminal.success", "success", correlation, {
1743
- status: backendResponse.status
1744
- });
1782
+ emitProxyTerminalFromStatus(correlation, backendResponse.status);
1745
1783
  } catch (err) {
1746
1784
  emitProxyEvent("terminal.failure", "failure", correlation, {
1747
1785
  error: err instanceof Error ? err.message : String(err)
@@ -1751,16 +1789,13 @@ async function startProxy(options) {
1751
1789
  });
1752
1790
  sendJson(res, 502, {
1753
1791
  error: "proxy_error",
1754
- 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)}`
1755
1793
  });
1756
1794
  }
1757
1795
  return;
1758
1796
  }
1759
1797
  if (req.method === "POST" && matchesProxyPath(req.url, PAYMENT_SETTLE_PROXY_PATH)) {
1760
- const correlation = {
1761
- trace_id: randomUUID(),
1762
- operation_id: randomUUID()
1763
- };
1798
+ const correlation = createProxyCorrelation(req.headers);
1764
1799
  logProxyEvent("info", "proxy_payment_settle_received");
1765
1800
  emitProxyEvent("request.received", "start", correlation, { path: PAYMENT_SETTLE_PROXY_PATH });
1766
1801
  try {
@@ -1769,6 +1804,7 @@ async function startProxy(options) {
1769
1804
  payload = await readProxyJsonBody(req);
1770
1805
  } catch {
1771
1806
  logProxyEvent("warn", "proxy_payment_settle_invalid_json");
1807
+ emitProxyTerminalFromStatus(correlation, 400, { reason: "invalid_json" });
1772
1808
  sendJson(res, 400, {
1773
1809
  error: "Bad request",
1774
1810
  message: "Invalid JSON body for /mnemospark/payment/settle"
@@ -1782,6 +1818,7 @@ async function startProxy(options) {
1782
1818
  const inlinePaymentAuthorization = record?.payment_authorization;
1783
1819
  if (!quoteId || !walletAddress) {
1784
1820
  logProxyEvent("warn", "proxy_payment_settle_missing_fields");
1821
+ emitProxyTerminalFromStatus(correlation, 400, { reason: "missing_fields" });
1785
1822
  sendJson(res, 400, {
1786
1823
  error: "Bad request",
1787
1824
  message: "Missing required fields: quote_id, wallet_address"
@@ -1790,6 +1827,7 @@ async function startProxy(options) {
1790
1827
  }
1791
1828
  if (inlinePayment !== void 0 && (inlinePayment === null || typeof inlinePayment !== "object" || Array.isArray(inlinePayment))) {
1792
1829
  logProxyEvent("warn", "proxy_payment_settle_invalid_payment_shape");
1830
+ emitProxyTerminalFromStatus(correlation, 400, { reason: "invalid_payment_shape" });
1793
1831
  sendJson(res, 400, {
1794
1832
  error: "Bad request",
1795
1833
  message: "Invalid field: payment must be an object when provided"
@@ -1798,6 +1836,9 @@ async function startProxy(options) {
1798
1836
  }
1799
1837
  if (inlinePaymentAuthorization !== void 0 && !(typeof inlinePaymentAuthorization === "string" || inlinePaymentAuthorization !== null && typeof inlinePaymentAuthorization === "object" && !Array.isArray(inlinePaymentAuthorization))) {
1800
1838
  logProxyEvent("warn", "proxy_payment_settle_invalid_payment_authorization_shape");
1839
+ emitProxyTerminalFromStatus(correlation, 400, {
1840
+ reason: "invalid_payment_authorization_shape"
1841
+ });
1801
1842
  sendJson(res, 400, {
1802
1843
  error: "Bad request",
1803
1844
  message: "Invalid field: payment_authorization must be an object or string when provided"
@@ -1809,6 +1850,7 @@ async function startProxy(options) {
1809
1850
  request_wallet: walletAddress,
1810
1851
  proxy_wallet: account.address
1811
1852
  });
1853
+ emitProxyTerminalFromStatus(correlation, 403, { reason: "wallet_mismatch" });
1812
1854
  sendJson(res, 403, {
1813
1855
  error: "wallet_proof_invalid",
1814
1856
  message: "wallet proof invalid"
@@ -1824,6 +1866,7 @@ async function startProxy(options) {
1824
1866
  logProxyEvent("warn", "proxy_payment_settle_wallet_signature_missing");
1825
1867
  res.writeHead(400, { "Content-Type": "application/json" });
1826
1868
  res.end(createWalletRequiredBody());
1869
+ emitProxyTerminalFromStatus(correlation, 400, { reason: "wallet_signature_missing" });
1827
1870
  return;
1828
1871
  }
1829
1872
  correlation.quote_id = quoteId;
@@ -1858,14 +1901,13 @@ async function startProxy(options) {
1858
1901
  });
1859
1902
  res.writeHead(authFailure.status, responseHeaders2);
1860
1903
  res.end(authFailure.bodyText);
1904
+ emitProxyTerminalFromStatus(correlation, authFailure.status, { reason: "auth_failure" });
1861
1905
  return;
1862
1906
  }
1863
1907
  const responseHeaders = createBackendForwardHeaders(backendResponse);
1864
1908
  res.writeHead(backendResponse.status, responseHeaders);
1865
1909
  res.end(backendResponse.bodyText);
1866
- emitProxyEvent("terminal.success", "success", correlation, {
1867
- status: backendResponse.status
1868
- });
1910
+ emitProxyTerminalFromStatus(correlation, backendResponse.status);
1869
1911
  } catch (err) {
1870
1912
  emitProxyEvent("terminal.failure", "failure", correlation, {
1871
1913
  error: err instanceof Error ? err.message : String(err)
@@ -1881,10 +1923,7 @@ async function startProxy(options) {
1881
1923
  return;
1882
1924
  }
1883
1925
  if (req.method === "POST" && matchesProxyPath(req.url, UPLOAD_PROXY_PATH)) {
1884
- const correlation = {
1885
- trace_id: randomUUID(),
1886
- operation_id: randomUUID()
1887
- };
1926
+ const correlation = createProxyCorrelation(req.headers);
1888
1927
  logProxyEvent("info", "proxy_upload_received");
1889
1928
  emitProxyEvent("request.received", "start", correlation, { path: UPLOAD_PROXY_PATH });
1890
1929
  try {
@@ -1893,15 +1932,17 @@ async function startProxy(options) {
1893
1932
  payload = await readProxyJsonBody(req);
1894
1933
  } catch {
1895
1934
  logProxyEvent("warn", "proxy_upload_invalid_json");
1935
+ emitProxyTerminalFromStatus(correlation, 400, { reason: "invalid_json" });
1896
1936
  sendJson(res, 400, {
1897
1937
  error: "Bad request",
1898
- message: "Invalid JSON body for /mnemospark-cloud upload"
1938
+ message: "Invalid JSON body for /mnemospark_cloud upload"
1899
1939
  });
1900
1940
  return;
1901
1941
  }
1902
1942
  const requestPayload = parseStorageUploadRequest(payload);
1903
1943
  if (!requestPayload) {
1904
1944
  logProxyEvent("warn", "proxy_upload_missing_fields");
1945
+ emitProxyTerminalFromStatus(correlation, 400, { reason: "missing_fields" });
1905
1946
  sendJson(res, 400, {
1906
1947
  error: "Bad request",
1907
1948
  message: "Missing required fields: quote_id, wallet_address, object_id, object_id_hash, quoted_storage_price, payload"
@@ -1911,12 +1952,12 @@ async function startProxy(options) {
1911
1952
  correlation.quote_id = requestPayload.quote_id;
1912
1953
  correlation.wallet_address = requestPayload.wallet_address;
1913
1954
  correlation.object_id = requestPayload.object_id;
1914
- emitProxyEvent("storage.call", "start", correlation, { target: "storage/upload" });
1915
1955
  if (requestPayload.wallet_address.toLowerCase() !== proxyWalletAddressLower) {
1916
1956
  logProxyEvent("warn", "proxy_upload_wallet_mismatch", {
1917
1957
  request_wallet: requestPayload.wallet_address,
1918
1958
  proxy_wallet: account.address
1919
1959
  });
1960
+ emitProxyTerminalFromStatus(correlation, 403, { reason: "wallet_mismatch" });
1920
1961
  sendJson(res, 403, {
1921
1962
  error: "wallet_proof_invalid",
1922
1963
  message: "wallet proof invalid"
@@ -1932,6 +1973,7 @@ async function startProxy(options) {
1932
1973
  logProxyEvent("warn", "proxy_upload_wallet_signature_missing");
1933
1974
  res.writeHead(400, { "Content-Type": "application/json" });
1934
1975
  res.end(createWalletRequiredBody());
1976
+ emitProxyTerminalFromStatus(correlation, 400, { reason: "wallet_signature_missing" });
1935
1977
  return;
1936
1978
  }
1937
1979
  const requiredMicros = BigInt(
@@ -1951,11 +1993,12 @@ async function startProxy(options) {
1951
1993
  requiredUSD,
1952
1994
  walletAddress: requestPayload.wallet_address
1953
1995
  });
1996
+ emitProxyTerminalFromStatus(correlation, 400, { reason: "insufficient_balance" });
1954
1997
  sendJson(res, 400, {
1955
1998
  error: "insufficient_balance",
1956
1999
  message: `Insufficient USDC balance. Current: ${sufficiency.info.balanceUSD}, Required: ${requiredUSD}`,
1957
2000
  wallet: requestPayload.wallet_address,
1958
- 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`
1959
2002
  });
1960
2003
  return;
1961
2004
  }
@@ -1978,6 +2021,7 @@ async function startProxy(options) {
1978
2021
  logProxyEvent("warn", "proxy_upload_settle_signature_missing");
1979
2022
  res.writeHead(400, { "Content-Type": "application/json" });
1980
2023
  res.end(createWalletRequiredBody());
2024
+ emitProxyTerminalFromStatus(correlation, 400, { reason: "settle_signature_missing" });
1981
2025
  return;
1982
2026
  }
1983
2027
  const uploadPaymentFetch = createPaymentFetch(walletPrivateKey).fetch;
@@ -2007,6 +2051,9 @@ async function startProxy(options) {
2007
2051
  });
2008
2052
  res.writeHead(settleResponse.status, responseHeaders2);
2009
2053
  res.end(settleResponse.bodyText);
2054
+ emitProxyTerminalFromStatus(correlation, settleResponse.status, {
2055
+ reason: "settle_failed"
2056
+ });
2010
2057
  return;
2011
2058
  }
2012
2059
  if (settledAlready) {
@@ -2018,6 +2065,7 @@ async function startProxy(options) {
2018
2065
  status: settleResponse.status
2019
2066
  });
2020
2067
  }
2068
+ emitProxyEvent("storage.call", "start", correlation, { target: "storage/upload" });
2021
2069
  const backendResponse = await forwardStorageUploadToBackend(requestPayload, {
2022
2070
  backendBaseUrl: MNEMOSPARK_BACKEND_API_BASE_URL,
2023
2071
  walletSignature,
@@ -2042,14 +2090,13 @@ async function startProxy(options) {
2042
2090
  });
2043
2091
  res.writeHead(authFailure.status, responseHeaders2);
2044
2092
  res.end(authFailure.bodyText);
2093
+ emitProxyTerminalFromStatus(correlation, authFailure.status, { reason: "auth_failure" });
2045
2094
  return;
2046
2095
  }
2047
2096
  const responseHeaders = createBackendForwardHeaders(backendResponse);
2048
2097
  res.writeHead(backendResponse.status, responseHeaders);
2049
2098
  res.end(backendResponse.bodyText);
2050
- emitProxyEvent("terminal.success", "success", correlation, {
2051
- status: backendResponse.status
2052
- });
2099
+ emitProxyTerminalFromStatus(correlation, backendResponse.status);
2053
2100
  } catch (err) {
2054
2101
  emitProxyEvent("terminal.failure", "failure", correlation, {
2055
2102
  error: err instanceof Error ? err.message : String(err)
@@ -2059,39 +2106,47 @@ async function startProxy(options) {
2059
2106
  });
2060
2107
  sendJson(res, 502, {
2061
2108
  error: "proxy_error",
2062
- 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)}`
2063
2110
  });
2064
2111
  }
2065
2112
  return;
2066
2113
  }
2067
2114
  if (req.method === "POST" && matchesProxyPath(req.url, UPLOAD_CONFIRM_PROXY_PATH)) {
2115
+ const correlation = createProxyCorrelation(req.headers);
2068
2116
  logProxyEvent("info", "proxy_upload_confirm_received");
2117
+ emitProxyEvent("request.received", "start", correlation, { path: UPLOAD_CONFIRM_PROXY_PATH });
2069
2118
  try {
2070
2119
  let payload;
2071
2120
  try {
2072
2121
  payload = await readProxyJsonBody(req);
2073
2122
  } catch {
2074
2123
  logProxyEvent("warn", "proxy_upload_confirm_invalid_json");
2124
+ emitProxyTerminalFromStatus(correlation, 400, { reason: "invalid_json" });
2075
2125
  sendJson(res, 400, {
2076
2126
  error: "Bad request",
2077
- message: "Invalid JSON body for /mnemospark-cloud upload/confirm"
2127
+ message: "Invalid JSON body for /mnemospark_cloud upload/confirm"
2078
2128
  });
2079
2129
  return;
2080
2130
  }
2081
2131
  const requestPayload = parseStorageUploadConfirmRequest(payload);
2082
2132
  if (!requestPayload) {
2083
2133
  logProxyEvent("warn", "proxy_upload_confirm_missing_fields");
2134
+ emitProxyTerminalFromStatus(correlation, 400, { reason: "missing_fields" });
2084
2135
  sendJson(res, 400, {
2085
2136
  error: "Bad request",
2086
2137
  message: "Missing required fields: quote_id, wallet_address, object_key, idempotency_key"
2087
2138
  });
2088
2139
  return;
2089
2140
  }
2141
+ correlation.quote_id = requestPayload.quote_id;
2142
+ correlation.wallet_address = requestPayload.wallet_address;
2143
+ correlation.object_key = requestPayload.object_key;
2090
2144
  if (requestPayload.wallet_address.toLowerCase() !== proxyWalletAddressLower) {
2091
2145
  logProxyEvent("warn", "proxy_upload_confirm_wallet_mismatch", {
2092
2146
  request_wallet: requestPayload.wallet_address,
2093
2147
  proxy_wallet: account.address
2094
2148
  });
2149
+ emitProxyTerminalFromStatus(correlation, 403, { reason: "wallet_mismatch" });
2095
2150
  sendJson(res, 403, {
2096
2151
  error: "wallet_proof_invalid",
2097
2152
  message: "wallet proof invalid"
@@ -2107,8 +2162,10 @@ async function startProxy(options) {
2107
2162
  logProxyEvent("warn", "proxy_upload_confirm_wallet_signature_missing");
2108
2163
  res.writeHead(400, { "Content-Type": "application/json" });
2109
2164
  res.end(createWalletRequiredBody());
2165
+ emitProxyTerminalFromStatus(correlation, 400, { reason: "wallet_signature_missing" });
2110
2166
  return;
2111
2167
  }
2168
+ emitProxyEvent("storage.call", "start", correlation, { target: "storage/upload/confirm" });
2112
2169
  const backendResponse = await forwardStorageUploadConfirmToBackend(requestPayload, {
2113
2170
  backendBaseUrl: MNEMOSPARK_BACKEND_API_BASE_URL,
2114
2171
  walletSignature
@@ -2116,6 +2173,7 @@ async function startProxy(options) {
2116
2173
  logProxyEvent("info", "proxy_upload_confirm_backend_response", {
2117
2174
  status: backendResponse.status
2118
2175
  });
2176
+ emitProxyEvent("storage.call", "result", correlation, { status: backendResponse.status });
2119
2177
  const authFailure = normalizeBackendAuthFailure(
2120
2178
  backendResponse.status,
2121
2179
  backendResponse.bodyText
@@ -2131,27 +2189,29 @@ async function startProxy(options) {
2131
2189
  });
2132
2190
  res.writeHead(authFailure.status, responseHeaders2);
2133
2191
  res.end(authFailure.bodyText);
2192
+ emitProxyTerminalFromStatus(correlation, authFailure.status, { reason: "auth_failure" });
2134
2193
  return;
2135
2194
  }
2136
2195
  const responseHeaders = createBackendForwardHeaders(backendResponse);
2137
2196
  res.writeHead(backendResponse.status, responseHeaders);
2138
2197
  res.end(backendResponse.bodyText);
2198
+ emitProxyTerminalFromStatus(correlation, backendResponse.status);
2139
2199
  } catch (err) {
2200
+ emitProxyEvent("terminal.failure", "failure", correlation, {
2201
+ error: err instanceof Error ? err.message : String(err)
2202
+ });
2140
2203
  logProxyEvent("error", "proxy_upload_confirm_forward_failed", {
2141
2204
  error: err instanceof Error ? err.message : String(err)
2142
2205
  });
2143
2206
  sendJson(res, 502, {
2144
2207
  error: "proxy_error",
2145
- 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)}`
2146
2209
  });
2147
2210
  }
2148
2211
  return;
2149
2212
  }
2150
2213
  if (req.method === "POST" && matchesProxyPath(req.url, STORAGE_LS_PROXY_PATH)) {
2151
- const correlation = {
2152
- trace_id: randomUUID(),
2153
- operation_id: randomUUID()
2154
- };
2214
+ const correlation = createProxyCorrelation(req.headers);
2155
2215
  logProxyEvent("info", "proxy_ls_received");
2156
2216
  emitProxyEvent("request.received", "start", correlation, { path: STORAGE_LS_PROXY_PATH });
2157
2217
  try {
@@ -2160,15 +2220,17 @@ async function startProxy(options) {
2160
2220
  payload = await readProxyJsonBody(req);
2161
2221
  } catch {
2162
2222
  logProxyEvent("warn", "proxy_ls_invalid_json");
2223
+ emitProxyTerminalFromStatus(correlation, 400, { reason: "invalid_json" });
2163
2224
  sendJson(res, 400, {
2164
2225
  error: "Bad request",
2165
- message: "Invalid JSON body for /mnemospark-cloud ls"
2226
+ message: "Invalid JSON body for /mnemospark_cloud ls"
2166
2227
  });
2167
2228
  return;
2168
2229
  }
2169
2230
  const requestPayload = parseStorageObjectRequest(payload);
2170
2231
  if (!requestPayload) {
2171
2232
  logProxyEvent("warn", "proxy_ls_missing_fields");
2233
+ emitProxyTerminalFromStatus(correlation, 400, { reason: "missing_fields" });
2172
2234
  sendJson(res, 400, {
2173
2235
  error: "Bad request",
2174
2236
  message: "Missing required fields: wallet_address, object_key"
@@ -2179,6 +2241,7 @@ async function startProxy(options) {
2179
2241
  correlation.object_key = requestPayload.object_key;
2180
2242
  if (requestPayload.wallet_address.toLowerCase() !== proxyWalletAddressLower) {
2181
2243
  logProxyEvent("warn", "proxy_ls_wallet_mismatch");
2244
+ emitProxyTerminalFromStatus(correlation, 403, { reason: "wallet_mismatch" });
2182
2245
  sendJson(res, 403, {
2183
2246
  error: "wallet_proof_invalid",
2184
2247
  message: "wallet proof invalid"
@@ -2194,6 +2257,7 @@ async function startProxy(options) {
2194
2257
  logProxyEvent("warn", "proxy_ls_wallet_signature_missing");
2195
2258
  res.writeHead(400, { "Content-Type": "application/json" });
2196
2259
  res.end(createWalletRequiredBody());
2260
+ emitProxyTerminalFromStatus(correlation, 400, { reason: "wallet_signature_missing" });
2197
2261
  return;
2198
2262
  }
2199
2263
  emitProxyEvent("storage.call", "start", correlation, { target: "storage/ls" });
@@ -2216,14 +2280,13 @@ async function startProxy(options) {
2216
2280
  });
2217
2281
  res.writeHead(authFailure.status, responseHeaders2);
2218
2282
  res.end(authFailure.bodyText);
2283
+ emitProxyTerminalFromStatus(correlation, authFailure.status, { reason: "auth_failure" });
2219
2284
  return;
2220
2285
  }
2221
2286
  const responseHeaders = createBackendForwardHeaders(backendResponse);
2222
2287
  res.writeHead(backendResponse.status, responseHeaders);
2223
2288
  res.end(backendResponse.bodyText);
2224
- emitProxyEvent("terminal.success", "success", correlation, {
2225
- status: backendResponse.status
2226
- });
2289
+ emitProxyTerminalFromStatus(correlation, backendResponse.status);
2227
2290
  } catch (err) {
2228
2291
  emitProxyEvent("terminal.failure", "failure", correlation, {
2229
2292
  error: err instanceof Error ? err.message : String(err)
@@ -2233,16 +2296,13 @@ async function startProxy(options) {
2233
2296
  });
2234
2297
  sendJson(res, 502, {
2235
2298
  error: "proxy_error",
2236
- 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)}`
2237
2300
  });
2238
2301
  }
2239
2302
  return;
2240
2303
  }
2241
2304
  if (req.method === "POST" && matchesProxyPath(req.url, STORAGE_DOWNLOAD_PROXY_PATH)) {
2242
- const correlation = {
2243
- trace_id: randomUUID(),
2244
- operation_id: randomUUID()
2245
- };
2305
+ const correlation = createProxyCorrelation(req.headers);
2246
2306
  logProxyEvent("info", "proxy_download_received");
2247
2307
  emitProxyEvent("request.received", "start", correlation, {
2248
2308
  path: STORAGE_DOWNLOAD_PROXY_PATH
@@ -2253,15 +2313,17 @@ async function startProxy(options) {
2253
2313
  payload = await readProxyJsonBody(req);
2254
2314
  } catch {
2255
2315
  logProxyEvent("warn", "proxy_download_invalid_json");
2316
+ emitProxyTerminalFromStatus(correlation, 400, { reason: "invalid_json" });
2256
2317
  sendJson(res, 400, {
2257
2318
  error: "Bad request",
2258
- message: "Invalid JSON body for /mnemospark-cloud download"
2319
+ message: "Invalid JSON body for /mnemospark_cloud download"
2259
2320
  });
2260
2321
  return;
2261
2322
  }
2262
2323
  const requestPayload = parseStorageObjectRequest(payload);
2263
2324
  if (!requestPayload) {
2264
2325
  logProxyEvent("warn", "proxy_download_missing_fields");
2326
+ emitProxyTerminalFromStatus(correlation, 400, { reason: "missing_fields" });
2265
2327
  sendJson(res, 400, {
2266
2328
  error: "Bad request",
2267
2329
  message: "Missing required fields: wallet_address, object_key"
@@ -2272,6 +2334,7 @@ async function startProxy(options) {
2272
2334
  correlation.object_key = requestPayload.object_key;
2273
2335
  if (requestPayload.wallet_address.toLowerCase() !== proxyWalletAddressLower) {
2274
2336
  logProxyEvent("warn", "proxy_download_wallet_mismatch");
2337
+ emitProxyTerminalFromStatus(correlation, 403, { reason: "wallet_mismatch" });
2275
2338
  sendJson(res, 403, {
2276
2339
  error: "wallet_proof_invalid",
2277
2340
  message: "wallet proof invalid"
@@ -2287,6 +2350,7 @@ async function startProxy(options) {
2287
2350
  logProxyEvent("warn", "proxy_download_wallet_signature_missing");
2288
2351
  res.writeHead(400, { "Content-Type": "application/json" });
2289
2352
  res.end(createWalletRequiredBody());
2353
+ emitProxyTerminalFromStatus(correlation, 400, { reason: "wallet_signature_missing" });
2290
2354
  return;
2291
2355
  }
2292
2356
  emitProxyEvent("storage.call", "start", correlation, { target: "storage/download" });
@@ -2313,6 +2377,7 @@ async function startProxy(options) {
2313
2377
  });
2314
2378
  res.writeHead(authFailure.status, responseHeaders);
2315
2379
  res.end(authFailure.bodyText);
2380
+ emitProxyTerminalFromStatus(correlation, authFailure.status, { reason: "auth_failure" });
2316
2381
  return;
2317
2382
  }
2318
2383
  if (backendResponse.status < 200 || backendResponse.status >= 300) {
@@ -2322,6 +2387,7 @@ async function startProxy(options) {
2322
2387
  const responseHeaders = createBackendForwardHeaders(backendResponse);
2323
2388
  res.writeHead(backendResponse.status, responseHeaders);
2324
2389
  res.end(backendResponse.bodyText);
2390
+ emitProxyTerminalFromStatus(correlation, backendResponse.status);
2325
2391
  return;
2326
2392
  }
2327
2393
  const downloadResult = await downloadStorageToDisk(requestPayload, backendResponse, {
@@ -2338,9 +2404,7 @@ async function startProxy(options) {
2338
2404
  file_path: downloadResult.filePath,
2339
2405
  bytes_written: downloadResult.bytesWritten
2340
2406
  });
2341
- emitProxyEvent("terminal.success", "success", correlation, {
2342
- status: 200
2343
- });
2407
+ emitProxyTerminalFromStatus(correlation, 200);
2344
2408
  } catch (err) {
2345
2409
  emitProxyEvent("terminal.failure", "failure", correlation, {
2346
2410
  error: err instanceof Error ? err.message : String(err)
@@ -2350,16 +2414,13 @@ async function startProxy(options) {
2350
2414
  });
2351
2415
  sendJson(res, 502, {
2352
2416
  error: "proxy_error",
2353
- 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)}`
2354
2418
  });
2355
2419
  }
2356
2420
  return;
2357
2421
  }
2358
2422
  if (req.method === "POST" && matchesProxyPath(req.url, STORAGE_DELETE_PROXY_PATH)) {
2359
- const correlation = {
2360
- trace_id: randomUUID(),
2361
- operation_id: randomUUID()
2362
- };
2423
+ const correlation = createProxyCorrelation(req.headers);
2363
2424
  logProxyEvent("info", "proxy_delete_received");
2364
2425
  emitProxyEvent("request.received", "start", correlation, { path: STORAGE_DELETE_PROXY_PATH });
2365
2426
  try {
@@ -2368,15 +2429,17 @@ async function startProxy(options) {
2368
2429
  payload = await readProxyJsonBody(req);
2369
2430
  } catch {
2370
2431
  logProxyEvent("warn", "proxy_delete_invalid_json");
2432
+ emitProxyTerminalFromStatus(correlation, 400, { reason: "invalid_json" });
2371
2433
  sendJson(res, 400, {
2372
2434
  error: "Bad request",
2373
- message: "Invalid JSON body for /mnemospark-cloud delete"
2435
+ message: "Invalid JSON body for /mnemospark_cloud delete"
2374
2436
  });
2375
2437
  return;
2376
2438
  }
2377
2439
  const requestPayload = parseStorageObjectRequest(payload);
2378
2440
  if (!requestPayload) {
2379
2441
  logProxyEvent("warn", "proxy_delete_missing_fields");
2442
+ emitProxyTerminalFromStatus(correlation, 400, { reason: "missing_fields" });
2380
2443
  sendJson(res, 400, {
2381
2444
  error: "Bad request",
2382
2445
  message: "Missing required fields: wallet_address, object_key"
@@ -2387,6 +2450,7 @@ async function startProxy(options) {
2387
2450
  correlation.object_key = requestPayload.object_key;
2388
2451
  if (requestPayload.wallet_address.toLowerCase() !== proxyWalletAddressLower) {
2389
2452
  logProxyEvent("warn", "proxy_delete_wallet_mismatch");
2453
+ emitProxyTerminalFromStatus(correlation, 403, { reason: "wallet_mismatch" });
2390
2454
  sendJson(res, 403, {
2391
2455
  error: "wallet_proof_invalid",
2392
2456
  message: "wallet proof invalid"
@@ -2402,6 +2466,7 @@ async function startProxy(options) {
2402
2466
  logProxyEvent("warn", "proxy_delete_wallet_signature_missing");
2403
2467
  res.writeHead(400, { "Content-Type": "application/json" });
2404
2468
  res.end(createWalletRequiredBody());
2469
+ emitProxyTerminalFromStatus(correlation, 400, { reason: "wallet_signature_missing" });
2405
2470
  return;
2406
2471
  }
2407
2472
  emitProxyEvent("storage.call", "start", correlation, { target: "storage/delete" });
@@ -2428,14 +2493,13 @@ async function startProxy(options) {
2428
2493
  });
2429
2494
  res.writeHead(authFailure.status, responseHeaders2);
2430
2495
  res.end(authFailure.bodyText);
2496
+ emitProxyTerminalFromStatus(correlation, authFailure.status, { reason: "auth_failure" });
2431
2497
  return;
2432
2498
  }
2433
2499
  const responseHeaders = createBackendForwardHeaders(backendResponse);
2434
2500
  res.writeHead(backendResponse.status, responseHeaders);
2435
2501
  res.end(backendResponse.bodyText);
2436
- emitProxyEvent("terminal.success", "success", correlation, {
2437
- status: backendResponse.status
2438
- });
2502
+ emitProxyTerminalFromStatus(correlation, backendResponse.status);
2439
2503
  } catch (err) {
2440
2504
  emitProxyEvent("terminal.failure", "failure", correlation, {
2441
2505
  error: err instanceof Error ? err.message : String(err)
@@ -2445,7 +2509,7 @@ async function startProxy(options) {
2445
2509
  });
2446
2510
  sendJson(res, 502, {
2447
2511
  error: "proxy_error",
2448
- 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)}`
2449
2513
  });
2450
2514
  }
2451
2515
  return;
@@ -2691,7 +2755,7 @@ import { mkdir as mkdir4 } from "fs/promises";
2691
2755
  import { homedir as homedir5 } from "os";
2692
2756
  import { dirname as dirname4, join as join7 } from "path";
2693
2757
  var DB_SUBPATH = join7(".openclaw", "mnemospark", "state.db");
2694
- var SCHEMA_VERSION = 2;
2758
+ var SCHEMA_VERSION = 3;
2695
2759
  function resolveDbPath(homeDir) {
2696
2760
  return join7(homeDir ?? homedir5(), DB_SUBPATH);
2697
2761
  }
@@ -2797,6 +2861,21 @@ async function createCloudDatastore(homeDir) {
2797
2861
  CREATE INDEX IF NOT EXISTS idx_friendly_names_wallet ON friendly_names(wallet_address);
2798
2862
  CREATE INDEX IF NOT EXISTS idx_friendly_names_created_at ON friendly_names(created_at);
2799
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");
2800
2879
  nextDb.prepare(
2801
2880
  `INSERT INTO schema_migrations(version, applied_at)
2802
2881
  VALUES(?, ?)
@@ -2808,7 +2887,10 @@ async function createCloudDatastore(homeDir) {
2808
2887
  try {
2809
2888
  await ensureReady();
2810
2889
  return fn();
2811
- } catch {
2890
+ } catch (error) {
2891
+ if (process.env.MNEMOSPARK_SQLITE_STRICT === "1") {
2892
+ throw error;
2893
+ }
2812
2894
  return fallback;
2813
2895
  }
2814
2896
  };
@@ -2933,13 +3015,19 @@ async function createCloudDatastore(homeDir) {
2933
3015
  upsertOperation: async (row) => {
2934
3016
  await safe(() => {
2935
3017
  const ts = nowIso();
3018
+ const terminalStatuses = /* @__PURE__ */ new Set(["succeeded", "failed", "cancelled", "timed_out"]);
2936
3019
  db.prepare(
2937
- `INSERT INTO operations(operation_id, type, object_id, quote_id, status, error_code, error_message, started_at, finished_at, created_at, updated_at)
2938
- 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(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2939
3022
  ON CONFLICT(operation_id) DO UPDATE SET
2940
3023
  type=excluded.type,
2941
- object_id=excluded.object_id,
2942
- quote_id=excluded.quote_id,
3024
+ object_id=COALESCE(excluded.object_id, operations.object_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),
2943
3031
  status=excluded.status,
2944
3032
  error_code=excluded.error_code,
2945
3033
  error_message=excluded.error_message,
@@ -2951,11 +3039,16 @@ async function createCloudDatastore(homeDir) {
2951
3039
  row.type,
2952
3040
  row.object_id,
2953
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,
2954
3047
  row.status,
2955
3048
  row.error_code,
2956
3049
  row.error_message,
2957
3050
  row.status === "started" ? ts : null,
2958
- row.status === "succeeded" || row.status === "failed" ? ts : null,
3051
+ terminalStatuses.has(row.status) ? ts : null,
2959
3052
  ts,
2960
3053
  ts
2961
3054
  );
@@ -2963,7 +3056,7 @@ async function createCloudDatastore(homeDir) {
2963
3056
  },
2964
3057
  findOperationById: async (operationId) => safe(() => {
2965
3058
  const row = db.prepare(
2966
- `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
2967
3060
  FROM operations
2968
3061
  WHERE operation_id = ?
2969
3062
  LIMIT 1`
@@ -3065,7 +3158,9 @@ var REQUIRED_UPLOAD = "--quote-id, --wallet-address, --object-id, --object-id-ha
3065
3158
  var REQUIRED_STORAGE_OBJECT = "--wallet-address and one of (--object-key | --name [--latest|--at])";
3066
3159
  var BOOLEAN_SELECTOR_FLAGS = /* @__PURE__ */ new Set(["latest"]);
3067
3160
  var BOOLEAN_ASYNC_FLAGS = /* @__PURE__ */ new Set(["async"]);
3161
+ var BOOLEAN_OP_STATUS_FLAGS = /* @__PURE__ */ new Set(["cancel"]);
3068
3162
  var BOOLEAN_SELECTOR_AND_ASYNC_FLAGS = /* @__PURE__ */ new Set(["latest", "async"]);
3163
+ var ORCHESTRATOR_MODES = /* @__PURE__ */ new Set(["inline", "subagent"]);
3069
3164
  function expandTilde(path) {
3070
3165
  const trimmed = path.trim();
3071
3166
  if (trimmed === "~") {
@@ -3079,29 +3174,54 @@ function expandTilde(path) {
3079
3174
  var CLOUD_HELP_TEXT = [
3080
3175
  "\u2601\uFE0F **mnemospark Cloud Commands**",
3081
3176
  "",
3082
- "\u2022 `/mnemospark-cloud` or `/mnemospark-cloud help` \u2014 show this message",
3177
+ "\u2022 `/mnemospark_cloud` or `/mnemospark_cloud help` \u2014 show this message",
3083
3178
  "",
3084
- "\u2022 `/mnemospark-cloud backup <file>` or `/mnemospark-cloud backup <directory> [--name <friendly-name>]`",
3085
- " 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>",
3086
3182
  "",
3087
- "\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.",
3088
3185
  " Required: " + REQUIRED_PRICE_STORAGE,
3089
3186
  "",
3090
- "\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.",
3091
3189
  " Required: " + REQUIRED_UPLOAD,
3092
3190
  "",
3093
- "\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.",
3094
3193
  " Required: " + REQUIRED_STORAGE_OBJECT,
3095
3194
  "",
3096
- "\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.",
3097
3197
  " Required: " + REQUIRED_STORAGE_OBJECT,
3098
3198
  "",
3099
- "\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.",
3100
3201
  " Required: " + REQUIRED_STORAGE_OBJECT,
3101
3202
  "",
3102
- "\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.",
3103
3205
  " Required: --operation-id",
3104
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
+ "",
3105
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."
3106
3226
  ].join("\n");
3107
3227
  var UnsupportedBackupPlatformError = class extends Error {
@@ -3191,9 +3311,73 @@ function parseStorageObjectRequestInput(flags, selector) {
3191
3311
  location
3192
3312
  });
3193
3313
  }
3194
- 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) {
3195
3367
  const tokens = tokenizeArgsRaw(args ?? "");
3196
- 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
+ }
3197
3381
  return filtered.join(" ");
3198
3382
  }
3199
3383
  function parseCloudArgs(args) {
@@ -3216,8 +3400,21 @@ function parseCloudArgs(args) {
3216
3400
  if (!backupTarget) {
3217
3401
  return { mode: "unknown" };
3218
3402
  }
3219
- const flags = parseNamedFlagsTokens(tokens.slice(1));
3220
- 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
+ };
3221
3418
  }
3222
3419
  if (subcommand === "price-storage") {
3223
3420
  const flags = parseNamedFlags(rest);
@@ -3243,6 +3440,10 @@ function parseCloudArgs(args) {
3243
3440
  if (!flags) {
3244
3441
  return { mode: "upload-invalid" };
3245
3442
  }
3443
+ const asyncArgs = parseAsyncOperationArgs(flags);
3444
+ if (!asyncArgs) {
3445
+ return { mode: "upload-invalid-async" };
3446
+ }
3246
3447
  const quoteId = flags["quote-id"]?.trim();
3247
3448
  const walletAddress = flags["wallet-address"]?.trim();
3248
3449
  const objectId = flags["object-id"]?.trim();
@@ -3253,7 +3454,7 @@ function parseCloudArgs(args) {
3253
3454
  return {
3254
3455
  mode: "upload",
3255
3456
  friendlyName: flags.name?.trim() || void 0,
3256
- async: flags.async === "true",
3457
+ ...asyncArgs,
3257
3458
  uploadRequest: {
3258
3459
  quote_id: quoteId,
3259
3460
  wallet_address: walletAddress,
@@ -3282,6 +3483,10 @@ function parseCloudArgs(args) {
3282
3483
  if (!flags) {
3283
3484
  return { mode: "download-invalid" };
3284
3485
  }
3486
+ const asyncArgs = parseAsyncOperationArgs(flags);
3487
+ if (!asyncArgs) {
3488
+ return { mode: "download-invalid-async" };
3489
+ }
3285
3490
  const selector = parseObjectSelector(flags);
3286
3491
  if (!selector) {
3287
3492
  return { mode: "download-invalid" };
@@ -3294,7 +3499,7 @@ function parseCloudArgs(args) {
3294
3499
  mode: "download",
3295
3500
  storageObjectRequest: request,
3296
3501
  nameSelector: selector.nameSelector,
3297
- async: flags.async === "true"
3502
+ ...asyncArgs
3298
3503
  };
3299
3504
  }
3300
3505
  if (subcommand === "delete") {
@@ -3313,12 +3518,12 @@ function parseCloudArgs(args) {
3313
3518
  return { mode: "delete", storageObjectRequest: request, nameSelector: selector.nameSelector };
3314
3519
  }
3315
3520
  if (subcommand === "op-status") {
3316
- const flags = parseNamedFlags(rest);
3521
+ const flags = parseNamedFlags(rest, BOOLEAN_OP_STATUS_FLAGS);
3317
3522
  const operationId = flags?.["operation-id"]?.trim();
3318
3523
  if (!operationId) {
3319
3524
  return { mode: "op-status-invalid" };
3320
3525
  }
3321
- return { mode: "op-status", operationId };
3526
+ return { mode: "op-status", operationId, cancel: flags?.cancel === "true" };
3322
3527
  }
3323
3528
  return { mode: "unknown" };
3324
3529
  }
@@ -3809,17 +4014,37 @@ async function uploadPresignedObjectIfNeeded(uploadResponse, uploadMode, encrypt
3809
4014
  if (!headers.has("content-type")) {
3810
4015
  headers.set("content-type", "application/octet-stream");
3811
4016
  }
3812
- const response = await fetchImpl(uploadResponse.upload_url, {
4017
+ const putBody = new Uint8Array(encryptedContent);
4018
+ const firstAttempt = await fetchImpl(uploadResponse.upload_url, {
3813
4019
  method: "PUT",
3814
4020
  headers,
3815
- body: new Uint8Array(encryptedContent)
4021
+ body: putBody,
4022
+ redirect: "manual"
3816
4023
  });
3817
- if (!response.ok) {
3818
- const details = (await response.text()).trim();
3819
- throw new Error(
3820
- `Presigned upload failed with status ${response.status}${details ? `: ${details}` : ""}`
3821
- );
4024
+ if (firstAttempt.ok) {
4025
+ return;
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
+ }
3822
4043
  }
4044
+ const details = (await firstAttempt.text()).trim();
4045
+ throw new Error(
4046
+ `Presigned upload failed with status ${firstAttempt.status}${details ? `: ${details}` : ""}`
4047
+ );
3823
4048
  }
3824
4049
  async function appendStorageUploadLog(upload, homeDir, nowDateFn = () => /* @__PURE__ */ new Date()) {
3825
4050
  return appendObjectLogLine(
@@ -3890,16 +4115,118 @@ function extractUploadErrorMessage(error) {
3890
4115
  function formatPriceStorageUserMessage(quote) {
3891
4116
  return [
3892
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}\``,
3893
- `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}\``
3894
4119
  ].join("\n");
3895
4120
  }
3896
4121
  function formatStorageLsUserMessage(result, requestedObjectKey) {
3897
4122
  const objectId = result.object_id ?? result.key;
3898
4123
  return `${objectId} with ${requestedObjectKey} is ${result.size_bytes} in ${result.bucket}`;
3899
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
+ }
3900
4223
  function createCloudCommand(options = {}) {
4224
+ const subagentOrchestrator = options.subagentOrchestrator ?? createInProcessSubagentOrchestrator();
3901
4225
  return {
3902
- name: "mnemospark-cloud",
4226
+ name: "mnemospark_cloud",
4227
+ nativeNames: {
4228
+ default: "mnemospark_cloud"
4229
+ },
3903
4230
  description: "Manage mnemospark cloud storage workflow commands",
3904
4231
  acceptsArgs: true,
3905
4232
  requireAuth: true,
@@ -3923,6 +4250,7 @@ function createCloudCommand(options = {}) {
3923
4250
  proxyQuoteOptions: options.proxyQuoteOptions,
3924
4251
  proxyUploadOptions: options.proxyUploadOptions,
3925
4252
  proxyUploadConfirmOptions: options.proxyUploadConfirmOptions,
4253
+ subagentOrchestrator,
3926
4254
  proxyStorageOptions: options.proxyStorageOptions
3927
4255
  });
3928
4256
  } catch (outerError) {
@@ -3932,7 +4260,42 @@ function createCloudCommand(options = {}) {
3932
4260
  }
3933
4261
  };
3934
4262
  }
3935
- 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) {
3936
4299
  if (!selector) {
3937
4300
  const parsedRequest2 = parseStorageObjectRequest(request);
3938
4301
  if (!parsedRequest2) {
@@ -3940,6 +4303,12 @@ async function resolveNameSelectorIfNeeded(datastore, request, selector) {
3940
4303
  }
3941
4304
  return { request: parsedRequest2 };
3942
4305
  }
4306
+ let sqliteUnavailable = false;
4307
+ try {
4308
+ await datastore.ensureReady();
4309
+ } catch {
4310
+ sqliteUnavailable = true;
4311
+ }
3943
4312
  const matches = await datastore.countFriendlyNameMatches(request.wallet_address, selector.name);
3944
4313
  if (matches > 1 && !selector.latest && !selector.at) {
3945
4314
  return {
@@ -3952,18 +4321,41 @@ async function resolveNameSelectorIfNeeded(datastore, request, selector) {
3952
4321
  latest: selector.latest,
3953
4322
  at: selector.at
3954
4323
  });
3955
- 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) {
3956
4347
  return { error: `No object found for --name ${selector.name}.` };
3957
4348
  }
3958
4349
  const parsedRequest = parseStorageObjectRequest({
3959
4350
  ...request,
3960
- object_key: resolved.objectKey
4351
+ object_key: resolvedObjectKey
3961
4352
  });
3962
4353
  if (!parsedRequest) {
3963
4354
  return { error: "Cannot resolve storage object request." };
3964
4355
  }
3965
4356
  return {
3966
- request: parsedRequest
4357
+ request: parsedRequest,
4358
+ degradedWarning
3967
4359
  };
3968
4360
  }
3969
4361
  async function emitCloudEvent(eventType, details, homeDir) {
@@ -3983,7 +4375,44 @@ async function emitCloudEventBestEffort(eventType, details, homeDir) {
3983
4375
  } catch {
3984
4376
  }
3985
4377
  }
3986
- async function runCloudCommandHandler(ctx, options) {
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
+ }
4410
+ function buildRequestCorrelation(forcedOperationId, forcedTraceId) {
4411
+ const operationId = forcedOperationId?.trim() || randomUUID3();
4412
+ const traceId = forcedTraceId?.trim() || randomUUID3();
4413
+ return { operationId, traceId };
4414
+ }
4415
+ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
3987
4416
  const parsed = parseCloudArgs(ctx.args);
3988
4417
  const objectLogHomeDir = options.objectLogHomeDir;
3989
4418
  const backupBuilder = options.buildBackupObjectFn;
@@ -3998,6 +4427,7 @@ async function runCloudCommandHandler(ctx, options) {
3998
4427
  const requestStorageLs = options.requestStorageLsFn;
3999
4428
  const requestStorageDownload = options.requestStorageDownloadFn;
4000
4429
  const requestStorageDelete = options.requestStorageDeleteFn;
4430
+ const subagentOrchestrator = options.subagentOrchestrator;
4001
4431
  if (parsed.mode === "help" || parsed.mode === "unknown") {
4002
4432
  return {
4003
4433
  text: CLOUD_HELP_TEXT,
@@ -4010,12 +4440,30 @@ async function runCloudCommandHandler(ctx, options) {
4010
4440
  isError: true
4011
4441
  };
4012
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
+ }
4013
4455
  if (parsed.mode === "upload-invalid") {
4014
4456
  return {
4015
4457
  text: `Cannot upload storage object: required arguments are ${REQUIRED_UPLOAD}.`,
4016
4458
  isError: true
4017
4459
  };
4018
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
+ }
4019
4467
  if (parsed.mode === "ls-invalid") {
4020
4468
  return {
4021
4469
  text: `Cannot list storage object: required arguments are ${REQUIRED_STORAGE_OBJECT}.`,
@@ -4028,6 +4476,12 @@ async function runCloudCommandHandler(ctx, options) {
4028
4476
  isError: true
4029
4477
  };
4030
4478
  }
4479
+ if (parsed.mode === "download-invalid-async") {
4480
+ return {
4481
+ text: `Cannot download file: ${INVALID_ASYNC_FLAGS_MESSAGE}`,
4482
+ isError: true
4483
+ };
4484
+ }
4031
4485
  if (parsed.mode === "delete-invalid") {
4032
4486
  return {
4033
4487
  text: `Cannot delete file: required arguments are ${REQUIRED_STORAGE_OBJECT}.`,
@@ -4041,66 +4495,450 @@ async function runCloudCommandHandler(ctx, options) {
4041
4495
  };
4042
4496
  }
4043
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
+ });
4044
4515
  if (parsed.mode === "op-status") {
4045
- const operation = await datastore.findOperationById(parsed.operationId);
4516
+ let operation = await datastore.findOperationById(parsed.operationId);
4046
4517
  if (!operation) {
4047
4518
  return {
4048
4519
  text: `Operation not found: ${parsed.operationId}`,
4049
4520
  isError: true
4050
4521
  };
4051
4522
  }
4052
- return {
4053
- text: [
4054
- `operation-id: ${operation.operation_id}`,
4055
- `type: ${operation.type}`,
4056
- `status: ${operation.status}`,
4057
- `started-at: ${operation.started_at ?? "n/a"}`,
4058
- `finished-at: ${operation.finished_at ?? "n/a"}`,
4059
- operation.error_code ? `error-code: ${operation.error_code}` : null,
4060
- operation.error_message ? `error-message: ${operation.error_message}` : null
4061
- ].filter((v) => Boolean(v)).join("\n"),
4062
- isError: operation.status === "failed"
4063
- };
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);
4064
4610
  }
4065
- if ((parsed.mode === "upload" || parsed.mode === "download") && parsed.async) {
4066
- const operationId = randomUUID3();
4611
+ if ((parsed.mode === "backup" || parsed.mode === "upload" || parsed.mode === "download") && parsed.async) {
4612
+ const asyncCorrelation = buildRequestCorrelation();
4613
+ const operationId = asyncCorrelation.operationId;
4067
4614
  const opType = parsed.mode;
4068
- const opObject = parsed.mode === "upload" ? parsed.uploadRequest.object_id : parsed.storageObjectRequest.object_key ?? null;
4615
+ const opObject = parsed.mode === "upload" ? parsed.uploadRequest.object_id : null;
4069
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
+ };
4070
4629
  await datastore.upsertOperation({
4071
4630
  operation_id: operationId,
4072
4631
  type: opType,
4073
4632
  object_id: opObject,
4074
4633
  quote_id: opQuote,
4634
+ trace_id: asyncCorrelation.traceId,
4635
+ orchestrator: orchestratorMode,
4636
+ timeout_seconds: timeoutSeconds,
4075
4637
  status: "started",
4076
4638
  error_code: null,
4077
4639
  error_message: null
4078
4640
  });
4079
- const syncArgs = stripAsyncFlag(ctx.args);
4080
- void runCloudCommandHandler({ args: syncArgs }, options).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) => {
4081
4891
  await datastore.upsertOperation({
4082
4892
  operation_id: operationId,
4083
4893
  type: opType,
4084
4894
  object_id: opObject,
4085
4895
  quote_id: opQuote,
4896
+ trace_id: asyncCorrelation.traceId,
4897
+ orchestrator: "inline",
4086
4898
  status: result.isError ? "failed" : "succeeded",
4087
4899
  error_code: result.isError ? "ASYNC_FAILED" : null,
4088
4900
  error_message: result.isError ? result.text : null
4089
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
+ );
4090
4912
  }).catch(async (err) => {
4913
+ const errorMessage = err instanceof Error ? err.message : String(err);
4091
4914
  await datastore.upsertOperation({
4092
4915
  operation_id: operationId,
4093
4916
  type: opType,
4094
4917
  object_id: opObject,
4095
4918
  quote_id: opQuote,
4919
+ trace_id: asyncCorrelation.traceId,
4920
+ orchestrator: "inline",
4096
4921
  status: "failed",
4097
4922
  error_code: "ASYNC_EXCEPTION",
4098
- error_message: err instanceof Error ? err.message : String(err)
4923
+ error_message: errorMessage
4099
4924
  });
4925
+ await emitOperationEventBestEffort(
4926
+ "operation.completed",
4927
+ {
4928
+ ...eventContextBase,
4929
+ status: "failed",
4930
+ errorCode: "ASYNC_EXCEPTION",
4931
+ errorMessage
4932
+ },
4933
+ objectLogHomeDir
4934
+ );
4100
4935
  });
4101
4936
  return {
4102
- text: `Operation started in background. operation-id: ${operationId}
4103
- 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")
4104
4942
  };
4105
4943
  }
4106
4944
  if (parsed.mode === "backup") {
@@ -4158,11 +4996,12 @@ Use /mnemospark-cloud op-status --operation-id ${operationId}`
4158
4996
  }
4159
4997
  }
4160
4998
  if (parsed.mode === "price-storage") {
4999
+ const correlation = buildRequestCorrelation();
4161
5000
  try {
4162
- const quote = await requestPriceStorageQuote(
4163
- parsed.priceStorageRequest,
4164
- options.proxyQuoteOptions
4165
- );
5001
+ const quote = await requestPriceStorageQuote(parsed.priceStorageRequest, {
5002
+ ...options.proxyQuoteOptions,
5003
+ correlation
5004
+ });
4166
5005
  await appendPriceStorageQuoteLog(quote, objectLogHomeDir);
4167
5006
  await datastore.upsertObject({
4168
5007
  object_id: quote.object_id,
@@ -4183,10 +5022,33 @@ Use /mnemospark-cloud op-status --operation-id ${operationId}`
4183
5022
  network: null,
4184
5023
  status: "quoted"
4185
5024
  });
5025
+ await emitCloudEventBestEffort(
5026
+ "price-storage.completed",
5027
+ {
5028
+ operation_id: correlation.operationId,
5029
+ trace_id: correlation.traceId,
5030
+ wallet_address: quote.addr,
5031
+ object_id: quote.object_id,
5032
+ quote_id: quote.quote_id,
5033
+ status: "succeeded"
5034
+ },
5035
+ objectLogHomeDir
5036
+ );
4186
5037
  return {
4187
5038
  text: formatPriceStorageUserMessage(quote)
4188
5039
  };
4189
5040
  } catch (err) {
5041
+ await emitCloudEventBestEffort(
5042
+ "price-storage.completed",
5043
+ {
5044
+ operation_id: correlation.operationId,
5045
+ trace_id: correlation.traceId,
5046
+ wallet_address: parsed.priceStorageRequest.wallet_address,
5047
+ object_id: parsed.priceStorageRequest.object_id,
5048
+ status: "failed"
5049
+ },
5050
+ objectLogHomeDir
5051
+ );
4190
5052
  const message = err instanceof Error ? err.message : typeof err === "string" ? err : String(err);
4191
5053
  return {
4192
5054
  text: message ? `Cannot price storage: ${message}` : "Cannot price storage",
@@ -4195,11 +5057,15 @@ Use /mnemospark-cloud op-status --operation-id ${operationId}`
4195
5057
  }
4196
5058
  }
4197
5059
  if (parsed.mode === "upload") {
5060
+ const uploadCorrelation = buildRequestCorrelation(
5061
+ executionContext.forcedOperationId ?? idempotencyKeyFn(),
5062
+ executionContext.forcedTraceId
5063
+ );
4198
5064
  try {
4199
5065
  const loggedQuote = await datastore.findQuoteById(parsed.uploadRequest.quote_id) ?? await findLoggedPriceStorageQuote(parsed.uploadRequest.quote_id, objectLogHomeDir);
4200
5066
  if (!loggedQuote) {
4201
5067
  return {
4202
- 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.",
4203
5069
  isError: true
4204
5070
  };
4205
5071
  }
@@ -4218,7 +5084,7 @@ Use /mnemospark-cloud op-status --operation-id ${operationId}`
4218
5084
  archiveStats = await stat2(archivePath);
4219
5085
  } catch {
4220
5086
  return {
4221
- 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.`,
4222
5088
  isError: true
4223
5089
  };
4224
5090
  }
@@ -4248,7 +5114,7 @@ Use /mnemospark-cloud op-status --operation-id ${operationId}`
4248
5114
  parsed.uploadRequest.wallet_address,
4249
5115
  objectLogHomeDir
4250
5116
  );
4251
- const idempotencyKey = idempotencyKeyFn();
5117
+ const idempotencyKey = uploadCorrelation.operationId;
4252
5118
  const shouldSettleBeforeUpload = requestStorageUpload !== requestStorageUploadViaProxy;
4253
5119
  if (shouldSettleBeforeUpload) {
4254
5120
  const paymentFetch = createPayment(walletKey).fetch;
@@ -4257,6 +5123,7 @@ Use /mnemospark-cloud op-status --operation-id ${operationId}`
4257
5123
  parsed.uploadRequest.wallet_address,
4258
5124
  {
4259
5125
  ...options.proxyUploadOptions,
5126
+ correlation: uploadCorrelation,
4260
5127
  fetchImpl: (input, init) => paymentFetch(input, init)
4261
5128
  }
4262
5129
  );
@@ -4278,6 +5145,7 @@ Use /mnemospark-cloud op-status --operation-id ${operationId}`
4278
5145
  {
4279
5146
  ...options.proxyUploadOptions,
4280
5147
  idempotencyKey,
5148
+ correlation: uploadCorrelation,
4281
5149
  fetchImpl: uploadFetchImpl
4282
5150
  }
4283
5151
  );
@@ -4297,7 +5165,10 @@ Use /mnemospark-cloud op-status --operation-id ${operationId}`
4297
5165
  object_key: uploadResponse.object_key,
4298
5166
  idempotency_key: idempotencyKey
4299
5167
  },
4300
- options.proxyUploadConfirmOptions
5168
+ {
5169
+ ...options.proxyUploadConfirmOptions,
5170
+ correlation: uploadCorrelation
5171
+ }
4301
5172
  );
4302
5173
  } catch (confirmError) {
4303
5174
  const transId = uploadResponse.trans_id ?? "unknown";
@@ -4346,18 +5217,50 @@ Use /mnemospark-cloud op-status --operation-id ${operationId}`
4346
5217
  status: "active"
4347
5218
  });
4348
5219
  if (parsed.friendlyName?.trim()) {
5220
+ const normalizedFriendlyName = parsed.friendlyName.trim();
4349
5221
  await datastore.upsertFriendlyName({
4350
- friendly_name: parsed.friendlyName.trim(),
5222
+ friendly_name: normalizedFriendlyName,
4351
5223
  object_id: finalizedUploadResponse.object_id,
4352
5224
  object_key: finalizedUploadResponse.object_key,
4353
5225
  quote_id: finalizedUploadResponse.quote_id,
4354
5226
  wallet_address: finalizedUploadResponse.addr
4355
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
+ }
4356
5259
  try {
4357
5260
  await appendJsonlEvent(
4358
5261
  "manifest.jsonl",
4359
5262
  {
4360
- friendly_name: parsed.friendlyName.trim(),
5263
+ friendly_name: normalizedFriendlyName,
4361
5264
  object_id: finalizedUploadResponse.object_id,
4362
5265
  object_key: finalizedUploadResponse.object_key,
4363
5266
  quote_id: finalizedUploadResponse.quote_id,
@@ -4373,6 +5276,7 @@ Use /mnemospark-cloud op-status --operation-id ${operationId}`
4373
5276
  "upload.completed",
4374
5277
  {
4375
5278
  operation_id: idempotencyKey,
5279
+ trace_id: uploadCorrelation.traceId,
4376
5280
  wallet_address: finalizedUploadResponse.addr,
4377
5281
  object_id: finalizedUploadResponse.object_id,
4378
5282
  object_key: finalizedUploadResponse.object_key,
@@ -4386,6 +5290,18 @@ Use /mnemospark-cloud op-status --operation-id ${operationId}`
4386
5290
  text: formatStorageUploadUserMessage(finalizedUploadResponse, cronJob.cronId)
4387
5291
  };
4388
5292
  } catch (error) {
5293
+ await emitCloudEventBestEffort(
5294
+ "upload.completed",
5295
+ {
5296
+ operation_id: uploadCorrelation.operationId,
5297
+ trace_id: uploadCorrelation.traceId,
5298
+ wallet_address: parsed.uploadRequest.wallet_address,
5299
+ object_id: parsed.uploadRequest.object_id,
5300
+ quote_id: parsed.uploadRequest.quote_id,
5301
+ status: "failed"
5302
+ },
5303
+ objectLogHomeDir
5304
+ );
4389
5305
  const uploadErrorMessage = extractUploadErrorMessage(error);
4390
5306
  return {
4391
5307
  text: uploadErrorMessage ?? "Cannot upload storage object",
@@ -4397,31 +5313,49 @@ Use /mnemospark-cloud op-status --operation-id ${operationId}`
4397
5313
  const resolved = await resolveNameSelectorIfNeeded(
4398
5314
  datastore,
4399
5315
  parsed.storageObjectRequest,
4400
- parsed.nameSelector
5316
+ parsed.nameSelector,
5317
+ objectLogHomeDir
4401
5318
  );
4402
5319
  if (resolved.error || !resolved.request) {
4403
5320
  return { text: resolved.error ?? "Cannot resolve storage object request.", isError: true };
4404
5321
  }
4405
5322
  const resolvedRequest = resolved.request;
4406
- const operationId = randomUUID3();
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
+ }
5334
+ const correlation = buildRequestCorrelation();
5335
+ const operationId = correlation.operationId;
5336
+ const knownObject = await datastore.findObjectByObjectKey(resolvedRequest.object_key);
5337
+ const operationObjectId = knownObject?.object_id ?? null;
4407
5338
  await datastore.upsertOperation({
4408
5339
  operation_id: operationId,
4409
5340
  type: "ls",
4410
- object_id: resolvedRequest.object_key,
5341
+ object_id: operationObjectId,
4411
5342
  quote_id: null,
4412
5343
  status: "started",
4413
5344
  error_code: null,
4414
5345
  error_message: null
4415
5346
  });
4416
5347
  try {
4417
- const lsResult = await requestStorageLs(resolvedRequest, options.proxyStorageOptions);
5348
+ const lsResult = await requestStorageLs(resolvedRequest, {
5349
+ ...options.proxyStorageOptions,
5350
+ correlation
5351
+ });
4418
5352
  if (!lsResult.success) {
4419
5353
  throw new Error("ls failed");
4420
5354
  }
4421
5355
  await datastore.upsertOperation({
4422
5356
  operation_id: operationId,
4423
5357
  type: "ls",
4424
- object_id: resolvedRequest.object_key,
5358
+ object_id: operationObjectId,
4425
5359
  quote_id: null,
4426
5360
  status: "succeeded",
4427
5361
  error_code: null,
@@ -4431,20 +5365,23 @@ Use /mnemospark-cloud op-status --operation-id ${operationId}`
4431
5365
  "ls.completed",
4432
5366
  {
4433
5367
  operation_id: operationId,
5368
+ trace_id: correlation.traceId,
4434
5369
  wallet_address: resolvedRequest.wallet_address,
4435
5370
  object_key: resolvedRequest.object_key,
4436
5371
  status: "succeeded"
4437
5372
  },
4438
5373
  objectLogHomeDir
4439
5374
  );
5375
+ const lsText = formatStorageLsUserMessage(lsResult, resolvedRequest.object_key);
4440
5376
  return {
4441
- text: formatStorageLsUserMessage(lsResult, resolvedRequest.object_key)
5377
+ text: resolved.degradedWarning ? `${resolved.degradedWarning}
5378
+ ${lsText}` : lsText
4442
5379
  };
4443
5380
  } catch {
4444
5381
  await datastore.upsertOperation({
4445
5382
  operation_id: operationId,
4446
5383
  type: "ls",
4447
- object_id: resolvedRequest.object_key,
5384
+ object_id: operationObjectId,
4448
5385
  quote_id: null,
4449
5386
  status: "failed",
4450
5387
  error_code: "LS_FAILED",
@@ -4454,6 +5391,7 @@ Use /mnemospark-cloud op-status --operation-id ${operationId}`
4454
5391
  "ls.completed",
4455
5392
  {
4456
5393
  operation_id: operationId,
5394
+ trace_id: correlation.traceId,
4457
5395
  wallet_address: resolvedRequest.wallet_address,
4458
5396
  object_key: resolvedRequest.object_key,
4459
5397
  status: "failed"
@@ -4470,34 +5408,52 @@ Use /mnemospark-cloud op-status --operation-id ${operationId}`
4470
5408
  const resolved = await resolveNameSelectorIfNeeded(
4471
5409
  datastore,
4472
5410
  parsed.storageObjectRequest,
4473
- parsed.nameSelector
5411
+ parsed.nameSelector,
5412
+ objectLogHomeDir
4474
5413
  );
4475
5414
  if (resolved.error || !resolved.request) {
4476
5415
  return { text: resolved.error ?? "Cannot resolve storage object request.", isError: true };
4477
5416
  }
4478
5417
  const resolvedRequest = resolved.request;
4479
- const operationId = randomUUID3();
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
+ }
5429
+ const correlation = buildRequestCorrelation(
5430
+ executionContext.forcedOperationId,
5431
+ executionContext.forcedTraceId
5432
+ );
5433
+ const operationId = correlation.operationId;
5434
+ const knownObject = await datastore.findObjectByObjectKey(resolvedRequest.object_key);
5435
+ const operationObjectId = knownObject?.object_id ?? null;
4480
5436
  await datastore.upsertOperation({
4481
5437
  operation_id: operationId,
4482
5438
  type: "download",
4483
- object_id: resolvedRequest.object_key,
5439
+ object_id: operationObjectId,
4484
5440
  quote_id: null,
4485
5441
  status: "started",
4486
5442
  error_code: null,
4487
5443
  error_message: null
4488
5444
  });
4489
5445
  try {
4490
- const downloadResult = await requestStorageDownload(
4491
- resolvedRequest,
4492
- options.proxyStorageOptions
4493
- );
5446
+ const downloadResult = await requestStorageDownload(resolvedRequest, {
5447
+ ...options.proxyStorageOptions,
5448
+ correlation
5449
+ });
4494
5450
  if (!downloadResult.success) {
4495
5451
  throw new Error("download failed");
4496
5452
  }
4497
5453
  await datastore.upsertOperation({
4498
5454
  operation_id: operationId,
4499
5455
  type: "download",
4500
- object_id: resolvedRequest.object_key,
5456
+ object_id: operationObjectId,
4501
5457
  quote_id: null,
4502
5458
  status: "succeeded",
4503
5459
  error_code: null,
@@ -4507,20 +5463,23 @@ Use /mnemospark-cloud op-status --operation-id ${operationId}`
4507
5463
  "download.completed",
4508
5464
  {
4509
5465
  operation_id: operationId,
5466
+ trace_id: correlation.traceId,
4510
5467
  wallet_address: resolvedRequest.wallet_address,
4511
5468
  object_key: resolvedRequest.object_key,
4512
5469
  status: "succeeded"
4513
5470
  },
4514
5471
  objectLogHomeDir
4515
5472
  );
5473
+ const downloadText = `File ${resolvedRequest.object_key} downloaded to ${downloadResult.file_path}`;
4516
5474
  return {
4517
- text: `File ${resolvedRequest.object_key} downloaded to ${downloadResult.file_path}`
5475
+ text: resolved.degradedWarning ? `${resolved.degradedWarning}
5476
+ ${downloadText}` : downloadText
4518
5477
  };
4519
5478
  } catch {
4520
5479
  await datastore.upsertOperation({
4521
5480
  operation_id: operationId,
4522
5481
  type: "download",
4523
- object_id: resolvedRequest.object_key,
5482
+ object_id: operationObjectId,
4524
5483
  quote_id: null,
4525
5484
  status: "failed",
4526
5485
  error_code: "DOWNLOAD_FAILED",
@@ -4530,6 +5489,7 @@ Use /mnemospark-cloud op-status --operation-id ${operationId}`
4530
5489
  "download.completed",
4531
5490
  {
4532
5491
  operation_id: operationId,
5492
+ trace_id: correlation.traceId,
4533
5493
  wallet_address: resolvedRequest.wallet_address,
4534
5494
  object_key: resolvedRequest.object_key,
4535
5495
  status: "failed"
@@ -4546,16 +5506,32 @@ Use /mnemospark-cloud op-status --operation-id ${operationId}`
4546
5506
  const resolved = await resolveNameSelectorIfNeeded(
4547
5507
  datastore,
4548
5508
  parsed.storageObjectRequest,
4549
- parsed.nameSelector
5509
+ parsed.nameSelector,
5510
+ objectLogHomeDir
4550
5511
  );
4551
5512
  if (resolved.error || !resolved.request) {
4552
5513
  return { text: resolved.error ?? "Cannot resolve storage object request.", isError: true };
4553
5514
  }
4554
5515
  const resolvedRequest = resolved.request;
4555
- const operationId = randomUUID3();
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
+ }
5527
+ const correlation = buildRequestCorrelation();
5528
+ const operationId = correlation.operationId;
4556
5529
  const existingObjectByKey = await datastore.findObjectByObjectKey(resolvedRequest.object_key);
4557
5530
  try {
4558
- const deleteResult = await requestStorageDelete(resolvedRequest, options.proxyStorageOptions);
5531
+ const deleteResult = await requestStorageDelete(resolvedRequest, {
5532
+ ...options.proxyStorageOptions,
5533
+ correlation
5534
+ });
4559
5535
  if (!deleteResult.success) {
4560
5536
  throw new Error("delete failed");
4561
5537
  }
@@ -4564,6 +5540,7 @@ Use /mnemospark-cloud op-status --operation-id ${operationId}`
4564
5540
  "delete.completed",
4565
5541
  {
4566
5542
  operation_id: operationId,
5543
+ trace_id: correlation.traceId,
4567
5544
  wallet_address: resolvedRequest.wallet_address,
4568
5545
  object_key: resolvedRequest.object_key,
4569
5546
  status: "failed"
@@ -4621,18 +5598,21 @@ Use /mnemospark-cloud op-status --operation-id ${operationId}`
4621
5598
  "delete.completed",
4622
5599
  {
4623
5600
  operation_id: operationId,
5601
+ trace_id: correlation.traceId,
4624
5602
  wallet_address: resolvedRequest.wallet_address,
4625
5603
  object_key: resolvedRequest.object_key,
4626
5604
  status: "succeeded"
4627
5605
  },
4628
5606
  objectLogHomeDir
4629
5607
  );
5608
+ const deleteText = formatStorageDeleteUserMessage(
5609
+ resolvedRequest.object_key,
5610
+ cronEntry?.cronId ?? null,
5611
+ cronDeleted
5612
+ );
4630
5613
  return {
4631
- text: formatStorageDeleteUserMessage(
4632
- resolvedRequest.object_key,
4633
- cronEntry?.cronId ?? null,
4634
- cronDeleted
4635
- )
5614
+ text: resolved.degradedWarning ? `${resolved.degradedWarning}
5615
+ ${deleteText}` : deleteText
4636
5616
  };
4637
5617
  }
4638
5618
  return {