Skip to content

fix(agentconnect-ui): one kernel wallet per agentcard user, and tie agentcard users to the wallet - #226

Merged
JamesLawton merged 2 commits into
stagingfrom
fix/agentcard-identity
Oct 28, 2026
Merged

JamesLawton merged 2 commits into
stagingfrom
fix/agentcard-identity

Conversation

@JamesLawton

Copy link
Copy Markdown
Collaborator

Stacked on #225; merge that first, or merge this one, which contains it.

Staging is moving from AgentCard sandbox credentials to live ones, so the owner's AgentCard user changes. The Kernel wallet item was one per vault (agentcard-wallet), and the code deleted it when the user changed. Kernel refuses to delete a wallet whose card is recovery_required, and the staging wallet has one, so that would have blocked every checkout.

  • One Kernel wallet item per AgentCard user (wallet-<user id>). A new user gets a new item, and nothing is deleted.
  • external_user_id on add-a-card sessions: the wallet address, so AgentCard ties its user to our wallet. AgentCard's API takes no verified email or sign-in from us; the cardholder's passkey stays theirs.

Tests: 287 pass.

🤖 Generated with Claude Code

// One card item per checkout. Kernel never lets a card whose outcome it can't
// tell (`recovery_required`) be reused or deleted, so a stuck one must not
// block the next checkout.
const cardKey = (checkoutId: string) => `card-${checkoutId}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CLAUDE.md still describes the old fixed key. It says the card item is checkout-card ("card item checkout-card with amount, currency and origin"), and this PR replaces that with one item per checkout (card-<checkoutId>) and one wallet item per AgentCard user (wallet-<userId>). Please update the Browser checkouts paragraph so it doesn't send the next reader looking for an item that no longer exists.

- MCP approvals (`money/actions.ts`, SPA `/approve`, `/api/actions/<keyId>/<actionId>`): a request saved for the owner to approve, for 15 minutes. Spends from a profile with `ownerApproves` (ChatGPT) and every earn withdrawal answer `pending_approval` with a link; the owner, signed in on the site, approves or declines. A withdrawal runs from the owner's wallet, so approving it means signing in on the page (kept until they answer); the worker never keeps an owner sign-in beyond that, so a compromise can't reach more than each connection's allowance. `get_action` reports the outcome. A decided request never runs again.
- Browser checkouts (`worker/browser-checkout.ts`, `worker/kernel.ts`): for agents with no browser (Grok, ChatGPT), MCP tools run a checkout at any store in a Kernel browser. `start_browser_checkout` opens the product page; `browser_look`/`browser_click`/`browser_type`/`browser_select`/`browser_press`/`browser_scroll`/`browser_open` act by element ref (fixed Playwright scripts run through Kernel's `/playwright/execute`; agent input reaches them as JSON data only). At the payment step `pay_browser_checkout` checks the total is on the page, sets the per-wallet Kernel vault's AgentCard card item (vault `oma-<wallet>`, wallet item imported with the wallet's AgentCard user, card item `checkout-card` with amount, currency and origin), types Kernel's placeholder aliases into the card fields and presses pay. Kernel holds the store's card request; the agent gets our `/approve?r=` link (an `Approval` of kind `browser`, never AgentCard's link), which sends the owner, once signed in (a site session for the same wallet counts), straight to AgentCard's own screen to approve or decline: one step, since AgentCard needs the cardholder's passkey anyway. `browser_checkout_status` long-polls the card item's authorization. Every browser checkout waits for the owner, whatever the auto-pay limit. Needs `KERNEL_API_KEY` (secret), `KERNEL_AGENTCARD_CONFIG` (wrangler var) and the AgentCard client: the worker registers that client with Kernel itself, as `<KERNEL_AGENTCARD_CONFIG>-<first 8 of the client id>`, so Kernel always holds the client that created the wallet's AgentCard users. `standard` and `openai` profiles get the tools (`openai` only with an allowance) as their first way to buy; Muse keeps `buy` and `confirm_purchase` instead. Approval links preview with the amount and store in chat apps: `/approve` runs through the worker, which adds the page tags, and `/api/approve/preview.png` draws the image (`worker/og-image.ts`, glyphs from `scripts/og-glyphs.py`).
- MCP client profiles (`worker/profiles.ts`): each connection gets a profile when the owner allows it, from its OAuth client id via `MCP_CLIENT_PROFILES` (JSON, client id to profile), else from its redirect URI (`chatgpt.com`, `chat.openai.com`: `openai`), else `standard`; kept in the grant's `props`. A profile is a tool allowlist, checked on `tools/list` and `tools/call`, whether connecting asks for an allowance, and whether each spend waits for the owner's approval. `standard`: every tool and spending within the allowance. Its instructions give one order for buying: browser checkout first (any store, any country, the owner approves each purchase); if that can't finish, `buy`, which pays at once under the auto-pay limit; then, only for an agent with its own browser, `authorize_checkout`. `openai` (ChatGPT): every money tool and buying the same way as `standard`; each spend (Polymarket included) waits for the owner's approval on the site; `propose_bundle` puts several spends (2 to 5) in one approval, run in order. Approving answers at once and the spend runs in the background (a Durable Object alarm); `get_action` waits up to 45 seconds for the answer and result, so the agent carries on by itself. `muse`: the same as `standard`, but it asks the user before every purchase: `buy` returns a quote (`quotes` in the wallet record, 15 minutes) and only `confirm_purchase` with the same `total_cents` pays it, at most once; over the auto-pay limit the owner still approves on `/approve`; no `authorize_checkout`. A platform's client is registered at `/oauth/register` with its redirect URIs, and the returned client id goes in `MCP_CLIENT_PROFILES`. Connections made before an allowance existed have no key; their money tools say to connect again.

@JamesLawton
JamesLawton merged commit 5568dbd into staging Oct 28, 2026
3 checks passed
@JamesLawton
JamesLawton deleted the fix/agentcard-identity branch October 28, 2026 17:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant