@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.
- package/README.md +21 -21
- package/dist/adapters/adapterPromptSurfaces.js +0 -20
- package/dist/adapters/deepbook/deepbookQuotePolicy.js +0 -4
- package/dist/adapters/deepbook/deepbookReviewEvidence.js +0 -9
- package/dist/adapters/deepbook/deepbookTransactionMaterialProducer.js +0 -9
- package/dist/adapters/intentPlanFactories.js +0 -6
- package/dist/core/action/contractNameRegistry.js +0 -53
- package/dist/core/action/forbiddenFields.js +0 -4
- package/dist/core/action/ptbVisualizationProducer.js +0 -3
- package/dist/core/action/receiptPtbGraph.js +0 -17
- package/dist/core/action/reviewTimeSimulationEvidence.js +0 -4
- package/dist/core/action/signableAdapterContract.js +0 -5
- package/dist/core/action/suiChainReceiptMapping.js +0 -18
- package/dist/core/action/suiChainReceiptReader.js +1 -21
- package/dist/core/action/suiChainReceiptVerifier.js +0 -10
- package/dist/core/action/transactionFunding.js +0 -7
- package/dist/core/action/types.js +0 -1
- package/dist/core/action/walletReviewContractAssembler.js +0 -2
- package/dist/core/activity/localDataService.js +65 -52
- package/dist/core/activity/localDataValidation.js +1 -3
- package/dist/core/activity/sqliteActivityStore.js +24 -24
- package/dist/core/activity/sqliteActivityStoreRows.js +0 -2
- package/dist/core/activity/sqliteActivityStoreSchema.js +81 -14
- package/dist/core/preferences/sqlitePreferencesRepository.js +5 -3
- package/dist/core/read/deepbookReadHelpers.js +0 -2
- package/dist/core/read/deepbookRegistry.js +0 -19
- package/dist/core/read/deepbookUsdcChartService.js +0 -2
- package/dist/core/read/readCardResult.js +0 -2
- package/dist/core/read/readService.js +0 -19
- package/dist/core/read/readServiceTypes.js +0 -2
- package/dist/core/session/cardSession.js +1 -5
- package/dist/core/session/cardSessionStore.js +54 -7
- package/dist/core/session/reviewValidity.js +0 -3
- package/dist/core/session/sessionRecordStore.js +0 -1
- package/dist/core/session/sessionStore.js +0 -32
- package/dist/core/session/settingsFileIdentity.js +9 -0
- package/dist/core/session/settingsFileProtocol.js +20 -0
- package/dist/core/session/settingsFileStore.js +337 -0
- package/dist/core/session/settingsInputError.js +32 -0
- package/dist/core/session/settingsOperation.js +220 -0
- package/dist/core/session/settingsPreparationSql.js +13 -0
- package/dist/core/session/settingsWorkflow.js +1183 -0
- package/dist/core/session/sqliteCardStore.js +0 -2
- package/dist/core/session/sqliteSessionStore.js +0 -51
- package/dist/core/session/sqliteSettingsWorkflowStore.js +453 -0
- package/dist/core/session/sqliteTransactionMaterialStore.js +0 -11
- package/dist/core/session/sqliteWalletWorkflowStore.js +160 -66
- package/dist/core/session/status.js +2 -4
- package/dist/core/session/transactionMaterialStore.js +0 -4
- package/dist/core/session/transactionRequest.js +0 -4
- package/dist/core/session/walletConnection.js +26 -10
- package/dist/core/session/walletWorkflow.js +70 -30
- package/dist/core/session/workflowState.js +14 -14
- package/dist/core/session/workflowView.js +18 -8
- package/dist/core/suiAddress.js +0 -5
- package/dist/core/suiEndpoint.js +0 -3
- package/dist/mcp/prompts.js +0 -12
- package/dist/mcp/responseGuidance.js +10 -5
- package/dist/mcp/schemas.js +0 -3
- package/dist/mcp/serverInfo.js +3 -4
- package/dist/mcp/toolNames.js +2 -2
- package/dist/mcp/tools/account/index.js +1 -19
- package/dist/mcp/tools/action/prepareSuiActionReview.js +0 -6
- package/dist/mcp/tools/read/readToolHelpers.js +5 -4
- package/dist/mcp/tools/read/serverStatusTools.js +0 -1
- package/dist/mcp/tools/session/executionResultTools.js +2 -2
- package/dist/mcp/tools/session/shared.js +1 -2
- package/dist/mcp/tools/session/statusTools.js +4 -5
- package/dist/mcp/tools/settings/index.js +8 -28
- package/dist/mcp-app/account.html +14 -14
- package/dist/mcp-app/account.notices.txt +1 -1
- package/dist/mcp-app/chart.html +9 -9
- package/dist/mcp-app/chart.notices.txt +1 -1
- package/dist/mcp-app/connect.html +14 -14
- package/dist/mcp-app/connect.notices.txt +1 -1
- package/dist/mcp-app/receipt.html +1298 -1463
- package/dist/mcp-app/receipt.notices.txt +226 -1204
- package/dist/mcp-app/review.html +1298 -1463
- package/dist/mcp-app/review.notices.txt +226 -1204
- package/dist/mcp-app/settings.html +3133 -0
- package/dist/mcp-app/settings.notices.txt +973 -0
- package/dist/mcp-ui/contracts.js +8 -2
- package/dist/mcp-ui/readCards.js +1 -1
- package/dist/mcp-ui/tools.js +34 -4
- package/dist/review-server/server.js +3 -51
- package/dist/runtime/application.js +41 -14
- package/dist/runtime/config.js +0 -2
- package/dist/runtime/localSettingsService.js +23 -2
- package/dist/runtime/logger.js +0 -2
- package/dist/runtime/reviewServerAcquire.js +1 -18
- package/dist/runtime/runtimeTermination.js +1 -4
- package/dist/runtime/shared/authenticatedFetch.js +2 -4
- package/dist/runtime/shared/control.js +4 -7
- package/dist/runtime/shared/dataAccess.js +0 -2
- package/dist/runtime/shared/http.js +14 -0
- package/dist/runtime/shared/mcpHttp.js +6 -6
- package/dist/runtime/shared/ownerLease.js +0 -4
- package/dist/runtime/shared/server.js +1 -2
- package/dist/runtime/shared/stdio.js +2 -5
- package/dist/runtime/smokeMainnetRead.js +0 -3
- package/dist/runtime/smokeReview.js +0 -4
- package/dist/runtime/start.js +0 -2
- package/dist/runtime/suiTransactionGraphqlMapping.js +0 -2
- package/dist/runtime/suiTransactionGraphqlQueries.js +0 -3
- package/dist/runtime/walletConnectConfig.js +0 -2
- package/dist/runtime/walletConnectStorage.js +2 -10
- package/dist/runtime/walletConnectTransport.js +0 -11
- package/dist/runtime/walletSdkChildRuntime.js +2 -6
- package/dist/runtime/walletSdkIpc.js +2 -1
- package/dist/runtime/walletSdkProcess.js +3 -8
- package/docs/AGENT_BEHAVIOR.md +45 -15
- package/docs/LOCAL_DB_ARCHITECTURE.md +145 -22
- package/docs/MCP_SETUP.md +99 -39
- package/docs/MCP_TOOLS.md +288 -24
- package/docs/SDK_API.md +63 -10
- package/docs/UTILITY_INDEX.md +9 -6
- package/docs/WALLET_CONNECTION.md +32 -10
- package/package.json +22 -8
- package/dist/core/activity/schemaVersion.js +0 -2
- package/dist/core/session/keyedRecordStore.js +0 -18
- package/dist/core/session/settingsSession.js +0 -1
- package/dist/core/session/settingsSessions.js +0 -45
- package/dist/review-app/brand-dark.svg +0 -1
- package/dist/review-app/brand-light.svg +0 -1
- package/dist/review-app/favicon.svg +0 -1
- package/dist/review-app/settings.css +0 -1
- package/dist/review-app/settings.js +0 -1
- package/dist/review-app/ui.css +0 -809
- package/dist/review-server/assets.js +0 -48
- package/dist/review-server/html.js +0 -45
- package/dist/review-server/http.js +0 -47
- package/dist/review-server/middleware/reviewToken.js +0 -7
- package/dist/review-server/settingsApi.js +0 -158
- package/docs/AGENT_DEVELOPMENT_POLICY.md +0 -815
- package/docs/FRONTEND_POLICY.md +0 -534
- package/docs/golden-scenarios/BEHAVIOR_MATRIX.md +0 -166
- package/docs/golden-scenarios/INTENT_EVIDENCE_GOLDEN_ANSWERS.md +0 -161
- 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
|
-
|
|
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 {
|
|
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" };
|
|
@@ -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
|
+
}
|