@zktx.io/sui-mcp 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/README.md +18 -18
  2. package/dist/adapters/adapterPromptSurfaces.js +0 -20
  3. package/dist/adapters/deepbook/deepbookQuotePolicy.js +0 -4
  4. package/dist/adapters/deepbook/deepbookReviewEvidence.js +0 -9
  5. package/dist/adapters/deepbook/deepbookTransactionMaterialProducer.js +0 -9
  6. package/dist/adapters/intentPlanFactories.js +0 -6
  7. package/dist/core/action/contractNameRegistry.js +0 -53
  8. package/dist/core/action/forbiddenFields.js +0 -4
  9. package/dist/core/action/ptbVisualizationProducer.js +0 -3
  10. package/dist/core/action/receiptPtbGraph.js +0 -17
  11. package/dist/core/action/reviewTimeSimulationEvidence.js +0 -4
  12. package/dist/core/action/signableAdapterContract.js +0 -5
  13. package/dist/core/action/suiChainReceiptMapping.js +0 -18
  14. package/dist/core/action/suiChainReceiptReader.js +1 -21
  15. package/dist/core/action/suiChainReceiptVerifier.js +0 -10
  16. package/dist/core/action/transactionFunding.js +0 -7
  17. package/dist/core/action/types.js +0 -1
  18. package/dist/core/action/walletReviewContractAssembler.js +0 -2
  19. package/dist/core/activity/localDataService.js +0 -6
  20. package/dist/core/activity/sqliteActivityStore.js +0 -8
  21. package/dist/core/activity/sqliteActivityStoreRows.js +0 -2
  22. package/dist/core/activity/sqliteActivityStoreSchema.js +0 -4
  23. package/dist/core/read/deepbookReadHelpers.js +0 -2
  24. package/dist/core/read/deepbookRegistry.js +0 -19
  25. package/dist/core/read/deepbookUsdcChartService.js +0 -2
  26. package/dist/core/read/readCardResult.js +0 -2
  27. package/dist/core/read/readService.js +0 -19
  28. package/dist/core/read/readServiceTypes.js +0 -2
  29. package/dist/core/session/cardSession.js +0 -4
  30. package/dist/core/session/cardSessionStore.js +1 -4
  31. package/dist/core/session/reviewValidity.js +0 -3
  32. package/dist/core/session/sessionRecordStore.js +0 -1
  33. package/dist/core/session/sessionStore.js +0 -15
  34. package/dist/core/session/settingsFileIdentity.js +0 -2
  35. package/dist/core/session/settingsFileProtocol.js +0 -5
  36. package/dist/core/session/settingsFileStore.js +1 -10
  37. package/dist/core/session/settingsInputError.js +0 -2
  38. package/dist/core/session/settingsOperation.js +0 -4
  39. package/dist/core/session/settingsPreparationSql.js +0 -2
  40. package/dist/core/session/settingsWorkflow.js +1 -37
  41. package/dist/core/session/sqliteCardStore.js +0 -2
  42. package/dist/core/session/sqliteSessionStore.js +0 -11
  43. package/dist/core/session/sqliteSettingsWorkflowStore.js +0 -10
  44. package/dist/core/session/sqliteTransactionMaterialStore.js +0 -11
  45. package/dist/core/session/sqliteWalletWorkflowStore.js +0 -29
  46. package/dist/core/session/status.js +0 -2
  47. package/dist/core/session/transactionMaterialStore.js +0 -4
  48. package/dist/core/session/transactionRequest.js +0 -4
  49. package/dist/core/session/walletConnection.js +0 -3
  50. package/dist/core/session/walletWorkflow.js +3 -21
  51. package/dist/core/session/workflowState.js +0 -8
  52. package/dist/core/session/workflowView.js +0 -6
  53. package/dist/core/suiAddress.js +0 -5
  54. package/dist/core/suiEndpoint.js +0 -3
  55. package/dist/mcp/prompts.js +0 -12
  56. package/dist/mcp/schemas.js +0 -3
  57. package/dist/mcp/tools/action/prepareSuiActionReview.js +0 -6
  58. package/dist/mcp/tools/read/serverStatusTools.js +0 -1
  59. package/dist/mcp/tools/session/shared.js +0 -2
  60. package/dist/mcp/tools/session/statusTools.js +0 -2
  61. package/dist/mcp-app/account.html +1 -1
  62. package/dist/mcp-app/chart.html +6 -6
  63. package/dist/mcp-app/connect.html +6 -6
  64. package/dist/mcp-app/receipt.html +298 -298
  65. package/dist/mcp-app/review.html +237 -237
  66. package/dist/mcp-app/settings.html +1 -1
  67. package/dist/mcp-ui/tools.js +3 -4
  68. package/dist/review-server/server.js +0 -2
  69. package/dist/runtime/application.js +1 -1
  70. package/dist/runtime/config.js +0 -2
  71. package/dist/runtime/localSettingsService.js +0 -3
  72. package/dist/runtime/logger.js +0 -2
  73. package/dist/runtime/reviewServerAcquire.js +1 -18
  74. package/dist/runtime/runtimeTermination.js +1 -4
  75. package/dist/runtime/shared/authenticatedFetch.js +0 -6
  76. package/dist/runtime/shared/control.js +0 -2
  77. package/dist/runtime/shared/dataAccess.js +0 -2
  78. package/dist/runtime/shared/mcpHttp.js +0 -5
  79. package/dist/runtime/shared/ownerLease.js +0 -4
  80. package/dist/runtime/shared/server.js +0 -1
  81. package/dist/runtime/shared/stdio.js +2 -5
  82. package/dist/runtime/smokeMainnetRead.js +0 -2
  83. package/dist/runtime/smokeReview.js +0 -4
  84. package/dist/runtime/start.js +0 -2
  85. package/dist/runtime/suiTransactionGraphqlMapping.js +0 -2
  86. package/dist/runtime/suiTransactionGraphqlQueries.js +0 -3
  87. package/dist/runtime/walletConnectConfig.js +0 -2
  88. package/dist/runtime/walletConnectStorage.js +0 -6
  89. package/dist/runtime/walletConnectTransport.js +0 -11
  90. package/dist/runtime/walletSdkChildRuntime.js +0 -5
  91. package/dist/runtime/walletSdkProcess.js +0 -6
  92. package/docs/AGENT_BEHAVIOR.md +1 -5
  93. package/docs/LOCAL_DB_ARCHITECTURE.md +3 -3
  94. package/docs/MCP_SETUP.md +14 -14
  95. package/docs/MCP_TOOLS.md +15 -0
  96. package/docs/SDK_API.md +12 -0
  97. package/docs/WALLET_CONNECTION.md +2 -1
  98. package/package.json +10 -2
  99. package/docs/AGENT_DEVELOPMENT_POLICY.md +0 -819
  100. package/docs/FRONTEND_POLICY.md +0 -611
  101. package/docs/golden-scenarios/BEHAVIOR_MATRIX.md +0 -166
  102. package/docs/golden-scenarios/INTENT_EVIDENCE_GOLDEN_ANSWERS.md +0 -161
  103. package/docs/golden-scenarios/INTENT_EVIDENCE_MATRIX.md +0 -259
