feat(mobile): polish agent chat (persist model, attachments, copy, reasoning default)#4387
Merged
Conversation
…asoning default) - Persist last-selected model per org/personal context and restore it when creating a new session - Add file attachments (images + docs) for new sessions and follow-up messages using the existing cloudAgentNext presigned-URL procedures - Make long-press copy work on both user and assistant messages with haptics and accessibility hints - Add a configurable default for whether reasoning traces render expanded, surfaced via a settings entry in the chat toolbar
…ference state - Generate UUIDs (expo-crypto) for attachment path/id: the upload-url and attachments schemas validate them with z.uuid(), so the previous Date.now/Math.random ids failed every request - Reference the server-returned R2 key basename in the wire payload instead of a client-invented filename that never matched the stored object - Always derive contentType from the extension map: OS pickers report generic types the backend's allowed-type enum rejects - Require a non-empty prompt to send/create (backend enforces min(1)); attachments-only sends previously 400ed after destroying the uploads - Gate the composer paperclip on the manager's supportsAttachments atom; non-cloud-agent sessions throw on attachment sends - Back the reasoning and model preferences with shared module-level SecureStore stores (useSyncExternalStore) so the settings toggle applies immediately and concurrent hook instances stop clobbering each other's writes; clear them on sign-out - Restore the !model guard in the new-session auto-select effect - Fix prompt measurement: single onLayout source and padding constants matching the actual px-2/py-2 input box - Move attachment side effects out of the setState updater (double-invoke safe), upload via Blob from the file URI, and drop the unbound double cast - Cleanups: reuse formatFileSize from @kilocode/kilo-chat, single AgentAttachmentWire type at all call sites, picker returns candidates directly, memoized upload-hook return, max-files constant, settings modal only mounted when shown
# Conflicts: # apps/mobile/src/components/agents/session-detail-content.tsx
jeanduplessis
approved these changes
Jul 3, 2026
iscekic
added a commit
that referenced
this pull request
Jul 3, 2026
Adopts #4387's per-context local model persistence (agent-model-preference store + usePersistedAgentModel) as the local cache layer, replacing this branch's simpler single-entry SecureStore hook and its storage key. useAutoSelectModel now resolves the local fallback via resolveModelForContext; the server lastSelected -> local -> org default -> first-available precedence is unchanged. Model changes in an existing session (session-detail-content) now also write through to the server preference, mirroring the new-session screen.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Four self-contained mobile agent-chat improvements, scoped to the mobile app with no backend contract changes:
hasLoadedguard and variant reset when the persisted variant no longer exists. The hook clears on sign-out alongside other per-user preferences.getAttachmentUploadUrl->PUTto the signed URL. The wire payload ({ path, files }) is forwarded throughmanager.sendandprepareSession, with mutual-exclusion enforcement (noimages+attachmentstogether). Slash-command sends are blocked when attachments are queued.useMessageCopy+ purecollectCopyableTextso the same handler runs on user and assistant messages, with iOSActionSheetIOSand Android immediate copy paths preserved. Adds a success haptic,toast.success('Copied to clipboard'), andaccessibilityHint='Long press to copy message text'on both.defaultExpanded(collapsed by default), threaded throughsession-detail-content->message-bubble->part-renderer->reasoning-part-renderer. Surfaced in a settings sheet opened from a smallSettings2button in the chat toolbar; uses an accessibleSwitchwithaccessibilityRole='switch'.Outcome-focused changes only — no shared types or contract changes; no dependency changes.
Verification
.ziprejected;/commandwith attachment blocked. Long-press user and assistant messages; iOS shows action sheet, Android copies immediately, haptic fires. Toggle reasoning default, new assistant reasoning renders expanded/collapsed; per-message tap still overrides; setting survives app restart.Automated checks (run from
apps/mobile):pnpm format,pnpm typecheck,pnpm lint,pnpm test,pnpm check:unusedall clean. 7 new unit tests added (use-persisted-agent-model,validate,parse-reasoning-default,collect-copyable-text); full suite: 378 tests passing.Visual Changes
N/A — no visual design changes. Reasoning settings entry appears as a new
Settings2icon in the chat toolbar (right of the mode/model selectors); opening it shows aModalmatching the existingrename-instance-modalpattern. The paperclip button appears in both the composer and the new-session input row. The attachment preview strip mirrors the existing Kilo ChatMessageAttachmentPreviewChippattern.Reviewer Notes
agent-model-preference,parse-reasoning-default,collect-copyable-text) are split out from the hooks to keep vitest SSR-transform clean (the hooks pull inexpo-secure-store/expo-clipboard/expo-hapticswhich transitively loadreact-native's Flow-typedindex.js).chat-toolbar.tsxandnew.tsxare now over the 300-line cap; both have a/* eslint-disable max-lines */header with a justification, perapps/mobile/AGENTS.md.mobile-session-manager.tsonly forwardsattachmentswhen present (conditional spread), so the wire contract stays identical when no attachments are sent.expo-image-manipulatorif needed.Math.random-based strings, not theuuidpackage, to avoid a new dep.