Skip to content

feat(android): in-chat model picker with favorites and recents#100798

Merged
steipete merged 1 commit into
mainfrom
feat/android-model-picker-pins-nash
Jul 6, 2026
Merged

feat(android): in-chat model picker with favorites and recents#100798
steipete merged 1 commit into
mainfrom
feat/android-model-picker-pins-nash

Conversation

@steipete

@steipete steipete commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Related: #100699

What Problem This Solves

Fixes an issue where Android users could not switch models in chat at all: the app had no model picker anywhere — the model catalog was only visible as a read-only provider readiness overview in Settings. iOS/macOS users can switch the session model from the composer; Android had no equivalent.

Why This Change Was Made

Adds a model chip to the Android chat composer that opens a bottom-sheet picker fed by the existing gateway model catalog, with Pinned / Recent / Models sections: star to pin/unpin without closing the sheet, tap to select. Selection patches the session via sessions.patch { key, model } (the same wire contract iOS/macOS use); pins and recents (capped at 5) persist in the app's plain preferences, and a recent is recorded only after gateway acceptance. A selection mutex orders model patches ahead of sends so the first post-selection message can never leave on the previous model. Because sessions.patch requires operator.admin, fresh Android connects now request that scope — exact parity with iOS, which already requests admin for token/password connects; previously paired device tokens keep their granted scopes (existing stored-scope narrowing) and simply can't patch models until re-paired, mirroring how operator.talk.secrets rolled out.

Security note (deliberate decision, please weigh in): sessions.patch is classified operator.admin in the gateway method descriptors, so this PR adds operator.admin to Android's fresh-connect scope request. This is exact parity with iOS, which already requests admin for every token/password connect (shouldRequestOperatorAdminScope in apps/ios/Sources/Model/NodeAppModel.swift), and follows the same rollout pattern as operator.talk.secrets: previously paired device tokens keep their narrower granted scopes. The alternative — a narrower gateway permission for model-only session patches — is a protocol/security contract change beyond this client PR's boundary; if maintainers prefer that, it should be its own gateway-side follow-up, and this scope request can be revisited then.

User Impact

Android users can now switch the session model from the chat composer, pin favorites, and reach recently used models first. On gateways where the operator connection lacks admin scope, selection fails with a visible chat error and no state is polluted.

Evidence

  • ./gradlew :app:testPlayDebugUnitTest focused suites green: ChatModelPickerTest (section ordering, provider-qualified ref derivation), SecurePrefsTest (favorites/recents persistence round-trip, dedupe/cap), ChatControllerModelSelectionTest (recent recorded only on RPC success, rejection surfaces error and records nothing, send-after-select ordering), ConnectionManagerTest/GatewayBootstrapAuthTest (fresh-connect scope set incl. operator.admin, stored-token narrowing unchanged).
  • :app:ktlintFormat clean on changed files; :app:ktlintCheck failures are pre-existing in five untouched test files (called out for transparency).
  • corepack pnpm native:i18n:sync run for the new picker strings.

@openclaw-barnacle openclaw-barnacle Bot added app: android App: android size: L maintainer Maintainer-authored PR labels Jul 6, 2026

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0a33875a5a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if (trimmed.isEmpty() && attachments.isEmpty()) return false
// Model patches and sends share one ordering boundary; the first post-selection turn
// must not leave on the previous model while sessions.patch is still in flight.
modelSelectionMutex.withLock {}

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.

P2 Badge Prevent sends after failed model patches

When the pending sessions.patch fails (for example on previously paired Android tokens that lack operator.admin), this empty lock only waits for it; sendMessageAwaitAcceptance then continues to chat.send and clears the patch error at updateErrorText(null), so an immediate post-selection message is sent on the previous session model while the UI just accepted a new choice. The queued selection should surface failure back to the send path or block/retry the send instead of silently falling through.

