Repository navigation
Promote dev → staging - #1182
Merged
Merged
Conversation
The TypeScript gap already shipped in 0.1.6. Agent docs still listed the old RecallOptions, which is how GH #708 concluded recency ranking was unreachable.
Agents reading only this file still could not see the weight fields.
…es out (WALM-670)
…ait times out (WALM-670)
Recall is similarity-ranked and limit-bounded, so a write can't be
confirmed and a namespace can't be counted. listMemories() wraps the
existing GET /v1/owners/{owner}/memories and reuses resolveOwner().
Metadata only: no blob fetch, no decrypt.
The relayer has no namespace filter, so the optional namespace is
applied client-side; a filtered page can be empty while has_more is
true. Tombstone fields default for relayers older than WALM-363.
…y reached (WALM-670) A same-key POST on a paid job always answered 202 `uploaded`, even when it lost the recovery claim. And the claim fenced `failed` rows for 60s, so a recovery that had just failed (the wallet job, or an enqueue error that returned before the publish) left a lease behind. The next retry enqueued nothing, still said `uploaded`, and GET kept saying `failed`: the poisoned-key shape for a blob that already exists. The claim now publishes `uploaded` in the same statement. A `failed` row is claimable at once, and only an `uploaded` row keeps the 60s fence. Two racing retries therefore enqueue one recovery, and the loser reports the row's real status. If enqueue fails, the claim is released back to `failed` with the lease cleared, so the next retry takes it immediately.
…d (WALM-670) The timeout message said "retry with the same idempotency key" and that remember_and_wait / rememberAndWait does this for you. Neither is reliably true. `remember` drops its key as soon as the POST is accepted, and an explicit idempotencyKey is never stored, so following the advice minted a new key and a second paid write while the first job was still running. A wait only times out on a job that is still pending, running or uploaded (a failed job raises), so the advice is now to call waitForRememberJob / wait_for_remember_job on the job id again, and to pass an explicit key again on any retry. Python also stores the redacted server_error on the exception attribute, not just in the message.
* fix(sdk): reject bad mock page sizes and honor recent recall MemWalMock.listNamespaces treated limit 0 as an endless page and a negative limit as slice's drop-the-tail. Bulk writes skipped the client's empty-batch check, idempotency keys were ignored, and sort recent stayed oldest-first. Positional recall(query, options, namespace) also dropped the namespace on both the mock and MemWal. Fixes #1144, #1145, #1146, #1147, #1124, #1038 * test(sdk): cover the mock limit, idempotency, and recent-recall edges The first tests matched the issue repros. These also lock the branches a tie-break-only sort, a 50-row window cap, a byte-length key, and a mutated options object would have left green.
…us polls (#1168) * fix(sdk): treat a remember options object as options and fix job status polls remember(text, { idempotencyKey }) and rememberAndWait used to send that object as the namespace. Empty job ids no longer hit GET /api/remember/. Duplicate ids in waitForRememberJobs settle in every slot. Bulk status returns namespace, status polls skip the SEAL session, batches over 20 are rejected before signing, and a destroyed client throws instead of reading a zeroed key or probing /version. #1142 #1087 #1121 #1084 #1104 #1135 #1136 #1090 * fix(sdk): reject a destroyed key still in flight and merge remember options An in-flight getPublicKeyHex resolved to the zeroed cache, and a key derived after destroy was the well-known key of the wiped seed. A third options argument was also dropped when the namespace slot was an object.
…1134) * fix: reject blank text, treat [::1] as local, and pin weekday dates Whitespace-only remember, analyze, and embed text is rejected before a job is created (#1129). The TypeScript client treats bracketed ::1 as loopback (#1128). Fact extraction copies a server-built weekday calendar (#1131). AUTH_REJECTED names a pasted wallet address without changing the 401 (#1132). * refactor: one weekday table, inline the loopback host check The English weekday names lived in a match and again in the calendar loop. One Sunday-first array covers both, indexed by num_days_from_sunday. The IPv6 bracket strip stays inside normalizeServerUrl.
search_similar kept every vector_entries row, so a remember retry or restore could surface the same Walrus blob twice with identical scores.
Duplicate index rows were eating the ANN LIMIT window. Over-fetch by the dropped count, then unique and truncate. Ranker docs no longer narrate WALM-594.
limit + dropped only added the unique shortfall, so a 2x index crawled many HNSW round-trips. Widen by dropped rows in the current window and stop at limit * 4.
…all-returns-duplicate-records-for-the-same fix(server): return each blob once in recall (WALM-594)
…161) recall(query, namespace) was silently treated as options and ran against the default namespace. Fail loudly instead of dropping the intended scope.
recallManual has no object-first overload, so hinting
method({ query, namespace }) sent callers to query.trim() failure.
…itional-overload-silently-accepts-string-as fix(sdk): throw when positional recall gets a namespace string (WALM-161)
…ixes #1143, #1040, WALM-721) - Declare "default" fallback condition across all exported subpaths (".", "./account", "./manual", "./ai") so bundlers (Next.js/Turbopack) and Node 22 require(esm) resolve cleanly instead of throwing ERR_PACKAGE_PATH_NOT_EXPORTED - Place "types" condition first for TypeScript nodenext/bundler resolution - Add exports-map regression tests asserting types-first ordering and default fallback
* fix(mcp): re-check the bridge before signing connect consent (#368) POST /preflight on load already rejects a phishing link. Two pieces of that fix were still missing: a wallet that connects after the consent screen is showing starts add_delegate_key without a click, and the page does not ask the bridge again immediately before the signature. Gate auto-continue on an explicit click, refuse to sign if the bridge no longer confirms the request, and warn when the relayer is not the default. * test(app): a connected wallet does not sign connect consent on its own The consent screen must stay up until the user clicks, even when a wallet is already connected.
…on-to-list-stored-blobs-writes-are-unauditable-and feat(sdk): listMemories() pages every memory without a query (WALM-388)
fix(relayer,sdk): stop a slow remember job reading as a dead key (WALM-670)
… budget (Fixes #1103, WALM-756) - In applyTokenBudget, select candidates by ascending distance under high-relevance-only strategy rather than array order - Preserve the caller's requested order (e.g. sort: recent) for the retained lowest-distance hits - Break distance ties stably by original index - Update TruncationStrategy documentation and recall client commentary - Add regression test reproducing the two-hit recency order bug and multi-hit order preservation
harrymove-ctrl
had a problem deploying
to
benchmark-dev
October 14, 2026 13:55 — with
GitHub Actions
Error
…llManualHit (#1178) - Add score and created_at fields to RecallMemory dataclass - Add created_at and importance fields to RecallManualHit dataclass - Forward fields from API response in MemWal.recall and MemWal.recall_manual - Populate score and created_at in MemWalMock.recall - Add regression tests in test_client.py and test_mock.py Fixes #1063 Fixes #1160
…#1079) (#1184) * fix(sdk): suppress recall and auto-save on tool execution steps (Fixes #1079) * fix(sdk): keep recalled memory on tool steps without a second recall Auto-save still runs only on a user turn. A later tool step re-injects the cached recall for that same user message, so the answer step does not lose the memory context. Fixes #1079 * fix(sdk): recall when a tool step has no cached memory A matching cache is still reused and does not call recall again. A miss, including the first call on a fresh withMemWal, recalls once and stores that result. Fixes #1079 * fix(sdk): cache an empty recall so tool steps do not call again A user turn with no matching memories used to clear the cache. The next tool step then called /api/recall again. An empty result is now remembered for that turn. Fixes #1079 * fix(sdk): recall again on the next user turn Tool steps still reuse a cached recall, including an empty one. A later user message recalls even when the text matches the previous turn.
harrymove-ctrl
had a problem deploying
to
benchmark-dev
October 14, 2026 13:56 — with
GitHub Actions
Error
Fixes #1107) (#1185) * fix(python-sdk): suppress recall and auto-save on tool execution steps (Fixes #1107) * fix(python-sdk): keep recalled memory on tool steps without a second recall Auto-save still runs only on a user turn. OpenAI and LangChain tool steps re-inject the cached recall for that same user message. Fixes #1107 * fix(python-sdk): treat LangChain generate as one conversation _generate and _agenerate receive one message list, the same list invoke passes. A tool step reuses that turn's cached recall, and a cache miss recalls once instead of dropping the memory. Fixes #1107 * test(python-sdk): call LangChain generate with one conversation The tests added on dev still passed a batch into _agenerate. The wrapper receives one message list, so these calls and their captures match that. * fix(python-sdk): cache an empty recall and refresh on the next user turn Tool steps reuse a recall that matched nothing. A later user message with the same text calls recall again. min_relevance stays 0.3. * fix(python-sdk): sort the same-text recall test imports Ruff I001 failed the Python unit jobs before pytest ran.
* feat(app): restyle the dashboard to the Sept 2026 design Keep delegate keys, namespaces, and the playground, and hide the Walrus Console promo until its calendar date. Console links open console.wal.app, Claude Code install commands match the docs, and pasting a delegate key checks the current account on chain. * fix(app): point the Walrus Console promo at console.walrus.xyz The date gate is unchanged. The links were opening the wrong host. * fix(app): toggle the Walrus Console promo with a boolean flag Replace the calendar-date gate with VITE_WALRUS_CONSOLE_ENABLED. The promo stays off unless that value is true. * feat(app): restyle the MCP connect screens to match the dashboard Use the same dark column, type, and flat cards for the local MCP login and the Claude consent page. The sign-in steps are unchanged. * fix(app): keep the playground open when this browser has no key Stop sending /playground back to the dashboard. Show the same yellow notice with a paste field, and leave Run disabled until a registered delegate key is attached. * fix(app): keep the playground key notice above the steps The shared dashboard alert order was pushing the yellow notice to the bottom of the playground. * fix(app): finish the playground key gate and MCP consent screen Keep the playground open without a key, and restyle the MCP consent page to the same type as the dashboard. Details stay collapsed until opened. * fix(app): open the playground at the top and drop shouted labels Scrolling from the dashboard no longer lands midway down the playground. Buttons and links keep the wording as written instead of full capitals. * fix(app): restore uppercase labels, including the MCP approve button The sentence-case pass was a mistake. Dashboard, playground, and the MCP consent button use the tracked capitals again. * fix(app): bake the Walrus Console flag into the Docker build Railway already sets VITE_WALRUS_CONSOLE_ENABLED, but the image never received it, so the promo stayed off. * fix(app): align the console button with the install steps Put the Walrus Console link in the same column as the step text, and open up the space between the agent steps. * fix(app): apply the dashboard design review notes Keep the install connector on the first tab, show the SDK quickstart in the app step, and drop the AI SDK block. Tighten mobile key rows, selection, and the paste-key form. * fix(app): space the connect cards and center the Codex tab icon Give the subtitle more room above the two connect modules, and keep the Codex mark centered in its tab. * feat(app): restyle the sign-in page and finish the dashboard review Keep the signed-in dashboard and the signed-out sign-in page on one branch. The playground stops after the basic health, remember, and recall steps. * feat(admin): chart memories by day and match the dashboard Add the admin activity API with a 30-day window, keep balance samples long enough for that comparison, and restyle admin plus the sign-in stage to the Sept 2026 layout. * feat(app): send the funnel to Statsig with key join ids Forward trackEvent and page views to Statsig, and attach the delegate public key plus transaction digest on the two successful key events so the visit can be joined to on-chain activity. * fix(docs): exempt the admin activity route from the public reference The freshness check already leaves the other ADMIN_API_KEY routes undocumented. /api/admin/activity belongs in that same list. * fix(app): lower the sign-in layout and match the glass tiles Shift the desktop and mobile content down with the phone, and fill the sign-in buttons white on hover. The right tile is more transparent and the left tile keeps a darker frost. * fix(app): drop the extra background under playground results The result text was sitting in a second gray box inside the yellow frame. Keep the frame and let the JSON sit directly in it. * fix(app): restyle the dashboard confirm dialogs Match the remove-key, delete-memory, and preview dialogs to the dashboard: black panel, Ratch title, and outline pills. * fix(app): use David's mascot-free Open Graph image Point the social preview at the 1200x630 wordmark image and keep the old filename serving the same file so existing links do not 404. * fix(app): replace the sign-in phone with the laptop graphic David's updated sign-in visual uses the dashboard on a laptop instead of a phone. The glass tiles follow that screen, and the top mobile tile sits behind it. * fix(app): enlarge the mobile sign-in laptop and its tiles Give the laptop and glass tiles a little more size, and open up the space above and below them. * fix(app): use the final page title and meta description Kenton confirmed the title and description, including the Open Graph and Twitter tags. * feat(admin): hourly memory chart, click-to-copy addresses, fuller error details Memories written: a 24h window has one calendar day, so the daily chart drew a single bar across the whole card. For windows of 24h or less the activity route now also returns memories_by_hour (same viewer-offset bucketing as the daily query, hours with no jobs filled with zero, absent for longer windows so existing clients are unaffected), and the chart draws one bar per hour. Midnight ticks show the date, and tooltips and the summary name the date and hour. Addresses: the upload-errors table showed only the first six characters (0x07e5). Every admin address (upload errors, top owners, uploader pool, sponsor wallet) now reads 0x5c3a...907e and copies the full value when clicked. A copy icon appears on hover; on click the text turns green, the icon pops to a check and a "Copied" chip rises and fades (red "Copy failed" if the clipboard is refused). The row copy button used to give no feedback at all, since its copied state was only wired to the modal. Error details: the modal showed a timestamp and the message. It now shows the job ID, full owner (copy, and a Suiscan link for the deployment's network), namespace, status, queued and failed times with the gap between them, the message with its own copy, a clear note when the server recorded none, and "Copy all details" as JSON for a ticket. The server already sent status and created_at; the client dropped them. The content area also scrolls now on short screens instead of being cut off. Rust: 2 new unit tests for hourly buckets (positive and negative offsets, empty window); admin_activity 10/10. App: tsc clean, vitest 112/112. * perf(server): index memory_tombstones by deleted_at for admin activity /api/admin/activity counts tombstones in a deleted_at window with no owner, for the current and the previous window. The only index is (owner, deleted_at, memory_id), which cannot serve that range, so each call scanned the table twice, and the admin view polls every 60s per open tab. Adds 024 with CREATE INDEX CONCURRENTLY in its own file (as 016/018/022) and registers it in CONCURRENTLY_BUILT_INDEXES so an interrupted build is recovered. storage::db tests 19/19, including the wiring and registry checks. * fix(app): keep chain ids out of analytics and redact every 64-hex value 7c9dd91 exempted delegate_public_key and transaction_digest from the redaction rules and sent both on delegate_key_register_complete and delegate_key_add_complete to GA/GTM, PostHog and Statsig. - A transaction digest resolves on-chain to the sender's wallet, so every analytics profile (PostHog id, Statsig stableID) became linkable to an address, which the existing redaction policy exists to prevent. - The exemption was keyed by property name, so a real 64-hex private key passed as delegate_public_key was sent unredacted. The old test only used a 63-character value, so it did not catch this. Both fields are removed from the two events and the exemptions are gone: a 64-hex value is redacted under any property name again. The events, page views and the Statsig sink are otherwise unchanged. If joining the funnel to chain activity is still wanted, it needs a privacy sign-off and an id that is not derivable from public chain data. Tests: 64-hex redacted under public-key names; the two key events no longer carry either field. vitest 122/122, tsc -b clean. * fix(app): pin the Statsig script by SRI and keep analytics off /admin The Statsig client is loaded from jsDelivr with no integrity attribute and there is no CSP, while the same origin keeps the delegate private key and the admin API key in sessionStorage. A changed CDN response would run with access to both. - The script tag now carries sha384 integrity for exactly @statsig/js-client@3.33.5/build/statsig-js-client.min.js. The hash was computed locally and matches jsDelivr's published hash for that file (the file is served immutable). In a browser, the correct hash loads and a tampered one is blocked. - No third-party analytics (GTM, PostHog, Statsig) initialises or sends on /admin, which holds the admin key and has nothing to measure. A CSP for the whole app is still worth adding, but it touches GTM, PostHog and wallet popups, so it is left out of this PR. vitest 124/124, tsc -b clean. * fix(app): clear a pasted delegate key once it is imported After "Use this key" succeeded on the dashboard, the pasted private key stayed in plain text in the textarea: its state lives in ConnectWalrusMemory and nothing cleared it (the Playground version does). - importExistingKey now resolves true once the key is stored, and the form clears the field then. A failed import keeps the value so a typo can be fixed. - The field is masked like a password, has autocomplete, autocorrect and autocapitalize off, and carries data-analytics-redact so the click guard treats it as sensitive. Test: the field keeps the key after a failed import and is empty after a successful one. vitest 125/125, tsc -b clean. * fix(app): pin the desktop sign-in laptop to the right edge A wide window letterboxed the device away from the corner. The wordmark follows the stage. * fix(app): match the Back to Console dialog's top padding to its sides The title sat tighter against the top edge than the left and right padding. * fix(app): center the sign-in hero above a 14-inch width The laptop stays on the right edge through 1512px. Wider windows center the group, and the wordmark lines up with the title. * fix(app): put the sign-in wordmark back in the frame's top left The logo sits 50px from the frame edge, left of the Sign in column, as in the design. * feat(app): animate the dashboard copy buttons Copy icons crossfade to a drawn check (or a cross on failure), the wallet address blurs to Copied at a locked width, and the button presses 1px. Copies fall back to execCommand, report failures, and announce the result to screen readers. Motion is CSS-only and off under reduced motion. * fix(app): lay the signed-in pages out for phones Under 640px the Connect Walrus Memory timeline starts flush left on a 48px rail instead of hanging off the first tab, commands scroll sideways instead of breaking mid-word, and the playground title fits. Under 480px the client icons sit above the card title. Under 420px Sign out collapses to its icon so the address no longer runs into the logo. * fix(app): pass VITE_CONSOLE_URL and the legal URLs into the Docker build (#1113) The Dockerfile had no ARG for them, so the Railway values never reached Vite and the Back to Console dialog was compiled out on every deploy. * fix(app): bake the current Console and deployment defaults The Walrus Site build never received the console and legal URLs, and the image still fell back to the pre-cutover packages. Defaults now match the live Railway deployments, and an unset Console URL points at that network's Memory tab. * fix(app): center the sign-in hero on a short 14-inch window A 14-inch MacBook at 100% zoom is shorter than the Figma frame, so pinning the stage right left a wide gap on the left. Center that window. Keep the laptop on the right edge only while the window is at least as tall as a 14-inch fullscreen. * fix(app): grow a short sign-in window until the laptop meets the right edge Use the updated laptop screen and the purple bloom behind it. On a window shorter than a 14-inch display, scale the stage to the full width so the laptop touches the right edge. * fix(app): keep the sign-in size from a 1.9 window and scroll past it Fit the desktop frame to 880px so the empty tail under the laptop tile does not shrink the page. At 1.9:1 and wider the composition stays the size it has at that ratio for the current width, and the page scrolls with the scrollbar hidden. Spare width still leaves 100px of the laptop clipped. --------- Co-authored-by: Harry Phan <phanhoangvinhhien@gmail.com> Co-authored-by: Nikola Le <91601109+nikola0x0@users.noreply.github.com>
* docs: add a guide for importing existing agent memory Workshop developers asked for a page that copies local memory, standing context, and session transcripts into Walrus Memory in batches, then keeps using that account. * docs: cover import rate limits, timeouts, and Antigravity Hosted mainnet allows 120 weighted requests per minute and 1000 per hour per account, and 60 per minute per delegate key. A bulk timeout means the upload may still be running, so the guide says to poll those job ids instead of sending the batch again. * docs: drop the staging relayer from the import sample Kimberly confirmed staging stays closed to the public, so the sample no longer points readers at relayer-staging. * docs: make the import sample safe to copy Send Claude memory files through analyze, keep only user and assistant text from a session transcript, and retry a refused call after Retry-After without resubmitting job ids the relayer already accepted. * docs: apply the import-guide review
…1222) /manual still dynamically imported @mysten/walrus for a client nothing called, which pulled the WASM package into Turbopack and Vite. Downloads stay on the HTTP aggregator. MemWal.recallManual returns RecallManualHitResult. MemWalManual.recallManual returns RecallManualMemoryResult. RecallManualResult stays as a deprecated alias of those two, not a mixed list. WALM-145, WALM-163
Keep the legacy (owner, idempotency_key) unique index for this release so a relayer rollback stays safe. The (owner, namespace, idempotency_key) index from 013 and the WALM-781 code still ship; 025 returns in the release after. Co-authored-by: Le Tien Phat <91601109+Niko1444@users.noreply.github.com>
2 tasks done
…LM-668) (#1224) testnet-v1.79.0 added `TransactionExpiration::Validity` as variant index 3. The indexer was pinned to testnet-v1.75.1, whose `TransactionExpiration` has three variants, so once testnet began producing `Validity` transactions every checkpoint carrying one failed to convert: transaction.bcs: invalid value: integer 3, expected variant index 0 <= i < 3 The indexer decodes whole checkpoints rather than individual events, so this is a hard stop and not a skipped field: `accounts_v1` retried the same checkpoint indefinitely and its watermark backlog grew to 48,527 pending commits on staging, leaving GET /api/accounts/{owner}/exists answering false for accounts that exist on-chain. Move both Sui pins to testnet-v1.80.0 (ceaaff1c), the current testnet release. The remote store layout is unchanged between the two revisions (same `{checkpoint}.binpb.zst` objects, same zstd + protobuf decode), so this needs no ingestion or deployment configuration change and the saved watermark stays valid. Cover it two ways: `tests/affected_checkpoint.rs` decodes testnet checkpoint 384534623 — the one named in the incident, committed as a fixture in the exact form the remote store serves — through the real `StoreIngestionClient`, and `tests/checkpoint_decoding.rs` pins `Validity` to variant index 3 while holding the earlier variants at their own indices, which already-indexed history depends on. (cherry picked from commit ca3f72d) (cherry picked from commit 69908ac) Co-authored-by: Harry Phan <phanhoangvinhhien@gmail.com>
…#879) * fix(oauth): don't burn the session when Sui is unavailable (WALM-605) `session_complete` consumed the one-time authorization session and only then verified the delegate key on-chain, mapping every `verify_delegate_key_onchain` failure — a Sui `GetObject` 429 included — to `invalid_request("delegate key is not registered on-chain")`. A throttled fullnode therefore looked identical to a genuinely unregistered key, and because the session was already spent the user could not retry: they had to restart `/oauth/authorize` from scratch. Follow-up to WALM-429 (#811), which taught signed HTTP auth the same distinction but left this callback behind. - Classify verify failures with `OnchainVerifyError::is_unavailable()`, the same primitive `auth::cache_reverify_action` uses, so the OAuth callback and signed recall cannot drift apart on what a 429 means. - Move the atomic claim below the on-chain read: only a definitive answer (registered or not registered) spends the session; an unreachable chain leaves the row `pending` so the same request can be re-submitted. Single-use is unchanged — no code is minted until the `WHERE status = 'pending'` update wins, and the session-bound wallet proof still gates everything above it. - Return `temporarily_unavailable` 503 with `Retry-After: 5` instead of a dead-end 400, matching signed auth's `AUTH_UPSTREAM_RETRY_AFTER_SECS`. * review: let the consent UI re-send /complete alone (WALM-605) Sparing the session only helps if something re-POSTs /complete. ConnectClaude did not: any /complete failure went to the terminal error step, and a reload re-ran handleConnect including POST /account, so a first-time connect that had already landed add_delegate_key re-submitted the same pubkey, aborted EDelegateKeyAlreadyExists (code 0), and the SPA reported "this wallet is not the owner". The WALM-605 journey still dead-ended. - fetchJson keeps the HTTP status and the OAuth `error` code. - The /complete request moves behind completeSession(), which holds its payload; on temporarily_unavailable / 503 the page shows a retry step that re-sends that request and nothing else. Definitive rejections still land in the terminal error state. - Tests cover both: the retry path re-POSTs /complete once, issues no second preflight and no second transaction; a 400 stays terminal. - session_complete claims inside its match arms, so the Verified path no longer carries an Option + expect() panic on a request path. - Trim the before/after WALM-605 narration and the test-module essay. * review: survive a reload during OAuth recovery (WALM-605) The retry button worked, but it hung off a useRef. A reload dropped it, sent the user back to consent, and the first-time path then re-ran handleConnect, re-submitted add_delegate_key for a key already on chain, and aborted with code 0 — which the SPA still reports as "this wallet is not the owner". Reloading is the obvious thing to try when a page says "retry", so that was still the reported WALM-605 dead end. - The /complete payload is mirrored into sessionStorage under its session id, so a remount resumes at the retry step and re-sends only /complete. The entry is scoped to the session so one authorization's wallet signature can never be replayed against another, and it is cleared on success. - Auto-connect is suppressed while a recovery payload exists, so the resume cannot fall through into a second /account. - Retry copy no longer claims the key is registered. A 503 means Sui could not be consulted, so it says the authorization is still pending instead. - Tests: a reload resumes and completes with no second /account and no second transaction; a payload from another session is ignored. * review: do not leave a resumable crumb after a spent session (WALM-605) The payload was written to sessionStorage at the start of every `/complete` and cleared only on 200. A definitive 400 (Unregistered has already claimed the row) therefore left it behind, and since `session_view` does not filter on `status = 'pending'`, a reload showed the Sui-busy retry step for a session that was already spent, looping there because nothing ever cleared the key. - The ref is still set up front, so a hung in-flight `/complete` can resume. sessionStorage is written only on the retryable branch, and the terminal branch clears it. - Test: a 400 followed by a remount stays on the error step. - Corrected the comment that claimed the payload joins the existing breadcrumb. It gets its own key precisely because the mount effect rewrites `memwal_claude_connect` to `{ session }` and would drop it. * fix(app): stop mapping add_delegate_key abort 0 to an owner mismatch (WALM-605) `account.move` defines EDelegateKeyAlreadyExists = 0 and ENotOwner = 4, so abort 0 means the key is already registered — not that the wallet is wrong. That is the state a WALM-605 connect lands in: the tx went through and the verify behind it hit a Sui 429, so re-running the flow re-submitted the same pubkey and told the user to switch wallets. Treat abort 0 as already-on-chain and go straight to /complete; report the owner mismatch on abort 4, where it belongs. * review: decide the duplicate-key skip on chain, not from the tx error (WALM-605)`alreadyOnChain` could never fire. ConnectClaude signs through `useSponsoredTransaction`, which throws `new Error(sponsorFailureMessage(...))`, and `sponsor::mask_upstream` collapses every upstream 5xx into a bare 502 `sponsor_upstream_error` that never echoes the upstream body. An Enoki dry-run of a duplicate `add_delegate_key` therefore reaches the SPA as "The transaction was rejected by the sponsor" — no abort code, no function name. The abort 4 and abort 2 matches were dead for the same reason. The test that "covered" this mocked a raw `MoveAbort ... abort code: 0` string, which is not what production throws, so it passed while the user dead-ended.Move the decision to where the answer exists:- `/account` verifies this session's own pending key against the chain and returns `resume_pending_registration`. Verified means an earlier attempt landed the tx, so the UI skips straight to `/complete`. - A Sui outage during that check returns a retryable 503 rather than guessing. Registering walks into the masked 502; skipping sends `/complete` at a key that is not there, which is definitive Unregistered and burns the session. The preflight claims nothing, so a retry costs only that request. - The classification goes through `classify_delegate_verify`, so the preflight and `session_complete` cannot drift on what a Sui 429 means (WALM-429). - `session_account` now sanitises `account_id` before the DB lookup and the on-chain read, matching `session_complete`.Client:- Drop all three abort-code branches. The owner-mismatch guidance they carried was unreachable; `session_complete` still reports a real mismatch as a 400. - Write `PENDING_COMPLETE_KEY` before the request instead of only on a retryable failure. A reload while `/complete` is in flight has no 503 to react to, and that is the one gap the ref cannot cover. Terminal exits still clear it, so a spent session cannot loop on the retry screen. - `tx_digest` is `already-on-chain` when resuming, not `reused-delegate`.Tests:- The sponsor 502 is built from the real `SponsorHttpError` / `sponsorFailureMessage` exports, so the fixture cannot drift from the wrapper, and asserts `/complete` is NOT sent — folding every 502 into "already registered" would push `ENotOwner` / `ETooManyDelegateKeys` into `/complete` and burn the session. - `/account` 503 submits no transaction. - A reload with `/complete` still in flight resumes at the retry step with no second signature and no second preflight. - Four Rust tests pin the preflight decision, including that it and `session_complete` agree on which errors are definitive. * review: return a retryable /account 503 to consent (WALM-605) A preflight POST /account 503 leaves the authorization session pending, but the catch sent it to the terminal error, which has no way back. Return to consent and show the reason so the user can start the request again. * review: use the restyled error class on the retry screens (WALM-605) The rebase onto dev dropped errorTextStyle. Both retry messages now use mcp-error, and the finish button uses mcp-btn. --------- Co-authored-by: Harry Phan <phanhoangvinhhien@gmail.com>
The configuration reference and API reference described key as hex-only and omitted the request deadline.
MemWalMock.analyze accepted an Invalid Date that MemWal throws on. A valid anchor is still not rewritten; the mock has no extractor.
* fix(sdk): reject an invalid recall query, limit, or weight The real client signed a blank query, a NaN limit, and a NaN scoring weight. Those now fail before the request. * test(sdk): expect the positive integer recall limit error dev already rejects limit 0 and below with TypeError. This branch keeps that check.
A string or null jobIds list threw TypeError in the mock. Both clients now throw the same validation error before any request.
NaN and a negative cutoff used to drop every hit and look like an empty namespace.
* fix(sdk): reject an object passed as remember text remember, rememberAsync, and rememberAndWait throw TypeError before signing when text is not a string. * review: reject an object remember text in the mock MemWal throws TypeError. The mock used to fall through to "text cannot be empty".
* fix(sdk): turn an empty or non-JSON 2xx body into an SDK error health() and signed requests called res.json() on success and leaked a raw SyntaxError. Callers now get an error that names the HTTP status. Fixes #1201 * fix(sdk): name empty JSON bodies in MemWalManual and MCP The same 2xx parse guard from MemWal now covers manual restore, compatibility, embed, Walrus upload, and the MCP relayer check. * fix(sdk): give an unreadable 2xx body its HTTP status A remember wait treated a status-less parse error as HTTP 0 and polled until timeout. * review: await the restore body and test the empty JSON path Without await, the deadline disposed before text() finished, so a stalled body became a TypeError. The empty-202 tests now stub the SEAL session and hit /api/remember.
harrymove-ctrl
approved these changes
Oct 16, 2026
Merged
6 of 7 tasks
This was referenced Oct 20, 2026
Closed
Merged
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Promotes
devintostaging: 49 PRs since #1122, frozen at34e520fd. Carries the indexer Validity fix (#1224), per-namespace idempotency keys in safety mode (#1176, #1225),listMemories()(#1020), and the SDK and Python fixes merged since the last promotion.Safety mode: this release adds the new idempotency index but keeps the old one, so the relayer can still be rolled back. Per-namespace keys go live next release, when the old index is dropped. Until then, reusing a key in a second namespace returns a 500 instead of a 409.
Changes
Relayer / indexer
TransactionExpiration::Validity; same code as the production hotfix69908acc(fix(indexer): decode checkpoints carrying the Validity expiration #1224)(owner, namespace, idempotency_key)index in013; old index kept (fix(server,sdk): scope remember idempotency keys per namespace (WALM-781) #1176, fix(server): hold back migration 025 until the next release (WALM-856) #1225, WALM-856)Security
/connect/mcpconsent bound to the local MCP bridge (fix(mcp): bind /connect/mcp consent to the local bridge (#368) #370)SDK
listMemories()(feat(sdk): listMemories() pages every memory without a query (WALM-388) #1020)createAccountandMemWalManualdefault to a gRPC Sui client (fix(sdk): default account and manual helpers to a gRPC Sui client (WALM-164) #1221)exportsmap works forrequire()and bundlers (fix(sdk): add default condition to exports map and place types first (Fixes #1143, #1040, WALM-721) #1181)withMemWalskips tool steps and surfaces recall and auto-save failures (fix(sdk): suppress recall and auto-save on tool execution steps (Fixes #1079) #1184, fix(sdk): warn on recall failure in withMemWal and add onRecallError callback #1194, fix(sdk): surface a failed auto-save from flush #1209)restoreSEAL session (fix(sdk): derive and forward idempotencyKey in rememberBulk (Fixes #1080) #1188, fix(sdk): clamp polling delay to deadline and ensure boundary poll check (#1081) #1187, fix(sdk): treat a remember options object as options and fix job status polls #1168, fix(sdk): attach SEAL session and timeout in MemWalManual.restore (Fixes #1137) #1190)Python SDK
Retry-After(fix(python-sdk): retry transport errors and bound polls by deadline (Fixes #1000, WALM-696) #1192, fix(python-sdk): support HTTP-date Retry-After and prevent float overflow (Fixes #1151, WALM-815) #1191)created_atandscoreon recall results; key, redaction and blank-text hardening (fix(python-sdk): expose created_at and score in RecallMemory and RecallManualHit #1178, fix(python-sdk): support uppercase 0X prefix in normalize_private_key #1172, fix(python-sdk): redact IPv6, 0.0.0.0, and localhost subdomains in error messages (Fixes #1161) #1173, fix: reject blank text, treat [::1] as local, and pin weekday dates #1134)MCP / app / docs
SKILL.mdrecall options (docs: show recall() scoringWeights on SKILL.md (WALM-616) #899);requestTimeoutMsand accepted key formats (docs: list requestTimeoutMs and the accepted key formats #1231)Not in this promotion
dev(fix(sdk): reject zero-width space and format characters in assertNonBlankText (Fixes #1156) #1177, fix(sdk): turn an empty or non-JSON 2xx body into an SDK error #1207, fix(sdk): reject a non-finite recall maxDistance #1227, fix(sdk): reject an object passed as remember text #1230, fix(python-sdk): default server_url to the hosted relayer #1232–fix(python-sdk): wrap POST /api/forget #1234, fix(mcp): deduplicate bulk writes on bridge reconnect replay #1046, ci(openclaw): run the plugin unit suite per PR and the E2E suite on staging/main #683, fix(chatbot): fail loudly on a weak or missing AUTH_SECRET #644). Next promotion.mainpublishes nothing new.Test plan
devfirst34e520fd. TheRelease SDKpublish job first lost an npm version race when five PRs merged within seconds (409on0.1.8-dev.23); the re-run published0.1.8-dev.24.remember_jobshas both idempotency indexes, both validValiditycheckpoint;/api/accounts/{owner}/existsistruefor a new account/connect/mcpfrom a real MCP login completes, a hand-built link is refused, the dashboard loadsstaging → mainonce the above pass