Skip to content

feat(mobile): agent chat screen over the agent WebSocket - #550

Merged
Miracle656 merged 2 commits into
Miracle656:mainfrom
Elizabethxxx:feat/mobile-agent-chat
Jul 30, 2026
Merged

feat(mobile): agent chat screen over the agent WebSocket#550
Miracle656 merged 2 commits into
Miracle656:mainfrom
Elizabethxxx:feat/mobile-agent-chat

Conversation

@Elizabethxxx

Copy link
Copy Markdown
Contributor

Brings the Claude-powered assistant to mobile: a chat screen backed by the packages/agent WebSocket service, ported from the web wallet's 741-line frontend/wallet/app/agent/page.tsx.

closes #493

What is here

  • frontend/mobile/lib/agentSocket.ts — the transport: protocol types, frame decoding, connection lifecycle, reconnect.
  • frontend/mobile/lib/agentProfile.ts — profile persistence, greeting, and role-aware suggestions, split out because they are pure logic worth testing.
  • frontend/mobile/app/agent.tsx — the screen: onboarding, thread, composer, and transaction approval.
  • Tests for both lib modules.

Acceptance

Connects. createAgentSocket opens against EXPO_PUBLIC_AGENT_WS_URL (default ws://localhost:3001) and reports connecting / connected / reconnecting / closed. The screen shows a banner for the last two rather than pretending the connection is always up.

Sends and receives. Speaks the same protocol as the web page — chat and clear_history out, thinking / response / error / history_cleared back — against the unchanged server in packages/agent/src/server.ts. Pending transactions arrive with the response and render an approval card that signs with the device fee-payer key and submits via Horizon, mirroring the web approval path.

Handles reconnect. Covered by 18 tests driving a stub socket through drops, errors, constructor failures, and teardown.

Why the transport is its own module

The web page opens a socket inline and retries every 2 seconds on close. That is fine for a browser tab. On a phone the socket drops routinely — backgrounding the app is enough — so the transport was pulled out and given three behaviours a mobile client needs:

  • Jittered exponential backoff (1s doubling to a 30s ceiling, minus up to 25%), so a server restart does not bring every phone back in lockstep.
  • An outbound queue (capped at 10), so a message composed while offline is delivered on reconnect instead of silently dropped. The screen tells the user it is waiting.
  • In-flight accounting. The agent protocol has no request ids and no replay: a chat whose socket dies before the response arrives is gone for good. The client reports how many replies were orphaned so the screen can say so, instead of leaving the thinking indicator up forever.

The socket constructor, the timers, and the backoff jitter are all injectable, which is what makes those paths testable without a server.

Notable porting differences

  • The web page renders agent markdown with dangerouslySetInnerHTML. React Native has no innerHTML, so **bold** and `code` are split into nested <Text> runs — which also means model output can never be interpreted as markup.
  • Profile reads are async (AsyncStorage) rather than synchronous (localStorage), so the screen loads the profile in an effect and shows a spinner until it settles.
  • Storage keys (veil_user_profile, veil_agent_notification, invisible_wallet_address, veil_signer_secret) match the web wallet's, so a user who set themselves up in the browser is not onboarded a second time.
  • The fee-payer address is derived from the stored secret rather than read from a cached public key, which is the mismatch the web page's comment warns about.
  • A corrupt profile or notification is treated as absent and cleared, so an unreadable entry costs three onboarding questions rather than an error screen on every visit.

Checks

npm run typecheck and npm test pass in frontend/mobile (125 tests including the pre-existing suites).

Adds /agent, the mobile client for the Claude-powered assistant in
packages/agent. It speaks the same protocol as the web wallet's agent
page and reuses its storage keys, so a profile set up in the browser
carries over rather than the user being onboarded twice.

The transport is split out into lib/agentSocket.ts because a phone's
socket drops constantly -- backgrounding the app is enough. It
reconnects with jittered exponential backoff instead of the web page's
fixed 2s retry, queues messages composed while offline and flushes them
on reconnect, and tracks in-flight requests: the agent server keeps no
outbox, so a reply interrupted by a drop never arrives and the screen
now says so instead of leaving the thinking indicator up forever.

The socket constructor, timers, and backoff jitter are all injectable,
which is what makes the reconnect paths testable without a server.

Agent markdown is rendered as nested Text runs rather than the web
page's dangerouslySetInnerHTML, so model output can never be
interpreted as markup.
@Elizabethxxx
Elizabethxxx requested a review from Miracle656 as a code owner July 28, 2026 20:18
@vercel

vercel Bot commented Jul 28, 2026

Copy link
Copy Markdown

@Elizabethxxx is attempting to deploy a commit to the miracle656's projects Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave

drips-wave Bot commented Jul 28, 2026

Copy link
Copy Markdown

@Elizabethxxx Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

app/agent.tsx replaces the route stub added by the navigation shell in Miracle656#507.
README keeps both sections.
@Miracle656

Copy link
Copy Markdown
Owner

Merging as-is — this is the best-engineered PR I've reviewed in this batch, and it needed no fixes beyond taking main's route stub out of the way.

What stands out is that you didn't just port the web page. The header comment lays out exactly why a phone needs different transport behaviour than a browser tab, and then the code delivers each point:

  • Exponential backoff with jitter rather than the web version's fixed 2-second retry — and the reasoning given (a server coming back up shouldn't be hit by every phone on the network in lockstep) is the right one.
  • An outbound queue, so a message composed while the socket is down is delivered on reconnect instead of vanishing.
  • In-flight accounting. This is the subtle one. The agent protocol has no request ids and no replay, so a chat whose socket dies before the response arrives is simply lost. Counting those and surfacing the number means the UI can say so rather than spinning forever. That's a failure mode most implementations don't notice until a user reports it.

Pulling the socket constructor, timers and jitter out as injectable dependencies is what makes 413 lines of reconnect tests possible without a server, and it's why I could verify the behaviour rather than take it on trust. agentSocket.test.ts and agentProfile.test.ts add 38 cases; the suite is now 14 files / 244 tests, all passing.

Also good: EXPO_PUBLIC_AGENT_WS_URL with a localhost default, and the AgentUserProfile type explicitly noted as mirroring UserProfile in packages/agent/src/agent.ts so the drift is visible if the server changes.

The only merge work was app/agent.tsx#507 had added a placeholder route there, so that's an add/add conflict resolved in favour of yours, plus keeping both README sections.

All three acceptance criteria on #493 hold: connects, sends and receives, and handles reconnect — the last one demonstrably, which is unusual.

tsc --noEmit clean, expo lint clean, CI green.

@Miracle656
Miracle656 merged commit a387ae7 into Miracle656:main Jul 30, 2026
10 of 13 checks passed
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.

65. Agent chat screen

3 participants