feat(mobile): agent chat screen over the agent WebSocket - #550
Conversation
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 is attempting to deploy a commit to the miracle656's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
@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! 🚀 |
app/agent.tsx replaces the route stub added by the navigation shell in Miracle656#507. README keeps both sections.
|
Merging as-is — this is the best-engineered PR I've reviewed in this batch, and it needed no fixes beyond taking 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:
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. Also good: The only merge work was All three acceptance criteria on #493 hold: connects, sends and receives, and handles reconnect — the last one demonstrably, which is unusual.
|
Brings the Claude-powered assistant to mobile: a chat screen backed by the
packages/agentWebSocket service, ported from the web wallet's 741-linefrontend/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.Acceptance
Connects.
createAgentSocketopens againstEXPO_PUBLIC_AGENT_WS_URL(defaultws://localhost:3001) and reportsconnecting/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 —
chatandclear_historyout,thinking/response/error/history_clearedback — against the unchanged server inpackages/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:
chatwhose socket dies before theresponsearrives 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
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.localStorage), so the screen loads the profile in an effect and shows a spinner until it settles.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.Checks
npm run typecheckandnpm testpass infrontend/mobile(125 tests including the pre-existing suites).