@zktx.io/sui-mcp 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/adapters/deepbook/deepbookTransactionMaterialProducer.js +3 -3
- package/dist/core/activity/localDataService.js +71 -52
- package/dist/core/activity/localDataValidation.js +1 -3
- package/dist/core/activity/sqliteActivityStore.js +24 -16
- package/dist/core/activity/sqliteActivityStoreSchema.js +83 -12
- package/dist/core/preferences/sqlitePreferencesRepository.js +5 -3
- package/dist/core/session/cardSession.js +1 -1
- package/dist/core/session/cardSessionStore.js +53 -3
- package/dist/core/session/sessionStore.js +0 -17
- package/dist/core/session/settingsFileIdentity.js +11 -0
- package/dist/core/session/settingsFileProtocol.js +25 -0
- package/dist/core/session/settingsFileStore.js +346 -0
- package/dist/core/session/settingsInputError.js +34 -0
- package/dist/core/session/settingsOperation.js +224 -0
- package/dist/core/session/settingsPreparationSql.js +15 -0
- package/dist/core/session/settingsWorkflow.js +1219 -0
- package/dist/core/session/sqliteSessionStore.js +0 -40
- package/dist/core/session/sqliteSettingsWorkflowStore.js +463 -0
- package/dist/core/session/sqliteWalletWorkflowStore.js +164 -41
- package/dist/core/session/status.js +2 -2
- package/dist/core/session/walletConnection.js +27 -8
- package/dist/core/session/walletWorkflow.js +73 -15
- package/dist/core/session/workflowState.js +16 -8
- package/dist/core/session/workflowView.js +18 -2
- package/dist/mcp/responseGuidance.js +10 -5
- 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/read/readToolHelpers.js +5 -4
- package/dist/mcp/tools/session/executionResultTools.js +2 -2
- package/dist/mcp/tools/session/shared.js +1 -0
- package/dist/mcp/tools/session/statusTools.js +4 -3
- 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 +33 -2
- package/dist/review-server/server.js +5 -51
- package/dist/runtime/application.js +41 -14
- package/dist/runtime/localSettingsService.js +26 -2
- package/dist/runtime/shared/authenticatedFetch.js +6 -2
- package/dist/runtime/shared/control.js +5 -6
- package/dist/runtime/shared/http.js +14 -0
- package/dist/runtime/shared/mcpHttp.js +9 -4
- package/dist/runtime/shared/server.js +1 -1
- package/dist/runtime/smokeMainnetRead.js +0 -1
- package/dist/runtime/walletConnectStorage.js +2 -4
- package/dist/runtime/walletSdkChildRuntime.js +2 -1
- package/dist/runtime/walletSdkIpc.js +2 -1
- package/dist/runtime/walletSdkProcess.js +3 -2
- package/docs/AGENT_BEHAVIOR.md +44 -10
- package/docs/AGENT_DEVELOPMENT_POLICY.md +16 -12
- package/docs/FRONTEND_POLICY.md +105 -28
- package/docs/LOCAL_DB_ARCHITECTURE.md +143 -20
- package/docs/MCP_SETUP.md +99 -39
- package/docs/MCP_TOOLS.md +273 -24
- package/docs/SDK_API.md +51 -10
- package/docs/UTILITY_INDEX.md +9 -6
- package/docs/WALLET_CONNECTION.md +30 -9
- package/package.json +13 -7
- 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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { workflowConnectionEligibility } from "./workflowState.js";
|
|
1
2
|
import { SessionStoreError } from "./sessionErrors.js";
|
|
2
3
|
import { Transaction } from "@mysten/sui/transactions";
|
|
3
4
|
import { verifyTransactionSignature } from "@mysten/sui/verify";
|
|
@@ -18,7 +19,11 @@ export class WalletWorkflow {
|
|
|
18
19
|
observing = new Map();
|
|
19
20
|
preparing = new Set();
|
|
20
21
|
pendingWrites = new Map();
|
|
22
|
+
// Child exit ends child I/O, not verification or submit work already running
|
|
23
|
+
// in this parent. The owning signature continuation alone ends that work.
|
|
24
|
+
parentSignatureWork = new Set();
|
|
21
25
|
eventListener;
|
|
26
|
+
settingsRecovery;
|
|
22
27
|
constructor(options) {
|
|
23
28
|
this.options = options;
|
|
24
29
|
}
|
|
@@ -48,6 +53,7 @@ export class WalletWorkflow {
|
|
|
48
53
|
if (this.started)
|
|
49
54
|
throw new WorkflowConflict("The wallet service has already started.");
|
|
50
55
|
this.started = true;
|
|
56
|
+
this.options.records.setWalletRunId(this.options.runtime.runId);
|
|
51
57
|
this.options.records.recover(this.now());
|
|
52
58
|
this.bindEvents();
|
|
53
59
|
this.options.runtime.start((event) => this.eventListener?.(event));
|
|
@@ -61,9 +67,11 @@ export class WalletWorkflow {
|
|
|
61
67
|
return;
|
|
62
68
|
const runtime = this.options.runtime;
|
|
63
69
|
if (event.type === "snapshot") {
|
|
64
|
-
this.options.records.applyWalletObservation(event.snapshot, event.previous, event.ready);
|
|
65
|
-
if (event.ready)
|
|
70
|
+
this.options.records.applyWalletObservation(event.snapshot, event.previous, event.ready, () => this.settingsRecovery?.runtimeEvent(event));
|
|
71
|
+
if (event.ready) {
|
|
66
72
|
runtime.publishReady(event.runId);
|
|
73
|
+
this.settingsRecovery?.readyPublished(event.runId);
|
|
74
|
+
}
|
|
67
75
|
}
|
|
68
76
|
else if (event.type === "stage") {
|
|
69
77
|
this.options.records.publishWalletState();
|
|
@@ -71,8 +79,38 @@ export class WalletWorkflow {
|
|
|
71
79
|
else if (event.type === "lost") {
|
|
72
80
|
this.pairings.clear();
|
|
73
81
|
this.retainWrite({ kind: "runtime_failure", runId: event.runId, observedAt: this.now().toISOString() });
|
|
82
|
+
this.settingsRecovery?.runtimeEvent(event);
|
|
83
|
+
}
|
|
84
|
+
else if (event.type === "exit") {
|
|
85
|
+
this.retainWrite({ kind: "runtime_exit", runId: event.runId, observedAt: this.now().toISOString(), parentProcessing: [...this.parentSignatureWork] });
|
|
74
86
|
}
|
|
75
87
|
}
|
|
88
|
+
attachSettingsRecovery(recovery) {
|
|
89
|
+
if (this.settingsRecovery)
|
|
90
|
+
throw new Error("Settings recovery is already attached.");
|
|
91
|
+
this.settingsRecovery = recovery;
|
|
92
|
+
}
|
|
93
|
+
recoveryContext() {
|
|
94
|
+
this.synchronizeWalletState();
|
|
95
|
+
return this.currentRecoveryFacts();
|
|
96
|
+
}
|
|
97
|
+
currentRecoveryFacts() {
|
|
98
|
+
return { availability: this.walletAvailability(), impact: this.options.records.recoveryImpact(this.options.runtime.runId) };
|
|
99
|
+
}
|
|
100
|
+
blockSettingsAdmissionFailure() {
|
|
101
|
+
const runId = this.options.runtime.runId;
|
|
102
|
+
this.options.runtime.block("wallet_state_unavailable");
|
|
103
|
+
this.retainWrite({ kind: "runtime_failure", runId, observedAt: this.now().toISOString() });
|
|
104
|
+
}
|
|
105
|
+
fenceForSettings(runId) { this.assertCurrent(); this.options.runtime.fence(runId); }
|
|
106
|
+
replaceForSettings(runId, reserve) {
|
|
107
|
+
this.assertCurrent();
|
|
108
|
+
return this.options.runtime.replace(runId, (nextRunId) => {
|
|
109
|
+
this.synchronizeWalletState();
|
|
110
|
+
reserve(nextRunId);
|
|
111
|
+
this.options.records.setWalletRunId(nextRunId);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
76
114
|
dataReplaced() {
|
|
77
115
|
this.pairings.clear();
|
|
78
116
|
this.pendingWrites.clear();
|
|
@@ -99,7 +137,7 @@ export class WalletWorkflow {
|
|
|
99
137
|
catch {
|
|
100
138
|
return;
|
|
101
139
|
}
|
|
102
|
-
const key = completion.kind === "runtime_failure" ?
|
|
140
|
+
const key = completion.kind === "runtime_failure" || completion.kind === "runtime_exit" ? `${completion.kind}:${completion.runId}` :
|
|
103
141
|
completion.kind === "request" ? `request:${completion.id}:${completion.field}` : `connection:${completion.cardId}:${completion.runId}`;
|
|
104
142
|
if (!this.pendingWrites.has(key))
|
|
105
143
|
this.pendingWrites.set(key, completion);
|
|
@@ -115,7 +153,9 @@ export class WalletWorkflow {
|
|
|
115
153
|
this.options.records.settle(completion.id, completion.field, false);
|
|
116
154
|
else if (completion.kind === "connection")
|
|
117
155
|
this.options.records.settleConnection(completion.id, completion);
|
|
118
|
-
else if (completion.
|
|
156
|
+
else if (completion.kind === "runtime_exit")
|
|
157
|
+
this.options.records.recordRunExit(completion.runId, completion.parentProcessing);
|
|
158
|
+
else
|
|
119
159
|
this.options.records.recordWalletFailure(completion);
|
|
120
160
|
this.pendingWrites.delete(key);
|
|
121
161
|
}
|
|
@@ -137,6 +177,8 @@ export class WalletWorkflow {
|
|
|
137
177
|
// read recorded facts. Retain failed repairs for the next applicable read.
|
|
138
178
|
if (failure && walletDependent)
|
|
139
179
|
throw failure;
|
|
180
|
+
if (walletDependent)
|
|
181
|
+
this.settingsRecovery?.synchronize();
|
|
140
182
|
if (walletDependent)
|
|
141
183
|
this.options.runtime.synchronize();
|
|
142
184
|
}
|
|
@@ -159,16 +201,27 @@ export class WalletWorkflow {
|
|
|
159
201
|
}
|
|
160
202
|
return { status: "ready" };
|
|
161
203
|
}
|
|
162
|
-
|
|
204
|
+
reconcileConnectionContext() {
|
|
163
205
|
this.assertCurrent();
|
|
164
206
|
this.synchronizeWalletState();
|
|
165
207
|
this.recoverDisconnects();
|
|
166
|
-
const state = this.options.records.
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
208
|
+
const state = this.options.records.reconcileAndEvaluate({ purpose: "current_wallet", walletAvailability: this.walletAvailability(), walletObservation: this.observation() });
|
|
209
|
+
return { connections: state.connections, connectionConflict: state.connectionConflict, walletAvailability: state.walletAvailability,
|
|
210
|
+
walletObservation: state.walletObservation, manageableConnectionIds: state.manageableConnectionIds,
|
|
211
|
+
connectionReconciliationRequired: state.connectionReconciliationRequired, assetReadAccount: state.assetReadAccount ?? { status: "address_required" } };
|
|
212
|
+
}
|
|
213
|
+
// Diagnostic facts are collected without maintenance. Reconciliation and
|
|
214
|
+
// admission own durable transitions and cannot use this snapshot as authority.
|
|
215
|
+
currentConnectionContext() {
|
|
216
|
+
this.assertCurrent();
|
|
217
|
+
const facts = this.options.records.readConnectionFacts(this.now()), availability = this.walletAvailability();
|
|
218
|
+
const { selection: _selection, ...eligibility } = workflowConnectionEligibility({ ...facts, walletAvailability: availability });
|
|
219
|
+
// The shared projection gates qualification on published availability;
|
|
220
|
+
// a memory snapshot alone does not prove its journal/result has committed.
|
|
221
|
+
return { connections: facts.connections, walletAvailability: availability, walletObservation: facts.walletObservation,
|
|
222
|
+
connectionConflict: eligibility.connectionConflict, manageableConnectionIds: eligibility.manageableConnectionIds,
|
|
223
|
+
connectionReconciliationRequired: eligibility.connectionReconciliationRequired,
|
|
224
|
+
assetReadAccount: eligibility.assetReadAccount ?? { status: "address_required" } };
|
|
172
225
|
}
|
|
173
226
|
recoverDisconnects() {
|
|
174
227
|
const available = this.walletAvailability().status === "available";
|
|
@@ -194,7 +247,9 @@ export class WalletWorkflow {
|
|
|
194
247
|
this.options.records.failPreparation(id, preparation, this.now());
|
|
195
248
|
const candidate = await this.options.sessions.inspectReview(id, this.now());
|
|
196
249
|
this.assertCurrent();
|
|
197
|
-
|
|
250
|
+
if (!candidate)
|
|
251
|
+
throw new SessionStoreError("session_not_found", "The saved review is unavailable.");
|
|
252
|
+
const evaluated = this.options.records.reconcileAndEvaluate({ purpose: "review", reviewSessionId: id, candidate, expectedCard, readTarget,
|
|
198
253
|
walletAvailability: this.walletAvailability(), walletObservation: this.observation(), uiObservation });
|
|
199
254
|
this.options.sessions.recordEvaluationEvents(evaluated.events);
|
|
200
255
|
return evaluated;
|
|
@@ -216,15 +271,16 @@ export class WalletWorkflow {
|
|
|
216
271
|
if (!state.session)
|
|
217
272
|
return undefined;
|
|
218
273
|
return { session: state.session, request: state.request, hasReviewInput: state.hasReviewInput, connectionConflict: state.connectionConflict, walletAvailability: state.walletAvailability, walletObservation: this.observation(),
|
|
274
|
+
connectionReconciliationRequired: state.connectionReconciliationRequired,
|
|
219
275
|
progress: reviewProgress(!!state.session.preparationId, state.request, state.walletAvailability, { stopped: state.authority?.observation_stopped === 1, pending: state.authority?.lookup_pending === 1 }) };
|
|
220
276
|
}
|
|
221
277
|
async describe(record, uiObservation = false) {
|
|
222
278
|
this.assertCurrent();
|
|
223
279
|
if (record.state.kind === "connect")
|
|
224
|
-
this.
|
|
280
|
+
this.reconcileConnectionContext();
|
|
225
281
|
const evaluated = record.state.kind === "review"
|
|
226
282
|
? await this.evaluateReview(String(record.state.input.reviewSessionId), record, uiObservation)
|
|
227
|
-
: this.options.records.
|
|
283
|
+
: this.options.records.reconcileAndEvaluate({ purpose: "current_wallet", expectedCard: record, walletAvailability: this.walletAvailability(), walletObservation: this.observation() });
|
|
228
284
|
this.assertCurrent();
|
|
229
285
|
if (record.state.kind === "connect") {
|
|
230
286
|
const data = projectConnectionView(evaluated);
|
|
@@ -444,6 +500,7 @@ export class WalletWorkflow {
|
|
|
444
500
|
await this.performSignature(request, connectionId, bytesBase64, run);
|
|
445
501
|
}
|
|
446
502
|
finally {
|
|
503
|
+
this.parentSignatureWork.delete(request.attemptId);
|
|
447
504
|
this.completeCallback({ kind: "request", id: request.attemptId, field: "sdk_pending" });
|
|
448
505
|
}
|
|
449
506
|
}
|
|
@@ -458,6 +515,7 @@ export class WalletWorkflow {
|
|
|
458
515
|
this.assertWalletAvailable(run);
|
|
459
516
|
response = await run.sign({ topic: connection.topic, account: request.account, transactionBytesBase64: bytesBase64,
|
|
460
517
|
sessionVersion: proof.observed.version }, request.attemptId);
|
|
518
|
+
this.parentSignatureWork.add(request.attemptId);
|
|
461
519
|
this.assertWalletAvailable(run);
|
|
462
520
|
}
|
|
463
521
|
catch {
|
|
@@ -581,7 +639,7 @@ export class WalletWorkflow {
|
|
|
581
639
|
this.recoverDisconnects();
|
|
582
640
|
const rows = [];
|
|
583
641
|
for (const card of this.options.records.pendingConnectionCards(this.now())) {
|
|
584
|
-
const evaluated = this.options.records.
|
|
642
|
+
const evaluated = this.options.records.reconcileAndEvaluate({ purpose: "current_wallet", expectedCard: card, walletAvailability: this.walletAvailability(), walletObservation: this.observation() });
|
|
585
643
|
if (evaluated.record?.state.state === "closed")
|
|
586
644
|
continue;
|
|
587
645
|
const data = projectConnectionView(evaluated);
|
|
@@ -15,6 +15,18 @@ export function reviewPreparationAccount(boundAccount, activeAccount, selectedAc
|
|
|
15
15
|
message: "The selected account has changed since this request was made." };
|
|
16
16
|
return { allowed: true, account: activeAccount };
|
|
17
17
|
}
|
|
18
|
+
export function workflowConnectionEligibility(facts) {
|
|
19
|
+
const selection = walletConnectionSelection(facts.connections, Date.parse(facts.evaluatedAt), facts.connectionPresence, facts.activeAccount ? { address: facts.activeAccount, walletId: facts.activeAccountConnectionId } : undefined);
|
|
20
|
+
const available = facts.walletAvailability.status === "available", dependent = facts.walletDependent !== false;
|
|
21
|
+
const usableConnectionId = available && dependent ? selection.connection?.connectionId : undefined;
|
|
22
|
+
const assetReadAccount = !dependent ? undefined
|
|
23
|
+
: usableConnectionId ? selection.assetReadAccount : { status: "address_required" };
|
|
24
|
+
return { selection, usableConnectionId, assetReadAccount, connectionConflict: selection.conflict,
|
|
25
|
+
manageableConnectionIds: available ? selection.manageableConnectionIds : [],
|
|
26
|
+
// A pending memory snapshot is not a published SDK observation. Startup
|
|
27
|
+
// and state-sync failures retain their own result and neutral diagnosis.
|
|
28
|
+
connectionReconciliationRequired: available && dependent ? selection.connectionReconciliationRequired : undefined };
|
|
29
|
+
}
|
|
18
30
|
// Shared domain eligibility. The DB supplies current facts at its decision
|
|
19
31
|
// boundary. Returned choices are advisory; admission still binds concrete input.
|
|
20
32
|
export function workflowEligibility(facts) {
|
|
@@ -27,12 +39,8 @@ export function workflowEligibility(facts) {
|
|
|
27
39
|
? Math.min(cardRemaining, Math.max(0, Date.parse(session.expiresAt) - at)) : cardRemaining;
|
|
28
40
|
const inputAvailable = remaining > 0 && record?.state.state === "ready" && record.acceptedInput === undefined;
|
|
29
41
|
const allowedActions = [];
|
|
30
|
-
const selection =
|
|
31
|
-
|
|
32
|
-
const usableConnectionId = walletAvailability.status === "available" && facts.walletDependent !== false
|
|
33
|
-
? selection.connection?.connectionId : undefined;
|
|
34
|
-
const assetReadAccount = facts.walletDependent === false ? undefined
|
|
35
|
-
: usableConnectionId ? selection.assetReadAccount : { status: "address_required" };
|
|
42
|
+
const { selection, ...connectionEligibility } = workflowConnectionEligibility(facts);
|
|
43
|
+
const { assetReadAccount } = connectionEligibility;
|
|
36
44
|
let preparationIssue;
|
|
37
45
|
if (record?.scope === "connect") {
|
|
38
46
|
if (inputAvailable) {
|
|
@@ -40,7 +48,7 @@ export function workflowEligibility(facts) {
|
|
|
40
48
|
if (walletAvailability.status === "available") {
|
|
41
49
|
if (["connect", "manage"].includes(String(record.state.input.intent)) && selection.pairingAllowed)
|
|
42
50
|
allowedActions.push("connect");
|
|
43
|
-
if (
|
|
51
|
+
if (selection.manageableConnectionIds.length)
|
|
44
52
|
allowedActions.push("disconnect");
|
|
45
53
|
if (selection.connection)
|
|
46
54
|
allowedActions.push("use_account");
|
|
@@ -74,7 +82,7 @@ export function workflowEligibility(facts) {
|
|
|
74
82
|
session.status === "ready_for_wallet_review" && (!request || request.reviewRevision !== session.reviewRevision)
|
|
75
83
|
? session.reviewState?.humanReadableReview?.freshness.expiresAt : undefined;
|
|
76
84
|
const nextRead = expiry && session ? Math.min(remaining, Date.parse(session.expiresAt) - at, Date.parse(expiry) - at) : undefined;
|
|
77
|
-
return { allowedActions, preparationIssue,
|
|
85
|
+
return { allowedActions, preparationIssue, ...connectionEligibility,
|
|
78
86
|
actionRemainingMs: remaining, inputRemainingMs: inputAvailable ? remaining : 0,
|
|
79
87
|
...(nextRead !== undefined && nextRead > 0 ? { nextStateReadAfterMs: nextRead } : {}) };
|
|
80
88
|
}
|
|
@@ -37,6 +37,8 @@ export const workflowViewSchema = z.object({
|
|
|
37
37
|
walletObservation: walletObservationSchema.optional(),
|
|
38
38
|
runtimeRecovery: walletRecoverySchema.optional(),
|
|
39
39
|
connectionConflict: connectionConflictSchema.optional(),
|
|
40
|
+
manageableConnectionIds: z.array(z.string().min(1)),
|
|
41
|
+
connectionReconciliationRequired: z.literal(true).optional(),
|
|
40
42
|
usableConnectionId: z.string().min(1).optional(),
|
|
41
43
|
assetReadAccount: assetReadAccountSchema.optional(),
|
|
42
44
|
acceptedWalletRunId: walletRunIdSchema.optional(),
|
|
@@ -60,10 +62,19 @@ export const workflowViewSchema = z.object({
|
|
|
60
62
|
}).strict().superRefine((view, context) => {
|
|
61
63
|
const connection = view.connections.find((item) => item.connectionId === view.usableConnectionId);
|
|
62
64
|
if (view.usableConnectionId && (!connection || connection.status !== "connected" ||
|
|
63
|
-
view.walletAvailability.status !== "available" || view.connectionConflict ||
|
|
65
|
+
view.walletAvailability.status !== "available" || view.connectionConflict || view.connectionReconciliationRequired ||
|
|
64
66
|
view.connections.some((item) => item.pendingAction || item.status === "awaiting_approval"))) {
|
|
65
67
|
context.addIssue({ code: "custom", path: ["usableConnectionId"], message: "The usable connection must match current connection facts." });
|
|
66
68
|
}
|
|
69
|
+
if (new Set(view.manageableConnectionIds).size !== view.manageableConnectionIds.length ||
|
|
70
|
+
view.manageableConnectionIds.some((id) => !view.connections.some((item) => item.connectionId === id &&
|
|
71
|
+
!item.pendingAction && item.status !== "awaiting_approval"))) {
|
|
72
|
+
context.addIssue({ code: "custom", path: ["manageableConnectionIds"], message: "Management must name distinct recorded targets without pending operations." });
|
|
73
|
+
}
|
|
74
|
+
if (view.connectionReconciliationRequired && (view.usableConnectionId || view.assetReadAccount?.status === "available" ||
|
|
75
|
+
view.allowedActions.some((action) => ["connect", "use_account", "prepare_review", "request_signature"].includes(action)))) {
|
|
76
|
+
context.addIssue({ code: "custom", path: ["connectionReconciliationRequired"], message: "Unrecorded SDK connections cannot authorize connection or account use." });
|
|
77
|
+
}
|
|
67
78
|
if (view.assetReadAccount?.status === "available" && (!connection ||
|
|
68
79
|
!connection.accounts.includes(view.assetReadAccount.account) || view.activeAccount !== view.assetReadAccount.account)) {
|
|
69
80
|
context.addIssue({ code: "custom", path: ["assetReadAccount"], message: "The default address must belong to the usable connection and stored selection." });
|
|
@@ -104,7 +115,10 @@ function nextStateRead(input) {
|
|
|
104
115
|
// These projections do not read clocks, storage, or admission rules. The
|
|
105
116
|
// evaluated facts and choices were collected together by the database owner.
|
|
106
117
|
export function projectConnectionView(input) {
|
|
107
|
-
const {
|
|
118
|
+
const { walletAvailability } = input;
|
|
119
|
+
const stored = input.record?.state.data;
|
|
120
|
+
const connection = input.record?.acceptedInput?.action === "disconnect" && stored?.disconnectOutcome
|
|
121
|
+
? connectionViewSchema.parse(stored.disconnectOutcome) : input.connection;
|
|
108
122
|
const readAfter = nextStateRead(input);
|
|
109
123
|
const progress = input.runtimeRecovery ? { status: "phase" in input.runtimeRecovery ? "waiting" : "idle" } :
|
|
110
124
|
workflowProgress(connection?.status === "awaiting_approval" || connection?.pendingAction === "disconnect", true, walletAvailability);
|
|
@@ -114,6 +128,7 @@ export function projectConnectionView(input) {
|
|
|
114
128
|
? { automaticAction: { action: "connect", walletRunId: walletAvailability.walletRunId } } : {}),
|
|
115
129
|
runtimeRecovery: input.runtimeRecovery, walletObservation: input.walletObservation,
|
|
116
130
|
connectionConflict: input.connectionConflict, usableConnectionId: input.usableConnectionId, assetReadAccount: input.assetReadAccount,
|
|
131
|
+
manageableConnectionIds: input.manageableConnectionIds, connectionReconciliationRequired: input.connectionReconciliationRequired,
|
|
117
132
|
connectionAction: ["connect", "disconnect"].includes(String(input.record?.acceptedInput?.action)) ? input.record?.acceptedInput?.action : undefined,
|
|
118
133
|
acceptedWalletRunId: input.record?.acceptedInput?.walletRunId,
|
|
119
134
|
...(readAfter === undefined ? {} : { nextStateReadAfterMs: readAfter }),
|
|
@@ -133,6 +148,7 @@ export function projectReviewView(input) {
|
|
|
133
148
|
return workflowViewSchema.parse({ kind: "review", mode: record.scope === "review_manage" ? "review_manage" : "review",
|
|
134
149
|
allowedActions: input.allowedActions, actionRemainingMs: input.actionRemainingMs, observe: progress.status === "waiting", progress, walletAvailability,
|
|
135
150
|
connections: input.connections, connectionConflict: input.connectionConflict, usableConnectionId: input.usableConnectionId,
|
|
151
|
+
manageableConnectionIds: input.manageableConnectionIds, connectionReconciliationRequired: input.connectionReconciliationRequired,
|
|
136
152
|
assetReadAccount: input.assetReadAccount, activeAccount: input.activeAccount, boundary: REVIEW_BOUNDARY, receipt: input.receipt,
|
|
137
153
|
walletObservation: input.walletObservation,
|
|
138
154
|
acceptedWalletRunId: record.acceptedInput?.walletRunId,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EXTERNAL_PROPOSAL_SETTLEMENT_TOKEN_SELECTION_UNSUPPORTED_CLAIM_ID } from "../core/proposal/types.js";
|
|
2
2
|
import { TOOL_NAMES } from "./toolNames.js";
|
|
3
|
-
export function interactionStatusUserAnswerUse(hasWalletObservation = false, hasConnectionConflict = false) {
|
|
3
|
+
export function interactionStatusUserAnswerUse(hasWalletObservation = false, hasConnectionConflict = false, reconciliationRequired = false) {
|
|
4
4
|
return {
|
|
5
5
|
canAnswer: [
|
|
6
6
|
"stored_active_account_read_context",
|
|
@@ -8,6 +8,7 @@ export function interactionStatusUserAnswerUse(hasWalletObservation = false, has
|
|
|
8
8
|
"connection_qualified_default_asset_account",
|
|
9
9
|
"pending_local_wallet_connection_interactions",
|
|
10
10
|
...(hasConnectionConflict ? ["multiple_saved_connections_require_user_targeted_disconnection"] : []),
|
|
11
|
+
...(reconciliationRequired ? ["unrecorded_service_connection_requires_wallet_removal_and_confirmed_absence"] : []),
|
|
11
12
|
"pending_local_review_interactions",
|
|
12
13
|
"wallet_operation_availability_separate_from_stored_transaction_facts"
|
|
13
14
|
],
|
|
@@ -24,9 +25,11 @@ export function interactionStatusUserAnswerUse(hasWalletObservation = false, has
|
|
|
24
25
|
"complete_wallet_history",
|
|
25
26
|
"profit_or_pnl"
|
|
26
27
|
],
|
|
27
|
-
preconditionFields: ["walletAvailability", "assetReadAccount", ...(hasConnectionConflict ? ["connectionConflict"] : [])
|
|
28
|
+
preconditionFields: ["walletAvailability", "assetReadAccount", ...(hasConnectionConflict ? ["connectionConflict"] : []),
|
|
29
|
+
...(reconciliationRequired ? ["connectionReconciliationRequired"] : [])],
|
|
28
30
|
answerFields: ["activeAccount", "connections", "assetReadAccount", "pendingWalletConnections", "pendingReviewSessions", "walletAvailability",
|
|
29
31
|
...(hasConnectionConflict ? ["connectionConflict"] : []),
|
|
32
|
+
"manageableConnectionIds", ...(reconciliationRequired ? ["connectionReconciliationRequired"] : []),
|
|
30
33
|
...(hasWalletObservation ? ["walletObservation"] : [])],
|
|
31
34
|
diagnosticOnlyFields: [
|
|
32
35
|
"pendingWalletConnections.truncated",
|
|
@@ -66,7 +69,8 @@ export function executionResultUserAnswerUse(fields = {}) {
|
|
|
66
69
|
"profit_or_pnl"
|
|
67
70
|
],
|
|
68
71
|
answerFields: [
|
|
69
|
-
"walletAvailability", "progress", ...(fields.hasConnectionConflict ? ["connectionConflict"] : []),
|
|
72
|
+
"walletAvailability", "progress", ...(fields.hasConnectionConflict ? ["connectionConflict"] : []),
|
|
73
|
+
...(fields.reconciliationRequired ? ["connectionReconciliationRequired"] : []), ...(hasWaitOutcome ? ["waitOutcome"] : []),
|
|
70
74
|
"reviewSessionId",
|
|
71
75
|
"status",
|
|
72
76
|
"pollingStatus",
|
|
@@ -91,7 +95,7 @@ export function executionResultUserAnswerUse(fields = {}) {
|
|
|
91
95
|
}
|
|
92
96
|
};
|
|
93
97
|
}
|
|
94
|
-
export function reviewStatusUserAnswerUse(hasReviewState, hasAdapterLifecycle = false, hasHumanReadableReview = false, hasSimulation = false, hasConnectionConflict = false) {
|
|
98
|
+
export function reviewStatusUserAnswerUse(hasReviewState, hasAdapterLifecycle = false, hasHumanReadableReview = false, hasSimulation = false, hasConnectionConflict = false, reconciliationRequired = false) {
|
|
95
99
|
return {
|
|
96
100
|
canAnswer: [
|
|
97
101
|
"current_local_review_session_status",
|
|
@@ -115,7 +119,8 @@ export function reviewStatusUserAnswerUse(hasReviewState, hasAdapterLifecycle =
|
|
|
115
119
|
"profit_or_pnl"
|
|
116
120
|
],
|
|
117
121
|
answerFields: [
|
|
118
|
-
"walletAvailability", "progress", ...(hasConnectionConflict ? ["connectionConflict"] : []),
|
|
122
|
+
"walletAvailability", "progress", ...(hasConnectionConflict ? ["connectionConflict"] : []),
|
|
123
|
+
...(reconciliationRequired ? ["connectionReconciliationRequired"] : []), "reviewSessionId",
|
|
119
124
|
"status",
|
|
120
125
|
"pollingStatus",
|
|
121
126
|
"statusCategory",
|
package/dist/mcp/serverInfo.js
CHANGED
|
@@ -50,11 +50,11 @@ export const SERVER_INSTRUCTIONS = [
|
|
|
50
50
|
"For USD-denominated payment coverage, balance total, or shortfall questions, call read.get_server_status, then read.list_settlement_asset_groups, then read.preview_intent_evidence. Use toolAvailability.requiredToolsAvailable and responseSummary.doNotCallQuoteToolsForThisQuestion; do not call wallet inventory or quote tools unless user asks a separate inventory or conversion question. For parity, use read.summarize_settlement_asset_group_parity. If toolAvailability cannot be used, say the current MCP server build cannot support the answer.",
|
|
51
51
|
"The active account context is read-only, not login, custody or signing permission. Implicit asset reads require session.get_interaction_status.assetReadAccount: available. Otherwise ask for an address only if the tool accepts account; connected-only tools require connection/account selection or wallet recovery.",
|
|
52
52
|
"Unsupported: other chains, autonomous trading, alerts, investment advice, arbitrary Move/package calls, P&L/tax.",
|
|
53
|
-
"One wallet connection at a time;
|
|
53
|
+
"One wallet connection at a time; docs: suimcp://docs/agent-behavior, suimcp://docs/mcp-tools."
|
|
54
54
|
].join("\n");
|
|
55
55
|
export const IMPLEMENTED_TOOLS = [
|
|
56
56
|
TOOL_NAMES.uiOpenAccount, TOOL_NAMES.uiOpenReceipt, TOOL_NAMES.uiOpenChart,
|
|
57
|
-
TOOL_NAMES.uiReadCard, TOOL_NAMES.uiSubmitCard, TOOL_NAMES.uiActCard,
|
|
57
|
+
TOOL_NAMES.uiReadCard, TOOL_NAMES.uiSubmitCard, TOOL_NAMES.uiActCard, TOOL_NAMES.uiReadSettingsFile,
|
|
58
58
|
TOOL_NAMES.readGetServerStatus,
|
|
59
59
|
TOOL_NAMES.readListSupportedProtocols,
|
|
60
60
|
TOOL_NAMES.readListDeepbookPools,
|
|
@@ -86,7 +86,6 @@ export const IMPLEMENTED_TOOLS = [
|
|
|
86
86
|
TOOL_NAMES.actionPrepareSuiActionReview,
|
|
87
87
|
TOOL_NAMES.actionPrepareExternalProposalReview,
|
|
88
88
|
TOOL_NAMES.accountGetActiveAccount,
|
|
89
|
-
TOOL_NAMES.accountClearActiveAccount,
|
|
90
89
|
TOOL_NAMES.sessionCreateWalletConnection,
|
|
91
90
|
TOOL_NAMES.sessionGetWalletConnection,
|
|
92
91
|
TOOL_NAMES.sessionWaitWalletConnection,
|
|
@@ -127,5 +126,5 @@ export const SERVER_LIMITATIONS = [
|
|
|
127
126
|
`DeepBook orderbook, mid-price, and quote outputs are ${DEEPBOOK_SOURCE_OWNER_RUNTIME_WORDING.pinnedSdkSnapshots} at fetchedAt; treat them as stale if the user delays.`,
|
|
128
127
|
`DeepBook USDC price history and price-at-time reads use ${DEEPBOOK_SOURCE_OWNER_RUNTIME_WORDING.officialIndexerCandleData} for the requested official interval. They are external candle evidence only, not live quotes, historical mid prices, global market prices, fiat USD values, USDC/USD peg guarantees, route recommendations, transaction-building inputs, signing readiness, P&L, cost basis, user-account transaction history, or user-account balance history. Sui MCP does not independently recompute those candle values from chain history for the response.`,
|
|
129
128
|
`ui.open_chart displays ${DEEPBOOK_SOURCE_OWNER_RUNTIME_WORDING.officialIndexerUsdcDenominatedCandles} in an internal read-only card. ui.open_account displays a public-address asset snapshot; ui.open_receipt displays the executed transaction graph when bound UI metadata is available, followed by on-chain facts and a Details slide. Card inputs end after backend admission, expiry or invalidation; chat navigation and frame recreation do not end valid unsubmitted input. Read the same DB state after a lost reply rather than resubmitting. Saved results do not authorize a new query or transaction. These cards are not live feeds, trading interfaces, route recommendations, fiat USD value, P&L, tax, cost basis, signing data, or signing readiness.`,
|
|
130
|
-
"Settings
|
|
129
|
+
"Settings uses an internal card. MCP settings tools create the card or read current settings; only the user's scoped card confirmation applies local changes or restarts the connection service. Backup bytes stay in app-only file transfer. No external Settings URL exists."
|
|
131
130
|
];
|
package/dist/mcp/toolNames.js
CHANGED
|
@@ -5,6 +5,7 @@ export const TOOL_NAMES = {
|
|
|
5
5
|
uiReadCard: "ui.read_card",
|
|
6
6
|
uiSubmitCard: "ui.submit_card",
|
|
7
7
|
uiActCard: "ui.act_card",
|
|
8
|
+
uiReadSettingsFile: "ui.read_settings_file",
|
|
8
9
|
sessionOpenReviewManagement: "session.open_review_management",
|
|
9
10
|
readGetServerStatus: "read.get_server_status",
|
|
10
11
|
readListSupportedProtocols: "read.list_supported_protocols",
|
|
@@ -43,8 +44,7 @@ export const TOOL_NAMES = {
|
|
|
43
44
|
sessionGetReviewStatus: "session.get_review_status",
|
|
44
45
|
sessionGetExecutionResult: "session.get_execution_result",
|
|
45
46
|
sessionWaitExecutionResult: "session.wait_execution_result",
|
|
46
|
-
accountGetActiveAccount: "account.get_active_account"
|
|
47
|
-
accountClearActiveAccount: "account.clear_active_account"
|
|
47
|
+
accountGetActiveAccount: "account.get_active_account"
|
|
48
48
|
};
|
|
49
49
|
export function assertValidToolName(name) {
|
|
50
50
|
if (!/^[A-Za-z0-9_.-]{1,128}$/.test(name)) {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { activeAccountResponse, activeAccountResponseSchema } from "../../activeAccountResponse.js";
|
|
3
|
-
import { registerSuiMcpTool } from "../../registerTool.js";
|
|
4
3
|
import { okToolResult } from "../../result.js";
|
|
5
|
-
import { noParamsInputSchema
|
|
4
|
+
import { noParamsInputSchema } from "../../schemas.js";
|
|
6
5
|
import { activityStoreToolError } from "../../toolErrors.js";
|
|
7
6
|
import { TOOL_NAMES } from "../../toolNames.js";
|
|
8
7
|
export function registerAccountTools(server, deps) {
|
|
@@ -24,21 +23,4 @@ export function registerAccountTools(server, deps) {
|
|
|
24
23
|
return activityStoreToolError(error, deps.logger);
|
|
25
24
|
}
|
|
26
25
|
});
|
|
27
|
-
registerSuiMcpTool(server, TOOL_NAMES.accountClearActiveAccount, {
|
|
28
|
-
title: "Clear active account read context",
|
|
29
|
-
description: "Remove the active read context. Does not disconnect a wallet or revoke onchain permission.",
|
|
30
|
-
inputSchema: noParamsInputSchema,
|
|
31
|
-
outputSchema: successOutputSchema({
|
|
32
|
-
status: z.literal("cleared")
|
|
33
|
-
}),
|
|
34
|
-
annotations: { readOnlyHint: false, openWorldHint: false }
|
|
35
|
-
}, async () => {
|
|
36
|
-
try {
|
|
37
|
-
await deps.activityStore.clearActiveAccount();
|
|
38
|
-
return okToolResult({ status: "cleared" });
|
|
39
|
-
}
|
|
40
|
-
catch (error) {
|
|
41
|
-
return activityStoreToolError(error, deps.logger);
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
26
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CONNECTION_CONFLICT_MESSAGE } from "../../../core/session/walletConnection.js";
|
|
1
|
+
import { CONNECTION_CONFLICT_MESSAGE, CONNECTION_RECONCILIATION_MESSAGE } from "../../../core/session/walletConnection.js";
|
|
2
2
|
import { ReadServiceCacheError, ReadServiceInputError } from "../../../core/read/readService.js";
|
|
3
3
|
import { parseSuiAddress } from "../../../core/suiAddress.js";
|
|
4
4
|
import { errorToolResult } from "../../result.js";
|
|
@@ -41,7 +41,7 @@ export async function resolveExplicitOrActiveAccount(input, deps) {
|
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
try {
|
|
44
|
-
const context = deps.workflow?.
|
|
44
|
+
const context = deps.workflow?.reconcileConnectionContext();
|
|
45
45
|
if (context?.assetReadAccount.status === "available")
|
|
46
46
|
return { status: "ok", account: context.assetReadAccount.account };
|
|
47
47
|
const availability = context?.walletAvailability ?? walletUnavailable("initialization_failed");
|
|
@@ -51,13 +51,14 @@ export async function resolveExplicitOrActiveAccount(input, deps) {
|
|
|
51
51
|
details: { ...(availability.status === "unavailable" ? { reason: availability.reason } : {}), message: availability.message, walletAvailability: availability } }) };
|
|
52
52
|
return { status: "error", result: errorToolResult({ kind: active ? "input_invalid" : "active_account_not_set",
|
|
53
53
|
details: { reason: "connected_account_required",
|
|
54
|
-
message: context?.connectionConflict ? CONNECTION_CONFLICT_MESSAGE : "This tool requires a selected account with a usable wallet connection. An address alone cannot be used.",
|
|
54
|
+
message: context?.connectionReconciliationRequired ? CONNECTION_RECONCILIATION_MESSAGE : context?.connectionConflict ? CONNECTION_CONFLICT_MESSAGE : "This tool requires a selected account with a usable wallet connection. An address alone cannot be used.",
|
|
55
55
|
...(context?.connectionConflict ? { connectionConflict: context.connectionConflict } : {}),
|
|
56
|
+
...(context?.connectionReconciliationRequired ? { connectionReconciliationRequired: true } : {}),
|
|
56
57
|
followUp: { tool: TOOL_NAMES.sessionGetInteractionStatus,
|
|
57
58
|
answerFields: ["walletAvailability", "connections", "pendingWalletConnections", "assetReadAccount"],
|
|
58
59
|
reason: `Check current connections and pending operations. Use pendingWalletConnections.items[].cardId with ${TOOL_NAMES.sessionGetWalletConnection} or ${TOOL_NAMES.sessionWaitWalletConnection}. If no operation is pending and the user requests connection or account selection, open ${TOOL_NAMES.sessionCreateWalletConnection}. Only the user may act in that card.` } } }) };
|
|
59
60
|
}
|
|
60
|
-
const message = context?.connectionConflict ? CONNECTION_CONFLICT_MESSAGE + " Provide a Sui address in chat for this public read." : availability.status !== "available"
|
|
61
|
+
const message = context?.connectionReconciliationRequired ? CONNECTION_RECONCILIATION_MESSAGE + " Provide a Sui address in chat for this public read." : context?.connectionConflict ? CONNECTION_CONFLICT_MESSAGE + " Provide a Sui address in chat for this public read." : availability.status !== "available"
|
|
61
62
|
? "The wallet connection cannot be checked. Please provide a Sui address in chat to view its assets."
|
|
62
63
|
: "No connected wallet is available for the selected account. Please provide a Sui address in chat.";
|
|
63
64
|
return { status: "address_required", message, result: errorToolResult({ kind: "input_invalid",
|
|
@@ -20,7 +20,7 @@ export function registerExecutionResultTools(server, deps) {
|
|
|
20
20
|
if (!state)
|
|
21
21
|
return errorToolResult({ kind: "session_not_found", details: { reviewSessionId } });
|
|
22
22
|
const request = state.request;
|
|
23
|
-
return okToolResult({ ...reviewStatusResponse(state), userAnswerUse: executionResultUserAnswerUse({ hasConnectionConflict: !!state.connectionConflict, hasRequest: !!request, hasExecutionResult: !!request?.execution }) });
|
|
23
|
+
return okToolResult({ ...reviewStatusResponse(state), userAnswerUse: executionResultUserAnswerUse({ hasConnectionConflict: !!state.connectionConflict, reconciliationRequired: !!state.connectionReconciliationRequired, hasRequest: !!request, hasExecutionResult: !!request?.execution }) });
|
|
24
24
|
}
|
|
25
25
|
catch (error) {
|
|
26
26
|
return sessionStoreToolError(error, deps.logger);
|
|
@@ -35,7 +35,7 @@ export function registerExecutionResultTools(server, deps) {
|
|
|
35
35
|
try {
|
|
36
36
|
const result = await waitForExecutionResult((id) => readCurrentReview(deps, id), reviewSessionId, { timeoutMs, signal: extra.signal });
|
|
37
37
|
return okToolResult({ waitOutcome: result.waitOutcome, ...reviewStatusResponse(result),
|
|
38
|
-
userAnswerUse: executionResultUserAnswerUse({ hasConnectionConflict: !!result.connectionConflict, hasRequest: !!result.request, hasExecutionResult: !!result.request?.execution, hasWaitOutcome: true }) });
|
|
38
|
+
userAnswerUse: executionResultUserAnswerUse({ hasConnectionConflict: !!result.connectionConflict, reconciliationRequired: !!result.connectionReconciliationRequired, hasRequest: !!result.request, hasExecutionResult: !!result.request?.execution, hasWaitOutcome: true }) });
|
|
39
39
|
}
|
|
40
40
|
catch (error) {
|
|
41
41
|
return sessionStoreToolError(error, deps.logger);
|
|
@@ -15,6 +15,7 @@ export function executionStatusCategorySchema() { return z.enum(EXECUTION_STATUS
|
|
|
15
15
|
export const timeoutInputSchema = z.number().int().min(1).max(MAX_WAIT_TIMEOUT_MS).default(DEFAULT_WAIT_TIMEOUT_MS).optional();
|
|
16
16
|
export function waitExecutionInputSchema() { return { reviewSessionId: z.string().min(1), timeoutMs: timeoutInputSchema }; }
|
|
17
17
|
export const reviewStatusResponseShape = {
|
|
18
|
+
connectionReconciliationRequired: z.literal(true).optional(),
|
|
18
19
|
connectionConflict: connectionConflictSchema.optional(), walletAvailability: walletAvailabilitySchema, walletObservation: walletObservationSchema.optional(), progress: workflowProgressSchema,
|
|
19
20
|
reviewSessionId: z.string(), status: internalSessionStatusSchema, reviewRevision: z.number().int().nonnegative(),
|
|
20
21
|
account: z.string().optional(), plans: z.array(actionPlanSchema), reviewState: reviewStateOutputSchema.optional(),
|
|
@@ -18,6 +18,7 @@ export function registerSessionStatusTools(server, deps) {
|
|
|
18
18
|
outputSchema: successOutputSchema({ walletAvailability: walletAvailabilitySchema, walletObservation: walletObservationSchema.optional(), activeAccount: activeAccountResponseSchema,
|
|
19
19
|
connections: z.array(connectionViewSchema), assetReadAccount: assetReadAccountSchema,
|
|
20
20
|
connectionConflict: connectionConflictSchema.optional(),
|
|
21
|
+
manageableConnectionIds: z.array(z.string().min(1)), connectionReconciliationRequired: z.literal(true).optional(),
|
|
21
22
|
pendingWalletConnections: z.object({ limit: z.number().int().positive(), truncated: z.boolean(),
|
|
22
23
|
items: z.array(z.object({ cardId: z.string(), connectionId: z.string().optional(),
|
|
23
24
|
status: pendingConnectionStatusSchema, progress: workflowProgressSchema, lastActivityAt: z.string() })) }),
|
|
@@ -35,14 +36,14 @@ export function registerSessionStatusTools(server, deps) {
|
|
|
35
36
|
try {
|
|
36
37
|
// Connection reconciliation can invalidate reviews and interrupt requests.
|
|
37
38
|
// Complete that maintenance before projecting its dependent review facts.
|
|
38
|
-
const context = deps.workflow?.
|
|
39
|
+
const context = deps.workflow?.reconcileConnectionContext() ?? { connections: [], manageableConnectionIds: [],
|
|
39
40
|
assetReadAccount: { status: "address_required" }, walletAvailability: walletUnavailable("initialization_failed") };
|
|
40
41
|
const connections = deps.workflow?.pendingConnections() ?? [];
|
|
41
42
|
const states = await Promise.all(deps.sessions.reviewSessionIds().map((id) => readCurrentReview(deps, id)));
|
|
42
43
|
const reviews = states.flatMap((state) => state && isReviewInteractionPending(state) ? [reviewStatusResponse(state)] : []);
|
|
43
44
|
return okToolResult({ activeAccount: activeAccountResponse(active), pendingWalletConnections: latest(connections),
|
|
44
45
|
pendingReviewSessions: latest(reviews), ...context,
|
|
45
|
-
userAnswerUse: interactionStatusUserAnswerUse("walletObservation" in context && context.walletObservation !== undefined, "connectionConflict" in context && !!context.connectionConflict) });
|
|
46
|
+
userAnswerUse: interactionStatusUserAnswerUse("walletObservation" in context && context.walletObservation !== undefined, "connectionConflict" in context && !!context.connectionConflict, "connectionReconciliationRequired" in context && !!context.connectionReconciliationRequired) });
|
|
46
47
|
}
|
|
47
48
|
catch (error) {
|
|
48
49
|
return sessionStoreToolError(error, deps.logger);
|
|
@@ -60,7 +61,7 @@ export function registerSessionStatusTools(server, deps) {
|
|
|
60
61
|
return errorToolResult({ kind: "session_not_found", details: { reviewSessionId } });
|
|
61
62
|
const session = state.session;
|
|
62
63
|
return okToolResult({ ...reviewStatusResponse(state),
|
|
63
|
-
userAnswerUse: reviewStatusUserAnswerUse(!!session.reviewState, !!session.reviewState?.adapterLifecycle, !!session.reviewState?.humanReadableReview, !!session.reviewState?.simulation, !!state.connectionConflict) });
|
|
64
|
+
userAnswerUse: reviewStatusUserAnswerUse(!!session.reviewState, !!session.reviewState?.adapterLifecycle, !!session.reviewState?.humanReadableReview, !!session.reviewState?.simulation, !!state.connectionConflict, !!state.connectionReconciliationRequired) });
|
|
64
65
|
}
|
|
65
66
|
catch (error) {
|
|
66
67
|
return sessionStoreToolError(error, deps.logger);
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { SETTINGS_APPLIES_AFTER_RESTART } from "../../../core/preferences/preferencesStore.js";
|
|
3
3
|
import { registerSuiMcpTool } from "../../registerTool.js";
|
|
4
|
-
import {
|
|
4
|
+
import { okToolResult } from "../../result.js";
|
|
5
5
|
import { noParamsInputSchema, successOutputSchema } from "../../schemas.js";
|
|
6
6
|
import { localSettingsToolError, sessionStoreToolError } from "../../toolErrors.js";
|
|
7
7
|
import { TOOL_NAMES } from "../../toolNames.js";
|
|
8
|
+
import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";
|
|
9
|
+
import { cardMetadata, createWorkflowCard } from "../../../mcp-ui/tools.js";
|
|
10
|
+
import { cardSnapshotSchema } from "../../../mcp-ui/contracts.js";
|
|
8
11
|
const endpointSettingSchema = z.object({
|
|
9
12
|
storedValue: z.string(),
|
|
10
13
|
effectiveValue: z.string(),
|
|
@@ -13,34 +16,11 @@ const endpointSettingSchema = z.object({
|
|
|
13
16
|
appliesAfter: z.literal(SETTINGS_APPLIES_AFTER_RESTART).optional()
|
|
14
17
|
});
|
|
15
18
|
export function registerSettingsTools(server, deps) {
|
|
16
|
-
server
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
inputSchema: noParamsInputSchema,
|
|
20
|
-
outputSchema: successOutputSchema({
|
|
21
|
-
settingsSessionId: z.string(),
|
|
22
|
-
settingsUrl: z.string(),
|
|
23
|
-
status: z.literal("created"),
|
|
24
|
-
openTarget: z.literal("system_browser"),
|
|
25
|
-
accessScope: z.literal("same_machine_loopback"),
|
|
26
|
-
expiresAt: z.string(),
|
|
27
|
-
lastActivityAt: z.string()
|
|
28
|
-
}),
|
|
29
|
-
annotations: { readOnlyHint: false, openWorldHint: false }
|
|
30
|
-
}, async () => {
|
|
19
|
+
registerAppTool(server, TOOL_NAMES.settingsCreateLocalSettingsSession, { title: "Settings", description: "Open a local settings card.", inputSchema: noParamsInputSchema,
|
|
20
|
+
outputSchema: successOutputSchema(cardSnapshotSchema.shape), _meta: cardMetadata("settings"),
|
|
21
|
+
annotations: { readOnlyHint: false, openWorldHint: false } }, async () => {
|
|
31
22
|
try {
|
|
32
|
-
|
|
33
|
-
return errorToolResult({ kind: "ui_unavailable", details: { reason: "Settings UI is unavailable in this runtime." } });
|
|
34
|
-
const created = await deps.sessions.createSettingsSession();
|
|
35
|
-
return okToolResult({
|
|
36
|
-
settingsSessionId: created.session.id,
|
|
37
|
-
settingsUrl: `${deps.reviewBaseUrl}/settings/${created.session.id}#${created.token}`,
|
|
38
|
-
status: "created",
|
|
39
|
-
openTarget: "system_browser",
|
|
40
|
-
accessScope: "same_machine_loopback",
|
|
41
|
-
expiresAt: created.session.expiresAt,
|
|
42
|
-
lastActivityAt: created.session.lastActivityAt
|
|
43
|
-
});
|
|
23
|
+
return await createWorkflowCard(server, deps, "settings", {});
|
|
44
24
|
}
|
|
45
25
|
catch (error) {
|
|
46
26
|
return sessionStoreToolError(error, deps.logger);
|