@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.
@@ -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
- * Bind the review server to its fixed port, taking the port over from a previous
8
- * instance of our own review server if necessary.
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) !== "EADDRINUSE") {
22
- throw error;
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
- if (holder.pid === deps.currentPid) {
30
- // We appear to hold the port ourselves yet cannot bind it. Never signal
31
- // our own process; surface the original bind error.
32
- throw error;
33
- }
34
- deps.logger.info("review port held by a previous say-ur-intent instance; taking it over", {
35
- port,
36
- previousPid: holder.pid
37
- });
38
- const terminated = deps.terminate(holder.pid);
39
- if (!terminated.ok) {
40
- if (terminated.reason === "no_permission") {
41
- throw new Error(`Review server port ${port} is held by a ${deps.serviceName} instance owned by another OS user; cannot take it over. ` +
42
- `Set SAY_UR_INTENT_REVIEW_PORT to a free port for this client.`);
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
- if (terminated.reason === "error") {
45
- throw new Error(`Failed to stop the ${deps.serviceName} instance holding review port ${port}: ${terminated.message ?? "unknown error"}.`);
56
+ const next = await tryStart(start, port);
57
+ if (!next) {
58
+ continue; // a peer still owns the port; keep deferring
46
59
  }
47
- // not_found: the holder already exited between probe and signal; fall
48
- // through and rebind.
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
- catch (retryError) {
59
- if (errorCode(retryError) !== "EADDRINUSE") {
60
- throw retryError;
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
- throw new Error(`Review server port ${port} did not become free after stopping the previous ${deps.serviceName} instance (pid ${holder.pid}). ` +
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
- * our own review server answers `/__identity`; any other process either returns
71
- * something else or does not answer, and we report it as "not ours" so the
72
- * caller never signals it.
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 { InMemorySessionStore } from "../core/session/sessionStore.js";
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 = new InMemoryLocalTransactionMaterialStore();
125
- const sessions = new InMemorySessionStore({
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,
@@ -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 { InMemoryLocalTransactionMaterialStore } from "../core/session/transactionMaterialStore.js";
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, startReviewServerWithTakeover, terminateProcessByPid } from "./reviewServerAcquire.js";
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 = new InMemoryLocalTransactionMaterialStore();
85
- const sessions = new InMemorySessionStore({
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: the newest instance takes
173
- // the fixed port over from a previous instance of our own review server so
174
- // the most recently started client owns the one wallet-autoconnect origin.
175
- const reviewServer = await startReviewServerWithTakeover((port) => reviewServerFactory.start(port), config.reviewPort, {
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://${reviewServer.host}:${reviewServer.port}`,
239
+ reviewBaseUrl: `http://${config.reviewHost}:${config.reviewPort}`,
240
240
  readService,
241
241
  transactionActivityService: new TransactionActivityService({
242
242
  activityStore: store,
@@ -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 | Partly implemented for DeepBook review; execution unsupported | Account-bound DeepBook 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. Wallet signature requests and execution remain unavailable; the byte handoff is gated on recomputed-digest equality with the reviewed contract. Fiat cash-out, P&L, tax, and cost-basis support are not part of the current release. |
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 yet available in the current release (sequenced later) | Intent evidence is separate from execution and does not build or execute payments. Do not describe payment execution as available until a reviewed implementation and response-local guidance exist. |
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
@@ -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. While a handoff is outstanding the server locks the session
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 not yet published to npm: use a developer checkout (local
438
- build) or packed tarball. `npx` client configs in this guide start working
439
- after the first npm publish.
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 yet verified;
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 previously verified review artifacts. Its `kind`
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.5`
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",
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.5",
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",
@@ -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
- Not yet implemented:
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
- No DeepBook transaction should be exposed as signable until registry validation, live quote refresh, object resolution, and `client.core.simulateTransaction` review are implemented.
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.