@@ -353,8 +353,6 @@ export function configureDatabase(db) {
353
353
  db.exec("PRAGMA foreign_keys=ON");
354
354
  db.exec("PRAGMA busy_timeout=5000");
355
355
  }
356
- // SQLite normalizes its own DDL. Compare only schema metadata, never user rows.
357
- // The reference is built once from the single current schema, not from the DB being checked.
358
356
  let currentSchemaSignature;
359
357
  function schemaSignature(db) {
360
358
  return JSON.stringify(db.prepare("SELECT type, name, tbl_name, sql FROM sqlite_schema WHERE name NOT LIKE 'sqlite_%' ORDER BY type, name").all());
@@ -414,8 +412,6 @@ function externalActivityScansTableSql() {
414
412
  )
415
413
  )`;
416
414
  }
417
- // Exported user data has one durable revision, including writers outside Settings.
418
- // A prepared confirmation is invalidated once, in the writer's own transaction.
419
415
  function settingsRevisionTriggers() {
420
416
  const userTables = ["accounts", "active_account_context", "review_sessions", "review_state_snapshots",
421
417
  "review_status_transitions", "review_executions", "review_requests", "external_activity_scans",
@@ -192,8 +192,6 @@ export function normalizeManagerAddresses(values) {
192
192
  return normalized;
193
193
  });
194
194
  }
195
- // Account inventory v1 exposes only ledger and rebate balances; governance,
196
- // stake, volume, and AccountInfo.open_orders need separate review before export.
197
195
  export function toDeepbookAccountSummary(accountInfo) {
198
196
  return {
199
197
  epoch: accountInfo.epoch,
@@ -2,24 +2,7 @@ import { mainnetCoins, mainnetPools } from "@mysten/deepbook-v3";
2
2
  import { DEEPBOOK_SCALAR_UNIT_SOURCE, decimalsFromScalar, normalizeCoinType } from "./coinMetadata.js";
3
3
  import { ReadServiceInputError } from "./readServiceTypes.js";
4
4
  export const PINNED_DEEPBOOK_COINS = mainnetCoins;
5
- // Curated DeepBook USDC chart pairs: the meaningful Sui-ecosystem base tokens worth
6
- // featuring against USDC, in display order. This is a deliberate editorial allowlist
7
- // — NOT a dynamic volume cut — so the chart shows a stable, hand-picked set and never
8
- // surfaces the dead/dust pools the indexer also lists (stable-vs-stable, wrapped
9
- // duplicates, single-trade listings). Each entry is a token judged worth registering
10
- // on its own merits:
11
- // SUI — Sui L1 gas / native token
12
- // DEEP — DeepBook protocol token (this venue's own token)
13
- // WAL — Walrus decentralized storage (Mysten)
14
- // NS — SuiNS name service
15
- // IKA — Ika MPC / dWallet network
16
- // XBTC — Bitcoin on Sui
17
- // USDT — Tether USD stablecoin
18
- // Edit this list to change which pairs the chart offers; order here is display order.
19
5
  const CURATED_USDC_CHART_BASE_ORDER = new Map(["SUI", "DEEP", "WAL", "NS", "IKA", "XBTC", "USDT"].map((symbol, index) => [symbol, index]));
20
- // Filter the chart pool list to the curated base tokens (by symbol) in curated
21
- // display order. Matching is by base SYMBOL because the indexer's pool name can
22
- // differ from the SDK pool key (e.g. BWETH_USDC vs BETH_USDC).
23
6
  export function curateUsdcChartPools(pools) {
24
7
  return pools
25
8
  .filter((pool) => CURATED_USDC_CHART_BASE_ORDER.has(pool.baseAsset.symbol))
@@ -52,8 +35,6 @@ export function deepbookUnitForCoinType(coinType, coins) {
52
35
  return { decimals, symbol: entry.symbol, name: entry.symbol };
53
36
  }
54
37
  export function findDeepbookCoinEntry(normalizedCoinType, coins) {
55
- // Validate the whole pinned token unit registry before using any DeepBook
56
- // coin match so one bad scalar cannot make the registry look partially trustworthy.
57
38
  assertDeepbookScalars(coins);
58
39
  for (const [symbol, coin] of Object.entries(coins)) {
59
40
  if (normalizeCoinType(coin.type) === normalizedCoinType) {
@@ -35,8 +35,6 @@ export function createDeepbookUsdcChartService(options = {}) {
35
35
  ...chartCommonFields()
36
36
  };
37
37
  }
38
- // Curate to the hand-picked allowlist of meaningful pairs (SUI/USDC, DEEP/USDC,
39
- // …), in display order; the indexer's dead/dust pools never appear.
40
38
  const pools = curateUsdcChartPools(selectDeepbookOfficialIndexerCanonicalUsdcPools(poolResult.pools).map(deepbookUsdcPriceHistoryPairFromOfficialPool));
41
39
  return {
42
40
  status: "ok",
@@ -12,8 +12,6 @@ const foundReceipt = z.object({ status: z.literal("found"), receipt: z.object({
12
12
  inputs: z.array(receiptInput).refine((items) => new Set(items.map((item) => item.index)).size === items.length),
13
13
  ptbGraph: receiptDisplaySchema.shape.ptbGraph
14
14
  }).passthrough() }).strict();
15
- // Partition the same source result before persistence. Never weaken the public
16
- // forbidden-field check or turn arbitrary fields into private metadata.
17
15
  export function projectReadCardResult(kind, input, source) {
18
16
  if (kind !== "receipt" || !source || typeof source !== "object" || !("status" in source) || source.status !== "found") {
19
17
  assertNoForbiddenMcpFields(source);
@@ -14,9 +14,6 @@ import { DEEPBOOK_MID_PRICE_DIRECTION, DEEPBOOK_MID_PRICE_PRECISION, DEEPBOOK_MI
14
14
  export * from "./readServiceTypes.js";
15
15
  export { listDeepbookTokenRegistry } from "./deepbookRegistry.js";
16
16
  const DEFAULT_DEEPBOOK_USDC_PRICE_AT_TIME_MAX_DISTANCE_MINUTES = 360;
17
- // Account inventory object scan: pages of listOwnedObjects to walk (50 objects per
18
- // page) and the max NFTs to surface. A coin-heavy account may truncate before all
19
- // objects are seen; the snapshot flags that rather than paging unbounded.
20
17
  const ACCOUNT_OBJECT_SCAN_PAGES = 6;
21
18
  const ACCOUNT_NFT_LIMIT = 60;
22
19
  const DEEPBOOK_USDC_PRICE_HISTORY_RESPONSE_SUMMARY = {
@@ -332,10 +329,6 @@ export class SuiReadService {
332
329
  cursor: summary.cursor
333
330
  };
334
331
  }
335
- // The public account snapshot for the account page: the SuiNS name, coin
336
- // balances (with the held-as split), owned NFTs, and other owned objects grouped
337
- // by type. Each part is read independently so a missing name or object scan never
338
- // blocks the balances.
339
332
  async summarizeAccountInventory(input) {
340
333
  const [walletSummary, name, owned] = await Promise.all([
341
334
  this.summarizeWalletAssets(input),
@@ -347,8 +340,6 @@ export class SuiReadService {
347
340
  account: input.account,
348
341
  fetchedAt: this.#fetchedAt(),
349
342
  name,
350
- // Drop coins whose total balance is zero — an emptied coin object or
351
- // accumulator residue is not a holding worth listing on the account page.
352
343
  balances: walletSummary.balances.filter((balance) => {
353
344
  try {
354
345
  return BigInt(balance.balance) !== 0n;
@@ -363,8 +354,6 @@ export class SuiReadService {
363
354
  objectsTruncated: owned.truncated
364
355
  };
365
356
  }
366
- // The account's primary SuiNS name, or null when it has none (the lookup throws
367
- // NOT_FOUND) or the call fails — a name is convenience, never gating.
368
357
  async #resolveAccountName(account) {
369
358
  const resolve = this.#client.core.defaultNameServiceName;
370
359
  if (!resolve) {
@@ -378,10 +367,6 @@ export class SuiReadService {
378
367
  return null;
379
368
  }
380
369
  }
381
- // Enumerate owned objects (capped) and split them: Coin objects belong to the
382
- // balance view, objects carrying a Display name/image are NFTs, and the rest are
383
- // grouped by Move type. listOwnedObjects returns all three intermixed, so a
384
- // coin-heavy account may truncate before reaching every NFT.
385
370
  async #scanOwnedObjects(account) {
386
371
  const nfts = [];
387
372
  const groups = new Map();
@@ -1493,10 +1478,6 @@ export class SuiReadService {
1493
1478
  #fetchedAt() {
1494
1479
  return this.#now().toISOString();
1495
1480
  }
1496
- // Resolve one coin type's verified unit (decimals/symbol) through the shared
1497
- // coin-metadata cache and DeepBook fallback. Public so other readers (e.g. the
1498
- // public chain-receipt reader) can render decimal amounts without duplicating
1499
- // the cache logic. Fails soft to an unavailable unit on an unresolvable type.
1500
1481
  async resolveCoinUnit(coinType) {
1501
1482
  let normalizedCoinType;
1502
1483
  try {
@@ -42,8 +42,6 @@ export const DEEPBOOK_USDC_PRICE_HISTORY_SOURCE_UNAVAILABLE_REASONS = [
42
42
  "candle_fetch_failed",
43
43
  "official_indexer_invalid_payload"
44
44
  ];
45
- // Internal placeholder for sender-independent DeepBook market reads. The pinned Sui gRPC
46
- // resolver uses the same dummy sender when no transaction sender is provided.
47
45
  export const DEFAULT_DEEPBOOK_SIMULATION_SENDER = "0x0000000000000000000000000000000000000000000000000000000000000000";
48
46
  export const NOT_INSPECTED_ASSET_CLASSES = [
49
47
  {
@@ -17,11 +17,7 @@ export const cardSnapshotSchema = cardStateSchema.extend({
17
17
  pollAfterMs: z.number().int().positive(),
18
18
  inputRemainingMs: z.number().int().nonnegative()
19
19
  });
20
- // Private display values from a receipt already read by the backend. These are
21
- // individual on-chain inputs, never a serialized transaction or a signature.
22
20
  export const receiptDisplaySchema = z.object({
23
- // Sui syntax is validated by the backend producer/projection. The View checks
24
- // equality with its selected digest and does not load an SDK to recompute it.
25
21
  transactionDigest: z.string().min(1),
26
22
  pureInputs: z.array(z.object({
27
23
  index: z.number().int().nonnegative(),
@@ -47,8 +47,6 @@ export class CardStore {
47
47
  catch {
48
48
  preparation = { status: "failed", error: "The choices for this card could not be loaded." };
49
49
  }
50
- // Preparation may finish after reset/import or shutdown. Never create a
51
- // card in replacement data from that stale continuation.
52
50
  this.assertCurrent();
53
51
  }
54
52
  assertNoForbiddenMcpFields({ input, preparation });
@@ -199,7 +197,6 @@ export class CardStore {
199
197
  }
200
198
  const complete = { ...admitted, ...("receiptDisplay" in projected ? { receiptDisplay: projected.receiptDisplay } : {}),
201
199
  state: { ...admitted.state, data: projected.data, state: "closed", reason: "completed", revision: admitted.state.revision + 1 } };
202
- // Do not report success or replay the source call if this commit fails.
203
200
  try {
204
201
  this.options.records.replace(admitted, complete);
205
202
  }
@@ -220,7 +217,7 @@ export class CardStore {
220
217
  try {
221
218
  this.options.logger?.error("Card read failed", { cardId: record.state.cardId, kind: record.state.kind, stage });
222
219
  }
223
- catch { /* Logging does not own the operation's state. */ }
220
+ catch { }
224
221
  }
225
222
  conflict(record, message) {
226
223
  return { ...this.response(record), error: { code: "card_conflict", message } };
@@ -11,8 +11,6 @@ export function invalidateReviewEvidence(session, now) {
11
11
  const state = { ...session.reviewState, evidenceValidity: "invalidated", updatedAt: now.toISOString() };
12
12
  for (const field of REVIEW_MATERIAL_DERIVED_FIELDS)
13
13
  delete state[field];
14
- // Keep the computation's failure reason, checks and stage provenance. Their
15
- // presence describes that failed attempt, not currently usable material.
16
14
  return { ...session, lastActivityAt: now.toISOString(), reviewState: state };
17
15
  }
18
16
  return { ...session, status: "refresh_required", lastActivityAt: now.toISOString(),
@@ -33,7 +31,6 @@ export function materialStillMatches(verified, session, rowRevision, artifacts,
33
31
  !!material && sameHandle(material, verified.transactionMaterial) && Date.parse(material.expiresAt) > now.getTime() &&
34
32
  Buffer.from(material.transactionBytes).toString("base64") === verified.transactionBytesBase64;
35
33
  }
36
- // The caller owns a synchronous transaction and supplies its one decision time.
37
34
  export function decideReviewEvaluation(candidate, current, rowRevision, artifacts, material, admitted, now) {
38
35
  if (current.status !== "expired" && Date.parse(current.expiresAt) <= now.getTime()) {
39
36
  return { ...current, status: "expired" };
@@ -34,7 +34,6 @@ export class InMemorySessionRecordStore {
34
34
  this.revisions.clear();
35
35
  }
36
36
  hasUnsettledRequest(_id, _now) {
37
- // This preparation-only fixture owns no wallet request records.
38
37
  return false;
39
38
  }
40
39
  }
@@ -104,8 +104,6 @@ export class LocalSessionStore {
104
104
  });
105
105
  return { session: cloneLocalSession(session), token };
106
106
  }
107
- // Snapshot access has no expiry, artifact reconciliation or external work.
108
- // Public consumers project this private record before exposing any fields.
109
107
  readReviewSession(id) {
110
108
  const session = this.sessions.get(id);
111
109
  return session ? cloneLocalSession(session) : undefined;
@@ -130,8 +128,6 @@ export class LocalSessionStore {
130
128
  transactionBytesBase64: Buffer.from(checked.transactionBytes).toString("base64") };
131
129
  }
132
130
  catch (error) {
133
- // Invalid evidence is a candidate for refresh, never permission to mutate
134
- // a potentially newer revision. Storage failures must still propagate.
135
131
  if (!(error instanceof LocalTransactionMaterialStoreError) && !(error instanceof ReviewEvidenceMismatch) && !(error instanceof ZodError))
136
132
  throw error;
137
133
  }
@@ -150,8 +146,6 @@ export class LocalSessionStore {
150
146
  this.recordEvaluationEvents(result.events);
151
147
  return cloneLocalSession(result.session);
152
148
  }
153
- // Preparation-only in-memory fixtures share the decision function. They do
154
- // not establish SQLite atomicity or wallet admission guarantees.
155
149
  const current = this.sessions.get(id);
156
150
  if (!current)
157
151
  return undefined;
@@ -186,8 +180,6 @@ export class LocalSessionStore {
186
180
  if (!session) {
187
181
  return false;
188
182
  }
189
- // Review sessions expose expired/final lifecycle states through read APIs after token validation.
190
- // Mutable methods own expiry transitions and return lifecycle-specific errors.
191
183
  return tokenMatchesHash(session.tokenHash, token);
192
184
  }
193
185
  async recordWalletConnected(id, account, now = new Date()) {
@@ -208,9 +200,6 @@ export class LocalSessionStore {
208
200
  throw new SessionStoreError("invalid_session_transition", `Review session already bound to a different account: ${id}`);
209
201
  }
210
202
  if (nextSession.status === "proposed") {
211
- // Explicit backend account binding follows the preparation lifecycle.
212
- // Active read context alone is not wallet approval; the workflow checks
213
- // the live wallet connection before invoking this operation.
214
203
  transition(nextSession, "awaiting_wallet");
215
204
  transition(nextSession, "wallet_connected");
216
205
  }
@@ -356,7 +345,6 @@ export class LocalSessionStore {
356
345
  await this.eventLog.append(record);
357
346
  }
358
347
  catch (error) {
359
- // Event logs are optional audit/debug sinks. SQLite and in-memory session state remain authoritative.
360
348
  this.logger.error("event log append failed", {
361
349
  eventType: record.type,
362
350
  error: error instanceof Error ? error.message : String(error)
@@ -572,9 +560,6 @@ export class LocalSessionStore {
572
560
  }
573
561
  }
574
562
  }
575
- // In-memory session store: the orchestration above with Map-backed record and
576
- // artifact stores. Public name + constructor are unchanged so existing callers and
577
- // the session-store contract test keep working as the regression wall.
578
563
  export class InMemorySessionStore extends LocalSessionStore {
579
564
  constructor(options) {
580
565
  super({
@@ -1,5 +1,3 @@
1
- // Both the filesystem and replacement/cleanup journal use this exact private
2
- // namespace. A display name or an export role alone is never a download path.
3
1
  export function settingsDownloadPath(fileId) {
4
2
  return `.settings-staging/${fileId}.download`;
5
3
  }
@@ -1,4 +1,3 @@
1
- // The existing internal card/HTTP limit applies to the complete JSON-RPC message.
2
1
  import { SETTINGS_IMPORT_MAX_SIZE } from "./settingsOperation.js";
3
2
  export const MAX_JSON_BODY_BYTES = 64 * 1024;
4
3
  export function settingsJsonSize(value) { return new TextEncoder().encode(JSON.stringify(value)).length; }
@@ -6,8 +5,6 @@ export function settingsRawChunkBudget(envelopeWithEmptyBase64) {
6
5
  const room = MAX_JSON_BODY_BYTES - settingsJsonSize(envelopeWithEmptyBase64);
7
6
  return room > 0 ? 3 * Math.floor(room / 4) : 0;
8
7
  }
9
- // The actual request may carry additional Host metadata. Rejection happens
10
- // before dispatch; its independently measured room lets the same offset retry.
11
8
  export function settingsUploadMessageBudget(message) {
12
9
  if (!message || typeof message !== "object")
13
10
  return undefined;
@@ -15,8 +12,6 @@ export function settingsUploadMessageBudget(message) {
15
12
  if (envelope.method !== "tools/call" || envelope.params?.name !== "ui.act_card" ||
16
13
  envelope.params.arguments?.input?.action !== "settings_upload_chunk")
17
14
  return undefined;
18
- // Safe-integer decimal maxima cover ids/revisions; upload offsets use the
19
- // accepted raw-file limit.
20
15
  envelope.id = Number.MAX_SAFE_INTEGER;
21
16
  envelope.params.arguments.revision = Number.MAX_SAFE_INTEGER;
22
17
  envelope.params.arguments.input.offset = SETTINGS_IMPORT_MAX_SIZE;
@@ -19,8 +19,6 @@ export function decodeSettingsChunk(value) {
19
19
  throw new SettingsConflict("Part of the file was not encoded correctly.");
20
20
  return data;
21
21
  }
22
- /** Private files are named from backend-generated identities only. SQLite owns
23
- * reservations and progress; this class verifies the corresponding physical fact. */
24
22
  export class SettingsFileStore {
25
23
  dataDirectory;
26
24
  constructor(dataDirectory) {
@@ -94,8 +92,6 @@ export class SettingsFileStore {
94
92
  throw new SettingsConflict("The snapshot is not ready to save.");
95
93
  await this.prepareDirectory("backups");
96
94
  const source = this.stagingPath(file), destination = this.localBackupPath(file);
97
- // Persist the verified source identity before the only effect. Recovery
98
- // verifies this same inode; it never creates a missing destination.
99
95
  const identity = await this.downloadSourceIdentity(file);
100
96
  rememberSource(identity);
101
97
  try {
@@ -133,7 +129,7 @@ export class SettingsFileStore {
133
129
  }
134
130
  if (error instanceof SettingsConflict || fileCode(error, "ELOOP"))
135
131
  return { status: "failed", reason: "target_conflict" };
136
- throw error; // Unreadable is not absent; keep the admitted save unsettled.
132
+ throw error;
137
133
  }
138
134
  try {
139
135
  if (!sourceIdentity)
@@ -190,8 +186,6 @@ export class SettingsFileStore {
190
186
  const length = (await handle.stat()).size;
191
187
  if (length < reservation.offset || length > reservation.offset + data.length)
192
188
  throw new SettingsConflict("The file size changed during transfer.");
193
- // A crash may leave the beginning of this exact reserved chunk. Verify it
194
- // before completing the remainder; never overwrite a different prefix.
195
189
  const written = length - reservation.offset;
196
190
  const prefix = Buffer.alloc(written);
197
191
  const read = await handle.read(prefix, 0, written, reservation.offset);
@@ -309,8 +303,6 @@ export class SettingsFileStore {
309
303
  if (!file.sealed || file.invalidated || !Number.isSafeInteger(offset) || !Number.isSafeInteger(length) ||
310
304
  offset < 0 || length < 1 || offset + length > file.totalSize)
311
305
  throw new SettingsConflict("This part of the backup is unavailable.");
312
- // Verify the artifact on the same open descriptor used for this chunk, so
313
- // path replacement cannot substitute unchecked content between two opens.
314
306
  const info = await lstat(directory);
315
307
  if (!info.isDirectory() || info.isSymbolicLink())
316
308
  throw new SettingsConflict("The local backup directory changed.");
@@ -341,6 +333,5 @@ export class SettingsFileStore {
341
333
  if (!(error && typeof error === "object" && "code" in error && error.code === "ENOENT"))
342
334
  throw error;
343
335
  }
344
- // The public backup is the user's artifact and is never removed here.
345
336
  }
346
337
  }
@@ -2,8 +2,6 @@ import { SuiEndpointError } from "../suiEndpoint.js";
2
2
  import { LocalSettingsError } from "../preferences/preferencesStore.js";
3
3
  import { LocalDataError } from "../activity/localDataTypes.js";
4
4
  export const SETTINGS_REPLACEMENT_BLOCKED_MESSAGE = "A wallet or transaction response is still pending, or the initial transaction-result waiting period has not ended. Local records cannot be replaced yet.";
5
- // Only authenticated Settings validation reaches this mapping. Return fixed
6
- // explanations and recovery categories, never imported values or exceptions.
7
5
  export function settingsInputError(error, kind) {
8
6
  if (!(error instanceof SuiEndpointError) &&
9
7
  !(error instanceof LocalSettingsError && error.kind === "input_invalid") &&
@@ -2,7 +2,6 @@ import { z } from "zod";
2
2
  import { assetReadAccountSchema, walletAvailabilitySchema } from "./walletConnection.js";
3
3
  import { LOCAL_DATA_EXPORT_FORMAT } from "../activity/localDataTypes.js";
4
4
  import { suiAddressStringSchema } from "../suiAddress.js";
5
- // This is the existing backup input limit, measured before Base64/JSON wrapping.
6
5
  export const SETTINGS_IMPORT_MAX_SIZE = 16 * 1024 * 1024;
7
6
  const identity = z.string().uuid();
8
7
  const size = z.number().int().nonnegative().safe();
@@ -89,9 +88,6 @@ export const settingsActionSchema = z.discriminatedUnion("action", [
89
88
  z.object({ action: z.literal("settings_back"), operationId: identity }).strict(),
90
89
  z.object({ action: z.literal("settings_cancel") }).strict()
91
90
  ]);
92
- // A known rejection belongs to the selected complete file, not to the current
93
- // local-data revision or short-lived confirmation. Selection/owner/epoch rules
94
- // remain enforced by the operation's store and action handlers.
95
91
  export function retainedBackupRejection(operation, file) {
96
92
  const saved = storedRestoreCheckSchema.safeParse(operation.preparation?.restoreCheck);
97
93
  if (operation.draft?.kind !== "import_local_data" || !file || operation.fileId !== file.fileId ||
@@ -1,5 +1,3 @@
1
- // The trigger and explicit Back use the same stored preparation transition.
2
- // A sealed upload is separate from the short-lived confirmation of its impact.
3
1
  export const SETTINGS_REUSABLE_UPLOAD_SQL = `
4
2
  kind='import_local_data' AND EXISTS (
5
3
  SELECT 1 FROM settings_files f
@@ -11,13 +11,11 @@ import { isSettingsDownloadFile, settingsDownloadPath } from "./settingsFileIden
11
11
  import { SettingsDownloadPathExists, settingsFileDigest, decodeSettingsChunk } from "./settingsFileStore.js";
12
12
  import { settingsIdentity } from "./sqliteSettingsWorkflowStore.js";
13
13
  import { SettingsConflict, settingsDownload, isSettingsDownloadFileAction, settingsViewSchema, endpointPreparationSchema, storedRestoreCheckSchema, retainedBackupRejection, permitsBackupRecheck } from "./settingsOperation.js";
14
- // One existing product-session length from physical snapshot readiness.
15
14
  export const DOWNLOAD_RETRY_WINDOW_MS = DEFAULT_SESSION_TTL_MS;
16
15
  export class SettingsWorkflow {
17
16
  options;
18
17
  pendingResults = new Map();
19
18
  finishedFileIO = new Set();
20
- // This tracks actual pending I/O only; SQLite owns check results and permission.
21
19
  checkingFiles = new Set();
22
20
  downloadTimer;
23
21
  closing = false;
@@ -34,16 +32,12 @@ export class SettingsWorkflow {
34
32
  create(record) { this.records.create(record); }
35
33
  operationKind(record) { return this.records.forCard(record).draft?.kind; }
36
34
  recoverOwner() {
37
- // Invalidate old input synchronously. Verifying a prior backup and removing
38
- // temporary files must not delay unrelated MCP startup or completed reads.
39
35
  this.records.transaction(() => {
40
36
  const downloads = this.records.db.prepare("SELECT operation_id FROM settings_operations WHERE kind='download_backup' AND owner_id!=?")
41
37
  .all(this.records.ownerId);
42
38
  for (const row of downloads) {
43
39
  const operation = this.records.get(row.operation_id);
44
40
  if (settingsDownload(operation)?.localSave?.status === "saving") {
45
- // Inherit settlement, never the departed card's input permission.
46
- // Keep its final/file reservation until the existing artifact is checked.
47
41
  this.records.db.prepare("UPDATE settings_operations SET owner_id=? WHERE operation_id=?").run(this.records.ownerId, operation.operationId);
48
42
  }
49
43
  else
@@ -76,10 +70,6 @@ export class SettingsWorkflow {
76
70
  if (file)
77
71
  this.records.releaseFileIO(file.fileId);
78
72
  }
79
- // The departed owner may have committed a terminal result and finished
80
- // physical I/O, but failed to persist its final I/O release. Terminal
81
- // operations were intentionally not reopened above. Release that stale
82
- // reservation without replaying the operation or touching a current owner.
83
73
  const ioId = this.records.control().file_io_id;
84
74
  if (ioId) {
85
75
  const file = this.records.file(ioId);
@@ -126,8 +116,6 @@ export class SettingsWorkflow {
126
116
  this.downloadTimer = setTimeout(() => {
127
117
  this.downloadTimer = undefined;
128
118
  void this.maintenance(() => this.maintainDownloads()).catch(() => {
129
- // Durable cleanup remains pending/failed; a same-operation read or
130
- // startup can retry. Never spin on an already elapsed deadline.
131
119
  });
132
120
  }, Math.max(0, Math.min(...deadlines) - this.now().getTime()));
133
121
  this.downloadTimer.unref?.();
@@ -149,16 +137,11 @@ export class SettingsWorkflow {
149
137
  this.options.assertCurrent();
150
138
  failure ??= error;
151
139
  }
152
- // A failed, already-due journal stays retryable through Check status/startup;
153
- // it cannot spin at zero delay or cancel another snapshot's future deadline.
154
140
  this.scheduleDownloadExpiry(failure !== undefined);
155
141
  if (failure)
156
142
  throw failure;
157
143
  }
158
144
  dataReplaced() {
159
- // The replacement action owns and awaits its transferred cleanup journal.
160
- // Starting another cleanup here would race its I/O reservation and return
161
- // before the action's required cleanup result is known.
162
145
  this.scheduleDownloadExpiry();
163
146
  }
164
147
  async close() {
@@ -186,7 +169,7 @@ export class SettingsWorkflow {
186
169
  result = { outcome: "completed", recordedAt: this.now().toISOString(), message: "The previously created local backup was verified.",
187
170
  details: { path: this.options.files.exportPath(file), filename: file.displayName, fileId: file.fileId, sha256: file.sha256, totalSize: file.totalSize } };
188
171
  }
189
- catch { /* A missing artifact is never regenerated on a read. */ }
172
+ catch { }
190
173
  }
191
174
  this.options.assertCurrent();
192
175
  this.retainResult(operation.operationId, result, false, file && result.outcome === "completed" ? { ...file, sealed: true, receivedSize: file.totalSize } : undefined);
@@ -218,8 +201,6 @@ export class SettingsWorkflow {
218
201
  return { file: checked.file, offset, chunkBase64: data.toString("base64") };
219
202
  }
220
203
  finally {
221
- // A read may have been the last physical I/O keeping an ended snapshot
222
- // alive. Settle its own journal; a saved-result read need not await it.
223
204
  await this.cleanupTemporaryFiles(operation.operationId);
224
205
  }
225
206
  }
@@ -260,8 +241,6 @@ export class SettingsWorkflow {
260
241
  this.records.transaction(() => {
261
242
  this.records.endDownload(file.operationId, "source_unavailable");
262
243
  const existing = this.records.file(file.fileId);
263
- // An exclusive-create collision never made this request the physical
264
- // file's owner. Do not delete that pre-existing item as cleanup.
265
244
  this.records.writeFile({ ...existing, invalidated: true, cleanup: "complete" });
266
245
  });
267
246
  result = { outcome: "failed", completionScope: "backup_snapshot", recordedAt: this.now().toISOString(),
@@ -325,8 +304,6 @@ export class SettingsWorkflow {
325
304
  retained = { saveAttemptId, outcome };
326
305
  this.localSaveResults.set(file.operationId, retained);
327
306
  }
328
- // Retain the ended physical outcome if the DB write fails. A read retries
329
- // this same settlement, not link(), a new snapshot, or a Host request.
330
307
  this.records.settleLocalSave(file.operationId, retained.saveAttemptId, retained.outcome);
331
308
  this.localSaveResults.delete(file.operationId);
332
309
  this.scheduleDownloadExpiry();
@@ -498,8 +475,6 @@ export class SettingsWorkflow {
498
475
  this.observeLocalSave(operation);
499
476
  let downloadExpiryPending = false;
500
477
  if (operation.result) {
501
- // The result is already committed. A failed release of known-ended I/O
502
- // must remain retryable without making that result depend on the write.
503
478
  try {
504
479
  this.synchronize(operation.operationId);
505
480
  }
@@ -541,7 +516,6 @@ export class SettingsWorkflow {
541
516
  await this.cleanupFile(file, false).catch(() => { this.options.assertCurrent(); });
542
517
  }
543
518
  }
544
- // Completed facts do not depend on current wallet or unrelated cleanup writes.
545
519
  const facts = operation.result ? undefined : await this.options.readFacts();
546
520
  this.options.assertCurrent();
547
521
  record = this.records.cards.get(record.state.cardId);
@@ -566,8 +540,6 @@ export class SettingsWorkflow {
566
540
  if (fileIOAvailable && file && !file.invalidated && file.receivedSize === file.totalSize && !file.reservation &&
567
541
  restoreCheck && permitsBackupRecheck(restoreCheck))
568
542
  allowed.push("settings_prepare");
569
- // A matching upload may resume without allocating another file. New
570
- // writes still require the shared reservation check at their admission.
571
543
  if (fileIOAvailable && ["draft", "uploading"].includes(operation.phase) &&
572
544
  (file && !file.invalidated || this.records.canReserveFileIO(operation.operationId)))
573
545
  allowed.push("settings_begin_upload");
@@ -677,8 +649,6 @@ export class SettingsWorkflow {
677
649
  return;
678
650
  }
679
651
  if (action.action === "settings_select" && action.draft.kind === "set_endpoint") {
680
- // Drafts are public display state. Reject credentials and invalid URLs
681
- // before storing or projecting the user's endpoint input.
682
652
  action = { ...action, draft: { ...action.draft,
683
653
  value: (action.draft.endpoint === "grpc" ? parseGrpcUrl : parseGraphqlUrl)(action.draft.value) } };
684
654
  }
@@ -980,8 +950,6 @@ export class SettingsWorkflow {
980
950
  }
981
951
  catch (error) {
982
952
  this.options.assertCurrent();
983
- // A rejected final verification does not apply data or end the card,
984
- // but it must revoke the previous ready result for this exact file.
985
953
  this.records.transaction(() => {
986
954
  const { card, operation } = this.records.requireInput(expected, action.operationId);
987
955
  if (operation.fileId !== file.fileId)
@@ -1128,8 +1096,6 @@ export class SettingsWorkflow {
1128
1096
  }
1129
1097
  return this.records.finish(id, retained.result);
1130
1098
  });
1131
- // A later cleanup can reserve this same file. The ended publication must
1132
- // not be reused as evidence that this new I/O has also ended.
1133
1099
  if (settledFileId)
1134
1100
  this.finishedFileIO.delete(settledFileId);
1135
1101
  if (applied && !ready)
@@ -1167,8 +1133,6 @@ export class SettingsWorkflow {
1167
1133
  for (const [id, pending] of this.pendingResults) {
1168
1134
  if (operationId !== undefined && id !== operationId || recoveryOnly && this.records.get(id)?.draft?.kind !== "restart_wallet_service")
1169
1135
  continue;
1170
- // Ready is replayed only inside the SDK snapshot transaction; a previous
1171
- // failed transaction did not prove that its observation was committed.
1172
1136
  if (!pending.ready)
1173
1137
  this.retainResult(id, pending.result, false, pending.file);
1174
1138
  }
@@ -60,8 +60,6 @@ export class SqliteCardRecordStore {
60
60
  @input, @accepted, @result, @display, @scope, @operation)`).run(values(record));
61
61
  }
62
62
  replace(expected, next) {
63
- // This single statement commits admission or a result atomically. A removed
64
- // card is never recreated by a delayed network response.
65
63
  return this.db.prepare(`UPDATE live_read_cards SET state=@state, reason=@reason, error=@error,
66
64
  revision=@revision, input_json=@input, accepted_input_json=@accepted, result_json=@result,
67
65
  receipt_display_json=@display, scope=@scope, operation_id=@operation
@@ -38,13 +38,6 @@ export function updateLiveReviewSessionRow(db, expectedRevision, session) {
38
38
  .run({ ...serialized, expectedRevision });
39
39
  return result.changes > 0;
40
40
  }
41
- /**
42
- * SQLite-backed live review-session records. Runtime product transitions are
43
- * committed by SqliteActivityStore when the store is created through that owner,
44
- * so live session state and review activity stay in one SQLite transaction.
45
- * Direct instances keep a low-level transition commit path for tests and mixed
46
- * harnesses that do not couple live state with activity rows.
47
- */
48
41
  export class SqliteSessionRecordStore {
49
42
  db;
50
43
  usesActivityStoreLiveSessionMutations;
@@ -107,8 +100,6 @@ export class SqlitePrivateReviewArtifactStore {
107
100
  if (!row) {
108
101
  return undefined;
109
102
  }
110
- // clonePrivateReviewArtifacts re-parses/validates the evidence on read, matching
111
- // the in-memory store's clone-on-read behaviour.
112
103
  return clonePrivateReviewArtifacts(JSON.parse(row.artifacts_json));
113
104
  }
114
105
  set(reviewSessionId, artifacts) {
@@ -126,8 +117,6 @@ export class SqlitePrivateReviewArtifactStore {
126
117
  this.db.prepare(`DELETE FROM live_private_review_artifacts`).run();
127
118
  }
128
119
  }
129
- // Reconstruct the ReviewSession from columns + JSON blobs. Optional fields are
130
- // omitted (not set to undefined) so the shape matches the in-memory store exactly.
131
120
  export function sessionFromLiveReviewSessionRow(row) {
132
121
  return {
133
122
  id: row.id,