Files
Every agent row keeps its 接入 switch and now has its model picker beside it. Picking a magpie model on an agent that isn't connected connects it first (agent.Pick, used by /api/set), so one pick sets the agent up; disconnecting still puts back what the agent had. The intro line says both ways. An opened row shook: the connected row centred its lines in its 56px min-height, and the 3px row-gap couldn't be folded away, so opening moved the name and the switch and changed the space under them. The first line now has its own height and isn't centred, the row-gap is 0 with the spacing in margins, and the opened part starts where the shut row ended (56/56 at 980px, 90/90 at 560px; the name at 8px and the switch at 19px open and shut). Opening and closing animate the part's height, padding, margin and border on iOS's sheet curve cubic-bezier(.32,.72,0,1), 460 ms open and 340 ms shut, and what's in it fades in from 6px above; a row already open slides shut before another opens. With reduced motion it opens at once. Gemini CLI's .env made by magpie for its key was left holding a newline after a disconnect (main too): DelEnvFile now removes a dotenv file once the deleted keys were all it held. TestPickConnectsFirst, the only.env case in TestEnvFile, agent-start-pick and agent-expand-steady (name and switch unmoved, the opened part's top at the shut row's end, at least 3 heights between on the way open and shut, growing/shrinking only, chromium and webkit at 980 and 560px) fail on the old code and pass. Tests that open rows wait for the slide. In a sandbox HOME with real accounts: Claude Code and Gemini CLI picked while not connected answer PONG and disconnect back to what they had; Goose's pick → disconnect round trip is byte-identical (goose itself stops at a Linear MCP OAuth, so no request). go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui for edit, agent and gui under a temp HOME pass; GUI tests that fail here fail the same on 82340f41 (account-arrange, cli-update, mobile-web, provider-levels-scroll, provider-proxy, redact-rule-row, routing-compact, provider-detect-models, plugin-move).
agents: Pi's model picker lists the models of its openai-codex sign-in from Pi's own registry, and its Default says it lets Pi pick (#709) — Pi's native options came from models.dev by provider id, and models.dev has no openai-codex (Pi's "OpenAI Codex (legacy)" ChatGPT sign-in), so a Pi signed in to both openai and openai-codex was offered openai/… only. Pi's (and OmO's) own options now come first from the model registry in the package Pi was installed from: the pi command on PATH leads to @earendil-works/pi-coding-agent, and pi-ai's dist/providers/data/<provider>.json (Pi 1.0) is read as is, in its order, chat models only (no gpt-image-2), memoized by the file's stamp — the list Pi's /model offers, which moves with every Pi update. A provider the registry doesn't have keeps models.dev's list; openai-codex with no registry in reach falls back to Codex CLI's models_cache.json (the same ChatGPT backend); models.dev's openai list isn't mapped onto it. The rows are openai-codex/<model> under "OpenAI Codex" with OpenAI's logo, a row each beside openai/<model> of the same name; picking one writes defaultProvider=openai-codex, defaultModel=<model> as before. omp shared the gap for its current model's provider (ownOptions): it now reads omp's own catalog (@oh-my-pi/pi-catalog src/models.json, non-chat kinds left out); omp's signed-in providers that aren't the current one are still #341's. Default in Pi's and OmO's model picker now says "clears the default model; Pi picks one on its own" (zh 清除显式默认模型,由 Pi 自动选择; no ja table exists), other agents' still say what they ship with. TestPiOffersItsOwnOpenAICodexModels (a fake npm-linked Pi 1.0 install, temp HOME and PI_CODING_AGENT_DIR, auth.json with openai and openai-codex oauth test metadata), TestPiOpenAICodexWithoutItsRegistry and TestOmpOffersItsOwnOpenAICodexModels fail on the old code (no openai-codex rows) and pass; pi-default-note.test.cjs (webkit, en/zh) fails on the old app.js and passes; agent-disconnect, agent-models and agent-model-groups GUI tests pass. Read-only against this machine's real installs: Pi 1.0.0 gives openai-codex 9 models and openai 44, omp's catalog openai-codex 8. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass but TestProvidersAnswerWhileListsComeIn, TestClaudeSignedOut, TestClaudeAccountNamedAsItsLogin and TestClaudeIdentityServedWhileAsked, timing tests that pass alone. Not tried in the GUI with real Pi sign-ins, nor with a Pi older than 1.0 (no data folder: models.dev and Codex CLI's list as before) or a Windows npm shim.
tests: TestDriftUnwiredEveryAgent leaves WSL agents alone — it walks every agent and picks a magpie model to see the wiring come back, and on a Windows machine with a real distro running, the WSL agents in All() are the user's own: the pick wrote fake/m1 into their real ~/.codex/config.toml, ~/.claude/settings.json and ~/.pi/agent/settings.json through \wsl.localhost. Their files live in the distro, beyond the sandbox home, so the test skips them (a.WSL set). Verified on such a machine: the full ./internal/agent suite leaves the three files' hashes untouched. (#283)
gui: Claude Code's model picker shows each model once (#496) — the rows magpie serves on the very Claude account Claude Code is signed in to fold into one, and models.dev's alias and dated id of one model are one row
codex: on magpie API, Codex's own model as the default keeps magpie as its provider and magpie's models in its picker (#701, nullburn: Codex 选回原生默认模型后,第三方模型目录消失(已选择 magpie API)) — with Codex's sign-in set to magpie API, picking one of Codex's own models (gpt-6-luna) as its default in the Agents row ran unroute(): model_provider, model_catalog_json, openai_base_url and magpie-models.json all went, so every third-party model left Codex's picker while the row still said magpie API; and choosing magpie API while on Codex's own model wired nothing (login.Set only re-ran set for a magpie model). Now, on magpie API, Codex's own model goes as magpie serves it on the ChatGPT account there (codexOwnViaMagpie: gpt-6-luna -> codex/gpt-6-luna, the entries shown to Codex first), so set() takes the magpie branch and only the default changes; the picker's OpenAI group offers those ids under their own names (Label gpt-6-luna) so the value matches; one magpie doesn't serve (the ChatGPT subscription off in magpie) is refused with a message naming both ways out, the config untouched, rather than quietly unwired; choosing magpie API on Codex's own model wires it at once; Sync wires a config an older magpie left so (#701's: model = "gpt-6-luna", sign-in api). Back on ChatGPT, a codex/… default turns into Codex's own model again, straight to OpenAI as before (codexOwnOf); the ChatGPT sign-in's paths are otherwise unchanged. TestCodexAPIKeepsMagpieOnOwnModel (api on an own model, magpie model then own model again, picker values, refusal, back to ChatGPT) and TestCodexAPISyncWiresOwnModel fail on the old code and pass; TestCodexProviderAlsoSetsBaseURL's api case now expects magpie to stay wired on Codex's own model. End to end in a temp HOME with a copy of a real models_cache.json: api, fake/m1, then gpt-6-luna leaves model = "codex/gpt-6-luna" with model_provider, model_catalog_json and openai_base_url, and codex-cli 0.160.0's
codex debug models with CODEX_HOME on that sandbox (gateway ports pointed at :1) lists fake/m1 beside codex/gpt-6-luna and the other codex/ models. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried in the Codex app or with a turn sent through a real ChatGPT account.
agents: Codex signed in with only an API key gets magpie as its provider, its picker listing magpie's models (#322, dakango: switching the Codex app to one of its own models went to OpenAI, 502) — magpie counted an API key in auth.json as a ChatGPT sign-in and only set openai_base_url, counting on Codex to ask the gateway for its models; but Codex asks for none with an API key (models-manager refresh_available_models returns early), so the picker showed Codex's built-in models and the one magpie model, and a built-in one went to OpenAI with the relay's key. Now only a ChatGPT sign-in takes that route; with an API key alone magpie is the model_provider with model_catalog_json listing every magpie model on for Codex, as when signed out, and a sync moves a setup an older magpie wrote; going back to Codex's own model restores the user's model_provider. A Codex model whose OpenAI can't be reached now fails naming the model and what to do instead of a bare 502. TestCodexAPIKeyUsesProvider, TestCodexAPIKeyBaseURLMoved and TestCodexOwnModelUnreached fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass.
fix(agent/codex): recognize CC Switch official OpenAI mirror for grouping and failover (#504) (#579)
agents: Pi's models go to PI_CODING_AGENT_DIR (#304), and the Codex app offers magpie's models' efforts (#310) — Pi keeps settings.json and models.json in PI_CODING_AGENT_DIR when set (~ expanded; @earendil-works/pi-coding-agent config.js), and magpie wrote ~/.pi/agent regardless; omp is read by its own rules (PI_CONFIG_DIR, OMP_PROFILE/PI_PROFILE, then PI_CODING_AGENT_DIR), and the library's Pi and omp targets follow. The Codex app's model picker offers only the efforts in [desktop] enabled-reasoning-efforts of config.toml (ChatGPT.app 26.924: default low…xhigh, ultra, persistent), so a magpie model's max or minimal was never offered; routed through magpie, the efforts its models take are added to the user's list (or the default), nothing else changed, only values the app knows (it drops the setting for any other), comments kept, nothing written when nothing is missing. TestPiAgentDir, TestOmpAgentDir and TestCodexDesktopEfforts fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass.
toml: Codex's config.toml with a multi-line array (notify = [ … ]) is edited right, and an edit that fails leaves every file as it was (#253 kerwinliu00001: "edited TOML is invalid: line 2, column 1: incomplete number", config.toml left with model_provider gone and [model_providers.magpie] added but no model) — the top-level key edits (GetTOMLTop, SetTOMLTop, DelTOMLTop) went line by line with regexes: a new key went after the last line that looked like
key = …, which for notify = [ was inside the array; key = value lines inside a multi-line string were read as keys and a line starting with [ as a table; replacing or deleting a multi-line value touched only its first line. They now locate the top-level keys with the TOML parser, as the table edits already did, each with its value's whole range however many lines it takes (parseTOMLFile, the unnamed root table beside the tables); SetTOMLTop finds all its keys in the file as read and writes once. A file the parser refuses is still read line by line, so a broken config keeps showing its model, and writes to it are refused untouched. And a pick is several writes (Codex: drop model_provider, add magpie's provider table, set openai_base_url and model), each checked, so one failing left the earlier ones on disk: edit.Atomically(fn, paths…) puts each file back to its bytes and mode, or removes one it created, when fn fails, and every field set and Sync of Codex (config.toml and magpie-models.json, so Codex in WSL too), Kimi and Grok goes through it, as do Codex's MCP server writes (putCodex, delCodex). Tested in temp dirs and a temp HOME: the reporter's file read, set and deleted directly and through Codex's model field (signed in, with model_provider and model above the array, with magpie as the provider), the array's text kept and the TOML valid, and back to a Codex model; nested arrays, trailing commas, comments in arrays, [x] and a = b in strings, an inline table in an array; """ and ''' strings holding model = … and [table] lines not read as keys, new keys after their closing quotes, one deleted whole; a forced failure of Codex's last write leaving config.toml byte-identical; TestAtomically. They fail on main (the Codex ones with the reporter's error). go vet ./internal/..., go build -tags nogui (and GOOS=linux, GOOS=windows) and go test -tags nogui ./... pass. Not tried against a real ~/.codex, in WSL or in the GUI; multi-line inline tables (TOML 1.1) are refused by the parser and so left untouched with an error.
agent: Codex's subagents can start at an effort of their own, and Claude Code's subagents can have a model of their own (#468, #469) — Codex reads [agents] default_subagent_reasoning_effort, the effort a spawned subagent starts at when the spawn names none (openai/codex core/src/agent/child_config.rs, config_toml.rs; in codex 0.159.2), and magpie never wrote it. Codex gets a subagent effort field: a square beside its subagents' one that opens the effort slider, unset said to mean the session's effort, or the subagent model's own default. Codex refuses a spawn at a level the subagents' model lacks, so only one it has is taken, and one a new subagent or main model lacks is dropped. Claude Code takes the Agent call's model, then the agent's frontmatter, then CLAUDE_CODE_SUBAGENT_MODEL, then the session's (2.1.287); magpie wrote that env only by itself, the main model while every tier followed it. It gets a subagents field, offered once routed through magpie, whose model outlives a new main model and a tier of its own; unset, the env is written as before. Claude Code has no setting for its subagents' effort beyond each agent's own frontmatter, and omp's task role already takes a thinking level, so neither changes. TestCodexSubagentEffort, TestClaudeSubagentModel and subagent-effort.test.cjs fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; subagent-effort, omp-roles, claude-effort, panel-effort, agent-layout, model-pick, click-scroll, old-webkit pass.
codex: on magpie API, Codex's own model as the default keeps magpie as its provider and magpie's models in its picker (#701, nullburn: Codex 选回原生默认模型后,第三方模型目录消失(已选择 magpie API)) — with Codex's sign-in set to magpie API, picking one of Codex's own models (gpt-6-luna) as its default in the Agents row ran unroute(): model_provider, model_catalog_json, openai_base_url and magpie-models.json all went, so every third-party model left Codex's picker while the row still said magpie API; and choosing magpie API while on Codex's own model wired nothing (login.Set only re-ran set for a magpie model). Now, on magpie API, Codex's own model goes as magpie serves it on the ChatGPT account there (codexOwnViaMagpie: gpt-6-luna -> codex/gpt-6-luna, the entries shown to Codex first), so set() takes the magpie branch and only the default changes; the picker's OpenAI group offers those ids under their own names (Label gpt-6-luna) so the value matches; one magpie doesn't serve (the ChatGPT subscription off in magpie) is refused with a message naming both ways out, the config untouched, rather than quietly unwired; choosing magpie API on Codex's own model wires it at once; Sync wires a config an older magpie left so (#701's: model = "gpt-6-luna", sign-in api). Back on ChatGPT, a codex/… default turns into Codex's own model again, straight to OpenAI as before (codexOwnOf); the ChatGPT sign-in's paths are otherwise unchanged. TestCodexAPIKeepsMagpieOnOwnModel (api on an own model, magpie model then own model again, picker values, refusal, back to ChatGPT) and TestCodexAPISyncWiresOwnModel fail on the old code and pass; TestCodexProviderAlsoSetsBaseURL's api case now expects magpie to stay wired on Codex's own model. End to end in a temp HOME with a copy of a real models_cache.json: api, fake/m1, then gpt-6-luna leaves model = "codex/gpt-6-luna" with model_provider, model_catalog_json and openai_base_url, and codex-cli 0.160.0's
codex debug models with CODEX_HOME on that sandbox (gateway ports pointed at :1) lists fake/m1 beside codex/gpt-6-luna and the other codex/ models. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried in the Codex app or with a turn sent through a real ChatGPT account.
codex: when the ChatGPT account runs out of its allowance after a magpie model was picked, magpie becomes Codex's provider, and goes back beside the sign-in when the allowance is back (#540: once the Codex 5-hour/weekly quota is used up, a third-party model through magpie sends nothing in new or old sessions; Dazzle-sys: the session was on the magpie model all along, the quota went elsewhere) — the Codex desktop app refuses to send for a ChatGPT account that is out of its allowance before any request is made, whichever model the turn is on, so a magpie model beside the sign-in (openai_base_url, ChatGPT mode) is blocked with it; Codex's open-source core and TUI only report a usage limit after a response, the block is the app's, keyed on the account.
bd5bf422 made magpie the provider (model_provider = "magpie", model_catalog_json; the app-server's getAuthStatus then answers requires_openai_auth false, the app's API state) only when a model was picked while the account was already out, so an allowance running out later left Codex as it was. KeepOnAnAccountWithRoom now also looks, every 5 minutes, whether the active Codex account is out (CodexUsedUp, now read through LoginUsage's cache, fetched at most once a minute, as the agents' sync asks it on every catalog sync) and tells the agents' sync when that changes (noteCodexUsedUp → catalog.Touched); Codex's Sync then sets the magpie model again, which wires it as the provider while the account is out, marked codex.out in the stash, and puts it back beside the sign-in once the allowance is back, only where it was moved for that (a provider asked for with sign-in "api" stays). Threads started on Codex's own provider and reopened may still be held by the app; the Codex app may need a restart to take the new config. agent's TestMain stubs codexUsedUp so no test asks OpenAI. TestCodexRunsOutAfterPick (ChatGPT mode, then out: provider; out again: stays; back: beside the sign-in; api stays) fails without the change; TestNoteCodexUsedUp checks only a change is told. No request was made to OpenAI. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui -count=1 ./... under a temp HOME pass.
wsl: OpenCode, Kimi Code, omp and the other CLIs magpie sets up through a file in the home are set up in WSL distros too (#515: on Windows only Codex, Pi, OmO and Claude Code were found in a distro) — fourteen agents join wslKinds, each found by its folder under the distro's $HOME or its command on the distro's own PATH, and listed as <name> · WSL <distro> (opencode@wsl:Ubuntu…): OpenCode (~/.config/opencode, opencode), MiMo Code (~/.config/mimocode, mimo), Kimi Code (~/.kimi-code, kimi; the old kimi-cli's ~/.kimi where that is all there is, found by the command), omp (~/.omp/agent, omp), Crush (~/.config/crush/crush.json, Linux's place, not Windows' LOCALAPPDATA), Hermes Agent (~/.hermes), Grok Build (~/.grok, by its folder alone, as on this machine), Droid (~/.factory), fx (~/.fx, by its folder alone: fx is also the JSON viewer), Command Code (~/.commandcode), MiniMax Code's mcode (~/.minimax), Muse Code (~/.config/muse), Qoder and Qoder CN (~/.qoder, ~/.qoder-cn). Each is its host constructor at a place (opencodeIn, kimiIn, ompIn, crushIn, hermesIn, grokIn, droidIn, fxIn, commandCodeIn, miniMaxIn, museIn, qoderIn/qoderCNIn; the host's opencode(home, cfg) and the rest wrap them), so it writes the same keys and schema as on this machine, but the gateway as the distro reaches it (place.gw/v1: 127.0.0.1 when mirrored, Windows' address under NAT) in its provider, its Check and its Sync, and its folder at the default, as the distro's variables (OPENCODE_CONFIG_DIR, KIMI_CODE_HOME, HERMES_HOME, FACTORY_HOME_OVERRIDE, MINIMAX_DATA_DIR, QODER_CONFIG_DIR…) aren't read (place.getenv is "" there); OpenChamber, a Windows app, isn't looked at for a distro's OpenCode, and omp there, whose version isn't known, gets xhigh rather than a max an older omp refuses. Their notices are the restart advice of their own, as for Codex. A stopped distro's agent is built without looking at its files (place.cold: exists/isDir are false, so the defaults are taken), its options read none (wslOwnAsleep, kimiOwnOptions/ompOwnOptions/miniMaxOwnOptions without a file), and a pick, once the distro is started, builds the agent again warm, so it writes the file the agent reads — an opencode.jsonc already there, ~/.kimi for kimi-cli — not the default. Left out: Gemini CLI and Antigravity (a key lent through .env, a launch command), dsh (profiles and .env), Devin (no gateway; its model list from the devin command on this machine), Goose (no magpie routing; its name is pressly's goose too), Cursor and Copilot (no magpie routing), and the apps (Cline, Hanako, WorkBuddy, ZCode, Claude Desktop, OpenChamber, Alma, Cindy); and the library's skills, MCP and instructions for the new WSL agents, which wslTargetOf still gives only Claude Code, Codex, Pi and OmO. Nothing ran on Windows or in a real WSL. wsl_more_test.go, on fake distro roots under temp dirs with the host's variables pointed elsewhere: TestWSLProbeFindsMoreAgents (each folder and command in the probe, none for grok and fx, kimi-cli by its command), TestWSLMoreAgentsDiscovered (all fourteen listed, no command, UA or aliases), TestWSLMoreAgentsPick (each agent, mirrored and NAT: its file in the distro names the distro's gateway, no 127.0.0.1 under NAT, Check passes, Sync keeps it, the notice; this machine's home and the folders its variables name untouched), TestWSLMoreAgentsStopped (nothing asked nor opened, the last value shown, options, a pick starts the distro and writes there) fail without the change; TestWSLStoppedPickFindsItsFiles fails without the warm rebuild (the pick wrote a second opencode.json beside the .jsonc). go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build and vet ./internal/..., GOOS=windows go test -c ./internal/agent/ ./internal/library/, and go test -tags nogui -count=1 ./... under a temp HOME pass (TestFXInState failed once on its TempDir cleanup race, passes on the rerun; untouched).
agents: Factory's Droid CLI is an agent magpie sets up (#242 tasselx) — droid keeps its settings in ~/.factory/settings.json ($FACTORY_HOME_OVERRIDE/.factory when set), its BYOK models the customModels array ({model, displayName, baseUrl, apiKey, provider "anthropic" | "openai" (Responses) | "generic-chat-completion-api", maxOutputTokens, noImageSupport …}), picked as "custom:<id>". Picking a catalog model through magpie appends one entry per catalog model after the user's own (so their ids never move), each with an explicit id "custom:magpie/<provider>/<model>", the catalog's label as displayName, the gateway and token
magpie, its context (maxContextLimit) and output when known, noImageSupport always written (droid takes a Chat Completions model as blind otherwise), and the API its provider speaks natively so the gateway relays it as it is: Responses → "openai" at /v1, Messages → "anthropic" at the gateway root (Anthropic's SDK adds /v1), anything else "generic-chat-completion-api" at /v1; and sets sessionDefaultSettings.model. The model shows in magpie as magpie/<ref>; the user's sessionDefaultSettings.model is stashed and put back on reset (a top-level "model", the docs' spelling, is left alone and is what droid falls back to), magpie's entries go and an emptied customModels or sessionDefaultSettings with them; everything else in the file, the user's entries included, is kept byte for byte. The picker offers the user's own custom models by the id droid gives them (its "id", else the displayName with whitespace as dashes and a count of the same name before it), including the legacy ~/.factory/config.json custom_models, which magpie never writes. Sync rewrites magpie's entries as providers change; Check reports a missing entry or a changed baseUrl/apiKey; its requests are Droid's in usage (User-Agent factory-cli/<version>). Icon is Factory's own favicon glyph (factory.ai/favicon.svg); lobehub has none. From the docs: docs.factory.ai/cli/byok/overview (customModels fields, the three providers, config.json legacy snake_case merged under settings.json), /cli/configuration/settings (model, sessionDefaultSettings.*), /droid-exec/overview (custom:<Display-Name>-<index>). Verified by reading the shipped droid 0.229.0 bundle (@factory/cli-linux-x64, unpacked, never installed or run): the zod schema of an entry (also id, index, maxContextLimit, enableThinking, reasoningEffort, bedrock-converse), ids as custom:${displayName.trim().replace(/\s+/g,"-")}-${n} with n counting equal names, an entry's own id winning, the lookup by id, top-level model/reasoningEffort/specModeModel moved into sessionDefaultSettings only when unset there, getModel reading sessionDefaultSettings.model, noImageSupport defaulting by provider, FACTORY_HOME_OVERRIDE, and BYOK requests carrying factory-cli/<version>. Tested in a sandbox HOME (internal/agent/droid_test.go): set/sync/reset on a settings.json as droid leaves it with a Groq BYOK model (its entry byte-identical, the file restored exactly), each protocol's provider and baseUrl, a moved baseUrl caught by Check and healed by Sync, picking the user's own model while on magpie's, no file at all, a top-level model, FACTORY_HOME_OVERRIDE and legacy config.json ids (custom:Local-Qwen-0/-1) offered and never written. go build -tags nogui (and GOOS=linux, GOOS=windows) and go test -tags nogui ./... pass (TestClaudeSignedOut and TestClaudeAccountNamedAsItsLogin flaked under load and pass alone). Not tried with a real droid: whether droid's /model lists the entries, that it reaches the gateway on each protocol, and how it sends thinking for a custom model (magpie sets no reasoningEffort/enableThinking) are unverified.
wsl: OpenCode, Kimi Code, omp and the other CLIs magpie sets up through a file in the home are set up in WSL distros too (#515: on Windows only Codex, Pi, OmO and Claude Code were found in a distro) — fourteen agents join wslKinds, each found by its folder under the distro's $HOME or its command on the distro's own PATH, and listed as <name> · WSL <distro> (opencode@wsl:Ubuntu…): OpenCode (~/.config/opencode, opencode), MiMo Code (~/.config/mimocode, mimo), Kimi Code (~/.kimi-code, kimi; the old kimi-cli's ~/.kimi where that is all there is, found by the command), omp (~/.omp/agent, omp), Crush (~/.config/crush/crush.json, Linux's place, not Windows' LOCALAPPDATA), Hermes Agent (~/.hermes), Grok Build (~/.grok, by its folder alone, as on this machine), Droid (~/.factory), fx (~/.fx, by its folder alone: fx is also the JSON viewer), Command Code (~/.commandcode), MiniMax Code's mcode (~/.minimax), Muse Code (~/.config/muse), Qoder and Qoder CN (~/.qoder, ~/.qoder-cn). Each is its host constructor at a place (opencodeIn, kimiIn, ompIn, crushIn, hermesIn, grokIn, droidIn, fxIn, commandCodeIn, miniMaxIn, museIn, qoderIn/qoderCNIn; the host's opencode(home, cfg) and the rest wrap them), so it writes the same keys and schema as on this machine, but the gateway as the distro reaches it (place.gw/v1: 127.0.0.1 when mirrored, Windows' address under NAT) in its provider, its Check and its Sync, and its folder at the default, as the distro's variables (OPENCODE_CONFIG_DIR, KIMI_CODE_HOME, HERMES_HOME, FACTORY_HOME_OVERRIDE, MINIMAX_DATA_DIR, QODER_CONFIG_DIR…) aren't read (place.getenv is "" there); OpenChamber, a Windows app, isn't looked at for a distro's OpenCode, and omp there, whose version isn't known, gets xhigh rather than a max an older omp refuses. Their notices are the restart advice of their own, as for Codex. A stopped distro's agent is built without looking at its files (place.cold: exists/isDir are false, so the defaults are taken), its options read none (wslOwnAsleep, kimiOwnOptions/ompOwnOptions/miniMaxOwnOptions without a file), and a pick, once the distro is started, builds the agent again warm, so it writes the file the agent reads — an opencode.jsonc already there, ~/.kimi for kimi-cli — not the default. Left out: Gemini CLI and Antigravity (a key lent through .env, a launch command), dsh (profiles and .env), Devin (no gateway; its model list from the devin command on this machine), Goose (no magpie routing; its name is pressly's goose too), Cursor and Copilot (no magpie routing), and the apps (Cline, Hanako, WorkBuddy, ZCode, Claude Desktop, OpenChamber, Alma, Cindy); and the library's skills, MCP and instructions for the new WSL agents, which wslTargetOf still gives only Claude Code, Codex, Pi and OmO. Nothing ran on Windows or in a real WSL. wsl_more_test.go, on fake distro roots under temp dirs with the host's variables pointed elsewhere: TestWSLProbeFindsMoreAgents (each folder and command in the probe, none for grok and fx, kimi-cli by its command), TestWSLMoreAgentsDiscovered (all fourteen listed, no command, UA or aliases), TestWSLMoreAgentsPick (each agent, mirrored and NAT: its file in the distro names the distro's gateway, no 127.0.0.1 under NAT, Check passes, Sync keeps it, the notice; this machine's home and the folders its variables name untouched), TestWSLMoreAgentsStopped (nothing asked nor opened, the last value shown, options, a pick starts the distro and writes there) fail without the change; TestWSLStoppedPickFindsItsFiles fails without the warm rebuild (the pick wrote a second opencode.json beside the .jsonc). go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build and vet ./internal/..., GOOS=windows go test -c ./internal/agent/ ./internal/library/, and go test -tags nogui -count=1 ./... under a temp HOME pass (TestFXInState failed once on its TempDir cleanup race, passes on the rerun; untouched).
agents: dsh's patch list written in flow style is edited like a block one, and a file magpie can't edit is named as itself (#445) — a profile's cordis.patch.yml whose top-level array was written as [ { id: some-plugin, disabled: false } ] (valid YAML, and what dsh's own writers keep the template's [] as once they add a row) failed every model switch with "config.yaml is not a list of entries magpie can edit": the patch-list reader took only '- ' lines, and the error named config.yaml whatever the file; since web's list goes first, one such profile stopped all of them. New edit.BlockList reads a file whose one document is a flow sequence as the same list in block style (nested mappings and lists in block style, scalars quoted and tagged as they were, the lines before it and the comments after it kept); dsh's model/effort wiring and its MCP servers (internal/library) read and write such a file through it, and its errors name the real path. dshProfiles puts web first by partition instead of a sort whose less wasn't a strict weak order. TestDshFlowPatchList (agent, library) fails without the change; TestBlockList covers one-line, folded/nested/!!js, empty, CRLF and what isn't a flow list. Sandbox HOME: magpie dsh deepseek-v4-pro on main fails with the config.yaml error, with the change both profiles get the model and web keeps some-plugin. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass.
models: reasoning levels are borrowed again for models Azure also hosts, and a routing group keeps its members' levels when one thinks with none to pick from (ARNO on Discord: routing group在接入harness的时候,没有设置reasoning_efforts。我发现是之前很多能自动推导reasoning_efforts列表的模型现在都推导失败了) — the Azure OpenAI preset (
c35471cf, Catalog "azure, openai") was taken for a maker, so models.dev's azure list was the maker's word on every model it hosts: Kimi K2.5, DeepSeek V3.2 and R1, Grok 4.20/4.1 Fast Reasoning, listed there with no levels, took none on a relay or custom provider (and the gateway capped them at high as levelless), where the providers listing any give low…high, none/high and the rest. Azure is now marked Hosts, as Groq and Ollama Cloud are; its own provider still takes its own list's word. And groupEntries narrowed a group to the levels every member has, so one member that thinks with no levels (a thinking switch, a Claude 4.5 with a budget alone, OpenRouter's qwen3.8-omni-flash) left the group none and dsh's route wrote it without reasoningEfforts; such a member is now skipped as an unknown one is, since the gateway sends it the effort asked, no more than high (fitFor). The same Entry.Efforts feeds dsh, Codex's catalog, Pi and OpenCode. On a copy of the owner's config and models.dev cache: no plain model loses levels; group/auto-claude-haiku-4-5 and -sonnet-4-5 get low/medium/high/max again (they had them on 09-30), group/auto-qwen3-8-omni-flash none/low/medium/xhigh; across models.dev, the 34 ids Azure emptied (kimi-k2.5, deepseek-v3.2, deepseek-r1, grok-4-20-reasoning …) borrow again. TestHostListIsNoMakersWord (provider) and TestDshGroupHasItsMembersLevels (agent: the group's dsh entry has reasoningEfforts and the thinking field offers low/high/max) fail on the old code. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
dsh: a model through magpie that lists no thinking levels offers none, rather than dsh's own four it then turned away (Dazzle-sys, #473: on LongCat-2.5-Preview, every step of DeepSeek Harness's thinking slider failed with "takes an effort of for this model, not \"low\"") — the Agents page's effort field fell back to dsh's own off, low, high and max when a gateway model's catalog entry listed no levels, while setting one checked the model's own levels, which were none, so the slider offered four stops and each was refused with an empty list. Such a model has no levels in dsh either (its route gives it no reasoningEfforts), so none is offered now and the Agents page shows no thinking control for it, as it does for Claude Code on Haiku 4.5; an effort asked for anyway (magpie dsh effort low) says the model has no thinking levels in magpie, never an empty list. dsh's own models and gateway models with levels are unchanged; no other agent's effort field offers levels its setter refuses. TestDshEffortModelWithoutLevels fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; panel-effort, claude-effort, click-scroll, old-webkit pass.
agents: Empryo (#612) — picking a magpie model in Empryo adds a "magpie" provider at the gateway to ~/.empryo/config.json's providers (baseURL + modelsAPI, no envVar) and sets defaultModel to magpie/<catalog id>; the user's defaultModel is stashed and put back on leaving, magpie's provider goes again, and a file magpie made is removed when empty. The user's own providers and keys are left alone. The gateway now answers GET /v1 (and /v1/) with its info, as Empryo lists a keyless provider only when its baseURL answers. Also on WSL. Built from Empryo's docs and its public v2 code, not run against the Empryo binary. TestEmpryo, TestBaseURLAnswers (GET /v1 was 404 before).
agents: Empryo (#612) — picking a magpie model in Empryo adds a "magpie" provider at the gateway to ~/.empryo/config.json's providers (baseURL + modelsAPI, no envVar) and sets defaultModel to magpie/<catalog id>; the user's defaultModel is stashed and put back on leaving, magpie's provider goes again, and a file magpie made is removed when empty. The user's own providers and keys are left alone. The gateway now answers GET /v1 (and /v1/) with its info, as Empryo lists a keyless provider only when its baseURL answers. Also on WSL. Built from Empryo's docs and its public v2 code, not run against the Empryo binary. TestEmpryo, TestBaseURLAnswers (GET /v1 was 404 before).
wsl: OpenCode, Kimi Code, omp and the other CLIs magpie sets up through a file in the home are set up in WSL distros too (#515: on Windows only Codex, Pi, OmO and Claude Code were found in a distro) — fourteen agents join wslKinds, each found by its folder under the distro's $HOME or its command on the distro's own PATH, and listed as <name> · WSL <distro> (opencode@wsl:Ubuntu…): OpenCode (~/.config/opencode, opencode), MiMo Code (~/.config/mimocode, mimo), Kimi Code (~/.kimi-code, kimi; the old kimi-cli's ~/.kimi where that is all there is, found by the command), omp (~/.omp/agent, omp), Crush (~/.config/crush/crush.json, Linux's place, not Windows' LOCALAPPDATA), Hermes Agent (~/.hermes), Grok Build (~/.grok, by its folder alone, as on this machine), Droid (~/.factory), fx (~/.fx, by its folder alone: fx is also the JSON viewer), Command Code (~/.commandcode), MiniMax Code's mcode (~/.minimax), Muse Code (~/.config/muse), Qoder and Qoder CN (~/.qoder, ~/.qoder-cn). Each is its host constructor at a place (opencodeIn, kimiIn, ompIn, crushIn, hermesIn, grokIn, droidIn, fxIn, commandCodeIn, miniMaxIn, museIn, qoderIn/qoderCNIn; the host's opencode(home, cfg) and the rest wrap them), so it writes the same keys and schema as on this machine, but the gateway as the distro reaches it (place.gw/v1: 127.0.0.1 when mirrored, Windows' address under NAT) in its provider, its Check and its Sync, and its folder at the default, as the distro's variables (OPENCODE_CONFIG_DIR, KIMI_CODE_HOME, HERMES_HOME, FACTORY_HOME_OVERRIDE, MINIMAX_DATA_DIR, QODER_CONFIG_DIR…) aren't read (place.getenv is "" there); OpenChamber, a Windows app, isn't looked at for a distro's OpenCode, and omp there, whose version isn't known, gets xhigh rather than a max an older omp refuses. Their notices are the restart advice of their own, as for Codex. A stopped distro's agent is built without looking at its files (place.cold: exists/isDir are false, so the defaults are taken), its options read none (wslOwnAsleep, kimiOwnOptions/ompOwnOptions/miniMaxOwnOptions without a file), and a pick, once the distro is started, builds the agent again warm, so it writes the file the agent reads — an opencode.jsonc already there, ~/.kimi for kimi-cli — not the default. Left out: Gemini CLI and Antigravity (a key lent through .env, a launch command), dsh (profiles and .env), Devin (no gateway; its model list from the devin command on this machine), Goose (no magpie routing; its name is pressly's goose too), Cursor and Copilot (no magpie routing), and the apps (Cline, Hanako, WorkBuddy, ZCode, Claude Desktop, OpenChamber, Alma, Cindy); and the library's skills, MCP and instructions for the new WSL agents, which wslTargetOf still gives only Claude Code, Codex, Pi and OmO. Nothing ran on Windows or in a real WSL. wsl_more_test.go, on fake distro roots under temp dirs with the host's variables pointed elsewhere: TestWSLProbeFindsMoreAgents (each folder and command in the probe, none for grok and fx, kimi-cli by its command), TestWSLMoreAgentsDiscovered (all fourteen listed, no command, UA or aliases), TestWSLMoreAgentsPick (each agent, mirrored and NAT: its file in the distro names the distro's gateway, no 127.0.0.1 under NAT, Check passes, Sync keeps it, the notice; this machine's home and the folders its variables name untouched), TestWSLMoreAgentsStopped (nothing asked nor opened, the last value shown, options, a pick starts the distro and writes there) fail without the change; TestWSLStoppedPickFindsItsFiles fails without the warm rebuild (the pick wrote a second opencode.json beside the .jsonc). go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build and vet ./internal/..., GOOS=windows go test -c ./internal/agent/ ./internal/library/, and go test -tags nogui -count=1 ./... under a temp HOME pass (TestFXInState failed once on its TempDir cleanup race, passes on the rerun; untouched).
agents: Grok Build's model picker folds magpie's rows for the account Grok Build is signed in to (Fate on Discord: grokbuild 在登录态下会加载重复的模型) — signed in, the picker listed Grok Build's own models and then the same models again as the Grok (SuperGrok) subscription via magpie. They are marked Same, as Claude Code's are (#496), so the picker folds them into one row a click opens. viaMagpie also counts as the agent's own a plugin's account for the agent's vendor (Grok moved onto its plugin) when it is the user the agent is signed in to (provider.AgentUser: Grok Build's auth.json). TestGrokFoldsItsOwnAccount fails without it (magpie/grok/grok-4.7 unfolded); the plugin path is not covered by a test.
agents: Grok Build's model picker folds magpie's rows for the account Grok Build is signed in to (Fate on Discord: grokbuild 在登录态下会加载重复的模型) — signed in, the picker listed Grok Build's own models and then the same models again as the Grok (SuperGrok) subscription via magpie. They are marked Same, as Claude Code's are (#496), so the picker folds them into one row a click opens. viaMagpie also counts as the agent's own a plugin's account for the agent's vendor (Grok moved onto its plugin) when it is the user the agent is signed in to (provider.AgentUser: Grok Build's auth.json). TestGrokFoldsItsOwnAccount fails without it (magpie/grok/grok-4.7 unfolded); the plugin path is not covered by a test.
agents: Droid, Qoder, OpenHanako, WorkBuddy, OpenCode and ZCode no longer get an output limit above the model's context window (#347)
wsl: OpenCode, Kimi Code, omp and the other CLIs magpie sets up through a file in the home are set up in WSL distros too (#515: on Windows only Codex, Pi, OmO and Claude Code were found in a distro) — fourteen agents join wslKinds, each found by its folder under the distro's $HOME or its command on the distro's own PATH, and listed as <name> · WSL <distro> (opencode@wsl:Ubuntu…): OpenCode (~/.config/opencode, opencode), MiMo Code (~/.config/mimocode, mimo), Kimi Code (~/.kimi-code, kimi; the old kimi-cli's ~/.kimi where that is all there is, found by the command), omp (~/.omp/agent, omp), Crush (~/.config/crush/crush.json, Linux's place, not Windows' LOCALAPPDATA), Hermes Agent (~/.hermes), Grok Build (~/.grok, by its folder alone, as on this machine), Droid (~/.factory), fx (~/.fx, by its folder alone: fx is also the JSON viewer), Command Code (~/.commandcode), MiniMax Code's mcode (~/.minimax), Muse Code (~/.config/muse), Qoder and Qoder CN (~/.qoder, ~/.qoder-cn). Each is its host constructor at a place (opencodeIn, kimiIn, ompIn, crushIn, hermesIn, grokIn, droidIn, fxIn, commandCodeIn, miniMaxIn, museIn, qoderIn/qoderCNIn; the host's opencode(home, cfg) and the rest wrap them), so it writes the same keys and schema as on this machine, but the gateway as the distro reaches it (place.gw/v1: 127.0.0.1 when mirrored, Windows' address under NAT) in its provider, its Check and its Sync, and its folder at the default, as the distro's variables (OPENCODE_CONFIG_DIR, KIMI_CODE_HOME, HERMES_HOME, FACTORY_HOME_OVERRIDE, MINIMAX_DATA_DIR, QODER_CONFIG_DIR…) aren't read (place.getenv is "" there); OpenChamber, a Windows app, isn't looked at for a distro's OpenCode, and omp there, whose version isn't known, gets xhigh rather than a max an older omp refuses. Their notices are the restart advice of their own, as for Codex. A stopped distro's agent is built without looking at its files (place.cold: exists/isDir are false, so the defaults are taken), its options read none (wslOwnAsleep, kimiOwnOptions/ompOwnOptions/miniMaxOwnOptions without a file), and a pick, once the distro is started, builds the agent again warm, so it writes the file the agent reads — an opencode.jsonc already there, ~/.kimi for kimi-cli — not the default. Left out: Gemini CLI and Antigravity (a key lent through .env, a launch command), dsh (profiles and .env), Devin (no gateway; its model list from the devin command on this machine), Goose (no magpie routing; its name is pressly's goose too), Cursor and Copilot (no magpie routing), and the apps (Cline, Hanako, WorkBuddy, ZCode, Claude Desktop, OpenChamber, Alma, Cindy); and the library's skills, MCP and instructions for the new WSL agents, which wslTargetOf still gives only Claude Code, Codex, Pi and OmO. Nothing ran on Windows or in a real WSL. wsl_more_test.go, on fake distro roots under temp dirs with the host's variables pointed elsewhere: TestWSLProbeFindsMoreAgents (each folder and command in the probe, none for grok and fx, kimi-cli by its command), TestWSLMoreAgentsDiscovered (all fourteen listed, no command, UA or aliases), TestWSLMoreAgentsPick (each agent, mirrored and NAT: its file in the distro names the distro's gateway, no 127.0.0.1 under NAT, Check passes, Sync keeps it, the notice; this machine's home and the folders its variables name untouched), TestWSLMoreAgentsStopped (nothing asked nor opened, the last value shown, options, a pick starts the distro and writes there) fail without the change; TestWSLStoppedPickFindsItsFiles fails without the warm rebuild (the pick wrote a second opencode.json beside the .jsonc). go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build and vet ./internal/..., GOOS=windows go test -c ./internal/agent/ ./internal/library/, and go test -tags nogui -count=1 ./... under a temp HOME pass (TestFXInState failed once on its TempDir cleanup race, passes on the rerun; untouched).
agents: Pi's model picker lists the models of its openai-codex sign-in from Pi's own registry, and its Default says it lets Pi pick (#709) — Pi's native options came from models.dev by provider id, and models.dev has no openai-codex (Pi's "OpenAI Codex (legacy)" ChatGPT sign-in), so a Pi signed in to both openai and openai-codex was offered openai/… only. Pi's (and OmO's) own options now come first from the model registry in the package Pi was installed from: the pi command on PATH leads to @earendil-works/pi-coding-agent, and pi-ai's dist/providers/data/<provider>.json (Pi 1.0) is read as is, in its order, chat models only (no gpt-image-2), memoized by the file's stamp — the list Pi's /model offers, which moves with every Pi update. A provider the registry doesn't have keeps models.dev's list; openai-codex with no registry in reach falls back to Codex CLI's models_cache.json (the same ChatGPT backend); models.dev's openai list isn't mapped onto it. The rows are openai-codex/<model> under "OpenAI Codex" with OpenAI's logo, a row each beside openai/<model> of the same name; picking one writes defaultProvider=openai-codex, defaultModel=<model> as before. omp shared the gap for its current model's provider (ownOptions): it now reads omp's own catalog (@oh-my-pi/pi-catalog src/models.json, non-chat kinds left out); omp's signed-in providers that aren't the current one are still #341's. Default in Pi's and OmO's model picker now says "clears the default model; Pi picks one on its own" (zh 清除显式默认模型,由 Pi 自动选择; no ja table exists), other agents' still say what they ship with. TestPiOffersItsOwnOpenAICodexModels (a fake npm-linked Pi 1.0 install, temp HOME and PI_CODING_AGENT_DIR, auth.json with openai and openai-codex oauth test metadata), TestPiOpenAICodexWithoutItsRegistry and TestOmpOffersItsOwnOpenAICodexModels fail on the old code (no openai-codex rows) and pass; pi-default-note.test.cjs (webkit, en/zh) fails on the old app.js and passes; agent-disconnect, agent-models and agent-model-groups GUI tests pass. Read-only against this machine's real installs: Pi 1.0.0 gives openai-codex 9 models and openai 44, omp's catalog openai-codex 8. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass but TestProvidersAnswerWhileListsComeIn, TestClaudeSignedOut, TestClaudeAccountNamedAsItsLogin and TestClaudeIdentityServedWhileAsked, timing tests that pass alone. Not tried in the GUI with real Pi sign-ins, nor with a Pi older than 1.0 (no data folder: models.dev and Codex CLI's list as before) or a Windows npm shim.
wsl: OpenCode, Kimi Code, omp and the other CLIs magpie sets up through a file in the home are set up in WSL distros too (#515: on Windows only Codex, Pi, OmO and Claude Code were found in a distro) — fourteen agents join wslKinds, each found by its folder under the distro's $HOME or its command on the distro's own PATH, and listed as <name> · WSL <distro> (opencode@wsl:Ubuntu…): OpenCode (~/.config/opencode, opencode), MiMo Code (~/.config/mimocode, mimo), Kimi Code (~/.kimi-code, kimi; the old kimi-cli's ~/.kimi where that is all there is, found by the command), omp (~/.omp/agent, omp), Crush (~/.config/crush/crush.json, Linux's place, not Windows' LOCALAPPDATA), Hermes Agent (~/.hermes), Grok Build (~/.grok, by its folder alone, as on this machine), Droid (~/.factory), fx (~/.fx, by its folder alone: fx is also the JSON viewer), Command Code (~/.commandcode), MiniMax Code's mcode (~/.minimax), Muse Code (~/.config/muse), Qoder and Qoder CN (~/.qoder, ~/.qoder-cn). Each is its host constructor at a place (opencodeIn, kimiIn, ompIn, crushIn, hermesIn, grokIn, droidIn, fxIn, commandCodeIn, miniMaxIn, museIn, qoderIn/qoderCNIn; the host's opencode(home, cfg) and the rest wrap them), so it writes the same keys and schema as on this machine, but the gateway as the distro reaches it (place.gw/v1: 127.0.0.1 when mirrored, Windows' address under NAT) in its provider, its Check and its Sync, and its folder at the default, as the distro's variables (OPENCODE_CONFIG_DIR, KIMI_CODE_HOME, HERMES_HOME, FACTORY_HOME_OVERRIDE, MINIMAX_DATA_DIR, QODER_CONFIG_DIR…) aren't read (place.getenv is "" there); OpenChamber, a Windows app, isn't looked at for a distro's OpenCode, and omp there, whose version isn't known, gets xhigh rather than a max an older omp refuses. Their notices are the restart advice of their own, as for Codex. A stopped distro's agent is built without looking at its files (place.cold: exists/isDir are false, so the defaults are taken), its options read none (wslOwnAsleep, kimiOwnOptions/ompOwnOptions/miniMaxOwnOptions without a file), and a pick, once the distro is started, builds the agent again warm, so it writes the file the agent reads — an opencode.jsonc already there, ~/.kimi for kimi-cli — not the default. Left out: Gemini CLI and Antigravity (a key lent through .env, a launch command), dsh (profiles and .env), Devin (no gateway; its model list from the devin command on this machine), Goose (no magpie routing; its name is pressly's goose too), Cursor and Copilot (no magpie routing), and the apps (Cline, Hanako, WorkBuddy, ZCode, Claude Desktop, OpenChamber, Alma, Cindy); and the library's skills, MCP and instructions for the new WSL agents, which wslTargetOf still gives only Claude Code, Codex, Pi and OmO. Nothing ran on Windows or in a real WSL. wsl_more_test.go, on fake distro roots under temp dirs with the host's variables pointed elsewhere: TestWSLProbeFindsMoreAgents (each folder and command in the probe, none for grok and fx, kimi-cli by its command), TestWSLMoreAgentsDiscovered (all fourteen listed, no command, UA or aliases), TestWSLMoreAgentsPick (each agent, mirrored and NAT: its file in the distro names the distro's gateway, no 127.0.0.1 under NAT, Check passes, Sync keeps it, the notice; this machine's home and the folders its variables name untouched), TestWSLMoreAgentsStopped (nothing asked nor opened, the last value shown, options, a pick starts the distro and writes there) fail without the change; TestWSLStoppedPickFindsItsFiles fails without the warm rebuild (the pick wrote a second opencode.json beside the .jsonc). go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build and vet ./internal/..., GOOS=windows go test -c ./internal/agent/ ./internal/library/, and go test -tags nogui -count=1 ./... under a temp HOME pass (TestFXInState failed once on its TempDir cleanup race, passes on the rerun; untouched).
groups: a routing group whose member's reasoning levels nothing knows offers its other members' levels, and Pi's thinking field offers and shows what Pi runs the model at (Moody-Sin, #597: 路由组设了推理强度最高是否正确应用?) — Pi on group/deepseek (腾讯DeepSeek) at magpie's 最高 listed off alone in its /thinking, and sent no reasoning_effort: Pi clamps defaultThinkingLevel max to the off it offers, so the group's members were asked at the vendor's default, not max.
provider: Antigravity's models at several levels are one model with those levels (#150: 统一 Antigravity 模型变体与客户端推理档位的映射) — Antigravity lists gemini-3.7-flash-low, -medium and -high as three models, "Gemini 3.7 Flash (Low)" …, so a client picked a level twice: once in the model, once as its reasoning effort, with no telling which won. Now ids that are one id with an effort word after it (-low, -medium, -high, -extra-low, -minimal, -xhigh) are one model, gemini-3.7-flash "Gemini 3.7 Flash", declaring only the levels there are (low,medium,high; Gemini 3.1 Pro low,high), so Codex, Pi and the rest get reasoning levels for it. The level a variant is at is what Antigravity's name says where each name says a different one (gemini-3.5-flash-extra-low is "(Low)", gemini-3.5-flash-low "(Medium)"), else its id's word; a model only named like another (gemini-3-flash-agent, "Gemini 3.5 Flash (High)"; gemini-pro-agent) or with no sibling (gpt-oss-120b-medium) stays as listed. A request asks for the variant the effort picks, an effort it lacks at the nearest level (xhigh is high; medium on low/high is high); with none asked, the id without a level if Antigravity has one, else high — what Gemini 3 does unasked — else the highest. A variant's request carries no thinkingLevel, so the id and the level can't disagree. An old variant id still works: as named with no effort, and moved to the effort asked for. Saved picks and agents set to a variant (antigravity/gemini-3.7-flash-high) are moved to the family, at that level where the agent's own is unset, as Cursor's are. For Gemini 3 models without variants, medium now goes as medium to Flash (which takes minimal/low/medium/high) and still as high to Pro (low/high). Tested against the list an account's fetchAvailableModels gave (sandbox cache)
agents: Pi's model picker lists the models of its openai-codex sign-in from Pi's own registry, and its Default says it lets Pi pick (#709) — Pi's native options came from models.dev by provider id, and models.dev has no openai-codex (Pi's "OpenAI Codex (legacy)" ChatGPT sign-in), so a Pi signed in to both openai and openai-codex was offered openai/… only. Pi's (and OmO's) own options now come first from the model registry in the package Pi was installed from: the pi command on PATH leads to @earendil-works/pi-coding-agent, and pi-ai's dist/providers/data/<provider>.json (Pi 1.0) is read as is, in its order, chat models only (no gpt-image-2), memoized by the file's stamp — the list Pi's /model offers, which moves with every Pi update. A provider the registry doesn't have keeps models.dev's list; openai-codex with no registry in reach falls back to Codex CLI's models_cache.json (the same ChatGPT backend); models.dev's openai list isn't mapped onto it. The rows are openai-codex/<model> under "OpenAI Codex" with OpenAI's logo, a row each beside openai/<model> of the same name; picking one writes defaultProvider=openai-codex, defaultModel=<model> as before. omp shared the gap for its current model's provider (ownOptions): it now reads omp's own catalog (@oh-my-pi/pi-catalog src/models.json, non-chat kinds left out); omp's signed-in providers that aren't the current one are still #341's. Default in Pi's and OmO's model picker now says "clears the default model; Pi picks one on its own" (zh 清除显式默认模型,由 Pi 自动选择; no ja table exists), other agents' still say what they ship with. TestPiOffersItsOwnOpenAICodexModels (a fake npm-linked Pi 1.0 install, temp HOME and PI_CODING_AGENT_DIR, auth.json with openai and openai-codex oauth test metadata), TestPiOpenAICodexWithoutItsRegistry and TestOmpOffersItsOwnOpenAICodexModels fail on the old code (no openai-codex rows) and pass; pi-default-note.test.cjs (webkit, en/zh) fails on the old app.js and passes; agent-disconnect, agent-models and agent-model-groups GUI tests pass. Read-only against this machine's real installs: Pi 1.0.0 gives openai-codex 9 models and openai 44, omp's catalog openai-codex 8. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass but TestProvidersAnswerWhileListsComeIn, TestClaudeSignedOut, TestClaudeAccountNamedAsItsLogin and TestClaudeIdentityServedWhileAsked, timing tests that pass alone. Not tried in the GUI with real Pi sign-ins, nor with a Pi older than 1.0 (no data folder: models.dev and Codex CLI's list as before) or a Windows npm shim.
wsl: OpenCode, Kimi Code, omp and the other CLIs magpie sets up through a file in the home are set up in WSL distros too (#515: on Windows only Codex, Pi, OmO and Claude Code were found in a distro) — fourteen agents join wslKinds, each found by its folder under the distro's $HOME or its command on the distro's own PATH, and listed as <name> · WSL <distro> (opencode@wsl:Ubuntu…): OpenCode (~/.config/opencode, opencode), MiMo Code (~/.config/mimocode, mimo), Kimi Code (~/.kimi-code, kimi; the old kimi-cli's ~/.kimi where that is all there is, found by the command), omp (~/.omp/agent, omp), Crush (~/.config/crush/crush.json, Linux's place, not Windows' LOCALAPPDATA), Hermes Agent (~/.hermes), Grok Build (~/.grok, by its folder alone, as on this machine), Droid (~/.factory), fx (~/.fx, by its folder alone: fx is also the JSON viewer), Command Code (~/.commandcode), MiniMax Code's mcode (~/.minimax), Muse Code (~/.config/muse), Qoder and Qoder CN (~/.qoder, ~/.qoder-cn). Each is its host constructor at a place (opencodeIn, kimiIn, ompIn, crushIn, hermesIn, grokIn, droidIn, fxIn, commandCodeIn, miniMaxIn, museIn, qoderIn/qoderCNIn; the host's opencode(home, cfg) and the rest wrap them), so it writes the same keys and schema as on this machine, but the gateway as the distro reaches it (place.gw/v1: 127.0.0.1 when mirrored, Windows' address under NAT) in its provider, its Check and its Sync, and its folder at the default, as the distro's variables (OPENCODE_CONFIG_DIR, KIMI_CODE_HOME, HERMES_HOME, FACTORY_HOME_OVERRIDE, MINIMAX_DATA_DIR, QODER_CONFIG_DIR…) aren't read (place.getenv is "" there); OpenChamber, a Windows app, isn't looked at for a distro's OpenCode, and omp there, whose version isn't known, gets xhigh rather than a max an older omp refuses. Their notices are the restart advice of their own, as for Codex. A stopped distro's agent is built without looking at its files (place.cold: exists/isDir are false, so the defaults are taken), its options read none (wslOwnAsleep, kimiOwnOptions/ompOwnOptions/miniMaxOwnOptions without a file), and a pick, once the distro is started, builds the agent again warm, so it writes the file the agent reads — an opencode.jsonc already there, ~/.kimi for kimi-cli — not the default. Left out: Gemini CLI and Antigravity (a key lent through .env, a launch command), dsh (profiles and .env), Devin (no gateway; its model list from the devin command on this machine), Goose (no magpie routing; its name is pressly's goose too), Cursor and Copilot (no magpie routing), and the apps (Cline, Hanako, WorkBuddy, ZCode, Claude Desktop, OpenChamber, Alma, Cindy); and the library's skills, MCP and instructions for the new WSL agents, which wslTargetOf still gives only Claude Code, Codex, Pi and OmO. Nothing ran on Windows or in a real WSL. wsl_more_test.go, on fake distro roots under temp dirs with the host's variables pointed elsewhere: TestWSLProbeFindsMoreAgents (each folder and command in the probe, none for grok and fx, kimi-cli by its command), TestWSLMoreAgentsDiscovered (all fourteen listed, no command, UA or aliases), TestWSLMoreAgentsPick (each agent, mirrored and NAT: its file in the distro names the distro's gateway, no 127.0.0.1 under NAT, Check passes, Sync keeps it, the notice; this machine's home and the folders its variables name untouched), TestWSLMoreAgentsStopped (nothing asked nor opened, the last value shown, options, a pick starts the distro and writes there) fail without the change; TestWSLStoppedPickFindsItsFiles fails without the warm rebuild (the pick wrote a second opencode.json beside the .jsonc). go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build and vet ./internal/..., GOOS=windows go test -c ./internal/agent/ ./internal/library/, and go test -tags nogui -count=1 ./... under a temp HOME pass (TestFXInState failed once on its TempDir cleanup race, passes on the rerun; untouched).
wsl: OpenCode, Kimi Code, omp and the other CLIs magpie sets up through a file in the home are set up in WSL distros too (#515: on Windows only Codex, Pi, OmO and Claude Code were found in a distro) — fourteen agents join wslKinds, each found by its folder under the distro's $HOME or its command on the distro's own PATH, and listed as <name> · WSL <distro> (opencode@wsl:Ubuntu…): OpenCode (~/.config/opencode, opencode), MiMo Code (~/.config/mimocode, mimo), Kimi Code (~/.kimi-code, kimi; the old kimi-cli's ~/.kimi where that is all there is, found by the command), omp (~/.omp/agent, omp), Crush (~/.config/crush/crush.json, Linux's place, not Windows' LOCALAPPDATA), Hermes Agent (~/.hermes), Grok Build (~/.grok, by its folder alone, as on this machine), Droid (~/.factory), fx (~/.fx, by its folder alone: fx is also the JSON viewer), Command Code (~/.commandcode), MiniMax Code's mcode (~/.minimax), Muse Code (~/.config/muse), Qoder and Qoder CN (~/.qoder, ~/.qoder-cn). Each is its host constructor at a place (opencodeIn, kimiIn, ompIn, crushIn, hermesIn, grokIn, droidIn, fxIn, commandCodeIn, miniMaxIn, museIn, qoderIn/qoderCNIn; the host's opencode(home, cfg) and the rest wrap them), so it writes the same keys and schema as on this machine, but the gateway as the distro reaches it (place.gw/v1: 127.0.0.1 when mirrored, Windows' address under NAT) in its provider, its Check and its Sync, and its folder at the default, as the distro's variables (OPENCODE_CONFIG_DIR, KIMI_CODE_HOME, HERMES_HOME, FACTORY_HOME_OVERRIDE, MINIMAX_DATA_DIR, QODER_CONFIG_DIR…) aren't read (place.getenv is "" there); OpenChamber, a Windows app, isn't looked at for a distro's OpenCode, and omp there, whose version isn't known, gets xhigh rather than a max an older omp refuses. Their notices are the restart advice of their own, as for Codex. A stopped distro's agent is built without looking at its files (place.cold: exists/isDir are false, so the defaults are taken), its options read none (wslOwnAsleep, kimiOwnOptions/ompOwnOptions/miniMaxOwnOptions without a file), and a pick, once the distro is started, builds the agent again warm, so it writes the file the agent reads — an opencode.jsonc already there, ~/.kimi for kimi-cli — not the default. Left out: Gemini CLI and Antigravity (a key lent through .env, a launch command), dsh (profiles and .env), Devin (no gateway; its model list from the devin command on this machine), Goose (no magpie routing; its name is pressly's goose too), Cursor and Copilot (no magpie routing), and the apps (Cline, Hanako, WorkBuddy, ZCode, Claude Desktop, OpenChamber, Alma, Cindy); and the library's skills, MCP and instructions for the new WSL agents, which wslTargetOf still gives only Claude Code, Codex, Pi and OmO. Nothing ran on Windows or in a real WSL. wsl_more_test.go, on fake distro roots under temp dirs with the host's variables pointed elsewhere: TestWSLProbeFindsMoreAgents (each folder and command in the probe, none for grok and fx, kimi-cli by its command), TestWSLMoreAgentsDiscovered (all fourteen listed, no command, UA or aliases), TestWSLMoreAgentsPick (each agent, mirrored and NAT: its file in the distro names the distro's gateway, no 127.0.0.1 under NAT, Check passes, Sync keeps it, the notice; this machine's home and the folders its variables name untouched), TestWSLMoreAgentsStopped (nothing asked nor opened, the last value shown, options, a pick starts the distro and writes there) fail without the change; TestWSLStoppedPickFindsItsFiles fails without the warm rebuild (the pick wrote a second opencode.json beside the .jsonc). go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build and vet ./internal/..., GOOS=windows go test -c ./internal/agent/ ./internal/library/, and go test -tags nogui -count=1 ./... under a temp HOME pass (TestFXInState failed once on its TempDir cleanup race, passes on the rerun; untouched).
agents: Pi's model picker lists the models of its openai-codex sign-in from Pi's own registry, and its Default says it lets Pi pick (#709) — Pi's native options came from models.dev by provider id, and models.dev has no openai-codex (Pi's "OpenAI Codex (legacy)" ChatGPT sign-in), so a Pi signed in to both openai and openai-codex was offered openai/… only. Pi's (and OmO's) own options now come first from the model registry in the package Pi was installed from: the pi command on PATH leads to @earendil-works/pi-coding-agent, and pi-ai's dist/providers/data/<provider>.json (Pi 1.0) is read as is, in its order, chat models only (no gpt-image-2), memoized by the file's stamp — the list Pi's /model offers, which moves with every Pi update. A provider the registry doesn't have keeps models.dev's list; openai-codex with no registry in reach falls back to Codex CLI's models_cache.json (the same ChatGPT backend); models.dev's openai list isn't mapped onto it. The rows are openai-codex/<model> under "OpenAI Codex" with OpenAI's logo, a row each beside openai/<model> of the same name; picking one writes defaultProvider=openai-codex, defaultModel=<model> as before. omp shared the gap for its current model's provider (ownOptions): it now reads omp's own catalog (@oh-my-pi/pi-catalog src/models.json, non-chat kinds left out); omp's signed-in providers that aren't the current one are still #341's. Default in Pi's and OmO's model picker now says "clears the default model; Pi picks one on its own" (zh 清除显式默认模型,由 Pi 自动选择; no ja table exists), other agents' still say what they ship with. TestPiOffersItsOwnOpenAICodexModels (a fake npm-linked Pi 1.0 install, temp HOME and PI_CODING_AGENT_DIR, auth.json with openai and openai-codex oauth test metadata), TestPiOpenAICodexWithoutItsRegistry and TestOmpOffersItsOwnOpenAICodexModels fail on the old code (no openai-codex rows) and pass; pi-default-note.test.cjs (webkit, en/zh) fails on the old app.js and passes; agent-disconnect, agent-models and agent-model-groups GUI tests pass. Read-only against this machine's real installs: Pi 1.0.0 gives openai-codex 9 models and openai 44, omp's catalog openai-codex 8. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass but TestProvidersAnswerWhileListsComeIn, TestClaudeSignedOut, TestClaudeAccountNamedAsItsLogin and TestClaudeIdentityServedWhileAsked, timing tests that pass alone. Not tried in the GUI with real Pi sign-ins, nor with a Pi older than 1.0 (no data folder: models.dev and Codex CLI's list as before) or a Windows npm shim.
agents: a command WSL finds on a Windows drive is not the distro's (#406) — WSL appends Windows' PATH, so command -v pi in Ubuntu found /mnt/c/Users/<u>/AppData/Roaming/npm/pi, the Windows npm shim, and magpie listed "Pi · WSL Ubuntu-24.04" and kept bin:pi in wsl.json. The probe now says where each command is and where /proc/mounts puts Windows' drives (/mnt/<letter>/ when it can't), and a command on one is not taken as installed in the distro, for Codex, Pi, OmO and Claude Code alike; one inside the distro, or its config directory, still is. A distro's findings carry the probe that made them, and a command found by an older probe is forgotten on load, so a stale bin:pi goes without the distro running. TestWSLWindowsCommandIsNotTheDistros, TestWSLOldProbeBinsForgotten and TestWSLRunningStaleWindowsPiReprobed fail without the change; TestWSLProbeScriptSaysWhere runs the script under sh. On a real WSL2 distro the probe said win:/mnt/c and win:/mnt/d. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass.
agent: OmO (oh-my-openagent, omo-ai), a fork of Pi that keeps its own ~/.omo/agent, gets its own row: a magpie model sets settings.json's defaultProvider/defaultModel and magpie's provider in models.json there as Pi's does, $OMO_CODING_AGENT_DIR / $SENPI_CODING_AGENT_DIR are honoured, the Library writes its AGENTS.md, mcp.json and skills, and
omo update updates it (#264 rhyme227). Pi's own ~/.pi is left alone. piIn becomes piLike(at, id, name, dir) so both share one wiring. Tested with omo_test.go in agent and library (fail without the change); go vet, go build -tags nogui (darwin, GOOS=linux, GOOS=windows), go test -tags nogui ./... pass.
agents: Pi's model picker lists the models of its openai-codex sign-in from Pi's own registry, and its Default says it lets Pi pick (#709) — Pi's native options came from models.dev by provider id, and models.dev has no openai-codex (Pi's "OpenAI Codex (legacy)" ChatGPT sign-in), so a Pi signed in to both openai and openai-codex was offered openai/… only. Pi's (and OmO's) own options now come first from the model registry in the package Pi was installed from: the pi command on PATH leads to @earendil-works/pi-coding-agent, and pi-ai's dist/providers/data/<provider>.json (Pi 1.0) is read as is, in its order, chat models only (no gpt-image-2), memoized by the file's stamp — the list Pi's /model offers, which moves with every Pi update. A provider the registry doesn't have keeps models.dev's list; openai-codex with no registry in reach falls back to Codex CLI's models_cache.json (the same ChatGPT backend); models.dev's openai list isn't mapped onto it. The rows are openai-codex/<model> under "OpenAI Codex" with OpenAI's logo, a row each beside openai/<model> of the same name; picking one writes defaultProvider=openai-codex, defaultModel=<model> as before. omp shared the gap for its current model's provider (ownOptions): it now reads omp's own catalog (@oh-my-pi/pi-catalog src/models.json, non-chat kinds left out); omp's signed-in providers that aren't the current one are still #341's. Default in Pi's and OmO's model picker now says "clears the default model; Pi picks one on its own" (zh 清除显式默认模型,由 Pi 自动选择; no ja table exists), other agents' still say what they ship with. TestPiOffersItsOwnOpenAICodexModels (a fake npm-linked Pi 1.0 install, temp HOME and PI_CODING_AGENT_DIR, auth.json with openai and openai-codex oauth test metadata), TestPiOpenAICodexWithoutItsRegistry and TestOmpOffersItsOwnOpenAICodexModels fail on the old code (no openai-codex rows) and pass; pi-default-note.test.cjs (webkit, en/zh) fails on the old app.js and passes; agent-disconnect, agent-models and agent-model-groups GUI tests pass. Read-only against this machine's real installs: Pi 1.0.0 gives openai-codex 9 models and openai 44, omp's catalog openai-codex 8. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass but TestProvidersAnswerWhileListsComeIn, TestClaudeSignedOut, TestClaudeAccountNamedAsItsLogin and TestClaudeIdentityServedWhileAsked, timing tests that pass alone. Not tried in the GUI with real Pi sign-ins, nor with a Pi older than 1.0 (no data folder: models.dev and Codex CLI's list as before) or a Windows npm shim.
agents: OpenChamber gets its own row, its model and small model set to magpie's (#321) — OpenChamber keeps its own default model (preferences.json, a copy in settings.json, under $OPENCHAMBER_DATA_DIR or ~/.config/openchamber) and sends it with every session over OpenCode's, so setting OpenCode's model changed nothing it used. Its row writes defaultModel (a fresh updatedAt, defaultVariant cleared as its own settings do) and the small-model override, keeping every other key and refusing a preferences.json it can't read; magpie's provider goes into the OpenCode config it runs on, and stays there while either uses it. OpenCode's config now follows $OPENCODE_CONFIG_DIR: the OpenCode 2 OpenChamber bundles reads only that folder, so what magpie wrote to ~/.config/opencode was never read; OpenCode 1 reads it last, so it wins there too. A project with its own model in OpenChamber is named in the row. Checked against @openchamber/web 2.0.4 on OpenCode 2.0.18 in a sandbox HOME: its settings API returned magpie's model and OpenCode listed magpie's provider. TestOpenCodeConfigDir, TestOpenChamberModel and TestOpenChamberSettingsOnly fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; click-scroll, old-webkit pass.
agent: Pi starts on the model magpie picks, and asks a Messages-only provider's models on Messages (#160 Aimer779; Cleary on Discord)
agents: Pi's models go to PI_CODING_AGENT_DIR (#304), and the Codex app offers magpie's models' efforts (#310) — Pi keeps settings.json and models.json in PI_CODING_AGENT_DIR when set (~ expanded; @earendil-works/pi-coding-agent config.js), and magpie wrote ~/.pi/agent regardless; omp is read by its own rules (PI_CONFIG_DIR, OMP_PROFILE/PI_PROFILE, then PI_CODING_AGENT_DIR), and the library's Pi and omp targets follow. The Codex app's model picker offers only the efforts in [desktop] enabled-reasoning-efforts of config.toml (ChatGPT.app 26.924: default low…xhigh, ultra, persistent), so a magpie model's max or minimal was never offered; routed through magpie, the efforts its models take are added to the user's list (or the default), nothing else changed, only values the app knows (it drops the setting for any other), comments kept, nothing written when nothing is missing. TestPiAgentDir, TestOmpAgentDir and TestCodexDesktopEfforts fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass.
groups: a routing group whose member's reasoning levels nothing knows offers its other members' levels, and Pi's thinking field offers and shows what Pi runs the model at (Moody-Sin, #597: 路由组设了推理强度最高是否正确应用?) — Pi on group/deepseek (腾讯DeepSeek) at magpie's 最高 listed off alone in its /thinking, and sent no reasoning_effort: Pi clamps defaultThinkingLevel max to the off it offers, so the group's members were asked at the vendor's default, not max.
agents: Pi's model picker lists the models of its openai-codex sign-in from Pi's own registry, and its Default says it lets Pi pick (#709) — Pi's native options came from models.dev by provider id, and models.dev has no openai-codex (Pi's "OpenAI Codex (legacy)" ChatGPT sign-in), so a Pi signed in to both openai and openai-codex was offered openai/… only. Pi's (and OmO's) own options now come first from the model registry in the package Pi was installed from: the pi command on PATH leads to @earendil-works/pi-coding-agent, and pi-ai's dist/providers/data/<provider>.json (Pi 1.0) is read as is, in its order, chat models only (no gpt-image-2), memoized by the file's stamp — the list Pi's /model offers, which moves with every Pi update. A provider the registry doesn't have keeps models.dev's list; openai-codex with no registry in reach falls back to Codex CLI's models_cache.json (the same ChatGPT backend); models.dev's openai list isn't mapped onto it. The rows are openai-codex/<model> under "OpenAI Codex" with OpenAI's logo, a row each beside openai/<model> of the same name; picking one writes defaultProvider=openai-codex, defaultModel=<model> as before. omp shared the gap for its current model's provider (ownOptions): it now reads omp's own catalog (@oh-my-pi/pi-catalog src/models.json, non-chat kinds left out); omp's signed-in providers that aren't the current one are still #341's. Default in Pi's and OmO's model picker now says "clears the default model; Pi picks one on its own" (zh 清除显式默认模型,由 Pi 自动选择; no ja table exists), other agents' still say what they ship with. TestPiOffersItsOwnOpenAICodexModels (a fake npm-linked Pi 1.0 install, temp HOME and PI_CODING_AGENT_DIR, auth.json with openai and openai-codex oauth test metadata), TestPiOpenAICodexWithoutItsRegistry and TestOmpOffersItsOwnOpenAICodexModels fail on the old code (no openai-codex rows) and pass; pi-default-note.test.cjs (webkit, en/zh) fails on the old app.js and passes; agent-disconnect, agent-models and agent-model-groups GUI tests pass. Read-only against this machine's real installs: Pi 1.0.0 gives openai-codex 9 models and openai 44, omp's catalog openai-codex 8. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass but TestProvidersAnswerWhileListsComeIn, TestClaudeSignedOut, TestClaudeAccountNamedAsItsLogin and TestClaudeIdentityServedWhileAsked, timing tests that pass alone. Not tried in the GUI with real Pi sign-ins, nor with a Pi older than 1.0 (no data folder: models.dev and Codex CLI's list as before) or a Windows npm shim.
agent: Pi starts on the model magpie picks, and asks a Messages-only provider's models on Messages (#160 Aimer779; Cleary on Discord)
pi: a model that always thinks has off hidden on the Messages API, and its Chinese refusal of thinking off is asked again (#699, liuweifeng: 走 Anthropic Messages API 的 always-thinks 模型被删掉 thinkingLevelMap 的 off,Pi 仍提供 off 导致上游 400) — piThinkingLevels left off out of thinkingLevelMap for every model on anthropic-messages, which is right for Claude (Pi offers a level left out, and Claude takes thinking disabled whatever its levels) but had Pi offer off for ZCode's GLM-5.3-Flash too, whose levels are low/high/max and which answers thinking disabled with 400 "该模型始终支持思考,不可关闭". Now off is left out only for a model that thinks only when asked: gateway.ThinksOnlyWhenAsked, the gateway's own anthropicModel (the test that decides it leaves a Claude's request as sent and turns another vendor's thinking off unless asked), plus a relay's opus-5.5 spelling. Another vendor's model there gets "off": null unless none is among its levels (GLM-5.2's none still maps off to it), as on Chat and Responses. The gateway's alwaysThinks, which asks a refused thinking-off request again with thinking left to the model, didn't know the Chinese wording, so the 400 reached Pi (and any agent whose request the gateway turned off); it now matches 始终…思考 / 思考…不可关闭. OpenCode's variants, dsh's levels and omp's efforts name only the model's own levels, off only for none, so they don't offer a level the model refuses; omp can't hide its off, and the gateway's retry now covers it. TestPiOffOnMessagesAPI (GLM-5.3-Flash off null and not offered; GLM-5.2 off=none; Claude and opus-5.5 off left out) and TestAnthropicPassthroughAlwaysThinksInChinese (asked again once without thinking) fail on the old code and pass. Pi's models.json generated in a temp HOME with PI_CODING_AGENT_DIR in it: zc/GLM-5.3-Flash has "off": null, low/high/max; claude-sonnet-5 has no off key. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass but TestDevinSignedInOnlyInMagpie, a timing test that passes alone. Not tried with a real ZCode account.
pi: changing the thinking level writes only that level, and only when it changed — with the model on magpie it rebuilt providers.magpie, so a contextWindow (or any other field) edited by hand was replaced. Default still clears defaultThinkingLevel and leaves the model and the provider where they are. Picking a model, and the catalog sync, still refresh the provider. OmO shares this path. (#285)
groups: a routing group whose member's reasoning levels nothing knows offers its other members' levels, and Pi's thinking field offers and shows what Pi runs the model at (Moody-Sin, #597: 路由组设了推理强度最高是否正确应用?) — Pi on group/deepseek (腾讯DeepSeek) at magpie's 最高 listed off alone in its /thinking, and sent no reasoning_effort: Pi clamps defaultThinkingLevel max to the off it offers, so the group's members were asked at the vendor's default, not max.
wsl: OpenCode, Kimi Code, omp and the other CLIs magpie sets up through a file in the home are set up in WSL distros too (#515: on Windows only Codex, Pi, OmO and Claude Code were found in a distro) — fourteen agents join wslKinds, each found by its folder under the distro's $HOME or its command on the distro's own PATH, and listed as <name> · WSL <distro> (opencode@wsl:Ubuntu…): OpenCode (~/.config/opencode, opencode), MiMo Code (~/.config/mimocode, mimo), Kimi Code (~/.kimi-code, kimi; the old kimi-cli's ~/.kimi where that is all there is, found by the command), omp (~/.omp/agent, omp), Crush (~/.config/crush/crush.json, Linux's place, not Windows' LOCALAPPDATA), Hermes Agent (~/.hermes), Grok Build (~/.grok, by its folder alone, as on this machine), Droid (~/.factory), fx (~/.fx, by its folder alone: fx is also the JSON viewer), Command Code (~/.commandcode), MiniMax Code's mcode (~/.minimax), Muse Code (~/.config/muse), Qoder and Qoder CN (~/.qoder, ~/.qoder-cn). Each is its host constructor at a place (opencodeIn, kimiIn, ompIn, crushIn, hermesIn, grokIn, droidIn, fxIn, commandCodeIn, miniMaxIn, museIn, qoderIn/qoderCNIn; the host's opencode(home, cfg) and the rest wrap them), so it writes the same keys and schema as on this machine, but the gateway as the distro reaches it (place.gw/v1: 127.0.0.1 when mirrored, Windows' address under NAT) in its provider, its Check and its Sync, and its folder at the default, as the distro's variables (OPENCODE_CONFIG_DIR, KIMI_CODE_HOME, HERMES_HOME, FACTORY_HOME_OVERRIDE, MINIMAX_DATA_DIR, QODER_CONFIG_DIR…) aren't read (place.getenv is "" there); OpenChamber, a Windows app, isn't looked at for a distro's OpenCode, and omp there, whose version isn't known, gets xhigh rather than a max an older omp refuses. Their notices are the restart advice of their own, as for Codex. A stopped distro's agent is built without looking at its files (place.cold: exists/isDir are false, so the defaults are taken), its options read none (wslOwnAsleep, kimiOwnOptions/ompOwnOptions/miniMaxOwnOptions without a file), and a pick, once the distro is started, builds the agent again warm, so it writes the file the agent reads — an opencode.jsonc already there, ~/.kimi for kimi-cli — not the default. Left out: Gemini CLI and Antigravity (a key lent through .env, a launch command), dsh (profiles and .env), Devin (no gateway; its model list from the devin command on this machine), Goose (no magpie routing; its name is pressly's goose too), Cursor and Copilot (no magpie routing), and the apps (Cline, Hanako, WorkBuddy, ZCode, Claude Desktop, OpenChamber, Alma, Cindy); and the library's skills, MCP and instructions for the new WSL agents, which wslTargetOf still gives only Claude Code, Codex, Pi and OmO. Nothing ran on Windows or in a real WSL. wsl_more_test.go, on fake distro roots under temp dirs with the host's variables pointed elsewhere: TestWSLProbeFindsMoreAgents (each folder and command in the probe, none for grok and fx, kimi-cli by its command), TestWSLMoreAgentsDiscovered (all fourteen listed, no command, UA or aliases), TestWSLMoreAgentsPick (each agent, mirrored and NAT: its file in the distro names the distro's gateway, no 127.0.0.1 under NAT, Check passes, Sync keeps it, the notice; this machine's home and the folders its variables name untouched), TestWSLMoreAgentsStopped (nothing asked nor opened, the last value shown, options, a pick starts the distro and writes there) fail without the change; TestWSLStoppedPickFindsItsFiles fails without the warm rebuild (the pick wrote a second opencode.json beside the .jsonc). go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build and vet ./internal/..., GOOS=windows go test -c ./internal/agent/ ./internal/library/, and go test -tags nogui -count=1 ./... under a temp HOME pass (TestFXInState failed once on its TempDir cleanup race, passes on the rerun; untouched).
agents: omp's roles pick their own models (subagents, smol, slow), keep their thinking level, and get back what they had before magpie (#325)
providers: a model a vendor names its own way is merged with the same model from other providers — by its id as Volcengine Ark spells it, or by the model the user says it is the same as in Names & levels (kyzhouxu, #583: 火山引擎获取到的默认 ds 4.1 flash 模型的名称跟其他供应商的名称不一样,没有自动合并到同一个模型分组) — the groups magpie finds merged a model by its id lowercased, its vendor prefix and an eight-digit date taken off and a version's dot made a dash, so Ark's deepseek-v4-1-flash-260910 (a six-digit YYMMDD snapshot) was a group of its own beside ali-tp's deepseek-v4.1-flash. sameModel now also takes off Ark's YYMMDD (a year from 23 to 39, a month and a day that are one), Vertex's "@YYYYMMDD", reads Fireworks' "p" between digits (deepseek-v4p1) as the dot and "_" as "-"; -flash, -thinking, ":batch", a four-digit release (qwen's -2507, kimi-k2-0905) and a number that is no date (-128000, -261301) stay apart. For an id no rule matches (an Ark endpoint id, a relay's own name), each model in a provider's Names & levels now has a "Same as" (等同于) box, its placeholder the name it is merged by now: the model it is the same as, as any vendor spells it, staged in draft.modelPrefs and made with the provider's Save like the other prefs (Cancel drops it, Restore default empties it, nothing sent or redrawn or scrolled before the Save). It is kept in settings' ModelSameAs by "<provider>/<model>" (renamed with the provider like every Model* map), set by provider.SetModelSame (its own id however spelt keeps nothing; a model the provider doesn't serve, or a name of no model, is refused) and taken by autoGroups, AutoStandIn and the agents' reseat (AutoGroupOf); the agents are told once. TestSameModelVendorSpellings (Ark, Fireworks, Vertex, underscores, and pairs that must stay apart) and TestModelSameAsMergesFoundGroup (c/ep-sol-x joins auto-sol once said, GroupFor("sol") finds it, back out when cleared, refusals) are new; TestProviderSaveModelPrefs covers a Save's same; provider-same-as.test.cjs (WebKit, en and zh: placeholder, staging, unsaved, Restore default, Cancel, one Save carrying exactly the changes, no scroll, Chinese for each string) fails without the change and passes with it; provider-levels-save and provider-detect pass. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui -count=1 ./... under a temp HOME pass.
gateway: every full id of a Claude family Claude Code names goes to its tier's model, served by a provider of the user's or not (KevinXC on Discord, v0.1.665, T3 Code 0.0.45 over Claude Code 2.1.287: claude-haiku-4-5, claude-opus-5, claude-fable-5 and claude-sonnet-4-6 went to the haiku/opus/fable/sonnet tiers, but claude-fable-5-1, claude-sonnet-5 and claude-sonnet-5-5 went to the Claude account, 429 "requires usage credits" / "weekly limit") — the stand-in (
4cbd96a3) was only asked for a model magpie shows no entry for, so a full id a provider of the user's serves bare (the Claude account lists claude-sonnet-5, claude-fable-5-1, …; a found auto-<model> group has it too) went there, while one none listed went to the tier: which tier a model reached depended on what the user's providers happen to list, not on the model. T3 Code names every model by its full id rather than Claude Code's alias, so it hit both. Now an id of the opus, sonnet, haiku or fable family (claudeFamily: claude-<family>-<any version>[-date][1m], and claude-3-5-sonnet-… alike, so a release magpie has never heard of is one too) from Claude Code routed through magpie goes to the model its tier is set to before groups or providers are looked at, count_tokens counting it there too. Kept as asked: a provider's "anth/claude-…", another agent's request, Claude Code not routed through magpie (StandIn says nothing), a routing group of the user's own named by the id (one magpie found, auto-…, was never picked for it), and the main model when Claude Code's ANTHROPIC_MODEL is that very id. A tier at an effort of its own (#536, "fake/sol:high[1m]") now reaches its model at that level through a stand-in too, where it was looked up whole and refused. TestClaudeTierStandInByFamily (claude-haiku-4-5, -20251001, claude-haiku-5, claude-opus-5, -5-5, -5-5[1m], claude-opus-6, claude-fable-5, -5-1, -5-1[1m], claude-sonnet-4-6, -5, -5-5, -5-5-20261001, claude-3-5-sonnet-20241022 through /v1/messages, beside a provider serving seven of them bare) fails without the change for the nine served ones; TestClaudeStandIn gains the families and the main model. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui -count=1 ./... under a temp HOME pass (TestDevinSignedInOnlyInMagpie, a known flake, aside).
models: Provider in model names has a third way, a name the user gave used just as they wrote it (#92 marsxxl: v0.1.591 的 Codex 下拉里自定义名称又带上了 · WorkBuddy / · Claude Code) —
559ce021 (#203) had a custom name keep " · <provider>" after it like any other, so "Opus 5.5" read "Opus 5.5 · Claude Code" in every list Labels feeds (Codex's model_catalog_json and the gateway's Codex /models, Pi's, OpenCode's, ZCode's, OMP's, Muse's), while #92 had been promised the name as written. Both are kept: the setting is now Off / Not on names I set (自定义名称不带供应商) / On. On stays the default and keeps #203's behaviour, so nobody's list changes unasked; own leaves a name the user gave as it is and a vendor's name with its provider's; off is #335's plain names. Stored as plainOwnNames beside plainNames, so a settings.json of before reads as it did (plainNames: true is off). POST /api/settings/plain-names takes mode on|own|off (a body of on alone still works), magpie model suffix on|own|off, a Settings save keeps it, and /v1/models' magpie_label now follows the setting through Labels as the agents' lists do. The row's hint is one line that fits (en, zh), and its zh name says 供应商 as the rest of the app. Prompts and headers are unchanged. TestSuffixMode, TestSuffixModesReachAgentFiles (Codex model_catalog_json and Pi models.json), TestModelsListSuffixModes (/v1/models magpie_label) and model-suffix-own.test.cjs (Chromium and WebKit, en and zh) fail without the change; TestModelName and TestModelNameAndLevelsReachAgentFiles keep #203's assertions. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestConfiguredModelsAdvertiseImageInput load-flaky, passes alone); plain-names, click-scroll, old-webkit pass.
agents: Droid, Qoder, OpenHanako, WorkBuddy, OpenCode and ZCode no longer get an output limit above the model's context window (#347)
agents: each agent's CLI shows its version and updates in one click (#202 JoeyMa-zh: Agents 页面增加"更新 CLI"功能 — 检查各 agent CLI 的版本并一键更新) — the Agents page now puts each CLI's installed version after its name (read with
<cli> --version, 10s timeout, no stdin) and, when a newer one is out and magpie can tell how it was installed, a small "Update to x.y.z" pill. How it was installed comes from the resolved binary: the vendor's own installer first (Claude Code's ~/.local/share/claude/versions → claude update, Codex standalone → codex update, ~/.opencode/bin → opencode upgrade), then a global node_modules (bun, pnpm, else npm, run as npm install -g --prefix <prefix> pkg@latest with that prefix's npm, and only where the prefix is writable), then a Homebrew formula or cask allowlisted per agent (brew upgrade [--cask]); anything else shows the version alone, with no button. The latest comes from the npm registry or brew info --json=v2 (no auto-update), cached 6h (15min after a failure); installed versions are cached by the binary's path, size and mtime. GET /api/agents/cli waits at most 3s and says pending, and the page asks again; POST /api/agents/cli/{id} runs the update (one at a time per agent, 10min cap), reads the version again and says so if it did not change, or gives the command's last lines when it fails. The pill goes busy in place and the page never scrolls; not in the tray panel. zh strings added. Tested with a new internal/agent/cliupdate_test.go (version order and parsing; npm/bun/pnpm/brew formula and cask/native installs, a read-only prefix, a local node_modules and unknown binaries in a fake PATH; an update against a fake registry and updater) and a new internal/gui/tests/cli-update.test.cjs in Chromium and WebKit (versions, pills only where magpie can update, row heights, an update with the view scrolled, a failing one, zh). No real update was run. go build (nogui, GOOS=windows, GOOS=linux), go test -tags nogui ./... and the GUI tests pass
agents: each agent's CLI shows its version and updates in one click (#202 JoeyMa-zh: Agents 页面增加"更新 CLI"功能 — 检查各 agent CLI 的版本并一键更新) — the Agents page now puts each CLI's installed version after its name (read with
<cli> --version, 10s timeout, no stdin) and, when a newer one is out and magpie can tell how it was installed, a small "Update to x.y.z" pill. How it was installed comes from the resolved binary: the vendor's own installer first (Claude Code's ~/.local/share/claude/versions → claude update, Codex standalone → codex update, ~/.opencode/bin → opencode upgrade), then a global node_modules (bun, pnpm, else npm, run as npm install -g --prefix <prefix> pkg@latest with that prefix's npm, and only where the prefix is writable), then a Homebrew formula or cask allowlisted per agent (brew upgrade [--cask]); anything else shows the version alone, with no button. The latest comes from the npm registry or brew info --json=v2 (no auto-update), cached 6h (15min after a failure); installed versions are cached by the binary's path, size and mtime. GET /api/agents/cli waits at most 3s and says pending, and the page asks again; POST /api/agents/cli/{id} runs the update (one at a time per agent, 10min cap), reads the version again and says so if it did not change, or gives the command's last lines when it fails. The pill goes busy in place and the page never scrolls; not in the tray panel. zh strings added. Tested with a new internal/agent/cliupdate_test.go (version order and parsing; npm/bun/pnpm/brew formula and cask/native installs, a read-only prefix, a local node_modules and unknown binaries in a fake PATH; an update against a fake registry and updater) and a new internal/gui/tests/cli-update.test.cjs in Chromium and WebKit (versions, pills only where magpie can update, row heights, an update with the view scrolled, a failing one, zh). No real update was run. go build (nogui, GOOS=windows, GOOS=linux), go test -tags nogui ./... and the GUI tests pass
library: agents in a running WSL distro take skills, MCP servers and instructions (#331) — the Library knew only this machine's agent ids, so codex@wsl:Ubuntu and the like were never offered, and the id's colon was refused as an agent's name and can't be in a Windows file name. Claude Code, Codex, Pi and OmO in a distro now get them at their default places under the distro's $HOME through \\wsl.localhost, only while it runs (opening a stopped one's files starts it; its state is kept and asking for it says to start it). Skills go in as copies, made beside and renamed into place and made again when the library's changes, since a Linux agent can't follow a link to a Windows folder (project skills share the same copy code); an MCP server that runs a Windows program (C:\…, *.exe/.cmd/.bat/.ps1) is refused for them with a message; Pi there gets no MCP servers, where its version decides the file. The id's colon is a dot in file and folder names, and setup export/import turns it back. TestWSLTargets, TestWSLSkillsCopied, TestWSLServers and TestWSLInstructions (a fake distro) fail without the change. go vet (and GOOS=windows for agent, library), go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. Not run against a real distro.
agents: whether WSL is in mirrored networking is asked of WSL itself (wslinfo --networking-mode), and a UTF-16 .wslconfig is read (lgtm on Discord: told "WSL Ubuntu isn't in mirrored networking … pointed at Windows (http://192.168.16.1:3425)", while their WSL has always been in Mirrored) — magpie decided it by %UserProfile%\.wslconfig alone, read as UTF-8: a .wslconfig Notepad saved as "Unicode" (UTF-16, which WSL reads) said nothing, a "[wsl2] # comment" header ended the section, and mirrored set anywhere magpie didn't read was missed — so a really mirrored distro was pointed at its default route, which under mirrored is the LAN router, not Windows. The probe each running distro already gets now also prints wslinfo --networking-mode (WSL 2.0 on), kept as the distro's Net ("mirrored", "nat", …) and deciding Mirrored; .wslconfig decides only when there is no wslinfo, and is read as UTF-8, UTF-16LE (BOM or not) or UTF-16BE. A distro seen stopped is probed again once it runs, so WSL restarted in another mode is seen without restarting magpie. The notice says what it knows: "WSL Ubuntu is in nat networking, not mirrored, …", or, without wslinfo, that magpie couldn't ask and .wslconfig doesn't set it — never "isn't in mirrored" on .wslconfig's word alone. TestWSLConfigUTF16 (UTF-8, its BOM, UTF-16LE with and without BOM, UTF-16BE; fails without the change for the UTF-16 three), TestParseProbeNet, TestWSLNetworkingFromWslinfo (wslinfo mirrored with no .wslconfig is 127.0.0.1; wslinfo nat beats a .wslconfig saying mirrored; the fallback either way and its notice) and TestWSLReprobedAfterStop are new. The probe run through the real wsl.exe on a Windows 11 box (WSL 2.6.1, a NAT distro) read net:nat. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui -count=1 ./... under a temp HOME pass.
agents: a command WSL finds on a Windows drive is not the distro's (#406) — WSL appends Windows' PATH, so command -v pi in Ubuntu found /mnt/c/Users/<u>/AppData/Roaming/npm/pi, the Windows npm shim, and magpie listed "Pi · WSL Ubuntu-24.04" and kept bin:pi in wsl.json. The probe now says where each command is and where /proc/mounts puts Windows' drives (/mnt/<letter>/ when it can't), and a command on one is not taken as installed in the distro, for Codex, Pi, OmO and Claude Code alike; one inside the distro, or its config directory, still is. A distro's findings carry the probe that made them, and a command found by an older probe is forgotten on load, so a stale bin:pi goes without the distro running. TestWSLWindowsCommandIsNotTheDistros, TestWSLOldProbeBinsForgotten and TestWSLRunningStaleWindowsPiReprobed fail without the change; TestWSLProbeScriptSaysWhere runs the script under sh. On a real WSL2 distro the probe said win:/mnt/c and win:/mnt/d. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass.
agents: a command WSL finds on a Windows drive is not the distro's (#406) — WSL appends Windows' PATH, so command -v pi in Ubuntu found /mnt/c/Users/<u>/AppData/Roaming/npm/pi, the Windows npm shim, and magpie listed "Pi · WSL Ubuntu-24.04" and kept bin:pi in wsl.json. The probe now says where each command is and where /proc/mounts puts Windows' drives (/mnt/<letter>/ when it can't), and a command on one is not taken as installed in the distro, for Codex, Pi, OmO and Claude Code alike; one inside the distro, or its config directory, still is. A distro's findings carry the probe that made them, and a command found by an older probe is forgotten on load, so a stale bin:pi goes without the distro running. TestWSLWindowsCommandIsNotTheDistros, TestWSLOldProbeBinsForgotten and TestWSLRunningStaleWindowsPiReprobed fail without the change; TestWSLProbeScriptSaysWhere runs the script under sh. On a real WSL2 distro the probe said win:/mnt/c and win:/mnt/d. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass.
agents: Droid, Qoder, OpenHanako, WorkBuddy, OpenCode and ZCode no longer get an output limit above the model's context window (#347)