Useful? React with 👍 / 👎.

): Long {
val generation = historyLoadGeneration.incrementAndGet()
_sessionKey.value = key
_selectedModelRef.value = null

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.

P2 Badge Hydrate selected model from loaded session info

When opening or switching to a session that already has a persisted model override, this clears the chip state and the later chat.history/sessions.list parsing never restores it from the gateway's sessionInfo.modelProvider/model fields. The composer can therefore show the generic Model/default state even though the gateway will keep sending on the stored override, causing users to make choices from an incorrect current model.

Useful? React with 👍 / 👎.

@clawsweeper

clawsweeper Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed July 6, 2026, 10:33 AM ET / 14:33 UTC.

Summary
The PR adds an Android chat composer model chip and bottom-sheet picker with favorites/recents preferences, sessions.patch model selection, i18n inventory updates, tests, and operator.admin on fresh connects.

PR surface: Other +629. Total +629 across 13 files.

Reproducibility: yes. for PR-introduced issues from source: failed model patches are not propagated to the immediate send path, loaded sessions cannot hydrate selected model state, and the picker uses a default-agent catalog for active-agent chats. I did not run a real Android device or emulator reproduction.

Review metrics: 2 noteworthy metrics.

  • Android operator scopes: 1 added. Fresh Android operator connects would newly request operator.admin, which changes the mobile security and upgrade boundary.
  • Android plain preferences: 2 added. The PR adds chat.modelFavorites and chat.modelRecents persisted keys, so preference behavior and upgrade handling matter before merge.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #100699
Summary: This PR is an implementation candidate for the Android model-picker favorites/recents slice of the open mobile chat polish bundle.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🧂 unranked krab
Proof: 🧂 unranked krab
Patch quality: 🧂 unranked krab
Result: blocked until real behavior proof is added.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P1] Fix failed-selection send gating, selected-model hydration, and active-agent catalog selection.
  • [P1] Add redacted Android emulator/device proof for successful selection, failed selection, and immediate-send behavior after selection.
  • Get explicit maintainer acceptance of operator.admin parity or split a narrower gateway permission.

Proof guidance:

  • [P1] Needs real behavior proof before merge: The PR body provides focused tests and CI notes only; it still needs redacted Android emulator/device screenshots, a recording, terminal/live output, or logs showing after-fix picker selection and failure behavior. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Risk before merge

  • [P1] Fresh Android connects would request full operator.admin, expanding the mobile device-token authority boundary for a model-picker feature.
  • [P1] Existing paired Android tokens keep narrower stored scopes, so upgraded users can hit visible model-selection failures; until the send path observes patch failure, an immediate message can still leave on the previous model.
  • [P1] The picker uses the default-agent catalog source, so non-default-agent chats can hide valid models or offer models that sessions.patch later rejects.
  • [P1] The PR still has tests/CI only and no redacted Android emulator/device proof of selecting a model, failure handling, and immediate-send behavior.

Maintainer options:

  1. Fix blockers and approve admin parity (recommended)
    Require the source fixes, real Android proof, and explicit maintainer acceptance that Android may request operator.admin for model switching before merge.
  2. Add a narrower gateway permission first
    Pause this branch while maintainers add a model-only session patch permission, then update Android scopes and tests to use that narrower contract.
  3. Park the branch
    Close or pause this PR if maintainers do not want Android model switching coupled to full admin authority right now.

Next step before merge

  • [P1] Manual review is required because the PR has unresolved P2 blockers, missing contributor-side real behavior proof, a protected maintainer label, and a security-boundary choice about operator.admin.

Maintainer decision needed

  • Question: Should Android fresh connects request full operator.admin for model switching in this PR, or should Android model switching wait for a narrower gateway permission?
  • Rationale: sessions.patch currently requires operator.admin, but adding that scope broadens fresh Android device-token authority and changes upgraded-user behavior for stored narrow-scope tokens, so automation cannot choose the permanent security contract.
  • Likely owner: steipete — steipete owns the linked mobile-polish issue and the recent mobile model-picker/session-control work this PR extends.
  • Options:
    • Approve admin parity after fixes (recommended): Accept iOS-style operator.admin for fresh Android connects, but require the source fixes, real Android proof, and explicit upgrade notes before merge.
    • Split a narrower permission: Pause this PR until the gateway exposes a model-only session patch permission and Android can request that narrower scope.
    • Defer Android picker: Keep the parent polish issue open and defer this branch if full admin scope is not acceptable for mobile model switching.

Security
Needs attention: The diff has no dependency or CI supply-chain change, but it deliberately broadens Android fresh-connect authority to operator.admin.

Review findings

  • [P2] Block sends after failed model patches — apps/android/app/src/main/java/ai/openclaw/app/chat/ChatController.kt:557
  • [P2] Hydrate selected model from session metadata — apps/android/app/src/main/java/ai/openclaw/app/chat/ChatController.kt:501
  • [P2] Use the active agent catalog for the picker — apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatScreen.kt:151
Review details

Best possible solution:

Fix the remaining source blockers, use an active-session/agent-aware model catalog path, add real Android proof, and merge only after maintainers explicitly accept Android admin-scope parity or choose a narrower gateway permission.

Do we have a high-confidence way to reproduce the issue?

Yes for PR-introduced issues from source: failed model patches are not propagated to the immediate send path, loaded sessions cannot hydrate selected model state, and the picker uses a default-agent catalog for active-agent chats. I did not run a real Android device or emulator reproduction.

Is this the best way to solve the issue?

No: the Android UI direction is plausible and matches the parent issue, but the patch is not the best mergeable solution until it fixes failed-selection send gating, model hydration, active-agent catalog scoping, proof, and the admin-scope decision.

Full review comments:

  • [P2] Block sends after failed model patches — apps/android/app/src/main/java/ai/openclaw/app/chat/ChatController.kt:557
    When sessions.patch fails, setSessionModelAwait returns false but sendMessageAwaitAcceptance only waits for the mutex and then proceeds to chat.send. Existing paired Android tokens without operator.admin can immediately send on the previous model after a failed selection, contradicting the intended ordering.
    Confidence: 0.93
  • [P2] Hydrate selected model from session metadata — apps/android/app/src/main/java/ai/openclaw/app/chat/ChatController.kt:501
    beginHistoryLoad clears _selectedModelRef, but the later chat.history sessionInfo parse still drops the gateway modelProvider/model fields. Opening a session with a stored model override can therefore show the generic model state while the gateway keeps sending on the override.
    Confidence: 0.91
  • [P2] Use the active agent catalog for the picker — apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatScreen.kt:151
    The sheet is built from modelCatalog, which NodeRuntime loads via models.list with empty params. Current gateway behavior scopes that list to the default agent, so non-default-agent chats can hide valid models or offer default-agent choices that sessions.patch later rejects.
    Confidence: 0.9

Overall correctness: patch is incorrect
Overall confidence: 0.92

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 94a0d2ec3b79.

Label changes

Label justifications:

  • P2: This is a normal-priority Android chat feature with concrete correctness blockers and limited blast radius compared with setup, data loss, or core runtime availability.
  • merge-risk: 🚨 compatibility: Existing paired Android devices retain narrower stored scopes, and the picker can offer default-agent catalog entries that fail for non-default-agent sessions.
  • merge-risk: 🚨 security-boundary: Merging would make fresh Android operator connections request full operator.admin, expanding the mobile device-token authority boundary.
  • rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🧂 unranked krab and patch quality is 🧂 unranked krab.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The PR body provides focused tests and CI notes only; it still needs redacted Android emulator/device screenshots, a recording, terminal/live output, or logs showing after-fix picker selection and failure behavior. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed

PR surface:

Other +629. Total +629 across 13 files.

View PR surface stats
Area Files Added Removed Net
Source 0 0 0 0
Tests 0 0 0 0
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 13 668 39 +629
Total 13 668 39 +629

Security concerns:

  • [medium] Android fresh connects request full admin scope — apps/android/app/src/main/java/ai/openclaw/app/node/ConnectionManager.kt:46
    Adding operator.admin to native Android operator scopes gives newly paired Android clients full admin authority for admin methods, not only model switching; this needs explicit maintainer acceptance or a narrower gateway permission.
    Confidence: 0.9

What I checked:

  • Repository policy applied: Root AGENTS.md was read fully and treats auth/session state, provider/model routing, persisted preferences, setup scopes, and fallback behavior as compatibility-sensitive review surfaces. (AGENTS.md:28, 94a0d2ec3b79)
  • Scoped Android policy checked: apps/android/AGENTS.md was read fully; the touched files do not add dependencies or release/license surfaces covered by the scoped rules. (apps/android/AGENTS.md:1, 94a0d2ec3b79)
  • Live PR state: Live GitHub data shows head 5a9e540, mergeStateStatus CLEAN, green relevant CI, and labels including maintainer, needs proof, compatibility risk, and security-boundary risk. (5a9e54039e2c)
  • Failed model patch still does not gate send: sendMessageAwaitAcceptance only waits for modelSelectionMutex and then proceeds to chat.send; setSessionModelAwait returns false on patch failure but that failure is not carried into the send path. (apps/android/app/src/main/java/ai/openclaw/app/chat/ChatController.kt:557, 5a9e54039e2c)
  • Selected model is cleared but not hydrated: beginHistoryLoad clears _selectedModelRef, while parseSessionEntry still builds ChatSessionEntry without model/modelProvider even though gateway sessionInfo includes those fields. (apps/android/app/src/main/java/ai/openclaw/app/chat/ChatController.kt:501, 5a9e54039e2c)
  • Gateway session info exposes model identity: Current main's buildGatewaySessionRow returns modelProvider and model in session rows, so Android has a gateway-backed source to hydrate the chip from sessionInfo. (src/gateway/session-utils.ts:2233, 94a0d2ec3b79)

Likely related people:

  • steipete: Authored the linked mobile-polish parent issue and recent merged Android/shared chat work that this PR extends, including session controls, message actions, link-preview chat UI, and the merged iOS model picker sibling. (role: recent area contributor and likely follow-up owner; confidence: high; commits: 1f06b890a66f, 88f1ec38d4a5, 876ab9bb0b46; files: apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatScreen.kt, apps/android/app/src/main/java/ai/openclaw/app/chat/ChatController.kt, apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel.swift)
  • vincentkoc: Local blame in this checkout attributes the current Android chat, connection-scope baseline, gateway model-list behavior, and sessions.patch descriptor lines to Vincent Koc's cbf9acf commit; the shallow/grafted history makes this a baseline routing signal rather than sole ownership. (role: baseline source-history signal; confidence: medium; commits: cbf9acff374c; files: apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatScreen.kt, apps/android/app/src/main/java/ai/openclaw/app/chat/ChatController.kt, apps/android/app/src/main/java/ai/openclaw/app/node/ConnectionManager.kt)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.
Review history (3 earlier review cycles)
  • reviewed 2026-07-06T10:31:18.064Z sha 0a33875 :: needs real behavior proof before merge. :: [P2] Block sends when model selection fails | [P2] Hydrate the model chip from session metadata | [P3] Add the section labels to native i18n
  • reviewed 2026-07-06T12:35:46.927Z sha 47040a7 :: needs real behavior proof before merge. :: [P2] Block sends when model selection fails | [P2] Hydrate the model chip from session metadata | [P2] Use the active agent's model catalog | [P3] Add the Android section labels to native i18n
  • reviewed 2026-07-06T13:41:51.627Z sha 8cf70f3 :: needs real behavior proof before merge. :: [P2] Block sends after failed model patches | [P2] Hydrate selected model from session metadata | [P2] Use the active agent catalog for the picker | [P3] Localize the picker section labels

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. labels Jul 6, 2026
@steipete
steipete force-pushed the feat/android-model-picker-pins-nash branch from 0a33875 to 47040a7 Compare July 6, 2026 11:44

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 47040a7a61

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

val modelSections =
remember(modelCatalog, modelFavorites, modelRecents) {
chatModelPickerSections(
catalog = modelCatalog,

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.

P2 Badge Use the active agent's model catalog

When the current chat session belongs to a non-default agent, this sheet is still built from the single modelCatalog that NodeRuntime.refreshModelCatalogFromGateway() loads with models.list and {} (apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt:2837), while the gateway's default list view scopes visibility to resolveDefaultAgentId when no agent is supplied (src/gateway/server-methods/models-list-result.ts:249). sessions.patch later validates against the agent resolved from the session key, so agent-specific models can be absent from the picker, and default-agent entries can be offered only to fail after selection. The picker needs an active-agent/all-model catalog source instead of the default-agent catalog.

Useful? React with 👍 / 👎.

@steipete
steipete force-pushed the feat/android-model-picker-pins-nash branch from 47040a7 to 8cf70f3 Compare July 6, 2026 13:18
@steipete
steipete force-pushed the feat/android-model-picker-pins-nash branch from 8cf70f3 to 5a9e540 Compare July 6, 2026 14:06
@steipete steipete self-assigned this Jul 6, 2026
@steipete
steipete merged commit d375d34 into main Jul 6, 2026
75 checks passed
@steipete
steipete deleted the feat/android-model-picker-pins-nash branch July 6, 2026 14:54
@steipete

steipete commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

Merged via squash.

@steipete

steipete commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

Post-merge review found three synchronization defects in the landed picker: failed model patches could be followed by a send on the prior model, session history did not hydrate/precedence-protect selected model state, and the picker used the default agent's catalog for non-default sessions.

Follow-up #100985 fixes those paths, adds bounded empty-catalog retry and regressions, and landed as 255b763. Exact-head CI proof: https://github.com/openclaw/openclaw/actions/runs/28804597098.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: android App: android maintainer Maintainer-authored PR merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. P2 Normal backlog priority with limited blast radius. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. size: L status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant