@stelis/say-ur-intent 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -8
- package/dist/core/action/contractNameRegistry.js +42 -0
- package/dist/core/action/ptbVisualizationProducer.js +6 -1
- package/dist/core/action/signableAdapterContract.js +2 -1
- package/dist/core/activity/sqliteActivityStore.js +36 -2
- package/dist/core/activity/sqliteActivityStoreSchema.js +54 -0
- package/dist/core/session/keyedRecordStore.js +18 -0
- package/dist/core/session/sessionRecordStore.js +33 -0
- package/dist/core/session/sessionStore.js +28 -11
- package/dist/core/session/settingsSessions.js +3 -1
- package/dist/core/session/sqliteSessionStore.js +175 -0
- package/dist/core/session/sqliteTransactionMaterialStore.js +64 -0
- package/dist/core/session/transactionMaterialStore.js +60 -43
- package/dist/core/session/walletIdentitySessions.js +4 -2
- package/dist/review-app/analysis.js +1 -1
- package/dist/review-app/mermaid-parser.core-DkwUYTPl.js +1 -1
- package/dist/review-app/review.js +32 -32
- package/dist/review-app/walletStatus-YODe5Y4P.js +7 -0
- package/dist/runtime/reviewServerAcquire.js +68 -81
- package/dist/runtime/smokeMainnetRead.js +8 -5
- package/dist/runtime/start.js +16 -16
- package/docs/AGENT_BEHAVIOR.md +2 -2
- package/docs/FRONTEND_POLICY.md +12 -2
- package/docs/LOCAL_DB_ARCHITECTURE.md +24 -1
- package/docs/MCP_SETUP.md +4 -3
- package/docs/MCP_TOOLS.md +2 -2
- package/docs/SDK_API.md +1 -1
- package/package.json +2 -2
- package/protocols/deepbook-v3.md +4 -4
- package/dist/review-app/walletStatus-CcojOdGy.js +0 -7
|
@@ -1,75 +1,88 @@
|
|
|
1
|
+
const DEFAULT_REACQUIRE_INTERVAL_MS = 3000;
|
|
1
2
|
function errorCode(error) {
|
|
2
3
|
return typeof error === "object" && error !== null
|
|
3
4
|
? error.code
|
|
4
5
|
: undefined;
|
|
5
6
|
}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
*
|
|
10
|
-
* Safety: the holder is positively identified over loopback before any signal is
|
|
11
|
-
* sent. A process that is not our review server is never signalled; a same-name
|
|
12
|
-
* instance owned by another OS user surfaces a clear error rather than a kill.
|
|
13
|
-
* The port is never silently reassigned, so the wallet autoconnect origin stays
|
|
14
|
-
* stable.
|
|
15
|
-
*/
|
|
16
|
-
export async function startReviewServerWithTakeover(start, port, deps) {
|
|
7
|
+
// Bind the port: returns the server on success, undefined when the port is already
|
|
8
|
+
// in use, and rethrows any other (non-EADDRINUSE) startup error.
|
|
9
|
+
async function tryStart(start, port) {
|
|
17
10
|
try {
|
|
18
11
|
return await start(port);
|
|
19
12
|
}
|
|
20
13
|
catch (error) {
|
|
21
|
-
if (errorCode(error)
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
const holder = await deps.probeIdentity(port);
|
|
25
|
-
if (!holder || holder.service !== deps.serviceName) {
|
|
26
|
-
throw new Error(`Review server port ${port} is already in use by a process that is not a ${deps.serviceName} review server. ` +
|
|
27
|
-
`Set SAY_UR_INTENT_REVIEW_PORT to a free port. The port is not reassigned automatically so the wallet autoconnect origin stays stable.`);
|
|
14
|
+
if (errorCode(error) === "EADDRINUSE") {
|
|
15
|
+
return undefined;
|
|
28
16
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Bind the review server to its fixed port, or defer to a healthy peer already
|
|
22
|
+
* serving it.
|
|
23
|
+
*
|
|
24
|
+
* - Port free → bind and own the single review origin.
|
|
25
|
+
* - Port held by a separate healthy instance of our review server → defer (run no
|
|
26
|
+
* local server; the peer serves the shared database for every client) and watch for
|
|
27
|
+
* the owner to exit, then take the origin over. No process is ever signalled.
|
|
28
|
+
* - Port held by anything else (foreign, no identity answer, or our own pid) → clear
|
|
29
|
+
* error; the origin is never silently reassigned.
|
|
30
|
+
*/
|
|
31
|
+
export async function startOrDeferReviewServer(start, port, deps) {
|
|
32
|
+
const owned = await tryStart(start, port);
|
|
33
|
+
if (owned) {
|
|
34
|
+
deps.logger.info("review server bound; owning the review origin", { port });
|
|
35
|
+
return { deferred: false, close: () => owned.close() };
|
|
36
|
+
}
|
|
37
|
+
const holder = await deps.probeIdentity(port);
|
|
38
|
+
if (!holder || holder.service !== deps.serviceName || holder.pid === deps.currentPid) {
|
|
39
|
+
throw new Error(`Review server port ${port} is already in use by a process that is not a separate ${deps.serviceName} review server. ` +
|
|
40
|
+
`Set SAY_UR_INTENT_REVIEW_PORT to a free port. The port is not reassigned automatically so the wallet autoconnect origin stays stable.`);
|
|
41
|
+
}
|
|
42
|
+
deps.logger.info("review port owned by a healthy peer; deferring and watching for takeover", {
|
|
43
|
+
port,
|
|
44
|
+
ownerPid: holder.pid,
|
|
45
|
+
...(holder.version ? { ownerVersion: holder.version } : {})
|
|
46
|
+
});
|
|
47
|
+
const reacquireIntervalMs = deps.reacquireIntervalMs ?? DEFAULT_REACQUIRE_INTERVAL_MS;
|
|
48
|
+
let stopped = false;
|
|
49
|
+
let acquired;
|
|
50
|
+
const watch = (async () => {
|
|
51
|
+
while (!stopped && !acquired) {
|
|
52
|
+
await deps.delay(reacquireIntervalMs);
|
|
53
|
+
if (stopped || acquired) {
|
|
54
|
+
break;
|
|
43
55
|
}
|
|
44
|
-
|
|
45
|
-
|
|
56
|
+
const next = await tryStart(start, port);
|
|
57
|
+
if (!next) {
|
|
58
|
+
continue; // a peer still owns the port; keep deferring
|
|
46
59
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const waitForReleaseMs = deps.waitForReleaseMs ?? 3000;
|
|
51
|
-
const pollIntervalMs = deps.pollIntervalMs ?? 100;
|
|
52
|
-
const attempts = Math.max(1, Math.ceil(waitForReleaseMs / pollIntervalMs));
|
|
53
|
-
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
54
|
-
await deps.delay(pollIntervalMs);
|
|
55
|
-
try {
|
|
56
|
-
return await start(port);
|
|
60
|
+
if (stopped) {
|
|
61
|
+
await next.close();
|
|
62
|
+
break;
|
|
57
63
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
64
|
+
acquired = next;
|
|
65
|
+
deps.logger.info("acquired review port after the previous owner exited", { port });
|
|
66
|
+
}
|
|
67
|
+
})();
|
|
68
|
+
watch.catch(() => undefined);
|
|
69
|
+
return {
|
|
70
|
+
deferred: true,
|
|
71
|
+
close: async () => {
|
|
72
|
+
// Stop watching; the loop's stopped-check closes any bind that lands in-flight,
|
|
73
|
+
// so we never await the (possibly mid-delay) watch loop here.
|
|
74
|
+
stopped = true;
|
|
75
|
+
if (acquired) {
|
|
76
|
+
await acquired.close();
|
|
62
77
|
}
|
|
63
78
|
}
|
|
64
|
-
|
|
65
|
-
`Set SAY_UR_INTENT_REVIEW_PORT to a free port.`);
|
|
66
|
-
}
|
|
79
|
+
};
|
|
67
80
|
}
|
|
68
81
|
/**
|
|
69
|
-
* Ask whatever is listening on the loopback review port to identify itself. Only
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
82
|
+
* Ask whatever is listening on the loopback review port to identify itself. Only our
|
|
83
|
+
* own review server answers `/__identity`; any other process either returns something
|
|
84
|
+
* else or does not answer, and we report it as "not ours" so the caller never defers
|
|
85
|
+
* to it.
|
|
73
86
|
*/
|
|
74
87
|
export async function probeReviewServerIdentity(port, host = "127.0.0.1", timeoutMs = 1000) {
|
|
75
88
|
try {
|
|
@@ -100,29 +113,3 @@ export async function probeReviewServerIdentity(port, host = "127.0.0.1", timeou
|
|
|
100
113
|
return null;
|
|
101
114
|
}
|
|
102
115
|
}
|
|
103
|
-
/**
|
|
104
|
-
* Gracefully signal a same-user process. SIGTERM lets the previous instance run
|
|
105
|
-
* its own shutdown handler (close the review server and database, then exit).
|
|
106
|
-
* Killing another OS user's process fails with EPERM, which we report instead of
|
|
107
|
-
* escalating.
|
|
108
|
-
*/
|
|
109
|
-
export function terminateProcessByPid(pid) {
|
|
110
|
-
try {
|
|
111
|
-
process.kill(pid, "SIGTERM");
|
|
112
|
-
return { ok: true };
|
|
113
|
-
}
|
|
114
|
-
catch (error) {
|
|
115
|
-
const code = errorCode(error);
|
|
116
|
-
if (code === "ESRCH") {
|
|
117
|
-
return { ok: false, reason: "not_found" };
|
|
118
|
-
}
|
|
119
|
-
if (code === "EPERM") {
|
|
120
|
-
return { ok: false, reason: "no_permission" };
|
|
121
|
-
}
|
|
122
|
-
return {
|
|
123
|
-
ok: false,
|
|
124
|
-
reason: "error",
|
|
125
|
-
message: error instanceof Error ? error.message : String(error)
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
}
|
|
@@ -9,11 +9,10 @@ import { validateSupportedAdapterLifecycle } from "../adapters/adapterLifecycleV
|
|
|
9
9
|
import { SqliteActivityStore } from "../core/activity/sqliteActivityStore.js";
|
|
10
10
|
import { TransactionActivityService } from "../core/activity/transactionActivityService.js";
|
|
11
11
|
import { createSuiReadService } from "../core/read/readService.js";
|
|
12
|
-
import {
|
|
12
|
+
import { LocalSessionStore } from "../core/session/sessionStore.js";
|
|
13
13
|
import { createMcpServer } from "../mcp/server.js";
|
|
14
14
|
import { TOOL_NAMES } from "../mcp/toolNames.js";
|
|
15
15
|
import { createReviewHttpServer } from "../review-server/server.js";
|
|
16
|
-
import { InMemoryLocalTransactionMaterialStore } from "../core/session/transactionMaterialStore.js";
|
|
17
16
|
import { buildSupportedReviewAdapters } from "../adapters/reviewAdapters.js";
|
|
18
17
|
import { createDeepbookSwapTransactionMaterialDigestProducer, createDeepbookSwapTransactionMaterialProducer } from "../adapters/deepbook/deepbookTransactionMaterialProducer.js";
|
|
19
18
|
import { createDeepbookSwapHumanReadableReviewProducer } from "../adapters/deepbook/deepbookHumanReviewProducer.js";
|
|
@@ -121,11 +120,15 @@ async function main() {
|
|
|
121
120
|
chainIdentifier,
|
|
122
121
|
coinMetadataCache: activityStore.createCoinMetadataCache()
|
|
123
122
|
});
|
|
124
|
-
const transactionMaterialStore =
|
|
125
|
-
const sessions = new
|
|
123
|
+
const transactionMaterialStore = activityStore.createTransactionMaterialStore();
|
|
124
|
+
const sessions = new LocalSessionStore({
|
|
126
125
|
activityStore,
|
|
127
126
|
logger,
|
|
128
|
-
validateAdapterLifecycle: validateSupportedAdapterLifecycle
|
|
127
|
+
validateAdapterLifecycle: validateSupportedAdapterLifecycle,
|
|
128
|
+
sessions: activityStore.createSessionRecordStore(),
|
|
129
|
+
artifacts: activityStore.createPrivateReviewArtifactStore(),
|
|
130
|
+
walletIdentityStore: activityStore.createWalletIdentityRecordStore(),
|
|
131
|
+
settingsStore: activityStore.createSettingsRecordStore()
|
|
129
132
|
});
|
|
130
133
|
reviewServer = await createReviewHttpServer({
|
|
131
134
|
host: config.reviewHost,
|
package/dist/runtime/start.js
CHANGED
|
@@ -15,13 +15,12 @@ import { createSuiReadService } from "../core/read/readService.js";
|
|
|
15
15
|
import { createTransactionObjectOwnershipProducer } from "../core/action/transactionObjectOwnershipProducer.js";
|
|
16
16
|
import { createReviewTimeSimulationProducer } from "../core/action/reviewTimeSimulationEvidence.js";
|
|
17
17
|
import { producePtbVisualizationArtifact } from "../core/action/ptbVisualizationProducer.js";
|
|
18
|
-
import {
|
|
19
|
-
import { InMemorySessionStore } from "../core/session/sessionStore.js";
|
|
18
|
+
import { LocalSessionStore } from "../core/session/sessionStore.js";
|
|
20
19
|
import { createMcpServer, startMcp } from "../mcp/server.js";
|
|
21
20
|
import { SERVER_NAME, SERVER_NETWORK, SERVER_VERSION } from "../mcp/serverInfo.js";
|
|
22
21
|
import { createReviewHttpServer } from "../review-server/server.js";
|
|
23
22
|
import { DEFAULT_SUI_GRAPHQL_URL, DEFAULT_SUI_GRPC_URL, composeRuntimeConfig, loadBootConfig } from "./config.js";
|
|
24
|
-
import { probeReviewServerIdentity,
|
|
23
|
+
import { probeReviewServerIdentity, startOrDeferReviewServer } from "./reviewServerAcquire.js";
|
|
25
24
|
import { RuntimeLocalSettingsService } from "./localSettingsService.js";
|
|
26
25
|
import { createStderrLogger } from "./logger.js";
|
|
27
26
|
import { SuiEndpointError, verifyMainnetGraphqlEndpoint, verifyMainnetGrpcEndpoint } from "./suiEndpoint.js";
|
|
@@ -81,12 +80,16 @@ async function main() {
|
|
|
81
80
|
});
|
|
82
81
|
}
|
|
83
82
|
});
|
|
84
|
-
const transactionMaterialStore =
|
|
85
|
-
const sessions = new
|
|
83
|
+
const transactionMaterialStore = store.createTransactionMaterialStore();
|
|
84
|
+
const sessions = new LocalSessionStore({
|
|
86
85
|
activityStore: store,
|
|
87
86
|
transactionMaterialStore,
|
|
88
87
|
logger,
|
|
89
|
-
validateAdapterLifecycle: validateSupportedAdapterLifecycle
|
|
88
|
+
validateAdapterLifecycle: validateSupportedAdapterLifecycle,
|
|
89
|
+
sessions: store.createSessionRecordStore(),
|
|
90
|
+
artifacts: store.createPrivateReviewArtifactStore(),
|
|
91
|
+
walletIdentityStore: store.createWalletIdentityRecordStore(),
|
|
92
|
+
settingsStore: store.createSettingsRecordStore()
|
|
90
93
|
});
|
|
91
94
|
const readService = createSuiReadService({
|
|
92
95
|
client: suiClient,
|
|
@@ -169,22 +172,19 @@ async function main() {
|
|
|
169
172
|
network: SERVER_NETWORK
|
|
170
173
|
}
|
|
171
174
|
});
|
|
172
|
-
// The review origin is a single-port singleton
|
|
173
|
-
// the fixed port
|
|
174
|
-
//
|
|
175
|
-
|
|
175
|
+
// The review origin is a single-port singleton shared through the local database:
|
|
176
|
+
// whichever process owns the fixed port serves every client. A second instance
|
|
177
|
+
// defers to a healthy peer (no signals, no port war) and takes the origin over
|
|
178
|
+
// only if that peer exits.
|
|
179
|
+
const reviewServer = await startOrDeferReviewServer((port) => reviewServerFactory.start(port), config.reviewPort, {
|
|
176
180
|
probeIdentity: (probePort) => probeReviewServerIdentity(probePort, config.reviewHost),
|
|
177
|
-
terminate: terminateProcessByPid,
|
|
178
181
|
delay: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
179
182
|
currentPid: process.pid,
|
|
180
183
|
serviceName: SERVER_NAME,
|
|
181
184
|
logger
|
|
182
185
|
});
|
|
183
186
|
reviewServerForCleanup = reviewServer;
|
|
184
|
-
logger.info("review server started", {
|
|
185
|
-
host: reviewServer.host,
|
|
186
|
-
port: reviewServer.port
|
|
187
|
-
});
|
|
187
|
+
logger.info(reviewServer.deferred ? "review server deferring to a healthy peer on the shared origin" : "review server started", { host: config.reviewHost, port: config.reviewPort, deferred: reviewServer.deferred });
|
|
188
188
|
let shuttingDown = false;
|
|
189
189
|
const shutdown = async (signal) => {
|
|
190
190
|
logger.info("shutdown requested", { signal });
|
|
@@ -236,7 +236,7 @@ async function main() {
|
|
|
236
236
|
promptSurfaces: ADAPTER_PROMPT_SURFACES,
|
|
237
237
|
sessions,
|
|
238
238
|
activityStore: store,
|
|
239
|
-
reviewBaseUrl: `http://${
|
|
239
|
+
reviewBaseUrl: `http://${config.reviewHost}:${config.reviewPort}`,
|
|
240
240
|
readService,
|
|
241
241
|
transactionActivityService: new TransactionActivityService({
|
|
242
242
|
activityStore: store,
|
package/docs/AGENT_BEHAVIOR.md
CHANGED
|
@@ -15,11 +15,11 @@ It is not the contributor rulebook and it is not enforcement. Development rules
|
|
|
15
15
|
| External proposal review sessions | Non-signable review in the current release | `action.prepare_external_proposal_review` can create a review URL from a structured external payment or Sui action proposal. Treat the proposal as untrusted display and review context only. It does not build, verify, simulate, sign, or execute transaction material. |
|
|
16
16
|
| Wallet signing | User-controlled on the local review page | MCP tools do not return signing readiness, signing data, or executable transaction bytes. Signing and execution happen only in the user's wallet from the local review page after the digest-gated handoff; results are recorded as execution receipts keyed by the review session. |
|
|
17
17
|
| PTB visualization | Rendered with emitted wallet review contracts | `reviewState.ptbVisualization` can accompany an emitted wallet review contract as a Mermaid flowchart of the stored local transaction shape. Treat it as visualization evidence only, not transaction-building input, wallet authorization, signing data, signing readiness, payment execution readiness, or route recommendation. |
|
|
18
|
-
| Transaction material build, wallet execution, fiat cash-out, and P&L |
|
|
18
|
+
| Transaction material build, wallet execution, fiat cash-out, and P&L | Material build and review evidence implemented for DeepBook and FlowX swap review; MCP signing and execution out of scope; fiat cash-out and P&L out of scope | Account-bound DeepBook and FlowX swap review can build local unsigned transaction material that remains internal to the review server, internally bind a Sui transaction digest to that stored material, project human-readable review facts from material-bound quote policy and object ownership evidence, and summarize review-time simulation of the stored material. The MCP layer does not request wallet signatures, execute, or return transaction bytes; wallet signing and execution happen only in the user's wallet from the local review page after the digest-gated byte handoff, which is gated on recomputed-digest equality with the reviewed contract. Fiat cash-out, P&L, tax, and cost-basis are out of scope. |
|
|
19
19
|
| Private-key custody, autonomous trading, fiat USD peg claims, and quote-only coverage or readiness claims | Unsupported safety and correctness boundary | Do not custody funds, hold private keys, or autonomously trade. Do not treat settlement assets as fiat USD, bank cash-out amounts, or peg guarantees. Do not turn quote-only conversion candidates into payment coverage, funding readiness, payment execution readiness, or signing readiness. |
|
|
20
20
|
| Silent settlement-token selection and route ranking | Out of scope by current design | Do not silently choose USDC, USDT, or another settlement token for the user. Do not rank venues, choose routes, or make best-price recommendations. |
|
|
21
21
|
| Other chains, autonomous trading, alerts, arbitrary Move calls, investment advice | Unsupported | Say the request is unsupported and redirect to available Sui mainnet read or review capabilities. |
|
|
22
|
-
| Payment execution | Not
|
|
22
|
+
| Payment execution | Not a current capability | Intent evidence is separate from execution and does not build or execute payments. Do not describe payment execution as available. |
|
|
23
23
|
| Lending, staking, relative balance actions | Unsupported | Do not describe executable actions for these categories as available product functionality. |
|
|
24
24
|
|
|
25
25
|
## Meta Principles
|
package/docs/FRONTEND_POLICY.md
CHANGED
|
@@ -118,6 +118,11 @@ Review checks are generally details. Failed checks and warning checks that deter
|
|
|
118
118
|
If the server returns a `PtbVisualizationArtifact`, the frontend may render only
|
|
119
119
|
Mermaid flowchart text plus diagnostics. The panel must show the generated time,
|
|
120
120
|
source, diagnostics, and unsupported-use boundary when those fields are present.
|
|
121
|
+
The Mermaid graph may show a registered Move Registry package name in place of a
|
|
122
|
+
registered package address, with a control to switch back to raw addresses and a
|
|
123
|
+
copyable Mermaid source that keeps raw addresses; that name is a package identity
|
|
124
|
+
label, not a safety, trust, route-quality, or signing-readiness signal, and any
|
|
125
|
+
package that is not registered keeps its raw address.
|
|
121
126
|
It must not store or render executable transaction material, wallet signature
|
|
122
127
|
requests, private-key material, or arbitrary Move calls. A PTB graph is not a
|
|
123
128
|
sign action, not a transaction-building action, not a wallet readiness signal,
|
|
@@ -139,7 +144,12 @@ wallet review contract, a connected wallet whose account equals the reviewed
|
|
|
139
144
|
account, and a successful digest-gated handoff. The signing step shows no wallet picker:
|
|
140
145
|
dapp-kit autoconnect restores the wallet recorded for the active account on the
|
|
141
146
|
fixed-port origin, and the sign action stays gated on the connected account
|
|
142
|
-
matching the reviewed account.
|
|
147
|
+
matching the reviewed account. When autoconnect cannot establish a connection -
|
|
148
|
+
for example after a reload or in a new tab, or for a hardware signer whose
|
|
149
|
+
device session is not restored automatically - the signing step may offer a
|
|
150
|
+
targeted reconnect for the one recorded wallet. That reconnect is not a wallet
|
|
151
|
+
picker: it resumes the recorded wallet's signer session, and the sign action
|
|
152
|
+
stays gated on the connected account matching the reviewed account. While a handoff is outstanding the server locks the session
|
|
143
153
|
(state recomputes are refused) and the page shows a signing-in-progress state
|
|
144
154
|
whose only action is cancel. Other states render:
|
|
145
155
|
|
|
@@ -210,7 +220,7 @@ Wallet and review screens must be keyboard reachable and screen-reader labeled.
|
|
|
210
220
|
|
|
211
221
|
Native push notifications are out of scope. A frontend may use low-authority browser affordances such as document title changes for off-tab terminal results, but only after server status changes.
|
|
212
222
|
|
|
213
|
-
Each session URL represents one independent tab surface. Cross-tab state sharing is out of scope unless a shared local store is explicitly designed.
|
|
223
|
+
Each session URL represents one independent tab surface. Cross-tab state sharing is out of scope unless a shared local store is explicitly designed. That shared local store is now explicitly designed — the shared SQLite database (see docs/LOCAL_DB_ARCHITECTURE.md) — so live review and session state is shared across local clients through it, while the frontend still does not share state directly between browser tabs.
|
|
214
224
|
|
|
215
225
|
## Out Of Scope
|
|
216
226
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Local DB Architecture
|
|
2
2
|
|
|
3
|
-
Say Ur Intent uses a local SQLite database for durable product state that must survive MCP server restarts. The database stores account read context, Say Ur Intent review activity evidence, and user-requested bounded Sui activity facts. It is not a custody store, wallet authorization store, background indexer, complete wallet-history store, or raw transaction archive.
|
|
3
|
+
Say Ur Intent uses a local SQLite database for durable product state that must survive MCP server restarts. The database stores account read context, Say Ur Intent review activity evidence, live review and session state shared across local AI clients, and user-requested bounded Sui activity facts. It is not a custody store, wallet authorization store, background indexer, complete wallet-history store, or raw transaction archive.
|
|
4
4
|
|
|
5
5
|
This document is for maintainers and contributors who change local state, import/export behavior, activity queries, or review evidence storage. Product users normally need only the README and `docs/MCP_SETUP.md`.
|
|
6
6
|
|
|
@@ -53,6 +53,15 @@ WAL mode can create companion files next to the main database, such as `say-ur-i
|
|
|
53
53
|
- `local_settings`: allowlisted local settings. The current key set is `suiGrpcUrl` and `suiGraphqlUrl`, stored as JSON-encoded text and applied after restart.
|
|
54
54
|
- `coin_metadata_cache`: account-independent positive cache for Sui coin metadata used only to format wallet balance display amounts. Rows are keyed by normalized coin type and verified mainnet chain identifier, expire after 24 hours, and are excluded from local data export/import. Read or write failures for this cache block affected wallet unit reads with `metadata_cache_unavailable`; they are not reported as unavailable token decimals.
|
|
55
55
|
|
|
56
|
+
The following live session tables hold runtime session state in the shared database so any one review server can serve a session another client created (see [Shared single-origin review server](#shared-single-origin-review-server)):
|
|
57
|
+
|
|
58
|
+
- `live_review_sessions`: the live review session record — status, bound account, the pending wallet-handoff lock, the plan / review-state / execution-result JSON, and timestamps — keyed by session id.
|
|
59
|
+
- `live_private_review_artifacts`: per-session private review evidence (the transaction-material handle, its digest commitment, and derived evidence), cascade-deleted with its review session.
|
|
60
|
+
- `live_transaction_materials`: locally built unsigned transaction bytes stored as a BLOB behind a redacted handle, with a TTL; deleted on signing, terminal result, or expiry.
|
|
61
|
+
- `live_wallet_identity_sessions` and `live_settings_sessions`: the short-lived wallet-identity and settings capture sessions, stored as validated JSON keyed by session id.
|
|
62
|
+
|
|
63
|
+
These live tables are distinct from the append-only review evidence tables above: the evidence tables remain the durable activity/audit record, while the live tables hold the in-flight session state that the review server serves.
|
|
64
|
+
|
|
56
65
|
Database columns use snake_case. MCP and HTTP JSON fields use camelCase, so the database `review_session_id` column stores the same review-session identity exposed as `reviewSessionId` in API responses.
|
|
57
66
|
|
|
58
67
|
Table relationships:
|
|
@@ -72,6 +81,20 @@ Logical local data reset is the local settings page action that clears stored pr
|
|
|
72
81
|
|
|
73
82
|
Non-terminal review session expiry is recorded lazily when the session is read or mutated after its TTL. There is no background expiry worker.
|
|
74
83
|
|
|
84
|
+
## Shared single-origin review server
|
|
85
|
+
|
|
86
|
+
All live session state lives in this one shared database, so multiple local AI clients (for example Claude and Codex running at the same time) share it. Exactly one process binds the fixed review port and serves every client's review pages from the shared database. A second client's MCP does not take the port over; it defers to that healthy peer and takes the origin over only if the owner exits. Because whichever process owns the port reads and writes the same session rows, a review created by one client is servable by the review server of another, and the single fixed origin keeps the browser wallet autoconnect stable.
|
|
87
|
+
|
|
88
|
+
Cross-process writes rely on WAL plus `PRAGMA busy_timeout`. The one write that must be atomic across processes — the wallet handoff lock — is a single conditional `UPDATE` on `live_review_sessions.pending_handoff_digest`, so two processes cannot hand off two different transactions for the same session.
|
|
89
|
+
|
|
90
|
+
### Unsigned transaction material on disk
|
|
91
|
+
|
|
92
|
+
`live_transaction_materials` stores locally built unsigned transaction bytes so a review can be signed by whichever process owns the port, not only the process that built it. Because these bytes are now on disk in the shared database rather than only in one process's memory, the data directory is created `0700` and the database file is set `0600` so other operating-system users cannot read them; the bytes carry a short TTL and are deleted on signing, terminal result, or expiry. This store is separate from the review evidence path: the MCP tool layer still does not return transaction bytes, and the activity-store evidence inputs still reject transaction bytes, signatures, and signing material before write.
|
|
93
|
+
|
|
94
|
+
### Schema versioning across shared clients
|
|
95
|
+
|
|
96
|
+
The live session tables are added without changing `user_version`. A runtime that does not know a live table simply ignores it, so a newer client can introduce live tables while an older client keeps opening the same shared database. Only a change that an older runtime cannot safely read should raise `user_version`, and such a change requires updating every client that shares the database, because a runtime does not open a database whose `user_version` is newer than it supports.
|
|
97
|
+
|
|
75
98
|
## Boundaries
|
|
76
99
|
|
|
77
100
|
The active account is a read context only. It is not signing authorization, login, authentication for transactions, custody, permission for transactions, or proof of ownership. It stores at most one address per database file; setting a new active account replaces the previous one without revoking anything onchain.
|
package/docs/MCP_SETUP.md
CHANGED
|
@@ -434,9 +434,10 @@ rather than silently moving.
|
|
|
434
434
|
- Here, `blocked` means required review evidence or user action is missing for
|
|
435
435
|
that session (for example `wallet_review_contract_emit_missing`), not a
|
|
436
436
|
release-wide signing stop.
|
|
437
|
-
- The package is
|
|
438
|
-
|
|
439
|
-
|
|
437
|
+
- The package is published to npm as `@stelis/say-ur-intent`, so the `npx` and
|
|
438
|
+
global-install client configs in this guide work directly. A developer
|
|
439
|
+
checkout (local build) or packed tarball is an option for testing local
|
|
440
|
+
changes.
|
|
440
441
|
|
|
441
442
|
## Mainnet Read Smoke
|
|
442
443
|
|
package/docs/MCP_TOOLS.md
CHANGED
|
@@ -595,7 +595,7 @@ Use these `reviewModel` fields for the proposal review answer:
|
|
|
595
595
|
proposal;
|
|
596
596
|
- `evidenceUsed`: local schema and proposal facts used for the review;
|
|
597
597
|
- `missingEvidence`: wallet, recipient, target, simulation, or adapter evidence
|
|
598
|
-
not
|
|
598
|
+
not verified;
|
|
599
599
|
- `requiredUserChoices`: choices that remain with the user;
|
|
600
600
|
- `unsupportedClaims`: conclusions the review does not support;
|
|
601
601
|
- `freshness`: timestamp status for the proposal;
|
|
@@ -654,7 +654,7 @@ is completed and not listed as missing, and `reviewState.simulation` is valid
|
|
|
654
654
|
only after `review_time_simulation` is completed and not listed as missing.
|
|
655
655
|
|
|
656
656
|
When `reviewState.humanReadableReview` is present, it is displayable review
|
|
657
|
-
evidence projected from
|
|
657
|
+
evidence projected from verified review artifacts. Its `kind`
|
|
658
658
|
currently identifies the first swap review projection. Its `assetFlow` raw
|
|
659
659
|
amounts, coin types, decimals, minimum output, and fee facts come from the
|
|
660
660
|
material-bound quote policy evidence. Its target pool and direction come from
|
package/docs/SDK_API.md
CHANGED
|
@@ -8,7 +8,7 @@ This document records the pinned SDK APIs used by the current runtime. The sourc
|
|
|
8
8
|
- `@mysten/deepbook-v3`: `1.3.6`
|
|
9
9
|
- `@mysten/dapp-kit-core`: `1.3.2`
|
|
10
10
|
- `@flowx-finance/sdk`: `2.1.0`
|
|
11
|
-
- `@stelis/agent-q-provider-sui`: `0.0.
|
|
11
|
+
- `@stelis/agent-q-provider-sui`: `0.0.7`
|
|
12
12
|
- `@zktx.io/ptb-model`: `0.5.0`
|
|
13
13
|
- `mermaid`: `11.12.0`
|
|
14
14
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stelis/say-ur-intent",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"mcpName": "io.github.stelis-dev/say-ur-intent",
|
|
5
5
|
"description": "Say Ur Intent is a local-first toolkit for helping AI applications and users inspect Sui DeFi actions before execution.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"@mysten/deepbook-v3": "1.3.6",
|
|
55
55
|
"@mysten/sui": "2.17.0",
|
|
56
56
|
"@scure/bip39": "2.2.0",
|
|
57
|
-
"@stelis/agent-q-provider-sui": "0.0.
|
|
57
|
+
"@stelis/agent-q-provider-sui": "0.0.7",
|
|
58
58
|
"@zktx.io/ptb-model": "0.5.0",
|
|
59
59
|
"better-sqlite3": "12.9.0",
|
|
60
60
|
"mermaid": "11.12.0",
|
package/protocols/deepbook-v3.md
CHANGED
|
@@ -15,10 +15,10 @@ Protocol concepts referenced by current runtime evidence include:
|
|
|
15
15
|
|
|
16
16
|
DeepBook orderbook, raw-quantity quote, and display-amount quote reads use an internal SDK simulation sender placeholder. They do not require wallet connection because these market reads are not wallet-account reads.
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
Signable swap review for the account-bound DeepBook swap route is part of current runtime support; `read.list_supported_protocols` and the concrete MCP tool responses are the authoritative status. Before a `ready_for_wallet_review` state allows user-controlled signing on the local review page, the review server validates the pinned registry, refreshes the live quote, resolves objects, and runs `client.core.simulateTransaction` review.
|
|
19
|
+
|
|
20
|
+
Out of scope:
|
|
19
21
|
|
|
20
|
-
- Signable swap review.
|
|
21
22
|
- Limit or market order review.
|
|
22
|
-
- Wallet signing or execution.
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
The MCP layer and review API do not sign, execute, or return transaction bytes. Wallet signing and execution happen only in the user's wallet from the local review page.
|