@zktx.io/sui-mcp 0.5.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 (138) hide show
  1. package/README.md +21 -21
  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 +65 -52
  20. package/dist/core/activity/localDataValidation.js +1 -3
  21. package/dist/core/activity/sqliteActivityStore.js +24 -24
  22. package/dist/core/activity/sqliteActivityStoreRows.js +0 -2
  23. package/dist/core/activity/sqliteActivityStoreSchema.js +81 -14
  24. package/dist/core/preferences/sqlitePreferencesRepository.js +5 -3
  25. package/dist/core/read/deepbookReadHelpers.js +0 -2
  26. package/dist/core/read/deepbookRegistry.js +0 -19
  27. package/dist/core/read/deepbookUsdcChartService.js +0 -2
  28. package/dist/core/read/readCardResult.js +0 -2
  29. package/dist/core/read/readService.js +0 -19
  30. package/dist/core/read/readServiceTypes.js +0 -2
  31. package/dist/core/session/cardSession.js +1 -5
  32. package/dist/core/session/cardSessionStore.js +54 -7
  33. package/dist/core/session/reviewValidity.js +0 -3
  34. package/dist/core/session/sessionRecordStore.js +0 -1
  35. package/dist/core/session/sessionStore.js +0 -32
  36. package/dist/core/session/settingsFileIdentity.js +9 -0
  37. package/dist/core/session/settingsFileProtocol.js +20 -0
  38. package/dist/core/session/settingsFileStore.js +337 -0
  39. package/dist/core/session/settingsInputError.js +32 -0
  40. package/dist/core/session/settingsOperation.js +220 -0
  41. package/dist/core/session/settingsPreparationSql.js +13 -0
  42. package/dist/core/session/settingsWorkflow.js +1183 -0
  43. package/dist/core/session/sqliteCardStore.js +0 -2
  44. package/dist/core/session/sqliteSessionStore.js +0 -51
  45. package/dist/core/session/sqliteSettingsWorkflowStore.js +453 -0
  46. package/dist/core/session/sqliteTransactionMaterialStore.js +0 -11
  47. package/dist/core/session/sqliteWalletWorkflowStore.js +160 -66
  48. package/dist/core/session/status.js +2 -4
  49. package/dist/core/session/transactionMaterialStore.js +0 -4
  50. package/dist/core/session/transactionRequest.js +0 -4
  51. package/dist/core/session/walletConnection.js +26 -10
  52. package/dist/core/session/walletWorkflow.js +70 -30
  53. package/dist/core/session/workflowState.js +14 -14
  54. package/dist/core/session/workflowView.js +18 -8
  55. package/dist/core/suiAddress.js +0 -5
  56. package/dist/core/suiEndpoint.js +0 -3
  57. package/dist/mcp/prompts.js +0 -12
  58. package/dist/mcp/responseGuidance.js +10 -5
  59. package/dist/mcp/schemas.js +0 -3
  60. package/dist/mcp/serverInfo.js +3 -4
  61. package/dist/mcp/toolNames.js +2 -2
  62. package/dist/mcp/tools/account/index.js +1 -19
  63. package/dist/mcp/tools/action/prepareSuiActionReview.js +0 -6
  64. package/dist/mcp/tools/read/readToolHelpers.js +5 -4
  65. package/dist/mcp/tools/read/serverStatusTools.js +0 -1
  66. package/dist/mcp/tools/session/executionResultTools.js +2 -2
  67. package/dist/mcp/tools/session/shared.js +1 -2
  68. package/dist/mcp/tools/session/statusTools.js +4 -5
  69. package/dist/mcp/tools/settings/index.js +8 -28
  70. package/dist/mcp-app/account.html +14 -14
  71. package/dist/mcp-app/account.notices.txt +1 -1
  72. package/dist/mcp-app/chart.html +9 -9
  73. package/dist/mcp-app/chart.notices.txt +1 -1
  74. package/dist/mcp-app/connect.html +14 -14
  75. package/dist/mcp-app/connect.notices.txt +1 -1
  76. package/dist/mcp-app/receipt.html +1298 -1463
  77. package/dist/mcp-app/receipt.notices.txt +226 -1204
  78. package/dist/mcp-app/review.html +1298 -1463
  79. package/dist/mcp-app/review.notices.txt +226 -1204
  80. package/dist/mcp-app/settings.html +3133 -0
  81. package/dist/mcp-app/settings.notices.txt +973 -0
  82. package/dist/mcp-ui/contracts.js +8 -2
  83. package/dist/mcp-ui/readCards.js +1 -1
  84. package/dist/mcp-ui/tools.js +34 -4
  85. package/dist/review-server/server.js +3 -51
  86. package/dist/runtime/application.js +41 -14
  87. package/dist/runtime/config.js +0 -2
  88. package/dist/runtime/localSettingsService.js +23 -2
  89. package/dist/runtime/logger.js +0 -2
  90. package/dist/runtime/reviewServerAcquire.js +1 -18
  91. package/dist/runtime/runtimeTermination.js +1 -4
  92. package/dist/runtime/shared/authenticatedFetch.js +2 -4
  93. package/dist/runtime/shared/control.js +4 -7
  94. package/dist/runtime/shared/dataAccess.js +0 -2
  95. package/dist/runtime/shared/http.js +14 -0
  96. package/dist/runtime/shared/mcpHttp.js +6 -6
  97. package/dist/runtime/shared/ownerLease.js +0 -4
  98. package/dist/runtime/shared/server.js +1 -2
  99. package/dist/runtime/shared/stdio.js +2 -5
  100. package/dist/runtime/smokeMainnetRead.js +0 -3
  101. package/dist/runtime/smokeReview.js +0 -4
  102. package/dist/runtime/start.js +0 -2
  103. package/dist/runtime/suiTransactionGraphqlMapping.js +0 -2
  104. package/dist/runtime/suiTransactionGraphqlQueries.js +0 -3
  105. package/dist/runtime/walletConnectConfig.js +0 -2
  106. package/dist/runtime/walletConnectStorage.js +2 -10
  107. package/dist/runtime/walletConnectTransport.js +0 -11
  108. package/dist/runtime/walletSdkChildRuntime.js +2 -6
  109. package/dist/runtime/walletSdkIpc.js +2 -1
  110. package/dist/runtime/walletSdkProcess.js +3 -8
  111. package/docs/AGENT_BEHAVIOR.md +45 -15
  112. package/docs/LOCAL_DB_ARCHITECTURE.md +145 -22
  113. package/docs/MCP_SETUP.md +99 -39
  114. package/docs/MCP_TOOLS.md +288 -24
  115. package/docs/SDK_API.md +63 -10
  116. package/docs/UTILITY_INDEX.md +9 -6
  117. package/docs/WALLET_CONNECTION.md +32 -10
  118. package/package.json +22 -8
  119. package/dist/core/activity/schemaVersion.js +0 -2
  120. package/dist/core/session/keyedRecordStore.js +0 -18
  121. package/dist/core/session/settingsSession.js +0 -1
  122. package/dist/core/session/settingsSessions.js +0 -45
  123. package/dist/review-app/brand-dark.svg +0 -1
  124. package/dist/review-app/brand-light.svg +0 -1
  125. package/dist/review-app/favicon.svg +0 -1
  126. package/dist/review-app/settings.css +0 -1
  127. package/dist/review-app/settings.js +0 -1
  128. package/dist/review-app/ui.css +0 -809
  129. package/dist/review-server/assets.js +0 -48
  130. package/dist/review-server/html.js +0 -45
  131. package/dist/review-server/http.js +0 -47
  132. package/dist/review-server/middleware/reviewToken.js +0 -7
  133. package/dist/review-server/settingsApi.js +0 -158
  134. package/docs/AGENT_DEVELOPMENT_POLICY.md +0 -815
  135. package/docs/FRONTEND_POLICY.md +0 -534
  136. package/docs/golden-scenarios/BEHAVIOR_MATRIX.md +0 -166
  137. package/docs/golden-scenarios/INTENT_EVIDENCE_GOLDEN_ANSWERS.md +0 -161
  138. package/docs/golden-scenarios/INTENT_EVIDENCE_MATRIX.md +0 -259
