Originally created by: imshaikot
Closes [#35].
Stacked on [#34] (OpenCode), whose branch this targets because both edit the catalog. Merge [#34] first, and this retargets to main.
The model select offers each agent CLI's own list where the CLI has one, and a curated list where it does not. Every failure falls back to a list at least as good as today's.
Runner contract: { args, parse } for a command, { file, parse } for a file. A parser answers null for anything but a clean list.| Agent | Reads |
|---|---|
| Codex | ~/.codex/models_cache.json ($CODEX_HOME respected), visibility: "list" only, in Codex's priority order. Nothing is spawned |
| Antigravity | agy models |
| Grok Build | grok models |
| Cursor CLI | cursor-agent models |
runners/models.ts owns the cache, ~/.browsentic/models.json (0600, written through a temp file and a rename):modelsFor() only reads the cache, so the probe that answers the popup never waits for a list.refreshModels() reads one agent's list, one read at a time per agent. A list stays fresh for 6 h, a failure is retried after 10 min, and a new bin or CLI version is read at once.agentInfo again when a list changes. Recheck forces a read.RunnerStatus.models is { ids, from: 'cli' | 'catalog', at?, error? }. SOCKET_PROTOCOL_VERSION goes from 18 to 19.model-filter.tsx), with the curated picks the account really has first. The active agent shows where its list came from, or why the read failed. A pinned model the CLI no longer lists stays selected, marked not listed.browsentic agent models <name> [--refresh]. browsentic agent also prints each agent's list source.childEnv(), pulled out of launch()), stdin closed, NO_COLOR, and an empty working folder. It runs as its own process group, killed at 10 s or at 512 KB of output, so a wrapper script's children go with it. Tests cover the hang, including a grandchild, the flood, a missing binary and a parser that throws.isModelId() gates every way an id reaches argv.writeAgentModel refuses a bad id, so the panel gets INVALID_INPUT and the CLI exits 1; readAgentConfig drops a hand-edited one with a log line, so the run falls back rather than failing; and vetPlan refuses any --model that fails it, tested for every kind.claude-opus-4-8[effort=high], which the docs recommend, has to pass.grok-4.6/grok-4.5 in an empty HOME, grok-4.7 signed in). Output saying not authenticated is treated as no list.fable, opus, sonnet, haiku, and the default model is sonnet.agy 1.2.11, cursor-agent 2026.09.18, grok 1.0.40, and Codex 0.155.1's cache with its identity and etag replaced.BROWSENTIC_HOME separate, real CLIs): Codex 3, Antigravity 14, Grok 1 and Cursor 241 models, each shown as listed by …, just now.agy that hangs was killed at 10 s with no process left behind, and browsentic agent answered in about a second meanwhile.fable → claude-fable-5-1, opus → claude-opus-5-5, sonnet → claude-sonnet-5, haiku → claude-haiku-4-5.Not verified: the picker in a real browser. It needs the daemon and the extension built from this branch together, which the protocol bump makes all-or-nothing. It was not done on a machine where other sessions were using the running daemon.
yarn check is green: 1675 tests (57 new: models.test.ts, catalog.test.ts, format-when.test.ts, and new cases in index.test.ts, config.test.ts and spawn.test.ts), coverage floors hold (models.ts 96%), and yarn compile and yarn daemon:compile are clean.
opencode models run has been recorded. It keeps its curated list until then.catalog.models. It decodes the new field and ignores it; moving it to runner.models is its own change.🤖 Generated with Claude Code
Ticket changed by: imshaikot