Repository navigation
Conversation
added 14 commits
October 14, 2026 01:05
The phone can now read and write the desktop's model role defaults over a typed verb instead of a config passthrough. - device_model_defaults_v1 capability plus the three-slot command fields: the whole object always travels, and null is a value, so a host can never read an absent key as "leave unchanged". - RemoteDefaultModels now projects all six host slots as string|null, and setModelDefaults answers with the reconciled projection. A host that only acknowledges the command is followed by an authoritative device read, mirroring the permission-mode pattern. - The composer's UI projection keeps its "absent means nothing configured" shape, so the wider wire type does not leak into presentation.
The device settings page needs one owner for the gate, the read and the write, so the pages only render what it reports. - RemoteModelDefaultsController reads get_model_catalog without a session id (device scope), gates on device_model_defaults_v1 plus the live link, locks while a write runs, and never updates values optimistically: a write is followed by an explicit re-read and only that read may change what the page shows. A result that lands after the gate changed is dropped. - DeviceModelSettingsEntryPolicy decides the entry, its greyed state, its upgrade badge and its one-line reason together, so the row and the reason can never disagree; the entry belongs to the connected control target only. - Both locales gain the entry, degrade, scope and status keys, and the node suite pins the command shape, the six-slot response, the gate matrix, the busy lock, the write-then-read order and the two distinct failures.
The connected desktop's row now offers its own model role settings instead of a re-connect action, and the sheet carries one more page for it. - The row's trailing pill is the same 14-radius SOFT capsule as the connect action, because it is the same slot; an offline row keeps no entry at all, and an older desktop keeps a greyed pill with an upgrade corner badge whose tap explains itself in place. - The sheet page id 'deviceModels' is registered now and reachable from the row. The pages of that page are still a placeholder marked TODO, so the navigation, the sheet state and the degrade path are real before the panels land. - Settings now own typed device-model actions (device-level catalog read and whole-object write) next to the permission-mode pair, so the pages can build the controller client without reaching into the transport.
A controller could read a device's `ai.default_models` through `get_model_catalog` but never write it. Add the typed write path instead of routing a raw config mutation: `SetModelDefaults` writes the three managed role slots as one object (an absent field and an explicit `null` both clear a slot), `ModelDefaultsUpdated` answers with the host's post-reconcile six-slot mapping, and `device_model_defaults_v1` lets a caller negotiate the verb before offering it. The response is a role mapping only, so no model credential can travel on it. Validation rejects a slot that names a model the device does not configure and rejects the semantic session selectors (`primary` / `fast` / `auto` / `default`). Enablement and capability validity stay with the config reconcile, whose repair the response reports instead of echoing the request.
Implement `RemoteSessionRuntimeHost::set_model_defaults` so a controller's primary / fast / image-understanding choice persists on the device it controls. The write merges the three managed slots over the current `ai.default_models` and stores the whole object through the config service, so the host-only slots (`search`, `image_generation`, `speech_recognition`) keep their device values and the existing reconcile runs. The reply is read back from the reconciled config: a slot pointing at a disabled model or one without the capability its role needs reports the repaired value rather than what the request asked for. An id the device does not configure is rejected before anything is persisted.
The settings pages need to know which models a role may be set to, what a role row says, and how the picker is entered and left before any of that is drawn. - `ModelRoleCandidatesPolicy` filters the desktop catalog per role (enabled and capable, desktop order preserved), names the pinned no-model value of each role, derives a role row's value and hint, and locates a role's resolved model with the fast slot falling back to the primary. - `DeviceModelDefaultsSheetState` holds the controller for the connected desktop and owns the write's feedback window, the busy lock and the read-back rule: a value only ever changes through a read. - `SettingsSheetState` gains the sheet's second level (`main` -> `deviceModels` -> `modelPick`) and resets the model state when the sheet closes.
Replaces the device model placeholder with the two real pages. The settings page shows the desktop's summary, one row per role (primary always resolved, fast saying so when it follows the primary, multimodal naming the capability it needs), the three scope lines, and the writes' own feedback: the whole sheet locks while a write is out, the confirmed value is reported for a moment after the read that answers it, and a refusal is shown verbatim with a retry that repeats exactly what failed. There is no manual refresh. The role picker is a second level of the same sheet: the pinned no-model row first, then the desktop's own candidates in its own order, with the multimodal badge only where the capability is the point. Selecting writes and returns; the value shown afterwards is always the one the desktop read back, never the tap. Both pages read the catalog as the protocol reports it and project models through the shared presentation mapper, so a model is named here exactly as the rest of the app names it. A catalog with nothing to offer keeps the rows tappable and says why the lists are empty; a device that does not support the feature says so instead of pretending to be empty. ArkTS notes: the sheet binds its request path as a literal, which ArkTS only accepts against property-typed members, so the two calls are declared as `DeviceModelDefaultsRequestPath` rather than as the controller's method-shaped client interface.
Runs the candidate policy and the sheet state for real over a recorded desktop: which models each role may take and in whose order, what the pinned no-model row means per role, what a role row says, the write lock and the read-back, the refusal and its retry, the stale write a closed sheet drops, and the sheet's two-level page stack. The pages' own tripwires keep the placeholder gone, the refresh button absent, and every scenario id reachable.
The design gallery mounts the production settings sheet over a fixture desktop that speaks the two device-model requests, so the previews exercise the real panels, the real gate and the real controller — including the write lock, which only exists while a request is actually out. Scenarios: device-models (the fixture's own default state), device-model-pick- fast, device-model-pick-multimodal, device-model-pick-primary, device-model-writing, device-model-updated, device-model-degraded and device-model-empty. The family carries one `-dark` suffix for the colour mode, so every state is reachable in both.
The remote device-model verb read `ai` and then wrote a whole
`ai.default_models` object built from that snapshot, so a desktop-side save
committed between the two locks was silently reverted — the hazard the config
service warns about for a get_config-then-set_config pair.
Merge, validate, and store now happen inside one `update_config("ai", ...)`
closure: the id check reads the same `ai.models` the manager will persist
against, the host-only slots are carried over from the same locked read, and the
manager's own `set` path still reconciles the object before it commits. A
rejected id returns from the closure without touching the stored section, so it
is never a partial write.
The verb also documents what clearing actually means per slot: unset `fast`
follows primary and unset `image_understanding` stays unconfigured, while unset
`primary` is re-filled by the reconcile with the device's first enabled
text-capable model.
Tests: the write-path seam is pinned to the single-closure call, and a new
behavioral fixture runs the real config service and reconcile to cover the
device-owned slots surviving the write, the persisted result, the cleared
`primary` fallback, and the rejected-id no-op.
The picker could be open when the desktop stopped answering it, and it drew the last roles it had as if the device had just reported them: the pinned row was checked — "fast follows the primary" on a desktop the phone could no longer reach — and an empty list was blamed on the catalog instead of on the link. `ModelRoleCandidatesPolicy.pickerView` now decides one picker frame from one device state: whether the reason card is shown, whether a retry can repeat the read, whether the rows are drawn, and whether those rows are the device's own answer at all. A mark and the "no models match this role" line are only drawn from a list the device answered; a page that has not been read yet says it is reading rather than blaming a catalog nobody has seen. Both pages state the reason in one shared card, with the retry offered only while the device can still answer, so a closed gate does not grow a button that cannot do anything. The connected desktop's model settings entry was also never exercised: the settings fixture advertised no host capabilities, so every shot of the device row could only ever show the greyed entry with its upgrade badge. The fixture now advertises the command, the pill carries its state in its id, and a test pins the capability chain from the state the shell reads to the branch the row draws. Previews: device-model-degraded-pick and device-model-pick-failed add the two picker states that have no list.
…e-config # Conflicts: # src/apps/mobile/harmonyos/entry/src/main/ets/entryability/EntryAbility.ets # src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteSessionManager.ets
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.
What
Lets a phone configure the controlled desktop's default model roles: 主力模型 / 快速模型 / 多模态模型 (host
ai.default_models.primary / fast / image_understanding). Entry: the settings sheet's 当前远程控制 device list — the connected control target gets a trailing 模型设置 pill (offline rows: none; older hosts: greyed + 升级 badge + reason). The whole flow lives inside the settings sheet as two in-sheet pages: 设备模型设置 → per-role picker.How — typed verb, zero host secrets
SetModelDefaults {primary?, fast?, image_understanding?}(whole-object write of the three slots;null= follow-primary / unconfigured) answered byModelDefaultsUpdatedcarrying all sixdefault_modelsslots after reconcile — the client always renders the echoed/re-read mapping, never the request (primary:nullis re-filled by the device's first enabled text-capable model).device_model_defaults_v1; older hosts cannot parse the verb, so the capability gates the entry, the pages, and every read/write, degrading loudly (never version-guessing).update_config("ai", …)): host-only slots (search/image_generation/speech_recognition) are preserved, concurrent desktop saves can no longer be lost, and reconcile still runs before persist. Unknown model ids are rejected with the same wording asnormalize_remote_model_selection.api_key, endpoint, or provider credential fields (pinned by a contract test). This deliberately does NOT ride thehost_invokeconfig passthrough, whose read family echoes plaintext provider keys (pre-existing, out of scope here).Client
Wire layer (
RemoteModels/RemoteCommandFactory/RemoteSessionManager) →RemoteModelDefaultsController(capability/connection gating, busy lock, write-then-reread with generation fencing — no optimistic updates) → settings entry pill →DeviceModelDefaultsPanel+ModelRolePickerPanel(fast picker: pinned 跟随主力 +text_chatcandidates; multimodal: pinned 未配置 +image_understandingcandidates with the 图片理解 badge only there; primary: required, no null row; candidate order follows the catalog). Unavailable / reading / failed states are stated on both pages with host error text verbatim and retry.Verification
workspace-editor.test.cjs, fails identically on main). New:device-model-defaults17 +model-role-pages21 — gating matrix, six-slot parse, write-then-reread ordering, picker filters/pinned-null rules, unavailable/failed picker states, capability chain.remote_connect_contracts78,remote_connect::tests22, coredevice4 (incl. a real-ConfigServicebehavioral test: host-only slots survive,primary:nullre-filled, rejected id leaves the file byte-identical). rustfmt clean.harmony:architecture,mobile:ui:check, i18n lockstep (both locales + rawfile) green; clean-rebuildCompileArkTS0 errors.theme:color-audit:allis red on thedesign-labsurface only (1 fallback + 7 css-private vars against a zero baseline) — reproduced byte-for-byte fromorigin/main's owndesign-system/apps/design-labtree and its baseline, so it is a pre-existing main-side failure: this branch changes nodesign-system/**orscripts/**file.artifacts/): entry enabled + degraded pills (layout dump proves the enabled id/opacity/clickable), both pickers light+dark, writing-lock, updated-with-readback, degraded, empty catalog, picker unavailable/failed states.Not verified
app.jsonwrites (desktop + CLI simultaneously) are serialized by file replacement only — pre-existing, unchanged here.EntryAbility,RemoteModels,RemoteSessionManager,string.json, …); whoever merges later rebases — regions are disjoint except import/scenario-list unions.