Commit Graph

  • f2c7536d7d gateway: a relayed WorkBuddy reply is one block in Qoder again, not a word to a line (ARNO on Discord: workbuddy provider断句出错) — copilot.tencent.com sends "tool_calls": [] on every delta, text too, and Qoder's stream adapter (qodercli 1.1.65) tests delta.tool_calls for being there rather than for holding a call, so it closed its text block on each chunk and opened a new one for the next piece of text. chatTidy, which already mends that stream's empty reasoning_content, finish_reason and tool name, now leaves an empty tool_calls list out; a real call's list goes on as it came. Translated to Messages or Responses the stream was already one text block (the decoder reads an empty list as no call). TestChatRelayDropsEmptyToolCalls fails without the change; TestChatTidyKeepsToolCalls, TestWorkBuddyTextIsOneBlock guard the rest. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. yetone 2026-10-01 18:01:19 +08:00
  • c3197ac217 plugins: magpie keeps the Bun plugins run on up to date by itself (the user: 我们能否自动去管理一个bun的版本?去自动的下载并维护这个bun的版本到最新版) — plugins ran on the one Bun magpie pinned, or on whatever $MAGPIE_BUN pointed at. Every six hours (first two minutes after start), while there are plugins, magpie now asks GitHub for Bun's newest release and takes it once it has been out two days: downloaded, its SHA-256 checked against Bun's SHASUMS, tried (it says its version, and runs a script with AsyncLocalStorage, fetch and fs), then switched to, the host restarted gracefully so a streaming reply finishes on the old one. BunVersion is the floor; the Bun before stays on disk, and a new one whose try fails, or that the host won't start on, is set aside for the one before and never taken again (bun/state.json). $MAGPIE_BUN is left alone. Plugins' page and magpie plugins show the version in use. TestCheckBunTakesASettledRelease, TestCheckBunWaitsAndKeepsTheFloor, TestCheckBunSetsAsideOneThatFailsItsTry, TestTryBunOnRealBun and TestHostFallsBackWhenTheNewBunDies pass on macOS, Linux and Windows; a real run from 1.3.14 to GitHub's 1.4.2 started the host on all three. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestFetchNewLeavesListedPlugins failed once under load, passes alone). yetone 2026-10-01 19:33:04 +08:00
  • 289de06b1f providers: the way to more subscriptions in Plugins is in the add sheet alone (the user: 这个不应该在这个页面,应该在第二个页面的这里) — the providers list's "More subscriptions in Plugins" button beside Add provider is gone; the add sheet's Subscriptions ends with its "More in Plugins" row, as before. plugin-market passes (Chromium, WebKit, en/zh); click-scroll, old-webkit, providers-off-fold pass. yetone 2026-10-01 19:07:14 +08:00
  • 4f9dcb39e5 plugins: a plugin subscription goes through every path a built-in one does (the user: 要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样) — a SOCKS5 proxy on a plugin provider or account now carries its requests: Bun speaks http(s) proxies only, so magpie stands a loopback http proxy in front of the SOCKS one, and a proxy that refuses says so as Go's transport does. A plugin's model list and its loader are asked through the account's own proxy, as a built-in's list is fetched through it. The Grok plugin draws and makes videos at Grok's Imagine API through the plugin, and keeps Codex's namespaced tools, as the built-in does; an official plugin beside its built-in (kiro-plugin, zcode-plugin) counts tokens by estimate as the built-in and the moved plugin do. FetchNew no longer re-lists plugin providers every ten minutes, their list being told by the plugin host; a plugin account's usage is cached under one name whichever way it is asked for; and a fetch hook that throws on its sign-in (a refused refresh) marks the account lapsed, as a built-in's refused refresh does. TestPluginSOCKSProxy, TestPluginModelListThroughAccountProxy, TestGrokPluginDraws, TestPluginBesideBuiltinCountsByEstimate, TestFetchNewLeavesListedPlugins, TestPluginUsageReadOnceWhicheverName and TestPluginFetchSignInExpiredLapses fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (the known load flakes pass alone); internal/plugin, netproxy and the plugin/proxy tests of provider and gateway pass on Linux and Windows. yetone 2026-10-01 19:07:12 +08:00
  • c6be065724 providers: Refresh and Test try the key the editor has, before a Save; a new key's box focuses the key, its name optional (the user: a key pasted over the saved one only errored on Refresh and Test, and Save closes the editor) yetone 2026-10-01 18:56:58 +08:00
  • fc248e381f plugins: the folder a plugin lives in is picked, not typed, and its page reads that folder's README (#411) volcanic 2026-10-01 18:46:56 +08:00
  • 037a79b964 fix(provider): preserve unreadable configuration on edits (#415) TryWorld 2026-10-01 18:33:45 +08:00
  • d9870d7501 claude: an account's usage is read by itself every 5 to 15 minutes, at random, and only once Claude Code was used since (the user: 调整一下吧, after 'run often, day and night, still doesn't look like a person') — /usage ran every 3 to 10 minutes whether anyone used Claude Code or not, so magpie left open overnight ran it some 200 times. The wait until the next unasked run is now drawn from 5 to 15 whole minutes, and once it is up /usage runs only if one of Claude Code's sessions (a .jsonl under its projects/) was written to since the last run, or Claude Code told magpie its limits through the bridge; an allowance nobody used hasn't moved, and a window that resets meanwhile still shows as reset. Opening Usage or pressing its Refresh still reads at once (no more than once in 30s). TestClaudeUsageIdle and TestClaudeUsedSince fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; claude-usage-asked, tray-usages pass. yetone 2026-10-01 18:02:20 +08:00
  • e001bd883a usage: a model spelled with a dotted version is priced at its maker's, and a model can be priced from any provider (tony on Discord: old sessions' copilot/claude models had no known price) — Copilot and its CLI's sessions name Anthropic's models claude-opus-4.6 and claude-haiku-4.5, models.dev's anthropic entry claude-opus-4-6 and claude-haiku-4-5, and a session's bare model was looked up only by its exact key at its maker, so neither was priced; a gateway call whose provider is gone or lists no price fell to the same miss. A price is now looked for with a dot and a dash taken for the same, after every exact match. gemini-3-pro-preview stays unpriced: Google's entry on models.dev no longer lists it and no price is made up. For it, and any model like it, magpie model price <model> <in>,<out>,<cache read>,<cache write> keeps a price under */<model> that counts from any provider not priced by the user, after <provider>/<model> and <provider>/* and before any list price, and for a session's bare model; --reset takes it away, magpie model prices lists it. TestDottedIDPricedAtItsMakers, TestAnyProviderPrice and TestPriceOfDottedAndAnyProvider fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. yetone 2026-10-01 17:42:11 +08:00
  • 1a9c4ca6b7 gateway: a tool search call magpie hands Codex has a tsc_ id, and one already in a session goes to OpenAI as one (Lullaby on Discord: Invalid 'input[98].id': 'fc_18da0e90a0fc07ee0000005f'. Expected an ID that begins with 'tsc', and auto-compaction failed) — a vendor's tool search, translated to Codex's tool_search_call, was given an fc_ id, as a function call is; Codex keeps it in its history and sends it back every turn, and once the conversation went to one of Codex's own models, a turn or /responses/compact, OpenAI turned the whole request away. Search calls now get tsc_ ids in the stream and the rendered reply, and a tool_search_call whose id isn't a tsc_ one is renamed (fc_X becomes tsc_X) before Codex's own /responses and /responses/compact, the rest of the body byte for byte, so a session needs no restart. Prompts and headers are unchanged. TestToolSearchCallHasTscID and TestCodexOwnModelSearchCallIDs fail without the change; TestSearchCallIDsUntouched keeps a clean request byte for byte. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. yetone 2026-10-01 17:42:01 +08:00
  • 08be4a2907 claude: an account's usage is read by itself every 3 to 10 minutes, at random, rather than every ten (the user: 每3~10分钟选一个随机的分钟去跑) — Claude Code's /usage ran again once the last reading was exactly ten minutes old, so magpie ran it on a clock. Each run now draws the wait until the next one that nobody asked for, a whole number of minutes from 3 to 10; opening Usage or pressing its Refresh still reads at once (no more than once in 30s), and magpie still never asks Anthropic. TestClaudeWaitRandom fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; claude-usage-asked, tray-usages pass. yetone 2026-10-01 17:41:43 +08:00
  • c0f3fa012a usage: combine gateway and local session requests with model charts and account labels (#239) 2026-10-01 17:42:10 +08:00
  • 967cf33b67 providers: a plugin's provider has no website link in its editor's head (Lemon on Discord: 请问这个链接是什么字段, the CodeArts plugin's editor said "codearts ↗") — a provider with no website was given https://<host>, and a plugin's host is its id from plugin://<id>, so the head showed the id as a link that opened https://codearts. Only a host with a dot or a port makes that link now; a preset's website and a local server's address are kept. nokey-editor.test.cjs fails without the change. nokey-editor, account-return, click-scroll, old-webkit pass. yetone 2026-10-01 17:22:56 +08:00
  • 74cb5dc1ae accounts: the account made first stays first — magpie signs Codex or Claude Code back in to it once it has room again (#408: 智能路由会自动把所使用账号设为首选) — since #208/#209 magpie signs the agent in to the next ticked account once the one it is on is 98% used, and left it there for good, so the account the user made first lost its place after its first spent window. magpie now keeps the account it moved off (login-returns.json beside logins.json) and signs the agent back in to it once that is below 90% (Smart's low share, so it doesn't flap) and still ticked; a switch the user makes meanwhile, in magpie or the agent, ends that. The Accounts list says it: the stand-in reads "First for now" (暂为首选), its title naming the one it goes back to, and that one "First again once it has room" (额度恢复后切回首选); the Routing note says magpie moves back. TestCodexSwitchedBackWhenItHasRoom and account-return.test.cjs fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; account-return, click-scroll, old-webkit, routing-signin pass. yetone 2026-10-01 17:09:21 +08:00
  • 1474b78f19 gateway: GET /v1/magpie/route tells a session's route as it happens (#405: a status bar wants the routed model and effort before the first token) — routing decides a group's member, and its decision model the effort, before the vendor is asked, but an agent's UI only learned it from the reply. A route now remembers the session its request named (X-Magpie-Session, or the agent's own session header; kept out of the trace's JSON and history), and GET /v1/magpie/route?session=<id> answers that session's latest route: the model asked, the group, the rule matched (with the effort picked), the member being tried and the effort it was sent at, every try with why a fallen-over one failed, then done/status/served. after=<seq>&wait=<s> (up to 60) waits for it to change. Only the named session is told; like /v1/magpie/quotas it answers this machine, and another only with the shared gateway's key. TestSessionRoute fails without the change (404). go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestUserPath and two Claude account tests flaked under load and pass alone). yetone 2026-10-01 17:05:59 +08:00
  • 57f1d53fd3 gateway: a Codex model of OpenAI's that fails with nothing said says where it went (#409: Codex showed "unexpected status 502 Bad Gateway: Unknown error" with DeepSeek picked in magpie) — the thread was still on Codex's own gpt-5.6-terra ("5.6 Terra" in the composer), which goes to OpenAI with Codex's sign-in, and OpenAI answered 502 with an empty body that magpie passed through, which Codex shows as "Unknown error". An error status with a blank body on /responses is now answered with that status and a message: OpenAI answered it and said nothing more, the model is one of Codex's own that goes to OpenAI rather than a provider in magpie, pick one of magpie's models; the request log and the trace say the same. An error with a body still passes unchanged. TestCodexOwnModelEmptyError fails without the change; TestCodexOwnModelErrorBodyKept and TestCodexMagpieModelEmptyError guard the rest. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build pass; gateway TestCodex* pass. yetone 2026-10-01 17:00:11 +08:00
  • 6b361b4698 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. yetone 2026-10-01 16:58:36 +08:00
  • 4af01cd742 providers: Command Code's free models are marked free, and providers switched off fold below the ones on (01huadalang on Discord: Space Bunny Alpha 在 Command Code 里是 FREE,magpie 只给 Ling 3.1 Flash 标了免费;已关闭的供应商还占着位置) — magpie marked a model free when the plan said so (m.free) or its id or name said "free"; Command Code's /provider/v1/models says nothing of it, so only ids like inclusionai/ling-3.1-flash:free got 免费. Its CLI (1.73.2) badges eleven models free in its own table (badge:"free"), Space Bunny Alpha and Pixel Canary among them; those are now marked Free on the Go table, on Go's fetched list and on a keyed plan's, so the chips say 免费 with "doesn't use the plan's credits". Neither the CLI nor the API says a discount (no "-99%" anywhere), so none is shown. On the Providers page the providers switched off now sit below the list under a "Turned off (N)" fold (已关闭的供应商), folded at first and remembered per viewer; a click on it moves nothing, and a provider switched off moves under it. TestCommandCodeFreeModels and providers-off-fold.test.cjs fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestClaudeRunEndsWhenTheClientLeaves a load flake, passes alone); providers-off-fold, click-scroll, old-webkit, provider-search, provider-remove, add-sheet, nokey-editor, plain-names, model-test-one pass. yetone 2026-10-01 16:43:20 +08:00
  • 10b940233c routing: a group's member can be sent in its vendor's fast mode, beside its own reasoning (vs on Discord: a member "gpt-6.1 sol · high · fast") — a member's effort was already its own (#189, "provider/model:effort") but there was no way to ask for speed, so a group could not hold "gpt-6.1-sol at high, priority" next to slower members. A group now lists in fast the members it sends fast, as Members spells them, kept beside Members rather than in their ids so an older version still routes to them, only not fast (checked: HEAD's build on the same providers.json sends the same members at their effort, no tier, and leaves the file as it was). When the gateway sends to such a member it asks in the upstream's own words, only where the model has a fast mode magpie can ask for (provider.CanFast): service_tier "priority" for a ChatGPT account's GPT models and an OpenAI key's GPT/o-series models on its Responses API, Cursor's -fast variant, and speed "fast" with the fast-mode-2026-02-01 beta for Claude Opus 4.8/5/5.5 on an Anthropic key (api.anthropic.com); a relay, Bedrock, a Claude subscription, a model without one, or a provider since moved off those hosts is sent untouched. It is applied in the agent's own API and carried through translation (parseAnthropic now reads speed), so Chat, Responses and Anthropic agents all get it. A translated request for OpenAI's Chat API is still told no tier (TestCursorPluginFast), hence the Responses requirement. The trace marks the try, the order and the group (fast), shown as "fast" beside the reasoning in Requests and the order. Typed, a member is provider/model[:effort]:fast: magpie group add/set models=… takes it, fast=<m1>,… sets them, models-= takes either spelling, and the group lists and shows members as typed with "· fast"; a model with no fast mode, or a group in the group, is refused. The GUI group editor shows a "Standard speed"/"Fast" toggle (标准速度/快速) beside a member's reasoning only for a model that has one, the card reads "· fast" after the reasoning, and fast follows a member's reasoning change and leaves with a removed member; the TUI's group view takes E to step a member's effort and F to toggle fast. Provider renames carry it. TestFastMemberOpenAI, TestFastMemberAnthropic, TestWithFastWire (gateway, per wire format, a fake vendor recording body and anthropic-beta), TestCanFast, TestSaveGroupFast, TestGroupMembersFast, TestGroupMemberEffortAndFast, TestGroupSaveFast and group-member-fast.test.cjs fail without the change. A sandbox magpie (own HOME, gateway on 3966) with providers at http://api.openai.com and http://api.anthropic.com through a local recording proxy sent priority+high to OpenAI and speed fast + the beta to Anthropic from Chat, Responses and Anthropic clients, and nothing extra for Sonnet or a non-fast member. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; group-member-fast, click-scroll, old-webkit, routing-levels, routing-manual, routing-effort, settings-groups pass (Chromium, WebKit, en/zh). yetone 2026-10-01 16:42:15 +08:00
  • 258d04f569 gateway: a request archive, off unless turned on, keeps each call's headers and bodies in the user's own S3 bucket and a recent call reads its own back (Jorben on Discord: 请求存档) — the Gateway page's recent calls only ever held the last bodies in memory, the first 256 KB of each, with no headers, gone at a restart. A switch over Recent calls ("Request archive", POST /api/settings/archive, settings' requestArchive) turns it on, refused with a reason while sync isn't set up with an s3:// bucket; with it on, each call the gateway serves gets an id (UTC HHMMSS-<16 hex>, said back in X-Magpie-Archive-Id) and, once it is recorded, its method, path and query, request headers and body and response status, headers and body go as one JSON file to <prefix>/magpie/archive/<YYYY-MM-DD>/<id>.json in the bucket sync keeps its backup in, signed with the same client (davsync.Bucket, no new SDK). Before anything leaves, Authorization, x-api-key, Cookie, Set-Cookie, any header or JSON field or query parameter named for a key, token, secret or password, and anything that looks like a key or token anywhere in the text (the redaction rules' patterns, in a stream's data lines too) become [REDACTED] — gone, no placeholder kept; the archive is plain JSON, readable in the bucket, not sealed with the passphrase. Uploads go one at a time on their own goroutine from a queue of 16 calls, each body capped at the 256 KB the call already keeps and marked truncated; a full queue drops the call with a log line, a failed upload is logged and shown under the switch until the next one goes, and no request waits on any of it. A recent call the archive kept shows its date and id under its bodies, and "Fetch from archive" (GET /api/archive?date=&id=, only a date and an id of that shape) shows the request's and response's headers and bodies as the bucket has them, or says it isn't there yet. The hook is in record() and the Call literal, so PR #239's request log lines are not touched; the archived call carries its usage, and with #239 its vendor request id. Only calls through serve() are archived, not Codex's backend, image or video calls. TestArchiveOn, TestArchiveOff, TestArchiveName, TestRequestArchiveToS3 (a real gateway request through the signature-checking fake S3), TestScrub and request-archive.test.cjs fail without the change; TestSettingsSaveKeepsWhatItDoesNotSend now sets requestArchive. A sandbox magpie web with a fake vendor and a fake S3 server uploaded nothing while off; on, it uploaded a signed archive with none of eight planted secrets in it, read it back through /api/archive and in the page, and with the S3 server down served the call in 0.1 s and said why the upload failed. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (Claude account tests and TestClaudeRunEndsWhenTheClientLeaves pass alone); request-archive, click-scroll, old-webkit, sse-body, gateway-fold pass. yetone 2026-10-01 16:37:50 +08:00
  • 8cd520662f plugin: a plugin can say the list it hands back is a fallback, and magpie keeps the one told last, as a built-in does — Command Code's Go accounts and ZCode give a table of their own when their vendor's list can't be had, not the defaults they were given, so magpie couldn't tell it from a list read and a hiccup swapped the models for the table. A models hook's list marked Symbol.for("magpie.fellBack") (OpenCode never sees a symbol's key) now counts as fallen back; commandcode 0.1.7 and zcode 0.1.6 mark theirs. TestFallenBackListKeepsTheLastOne/throws=own fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./internal/plugin ./internal/provider pass; internal/plugin passes on Windows and Linux. yetone 2026-10-01 17:06:17 +08:00
  • ca29a7badd plugin: a plugin whose models hook throws keeps the list it told last, as a built-in does — 9abf441d kept the last list for a hook that gave its defaults back, but Cursor's, Grok's and Devin's hooks throw when their vendor fails, and an account past the first whose hook threw was taken to have all the provider's models; a vendor's hiccup still swapped the list for the plugin's defaults. A hook that throws now marks its listing fallen back, the provider's and each account's, and magpie keeps the list told last in its place. TestFallenBackListKeepsTheLastOne/throws=1 fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; internal/plugin passes on Windows, internal/plugin and internal/provider on Linux. yetone 2026-10-01 16:43:52 +08:00
  • e698e7dddd claude: adding a Claude account is Claude Code's own claude auth login, and magpie never refreshes a Claude sign-in nor asks Anthropic anything with one (the user: 把 magpie 自己的 Claude 登录改成直接用 claude /login,只要体验上没什么差别就改) — magpie traded the sign-in page's code for tokens with Claude Code's OAuth client, asked Anthropic's profile and model list with them, refreshed saved accounts itself (daily keepalive included) and handed a saved account to Claude Code as CLAUDE_CODE_OAUTH_TOKEN. Now Add account runs claude auth login --claudeai in a config directory of magpie's own: the page Claude Code would open comes to magpie's window (Claude Code is given magpie as its BROWSER, which writes the page down and exits), the browser comes back to Claude Code's own listener, and when the browser can't reach this machine the address it ended on, or the code Claude's page shows, is pasted as before and handed to Claude Code. Who signed in is what Claude Code kept (.claude.json, auth status). A saved account in use beside the signed-in one runs Claude Code with CLAUDE_CONFIG_DIR set to its own directory (claude-accounts/ beside magpie's config), its sign-in put there once from logins.json and read back as Claude Code refreshes it; switching to it moves it into Claude Code's own store and removes the directory, as forgetting it does. Claude's models are the catalog's (models.dev), not asked of Anthropic; an imported Claude login is kept as the file has it, and Claude Code refreshes it on first use. Claude Code is required to add a Claude account. TestClaudeSignInByPaste, TestClaudeSignInByPastedAddress, TestClaudeSavedAccountDir (a fake claude CLI, and a server standing in for Anthropic that fails the test if asked) and the reworked sign-in, import and gateway tests fail without the change. With the real Claude Code 2.1.286 in a sandbox directory, its sign-in page (localhost callback) reached magpie's binary and the sign-in waited for the browser; nothing was left behind once it was canceled. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; claude-risk, plugin-signin, signin-callback, signin-link, signin-paste-codex, routing-signin, own-signin-remove, click-scroll, old-webkit pass. yetone 2026-10-01 16:27:19 +08:00
  • b7037c101e plugin: plugins keep up to date as magpie does (the user: 插件的升级是怎么做的?我们有插件的升级提醒吗?) — a plugin updated only when the reader opened Plugins and clicked Update, save the built-ins' moved plugins held at their least version, so most users stayed on the version they first installed, and updating cut any reply streaming through a plugin. Now the magpie serving the gateway asks npm 30s after it starts and every 6 hours: the community's plugins (@magpie-community/*) update by themselves, and their row says Auto-updated (from which version, when) for three days; someone else's, or one pinned to a version, waits for the reader with a dot on Plugins naming it, gone once it's updated. A host being replaced finishes the calls it is serving (up to 10 minutes) while new ones go to the new host. TestCheckUpdates, TestPinned, TestRestartFinishesTheReply (fails without the change: the plugin host quit: signal: killed) and plugin-updates.test.cjs (Chromium, WebKit, en/zh). go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; internal/plugin passes on Windows, internal/plugin and internal/provider on Linux; plugin-market, plugin-signin, plugin-move, plugin-release, click-scroll, old-webkit pass. yetone 2026-10-01 15:26:43 +08:00
  • 0a43210276 usage: the request filter says it is one (#361 review) — the filter a routing story's "View usage" sets read only "10/1/2026, 4:05:31 PM · group/fo ×", with nothing saying the rows were narrowed to one request; it now reads "Request: 10/1/2026, 4:05:31 PM · group/fo" (请求:…). usage-ledger.test.cjs checks the prefix in en and zh and passes 22/22 (Chromium, WebKit). yetone 2026-10-01 16:21:58 +08:00
  • ead06b1ccf gui: a model kept for routing groups is said, not silently gone (悠悠哥 on Discord: after turning on 只通过路由分组使用, hy4 vanished from the agents' model lists) — a provider set to "Only through routing groups" (Unlisted) keeps its models out of provider.Catalog(), which feeds every agent's picker, /v1/models and the configs magpie writes; Served() still serves them, but only to the routing groups they are in or a request naming the id. A model in no group was therefore usable by nothing the UI offers, and nothing said so. The option keeps its purpose (the models stay out of the lists); instead /api/state now carries the unlisted models with the groups each is in, and the picker, finding one in its filter, says why it isn't offered: in a group, "agents reach hy3 by picking Mine" with a "Pick Mine" button; in none, "no agent can use it" with "Make a routing group of it", which opens the Routing page on a new group of that model (from the tray panel, the window is opened on it through window/main?view=routing&newgroup=…). The provider's editor, while the tick is on, names its exposed models in no group in amber, each a click from a group of its own (before saving, it says to make them in Routing once saved). /api/providers gives each model's groups. No left-border accent; the clicks scroll nothing (scrollOnPurpose). TestUnlistedModelsSaid and unlisted-models.test.cjs (Chromium, WebKit, en/zh) fail without the change. A sandbox magpie with an unlisted Hunyuan (hy3 in a group, hy4 in none) listed neither in the pickers, and said each as above. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; unlisted-models, model-pick, routing-manual, click-scroll, old-webkit pass. yetone 2026-10-01 16:16:21 +08:00
  • 9ac96e23cd gateway: a model that thinks with no levels to pick says it thinks on /v1/models (#402) — mimo-v2.6-flash (Xiaomi's, OpenCode Go's, and a group of them) was listed reasoning: false with no levels, though models.dev says it reasons: /v1/models took reasoning to be having levels, and the model thinks with a switch alone. A model now carries whether it thinks — models.dev's reasoning flag for its own vendor, a plugin's, else what most of the providers models.dev lists serving it say, for a vendor's or relay's list that says nothing of it (Xiaomi's API gives ids alone) — and is listed reasoning: true with empty supported_reasoning_levels, told apart from one that doesn't think; levels the user kept down to none leave it thinking. A group thinks when every member does, or when it offers levels. TestModelsReasoningWithoutLevels fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestClaudeRunEndsWhenTheClientLeaves and TestUserPath, load flakes, pass alone). yetone 2026-10-01 16:08:33 +08:00
  • 5b917c1d8f grok: Codex's sub-agent tools reach a Grok subscription (#404) — Codex groups spawn_agent, wait_agent, send_message and followup_task in its collaboration namespace and set_thread_title in codex_app, and since aa08863d12 a namespace was left out whole, as Grok's backend turns a request away over the type: multi_agent_v2 on Grok had no tools to spawn with. A namespace's functions now go to Grok as functions of their own under the flat name the translation path gives them (collaboration__spawn_agent, flatName, now provider.FlatName), and so do a call to one handed back and a tool_choice naming one; the namespace type itself still never reaches Grok, nor does a freeform tool (apply_patch, or one inside a namespace), and a namespace with no function goes whole. Responses Lite's "functions" namespace stays plain names. On the way back a call Grok makes to a flat name reaches Codex under its own name and namespace with an empty encrypted_function_args, as callTo gives one, streamed (output_item.added/done, response.completed) or not. A ChatGPT account still gets the namespace as Codex sent it. TestGrokGetsNamespacedTools (a fake Grok upstream returning a call to collaboration__spawn_agent), TestGrokBodyFlattensNamespacedCalls and the updated TestGrokBodyLeavesOutCustomTools fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestUserPath, a load flake, passes alone). yetone 2026-10-01 16:07:24 +08:00
  • 2fc277de9f gateway: GLM's thinking streams again through a group (悠悠哥 on Discord: ZCode's thinking shows only at the end, all at once) — since a8f0baaa (#248) a stream that had only reasoned was held, up to 4 minutes, until its text or a call began, for a refusal after the reasoning to go to the next account; so GLM on a ZCode account (or any key with retries left, or any group member before the last) showed Claude Code its thinking only once the text came, in one burst. Only Claude's, OpenAI's and Gemini's safety filters end a reply that has only reasoned with a refusal, so reasoning is now held only for those models (claude-*, gpt-*/o-series/codex, gemini-*); anybody else's (GLM, DeepSeek, Kimi, MiniMax…) goes through as it comes, as before #248. Requests, headers and prompts are unchanged. TestThinkingStreamsThroughGroup (a fake vendor streaming three thoughts 200ms apart, on Anthropic's API and on Chat with reasoning_content, read by a real HTTP client through a group) and TestThinkingHeldFor fail without the change; TestRefusalAfterThinkingFailsOver now names Claude and GPT models and still passes. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. yetone 2026-10-01 15:55:44 +08:00
  • 02d501894b usage: link request accounting to routing history (#361) Aliceandraper 2026-10-01 16:18:02 +08:00
  • 8b386042f6 fix(gateway): avoid retrying refused off efforts (#401) TryWorld 2026-10-01 16:17:56 +08:00
  • 823e8baedd ui-preview: the sandbox has Claude Desktop, on magpie (#400) — the preview's home had Claude Code, Codex, Gemini and OpenCode but no Claude Desktop, so a PR changing Desktop's row (#399: tier menu, model picker, gateway notice) recorded "not available". run.sh now makes $XDG_CONFIG_HOME/Claude, where magpie looks for Desktop on Linux (the runner), and runs magpie claude-desktop provider magpie, which writes Claude/ and Claude-3p/configLibrary in that home as the Agents page would; no Desktop app is installed. The CLI writes the folders where the OS keeps them, so a local run on macOS gets Desktop under ~/Library/Application Support too. Checked in a sandbox HOME (darwin): Desktop absent from magpie ls before, "provider magpie" after, its profile pointing at MAGPIE_ADDR 127.0.0.1:3430; the Linux path is TestDesktopDirs's. bash -n run.sh, go test -tags nogui ./internal/agent -run Desktop pass. yetone 2026-10-01 15:38:28 +08:00
  • fc9643a5e5 tests: sessions-today answers /api/usage/quotas with an array — opening the page asks for the quotas, and the catch-all's {} made the page throw, failing 4 of 16 subtests on main; it now gets [] as the Go side writes it. sessions-today passes 16/16 (Chromium, WebKit, en/zh). yetone 2026-10-01 15:23:44 +08:00
  • 41a5ad6513 claude: an account's usage keeps itself up to date again, every ten minutes, through Claude Code (the user: 既然 Magpie 不再发任何的 Claude 请求了,轮询可以再加上,只是别那么频繁) — since v0.1.558 the usage was only read when Usage was opened or Refresh pressed, so the figures left open, in the menu bar and for routing went stale. The account Claude Code is signed in to now has its /usage run again by itself once its last reading is ten minutes old (an ask still reads at once, no more than once in 30s); magpie still never asks Anthropic, and a saved account is still never read. A failed run says why until the next one rather than "not read yet". TestClaudeWindowsAsked fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; claude-usage-asked, tray-usages, click-scroll, old-webkit pass. yetone 2026-10-01 15:16:20 +08:00
  • 6bf1c3987f routing: a group's rule can hold hours of the day (Discord: send DeepSeek's or GLM's peak-price hours to another provider) — a rule takes time: {from, to, days} in this computer's local time, HH:MM to HH:MM; hours that end before they begin run past midnight and belong to the day they begin on (Friday's 22:00–08:00 takes Saturday's early hours), days are optional, and the hours are checked as a turn begins, so a turn that began inside them finishes where it went. The rule reads "time 09:00–18:00 Mon–Fri" on the group and in the trace; the editor types the hours into one pill (从 … 到 …), notes past midnight, marks a time that isn't one and picks days like the agents (every day, weekdays, weekends, or each); magpie group rule add … time=14:00-18:00 days=mon-fri and the TUI's rule line take it too. Older versions ignore the field, and a rule with only hours then matches nothing there. TestRuleTimeMatches and TestRuleTimeWindowAtTurnStart (injected clock) and routing-time.test.cjs fail without the change. A sandbox magpie with two fake vendors sent a request inside the hours to the rule's member and one outside them, or on another day, to the first. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; routing-time, routing-compact, routing-manual, routing-steady, settings-groups, click-scroll, old-webkit pass. yetone 2026-10-01 14:48:46 +08:00
  • 15fa17f1c4 gateway: ZCode's count_tokens is estimated, and a vendor's firewall block is said in plain words (ZCode Start Plan: HTTP 405 "request has been blocked due to unusual activity") — Claude Code asks count_tokens before every turn, and magpie sent each to zcode.z.ai/api/v1/zcode-plan/anthropic/v1/messages/count_tokens, which answers it with an error every time and which ZCode itself never asks; a ZCode account's count, built-in or on its plugin, is now estimated as Cursor's, Grok's and Kiro's are, and zcode.z.ai is not asked. The 405 is Alibaba Cloud's edge firewall (ESA) in front of zcode.z.ai blocking the address: its HTML page was shown as "405 Method Not Allowed" and, a 405 not passing as a provider's failure, no other member of a group was asked. A block page (request has been blocked, errors.aliyun.com) now reads "the provider's network firewall blocked requests from this IP; wait a while, or switch to another network or proxy" to the agent and in the trace, where the Routing page gives it on its own line in English or Chinese, and the group goes on to its next member, the blocked one resting as for any other failure. No request header, user agent or device id changes. TestZCodeCountTokensEstimated, TestMovedZCodeCountTokensEstimated, TestEdgeBlockedSaidPlainly and routing-blocked.test.cjs fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; routing-blocked, routing-wb-refused, routing-proxy, routing-shape, click-scroll, old-webkit pass. yetone 2026-10-01 14:44:05 +08:00
  • 9abf441dfa plugin: a plugin whose model list can't be fetched keeps the one it told last, as a built-in does (ARNO on Discord: the Command Code plugin lists no Pixel Canary, the built-in does) — a built-in whose fetch fails keeps catalog.Live, the list it fetched last, while Command Code's, Kiro's, Qoder's, WorkBuddy's and Zed's plugins catch the failure and give their short defaults back, so a vendor's hiccup shrank the models. The host now sees a models hook that asked its vendor, had no answer from its last ask and handed back the list it was given, and marks that listing; magpie keeps the provider's and each account's last told list in its place, in memory and from plugin-providers.json after a restart, and the vendor back, its list is told again. TestFallenBackListKeepsTheLastOne fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; internal/plugin and internal/provider pass on Windows and Linux. yetone 2026-10-01 15:01:07 +08:00
  • db0a3344f4 gui: a Plugins card's move button has a row of its own and says how many accounts it moves — on the Plugins tab, a card that offers a built-in's accounts put "Move my {name} accounts ({n})" in its top row beside the plugin's name; the button doesn't shrink (.pm-act is flex: none, nowrap), so in a two- or three-column window "Move my Grok (SuperGrok) accounts (1)" ran past the card's right edge, and Kiro's shorter one squeezed its name to "K…" and cut "magpie community". The move button (and its "Moving…") now has a row of its own at the card's foot, full width, wrapping if it must; Install, Sign in and the rest stay in the top row. Its words say what the number is: "Move my 1 Grok (SuperGrok) account", "Move my 2 Zed accounts", 「迁移我的 1 个 Grok (SuperGrok) 账号」. plugin-move-overflow.test.cjs (Chromium, WebKit, en/zh, 920/600/480px with the column count checked) fails without the change at 920 and 600px; plugin-move, plugin-market, market-have, old-webkit, plugin-moved-signin, plugin-release, plugin-signin pass (plugin-market's "typing keeps the field" fails now and then on main here too). Go not run: only CSS, JS and strings changed. (#398) GinWU 2026-10-01 14:56:25 +08:00
  • f56d105495 provider: a Copilot account signed in from magpie gets its allowance card, the editors' sign-in or not (#386) Bo Zou 2026-10-01 14:43:13 +08:00
  • d5bb8eedd9 agents: Antigravity CLI's calls through magpie are agy's (X: agy 的请求和会话在Magpie 检测不到吗?) — agy asks as Google's Gemini SDK, its User-Agent google-genai-sdk/… with nothing of agy's own, and the command magpie gives to start it on the gateway set GEMINI_API_KEY to the plain token, so the Usage page and the routing trace put its calls under "google-genai-sdk", never Antigravity CLI. The command now sets agy's own key (magpie-agy), which agy sends as x-goog-api-key and the gateway reads as the agent's, as for Alma, Hanako and WorkBuddy; a command copied before still gets answers, recorded as before. agy's sessions from its own Google sign-in are still not listed: their tokens are only in its conversation databases' undocumented protobuf. Verified with agy 1.2.14 in a sandbox HOME on a sandbox gateway and a fake upstream: the new command's call recorded as agy ("Antigravity CLI" in magpie usage), the old one's as google-genai-sdk. TestAgy (the launch command) fails without the change; TestAgyAgent guards the gateway side (x-goog-api-key and ?key=). go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; agy-launch, click-scroll, old-webkit pass. yetone 2026-10-01 14:27:51 +08:00
  • c900734020 feat(usage): account for upstream provider keys (#364) Ns2Kracy 2026-10-01 14:33:18 +08:00
  • 23472507c0 agents: Muse Code, Meta's muse CLI, is an agent magpie sets up (Discord: Muse Code 能加进来吗) — magpie points its endpoint_transport (~/.config/muse/settings.json, $XDG_CONFIG_HOME) at the gateway with auth "none", so the Meta sign-in's token is neither read nor sent, and sets its model to the catalog id; the endpoint and model the user had are stashed and put back on reset, the file's other keys (mcpServers, tui, proxy…) kept, and a settings file magpie had to make (with the schema_version Muse refuses one without) goes again. Muse lists its models from /muse-code/models on its endpoint's host before every session and starts none without it, so the gateway now serves that list, each row with the metadata["muse-code"] Muse shows and a limit with both window and reply size (one without either is hidden; none has it ask for 128K-token replies). A Responses stream the gateway translates from another API started with output null, which Muse reads as a broken stream and retries ten times; it is an empty list, as OpenAI's is. Its requests (muse-build/…) count under Muse Code in usage; Meta's icon from dev.meta.ai. Checked with muse 1.4.2 in a sandbox HOME against a sandbox gateway: exec answered through a Responses fake and through a chat fake (25 tools flattened, a read_file round trip), and switching back left settings.json byte-identical, or removed the one magpie made. TestMuse, TestMuseNoSettings, TestMuseModels and TestResponsesStartOutputList fail without the change (TestDriftUnwiredEveryAgent covers it too). go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestSavedButSignedOutClaudeSaysWhy flaked once, passes alone); click-scroll, old-webkit pass. yetone 2026-10-01 14:17:46 +08:00
  • 69ec68383d agents: the panel's effort slider puts a level it doesn't offer in its place, and its bars light where the slider stands (#348) cyxer000 2026-10-01 14:15:05 +08:00
  • 3c67b5ca5e gateway: a repeated tool call id continues the call it names, not a new call — some relays repeat a tool call's id on every fragment of a streamed reply, where the OpenAI spec sends it on the first alone. The decoder read any non-empty id as "a new call starts", so one tool call came out of translation as one call per fragment: chatEncoder numbered each of them a call of its own, and anthropicEncoder and responsesEncoder opened a content block and an output item for each, every one holding a slice of the arguments and none of them valid JSON. An agent asking for a tool through such a relay was answered several broken calls instead of the one it made — Claude Code, which asks on the Anthropic API, among them. A fragment now continues the call already open when its index matches and it either names no id or names the id already open; only a new id or a new index starts the next one. (#395) volcanic 2026-10-01 14:14:58 +08:00
  • 618e51a447 grok: moving Grok to its plugin takes the plugin at 0.1.5 or later (goal: 插件订阅 provider 跟内置的所有逻辑都要一模一样) — a token Grok refuses before its time now reads as an expired sign-in, as one past its time does and as the built-in read it, so a move takes such an account along untried instead of failing on "Grok models: 401". @magpie-community/opencode-grok-auth 0.1.5 is on npm and installs and loads in a sandbox. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. yetone 2026-10-01 14:14:06 +08:00
  • 288767a648 gateway: a translated request asking a model for no reasoning, refused with the levels it takes, is asked again at its lowest (#394: Claude Code's auto mode classifier on Command Code's deepseek/deepseek-v4.1-flash-fast, 400 Invalid option: expected one of "low"|"medium"|"high"|"xhigh"|"max") — the Command Code plugin lists a Pro/GOAT key's models without their levels, so magpie borrowed the model's from the providers listing it (none, low, high, max), and the classifier, asked the least reasoning the model has, went out with reasoning_effort "none", which Command Code turns away. Only a passed-through request was asked again at low (8b8a9e5a, Qoder's "none"); the classifier comes from Claude Code's Anthropic API translated to chat completions. Now that path asks again at the model's lowest level, and once refused asks so from the start for that provider and model; a request with an effort, or none at all, goes as before. TestTranslatedEffortNoneRefused (a fake upstream answering Command Code's error) fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestFXInState alone). yetone 2026-10-01 13:51:06 +08:00
  • 1abcf8d7a2 gateway: a vendor that turns away a field it doesn't know is asked again without it (#393: Mistral's 422 extra_forbidden for OpenCode's "store") — OpenCode sends store:false on every Chat Completions request, and Kimi Code its thinking switch; relayed as sent, Mistral refused each one, so no OpenCode request reached a Mistral model through magpie. Only the cache key was asked again without, and only on a translated request. Now a relayed Chat or Responses request that an upstream refuses naming one of the optional fields it carries (store, metadata, service_tier, prompt_cache_key, prompt_cache_retention, safety_identifier, stream_options, parallel_tool_calls, verbosity, thinking, enable_thinking) — quoted, as Mistral's loc ["body","store"], Gemini's Unknown name "store" and Groq's 'store' do — is sent again without them, and once that works they're left out for that provider from then on. Any other refusal reaches the client as before. TestMistralRefusesStore (a fake upstream answering Mistral's extra_forbidden body) fails without the change; go vet and go test -tags nogui ./internal/gateway pass. yetone 2026-10-01 13:47:24 +08:00
  • a2714f7506 claude: a Claude account's usage resets are read in the zone /usage names on Windows too, and the provider tests pass whole on Windows (goal: 插件订阅 provider 跟内置的所有逻辑都要一模一样, run on Windows and Linux) — Windows has no time zone database, so "resets Oct 1 at 3:30pm (Asia/Shanghai)" was read in the machine's own zone, hours off for anyone not in that zone; magpie now carries the database (time/tzdata). The provider tests set HOME but not USERPROFILE, which Windows takes the home from, so on Windows they read the test box's own agent sign-ins (Gemini models in a list, an extra provider, a Cursor version); every test home sets both now. Tests whose fake CLI is a shell script (Claude Code's auth status, codex --version, macOS's security) skip on Windows, as the fake devin did, and file mode bits go unchecked there. TestParseClaudeUsage fails on Windows without the change. On Windows and on Linux (WSL) the provider package passes whole, three times on Windows (15 failed before), the plugin package and the move/plugin/Grok/Devin/usage tests five times over on Linux. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. yetone 2026-10-01 13:51:22 +08:00
  • 5fe0906907 gateway: a remote magpie's routing group answering with the member it routed to isn't a swap (莫 on Discord: 发送模型 group/auto-deepseek-v4-1-flash, 实际模型 deepseek/deepseek-v4.1-flash shown amber) — a request for one of another magpie's groups ("group/<id>", the name only a magpie answers to) comes back with the model its member's vendor named, and the Usage page's Requests and the Routing page marked that as the vendor serving another model. Now it is routed, not swapped (usage.GroupRouted): the try, the route and the ledger row say so, the served member shows plain, muted on the Usage page, with a title (and a line in the request's story) saying the remote magpie's group routed the request to it, in English and Chinese. Rows recorded before are judged again as the ledger lists them, and routes kept in the Routing page's history as they are read. A real swap through a remote magpie, and a swap within a group of this magpie's own, are still marked; a swap behind the remote's group shows on that magpie's own Routing page. Checked end to end with two sandbox magpies over a fake vendor: the local ledger and routing history mark group/… false (swapped before the change) and fake/sol served as luna true. TestRemoteMagpieGroupNotSwapped, TestRemoteMagpieGroupLedgerRow, TestRemoteMagpieGroupHistory and remote-group-served.test.cjs (Chromium, WebKit, en/zh) fail without the change. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (load flakes pass alone); routing-served, usage-ledger, panel-routing, remote-magpie, click-scroll, old-webkit pass. yetone 2026-10-01 13:45:43 +08:00
  • 7dd809002b accounts: on Linux an account put first is read first at once, and on Windows a file being replaced isn't read as none (goal: 插件订阅 provider 跟内置的所有逻辑都要一模一样, run on Windows and Linux) — the provider tests run on the Windows test box and in WSL found two bugs of magpie's own files. Linux stamps a file's time from a clock a tick (a few milliseconds) coarse, and filememo took same time and size for the same file: two accounts swapped (Devin, Grok, WorkBuddy, ZCode, Claude…) read back in the old order, so magpie kept using the account the user had just moved down, and a moved-back plugin's accounts read as before. A file written within the last two seconds is now also compared by its bytes. On Windows a file being renamed over refuses to open for a moment ("Access is denied"), and one held open refuses to be renamed over: plugin-auth.json, plugins.json and the plugin providers read as nothing (a usage fetch right after a move showed no cards), logins.json failed to save, and host.js, reading plugin-auth.json as {}, would have written the other accounts away on the next sign-in. New internal/steady waits such a read or rename out (half a second at most, Windows only); filememo, edit.WriteAtomic, writePrivate, Kiro's writes and the plugin store go through it, host.js retries its read and rename, and the plugin store writes its files by a rename. Tests: USERPROFILE beside HOME in the test homes, the fake devin (a shell script) skipped on Windows, plugin-auth.json's mode bits unchecked there. TestReadSameSizeSameTick fails on Linux and TestRenameOverAFileHeldOpen/TestReadAFileHeldOpen on Windows without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; on Linux (WSL) the provider and plugin packages pass whole (13 failed before), on Windows the plugin package and the move/plugin/Grok/Devin/usage tests pass 10 times over (TestMoveToPlugin failed one run in a few before); 15 built-in tests that assume Unix (mode bits, Keychain, shell-script CLIs) still fail there, as before. yetone 2026-10-01 13:27:09 +08:00
  • 22b6ad19f9 gateway: a streamed reply's body reads as JSON (Jorben on Discord) — Recent calls showed a streamed response body as its raw event:/data: lines. Now a body of server-sent events is read three ways, picked beside its label and remembered: Events, each event's data pretty-printed as JSON under its name ([DONE] and other non-JSON data as it is); Reply, what the stream adds up to (Anthropic Messages content blocks with thinking, text and tool_use input; OpenAI Responses output, from response.completed or from its deltas when cut; Chat Completions content, reasoning and tool calls; Gemini parts), tool arguments as objects, with how it stopped and its usage; and Raw, the body as it came. Copy copies what is shown. A long stream draws 200 events at a time from a button under the box, which stays where it is, the page with it; the events drawn and a body's scroll stay as new calls redraw the list. A body that isn't a stream is as before. sse-body.test.cjs (Chromium, WebKit, en/zh) fails without the change; click-scroll, old-webkit, gateway-fold pass. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./internal/gui/... pass. yetone 2026-10-01 13:17:45 +08:00
  • 2a3478fe0e gateway: another machine reads GET /v1/magpie/quotas with the key of magpie shared on the local network (#389) — the quotas answered only loopback, so a magpie shared over a LAN or tailscale told the machines it serves nothing of what is left, though they could already send it work with the same key. A request that lanGuard let through with the LAN key (constant-time compared, as for a model call) is now answered; no key or a wrong one is still refused by the guard (401), and a gateway not shared from Settings — MAGPIE_ADDR's open one included, which has no key — still answers only this machine, its 403 saying to share magpie on the local network and send the key as the API key (Authorization: Bearer or x-api-key). A reverse proxy on this machine is loopback, as before. magpie quota's help says so. TestQuotasOverLAN fails without the change; checked end to end with a sandbox magpie on its own port, curling the LAN IP with the key (200), without or with a wrong one (401), and unshared (403). go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestZhipuKeyTeamFields's fake server raced once under load, passes alone). yetone 2026-10-01 13:02:57 +08:00
  • 024f494bc4 Allow provider-specific upstream model names (#280) Comic Changandyetone 2026-10-01 13:03:57 +08:00
  • 42d81bc1ff sessions: Pi's and omp's sessions count their prompts, replies, tool calls and skills (#384) — Usage → Sessions filtered to Pi showed no session shape by messages or tool calls, no tools and no skills: piParse read only the title, the model and the usage, and the fast path dropped every user line after the first. Now, from Pi's v3 session format as its source writes it (packages/ai types.ts: AssistantMessage content ToolCall {type:"toolCall",name,arguments}, ToolResultMessage nestedCalls.calls; agent-session.ts: /skill:name sent as <skill name="…" location="…">; skills.ts: a skill is loaded by reading its SKILL.md), a typed user message is a prompt, an assistant message a reply, each toolCall part a tool call, a tool's nestedCalls (a codemode script's) calls too, and a read of a SKILL.md (or omp's skill://) or a /skill: prompt a skill; a fork's copied entries count in the session they came from. ToolCategory knows Pi's lowercase names (bash, powershell, read, write, find, ls). The sessions cache version goes to 6 so kept parses are read again. TestPiToolsAndMessages and TestToolCategoryPi fail without the change. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestUserPath and the Claude account tests flake under load, pass alone). yetone 2026-10-01 12:32:36 +08:00
  • be8fbf1b22 provider: a MiniMax Coding Plan key shows its windows as rings (#387) — a MiniMax key (api.minimaxi.com, api.minimax.io) had no plan card, only what a balance URL could say in words. Its plan's windows are now asked where MiniMax tells them to the key, GET /v1/token_plan/remains with the key as Bearer (as MiniMax's docs in OpenClaw, oh-my-pi's minimax-code usage and Kilo Code ask it): each model_remains bucket's interval and week, told as what remains, so used is 100 minus it; status 2 (used up) is full whatever the percentage says; a bucket the plan doesn't have (both windows status 3, no totals) is left out; "general" is what the models draw on, another bucket (video…) is shown aside and doesn't stop them. MiniMax answers 200 to a key it refuses, so base_resp's code decides: a pay-as-you-go key gets no card. No relay-specific source is added. TestReadMiniMaxPlan, TestPlanQuotaSource and TestPlanQuotas fail without the change. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestUserPath and the Claude account tests flake under load, pass alone). Not tried with a real Coding Plan key. yetone 2026-10-01 12:37:09 +08:00
  • 9547cee9ce gateway: a member whose proxy is down goes on to the next without resting (#381) — with the local proxy (127.0.0.1:1082) not listening, Cursor and SuperGrok in a group failed with "proxyconnect tcp: … connection refused", were counted as failing ("other") and set aside on a backoff that grew with every request, so they stayed out after the proxy came back. A 502 whose error is Go's proxyconnect or socks connect is now "proxy": nothing reached the vendor, the next member is asked as for a request a vendor couldn't read, and nobody rests. The Routing page tags it "proxy not reachable" and says the request never reached the vendor, in English and Chinese. TestProxyDownDoesNotRest (HTTP and SOCKS proxies) and routing-proxy.test.cjs (Chromium, WebKit, en/zh) fail without the change; routing-shape, click-scroll, old-webkit pass. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. (#381) yetone 2026-10-01 12:36:19 +08:00
  • 89672eb966 gateway: a thinking model behind a Responses or chat API gets its reasoning back between tool calls (#388) — Codex offers its web_search, so its request to a provider that doesn't search by itself is translated, and the translation left every reasoning item out: DeepSeek V4.1 Flash refused the next turn with "The reasoning_text in the thinking mode must be passed back to the API". A turn's reasoning (the item's reasoning_text, else its summary, all magpie hands a client of a translated reply) now goes back as a reasoning item, for a DeepSeek model (another may be passed on to OpenAI, which refuses unsealed reasoning), to any Responses API but OpenAI's, xAI's, Copilot's and Azure's, which read only their own sealed reasoning; on chat completions a DeepSeek model gets reasoning_content by its name as well as its host. TestReasoningReplayed fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. (#388) yetone 2026-10-01 12:32:22 +08:00
  • 07ec8240a4 gateway: a tool sent on to a Responses API says strict, as Codex says it (#383) — MiniMax Code on a Codex model through magpie was turned away on every request with "Invalid JSON schema: regex lookaround is not supported. Found at $.properties.path.pattern": its Anthropic tools went to the ChatGPT backend with no strict, which holds them to strict mode's rules, while the same tools with strict false or null are taken. Every function tool on a translated Responses request now carries strict: false, or true when the client asked for it (Responses and Chat tools keep theirs). TestResponsesToolStrict fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (load flakes rerun alone). (#383) yetone 2026-10-01 12:32:22 +08:00
  • 0cbca2352e gateway: a Claude subscription asked for xhigh effort runs Claude Code at xhigh, not max (#385) — the bridge turned xhigh into --effort max, from before Claude Code had a level of that name; Claude Code 2.1.286 takes low, medium, high, xhigh and max, sends xhigh as output_config.effort "xhigh", and max spends more of the plan. xhigh now goes as asked. TestClaudeCLIEffortXHigh fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (load flakes rerun alone). (#385) yetone 2026-10-01 12:19:29 +08:00
  • 9bf6d43527 claude: magpie no longer asks Anthropic for a Claude account's usage, and adding a Claude account warns of a ban (the user: 不要伪造任何的 Claude 请求,能否通过 claude cli 获取 / 我害怕被封; 添加账号时要像其他的 Subscription Provider 一样显示风险提示) — the usage was read by magpie itself calling Anthropic's /api/oauth/usage with the account's token, on a timer, for every saved account, and Claude's usage-limit resets (cedar_ember) were listed, spent and auto-spent the same way. Now the usage is what Claude Code's own /usage prints (claude -p /usage, a command Claude Code answers itself without asking a model), run only when Usage is opened or Refresh pressed (GUI, magpie quota, accounts, TUI), for the account Claude Code is signed in to, at most once in 30s; a saved account shows only what Claude Code reported while using it, or the last reading kept. The Claude resets, Auto-use and their settings are gone (Codex's stay). Session jsonl files hold no usage figures, so they aren't read. Starting a Claude sign-in or import (GUI and CLI) first says Anthropic may suspend or ban the account and that it is at the user's own risk. TestClaudeWindowsAsked, TestParseClaudeUsage, TestClaudeUsageRunsClaudeCode, claude-usage-asked.test.cjs and claude-risk.test.cjs fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; routing-auto-reset, codex-auto-reset, quota-families, tray-usages, click-scroll, old-webkit, plugin-signin, usage-alert, usage-ledger pass. yetone 2026-10-01 12:35:03 +08:00
  • 7a091e31ae devin: the CLI's own account is named by its plan, as the Devin plugin names it (goal: 插件订阅 provider 跟内置的所有逻辑都要一模一样) — a sandbox move showed the built-in's own row with no plan while the plugin's said Devin Pro: the tier devin auth status gives was read and dropped. TestDevinUsage fails without the change, and now waits for the CLI as long as a loaded machine needs (it failed under -count=8). go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; in a sandbox HOME, Devin's two accounts read the same day/week windows on the built-in and on the plugin, and Grok moved with one account past its time and back. yetone 2026-10-01 12:36:59 +08:00
  • a91385c175 gui: the menu bar's usage can go without its logos (Jevin on Discord: 为什么托盘的额度显示要带小图标啊 反而丑了一些) — on the Mac each card in the menu bar was a small logo before its stacked percentages, with no way to drop it. Settings has a Menu bar logos On/Off row under Menu bar refresh (shown on a Mac with a card picked), On by default as before; Off draws each card as its percentages alone, a faint hairline between one card and the next, and the bar is redrawn at once. TestTrayPlain, TestTrayImagePlain and tray-logos.test.cjs (Chromium, WebKit, en/zh) fail without the change; tray-usages.test.cjs clicks Currency where it is, as Playwright otherwise scrolled to it under the footer now the row sits above it. go vet, go build (darwin GUI), go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; click-scroll, old-webkit, tray-usages, tray-inuse, settings-groups, usage-alert, update-hide, currency pass. yetone 2026-10-01 12:25:42 +08:00
  • 181eefdc31 plugins: built-in Devin shows its usage as the Devin plugin does, and a Grok account whose sign-in has expired no longer stops Grok's move (goal: 插件订阅 provider 跟内置的所有逻辑都要一模一样) — the plugin showed a Devin account's day and week quotas, its ACUs and extra balance from GetUserStatus while the built-in showed no usage at all, so routing had no allowance to fail over on until the move; the built-in now reads the same and fills the same windows (1 day, 7 days, ACUs aside), plan end and balance, and a refused key says to sign in again. A move checks each account through the plugin and stopped when one failed; Grok's plugin says an expired sign-in only in words ("Grok's sign-in has expired"), so one dead account kept every Grok account on the built-in. Such an account now moves along untried and unmarked, as a built-in Grok account was never marked; any other failure still stops the move. TestDevinUsage and TestMoveExpiredInWords fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. yetone 2026-10-01 12:24:45 +08:00
  • b69fd168c2 remote magpie: its image models reach this magpie, and look-alike models say which provider they are from (Jorben and 莫 on Discord) — another magpie's /v1/models listed chat models only, so its image models were never found here unless their id said "image"; a magpie now asks for them (X-Magpie-Drawers), the other lists them marked kind:image, and they show in Settings → Images and draw through its /v1/images/generations, which asks the model's vendor the way it draws there. Each model also comes with its name and its provider there (magpie_label), so two relays' "Claude Sonnet 5" no longer read the same. An older magpie on either side reads the list as before. TestRemoteMagpieModels fails without the change; TestRemoteMagpieOldList covers an old remote. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; remote-magpie passes. yetone 2026-10-01 12:05:20 +08:00
  • 51834ba937 migrate: a move installs the plugin versions that match their built-ins (要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样) — Command Code 0.1.6 (a Go account lists Go's models), Grok 0.1.4 (grok-4.7's reasoning levels), Qoder 0.1.4 (a refused refresh marks the sign-in), ZCode 0.1.5 (the MCP quota set aside) and Zed 0.1.6 (a model token refused while listing marks the account), all published and installed from npm in a sandbox before this. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. yetone 2026-10-01 12:05:03 +08:00
  • 77a545e691 gui: the Update button can be skipped for a version or turned off (Wang Hsiaohi on Discord) — the purple ↑ Update in the panel's header showed whenever a version was out, with nothing to hide it. A × on hover (or a right-click) hides it until a newer version, and Settings → About has an Update button On/Off row with Show again for a skipped version. Only the button hides: magpie still downloads and installs on quit, and Settings → Version still offers the restart. TestUpdateSkip, TestSettingsSaveKeepsWhatItDoesNotSend and update-hide.test.cjs (Chromium, WebKit, en/zh, panel and window) fail without the change. yetone 2026-10-01 11:46:31 +08:00
  • 4ba51bb627 plugin: a plugin names its own icon and the key field's title and hint (Lemon on Discord) — a plugin's provider took only the market's icon, and an api method's key field always read "<name> API key" with no hint. auth.icon (or package.json "magpie": {"icon"}) as an https URL, fetched once through the public-only icon path, or a data:image URI now comes before the market icon; an api method's label titles the key field as in OpenCode, and a new placeholder hints it, in the window and in magpie plugin login. README has a For plugin authors section. TestPluginOwnIcon, TestPluginOwnIconAndKeyHint and plugin-key-hint.test.cjs (Chromium, WebKit, en/zh) fail without the change. yetone 2026-10-01 11:42:35 +08:00
  • 49c98f40f5 gateway: a Remote magpie records the agent that asked on the other computer (Jorben on Discord) — requests forwarded to a remote magpie went as magpie itself, so the remote's usage and routing said "magpie" for all of them. The forwarding magpie now names the agent and itself (X-Magpie-Agent, X-Magpie-Via), read only from a magpie User-Agent and stripped before any vendor; the remote records that agent with Via, and Requests shows "Claude Code · via office-mac". It is only a label: models, routing rules and the drift check still go by the real sender. TestRemoteMagpieAgent, TestLastSeenNotVia and usage-ledger.test.cjs fail without the change. yetone 2026-10-01 11:38:36 +08:00
  • 0bb119f87f docker: sign-ins and plugins survive a restart, and the image checks its own health (Jorben on Discord) — only /config was a volume, while HOME (/home/nonroot) held the agents' sign-ins (~/.codex, ~/.claude…) and the cache held Bun: a new container lost them all, and distroless/static has no libc, so the Bun magpie downloads couldn't start (fork/exec … no such file or directory). HOME and XDG cache/data/state now live under /config (made on start, so an old volume keeps working), the base is distroless/cc (glibc), and magpie healthcheck backs a HEALTHCHECK with no curl needed. Verified in Docker: an old volume carried over, a market plugin installed and stayed across a recreate without downloading Bun again, the container reported healthy. TestHealthcheck and TestMakeDirs fail without the change. yetone 2026-10-01 11:38:36 +08:00
  • b4c6772677 lan: magpie in Docker shares an address other computers can reach (莫 on Discord) — inside a container the LAN address shown was the container's own (172.17…). MAGPIE_PUBLIC_URL now names the share address; without it, a page opened through the host's IP shows that host, and the Gateway page says it runs in a container and how to set the address. lan-docker.test.cjs (Chromium, WebKit, en/zh) and the serve/lan tests fail without the change. yetone 2026-10-01 11:28:35 +08:00
  • 0376ddfb55 gui: folding a list shorter than the window no longer flips the view for 4 seconds (#355) — with Reduce motion on, collapsing a list that ends above the window's bottom made hold and fitRoom undo each other every frame: the room was sized from scrollHeight, which never falls below the view, so the blank under the list counted as content. The room is now sized from where the content really ends, and dropped once back at the top. rollup-short.test.cjs (Chromium, WebKit, en/zh, Reduce motion on and off) fails without the change; old-webkit, click-scroll, panel-fold pass. yetone 2026-10-01 11:28:34 +08:00
  • 7d0f33d702 copilot: a model the plan refuses is remembered and Auto falls back (#371, #256) — Copilot answers a model the account's plan doesn't carry (Student, Free) with 400 model_not_supported; magpie kept offering and picking it. Such a model is now marked refused for that token for 6h, left out of the models listed and of Auto's picks, and the request is retried on the next model (fallback, then default, then non-premium such as gpt-4.1). TestCopilotRefused* fail without the change. yetone 2026-10-01 11:28:32 +08:00
  • eb89665d98 gateway: Codex's compact reaches xAI, Grok and Copilot, and a web-search turn keeps its client_metadata (#378, #374) — a compact request drops its tools but kept tool_choice "auto", which Grok's subscription, the xAI API and Copilot's /responses refuse; a lone tool_choice is now dropped for those (a relay like #292's still gets the body as sent). A Codex request rebuilt by magpie (web_search) lost client_metadata and text (its output schema); both now go on to Responses upstreams as sent, never to Chat or Anthropic ones. TestGrokBodyDropsLoneToolChoice, TestCodexCompactsThroughXAI, TestCodexSearchKeepsClientMetadata and TestClientMetadataOnlyToResponses fail without the change. yetone 2026-10-01 11:28:31 +08:00
  • 957baf4925 fix: 修复虚拟双栈地址导致供应商图标获取失败 (#376) Fanbuz 2026-10-01 11:28:06 +08:00
  • abbe314f74 gui: Settings re-reads the WebDAV setup when redrawn; the off-state description names only what is on (#307) Gabyran 2026-10-01 11:25:25 +08:00
  • b932a70032 gateway: a reply on /v1/messages has an Anthropic id whoever served it (要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样) — a built-in moved onto its plugin (Devin, measured end to end) answered Claude Code with the plugin's OpenAI-shaped chatcmpl-… id where the built-in gave msg_…; the Anthropic replies, streamed and whole, now take msg_ as the chat replies already take chatcmpl-. TestAnthropicReplyID fails without the change. go vet, go test -tags nogui ./internal/gateway pass. yetone 2026-10-01 11:20:00 +08:00
  • b274ddd28e plugin: a move's check still goes through the account's proxy, now that it reads usage too — TestPluginProxy took the check's last request for its models; the check asks for the models and then the usage, so the test now looks for both among what the account's own proxy saw. renderExcluded's comment goes back over renderExcluded rather than over renderMovable. go vet, go test -tags nogui ./... pass. yetone 2026-10-01 11:15:20 +08:00
  • f5a2b5d406 gui: a moved provider's editor reads as the built-in's — its model list dated, its agent's own sign-in said, its move a button (要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样) — after a move the list said "magpie's list · Refresh asks the vendor" where the built-in said "vendor list · 9m ago"; it is now dated by when the plugin last listed it, in the editor and in magpie providers. "Command Code itself stays signed in as it is." is kept for Devin, ZCode, WorkBuddy, Kiro and Command Code, whose agents keep their own sign-in through the move; and Move to the plugin / Use the built-in again is drawn as a button, not a line of text. plugin-move.test.cjs fails without the kept line. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; plugin-move, click-scroll, old-webkit, plugin-market, plugin-signin, nokey-editor pass. yetone 2026-10-01 07:13:00 +08:00
  • a28509e268 providers: a built-in moved before its host was kept shows the vendor's host, not the plugin's id (要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样) — magpie providers and the Providers list showed "commandcode-plan", "zcode", "grok" for moves made before the move kept the built-in's host; those now show the built-in's own (api.commandcode.ai, api.z.ai, cli-chat-proxy.grok.com…), and one whose built-in showed none shows none. TestMovedShowsBuiltinHost fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. yetone 2026-10-01 07:12:59 +08:00
  • ff94998ad2 accounts: a moved built-in keeps its place in Providers and in magpie accounts (要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样) — with WorkBuddy and WorkBuddy AI both moved, WorkBuddy AI came first when its plugin listed it first, as a moved one placed already didn't count; and magpie accounts put every moved built-in's accounts after all the others. Both now go where the built-in stood. TestMovedAccountsKeepTheirOrder and TestAllLoginsMovedKeepTheirPlace fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. yetone 2026-10-01 07:12:59 +08:00
  • cc6fc56b3b cli: magpie plugin list says a built-in runs the plugin's provider rather than "not signed in" (要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样) — after a move back the plugin stays when the user had installed it, and its rows read "not signed in · magpie plugin login kiro", as if broken; they now say "runs on magpie's built-in (2 accounts) · magpie plugin move kiro". go build -tags nogui passes. yetone 2026-10-01 07:01:57 +08:00
  • 93c3cde024 gui: moving a built-in onto its plugin says what it is doing and why it didn't go, where the user is (记得把整套迁移逻辑做得丝滑优雅精致优美简单方便易用 / 要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样) — the Plugins tab opened as the page (?view=plugins) stayed blank; Move to the plugin greyed out for up to 18 s, then closed the editor, its error far off in the footer in English, and the button never came back. The button now says "Installing the plugin and checking each account…", a failure is said under it in the reader's language (npm unreachable, every account lapsed, models the plugin doesn't serve) with Try again, and a move that goes through turns the field over in the editor still open where it was, with a toast of the accounts and models it took. Runs on reads "magpie's built-in · or the community Zed plugin", the npm package in its tooltip; a quiet line over the list names the built-ins a plugin can run (Take a look · Not now); a plugin's card offers "Move my Zed accounts (2)", its installed row "Move Zed here" and "Zed: on magpie's built-in" rather than a signed-out plugin, and switching it on again offers the move back in the row. Accounts: a lapsed-only provider shows Signed out, not a green pill; plan chips read "Zed Pro", not "Zed Zed Pro", and a long one isn't cut; the "{agent} itself stays signed in" line is kept for a moved built-in whose agent keeps its own sign-in, named as the agent, and dropped where none is its own. The vendor list's age is said in the reader's language ("8m ago" in the zh UI). plugin-move.test.cjs (Chromium, WebKit, en/zh) fails without each of: the Plugins tab loading on ?view=plugins, Try again, the inline reason, the editor staying open, the progress text, the card's move. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; plugin-move, click-scroll, old-webkit, plugin-market, plugin-signin, nokey-editor pass. yetone 2026-10-01 07:01:57 +08:00
  • f9e5c8dfe6 cli: magpie --help lists plugin move|migrate and move-back|unmigrate (记得把整套迁移逻辑做得丝滑优雅精致优美简单方便易用) — the commands existed but only magpie plugin help named them, and without their other names. go build -tags nogui passes. yetone 2026-10-01 06:45:12 +08:00
  • 77c2ad12fa migrate: a move that fails leaves nothing behind and says why, and a move only goes through on an account the plugin proved works (要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样 / 记得把整套迁移逻辑做得丝滑优雅精致优美简单方便易用) — a failed move left the plugin it had installed (a Plugins row saying signed out) and hidden plugin:<id> rows in logins.json, and a move checked only that the plugin loaded, so an account the vendor refused or couldn't be reached through moved as if it worked. The plugin a move installed now goes with a failed move, and with a move back when it serves nothing else; the stray rows go; the check reads each account's usage as well as its models, and an account the vendor refuses is marked and moves untried, as a lapsed one does, while one whose vendor can't be reached at all fails the move with nothing changed. A move with every account lapsed says so before installing anything. Failures carry a code (offline, install, lapsed, unserved, account) for the page to say in its language, with the models named, not their ids. A moved provider shows the built-in's API host (magpie providers showed the plugin's id), and Zed's accounts take their model list with them, so the plugin can serve it until it reads its own. TestMoveNeedsAWorkingAccount, TestMoveTidiesItsPlugin, TestMoveProvesEachAccount, TestMovedShowsBuiltinHost and TestZedMoveCarriesItsModels fail without the change. go vet, go build -tags nogui, go test -tags nogui ./... pass. yetone 2026-10-01 06:45:12 +08:00
  • c641c1da2d usage: a built-in moved onto its plugin keeps one history per account (要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样) — the built-in's calls were kept as going to "dee@example.com" (or "api.factory.ai as dee@example.com"), the plugin's as "kiro as dee@example.com", so Usage split an account's model rows at the move, the older half as if the id had gone elsewhere. A subscription's calls are now told apart by the account they went as, the host only for the rest; records already kept group together with no rewrite. TestSummarizeKeepsMovedAccountTogether fails without the change. go vet, go test ./internal/usage pass. yetone 2026-10-01 06:29:19 +08:00
  • 94c5b541c5 claude: nothing is sent to Anthropic in Claude Code's name any more, and a Claude account's model test runs Claude Code (the user: 先把伪装代码删掉,测试模型改走 CLI) — claude_cloak.go made magpie's own requests on a Claude sign-in look like Claude Code's (its User-Agent, session and device ids, Stainless headers, betas, a billing header with a fingerprint and xxHash in the body). Generations already ran Claude Code through the bridge; the cloak was left driving the provider test (a direct /v1/messages "hi") and the model list. It is gone: a Claude account's direct requests are refused unsigned, the model test has Claude Code answer "hi" at the model tested (the warm-up's run, --model the one tested, a saved account's sign-in as CLAUDE_CODE_OAUTH_TOKEN), and the model list says it is magpie (User-Agent magpie, only the oauth beta). TestClaudeTestRunsThroughClaudeCode and TestClaudeTestRunsClaudeCode fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. yetone 2026-10-01 11:12:25 +08:00
  • 7a3df78ff7 agents: omp's model picker lists the models of the providers in its own models.yml (#326) cyxer000 2026-10-01 11:10:49 +08:00
  • e8315b18ed gateway: a relay that searches the web by itself is sent Claude Code's WebSearch as it was sent (#359) (#365) RandyandClaude 2026-10-01 11:10:41 +08:00
  • 5523eb176a provider: a 403 from Factory says it serves a subscription only to Droid, not to sign in again (#242) — the same account's Opus 5.5, GPT-5.5 and GLM answered Droid through magpie every time and refused Grok Build (and Claude Code) every time, on the same models and efforts, with the same headers magpie builds; the old message sent people to check droid and sign in again, which changes nothing. Now it says Factory takes a subscription's requests only from Droid, other agents are refused, and the org's model policy or the plan is what's left when Droid is refused too. TestFactoryForbiddenWithoutOrg fails without the change. yetone 2026-10-01 11:03:55 +08:00
  • 813fdcb2e5 migrate: a moved built-in keeps its models' reasoning levels, its own account's plan and one own row, and the move checks each account through its proxy (要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样) — Command Code's, Grok's and WorkBuddy's models their vendor gives no levels for lost the ones the built-in borrowed from their maker; moved, they borrow them again. Devin's own account moved without the plan the Devin CLI says, which the built-in showed. The agent signing in to another account while moved wrote a new own row for the built-in, which came back beside the one set aside as a second own account; nothing is written while moved, and going back keeps the one. And the move's check of an account asked the vendor directly, not through the proxy its requests take. Found walking Command Code, Grok, Devin, ZCode and WorkBuddy through sandbox moves. TestMovedKeepsEfforts, TestMovedOwnRowNotWritten, TestDevinOwnMovesWithPlan and TestPluginProxy fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. yetone 2026-10-01 07:04:39 +08:00
  • 936a6fc710 gateway: a stream the upstream cut off mid-reply ends in an error event, not as if whole (#370) — a relay of a Messages or Responses stream that the upstream dropped, or ended short of its last event, was closed as though complete: no message_stop and no error, which dsh reads as "DeepSeek Messages stream ended before message_stop" (STREAM_CLOSED) and never retries, saying nothing of the cause. The relay now ends such a stream with the protocol's own error event (Anthropic event: error / api_error, which dsh retries; Responses response.failed; a Chat error only when the read failed, since a whole Chat stream may end without [DONE]) naming the provider, and logs the request as failed. A stream counts as whole by its last event's data or its event: name, so a response.completed too big to read (an image's base64) is not taken for a cut one. TestPassthroughStreamSeveredUpstream, TestPassthroughStreamEndsShort, TestPassthroughResponsesEndsShort and TestPassthroughResponsesBigLastEvent fail without the change; TestPassthroughStreamWhole guards a whole stream. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. yetone 2026-10-01 06:55:54 +08:00
  • 30236bf035 migrate: moving Kiro back with an API key no longer hangs magpie, a move's lock left by a killed magpie frees itself, and plugins changed in a terminal reach the running app (要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样) — Kiro's back put the plugin's API key onto the Kiro provider while holding logins.json's lock; saving the provider syncs the agents, which read the accounts and waited on that same lock, so the move back never returned and every request after it hung. The key is now put back once the lock is let go (a mover's settle). A move killed while hung left migrations.json.lock, refusing every move for 15 minutes as "another magpie is moving"; the lock now holds its magpie's pid and one whose magpie is gone is taken. And magpie plugin add, remove or move in a terminal left a running app with the plugins it had loaded (a moved Kiro gone until restart, "magpie knows no model kiro/auto"): a plugins.json changed by another magpie now restarts the host and takes the providers that magpie was told meanwhile. Found walking Qoder, Kiro and Cursor through sandbox moves. TestKiroMover (hangs), TestMoveLockOfDeadMagpie and TestPluginsChangedElsewhere fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. yetone 2026-10-01 06:53:27 +08:00
  • 471956c837 agents: dsh takes magpie as a custom provider of its own, Magpie, not by taking over its DeepSeek one (01huadalang on Discord: 集成进 dsh 会有 bug,会被 dsh 覆写,应该走 dsh 的那个另外的供应商) — magpie rewrote dsh's llm-deepseek entry in every profile to point at the gateway with the key MAGPIE_API_KEY. dsh's Models page showed it as DeepSeek, so a DeepSeek key typed there went into dsh's key store under that name, which dsh reads before .env: the next request carried the DeepSeek key and failed, magpie seemingly overwritten; dsh's own DeepSeek models were gone meanwhile. magpie now adds a magpie provider under llm-pi-ai, as dsh's Add model provider → Custom model API writes one (gateway URL, openai-completions, MAGPIE_GATEWAY_KEY, magpie's models with their limits, images and effort levels), and points the default model at it; llm-deepseek and the user's other providers are left alone, a profile wired the old way is moved on sync with its model and effort kept, new profiles get it, switching to a dsh model or clearing takes it out, and the check says when dsh's key store holds another MAGPIE_GATEWAY_KEY. Verified with dsh 0.2.0-rc.2 in a sandbox HOME against a fake gateway: headless and web requests carry magpie's token, a DeepSeek key saved in dsh's Models page no longer displaces it. TestDshCustomProvider and TestDshMovesOffDeepSeekRow fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. yetone 2026-10-01 06:34:12 +08:00
  • e86a34fce1 migrate: moving back keeps an account's expired sign-in marked, and one refused while the move lists its models goes along marked (要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样) — Zed's, Factory's and MiMo's back write the plugin's sign-in as one just made, so an account the vendor had refused through the plugin came back to the built-in showing signed in until its next refusal; the plugin's mark now goes with it. And a models hook saying a sign-in expired failed the move's strict check, so one refused Zed account stopped the whole move where the built-in would mark it and carry on; it is marked and moves untried, as a lapsed one does. Found walking Zed, Factory and MiMo through a sandbox move and back. TestMoveBackKeepsLapse and TestMoveRefusedWhileListing fail without the change. go vet, go build -tags nogui (GOOS=linux), GOOS=windows go build, go test -tags nogui ./internal/provider ./internal/plugin ./internal/gateway pass. yetone 2026-10-01 06:33:27 +08:00
  • 31b6257c7e usage: an Antigravity account shows a meter for each model family, not for each model (01huadalang on Discord: 谷歌的反重力如果登录多个账号显示得很丑…low mid 和 high 都要显示,这样好臃肿) — Antigravity gives a quota for each model it lists (13 in its recorded list), and the Usage page drew them all, 9–13 meters an account, while the panel and the account rows showed the first 3 or 2 by id. Each window now carries its family (Gemini, Claude, GPT-OSS…), and the Usage page, the panel and the account rows show one meter a family, the most used of its models (the soonest reset on a tie); its tooltip lists every model, and "Every model" swaps the account's meters in place without moving the page. Windows, routing and the model filter are unchanged; cards without families look as before. TestAntigravityQuotaFamilies and quota-families.test.cjs (Chromium, WebKit, en/zh) fail without the change; click-scroll, old-webkit, tray-usages, claude-resets, codex-auto-reset pass. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. yetone 2026-10-01 06:19:23 +08:00
  • b00387f9db provider: a Command Code Go account lists the models Command Code has for Go, not ten (01huadalang on Discord: command 账号登录只有十个模型,正确的应该是几十个) — for Go, magpie never asked for a list and showed a table of 10, on the word that Command Code has none; its Provider API's list answers without a key (86 models), and its CLI (1.73.0) offers Go every model its picker shows but the premium ones and those it blocks for Go. A Go account now fetches that list without its key, less the 30 refused, and saves it (56 today); the table, the CLI's whole Go set (57), stands in until then or when the fetch fails. Requests still go to /alpha/generate. TestCommandCodeGoModels fails without the change (10 models). go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./internal/provider pass. yetone 2026-10-01 06:14:55 +08:00
  • 78c32292e4 plugins: a models hook can say its account's sign-in expired, as a built-in whose model list the vendor refused marked the account (要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样) — a refusal in provider.models was only logged, so an account Zed or Qoder turned away while listing its models stayed unmarked where the built-in marked it. An error thrown with signIn: expired, kept or renewed now tells magpie, which reads it as it does an answer's X-Magpie-Sign-In. TestPluginModelsSayExpired fails without the change. go vet, go build -tags nogui (GOOS=linux), GOOS=windows go build, go test -tags nogui ./internal/provider ./internal/plugin ./internal/gateway pass. yetone 2026-10-01 06:12:11 +08:00
  • 475a55a702 migrate: built-ins move onto plugins that fail and mark the sign-in as they did — each mover now asks the plugin version that answers with its built-in's status and words and says through X-Magpie-Sign-In when the account's sign-in expired, stayed or renewed, in requests and usage reads alike: Command Code, Cursor, Devin, Kiro, MiMo, WorkBuddy and Zed 0.1.5, Factory and ZCode 0.1.4, Grok and Qoder 0.1.3, each published and installed from npm before this. go vet, go build -tags nogui (GOOS=linux), GOOS=windows go build, go test -tags nogui ./internal/provider ./internal/gateway ./internal/plugin pass. yetone 2026-10-01 06:04:01 +08:00
  • e3b6397abe gateway: Kiro is sent the images a tool gave back, and tool names longer than 64 characters (#369) lyn.eulerandClaude 2026-10-01 05:37:11 +08:00
  • f1906bca6a gateway: draw and film through a Grok subscription (Grok Imagine) (#360) ArvakandClaude Sonnet 5.5 2026-10-01 04:59:22 +08:00