Skip to content

# feat(mobile): universal native agent companion — cross-app chat, approvals & mentions#2265

Merged
steve8708 merged 8 commits into
BuilderIO:mainfrom
shivanshu11092003:native-chat-mobile
Jul 21, 2026
Merged

# feat(mobile): universal native agent companion — cross-app chat, approvals & mentions#2265
steve8708 merged 8 commits into
BuilderIO:mainfrom
shivanshu11092003:native-chat-mobile

Conversation

@shivanshu11092003

@shivanshu11092003 shivanshu11092003 commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

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's
HTTP/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 to
execute or approve.

What's included

Universal native chat & streaming

  • Wire-event reducer folds SSE events (text, thinking, tool_start,
    approval_required, tool_done, done, error) into native message state,
    streamed over expo/fetch ReadableStreams.
  • tool_done / approval_required are matched by toolCallId (falling back to
    id), and isError results render as failed tool cards.
  • Dropped-stream recovery: the server always closes a finished run with a
    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. If
    recovery fails, a native error card with Retry is surfaced.
  • 200-with-JSON responses (proxy/middleware failures disguised as success) are
    detected and surfaced rather than parsed as an empty stream.

Cross-app thread history & per-app control

  • History aggregates threads across every workspace app (each app is its own
    deployment/thread store) via a fan-out; per-app failures degrade gracefully.
  • A per-app filter chip row loads only the selected app's threads and
    defaults to Chat.
  • The active thread's origin app base URL is routed through every operation
    (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

  • Native Approve/Deny cards render whenever any app emits approval_required.
  • Approve resumes execution with
    runTurn("Approved…", { approvedToolCalls: [approvalKey] }); Deny targets the
    specific approvalKey. Because operations are app-scoped, you can approve a
    destructive Mail/Analytics/Plan action from the phone against the correct app.

Settings, API keys & model engine

  • Configure provider API keys (Anthropic, OpenAI, Gemini) natively; keys are
    written to the server vault via the framework key route — never to device
    storage.
  • Model catalog hides engines whose optional package isn't installed
    (packageInstalled !== false, matching the web picker) and only offers key
    inputs for providers that can actually run in this app — fixes the
    Engine "ai-sdk:google" requires optional packages… dead end.
  • Model / effort / mode (act vs plan) selectors.

Workspace navigation & command polling

  • Polls GET /_agent-native/application-state/navigate when idle, de-dupes by
    _writeId, acknowledges via DELETE, and opens the target thread.
  • extractThreadId handles both query-string (?threadId=123) and path
    (/chat/123, /dispatch/chat/123) formats.

@-mentions in the composer

  • Typing @ opens a native suggestion menu backed by the app's
    /_agent-native/agent-chat/mentions endpoint (files, workspace pages, skills,
    agents) scoped to the active thread's app.
  • Picking an item inserts @Label and attaches a reference to the turn, which
    the 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

  • Race protection: runTurn / resumeRun / openThread capture
    currentGeneration = ++activeGenerationRef.current; stale completions never
    overwrite the active thread.
  • Cancellation propagation: liveTurnRef holds an AbortController so
    in-flight requests abort immediately on thread switch / new chat / stop.
  • Format support: query-string and path-based navigation commands both
    resolve to a thread id.
  • Code quality: tsc --noEmit clean, zero-warning oxfmt, unit tests green.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Here's a visual recap of what changed:

Visual recap

Open the full interactive recap

builder-io-integration[bot]

This comment was marked as outdated.

@shivanshu11092003 shivanshu11092003 changed the title feat(mobile-app): develop native chat feature instead of web view # feat(mobile): universal native agent companion — cross-app chat, approvals & mentions Jul 20, 2026
…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.
builder-io-integration[bot]

This comment was marked as outdated.

… 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.
builder-io-integration[bot]

This comment was marked as outdated.

- 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.

@builder-io-integration builder-io-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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)

Comment thread packages/mobile-app/components/chat/Composer.tsx Outdated
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.
@shivanshu11092003
shivanshu11092003 marked this pull request as draft July 21, 2026 17:29
@shivanshu11092003
shivanshu11092003 marked this pull request as ready for review July 21, 2026 17:30
@steve8708

Copy link
Copy Markdown
Contributor

awesome @shivanshu11092003! can you share some screenshots of what the UX of this looks like?

@shivanshu11092003

shivanshu11092003 commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

Yess @steve8708

d c b
a 6 7

@steve8708
steve8708 merged commit 2e0b767 into BuilderIO:main Jul 21, 2026
33 of 34 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants