Ask a provider for its models, and let the app update its CLI

The Claude model list was four words in machines.rs, copied into every
machine's config.ron at discovery -- so a model the CLI had gained was
unreachable from the phone, which is how Opus 5.5 came to be invisible.
Every catalog is now read from the provider itself when a picker opens:
Claude Code over its control channel (control_request{subtype:list_models},
the same channel the driver sends set_model down, since the CLI has no
listing command), Codex over app-server, llama.cpp from the GGUFs on its
machine. Echo is the one case config.ron still answers, having nothing to
ask. A row the CLI marks disabled -- a model the installed version is too
old to run -- is dropped rather than offered as a chip that fails.

The reading of each catalog lives in that driver's own module, and
provider_models is one line per driver: core code says which driver to ask
and never what an answer looks like. parse_codex_models moved out for the
same reason.

Beside it, a provider now reports the version of its program and can be
told to update it. The version comes from --version on its own machine,
read from whichever stream carried it but only from a run that exited 0 --
llama-server prints its version to stderr, and so does "command not found".
It is never compared against a latest release, which nothing here can know.
Update runs the driver's own updater, or an updateCommand the config file
names for an install those will not touch; its whole output comes back,
because every install on these machines is package-managed and the
updater's refusal is the sentence worth reading. Nothing on its stdin, so a
password prompt fails rather than hangs.

A catalog that cannot be read no longer fails the whole provider view: that
is exactly the provider somebody came there to update, and refusing the
screen took the version and the Update button away with it.

Verified against a scratch server and on the emulator: the live catalog
(1.06s, disabled row absent), all four providers' versions including the
unknown state, the confirmation and output dialogs, a configured command
returning in 87ms with its stdin closed, and Update correctly disabled for
llama.cpp.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
iris-aiandClaude Opus 5 committed 2026-09-23 18:32:51 -04:00
1 parent 53fc59a946
commit 7371af8e36
14 files changed
+773 -135

No files matched your search

+33
View File
@@ -85,6 +85,39 @@ a control that silently did nothing).
should keep being served by the ordinary build. A provider of its own is
what that has to be, because a router process, its preset file and a
model's load settings all hang off the provider.
- **A provider's model catalog is asked for, never written down** (2026-09-23).
`GET /machines/{id}/providers/{p}/models` puts the question to the provider
itself at the moment a picker opens: Claude Code over its control channel
(`control_request{subtype:list_models}`), Codex over app-server
(`model/list`), llama.cpp by listing the GGUFs on that machine's disk. Echo
is the one case the config file answers, because there is nothing to ask.
Until this the Claude list was four words in `machines.rs` copied into every
machine's `config.ron` at discovery, so a model the CLI had gained was
unreachable from the phone and one it had lost was still offered — Opus 5.5
is what found it. **What decides which models exist is the installed CLI and
the account**, neither of which this app is a party to, so anything it
remembers about them is a guess going stale. Rows the CLI marks `disabled`
(a model the installed version is too old to run) are dropped rather than
offered, since choosing one is a session that fails at the spawn. The dispatch
is a line per driver in `machines::provider_models` and the reading of each
catalog lives in that driver's own module — core code knows which driver to
ask, never what an answer looks like.
- **A provider says which version it is, and can be told to update** (2026-09-23).
The provider view reports `<program> --version` from the machine it runs on,
as `known` or `unknown` with the reason: the version is read from whichever of
stdout and stderr carried it, but **only from a run that succeeded**, because
"command not found" is also written to stderr and must never be drawn where a
version goes. It is never compared against a latest release — what the latest
is cannot be known from here, and "up to date" would be a claim nobody
checked. Update runs the driver's own updater (`claude update`, `codex
update`), or an `updateCommand` the config file names for an install those
will not touch — config-file only, never a route, so the phone still names no
command. The updater's **whole output** is what comes back rather than a
verdict read out of it: every install on these two machines is package-managed,
and "Claude is managed by a package manager" is the sentence somebody needs.
It runs with nothing on its stdin, so a command that stops to ask for a
password fails instead of waiting for ever, with a ten-minute backstop for one
that waits on something else.
- **Migration code is deleted once the update carrying it is received.** The
providers/hosts migration ran on the one host there is and is gone. A file
in the old shape now fails to parse, which is correct because no such file