feat(agents-mobile): schema-driven spawn args, model controls & image attachments (desktop parity)#4553
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #4553 +/- ##
===========================================
- Coverage 72.77% 58.07% -14.71%
===========================================
Files 86 369 +283
Lines 9779 40459 +30680
Branches 2982 11470 +8488
===========================================
+ Hits 7117 23497 +16380
- Misses 2608 16888 +14280
- Partials 54 74 +20
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Electric Agents Mobile BuildLocal mobile checks ran for commit The EAS Android preview build was skipped because the |
Claude Code ReviewSummaryIteration 6 is a real content update, not a rebase: commit What's Working Well
Issues FoundCritical (Must Fix)None. Important (Should Fix)None. Suggestions (Nice to Have)
Issue ConformanceNo linked issue (consistent across all iterations) — soft warning per convention. No scope creep: every change in this commit traces to a review item or a parity gap with the desktop composer. The changeset wording was also tightened to call out the title hardening as the sole server-side behavior change. Previous Review Status
Review iteration: 6 | 2026-06-11 |
Review follow-up (8446ef4)Thanks for the review. Addressed both actionable items; documenting the two suggestions we intentionally left as-is. Addressed
Deliberately not changed
Note on the "missing changeset / unrelated 0.4.15 entries"The unrelated |
Simulator.Screen.Recording.-.iPhone.15.Pro.-.2026-06-10.at.14.49.10.movandroid-photo.webm |
…ttings helpers Move the creation_schema classification helpers (inlineSchemaProperties, model/reasoning/speed detection + grouping, string-array/JSON parsing, object-schema guards) out of NewSessionView/SchemaForm into a DOM-free lib/schemaProperties module so the native mobile composer can reuse the exact same rules. Behaviour-preserving for desktop; the two model-settings memos collapse into one groupModelSettings call and SchemaForm imports the moved guards. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…end path
uploadMessageAttachments now takes File | NativeFileDescriptor ({uri,name,type}) and branches on the part shape: the web path keeps FormData.set + File, the native path uses FormData.append (RN's FormData has no .set) and serializes the {uri,name,type} object. The new types thread through sendEntityMessage / createSendMessageAction / createSendComposerInputAction so the mobile composer can reuse the same optimistic send+upload+rollback orchestration. No wire-format change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eed controls Render an entity type's synced creation_schema as native spawn-arg controls (parity with the desktop SchemaForm/DefaultAgentComposer): enums become BottomSheet picker pills (the model enum groups by provider and remembers the last pick via AsyncStorage), booleans become switches, string/number become text fields, string-arrays a comma field, and objects a JSON field. Field labels are humanized (reasoningEffort -> Reasoning Effort) and provider names mapped (openai -> OpenAI). spawnArgs derives initial values/defaults and finalizes them for the spawn payload; reuses the shared schemaProperties helpers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…osers Add image attachments (photo library / camera via expo-image-picker, iOS uses a native ActionSheet; transcoded to JPEG via expo-image-manipulator so HEIC is accepted by vision models) gated on whether the session's model supports image input. In-session reuses the shared createSendComposerInputAction; at spawn it mirrors desktop doSpawn (spawn without initialMessage, then send immediate with attachments). Also wires the schema-arg controls into the new-session composer and adds the attach button + thumbnail tray + image/camera icons. Display is handled by the existing desktop chat-log WebView embed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…obile WebView From mobile device testing: (1) wrap the embed router in WorkspaceProvider so the state-inspector view's useWorkspace resolves; (2) publish the composer inset on document.documentElement and anchor the image-preview dialog above the native composer (clears CHAT_COMPOSER_OVERLAP) so it isn't hidden under it; (3) move the timeline thumbnail's aspect-ratio onto an image-only wrapper with an absolutely-positioned img so iOS WebKit stops letting the image's intrinsic height overflow the bubble. All mobile-only / default-0 — desktop unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The low-cost, text-only title model could go conversational on image/attachment messages it can't see (e.g. "I'm sorry but no images were actually shared...") and that became the session title. Firmer system prompt (handle unseen attachments, never apologize, always output a short title) plus a guard that rejects sentence-like (>8 word) responses and falls back to the locally-derived title. Adds a regression test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address PR review: - Add @electric-ax/agents to the changeset — horton.ts title-generation hardening is a behavioral change in a publishable package and was shipping with no version bump / changelog entry. - buildInitialSpawnArgs now respects omitKeys, the one function in the spawn-args pipeline that didn't. Harmless today (horton's workingDirectory has no default) but a latent leak; threaded through both seeding loops, passed SCHEMA_OMIT_KEYS at the call site, and covered with a test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…comment Self-review cleanup, no behavior change: - Extract MODEL_PROVIDER_LABELS + modelProviderKey/modelOptionLabel into the shared schemaProperties module; desktop NewSessionView and mobile SchemaArgsControls now import them instead of each holding a byte- identical copy. Finishes the "extract rather than duplicate" approach the PR already applies to the classification helpers and removes the provider-label drift risk across the RN/DOM boundary. - Trim the redundant "RN FormData lacks set" rationale in sendMessage so it lives once, at the addField branch where the append-vs-set choice is. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
With schema/model/sandbox sections now stacking up, the Start button sat at the very end of the scroll. Move it (and the spawn-error row) into a bottom action bar pinned outside the ScrollView so it stays reachable however many sections are open. The bar reports its height via onLayout and the scroll content is padded by that height so every section still scrolls clear; the bar owns its safe-area bottom inset (Screen leaves the bottom to screen-specific controls, matching the SessionScreen composer). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
80b6355 to
d4bed46
Compare
…titles - finalizeSpawnArgs re-coerces numeric text the editing-time round-trip guard left raw (0.50, 1e3), matching desktop's number submission - seed enum[0] only for required enums; optional enums start unset and get a clearable "None" item, mirroring desktop SchemaForm - gate spawn on required string-arrays that parse to empty (separator- only text or []), matching desktop canSubmit - drop the media-library permission gate before launchImageLibraryAsync; the system pickers don't need it and denial dead-ended the flow - looksLikeNonTitle also rejects conversational punctuation so short refusals under the word cap fall back to the local title Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Summary
Brings
agents-mobileto parity with the desktop agents app for four composer / spawn capabilities, building on #4533 (the native mobile slash-command composer):creation_schemaas native controls (feature 7)No server / runtime changes were required. Every wire contract already exists; this PR makes the mobile client produce the same payloads desktop does, plus a small set of shared-UI fixes that device testing surfaced.
Why
#4533 reached slash-command composer parity on mobile, but the composer was otherwise text-only. The four gaps above are all implemented on desktop in
agents-server-ui; this closes them on mobile.Key insight: 4 features → 2 capabilities, 0 server changes
enumproperties of the entity type'screation_schema, detected by field-name convention.creation_schemais already synced to mobile andspawnEntityalready forwardsargs, so this is purely additive native UI over data mobile already has.uploadMessageAttachmentsused browserFile/FormData.set, which React Native lacks.What's included
Capability A — schema-driven spawn args + model/reasoning/speed (7, 8)
lib/spawnArgs.ts,lib/lastPickedModel.ts,components/SchemaArgsControls.tsx, wired intoNewSessionScreen.creation_schemanatively, full parity with desktopSchemaForm: enum → BottomSheet picker pill (model grouped by provider, last pick remembered in AsyncStorage), boolean →Switch, string/number → text field, string-array → comma field, object → JSON field. Field labels are humanized (reasoningEffort→ "Reasoning Effort") and provider names mapped (openai→ "OpenAI"). Required fields gate "Start session"; server still validates against the schema.onLayoutand the scroll content is padded by that height so every section still scrolls clear; the bar owns its safe-area bottom inset (Screenleaves the bottom to screen-specific controls, matching the in-session composer).Capability B — image attachments (6, 9)
lib/attachments.ts+ attach button / thumbnail tray inNativeComposer;image/cameraicons.expo-image-picker(library + camera; iOS uses a nativeActionSheetIOS),expo-image-manipulator(transcode to JPEG).createSendComposerInputAction({ attachments }). At spawn it mirrors desktopdoSpawn(spawn withoutinitialMessage, then sendimmediatewith attachments). Gated on whether the session's model supports image input (schemaModelSupportsImageInput).Shared / cross-cutting
agents-server-ui/lib/schemaProperties.ts(new): the DOM-free schema-classification helpers (inline props, model/reasoning/speed detection + grouping, string-array/JSON parsing, object-schema guards) extracted fromSchemaForm/NewSessionViewso desktop and the native mobile composer share one source of truth — now including the model-provider label map +provider:modelid parsing that were otherwise mirrored on each platform. Behaviour-preserving for desktop.agents-server-ui/lib/sendMessage.ts:uploadMessageAttachmentsacceptsFile | NativeFileDescriptorand branchesFormData.append(RN) vs.set(web). No wire-format change.WorkspaceProvideraround the embed router so the state-inspector view'suseWorkspaceresolves; image-preview dialog respects the composer inset; iOS timeline thumbnail containment.agents/horton.ts: robust session-title generation for image/attachment messages.Key decisions (the "why", for future sessions)
agents-server-uipure logic +agents-runtime/client, and builds native UI itself (the established pattern). The desktop side of the refactor is mostly deletion.ActionSheetIOS, not ourBottomSheet. Presenting the native image picker over an RNModalfreezes / fails to open on iOS; the native action sheet has no such conflict. Android keeps theBottomSheet(intent-based picker, no conflict).finish_reason=error);expo-image-manipulatornormalizes to JPEG.Known limitations / follow-ups
position: absolute; bottom: 0and relies on Android window resize to lift above the IME; the iOS-style translate is intentionally not applied on Android (doing so double-offsets — device testing showed a 2× lift, proving resize is active). If a device ever shows the composer covered under the keyboard, the deterministic fix isandroid:windowSoftInputMode="adjustNothing"(config plugin) + translate-only — that needs a native rebuild. Rationale is in theSessionScreencomposer-transform comment.AttachmentImagePreviewDialog.module.css(36px= 16 gap + 20CHAT_COMPOSER_OVERLAPcompensation); nudge if a device shows too much / too little gap.Testing
schemaProperties(agents-server-ui),spawnArgs(agents-mobile),generate-title(agents) — all green. Typecheck clean acrossagents-server-ui,agents-mobile,agents-desktop,agents;expo-doctor18/18.Build / deploy notes
expo-image-picker,expo-image-manipulator) + anexpo-image-pickerconfig plugin (iOS photo/camera usage strings) → requires a native dev build (not Expo Go; a Metro reload is not enough).agents-server-ui) ship in the WebView DOM bundle → Metro reload.agents(the horton handler) → restart / redeploy the agents server / runner.🤖 Generated with Claude Code