@@ -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
  {
@@ -2,7 +2,7 @@ import { z } from "zod";
2
2
  export const walletDisplaySchema = z.object({
3
3
  connectionId: z.string().min(1), pairingUri: z.string().startsWith("wc:"), expiresAt: z.string().datetime()
4
4
  }).strict();
5
- export const cardKindSchema = z.enum(["account", "receipt", "chart", "connect", "review"]);
5
+ export const cardKindSchema = z.enum(["account", "receipt", "chart", "connect", "review", "settings"]);
6
6
  export const cardStateSchema = z.object({
7
7
  cardId: z.string().min(1), kind: cardKindSchema,
8
8
  state: z.enum(["ready", "running", "closed"]),
@@ -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(),
@@ -8,6 +8,9 @@ import { assertNoForbiddenMcpFields } from "../action/forbiddenFields.js";
8
8
  import { cardSnapshotSchema } from "./cardSession.js";
9
9
  import { WorkflowConflict } from "./sqliteWalletWorkflowStore.js";
10
10
  import { parseWorkflowAction } from "./workflowView.js";
11
+ import { randomUUID } from "node:crypto";
12
+ import { settingsActionSchema, isSettingsDownloadFileAction, SettingsConflict } from "./settingsOperation.js";
13
+ import { settingsInputError } from "./settingsInputError.js";
11
14
  export class CardError extends Error {
12
15
  }
13
16
  export class CardStore {
@@ -27,6 +30,8 @@ export class CardStore {
27
30
  async create(kind, input, execute = false) {
28
31
  this.assertCurrent();
29
32
  const business = kind === "connect" || kind === "review";
33
+ if (kind === "settings" && (!this.options.settings || execute || Object.keys(input).length))
34
+ throw new CardError("Settings cards are unavailable for this input.");
30
35
  if (business && (!this.options.workflow || execute))
31
36
  throw new CardError("This type of card is unavailable.");
32
37
  const parsed = execute ? parseCardInput(kind, input) : undefined;
@@ -42,20 +47,22 @@ export class CardStore {
42
47
  catch {
43
48
  preparation = { status: "failed", error: "The choices for this card could not be loaded." };
44
49
  }
45
- // Preparation may finish after reset/import or shutdown. Never create a
46
- // card in replacement data from that stale continuation.
47
50
  this.assertCurrent();
48
51
  }
49
52
  assertNoForbiddenMcpFields({ input, preparation });
50
53
  const { base, token } = createLocalSessionBase(this.now(), DEFAULT_SESSION_TTL_MS);
51
54
  const record = { ownerId: this.options.ownerId, tokenHash: base.tokenHash,
52
- scope: kind === "connect" ? "connect" : kind === "review" ? input.mode === "manage" ? "review_manage" : "review" : "read",
55
+ scope: kind === "settings" ? "settings" : kind === "connect" ? "connect" : kind === "review" ? input.mode === "manage" ? "review_manage" : "review" : "read",
56
+ ...(kind === "settings" ? { operationId: randomUUID() } : {}),
53
57
  ...(kind === "review" && input.mode === "manage" && typeof input.attemptId === "string" ? { operationId: input.attemptId } : {}),
54
58
  state: { cardId: base.id, kind, state: preparation.status === "failed" ? "closed" : "ready", revision: 0,
55
59
  createdAt: base.createdAt, expiresAt: base.expiresAt, input,
56
60
  ...(preparation.status === "failed" ? { reason: "failed", error: preparation.error } :
57
61
  preparation.data === undefined ? {} : { data: preparation.data }) } };
58
- this.options.records.create(record);
62
+ if (kind === "settings")
63
+ this.options.settings.create(record);
64
+ else
65
+ this.options.records.create(record);
59
66
  if (preparation.status === "failed")
60
67
  this.reportFailure(record, "input_preparation");
61
68
  const result = parsed === undefined ? await this.describe(record, false) : await this.submit({
@@ -64,8 +71,24 @@ export class CardStore {
64
71
  return { ...result, permission: token };
65
72
  }
66
73
  async read(input) { return this.describe(this.require(input), true); }
74
+ async readSettingsFile(input, limit) {
75
+ const record = this.require(input);
76
+ if (record.state.kind !== "settings" || !this.options.settings)
77
+ throw new CardError("This card does not provide a backup file.");
78
+ if (record.operationId !== input.operationId)
79
+ throw new CardError("This backup does not match the Settings operation.");
80
+ return this.options.settings.readFile(record, input.fileId, input.offset, limit, input.attemptId);
81
+ }
67
82
  async readSaved(cardId) { return (await this.describe(this.current(cardId), false)).snapshot; }
68
83
  async describe(record, uiObservation) {
84
+ if (record.state.kind === "settings") {
85
+ if (!this.options.settings)
86
+ throw new CardError("Settings cards are unavailable.");
87
+ const described = await this.options.settings.describe(record);
88
+ return { snapshot: cardSnapshotSchema.parse({ ...described.record.state, data: described.data,
89
+ inputRemainingMs: described.record.state.state === "ready" ? Math.max(0, Date.parse(described.record.state.expiresAt) - this.now().getTime()) : 0,
90
+ pollAfterMs: EXECUTION_POLLING_INTERVAL_SECONDS * 1000 }) };
91
+ }
69
92
  if (record.state.kind !== "connect" && record.state.kind !== "review")
70
93
  return this.response(record);
71
94
  if (!this.options.workflow)
@@ -83,6 +106,31 @@ export class CardStore {
83
106
  }
84
107
  async act(input) {
85
108
  const record = this.require(input);
109
+ if (record.state.kind === "settings") {
110
+ if (!this.options.settings)
111
+ throw new CardError("Settings cards are unavailable.");
112
+ const parsed = settingsActionSchema.safeParse(input.input);
113
+ if (!parsed.success)
114
+ return { ...await this.describe(record, false), error: { code: "invalid_card_input", message: "This Settings action is not valid." } };
115
+ if ((parsed.data.action === "settings_confirm" || parsed.data.action === "settings_download_backup") && record.acceptedInput !== undefined && isDeepStrictEqual(record.acceptedInput, parsed.data))
116
+ return this.describe(record, false);
117
+ if (input.revision !== record.state.revision && !isSettingsDownloadFileAction(parsed.data))
118
+ return { ...await this.describe(record, false), error: { code: "card_conflict", message: "This Settings selection changed. Review the current selection before continuing." } };
119
+ const kind = parsed.data.action === "settings_select" ? parsed.data.draft.kind : this.options.settings.operationKind(record);
120
+ try {
121
+ await this.options.settings.act(record, parsed.data);
122
+ }
123
+ catch (error) {
124
+ const invalid = settingsInputError(error, kind);
125
+ if (!(error instanceof SettingsConflict) && invalid === undefined)
126
+ throw error;
127
+ return { ...await this.describe(this.current(input.cardId), false), error: {
128
+ code: invalid === undefined ? "card_conflict" : "invalid_card_input",
129
+ message: invalid?.message ?? error.message
130
+ } };
131
+ }
132
+ return this.describe(this.current(input.cardId), false);
133
+ }
86
134
  if ((record.state.kind !== "connect" && record.state.kind !== "review") || !this.options.workflow) {
87
135
  return this.conflict(record, "This card does not accept wallet workflow actions.");
88
136
  }
@@ -112,7 +160,7 @@ export class CardStore {
112
160
  }
113
161
  async submit(input) {
114
162
  const record = this.require(input);
115
- if (record.state.kind === "connect" || record.state.kind === "review")
163
+ if (record.state.kind === "connect" || record.state.kind === "review" || record.state.kind === "settings")
116
164
  throw new CardError("This card does not accept data selections.");
117
165
  let parsed;
118
166
  try {
@@ -149,7 +197,6 @@ export class CardStore {
149
197
  }
150
198
  const complete = { ...admitted, ...("receiptDisplay" in projected ? { receiptDisplay: projected.receiptDisplay } : {}),
151
199
  state: { ...admitted.state, data: projected.data, state: "closed", reason: "completed", revision: admitted.state.revision + 1 } };
152
- // Do not report success or replay the source call if this commit fails.
153
200
  try {
154
201
  this.options.records.replace(admitted, complete);
155
202
  }
@@ -170,7 +217,7 @@ export class CardStore {
170
217
  try {
171
218
  this.options.logger?.error("Card read failed", { cardId: record.state.cardId, kind: record.state.kind, stage });
172
219
  }
173
- catch { /* Logging does not own the operation's state. */ }
220
+ catch { }
174
221
  }
175
222
  conflict(record, message) {
176
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
  }
@@ -16,7 +16,6 @@ import { isFinalSessionStatus } from "./status.js";
16
16
  import { parseSuiAddress } from "../suiAddress.js";
17
17
  import { cloneLocalSession, createLocalSessionBase, DEFAULT_SESSION_TTL_MS, isLocalSessionExpired, tokenMatchesHash } from "./localSession.js";
18
18
  import { SessionStoreError } from "./sessionErrors.js";
19
- import { SettingsSessionManager } from "./settingsSessions.js";
20
19
  const PRIVATE_DERIVED_REVIEW_FIELD_BINDINGS = [
21
20
  {
22
21
  field: "humanReadableReview",
@@ -51,7 +50,6 @@ export class LocalSessionStore {
51
50
  sessions;
52
51
  privateReviewArtifacts;
53
52
  ownerId;
54
- settings;
55
53
  ttlMs;
56
54
  clock;
57
55
  eventLog;
@@ -65,11 +63,6 @@ export class LocalSessionStore {
65
63
  this.privateReviewArtifacts = options.artifacts;
66
64
  this.ttlMs = options.ttlMs ?? DEFAULT_SESSION_TTL_MS;
67
65
  this.ownerId = options.ownerId ?? randomUUID();
68
- this.settings = new SettingsSessionManager({
69
- ttlMs: this.ttlMs,
70
- appendEventLog: (record) => this.appendEventLog(record),
71
- ...(options.settingsStore ? { recordStore: options.settingsStore } : {})
72
- });
73
66
  this.eventLog = options.eventLog ?? new NullEventLogSink();
74
67
  this.activityStore = options.activityStore;
75
68
  this.transactionMaterialStore = options.transactionMaterialStore;
@@ -111,8 +104,6 @@ export class LocalSessionStore {
111
104
  });
112
105
  return { session: cloneLocalSession(session), token };
113
106
  }
114
- // Snapshot access has no expiry, artifact reconciliation or external work.
115
- // Public consumers project this private record before exposing any fields.
116
107
  readReviewSession(id) {
117
108
  const session = this.sessions.get(id);
118
109
  return session ? cloneLocalSession(session) : undefined;
@@ -137,8 +128,6 @@ export class LocalSessionStore {
137
128
  transactionBytesBase64: Buffer.from(checked.transactionBytes).toString("base64") };
138
129
  }
139
130
  catch (error) {
140
- // Invalid evidence is a candidate for refresh, never permission to mutate
141
- // a potentially newer revision. Storage failures must still propagate.
142
131
  if (!(error instanceof LocalTransactionMaterialStoreError) && !(error instanceof ReviewEvidenceMismatch) && !(error instanceof ZodError))
143
132
  throw error;
144
133
  }
@@ -157,8 +146,6 @@ export class LocalSessionStore {
157
146
  this.recordEvaluationEvents(result.events);
158
147
  return cloneLocalSession(result.session);
159
148
  }
160
- // Preparation-only in-memory fixtures share the decision function. They do
161
- // not establish SQLite atomicity or wallet admission guarantees.
162
149
  const current = this.sessions.get(id);
163
150
  if (!current)
164
151
  return undefined;
@@ -193,8 +180,6 @@ export class LocalSessionStore {
193
180
  if (!session) {
194
181
  return false;
195
182
  }
196
- // Review sessions expose expired/final lifecycle states through read APIs after token validation.
197
- // Mutable methods own expiry transitions and return lifecycle-specific errors.
198
183
  return tokenMatchesHash(session.tokenHash, token);
199
184
  }
200
185
  async recordWalletConnected(id, account, now = new Date()) {
@@ -215,9 +200,6 @@ export class LocalSessionStore {
215
200
  throw new SessionStoreError("invalid_session_transition", `Review session already bound to a different account: ${id}`);
216
201
  }
217
202
  if (nextSession.status === "proposed") {
218
- // Explicit backend account binding follows the preparation lifecycle.
219
- // Active read context alone is not wallet approval; the workflow checks
220
- // the live wallet connection before invoking this operation.
221
203
  transition(nextSession, "awaiting_wallet");
222
204
  transition(nextSession, "wallet_connected");
223
205
  }
@@ -327,15 +309,6 @@ export class LocalSessionStore {
327
309
  }
328
310
  return session;
329
311
  }
330
- async createSettingsSession(now = new Date()) {
331
- return this.settings.create(now);
332
- }
333
- async getSettingsSession(id, now = new Date()) {
334
- return this.settings.get(id, now);
335
- }
336
- async validateSettingsToken(id, token, now = new Date()) {
337
- return this.settings.validateToken(id, token, now);
338
- }
339
312
  async prepareReviewedTransaction(id, planId, account, now = this.clock()) {
340
313
  const candidate = await this.inspectReview(id, now);
341
314
  if (!candidate)
@@ -360,7 +333,6 @@ export class LocalSessionStore {
360
333
  this.deleteReviewSessionTransactionMaterials(id);
361
334
  }
362
335
  this.sessions.clear();
363
- this.settings.clear();
364
336
  await this.appendEventLog({
365
337
  type: "local_sessions.invalidated",
366
338
  sessionId: "all",
@@ -373,7 +345,6 @@ export class LocalSessionStore {
373
345
  await this.eventLog.append(record);
374
346
  }
375
347
  catch (error) {
376
- // Event logs are optional audit/debug sinks. SQLite and in-memory session state remain authoritative.
377
348
  this.logger.error("event log append failed", {
378
349
  eventType: record.type,
379
350
  error: error instanceof Error ? error.message : String(error)
@@ -589,9 +560,6 @@ export class LocalSessionStore {
589
560
  }
590
561
  }
591
562
  }
592
- // In-memory session store: the orchestration above with Map-backed record and
593
- // artifact stores. Public name + constructor are unchanged so existing callers and
594
- // the session-store contract test keep working as the regression wall.
595
563
  export class InMemorySessionStore extends LocalSessionStore {
596
564
  constructor(options) {
597
565
  super({
@@ -0,0 +1,9 @@
1
+ export function settingsDownloadPath(fileId) {
2
+ return `.settings-staging/${fileId}.download`;
3
+ }
4
+ export function settingsBackupPath(operationId) { return `backups/${operationId}.json`; }
5
+ export function isSettingsDownloadFile(file) {
6
+ return file.role === "export" && file.relativePath === settingsDownloadPath(file.fileId);
7
+ }
8
+ export const SETTINGS_DOWNLOAD_FILE_SQL = "(role='export' AND relative_path=('.settings-staging/' || file_id || '.download'))";
9
+ export const SETTINGS_FILE_CLEANUP_SQL = `(invalidated=1 OR (role='export' AND NOT ${SETTINGS_DOWNLOAD_FILE_SQL}))`;
@@ -0,0 +1,20 @@
1
+ import { SETTINGS_IMPORT_MAX_SIZE } from "./settingsOperation.js";
2
+ export const MAX_JSON_BODY_BYTES = 64 * 1024;
3
+ export function settingsJsonSize(value) { return new TextEncoder().encode(JSON.stringify(value)).length; }
4
+ export function settingsRawChunkBudget(envelopeWithEmptyBase64) {
5
+ const room = MAX_JSON_BODY_BYTES - settingsJsonSize(envelopeWithEmptyBase64);
6
+ return room > 0 ? 3 * Math.floor(room / 4) : 0;
7
+ }
8
+ export function settingsUploadMessageBudget(message) {
9
+ if (!message || typeof message !== "object")
10
+ return undefined;
11
+ const envelope = structuredClone(message);
12
+ if (envelope.method !== "tools/call" || envelope.params?.name !== "ui.act_card" ||
13
+ envelope.params.arguments?.input?.action !== "settings_upload_chunk")
14
+ return undefined;
15
+ envelope.id = Number.MAX_SAFE_INTEGER;
16
+ envelope.params.arguments.revision = Number.MAX_SAFE_INTEGER;
17
+ envelope.params.arguments.input.offset = SETTINGS_IMPORT_MAX_SIZE;
18
+ envelope.params.arguments.input.chunkBase64 = "";
19
+ return settingsRawChunkBudget(envelope);
20
+ }