# feat(mobile): universal native agent companion — cross-app chat, approvals & mentions#2265
Conversation
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
Here's a visual recap of what changed: Open the full interactive recap |
…mentions Make the native agent chat a universal control plane across workspace apps: - Cross-app thread history with a per-app filter (defaults to Chat); each thread's origin app base URL routes every op (open/send/approve/deny/resume/ stop/fork/share) so approvals and turns hit the right app. - Dropped-stream recovery: reattach to a run from the last seen seq when a stream ends without a terminal event; surface a Retry error card if recovery fails. Guard 200-with-JSON non-stream responses. - Wire reducer matches tool_done/approval_required by toolCallId and renders isError results as failed. - Navigate-command polling with query-string and path threadId extraction. - @-mentions: native suggestion menu backed by the mentions endpoint, scoped to the active app; picked items attach as turn references (reconciled on send). - Vault API keys + model catalog hides engines whose optional package is not installed and only offers usable provider key inputs. 93 unit tests passing; tsc clean; oxfmt applied.
… settings origin - Navigation polling now fetches/acknowledges against the active thread's origin (baseUrlRef) and opens the target thread on that app, so commands from a Dispatch/Content/etc. thread are consumed instead of being lost to the default Chat origin. - Reducer settles the stream on loop_limit and auto_continue (mobile does not auto-continue), so a run ending on either no longer hangs in streaming state. - WireEventType declares loop_limit and auto_continue, matching isTerminalWireEvent. - Model catalog no longer lets hidden engines mark a provider key configurable; ChatSettingsSheet reads the model catalog and writes API keys against the active app's base URL and reloads on change. 94 tests passing; tsc clean; oxfmt applied.
- Model catalog requires ALL of an engine's required env vars before offering it (every, not some) — a multi-key engine with one key set no longer surfaces models that fail on selection. - fetchMentions streams the NDJSON body incrementally over expo/fetch and reports each batch via onItems, so fast sources (resources) show before slow ones (codebase scans / custom providers) finish. - ThreadHistorySheet guards filter requests with a generation ref, so a slow app's results can't overwrite a later-selected app. - Reset model/engine to Auto when the active thread's app changes, so a model chosen for one deployment is never submitted to another that lacks it; effort/mode stay app-agnostic. 94 tests passing; tsc clean; oxfmt applied.
There was a problem hiding this comment.
Builder reviewed your changes and found 1 potential issue 🟡
Review Details
Incremental Code Review Summary
This revision fixes the previously open issues: navigation polling now uses the active origin, terminal loop events settle the reducer, terminal event literals are included in the protocol type, engine configuration requires all keys, mention batches stream incrementally, history filter responses are guarded, and model settings are scoped to the active deployment. I verified those fixes in the latest PR diff and resolved all seven corresponding review threads.
One new native-composer issue remains:
- 🟡 MEDIUM — Dictation insertion updates the controlled text but does not move the TextInput selection to the end, so typing immediately afterward can insert characters at the beginning of the dictated text.
The broader cross-app routing, stream recovery, and native chat architecture otherwise looks sound. This remains standard risk due to the shared streaming and cross-app state surface.
🧪 Browser testing: Will run after this review (PR touches UI code)
The composer's selection is controlled (for @-mentions), so appending dictation left the caret at its prior position (0 in an empty composer) and new keystrokes landed before the dictated text. Set selection to the end when applying it.
|
awesome @shivanshu11092003! can you share some screenshots of what the UX of this looks like? |
|
Yess @steve8708
|







feat(mobile): universal native agent companion — cross-app chat, approvals & mentions
Summary
Turns the mobile app into a 100% native control plane for the whole
Agent-Native workspace. The in-app agent surface (the sidebar/panel that lives
as an embedded frame inside every web app) is now mirrored natively on mobile:
anything the embedded agent can do in Content, Slides, Dispatch, Analytics,
Plan, Mail, etc. — read state, generate and edit content/design, run actions,
and pause for approval — can be driven remotely from the phone, with or
without the laptop running.
There are zero WebViews in the chat path. The UI (messages, markdown, tool
cards, approval cards, composer) is built from native React Native primitives
(
View,Text,FlatList,Pressable) and talks directly to the framework'sHTTP/SSE backend. Because the phone and the embedded web agent share the same
central SQL-backed backend, work done on mobile shows up on the laptop the next
time it syncs (
useDbSync()→/_agent-native/poll) — no laptop required toexecute or approve.
What's included
Universal native chat & streaming
text,thinking,tool_start,approval_required,tool_done,done,error) into native message state,streamed over
expo/fetchReadableStreams.tool_done/approval_requiredare matched bytoolCallId(falling back toid), andisErrorresults render as failed tool cards.terminal event; a stream that just stops (mobile network drop, proxy timeout,
hosted background handoff) now reattaches from the last seen
seq(
/runs/:id/events?after=) instead of silently showing a blank turn. Ifrecovery fails, a native error card with Retry is surfaced.
detected and surfaced rather than parsed as an empty stream.
Cross-app thread history & per-app control
deployment/thread store) via a fan-out; per-app failures degrade gracefully.
defaults to Chat.
(
openThread,send/runTurn,approve/deny,resumeRun,stop,reattach, fork/share) so a thread opened from any app talks to that app.
Universal human-in-the-loop approvals
approval_required.runTurn("Approved…", { approvedToolCalls: [approvalKey] }); Deny targets thespecific
approvalKey. Because operations are app-scoped, you can approve adestructive Mail/Analytics/Plan action from the phone against the correct app.
Settings, API keys & model engine
written to the server vault via the framework key route — never to device
storage.
(
packageInstalled !== false, matching the web picker) and only offers keyinputs for providers that can actually run in this app — fixes the
Engine "ai-sdk:google" requires optional packages…dead end.Workspace navigation & command polling
GET /_agent-native/application-state/navigatewhen idle, de-dupes by_writeId, acknowledges viaDELETE, and opens the target thread.extractThreadIdhandles both query-string (?threadId=123) and path(
/chat/123,/dispatch/chat/123) formats.@-mentions in the composer@opens a native suggestion menu backed by the app's/_agent-native/agent-chat/mentionsendpoint (files, workspace pages, skills,agents) scoped to the active thread's app.
@Labeland attaches areferenceto the turn, whichthe backend inlines as context — same contract as the web composer. References
are reconciled on send (a deleted mention doesn't travel with the turn).
Architectural verification
runTurn/resumeRun/openThreadcapturecurrentGeneration = ++activeGenerationRef.current; stale completions neveroverwrite the active thread.
liveTurnRefholds anAbortControllersoin-flight requests abort immediately on thread switch / new chat / stop.
resolve to a thread id.
tsc --noEmitclean, zero-warningoxfmt, unit tests green.