Commit Graph
797 Commits
Author SHA1 Message Date
yetone ee8a371cf4 gui, update: saving the Settings page keeps the models hidden from each agent, and update checks go through magpie's proxy and say so (#294: 设置代理之后只有模型代理生效了,代理没有用于检查 magpie 是否有更新) — POST /api/settings carried over the models' names, levels and images but not HiddenModels, which the page never sends, so changing the theme, the language or anything else there showed every model hidden one by one on the Agents page again; it is carried over with the others. The update feed and the release came through http.DefaultTransport, proxied only because main installs netproxy first; the update client now takes netproxy.Func on a transport of its own (Settings' Proxy, else *_PROXY, else the system's). A check that failed at start-up, before a proxy was set, stayed failed for six hours: saving a new proxy now checks again through it. And the Settings row read "requests to vendors go through {proxy}", which says update checks don't: it now names both. TestSettingsSaveKeepsWhatItDoesNotSend (every field prefsKeep doesn't send survives a save of the theme alone; fails on main with HiddenModels emptied), TestUpdateGoesThroughTheProxy (feed and asset at hosts only a local proxy answers; fails without the transport), TestSettingsProxyRechecksAFailedUpdate (fails without the re-check). account-proxy, provider-proxy and click-scroll GUI tests pass; go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestUserPath, TestClaudeSignedOut, TestClaudeAccountNamedAsItsLogin, known flakes under load, pass alone). 2026-09-30 16:03:52 +08:00
yetone a69ae948dc library: Claude Desktop's MCP servers go into Claude-3p/claude_desktop_config.json as well, where its 3p mode reads them (#297: Claude Desktop 使用 magpie 三方网关(3p)后,library 写入的 MCP 在 Desktop 里看不到) — in 3p mode, which magpie's own provider switch turns on, Desktop's whole userData is Claude-3p (~/Library/Application Support/Claude-3p, %LOCALAPPDATA%\Claude-3p on Windows, ~/.config/Claude-3p), its MCP servers included, so the servers the library wrote into Claude/'s file never showed in Settings → Developer. Where the Claude-3p folder is, its file is now written with Claude/'s, in one edit with each file's own keys kept, read for servers to bring in, and cleared on removal even when Claude/'s no longer has the entry — both, not whichever mode is on, so switching the provider leaves nothing stale and Desktop finds them in either mode; no Claude-3p folder is made for a Desktop never in 3p, and one only ever in 3p (no Claude folder) gets its servers there alone. A server given before reaches Claude-3p on the next sync. Tested with new desktop3p_test.go in a sandbox HOME (both files written and cleared, the user's servers and keys kept, sync of an older server, 3p only); each fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. Not tried against a real Desktop in 3p. 2026-09-30 15:56:52 +08:00
yetone 3810dc3f8c zed: every tool result sent to Zed says is_error, false as well as true, so a turn after a tool call is answered again (yetone/magpie-releases#9, aurevae: "Zed: failed to parse Anthropic request: missing field is_error" on Pi through /v1/chat/completions and on /v1/messages) — magpie's Anthropic request leaves is_error out when it is false, as Anthropic's API allows, but Zed's cloud refuses a tool result without it, so with a Zed account every request after the model's first tool call failed with a 400. The reporter's own table shows the same request going through with is_error true. Zed's Anthropic request now has is_error on each tool_result; the tool inputs' numbers are kept as they were. TestZedToolResultSaysIsError checks a successful and a failed tool result, and a 20-digit number in a tool's input; it fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestDevinSignedInOnlyInMagpie, a known flake under load, passes alone). 2026-09-30 15:53:29 +08:00
yetone de07d8de24 ci: a UI preview goes by the diff, not the PR's description — on #287 the description (out of date) promised a 用量小数位 setting the code doesn't have, and the preview repeated it as the change, captioned a screenshot "默认 0 位" of a setting that wasn't there. The planner is told the description may be wrong, to plan and summarise from the diff, and what the sandbox lacks (subscription quotas above all); the preview now says, apart from the summary (改动(按代码)), where the description and the code disagree (描述与代码不符) and what the sandbox can't show (沙盒里看不到). Tested locally with run.sh on #287's code: it names the missing setting, says the sandbox has only a DeepSeek balance and no percentage windows, and shows the two places the change would be. 2026-09-30 15:45:23 +08:00
Eachann 3dc5577d43 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)
Tested with TestPiThinkingLeavesTheProvider: a hand-edited contextWindow and another provider stay byte for byte through an unchanged level, a changed level and Default; the level itself changes or clears, and Default again does not rewrite the file. gofmt, go vet -tags nogui ./internal/agent/ and go test -tags nogui ./internal/agent/ pass.
2026-09-30 15:41:17 +08:00
yetone bce583dc6e codexcat: each model magpie lists for Codex says it takes parallel tool calls, so Codex 0.147 loads the catalog again (#298) — it failed on every start with "failed to parse model_catalog_json … missing field supports_parallel_tool_calls", leaving Codex unusable with magpie. Codex 0.147 requires the field; later Codex (0.159) default it and ask for parallel calls whatever it says, so true is what they already do. Reproduced in a sandbox HOME with codex-cli 0.147.0 and magpie's catalog; with the field, 0.147.0 and 0.159.2 both load it and send the request (parallel_tool_calls true, as before on 0.159). TestCodexCatalogParallelToolCalls checks every entry; it fails without the field. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-09-30 15:39:37 +08:00
yetone 795a4d7130 ci: the UI preview checks out a fork's PR — actions/checkout now refuses fork code under pull_request_target unless told allow-unsafe-pr-checkout, and every fork PR failed there (#287, run 36682482508); #289 came from a branch here, so it passed. The record job is the one meant to run the PR's code: read-only token, no credentials kept. 2026-09-30 15:34:30 +08:00
yetone d0d0fcef76 providers: DimAgent's subscription is taken out of magpie (a DimAgent team member on X: dim doesn't allow its subscription used through a proxy, and users doing so get banned or lose their discount) — #226 signed in to DimAgent as its desktop client does and served its models through the gateway. The sign-in, the account provider and its dimagent/<model> models, the quota card, magpie accounts … dimagent and the icon are gone, with internal/dimagent. An account signed in before is left out when logins.json is read and dropped from the file at its next write, so its tokens aren't kept. The pasted-callback field and route stay, generic, with no sign-in using them. Tested with TestDimAgentLoginDropped (fails without the filter); in a sandbox HOME with a DimAgent login left in logins.json, magpie accounts --json lists nothing and magpie accounts add dimagent is refused. signin-callback (fixture renamed), old-webkit and click-scroll pass; go vet, go build (GUI, -tags nogui, GOOS=linux, GOOS=windows) and go test -tags nogui ./... pass (TestUserPath and the Claude/Devin sign-in tests flaked under the full run and pass alone). 2026-09-30 15:33:40 +08:00
yetone c46370ad9a gateway: a Codex compaction for a magpie model asks for its summary as Codex asks for its own — streamed, with tool_choice and parallel_tool_calls — so an upstream that checks a Codex request's shape no longer refuses it with "invalid codex request" (#292, byhooi). Codex compacts remotely (a compaction_trigger in /responses) whenever it is on the OpenAI provider, which is how it reaches magpie, and it has no setting to turn that off (remote_compaction_v2 is gone). magpie answers the trigger by asking the model for a summary, but it sent that with stream false and no tool_choice or parallel_tool_calls. The tools are still left out, so the reply is text; compactReply reads the summary from a stream as well as from a whole response, and a failed stream is a 502 with the upstream's message rather than an empty summary. Tested with TestCodexCompactsThroughCodexRelay (a fake upstream that refuses a request not streamed or without tool_choice, parallel_tool_calls, prompt_cache_key or include; fails on main with "invalid codex request") and TestCodexCompactsMagpieModel. go vet, go build -tags nogui (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui ./... pass. 2026-09-30 15:29:15 +08:00
yetone 4dc3d93343 kimi: the new Kimi Code (2.x) is wired in ~/.kimi-code, where it reads its settings — a model picked in magpie never reached it, nor did magpie's models reach its /model (#290, kyzhouxu: Gemini 3.8 flash picked, Kimi Code still starts on the old one). magpie wrote only ~/.kimi/config.toml, the old kimi-cli's; the new CLI copies that over once when it first starts and never reads it again. KimiDir picks $KIMI_CODE_HOME, ~/.kimi-code if there, $KIMI_SHARE_DIR, ~/.kimi if there, else ~/.kimi-code. The new CLI's model tables also say tool_use (kimi-cli refuses the word, so it isn't written there), and its skills go to ~/.agents/skills, which it reads (not ~/.config/agents/skills). Tested with TestKimiCode (fails on main: path .kimi/config.toml, want .kimi-code/config.toml) and TestKimi; in a sandbox HOME the new CLI's kimi provider list shows magpie and the new default model, kimi doctor finds the config valid, and kimi -p went through a sandbox gateway to a fake upstream asked for gemini-3.8-flash with its 26 tools. go vet, go build -tags nogui (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui ./... pass (flaky tests under load pass alone). 2026-09-30 15:29:14 +08:00
yetone 972355cfbe gui: a provider with no key opens its editor again, and a key's plan and balance show the last reading while the vendor is failing — willz on Discord: a local Ollama added without a key only toggled its row when clicked, with "undefined is not an object (evaluating 'copied?.key.set')". drawEditor set copied to false when the provider wasn't a copy, and false?.key is undefined; it is null now. chalice on Discord: a Command Code key's card read "Usage unavailable" — Command Code answers billing/credits 503 at times, and a key's cards, unlike a subscription's, kept no last reading: the balance showed the error and the plan card went. Both now keep the last good reading (quotas.json, under a sha256 digest of the key, never the key) and show it with its time while the failure passes; a key never read still has no plan card. Tested with nokey-editor.test.cjs (an Ollama preset and a custom one without a key open in Chromium and WebKit, English and Chinese; fails without the change) and TestKeyQuotasKeepLast (a fake Command Code going 503, readings reloaded from disk; fails without the change). click-scroll passes; go vet, go build -tags nogui (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui ./... pass (two Claude account tests flaky under load pass alone). 2026-09-30 15:22:28 +08:00
yetone 190248d204 ci: the UI preview records without waiting for approval (the user: 去掉批准吧) — the ui-preview environment keeps the DeepSeek key but no longer names a reviewer; the comments and the "recording" note stop mentioning one. 2026-09-30 15:07:06 +08:00
yetone c9d9652380 library: an agent whose folder is a file isn't written, and skills reach 13 more agents with one link in ~/.agents/skills for those that share it (neiko on Discord: with DeepSeek Harness not installed the Library kept failing on "open ~/.dsh/AGENTS.md: not a directory" and ~/.dsh/config.yaml; inaction: the skills list is short of the Agents page, Kimi Code has none). An agent counted as there when its folder path existed or its command was on PATH, never asking whether the path was a folder — a file at ~/.dsh, or another dsh, made DeepSeek Harness look installed. Now an agent whose folder is a file (or under one) isn't detected (agent.Taken; Goose's, Cindy's and Claude Desktop's own checks want a folder too), a Library target leaves out each place a file stands in the way of, one with none left isn't listed, and an agent given no MCP server with none of magpie's isn't read. Skills: Kimi Code, Goose and Cindy get ~/.agents/skills (Kimi's ~/.config/agents/skills when there, as Kimi reads it first; never ~/.kimi/skills, which would hide Claude Code's from it); Command Code, Devin, Hermes, Droid, Cline, Qoder, Qoder CN, Grok, WorkBuddy, Hanako and fx their own skills folder; Kimi, Goose and Grok a project's .agents/skills. The shared folder is filled first, and an agent that reads ~/.agents/skills besides its own (Codex, Gemini CLI, OpenCode, Crush, DeepSeek Harness and those above) gets no second link to a skill already there. Tested with new taken_test.go (neiko's two errors without the change) and agent_skills_test.go (skills folders, written once, a shared skill imported for Kimi), in sandbox HOMEs; library-items and shared-skills 24/24 in Chromium and WebKit; go vet, go build (-tags nogui, GOOS=linux, GOOS=windows), go test -tags nogui ./... pass. Not tried with real Kimi Code, Goose or Cindy. 2026-09-30 14:40:41 +08:00
yetone e79343b1bb ci: the UI preview's planner is shown how to reach what the PR changes — the pages are outlined closed, so on #289 (a hover colour inside an agent's model list) it once said nothing visible changed and once opened the drag handle's menu instead. It now also gets the PR's own front-end code around the diff: where the changed classes and functions are built and, two hops up, what calls them (here modelsEntry's .ag-models button opening openAgentModels), and is told that something missing from the outlines is to be opened, not taken as absent. A close-up no longer fails on clip (declared inside the try it was read after), and a DeepSeek reply that is cut short or not JSON is asked for again instead of ending the repair. Tested locally with run.sh on #289's source: 2 screenshots of the list, the button red on hover, and the video, no errors. 2026-09-30 14:40:04 +08:00
yetone 4828c66aa7 gui: the model picker's rail names an icon beside it, on the right, level with it — not in the browser's tooltip under the pointer, which put Devin's name over ZCode's Z below it, as if that were ZCode's (Elan on X: 切换的时候zcode的logo是一个动画bug吗). The name comes after a moment, moves at once from one icon to the next, and goes on a click, on leaving the rail, on its scroll and when the picker closes; a rail icon focused from the keyboard is named too. Tested with rail-tip.test.cjs in Chromium and WebKit, English light and Chinese dark: the name to the right of its icon, level with it, over no rail icon, no title left on the button, the next icon named at once, gone on click, leave and Esc; it fails on the old code. click-scroll, old-webkit, menu-scroll and model-pick pass; go vet, go build -tags nogui (darwin, GOOS=linux, GOOS=windows), go test -tags nogui ./internal/gui/... pass. 2026-09-30 14:31:04 +08:00
yetone 3a193423b6 ci: a UI preview's close-up keeps what's around the thing it shows (at least 560×340) and no screenshot is stretched past its own size in the PR — the first real run (#289) cropped a hover to the button alone, 100×68, then showed it 760 wide. Checked on #289 with workflow_dispatch. 2026-09-30 14:27:19 +08:00
yetone 1721fac530 devin: an account signed in only in magpie works without the Devin CLI signed in, and is listed once (蓝猫 on Discord: every request from Claude Code failed until devin auth login, then the same account showed twice) — the model list that turns a family and effort into the variant Devin takes (claude-sonnet-5-5 + high → claude-sonnet-5-5-high) was asked only of the CLI's own sign-in, so with that signed out or its key refused the bare family id went to Devin and was refused. It is now asked of the signed-in accounts in their own homes, the one in use first, the CLI's own last. And the CLI's own sign-in isn't listed beside a working magpie account of the same user (an old logins.json entry renamed to that user when the CLI signed in again). Tested with a new TestDevinSignedInOnlyInMagpie in a sandbox HOME with a fake devin CLI and exchange server, following 蓝猫's steps: it fails without either change (variant stays claude-sonnet-5-5; dev@example.com listed twice). go vet, go build (-tags nogui, GOOS=linux, GOOS=windows), go test -tags nogui ./... pass (TestUserPath and TestClaudeAccountNamedAsItsLogin flake under load, pass alone). Not tried against the real Devin API. 2026-09-30 14:24:53 +08:00
yetone 59aec42beb agents: an agent's model list shows each model's maker's logo (这里能否显示模型的图标?) — the list had a logo only on a provider's heading, so a relay's or a routing group's rows were names alone. Now each row has its maker's: known by the model's family (gpt → OpenAI, claude, gemini, grok, qwen, glm, mimo/xiaomi, step, …; "vendor/model" ids too), a routing group by the name it was given or its id, a model of a vendor's own provider by the provider's logo; a routing group whose maker isn't known shows the routing mark, anything else keeps the slot empty so the names line up; a model turned off has its logo dimmed. Checked in a sandbox HOME with a copy of the real providers (keys blanked): 路由组 shows OpenAI for GPT-6 Sol/Luna/Astra, Claude, MiMo, Grok, Qwen and the routing mark for semantic/test; AiHubMix's rows each their maker's; light and dark 2026-09-30 14:23:48 +08:00
yetone 7396b11998 ci: a pull request that changes the UI shows it — the real app built from the PR, run with a real DeepSeek key, screenshots and, when the change spans pages or needs clicking, an mp4 recording with the pointer, its trail and every click drawn in, in a fixed block at the foot of the PR's description (the user: 如果某个PR涉及了UI/UX改动,那么就把这个PR中的Magpie真实的运行起来…录屏中一定要画出鼠标指针的位置,以及鼠标的轨迹,以及鼠标的点击; 一定要贴在原始的PR Description的下面的固定区域).
.github/workflows/ui-preview.yml, on pull_request_target or by hand (-f pr=N): detect (internal/gui/assets/, the GUI's non-test Go, or the ui-preview label) puts a "recording" note in the block; record, behind the ui-preview environment's approval since PRs come from forks, builds the PR's magpie (nogui), runs `magpie web` in a fresh home with DeepSeek added and a few requests sent through the gateway, and has DeepSeek plan from the diff and the pages' real outlines what to click and capture (record.mjs, Chromium at 2x, cursor.js drawing the mouse); publish, which never runs the PR's code, puts the files on the ui-previews branch served by Pages and rewrites only the block between <!-- magpie-ui-preview:begin/end --> — the video's poster links to its player page, since GitHub embeds no video it didn't host. Captions say what is being done, never what should come of it. Anything on screen matching the key or the web link's key throws the whole recording away.

Tested locally with run.sh on 033a151's diff in a sandbox home: 3 screenshots and a 16 s mp4 of the Codex model list's 全部隐藏/全部显示, pointer, trail and clicks visible; actionlint passes. Not yet run on GitHub.
2026-09-30 14:22:13 +08:00
yetone 49e5c99c73 alma: a provider whose models Alma lists as the models themselves ({"id","name","enabled","capabilityOverrides",…}) is read as their ids, so it no longer leaves all of Alma's providers unread — Alma showed as not set with "json: cannot unmarshal object into Go struct field almaProvider.models of type string" (the user, a custom provider "Relay Test" with gpt-5.5 at medium/xhigh). Alma keeps a provider's models as ids or as objects and reads either as its id (server.ts updateProviderModels, database-sqlite, references/providers); magpie read only ids. The ids are still what magpie writes. Tested with TestAlmaModelObjects (a provider with object models beside the user's: magpie's model is applied and read back, magpie's own listed as objects syncs nothing, no drift; fails on main with that error) and by decoding the running Alma's /api/providers. go vet, go test -tags nogui ./..., go build -tags nogui (darwin, GOOS=linux, GOOS=windows) pass. 2026-09-30 14:21:35 +08:00
Anx 575a8f5fe3 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) 2026-09-30 14:11:30 +08:00
yetone 85fa42c172 devin: a tool's description goes in the instructions and the tool with a pointer to it, so WorkBuddy's requests are answered (silenx on X: devin 的到 workbuddy 下使用一直出现 502,不知哪里设置问题) — Devin answers "an internal error occurred" to some agents' tools as they describe themselves, whatever the model: WorkBuddy's Read ("Reads a file from the local filesystem. You can access any file directly by using this tool.", also with case or spacing changed, or with words before it) and its TaskOutput ("Takes a task_id parameter identifying the task"), which magpie passed on as a 502 on every request. The same words in a message go through, so the descriptions are now at the head of the first message after the instructions, in <tool_descriptions>, and each tool is sent with its name, its schema and a pointer to its own. Reproduced in a sandbox HOME on a Devin Teams sign-in with WorkBuddy's own request, captured from its bundled CLI: 502 on SWE-2, Claude Opus 5.5 and SWE-1.7 Lightning, 200 without tools; now 200, and WorkBuddy's CLI on all three read a file and wrote another through its Read and Write tools; Claude Code on SWE-2 did the same. TestBuildDevin checks the descriptions in the instructions, each tool's pointer, and none of either when no tool is to be called. go vet, go build -tags nogui (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui ./... pass. 2026-09-30 14:09:57 +08:00
yetone 033a15112d gui: an agent's model list hides every model at once — "Hide all" at its foot beside "Show all" (which was "Show all again"), so picking a few means hiding all and ticking those back, not a group at a time (the user: 能有一个便捷按钮来整体的全部隐藏?现在只能按照组全部隐藏). The model the agent is set to stays; "Shown" keeps what was just hidden till the view changes; each button is disabled when it has nothing to do. Tested with agent-models.test.cjs in Chromium and WebKit, English and Chinese: Hide all sends the 30 models but the one in use, the line under the name reads 1 / 31, then Show all sends none. click-scroll and old-webkit pass; go vet, GOOS=linux build, go test -tags nogui ./internal/gui/... pass. 2026-09-30 13:33:51 +08:00
yetone c6ca0ec3e5 gui: the tray panel's link to one request opens the Routing page on it again — ShowMain escaped the whole view it was given (831df85, v0.1.458, for magpie gui <tab>), so "routing&req=42" reached the window as one view named that, which is none: the window came up on no tab, the request not picked. Found reviewing #239, whose "Open Usage" link hit the same. The view mainView builds keeps its req as its own parameter (mainURL); only a tab named on the command line — magpie gui settings, a restart to update — is escaped (argView). Tested with a new TestMainURL (routing&req=42 comes through as view=routing, req=42; a command-line tab is escaped); it fails with the old escaping. panel-routing.test.cjs passes; go vet, go build (GUI, -tags nogui, GOOS=linux, GOOS=windows), go test -tags nogui ./... pass. 2026-09-30 13:09:27 +08:00
yetone 5b35b139c3 zcode: requests to zcode.z.ai name the machine with X-Device-Mid, so a Z.ai account with only ZCode's Start Plan signs in again (#282 volcanicll: /api/v1/zcode-plan/billing/balance now refuses a request without it, 400 code 3001 "parameter error", and sign-in read that as the Start Plan having ended) — ZCode's apiClient sends it on every request; the server asks only that it is there. magpie makes one v4 UUID for this machine, kept as zcode-device-mid beside providers.json (never ZCode's deviceMid, never ~/.zcode), and sends it on zcode.z.ai calls and Start Plan model requests; Z.ai's and BigModel's business APIs don't get it. A Start Plan balance that can't be read is now said as such in the sign-in error, with the server's code ("parameter error (400, code 3001)"), instead of "has ended or was never started", which stays for an account whose Start Plan really has. Tested with a fake zcode.z.ai refusing a request without the header like the real one (TestZCodeSignInStartPlan, TestZCodeStartPlanOwnAccount, TestZCodeStartPlanRouting and a new TestZCodeSignInStartPlanDeviceMid fail without it): the id is a v4 UUID, kept across a restart, sent to zcode.z.ai only; with it stripped sign-in says the server's reason; an ended Start Plan still says ended. go vet, go build (GUI, -tags nogui, GOOS=linux, GOOS=windows), go test -tags nogui ./... pass. Not tried against the real zcode.z.ai. 2026-09-30 12:51:59 +08:00
yetone 3df75514b9 agents: each agent's own model list — under an agent's name on the Agents page, "Showing 5 / 32 models" (or "All 41 models") opens a list to take models out of that agent's picker one by one and put them back (@ChongkaiX on X: 在 magpie 里选哪些模型出现在 Codex 的模型列表里) — until now an agent's list could only be narrowed by whole providers or families (Visible). settings.HiddenModels holds, by agent, the catalog entries taken out, after Visible: a deny list, so a new model is shown. provider.CatalogFor drops them, so the gateway's /models, the agents' files and pickers and Codex's catalog all follow in one place, and catalog.Touched rewrites the agents' files; Codex's own ChatGPT models are dropped from the backend's list too (CodexNativeHidden), and the list's ETag and models_cache tag carry them (CodexListTag) so Codex asks again. The model an agent is set to is marked 在用 and never taken out, on the server as well. The list groups routing groups first, then providers with their logos and on/total, a long group folded; search, 全部/已显示, a group's 全部显示/全部隐藏 on hover, 恢复全部显示; a click on a row applies at once, Esc or a click elsewhere closes it, and nothing scrolls. A renamed provider's hidden models follow it. Tested with agent-models.test.cjs in Chromium and WebKit, English and Chinese; TestHiddenModels (provider), TestCodexModelListHidesOnAgentsPage (gateway) and TestAgentModelsAPI (gui) in sandbox HOMEs. click-scroll, panel-fold, old-webkit, model-pick, menu-scroll, add-button pass; GOOS=linux go vet, linux and windows builds, go test -tags nogui ./... pass. Not tried in the real Wails window or against a real Codex. 2026-09-30 12:44:59 +08:00
yetone 98d1674a52 library: Antigravity is a Library target — rules, MCP servers and skills reach agy and the Antigravity app alike (#277) — both read their user-wide customizations from ~/.gemini/config: GEMINI.md there, mcp_config.json (a remote server written as agy's own mcp add writes it, its serverUrl, "type": "sse" only for an SSE one so magpie reads it back as one; a url agy read in place of serverUrl is read too; the user's own servers and anything they added to an entry, disabled included, are kept) and skills; a project's skills go in the shared .agents/skills. The rules card says Antigravity also reads Gemini CLI's ~/.gemini/GEMINI.md, so text given to both is read twice. RTK is left out: rtk 0.50 has no user-wide hook for Antigravity. Tested with antigravity_test.go in a sandbox home (target paths and the antigravity-cli alias; stdio, http and sse servers written and read back, user keys and servers kept); library-items.test.cjs 18/18; go vet, go build (GUI, -tags nogui, GOOS=linux, GOOS=windows), go test -tags nogui ./... pass. 2026-09-30 12:42:56 +08:00
yetone 831df8579e update: a restart to update from the open window (Windows, Linux) opens the window again, on the tab it was asked from, instead of the tray icon alone (#275 Aimer779: 设置页更新重启后主窗口消失,只剩托盘) — RelaunchBinary always started magpie tray, autostart's argument, so the new version came up as a tray icon however the update was asked for; the Mac reopens its .app and was fine. The window's Update pill and Settings' "Restart to update" send the tab they're on, the new process starts as magpie gui <tab>; the tray menu's Restart to Update brings the window back if it was open and keeps to the tray if not; autostart stays tray. magpie gui <tab> / magpie app <tab> open the window on that tab, and hand it to a magpie already running. The MAGPIE_REPLACES handover is unchanged: the new process waits for the old one before its single-instance lock. Tested with TestRelaunchArgs and TestRelaunchBinaryArgs (a script in magpie's place is started with gui settings <pid> / tray <pid>); go vet, go build (GUI and -tags nogui, GOOS=linux, GOOS=windows), go test -tags nogui ./... pass. Not tried: a real restart to update on Windows or Linux. 2026-09-30 12:33:36 +08:00
yetone 60e4591315 routing: a request's row shows the reasoning the agent asked for before the one it was sent at when they differ (xhigh → max), so a level the agent didn't pick reads as the agent's or as magpie's at a glance (呆滞 on X: pi 里面选择是 xhigh 但是magpie 里面显示的是max) — the row showed only the level sent, the asked one only in its title. Pi sends max itself for xhigh when its thinkingLevelMap for the model has xhigh null (a level unticked in magpie) — the row then reads "max"; magpie fitting xhigh to a model without it reads "xhigh → max". Tested with routing-effort.test.cjs in Chromium and WebKit, English and Chinese: asked/sent xhigh/max, max/max, xhigh/xhigh and none, each inside its row and unclipped, the title naming both. click-scroll, routing-kind, routing-steady, old-webkit pass; go vet, go build -tags nogui, go test -tags nogui ./internal/gui/... pass. 2026-09-30 12:26:31 +08:00
Zhaofeng 99a6c0d354 codex: a subscription switched off narrows nothing, its models kept whole (switching the codex provider off left the one picked ChatGPT model as the only one Codex listed) — the codex provider's picks narrow the ChatGPT account's own model list as well as magpie's added models (CodexNativePicked, aed12ac), and ownCodex narrows the picker's OpenAI half the same way. Neither looked at Off, so a provider switched off — which serves no agent anything, by Provider.Off's own promise — still narrowed the account's list to the picks it kept, and a single pick left Codex listing that one model alone. Both now treat a switched-off provider as picking nothing: the account's list is left whole, and the picks stay in providers.json, narrowing again as soon as it is switched on. Tested with a new TestCodexModelListNotNarrowedWhileOff in internal/gateway (a fake backend listing two models; switched off with picks=[gpt-6-sol] both come through, switched on just the pick; fails on main with [gpt-6-sol]) and a new TestCodexOwnModelsNotNarrowedWhileOff in internal/agent (Codex's own picker: both models while off, just the pick while on; fails on main with [gpt-b]). go build -tags nogui ./..., go vet ./internal/agent ./internal/provider ./internal/gateway and go test -tags nogui -count=1 ./... pass (#274) 2026-09-30 12:24:56 +08:00
minuque f21007b164 devin: devin auth status 失败或超时不再把账号当成未登录(供应商列表和路由里间歇消失,同 #154),查询预算从 10 秒放宽到 30 秒 (#221)
* devin: `devin auth status` 失败或超时不再把账号当成未登录(供应商列表和路由里间歇消失,同 #154),查询预算从 10 秒放宽到 30 秒

* devin: 新增的两个测试结束后恢复 DevinExecutable,并清掉服务的身份再 forget(失败的查询现在会保留它,#221 review)

* devin: 账号的 token 被吊销或过期后不再显示为已登录(CLI 退出 0、报告里是 Authentication required / Invalid token),断网那一种仍保留账号(#221 review)
2026-09-30 12:14:48 +08:00
Anx d99d130d63 gui: TestExplorerWithoutPath is built where explorer is: windows and not nogui — it had no build tag, so go test -tags nogui ./internal/gui, which the project runs, failed to compile on Windows: the test was in while folder_windows.go, which has explorer and fileExists, was left out by !nogui. (#272) 2026-09-30 12:14:41 +08:00
yetone ca63863698 routing: a request's row lays itself out by the width of the list it is in, so the reasoning ("· high") no longer lands on top of the numbers (#273 yangkx1024: "7.1 秒" and "high" drawn over each other) — the rows switched to their stacked layout by the width of the page's two-column area, which is 860px or more whenever there are two columns, so a list ~470px wide kept its rows to one line; the numbers took their width first, where it went got none, and the reasoning, which never shrinks, spilled over them. Each column is now a container: over 640px one line as before, below it where it went and the numbers share a line under the model, below 460px each has its own and the numbers may wrap; old WebKit without subgrid stacks from 700px. Tested with routing-effort-row.test.cjs in Chromium and WebKit, English and Chinese, at 1440, 1000 and 480px: no text run in a row over another or out of the row, "high" whole; 4 fail on the old CSS at 1000px. click-scroll, routing-*, old-webkit, panel-routing, mask-emails pass; go vet, builds, go test -tags nogui ./... pass. 2026-09-30 12:10:32 +08:00
yetone 26f3a68a7a dsh: DeepSeek Harness's thinking takes low as well — off, low, high, max, as llm-deepseek's reasoningEffort has since 0.1.1 (#269) — magpie offered off/high/max from an older dsh. Checked in dsh 0.1.1 to 0.2.0-rc.2 and master, nothing between; in a sandbox HOME dsh 0.2.0-rc.2 with reasoningEffort low sent output_config.effort low to a fake server. TestDshEffort takes low, still refuses medium; fails on the old list. 2026-09-30 11:50:05 +08:00
yetone 128156c134 cursor: a sign-in link cursor-agent wrapped over lines is joined whole before it is opened (#261: the page said "This sign-in link is incomplete or has expired" for https://cursor.com/loginDeepControl?, the link cut after its "?") — runCLISignIn took the first https:// run of a line, so a link broken where the terminal wrapped lost everything after the break. A Cursor link without challenge and uuid now takes the lines after it that are only URL characters; one that has them takes only more of its query read with it, not a word printed after it; a link never made whole is used as it was once the wait is over. Grok's link is handed on as printed. Not reproduced with cursor-agent 2025.08.08, 2026.09.23 or 2026.09.28, which print it on one line. Tested with TestCursorSignInJoinsAWrappedLink (a break after ?, over three lines, in and after the uuid, the rest 0.3s later, whole then "Waiting...", never whole; fails without the change) and TestCLISignInFirstLinkAsPrinted. 2026-09-30 11:50:05 +08:00
yetone ad164b6311 providers: a provider can be duplicated, the signed-in account paused, and a Balance URL names each key with {key} (#268, #263, #266)
- #268: the editor's Duplicate opens Add filled in from the provider ("X copy", a new id, its URLs, models, headers, balance and proxy); AddCopy takes the source's keys, routing, balance token and Zhipu team unless a new key is pasted, the same-key refusal skipped for it. Signed-in accounts can't be copied.
- #263: unticking the account Codex or Claude Code is signed in to pauses it while another account is on (logins.json `paused`): the gateway passes over it and the agent stays signed in, so its remote control keeps working. With no other account on it is used again; switching accounts clears it. The row reads Paused, dimmed; ticking resumes it.
- #266: {key}, {apiKey} or {api_key} in the Balance URL or a header is each key's own (URL-escaped in the URL), so every key shows its own balance; a network error naming the key shows it masked.

Tested with TestAddCopy, TestCodexPausedOwnAccountPassedOver, TestBalanceURLNamesTheKey (each fails without its change) and duplicate-pause.test.cjs in Chromium and WebKit, English and Chinese (6 of 8 fail on the old app.js); balance-fix, add-sheet, click-scroll pass. go vet, go build -tags nogui (darwin, GOOS=linux, GOOS=windows), go test -tags nogui ./... pass. Not tried with real accounts.
2026-09-30 11:49:37 +08:00
yetone 04f374fa8c 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. 2026-09-30 11:38:07 +08:00
yetone f464e70b61 workbuddy: a check-in that never reached WorkBuddy is tried again a minute later, then 2, 4, 8, 16 and 30, not after half an hour (#265 WhyWhatHow: the check-in went out the second Windows woke, before its network was back, "no such host", then 签到失败 for 30 minutes)
A failed check-in always waited wbCheckinRetry, and the loop only looked every 30 minutes. A failure whose request got no reply at all (a net.Error: no name lookup, no connection, no answer in time) is now kept as offline with a count of such tries that day, and waits a minute, doubling each time up to 30; one WorkBuddy answered (a 500, a refused token) still waits 30 minutes. The loop's decision is now wbCheckinLoop.tick, which also looks when a failure comes due. Records written before read as an ordinary failure. Tests: TestWorkBuddyCheckinOfflineRetriesSoon (network down at the first tick after waking, credits claimed by the same loop a minute later; with the network down throughout, tries at minutes 0, 1, 3, 7, 15, 31, 61, 91 — it fails with the 30-minute wait), TestWorkBuddyCheckinRetriesAfterFailure now with an HTTP 500 for the 30-minute case. go vet, go build -tags nogui (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui ./... pass. Not tried on a real sleeping Windows machine or account.
2026-09-30 11:30:11 +08:00
yetone 2122b5a8a2 gateway: a provider's floor on max_tokens said with >= or > is read, so a one-token probe to Command Code is asked again at 16 instead of failing with a 400 (#260 HuHong-Tao: "Expected a value >= 16, but got 1 instead." — Claude Desktop's gateway check reported the model unavailable)
tokenFloor read the error from the body magpie had re-encoded for the agent, where Go's JSON encoder had escaped > as >, so the >= and > wordings never matched and the request wasn't retried. The pattern now also takes the escaped sign (a vendor may send it that way too), a trailing = meaning the number is the floor itself; and writeJSON no longer escapes <, > and &, as withFields already didn't, so the agent sees the vendor's words as written — the same JSON either way. Tests: TestTokenFloor gains the escaped and > cases; TestTooFewTokensSaidWithSigns goes through the gateway on /v1/messages (streaming) and /v1/chat/completions with Command Code's wording (asked again at 16), "max_tokens must be > 2" (at 3) and a vendor that sends > itself. Both fail without the change (a 400 after one request). Not tried against Command Code itself.
2026-09-30 11:30:11 +08:00
yetone 4e8cda60ed web: the page in a browser has magpie's icon in its tab (Discord, ARNO: magpie的web页面没有icon)
index.html named no icon, so `magpie web` showed the browser's blank page icon. It now links icons/favicon.svg, magpie's bird drawn dark, or light when the system is dark, as a tab bar would show it. Checked by rendering it in Chromium in light and dark schemes; go vet, go build -tags nogui (darwin, GOOS=linux, GOOS=windows), go test -tags nogui ./internal/gui/... and mask-emails.test.cjs pass.
2026-09-30 11:26:32 +08:00
yetone 6927bedfcb windows: the title bar is drawn in the page's own colour, light or dark as magpie's theme is, and a narrow agent row gives up the update pill's words and the version before the agent's name (X, ChengYue: "Mac上是真好看,Windows上的UI简直不要太丑~")
On Windows the system's grey title bar sat over the page as a strip of its own, and stayed light over a page set to dark; at the window's first width Claude Code's name was cut to "C." beside a full "Update to …" pill. Now the main window opens with its title bar and background in the page's --bg for the saved theme (Wails' Windows CustomTheme, #f4f4f6 / #1a1a1e), and the page reports the colour it shows (POST /api/window/titlebar) at start and after every change of theme, system or chosen, for Go to paint the caption with DWM and switch its buttons dark or light; elsewhere nothing changes. The agent row is a size container: under 250px the pill shows only its arrow (its words kept as its aria-label), under 150px the version goes, and the name gives way last.

Tested on Windows 11 (sandboxed HOME): with theme light and dark, the bar and the page are one surface, caption buttons in the matching colour, the name whole. cli-update.test.cjs gains a zh subtest at 980/620/540px (fails on the old CSS). The GUI suite passes in Chromium (settings-groups is flaky on HEAD too), go vet, go build (GOOS=windows, GOOS=linux -tags nogui, dev) and go test -tags nogui ./... pass.
2026-09-30 11:22:21 +08:00
yetone fd1427de0b codex: with magpie as Codex's provider, openai_base_url points at magpie as well, so a thread started on Codex's built-in provider and reopened sends a magpie model to magpie, not to ChatGPT's backend (#259 isymbo: ChatGPT Desktop, "The 'xiaomi/mimo-v2.6-pro' model is not supported when using Codex with a ChatGPT account")
ChatGPT Desktop 26.924.22138 is the Codex app renamed (bundle com.openai.codex, codex-cli 0.158.0-alpha.2.1): it reads ~/.codex and honours openai_base_url, model_provider and chatgpt_base_url; the error text comes from chatgpt.com. Codex keeps a thread on the provider it was started on, so an old ChatGPT conversation reopened while magpie is Codex's provider (signed out, allowance used up, or the sign-in set to api) stays on the built-in provider, and magpie removed openai_base_url in that mode, so a magpie model picked in it went to chatgpt.com, which refused it. Provider mode now sets openai_base_url to magpie's gateway too (whose /backend-api/codex serves "provider/model" and passes Codex's own models on), unless the user has one of their own; picking one of Codex's own models or a reset still removes magpie's. Sync adds it to configs an older magpie wrote in provider mode, when the catalog file is magpie's. Verified by driving ChatGPT Desktop's bundled `codex app-server` in a sandbox HOME with a fake ChatGPT auth.json and fake servers: before, a thread started on the built-in provider and reopened with model_provider = "magpie" went to the built-in base URL; now it reaches magpie's /backend-api/codex/responses, and a new thread /v1/responses. Tests: codex_thread_test.go (TestCodexProviderAlsoSetsBaseURL signed out and api, TestCodexProviderKeepsOwnBaseURL, TestCodexSyncAddsBaseURLToProvider; the first and third fail without the change), codex_route_test.go updated for the base URL in provider mode. go vet, go build -tags nogui (and GOOS=linux, GOOS=windows) and go test -tags nogui ./... pass. Not tried in the real ChatGPT Desktop window or with a real account; a thread already open in a running app keeps the settings it was loaded with, and cloud threads never read the local config.
2026-09-30 11:16:59 +08:00
yetone 982c636018 settings: the Masking rules row shows its words in full and puts its fields on one line under them — a name, prefix or regex, the match taking the room left, Add — from the row's left edge to its right (TechVerser on X: 这个地方错位了吗?😢 — the name field floated mid-row, Prefix/Regex sat at the right, the match and Add wrapped to a line of their own and the description was cut off). The four fields were squeezed into the row's value cell, capped at 60% of the row and wrapped from the right, with the switch's push to the right still on it; the row now wraps, its words take the full width without an ellipsis and its fields the line below. Tested with redact-rule-row.test.cjs in Chromium and WebKit, English and Chinese, at 1100px and 640px: the words not cut, the four fields on one line in order with none on top of another, flush with the row's edges, the added rule's row as before, picking Regex moves nothing; it fails on the old CSS. go vet, go build -tags nogui (darwin, GOOS=linux, GOOS=windows), go test -tags nogui ./..., click-scroll and old-webkit pass. 2026-09-30 11:11:02 +08:00
yetone 4a3e236a7b gui: a second magpie on the same config hands over to the one running whatever its path, so Omarchy's tray no longer shows two magpies (songlairui on X: 为什么我的 omarchy 上会有两只喜鹊,一起来,一起退) — the single-instance id off the Mac covered the executable's path as well as the config dir, so a copy started from another path (autostarted from where it was first run, opened later from where it was put, or a package beside a download) ran as a second app with its own tray item beside the first, both on the gateway's one port. Omarchy's tray, Quickshell's watcher and host each drop a duplicate registration, so two icons can only come from two processes. The id now covers the config dir alone; a sandboxed HOME or config dir still runs on its own. Tested in the Omarchy VM (Hyprland 0.56): v0.1.428 at ~/.local/bin and a copy at /tmp ran side by side, RegisteredStatusNotifierItems listing two; this build at ~/ and a copy at /tmp: the copy's panel opened the running one's panel and exited, one item registered. go vet, go build -tags nogui (GOOS=linux, GOOS=windows) and go test -tags nogui ./internal/gui/... pass. 2026-09-30 11:03:08 +08:00
yetone 23365f2216 library: a long link or description stays inside its row, an icon whose picture failed is drawn again, and what the Library couldn't write is listed on the page (#257 long links break an item; Discord, neiko: Claude's icon missing until a refresh; JasonLeeForOnly: the toast says "see Library" and the Library shows nothing)
#257: in a skill's source line the "linked from" / 链接自 label could shrink, and Chinese breaks after any character, so a long path squeezed it into a column of single characters; the line grew tall, and skill rows in a long list have a fixed height with overflow hidden, so the name was clipped and the path cut. The label now stays whole on one line and the path gives way with an ellipsis; long names and descriptions break anywhere rather than widening the row. Icons: each agent's icon was made once and copied onto every row, so if the first copy's picture failed to load every copy stayed empty until a reload. An icon whose picture fails is dropped from that cache, a drawn one asks for its picture again (up to 3 times, growing delays), and the Library's icons are built apart from the Providers page's recycled ones. Problems: the Library only showed a problem as an amber dot on a chip, with nowhere for an agent's instructions, its whole MCP file, a removed server, a project, or an agent hidden from the page, and the failures of applying a profile were never shown although the toast pointed there. GET /api/library now returns every standing problem, and a card above each tab's content lists them (amber dot, no stripe): which agent or project, what failed, the error, with Try again re-running all/sync; it updates in place when a chip is toggled. en and zh. Tested with a new library-items.test.cjs, Chromium and WebKit, en and zh (18/18): long names, links and descriptions inside their rows with the label on one line in light and dark at 980 and 560px, the card's lines and errors, Try again posting all/sync without moving the page, a chip click updating the card with the row in place, Claude Code's icon appearing after its picture fails for the first 300ms; all fail against the old library.js/css. click-scroll, old-webkit and shared-skills pass. go vet, go build -tags nogui (and GOOS=linux, GOOS=windows) and go test -tags nogui ./... pass (TestClaudeSignedOut timed out under load and passes alone). Not tried in the real Wails window; the icon's first failure is simulated, its real cause not reproduced.
2026-09-30 11:01:17 +08:00
yetone 224304ab08 codex: third-party and group models get Codex's tool search, so the first request no longer carries every MCP and ChatGPT-app tool's schema (#258: 190K tokens before the first word on a magpie model, ~26K on Codex's own)
Codex sends every MCP tool's full schema inline in `tools` unless the model's catalog entry says supports_search_tool (core/src/tools/spec_plan.rs search_tool_enabled; mcp_tool_exposure.rs Deferred vs Direct); its own gpt-5.5/5.6 entries say so, magpie's generic entries didn't, so with a ChatGPT sign-in's apps a turn began at ~190K tokens. The generic entries now set supports_search_tool: true; base_instructions, the context window and the rest are as they were, and code mode and Responses Lite stay off. The gateway serves the search to models that don't know it: for a model it translates for (Chat, Anthropic…), the client-run tool_search is offered as a function, earlier tool_search_call/tool_search_output become a call and its result ("These tools are now available to call: …"), the tools found are offered after those sent (once, namespaces merged), and a call to it goes back to Codex as the tool_search_call Codex runs, streamed or not. A request relayed to another Responses API (not the ChatGPT backend, which runs it as sent) is rewritten the same way, the found tools' defer_loading dropped, and its stream line by line (toolsearch.go). Measured with codex exec in a sandbox HOME against a fake Responses backend and a fake MCP server with 150 tools: the first request's body went from 307,198 to 23,580 bytes. Tested with toolsearch_test.go (translated, streamed and rendered, end to end through a Chat fake and a relayed Responses fake, merging, the stream split mid-line) and TestCodexCatalogToolSearch (search on, prompt, context window and the other fields unchanged). go vet, go build -tags nogui (and GOOS=linux, GOOS=windows) and go test -tags nogui ./... pass. Not tried with real Gemini/antigravity calling the search, real ChatGPT apps, or real third-party Responses providers; a non-streamed relayed reply isn't rewritten (Codex always streams).
2026-09-30 10:49:28 +08:00
yetone 446b39ae44 factory: an account with no organization asks Factory's whoami for it before it sends, as droid does, and every new token asks again, so its model requests carry X-Factory-Org-Id (#242 tasselx: "Factory: Forbidden" on GLM-5.2)
droid (0.229.0, still the latest) asks /api/cli/whoami about each token it holds and sends the orgId it answers as X-Factory-Org-Id on every request (its auth's Do → _r → Pr/Ar, Ci() spread into xc). Accounts signed in with v0.1.432, or whose org had been dropped after a refusal, kept none, and magpie never asked whoami again after renewing a token, so their requests went with no org and Factory answered its generic 403 {detail:"Forbidden"}. Now an account with no org asks whoami before its request (at most every 10 minutes per account), each renewal asks again, an org whoami refuses is left off and whoami asked without it, and the org, region and whoami's premBaseHostV2 (an org's own host, where droid sends model requests) are kept. whoami goes with droid's three headers only (Authorization, X-Factory-Whoami-Extended, the org). Claude requests carry X-Api-Key: placeholder, as droid's Anthropic client does. A 403 on an account that sent no org asks whoami and retries once; one that stays tells the user to check droid on the same account and model and to sign in again (Provider.Explain, in the gateway's three error paths). Tested with TestFactoryForbiddenWithoutOrg against a fake Factory refusing requests without the org header, the account saved as v0.1.432 did: glm-5.2 succeeds with the exact headers, whoami asked once with droid's headers, a 403 while whoami was down asks again and retries, the enterprise host used, the explanation on 403s only; it fails with the lookup off. The existing Factory tests are updated for the headers and org. go vet, go build -tags nogui (and GOOS=linux, GOOS=windows) and go test -tags nogui ./... pass. Not tried against Factory's real server or tasselx's account.
2026-09-30 10:46:55 +08:00
yetone 5b6ce747f2 gateway: a group whose later members are all out of allowance retries a passing failure on the member that may still answer, and tells the agent that failure rather than the allowance's (Discord, waroy: Codex run out, Grok busy a moment — Codex was told usage exhausted and stopped taking input)
In a routing group whose first member (a Codex account) had run out of its allowance, a turn went to the next member; when that one failed passingly (busy, a 5xx, a timeout) and the members after it were also spent, the turn ended on the last member's quota error, and Codex, seeing "usage limit reached", stopped taking input until restarted. Now serve() keeps the first failure that is not about allowance, and when every candidate after the current one rests on quota or credit (spentAfter), a passing failure is asked again on the one that may answer, with the same waits as the last candidate gets. If the turn still fails and its last failure was a spent allowance while an earlier member failed otherwise, the agent is told that other failure (its status and error), so it retries instead of thinking the account is out. Tested with codex_quota_group_test.go: a spent Codex moves to the next group member, to Grok with sealed reasoning kept out, Grok busy once is asked again and answers, and Grok failing throughout is told as Grok's error, not Codex's quota; the last two fail before the change. go vet, go build -tags nogui (and GOOS=linux, GOOS=windows) and go test -tags nogui ./... pass (TestUserPath timed out under load and passes alone). Not tried with waroy's real accounts or the real Codex app.
2026-09-30 10:44:18 +08:00
yetone 3fe2ff9958 rules: a routing group's rule can be for the agent compacting its conversation, so a cheaper, faster model writes the summary while the conversation stays on its own (X, MR.Guo: 希望 Magpie 能像 OpenCode 一样支持单独指定 context compression model:当 Agent 需要做上下文压缩时…指定一个更便宜、更快的小模型专门负责总结和压缩历史上下文) — a compaction went to whatever model the conversation was on, the priciest in the group as often as not, and a long session paid for each summary at that price. A rule's new condition, compact (provider.Rule.Compact, "compacting"; magpie group rule add <group> use=<model> compact, compact=yes|no; the GUI editor's 压缩上下文时 / compacting toggle; the TUI hint), matches a request that is an agent compacting, told from the agent's own prompt (internal/gateway/compacting.go, read from each one's release): the system prompt of Claude Code 2.1's /compact and auto-compact, OpenCode's compaction agent, Pi, Gemini CLI's state_snapshot and Qwen Code, or the last user message of Claude Code's, Codex's (and magpie's own for a Codex compaction_trigger) and Kimi Code's; a prompt only quoted earlier in a conversation doesn't count. Only in a group with such a rule is a request looked at this way, so nothing else changes. A compaction is decided on its own, never held by the turn's rule, its classifier never asked; it isn't remembered for the turn, and neither the conversation's affinity nor the turn's size is taken from its answer, so the requests after it go on on the model the turn was on and its cache (without that, the next request of a Claude Code turn followed the summary to the cheap model). A member whose context is less than the compaction is passed over for the next rule that matches, or the group's order (RuleHit.Small). The trace tells it ("The agent is compacting the conversation, so rule 1 sends the summary to …", the models passed over named), and the editor notes a compact rule's model taking less than another member. Docs: start#rules (en, zh). Tested: internal/gateway/compacting_test.go — each agent's prompt taken, a plain and a quoted one not; in a group with rules compact→a/small and tokens≥2000→b/big, a long Claude Code turn on b, its /compact to a (Compact, N=1), the turn's next request back on b held, Codex's compaction to a too; a 70k-token compaction skipping a (64k) for rule 2 with Small [a/small]; a group with no compact rule routing a compaction as before; ParseRule/Line/Conditions/Matches for compact. TestRuleCompact failed with the affinity kept from the summary. internal/gui/tests/routing-compact.test.cjs, Chromium and WebKit, en and zh: the compaction in the trace, the rule's words, the toggle on and off with nothing moved, the room note, a compacting-only rule saved as compact:true and not refused. routing-kind/served/signin/steady, click-scroll and old-webkit pass. go vet, go build -tags nogui (and GOOS=linux, GOOS=windows) and go test -tags nogui ./... pass. Not tried against real agents' compactions through the gateway (the prompts are matched as read from their releases) nor in the real Wails window. 2026-09-30 10:24:18 +08:00
yetone ffa55fc277 copilot: Auto — Copilot's own pick, the only choice a Student plan has — is listed for every Copilot account and works through magpie, as Copilot's clients do it (#256; Discord, CakeJskSUMM: Copilot Student can only use Auto) — Copilot's /models doesn't list Auto; its clients add it themselves (Copilot CLI 1.0.79: {id:"auto",name:"Auto"}, AUTO_MODEL_ID) and ask POST {api}/models/session with {"auto_mode":{"model_hints":["auto"]}} for a session_token, the selected_model and expires_at, each request then naming that model with the token in Copilot-Session-Token. magpie listed no Auto, failed an account whose list left nothing to pick by hand ("Copilot lists no chat model for this account"), and sent "auto" as a model. Now copilot_auto.go keeps the session per GitHub account, asked again two minutes before it expires; the gateway resolves Auto before choosing the API, so a picked model served only on /responses goes there; a body still naming "auto" (the model test) is rewritten; the endpoints of every model Copilot lists, hidden ones too, are kept for the picked one. A reply to "auto" from another model isn't counted as swapped (Cursor's auto too); the Copilot CLI's picker, which offers auto itself, leaves out the listed one. Tested with fakes in a sandbox HOME: provider/copilot_auto_test.go (a Student-like account whose models are all disabled lists [auto], the session body sent as above, requests carry the picked gpt-5-mini and the token, one session reused, renewed near expiry, an object-shaped selected_model, a refusal reported) and gateway/copilot_auto_test.go (a chat request for copilot/auto goes to /responses as gpt-5-mini with the token, two turns one session; fails without the gateway change); served_test gains the auto cases. go vet ./internal/..., go build -tags nogui (and GOOS=linux, GOOS=windows) and go test -tags nogui ./... pass. Not tried with a real Student account: what its /models returns, whether the plain session request suffices (Auto v2's /models/session/intent is not done), and the body is from VS Code's Copilot Chat, not captured traffic. 2026-09-30 10:17:15 +08:00