@zktx.io/sui-mcp 0.7.0 → 0.8.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.
Files changed (69) hide show
  1. package/README.md +83 -42
  2. package/dist/core/activity/localDataService.js +8 -21
  3. package/dist/core/activity/localDataValidation.js +22 -46
  4. package/dist/core/activity/sqliteAccountStore.js +46 -0
  5. package/dist/core/activity/sqliteActivityStore.js +65 -147
  6. package/dist/core/activity/sqliteActivityStoreSchema.js +42 -26
  7. package/dist/core/activity/transactionActivityService.js +2 -2
  8. package/dist/core/preferences/preferencesStore.js +2 -2
  9. package/dist/core/preferences/sqlitePreferencesRepository.js +10 -0
  10. package/dist/core/session/cardResult.js +41 -0
  11. package/dist/core/session/cardSession.js +1 -1
  12. package/dist/core/session/cardSessionStore.js +51 -19
  13. package/dist/core/session/chainAccounts.js +120 -0
  14. package/dist/core/session/sessionStore.js +4 -8
  15. package/dist/core/session/settingsFileStore.js +3 -30
  16. package/dist/core/session/settingsOperation.js +27 -19
  17. package/dist/core/session/settingsWorkflow.js +161 -87
  18. package/dist/core/session/sqliteCardStore.js +34 -14
  19. package/dist/core/session/sqliteSessionStore.js +5 -5
  20. package/dist/core/session/sqliteSettingsWorkflowStore.js +135 -41
  21. package/dist/core/session/sqliteTransactionRequest.js +18 -0
  22. package/dist/core/session/sqliteWalletConnectionOperation.js +13 -0
  23. package/dist/core/session/sqliteWalletWorkflowStore.js +569 -333
  24. package/dist/core/session/status.js +2 -2
  25. package/dist/core/session/wait.js +6 -11
  26. package/dist/core/session/walletConnection.js +71 -46
  27. package/dist/core/session/walletConnectionOperations.js +24 -0
  28. package/dist/core/session/walletRuntime.js +7 -13
  29. package/dist/core/session/walletWorkflow.js +248 -99
  30. package/dist/core/session/workflowState.js +104 -38
  31. package/dist/core/session/workflowView.js +92 -101
  32. package/dist/mcp/activeAccountResponse.js +3 -0
  33. package/dist/mcp/responseGuidance.js +7 -9
  34. package/dist/mcp/serverInfo.js +11 -10
  35. package/dist/mcp/toolErrors.js +5 -3
  36. package/dist/mcp/toolNames.js +1 -0
  37. package/dist/mcp/tools/action/prepareSuiActionReview.js +9 -16
  38. package/dist/mcp/tools/read/readToolHelpers.js +9 -7
  39. package/dist/mcp/tools/session/executionResultTools.js +2 -2
  40. package/dist/mcp/tools/session/shared.js +2 -2
  41. package/dist/mcp/tools/session/statusTools.js +12 -10
  42. package/dist/mcp/tools/session/walletConnectionTools.js +26 -14
  43. package/dist/mcp/tools/settings/index.js +7 -4
  44. package/dist/mcp-app/account.html +7 -7
  45. package/dist/mcp-app/chart.html +3 -3
  46. package/dist/mcp-app/connect.html +13 -13
  47. package/dist/mcp-app/disconnect.html +3637 -0
  48. package/dist/mcp-app/disconnect.notices.txt +1480 -0
  49. package/dist/mcp-app/receipt.html +393 -393
  50. package/dist/mcp-app/review.html +459 -459
  51. package/dist/mcp-app/settings.html +7 -7
  52. package/dist/mcp-ui/contracts.js +3 -1
  53. package/dist/mcp-ui/tools.js +4 -4
  54. package/dist/runtime/application.js +1 -1
  55. package/dist/runtime/localSettingsService.js +7 -4
  56. package/dist/runtime/smokeMainnetRead.js +5 -4
  57. package/dist/runtime/walletConnectTransport.js +16 -16
  58. package/dist/runtime/walletSdkChildRuntime.js +29 -12
  59. package/dist/runtime/walletSdkIpc.js +5 -4
  60. package/dist/runtime/walletSdkProcess.js +24 -8
  61. package/docs/AGENT_BEHAVIOR.md +82 -54
  62. package/docs/LOCAL_DB_ARCHITECTURE.md +100 -70
  63. package/docs/MCP_SETUP.md +319 -339
  64. package/docs/MCP_TOOLS.md +292 -177
  65. package/docs/SDK_API.md +15 -6
  66. package/docs/TRANSACTION_ACTIVITY_LOG.md +4 -0
  67. package/docs/UTILITY_INDEX.md +9 -7
  68. package/docs/WALLET_CONNECTION.md +162 -167
  69. package/package.json +1 -1
@@ -1,80 +1,146 @@
1
- import { walletConnectionSelection } from "./walletConnection.js";
1
+ import { CONNECTION_RECONCILIATION_MESSAGE, walletConnectionEligibility, usableWalletConnection, connectionHasSuiSigningScope, projectWalletConnection } from "./walletConnection.js";
2
2
  import { isInitialChainObservation } from "./transactionRequest.js";
3
- export const workflowActions = ["connect", "disconnect", "use_account", "stop_connection", "prepare_review",
3
+ import { SUI_MAINNET_WALLET_CHAIN } from "./chainAccounts.js";
4
+ import { parseSuiAddress } from "../suiAddress.js";
5
+ export const workflowActions = ["connect", "disconnect", "stop_connection", "prepare_review",
4
6
  "request_signature", "cancel", "stop_waiting", "read_result"];
5
- export function reviewPreparationAccount(boundAccount, activeAccount, selectedAccount = activeAccount) {
6
- if (!activeAccount)
7
- return { allowed: false, message: "No account from a connected wallet is selected for this review." };
8
- if (boundAccount && boundAccount !== activeAccount)
9
- return { allowed: false,
10
- message: `This review uses ${boundAccount}, but the currently selected account is ${activeAccount}.` };
11
- if (selectedAccount !== activeAccount)
12
- return { allowed: false,
13
- message: "The selected account has changed since this request was made." };
14
- return { allowed: true, account: activeAccount };
7
+ export const workflowNextSteps = ["none", "wait", "check_status", "open_settings", "request_disconnect", "request_review"];
8
+ function walletServiceNextStep(status) {
9
+ if (status === "available")
10
+ return undefined;
11
+ return { kind: status === "unavailable" ? "open_settings" : "wait", reason: "wallet_unavailable",
12
+ message: status === "unavailable" ? "Open Settings, then Troubleshooting, to restart the connection service. No wallet request was sent."
13
+ : "Wait for the connection service to finish starting, then check status." };
14
+ }
15
+ export function walletActionDecision(facts, action, targetId) {
16
+ const deny = (kind, reason, message) => ({ allowed: false, nextStep: { kind, reason, message } });
17
+ const service = walletServiceNextStep(facts.walletAvailability.status);
18
+ if (service)
19
+ return { allowed: false, nextStep: service };
20
+ if (facts.pendingOperation)
21
+ return deny("check_status", "operation_busy", "Wait for the current wallet operation and its cleanup to finish, then check this request's status.");
22
+ const selection = walletConnectionEligibility(facts.connection, Date.parse(facts.evaluatedAt), facts.connectionPresence, facts.activeAccount, facts.readAddressCleared);
23
+ if (!facts.connectionPresence.confirmed || !facts.connectionPresence.managed || selection.connectionReconciliationRequired)
24
+ return deny("check_status", "connection_state_unconfirmed", selection.connectionReconciliationRequired ? CONNECTION_RECONCILIATION_MESSAGE : "The wallet session has not been confirmed. Check its status before continuing.");
25
+ if (action === "connect") {
26
+ if (!facts.requestedChains.length)
27
+ return deny("open_settings", "wallet_networks_required", "Choose networks in Settings before requesting a wallet connection.");
28
+ return selection.pairingAllowed ? { allowed: true, nextStep: { kind: "none" } }
29
+ : deny("request_disconnect", "connection_present", "Ask in chat for a Disconnect card before requesting a new wallet connection.");
30
+ }
31
+ if (targetId && facts.connection?.connectionId !== targetId)
32
+ return deny("check_status", "connection_target_changed", "The wallet session for this card is no longer current. Ask in chat for current connection status.");
33
+ return selection.manageable ? { allowed: true, nextStep: { kind: "none" } }
34
+ : deny("none", "no_session_to_disconnect", "There is no current wallet session to disconnect. No request was sent.");
35
+ }
36
+ export function fixedReviewTarget(session, input) {
37
+ const hasInputTarget = input?.connectionId !== undefined || input?.account !== undefined;
38
+ const inputId = typeof input?.connectionId === "string" && input.connectionId.length ? input.connectionId : undefined;
39
+ const inputAccount = typeof input?.account === "string" ? parseSuiAddress(input.account) : undefined;
40
+ if (hasInputTarget && (!inputId || !inputAccount))
41
+ return undefined;
42
+ if (inputId && session.walletConnectionId && inputId !== session.walletConnectionId ||
43
+ inputAccount && session.account && inputAccount !== session.account)
44
+ return undefined;
45
+ const connectionId = inputId ?? session.walletConnectionId, account = inputAccount ?? session.account;
46
+ return connectionId && account ? { connectionId, account } : undefined;
15
47
  }
16
48
  export function workflowConnectionEligibility(facts) {
17
- const selection = walletConnectionSelection(facts.connections, Date.parse(facts.evaluatedAt), facts.connectionPresence, facts.activeAccount ? { address: facts.activeAccount, walletId: facts.activeAccountConnectionId } : undefined);
49
+ const selection = walletConnectionEligibility(facts.connection, Date.parse(facts.evaluatedAt), facts.connectionPresence, facts.activeAccount, facts.readAddressCleared);
18
50
  const available = facts.walletAvailability.status === "available", dependent = facts.walletDependent !== false;
19
- const usableConnectionId = available && dependent ? selection.connection?.connectionId : undefined;
20
- const assetReadAccount = !dependent ? undefined
21
- : usableConnectionId ? selection.assetReadAccount : { status: "address_required" };
22
- return { selection, usableConnectionId, assetReadAccount, connectionConflict: selection.conflict,
23
- manageableConnectionIds: available ? selection.manageableConnectionIds : [],
51
+ return { selection, usableConnectionId: available && dependent ? selection.connection?.connectionId : undefined,
52
+ assetReadAccount: !dependent ? undefined : available ? selection.assetReadAccount : { status: "address_required" },
53
+ connection: projectWalletConnection(facts.connection, Date.parse(facts.evaluatedAt), facts.connectionPresence, available),
54
+ pendingOperation: facts.pendingOperation,
55
+ canDisconnect: available && selection.manageable,
24
56
  connectionReconciliationRequired: available && dependent ? selection.connectionReconciliationRequired : undefined };
25
57
  }
26
58
  export function workflowEligibility(facts) {
27
59
  const { record, session, request, authority, walletAvailability } = facts;
28
60
  const at = Date.parse(facts.evaluatedAt);
29
- const cardRemaining = record?.ownerId === facts.ownerId ? Math.max(0, Date.parse(record.state.expiresAt) - at) : 0;
61
+ const cardRemaining = record?.ownerId === facts.ownerId && record.state.reason !== "cancelled"
62
+ ? Math.max(0, Date.parse(record.state.expiresAt) - at) : 0;
30
63
  const remaining = record?.scope === "review" && session && !request
31
64
  ? Math.min(cardRemaining, Math.max(0, Date.parse(session.expiresAt) - at)) : cardRemaining;
32
65
  const inputAvailable = remaining > 0 && record?.state.state === "ready" && record.acceptedInput === undefined;
33
66
  const allowedActions = [];
34
67
  const { selection, ...connectionEligibility } = workflowConnectionEligibility(facts);
35
- const { assetReadAccount } = connectionEligibility;
36
- let preparationIssue;
37
- if (record?.scope === "connect") {
68
+ const reviewTarget = session && !session.plans[0]?.reviewModel ? fixedReviewTarget(session, record?.state.input) : undefined;
69
+ const targetId = reviewTarget?.connectionId, targetAccount = reviewTarget?.account;
70
+ const reviewConnection = walletAvailability.status === "available" && !facts.connectionPresence.unrecorded && facts.walletDependent !== false && targetId && targetAccount
71
+ ? usableWalletConnection(facts.connection, at, facts.connectionPresence, targetId, SUI_MAINNET_WALLET_CHAIN, targetAccount) : undefined;
72
+ const preparationIssue = session && record?.scope === "review" && !session.plans[0]?.reviewModel && !reviewTarget
73
+ ? "This review has no matching fixed wallet target. Start a new review." : undefined;
74
+ const scopedDisconnect = record?.scope === "disconnect";
75
+ const walletDecision = walletActionDecision(facts, scopedDisconnect ? "disconnect" : "connect", scopedDisconnect ? String(record?.state.input.connectionId) : undefined);
76
+ let nextStep = facts.operation?.status === "running" && facts.operation.cardId === record?.state.cardId
77
+ ? { kind: "wait" } : walletDecision.nextStep;
78
+ const disconnectTargetMatches = !scopedDisconnect || record.state.input.connectionId === facts.connection?.connectionId;
79
+ if (record?.scope === "connect" || scopedDisconnect) {
38
80
  if (inputAvailable) {
39
81
  allowedActions.push("cancel");
40
82
  if (walletAvailability.status === "available") {
41
- if (["connect", "manage"].includes(String(record.state.input.intent)) && selection.pairingAllowed)
83
+ if (record.scope === "connect" && walletDecision.allowed)
42
84
  allowedActions.push("connect");
43
- if (selection.manageableConnectionIds.length)
85
+ if (scopedDisconnect && walletDecision.allowed)
44
86
  allowedActions.push("disconnect");
45
- if (selection.connection)
46
- allowedActions.push("use_account");
47
87
  }
48
88
  }
49
- if (remaining > 0 && facts.connection?.status === "awaiting_approval")
89
+ if (record.state.state !== "closed" && remaining > 0 && facts.operation?.action === "connect" && facts.operation.status === "running")
50
90
  allowedActions.push("stop_connection");
51
91
  }
52
92
  if (session && inputAvailable && record?.scope === "review" && session.status !== "expired" && Date.parse(session.expiresAt) > at && !session.preparationId) {
53
93
  allowedActions.push("cancel");
54
- if (walletAvailability.status === "available" && selection.connection && !session.plans[0]?.reviewModel) {
55
- const account = reviewPreparationAccount(session.account, assetReadAccount?.status === "available" ? assetReadAccount.account : undefined);
56
- if (!account.allowed)
57
- preparationIssue = account.message;
58
- else if (!facts.busyForAccount)
94
+ if (walletAvailability.status === "available" && reviewConnection && !session.plans[0]?.reviewModel) {
95
+ if (!facts.busyForAccount)
59
96
  allowedActions.push("prepare_review");
60
- if (!facts.busyForAccount && session.status === "ready_for_wallet_review" && session.reviewState?.transactionReviewData &&
97
+ if (!facts.busyForAccount && connectionHasSuiSigningScope(reviewConnection, targetAccount) && session.status === "ready_for_wallet_review" && session.reviewState?.transactionReviewData &&
61
98
  (!request || request.reviewRevision !== session.reviewRevision))
62
99
  allowedActions.push("request_signature");
63
100
  }
64
101
  }
65
- if (record && session && remaining > 0 && request && request.reviewSessionId === session.id &&
102
+ if (record && record.state.state !== "closed" && session && remaining > 0 && request && request.reviewSessionId === session.id &&
66
103
  authority?.owner_id === facts.ownerId &&
67
104
  (record.operationId === request.attemptId || record.scope === "review_manage" && record.state.input.attemptId === request.attemptId)) {
68
- if (["awaiting_signature", "submitting", "awaiting_chain_result"].includes(request.requestStatus))
105
+ if (request.requestStatus === "awaiting_signature" || !authority.observation_stopped && isInitialChainObservation(request.requestStatus))
69
106
  allowedActions.push("stop_waiting");
70
- if (isInitialChainObservation(request.requestStatus) || request.requestStatus === "outcome_unknown")
107
+ if (record.scope === "review_manage" && record.state.state === "ready" && record.acceptedInput === undefined && (isInitialChainObservation(request.requestStatus) || request.requestStatus === "outcome_unknown"))
71
108
  allowedActions.push("read_result");
72
109
  }
73
110
  const expiry = session && inputAvailable && record?.scope === "review" && !session.preparationId &&
74
111
  session.status === "ready_for_wallet_review" && (!request || request.reviewRevision !== session.reviewRevision)
75
112
  ? session.reviewState?.humanReadableReview?.freshness.expiresAt : undefined;
76
- const nextRead = expiry && session ? Math.min(remaining, Date.parse(session.expiresAt) - at, Date.parse(expiry) - at) : undefined;
77
- return { allowedActions, preparationIssue, ...connectionEligibility,
113
+ const reviewRead = expiry && session ? Math.min(remaining, Date.parse(session.expiresAt) - at, Date.parse(expiry) - at) : undefined;
114
+ const connectionDeadlines = inputAvailable && facts.walletDependent !== false && facts.connection?.status === "connected" &&
115
+ (["connect", "disconnect"].includes(record?.scope ?? "") || facts.connection.connectionId === reviewTarget?.connectionId)
116
+ ? [Date.parse(facts.connection.expiresAt) - at].filter((delay) => delay > 0) : [];
117
+ const readDelays = [reviewRead, ...connectionDeadlines].filter((delay) => delay !== undefined && delay > 0);
118
+ const nextRead = readDelays.length ? Math.min(...readDelays) : undefined;
119
+ if (session) {
120
+ nextStep = { kind: "none" };
121
+ if (!session.plans[0]?.reviewModel && !request) {
122
+ const service = walletServiceNextStep(walletAvailability.status);
123
+ if (service)
124
+ nextStep = service;
125
+ else if (facts.connectionPresence.unrecorded)
126
+ nextStep = { kind: "check_status", reason: "connection_state_unconfirmed", message: CONNECTION_RECONCILIATION_MESSAGE };
127
+ else if (facts.pendingOperation)
128
+ nextStep = { kind: "check_status", reason: "operation_busy", message: "Wait for the current wallet operation and its cleanup to finish, then check this review's status." };
129
+ else if (reviewTarget && connectionEligibility.connection?.connectionId === targetId && connectionEligibility.connection?.sessionPresence === "absent")
130
+ nextStep = { kind: "request_review", message: "The original wallet session is no longer present. Reconnect a wallet and ask in chat for a new transaction review." };
131
+ else if (reviewTarget && facts.connection && facts.connection.connectionId !== targetId)
132
+ nextStep = { kind: "request_review", message: "This review belongs to the earlier wallet connection. Ask in chat for a new transaction review." };
133
+ else if (!reviewConnection && connectionEligibility.canDisconnect && facts.connection?.connectionId === targetId)
134
+ nextStep = { kind: "request_disconnect", message: "The original wallet connection is unavailable. Ask in chat for a Disconnect card, then start a new review after reconnecting." };
135
+ else if (!reviewConnection)
136
+ nextStep = { kind: "check_status", message: "This review keeps its original account and connection. Ask in chat to check that wallet connection's status before continuing." };
137
+ else if (facts.busyForAccount)
138
+ nextStep = { kind: "wait", message: "A previous transaction request for this Sui account is still pending. Wait for it to finish, then check this review's status." };
139
+ }
140
+ }
141
+ return { allowedActions, preparationIssue, nextStep, ...connectionEligibility, reviewTarget,
142
+ canDisconnect: connectionEligibility.canDisconnect && disconnectTargetMatches,
143
+ ...(session ? { usableConnectionId: reviewConnection?.connectionId } : {}),
78
144
  actionRemainingMs: remaining, inputRemainingMs: inputAvailable ? remaining : 0,
79
145
  ...(nextRead !== undefined && nextRead > 0 ? { nextStateReadAfterMs: nextRead } : {}) };
80
146
  }
@@ -1,84 +1,86 @@
1
- import { workflowActions } from "./workflowState.js";
1
+ import { workflowActions, workflowNextSteps } from "./workflowState.js";
2
2
  import { z } from "zod";
3
- import { suiAddressStringSchema, parseSuiAddress } from "../suiAddress.js";
3
+ import { suiAddressStringSchema } from "../suiAddress.js";
4
4
  import { actionPlanSchema, internalSessionStatusSchema, reviewStateOutputSchema } from "../action/schemas.js";
5
- import { walletConnectionSchema, walletAvailabilitySchema, workflowProgressSchema, WALLET_CONNECTION_POLL_SECONDS, connectionConflictSchema, assetReadAccountSchema } from "./walletConnection.js";
5
+ import { walletConnectionSchema, walletAvailabilitySchema, workflowProgressSchema, WALLET_CONNECTION_POLL_SECONDS, connectionChainStateSchema } from "./walletConnection.js";
6
6
  import { transactionRequestSchema } from "./transactionRequest.js";
7
7
  import { workflowProgress } from "./walletConnection.js";
8
8
  import { reviewProgress, EXECUTION_POLLING_INTERVAL_SECONDS } from "./status.js";
9
- import { walletRunIdSchema, walletRecoverySchema, walletObservationSchema } from "./walletRuntime.js";
9
+ import { walletRunIdSchema, walletObservationSchema } from "./walletRuntime.js";
10
+ import { walletConnectionActionSchema, walletConnectionOperationSchema } from "./walletConnectionOperations.js";
11
+ import { walletChainSchema, walletChainsSchema, SUI_MAINNET_WALLET_CHAIN, normalizeChainAddress } from "./chainAccounts.js";
10
12
  export const CONNECT_BOUNDARY = "A wallet connection provides account context; it is not a transaction approval or proof of address ownership.";
11
13
  export const REVIEW_BOUNDARY = "Review evidence is not a safety guarantee. Only an explicit card action and wallet approval may authorize this exact transaction.";
12
- export const connectionViewSchema = walletConnectionSchema.extend({ pendingAction: z.literal("disconnect").optional() });
14
+ export const connectionViewSchema = walletConnectionSchema.extend({
15
+ sessionPresence: z.enum(["present", "absent", "unusable", "unconfirmed"]),
16
+ chainStates: z.array(connectionChainStateSchema).optional(),
17
+ disconnectImpact: z.object({ impactId: z.string(), requestCount: z.number().int().nonnegative(), chains: walletChainsSchema }).strict().optional()
18
+ });
13
19
  export { assetReadAccountSchema } from "./walletConnection.js";
14
- export const pendingConnectionStatusSchema = z.enum(["input_required", "awaiting_approval", "disconnect_pending"]);
15
- const connectAction = z.object({ action: z.literal("connect"), walletRunId: walletRunIdSchema }).strict();
16
- const disconnectAction = z.object({ action: z.literal("disconnect"), walletRunId: walletRunIdSchema, connectionId: z.string().min(1) }).strict();
17
- const accountAction = z.object({ action: z.literal("use_account"), walletRunId: walletRunIdSchema, connectionId: z.string().min(1), account: suiAddressStringSchema }).strict();
20
+ export const pendingConnectionStatusSchema = z.enum(["awaiting_approval", "disconnect_pending", "cleanup_pending"]);
18
21
  const reviewAction = z.object({ action: z.enum(["prepare_review", "request_signature"]), connectionId: z.string().min(1),
19
22
  walletRunId: walletRunIdSchema, account: suiAddressStringSchema, reviewRevision: z.number().int().nonnegative() }).strict();
20
- const simpleAction = z.object({ action: z.enum(["cancel", "stop_connection", "stop_waiting", "read_result"]) }).strict();
21
- export const workflowActionSchema = z.union([connectAction, disconnectAction, accountAction, reviewAction, simpleAction]);
22
- export const automaticWorkflowActionSchema = z.union([connectAction, reviewAction.extend({ action: z.literal("prepare_review") })]);
23
+ const simpleAction = z.object({ action: z.enum(["cancel", "stop_waiting", "read_result"]) }).strict();
24
+ const stopConnectionAction = z.object({ action: z.literal("stop_connection"), operationId: z.string().uuid() }).strict();
25
+ export const workflowActionSchema = z.union([walletConnectionActionSchema, stopConnectionAction, reviewAction, simpleAction]);
26
+ export const automaticWorkflowActionSchema = z.union([walletConnectionActionSchema.options[0], reviewAction.extend({ action: z.literal("prepare_review") })]);
23
27
  export function parseWorkflowAction(input) {
24
28
  const action = workflowActionSchema.parse(input);
25
- return "account" in action ? { ...action, account: parseSuiAddress(action.account) } : action;
26
- }
27
- export const workflowViewSchema = z.object({
28
- kind: z.enum(["connect", "review"]),
29
- mode: z.enum(["connect", "review", "review_manage"]),
30
- allowedActions: z.array(z.enum(workflowActions)),
31
- actionRemainingMs: z.number().int().nonnegative(),
32
- nextStateReadAfterMs: z.number().int().nonnegative().optional(),
33
- automaticAction: automaticWorkflowActionSchema.optional(),
34
- observe: z.boolean(),
35
- walletAvailability: walletAvailabilitySchema,
36
- walletObservation: walletObservationSchema.optional(),
37
- runtimeRecovery: walletRecoverySchema.optional(),
38
- connectionConflict: connectionConflictSchema.optional(),
39
- manageableConnectionIds: z.array(z.string().min(1)),
40
- connectionReconciliationRequired: z.literal(true).optional(),
41
- usableConnectionId: z.string().min(1).optional(),
42
- assetReadAccount: assetReadAccountSchema.optional(),
43
- acceptedWalletRunId: walletRunIdSchema.optional(),
44
- progress: workflowProgressSchema,
45
- boundary: z.enum([CONNECT_BOUNDARY, REVIEW_BOUNDARY]),
46
- connections: z.array(connectionViewSchema),
47
- activeAccount: z.string().optional(),
48
- connection: connectionViewSchema.optional(),
49
- connectionAction: z.enum(["connect", "disconnect"]).optional(),
50
- review: z.object({
51
- reviewSessionId: z.string(), plan: actionPlanSchema, reviewRevision: z.number().int().nonnegative(),
52
- status: internalSessionStatusSchema, account: z.string().optional(), preparing: z.boolean(),
53
- accountRequestPending: z.literal(true).optional(),
54
- error: z.string().optional(),
55
- state: reviewStateOutputSchema.optional()
56
- }).strict().optional(),
57
- request: transactionRequestSchema.optional(),
58
- receipt: z.unknown().optional(),
59
- netGasMist: z.string().regex(/^-?[0-9]+$/).optional(),
60
- observationStopped: z.boolean().optional()
61
- }).strict().superRefine((view, context) => {
62
- const connection = view.connections.find((item) => item.connectionId === view.usableConnectionId);
63
- if (view.usableConnectionId && (!connection || connection.status !== "connected" ||
64
- view.walletAvailability.status !== "available" || view.connectionConflict || view.connectionReconciliationRequired ||
65
- view.connections.some((item) => item.pendingAction || item.status === "awaiting_approval"))) {
66
- context.addIssue({ code: "custom", path: ["usableConnectionId"], message: "The usable connection must match current connection facts." });
67
- }
68
- if (new Set(view.manageableConnectionIds).size !== view.manageableConnectionIds.length ||
69
- view.manageableConnectionIds.some((id) => !view.connections.some((item) => item.connectionId === id &&
70
- !item.pendingAction && item.status !== "awaiting_approval"))) {
71
- context.addIssue({ code: "custom", path: ["manageableConnectionIds"], message: "Management must name distinct recorded targets without pending operations." });
29
+ if ("account" in action) {
30
+ const account = normalizeChainAddress(SUI_MAINNET_WALLET_CHAIN, action.account);
31
+ if (!account)
32
+ throw new Error("A valid chain account is required.");
33
+ return { ...action, account };
72
34
  }
73
- if (view.connectionReconciliationRequired && (view.usableConnectionId || view.assetReadAccount?.status === "available" ||
74
- view.allowedActions.some((action) => ["connect", "use_account", "prepare_review", "request_signature"].includes(action)))) {
75
- context.addIssue({ code: "custom", path: ["connectionReconciliationRequired"], message: "Unrecorded SDK connections cannot authorize connection or account use." });
76
- }
77
- if (view.assetReadAccount?.status === "available" && (!connection ||
78
- !connection.accounts.includes(view.assetReadAccount.account) || view.activeAccount !== view.assetReadAccount.account)) {
79
- context.addIssue({ code: "custom", path: ["assetReadAccount"], message: "The default address must belong to the usable connection and stored selection." });
35
+ return action;
36
+ }
37
+ function checkReviewAccount(view, context) {
38
+ if (view.reviewTarget && (view.review?.account && view.review.account !== view.reviewTarget.account ||
39
+ view.request && view.request.account !== view.reviewTarget.account)) {
40
+ context.addIssue({ code: "custom", path: ["reviewTarget"], message: "The fixed review target must match its recorded account and request." });
80
41
  }
81
- });
42
+ }
43
+ const nextStepSchema = z.object({ kind: z.enum(workflowNextSteps), message: z.string().optional(), reason: z.string().optional() }).strict();
44
+ const liveFields = {
45
+ allowedActions: z.array(z.enum(workflowActions)), actionRemainingMs: z.number().int().nonnegative(),
46
+ nextStateReadAfterMs: z.number().int().nonnegative().optional(), observe: z.boolean(),
47
+ walletAvailability: walletAvailabilitySchema, walletObservation: walletObservationSchema.optional(),
48
+ progress: workflowProgressSchema, nextStep: nextStepSchema
49
+ };
50
+ export const connectViewSchema = z.object({ ...liveFields, kind: z.literal("connect"), mode: z.literal("connect"),
51
+ allowedActions: z.array(z.enum(["connect", "cancel", "stop_connection"])),
52
+ boundary: z.literal(CONNECT_BOUNDARY), requestedChains: walletChainsSchema,
53
+ operation: walletConnectionOperationSchema.optional(), automaticAction: walletConnectionActionSchema.options[0].optional() }).strict();
54
+ export const disconnectViewSchema = z.object({ ...liveFields, kind: z.literal("disconnect"), mode: z.literal("disconnect"),
55
+ allowedActions: z.array(z.enum(["disconnect", "cancel"])),
56
+ boundary: z.literal(CONNECT_BOUNDARY), connection: connectionViewSchema.optional(),
57
+ operation: walletConnectionOperationSchema.optional() }).strict();
58
+ const reviewPayloadSchema = z.object({
59
+ reviewSessionId: z.string(), plan: actionPlanSchema, reviewRevision: z.number().int().nonnegative(),
60
+ status: internalSessionStatusSchema, account: z.string().optional(), preparing: z.boolean(),
61
+ accountRequestPending: z.literal(true).optional(), error: z.string().optional(), state: reviewStateOutputSchema.optional()
62
+ }).strict();
63
+ const reviewTargetSchema = z.object({ connectionId: z.string(), account: suiAddressStringSchema }).strict();
64
+ const netGasSchema = z.string().regex(/^-?[0-9]+$/).optional();
65
+ export const reviewViewSchema = z.object({ ...liveFields, kind: z.literal("review"), mode: z.enum(["review", "review_manage"]),
66
+ allowedActions: z.array(z.enum(["prepare_review", "request_signature", "read_result", "stop_waiting", "cancel"])),
67
+ boundary: z.literal(REVIEW_BOUNDARY), reviewTarget: reviewTargetSchema.optional(), review: reviewPayloadSchema,
68
+ walletName: z.string().optional(), acceptedWalletRunId: walletRunIdSchema.optional(),
69
+ automaticAction: reviewAction.extend({ action: z.literal("prepare_review") }).optional(),
70
+ request: transactionRequestSchema.optional(), receipt: z.unknown().optional(), netGasMist: netGasSchema,
71
+ observationStopped: z.boolean().optional()
72
+ }).strict().superRefine(checkReviewAccount);
73
+ export const workflowViewSchema = z.discriminatedUnion("kind", [connectViewSchema, disconnectViewSchema, reviewViewSchema]);
74
+ const closedResult = z.object({ message: z.string(), recordedAt: z.string().datetime() }).strict();
75
+ const closedFields = { allowedActions: z.array(z.never()), observe: z.literal(false), actionRemainingMs: z.literal(0), result: closedResult };
76
+ export const closedWalletCardSchema = z.object({ ...closedFields, kind: z.enum(["connect", "disconnect"]),
77
+ result: closedResult.extend({ pendingWork: z.enum(["approval_response", "session_removal", "session_status"]).optional() }), walletName: z.string().optional(),
78
+ networks: z.array(z.object({ chainId: walletChainSchema, status: z.enum(["bound", "not_approved", "multiple_accounts", "invalid_response"]) }).strict()) }).strict();
79
+ export const closedReviewCardSchema = z.object({ ...closedFields, kind: z.literal("review"),
80
+ mode: z.enum(["review", "review_manage"]), review: reviewPayloadSchema.optional(),
81
+ reviewTarget: reviewTargetSchema.optional(), request: transactionRequestSchema.optional(),
82
+ receipt: z.unknown().optional(), netGasMist: netGasSchema,
83
+ observationStopped: z.boolean().optional(), boundary: z.literal(REVIEW_BOUNDARY) }).strict().superRefine(checkReviewAccount);
82
84
  function automaticReviewAction(input) {
83
85
  const { record, session, request } = input;
84
86
  if (!session || record?.scope !== "review" || request || session.preparationError ||
@@ -86,18 +88,11 @@ function automaticReviewAction(input) {
86
88
  return undefined;
87
89
  const needsPreparation = !session.reviewState && session.reviewRevision === 0 ||
88
90
  session.reviewState?.status === "refresh_required" && session.reviewState.refreshReason === "review_evidence_stale";
89
- if (!needsPreparation || input.assetReadAccount?.status !== "available")
90
- return undefined;
91
- const account = input.assetReadAccount.account;
92
- const candidates = reviewWalletChoices(input, account);
93
- if (candidates.length !== 1)
91
+ if (!needsPreparation || !input.reviewTarget)
94
92
  return undefined;
95
- return { action: "prepare_review", walletRunId: input.walletAvailability.walletRunId, connectionId: candidates[0].connectionId,
96
- account, reviewRevision: session.reviewRevision };
97
- }
98
- export function reviewWalletChoices(selection, account, requireSigningMethod = false) {
99
- return selection.connections.filter((item) => item.connectionId === selection.usableConnectionId && item.accounts.includes(account) &&
100
- (!requireSigningMethod || item.methods.includes("sui_signTransaction")));
93
+ const account = input.reviewTarget.account;
94
+ return { action: "prepare_review", walletRunId: input.walletAvailability.walletRunId,
95
+ connectionId: input.reviewTarget.connectionId, account, reviewRevision: session.reviewRevision };
101
96
  }
102
97
  function nextStateRead(input) {
103
98
  const walletInput = input.record?.state.state === "ready" && input.record.scope !== "review_manage" && !input.request &&
@@ -109,24 +104,20 @@ function nextStateRead(input) {
109
104
  return delays.length ? Math.min(...delays) : undefined;
110
105
  }
111
106
  export function projectConnectionView(input) {
112
- const { walletAvailability } = input;
113
- const stored = input.record?.state.data;
114
- const connection = input.record?.acceptedInput?.action === "disconnect" && stored?.disconnectOutcome
115
- ? connectionViewSchema.parse(stored.disconnectOutcome) : input.connection;
116
- const readAfter = nextStateRead(input);
117
- const progress = input.runtimeRecovery ? { status: "phase" in input.runtimeRecovery ? "waiting" : "idle" } :
118
- workflowProgress(connection?.status === "awaiting_approval" || connection?.pendingAction === "disconnect", true, walletAvailability);
119
- return workflowViewSchema.parse({ kind: "connect", mode: "connect", allowedActions: input.allowedActions,
120
- actionRemainingMs: input.actionRemainingMs, observe: progress.status === "waiting", progress, walletAvailability,
121
- ...(input.record?.state.input.intent === "connect" && input.allowedActions.includes("connect")
122
- ? { automaticAction: { action: "connect", walletRunId: walletAvailability.walletRunId } } : {}),
123
- runtimeRecovery: input.runtimeRecovery, walletObservation: input.walletObservation,
124
- connectionConflict: input.connectionConflict, usableConnectionId: input.usableConnectionId, assetReadAccount: input.assetReadAccount,
125
- manageableConnectionIds: input.manageableConnectionIds, connectionReconciliationRequired: input.connectionReconciliationRequired,
126
- connectionAction: ["connect", "disconnect"].includes(String(input.record?.acceptedInput?.action)) ? input.record?.acceptedInput?.action : undefined,
127
- acceptedWalletRunId: input.record?.acceptedInput?.walletRunId,
128
- ...(readAfter === undefined ? {} : { nextStateReadAfterMs: readAfter }),
129
- connections: input.connections, activeAccount: input.activeAccount, connection, boundary: CONNECT_BOUNDARY });
107
+ const { walletAvailability, operation } = input, readAfter = nextStateRead(input);
108
+ const progress = workflowProgress(!!operation && operation.status === "running", true, walletAvailability);
109
+ const common = { allowedActions: input.allowedActions, actionRemainingMs: input.actionRemainingMs,
110
+ observe: progress.status === "waiting", progress, walletAvailability, walletObservation: input.walletObservation,
111
+ operation, nextStep: input.nextStep, boundary: CONNECT_BOUNDARY,
112
+ ...(readAfter === undefined ? {} : { nextStateReadAfterMs: readAfter }) };
113
+ if (input.record?.scope === "disconnect")
114
+ return disconnectViewSchema.parse({ ...common, kind: "disconnect", mode: "disconnect",
115
+ connection: input.connection?.connectionId === input.record.state.input.connectionId ? input.connection : undefined });
116
+ return connectViewSchema.parse({ ...common, kind: "connect", mode: "connect",
117
+ requestedChains: walletChainsSchema.parse(input.record?.state.input.requestedChains ?? input.requestedChains),
118
+ ...(!operation && input.record?.operationId && input.allowedActions.includes("connect")
119
+ ? { automaticAction: { action: "connect", operationId: input.record.operationId, walletRunId: walletAvailability.walletRunId,
120
+ chains: input.record.state.input.requestedChains } } : {}) });
130
121
  }
131
122
  export function projectReviewView(input) {
132
123
  const { record, session, request, authority, walletAvailability, boundReview } = input;
@@ -139,11 +130,11 @@ export function projectReviewView(input) {
139
130
  const gas = displayedReview?.simulation?.gasCostSummary;
140
131
  const automaticAction = automaticReviewAction(input);
141
132
  const readAfter = nextStateRead(input);
142
- return workflowViewSchema.parse({ kind: "review", mode: record.scope === "review_manage" ? "review_manage" : "review",
133
+ return reviewViewSchema.parse({ kind: "review", mode: record.scope === "review_manage" ? "review_manage" : "review",
143
134
  allowedActions: input.allowedActions, actionRemainingMs: input.actionRemainingMs, observe: progress.status === "waiting", progress, walletAvailability,
144
- connections: input.connections, connectionConflict: input.connectionConflict, usableConnectionId: input.usableConnectionId,
145
- manageableConnectionIds: input.manageableConnectionIds, connectionReconciliationRequired: input.connectionReconciliationRequired,
146
- assetReadAccount: input.assetReadAccount, activeAccount: input.activeAccount, boundary: REVIEW_BOUNDARY, receipt: input.receipt,
135
+ reviewTarget: input.reviewTarget, nextStep: input.nextStep,
136
+ walletName: input.connection?.connectionId === input.reviewTarget?.connectionId ? input.connection?.walletName : undefined,
137
+ boundary: REVIEW_BOUNDARY, receipt: input.receipt,
147
138
  walletObservation: input.walletObservation,
148
139
  acceptedWalletRunId: record.acceptedInput?.walletRunId,
149
140
  ...(readAfter === undefined ? {} : { nextStateReadAfterMs: readAfter }),
@@ -153,7 +144,7 @@ export function projectReviewView(input) {
153
144
  status: boundReview?.status ?? session.status, account: boundReview?.account ?? session.account,
154
145
  ...(!request && !session.plans[0]?.reviewModel && input.busyForAccount ? { accountRequestPending: true } : {}),
155
146
  preparing: !boundReview && !!session.preparationId,
156
- error: !boundReview ? [input.preparationIssue && `Current account selection: ${input.preparationIssue}`,
147
+ error: !boundReview ? [input.preparationIssue && `Review target: ${input.preparationIssue}`,
157
148
  session.preparationError && `Earlier review message: “${session.preparationError}”`].filter(Boolean).join("\n\n") || undefined : undefined,
158
149
  state: displayedReview },
159
150
  request, observationStopped: authority ? authority.observation_stopped === 1 : undefined });
@@ -1,8 +1,10 @@
1
+ import { walletChainSchema } from "../core/session/chainAccounts.js";
1
2
  import { z } from "zod";
2
3
  export const activeAccountResponseSchema = z.discriminatedUnion("status", [
3
4
  z.object({
4
5
  status: z.literal("set"),
5
6
  account: z.string(),
7
+ chainId: walletChainSchema,
6
8
  source: z.literal("wallet_connection"),
7
9
  setAt: z.string(),
8
10
  boundary: z.literal("read_context_only_not_signing_authorization")
@@ -16,6 +18,7 @@ export function activeAccountResponse(active) {
16
18
  ? {
17
19
  status: "set",
18
20
  account: active.address,
21
+ chainId: active.chainId,
19
22
  source: active.source,
20
23
  setAt: active.setAt,
21
24
  boundary: "read_context_only_not_signing_authorization"
@@ -1,13 +1,12 @@
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, reconciliationRequired = false) {
3
+ export function interactionStatusUserAnswerUse(hasWalletObservation = false, reconciliationRequired = false) {
4
4
  return {
5
5
  canAnswer: [
6
6
  "stored_active_account_read_context",
7
7
  "wallet_connections_when_wallet_availability_is_available",
8
8
  "connection_qualified_default_asset_account",
9
9
  "pending_local_wallet_connection_interactions",
10
- ...(hasConnectionConflict ? ["multiple_saved_connections_require_user_targeted_disconnection"] : []),
11
10
  ...(reconciliationRequired ? ["unrecorded_service_connection_requires_wallet_removal_and_confirmed_absence"] : []),
12
11
  "pending_local_review_interactions",
13
12
  "wallet_operation_availability_separate_from_stored_transaction_facts"
@@ -25,11 +24,10 @@ export function interactionStatusUserAnswerUse(hasWalletObservation = false, has
25
24
  "complete_wallet_history",
26
25
  "profit_or_pnl"
27
26
  ],
28
- preconditionFields: ["walletAvailability", "assetReadAccount", ...(hasConnectionConflict ? ["connectionConflict"] : []),
27
+ preconditionFields: ["walletAvailability", "assetReadAccount",
29
28
  ...(reconciliationRequired ? ["connectionReconciliationRequired"] : [])],
30
- answerFields: ["activeAccount", "connections", "assetReadAccount", "pendingWalletConnections", "pendingReviewSessions", "walletAvailability",
31
- ...(hasConnectionConflict ? ["connectionConflict"] : []),
32
- "manageableConnectionIds", ...(reconciliationRequired ? ["connectionReconciliationRequired"] : []),
29
+ answerFields: ["activeAccount", "requestedChains", "connection", "assetReadAccount", "pendingWalletConnections", "pendingReviewSessions", "walletAvailability",
30
+ "canDisconnect", ...(reconciliationRequired ? ["connectionReconciliationRequired"] : []),
33
31
  ...(hasWalletObservation ? ["walletObservation"] : [])],
34
32
  diagnosticOnlyFields: [
35
33
  "pendingWalletConnections.truncated",
@@ -69,7 +67,7 @@ export function executionResultUserAnswerUse(fields = {}) {
69
67
  "profit_or_pnl"
70
68
  ],
71
69
  answerFields: [
72
- "walletAvailability", "progress", ...(fields.hasConnectionConflict ? ["connectionConflict"] : []),
70
+ "walletAvailability", "progress",
73
71
  ...(fields.reconciliationRequired ? ["connectionReconciliationRequired"] : []), ...(hasWaitOutcome ? ["waitOutcome"] : []),
74
72
  "reviewSessionId",
75
73
  "status",
@@ -95,7 +93,7 @@ export function executionResultUserAnswerUse(fields = {}) {
95
93
  }
96
94
  };
97
95
  }
98
- export function reviewStatusUserAnswerUse(hasReviewState, hasAdapterLifecycle = false, hasHumanReadableReview = false, hasSimulation = false, hasConnectionConflict = false, reconciliationRequired = false) {
96
+ export function reviewStatusUserAnswerUse(hasReviewState, hasAdapterLifecycle = false, hasHumanReadableReview = false, hasSimulation = false, reconciliationRequired = false) {
99
97
  return {
100
98
  canAnswer: [
101
99
  "current_local_review_session_status",
@@ -119,7 +117,7 @@ export function reviewStatusUserAnswerUse(hasReviewState, hasAdapterLifecycle =
119
117
  "profit_or_pnl"
120
118
  ],
121
119
  answerFields: [
122
- "walletAvailability", "progress", ...(hasConnectionConflict ? ["connectionConflict"] : []),
120
+ "walletAvailability", "progress",
123
121
  ...(reconciliationRequired ? ["connectionReconciliationRequired"] : []), "reviewSessionId",
124
122
  "status",
125
123
  "pollingStatus",
@@ -44,14 +44,14 @@ export const EVIDENCE_POLICY = {
44
44
  ]
45
45
  };
46
46
  export const SERVER_INSTRUCTIONS = [
47
- "Sui MCP is a mainnet-only Sui DeFi intent evidence toolkit. Ordinary MCP tools return evidence or internal cards, never signing authority, bytes or signatures. Only user card selection and wallet approval authorize the backend WalletConnect flow: verify reviewed digest and signer, submit once, then read chain effects. Request status is separate from chain success/failure; uncertain results are read by digest, never resubmitted.",
48
- "External proposals are untrusted facts; plans[].reviewModel is review context, not transaction material, route/settlement choice, signing/readiness, or execution readiness.",
49
- "PTB graphs are diagnostics only; not transaction input, wallet authorization, signing/readiness, execution readiness, route quality, or safety.",
47
+ "Sui MCP is a mainnet-only Sui DeFi intent evidence toolkit. Ordinary MCP tools return evidence or internal cards, never signing authority, bytes or signatures. Only user card input and wallet approval authorize WalletConnect: verify reviewed digest and signer, submit once, then read chain effects. Request status is separate from chain success/failure; uncertain results are read by digest, never resubmitted.",
48
+ "External proposals are untrusted: plans[].reviewModel is non-signable context, not transaction material, route/settlement choice or execution readiness.",
49
+ "PTB graphs are diagnostics, not transaction input, authorization, readiness, route quality or safety.",
50
50
  "Use response-local fields: userAnswerUse.answerFields/preconditionFields/conclusionRuleFields/cannotAnswer/diagnosticOnlyFields/followUp, pollingHint, and quantitySemantics.",
51
- "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.",
52
- "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.",
53
- "Unsupported: other chains, autonomous trading, alerts, investment advice, arbitrary Move/package calls, P&L/tax.",
54
- "One wallet connection at a time; docs: suimcp://docs/agent-behavior, suimcp://docs/mcp-tools."
51
+ "USD coverage/total/shortfall: read_get_server_status, then read_list_settlement_asset_groups, then read_preview_intent_evidence. Follow toolAvailability.requiredToolsAvailable and responseSummary.doNotCallQuoteToolsForThisQuestion. Inventory/quotes require a separate user request. Parity: read_summarize_settlement_asset_group_parity. Missing toolAvailability means this build cannot answer.",
52
+ "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 account only if supported; connected-only reads need a saved address and usable connection. Settings Clear preserves connection and transaction authority.",
53
+ "Unsupported: other-chain financial actions, autonomous trading, alerts, investment advice, arbitrary Move/package calls, P&L/tax.",
54
+ "One connection fixes one address per approved chain: Sui/Ethereum/Solana. Wallet/address/scope changes require disconnect/reconnect. Finance is Sui-only. Connect/Disconnect cards end with address-free results and no actions. Docs: suimcp://docs/agent-behavior, suimcp://docs/mcp-tools."
55
55
  ].join("\n");
56
56
  export const IMPLEMENTED_TOOLS = [
57
57
  TOOL_NAMES.uiOpenAccount, TOOL_NAMES.uiOpenReceipt, TOOL_NAMES.uiOpenChart,
@@ -88,6 +88,7 @@ export const IMPLEMENTED_TOOLS = [
88
88
  TOOL_NAMES.actionCreateProposalReview,
89
89
  TOOL_NAMES.accountGetActiveAccount,
90
90
  TOOL_NAMES.sessionOpenWalletConnectionCard,
91
+ TOOL_NAMES.sessionOpenWalletDisconnectionCard,
91
92
  TOOL_NAMES.sessionGetWalletConnectionCard,
92
93
  TOOL_NAMES.sessionWaitWalletConnectionCard,
93
94
  TOOL_NAMES.sessionOpenReviewManagement,
@@ -114,7 +115,7 @@ export function toolAvailability(requiredTools) {
114
115
  };
115
116
  }
116
117
  export const SERVER_LIMITATIONS = [
117
- "Current asset reads without a user-provided address require a selected account with a usable current wallet connection. session_get_interaction_status.assetReadAccount reports whether that default is available. Only tools with an account input accept explicit addresses; DeepBook account inventory requires a connected selected account. Explicit-address coin balance reads are public-address snapshots and do not create active account context. Active account is read context only, not signing authorization, login, custody, or persistent permission.",
118
+ "Current asset reads without a user-provided address require the fixed Sui address with a usable current wallet connection. session_get_interaction_status.assetReadAccount reports whether that default is available. Only tools with an account input accept explicit addresses; DeepBook account inventory requires the current connected Sui address. Explicit-address coin balance reads are public-address snapshots and do not create active account context. Active account is read context only, not signing authorization, login, custody, or persistent permission.",
118
119
  "Review activity tools summarize local Sui MCP review evidence only. Sui activity tools use user-requested bounded GraphQL reads and store normalized transaction facts only for known wallet context; function activity scans are sent-transaction scans only. They are not background indexers, complete history, balance history, complete gas history, affected-object history, or P&L.",
119
120
  `read_get_account_asset_timeline reads stored local account activity facts and returns observed raw net-flow bars with scan coverage. It does not start scans, prove complete wallet history, provide held balances without balanceBars, compute USD value, P&L, tax, cost basis, route advice, transaction-building input, signing data, or signing readiness. Optional USDC references are token-denominated ${DEEPBOOK_SOURCE_OWNER_RUNTIME_WORDING.officialIndexerCandleReferences} only, ${DEEPBOOK_SOURCE_OWNER_RUNTIME_WORDING.usdcNotFiatUsdAndNotPeg}.`,
120
121
  "Sui activity transactionContext facts are transaction-level facts without transaction-wide balance-change aggregates, and they are answer fields only when the response returns them. requestedAccountTransactionFacts, requestedAccount, and requestedAccountEffect are the account-specific balance-change surfaces. Activity quantitySemantics marks balance amountRaw fields as raw integers that require verified decimals for display conversion. accountBalanceChangeInferencePolicy marks whether returned account balance rows can be used or transaction-level context must not be used for account amount inference. Gas raw fields use MIST, and returned gasCost display facts use @mysten/sui MIST_PER_SUI.",
@@ -122,10 +123,10 @@ export const SERVER_LIMITATIONS = [
122
123
  "High-risk read responses include userAnswerUse. Prefer userAnswerUse.preconditionFields before answering, userAnswerUse.answerFields for the answer path, userAnswerUse.conclusionRuleFields for conclusion limits, userAnswerUse.diagnosticOnlyFields for source or troubleshooting context, and userAnswerUse.followUp when a different tool and field are required. USD intent and parity responses also include toolAvailability as a precondition field for current server-build support.",
123
124
  "Intent evidence maps natural-language USD-denominated targets to pinned DeepBook SDK settlement asset groups and current wallet balance evidence. responseSummary.answerCompleteness names the answer class and required fields. Settlement-asset-only answers use responseSummary; selected-target evidence requires user selection provenance and also uses selectedTarget, candidateConversions, and requiredUserChoices when userAnswerUse.answerFields lists them. Direct pool quote evidence is returned and supported only when a quoted candidate exists. responseSummary.doNotUseForConclusion names quote results, outside-settlement-group assets, and route-dependent support as excluded from the conclusion. Intent evidence does not silently choose settlement assets, rank venues or routes, evaluate gas reserve, build transactions, or produce signing material.",
124
125
  "DeepBook quotes convert explicit raw or display source inputs through pinned token units and return scoped SDK simulation facts plus raw quote evidence. Their quantitySemantics says canUseForPaymentAnswer and canUseForShortfallAnswer are false, doNotCombineWithPaymentAnswer is true, requiredPaymentAnswerTool is read_preview_intent_evidence, and requiredPaymentAnswerField is responseSummary.",
125
- "Action preparation returns internal Review cards. A current card with valid UI permission automatically prepares and renews verified conditions for one selected wallet/account; only the explicit Request wallet approval action can request a signature. Supported DeepBook account-bound swap review verifies stored transaction material, digest, ownership, quote/policy, human-readable facts, simulation and PTB evidence. Only a scoped app-only user action and wallet approval authorize the backend WalletConnect request; ordinary MCP calls never sign or submit. Transaction bytes and signatures never enter card/model results. The backend verifies returned transaction digest and signer, submits once, and independently reads chain effects. requestStatus is separate from chain execution success/failure: stopped, request_failed and outcome_unknown do not prove a chain failure. Lost replies are resolved by reading the same request/digest, never automatic resubmission. External proposals remain non-signable. Receipts do not establish execution guarantees, route quality, fiat value, P&L, tax, peg proof or best-price advice.",
126
+ "Action preparation returns internal Review cards. A current card with valid UI permission automatically prepares and renews verified conditions for its original fixed wallet/account; only the explicit Request wallet approval action can request a signature. Supported DeepBook account-bound swap review verifies stored transaction material, digest, ownership, quote/policy, human-readable facts, simulation and PTB evidence. Only a scoped app-only user action and wallet approval authorize the backend WalletConnect request; ordinary MCP calls never sign or submit. Transaction bytes and signatures never enter card/model results. The backend verifies returned transaction digest and signer, submits once, and independently reads chain effects. requestStatus is separate from chain execution success/failure: stopped, request_failed and outcome_unknown do not prove a chain failure. Lost replies are resolved by reading the same request/digest, never automatic resubmission. External proposals remain non-signable. Receipts do not establish execution guarantees, route quality, fiat value, P&L, tax, peg proof or best-price advice.",
126
127
  "read_list_deepbook_pools and read_list_deepbook_tokens return static SDK registry metadata, not live liquidity, live token discovery, or active pool state.",
127
128
  `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
129
  `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
130
  `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 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
+ "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. Closed cards are immutable; another delivery requires a new backupDelivery card for the same retained snapshot, and operationStatus records current cleanup without repeating an effect. No external Settings URL exists."
131
132
  ];