Files
magpie/internal
..
gateway: a gateway key can be held to some models (#882, LCorleone) — a key could reach every model magpie serves. A key now keeps an optional list of models (caller-keys.json "models", none = every model, so older files read as before): <provider>/<model> or <provider>/*, matched against the provider that serves the call, so a bare model name is resolved first and OpenRouter's "anthropic/x" is not Anthropic's. Such a key sees only its models in /v1/models, /v1/models/{id} and the Anthropic (Claude Desktop) and Gemini lists, a routing group only when every member is its; asking for another, by id, bare name or through a group with a member it may not use, is refused before any provider is asked with a 403 in the API's own error shape (permission_error) that names the key and its models, logged as a rejected call like an over-limit key's 429; a fallback it may not use is skipped; embeddings, images and videos it names are held too. magpie's own calls for it (a web search, a picture described, Codex titles with the model Settings name) are marked in their context, not by a User-Agent a client could send, and go on. Set it with the new badge on the key's row in the Gateway page (the app's menu: All models, every model of a provider, each model; sent when the menu closes) or magpie gateway-key models <id> openai/gpt-5 anthropic/* (all takes it off); gateway-key list has a MODELS column. The app's menu, fitting neither below nor above its button, now goes on the roomier side and scrolls instead of running off the window. en/zh/ja/de. TestGatewayKeyModelsHoldAKey (lists, 403s by id, bare name and group, Anthropic shape, fallback skipped, magpie's own call, a free key) fails without the change; TestModelPatterns, TestKeyModelsKept, TestGatewayKeyModelsCLI, TestCallerKeyModelsRoutes pass; gateway-key-models.test.cjs (Chromium and WebKit, en and zh: no native select, menu inside a 420px window, picks sent on close, All models, no scroll, no left border, 560px fits) passes with gateway-connect-keys, gateway-key-limit, gateway-caller-keys and click-scroll. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass.
2026-10-05 16:52:23 +08:00
gateway, gui: a provider's keys can be routed by weight, a key weighing 3 taking three requests for every one a key weighing 1 takes (dengtongcai, #841) — Routing already spread requests over a provider's keys in order, in turn, least used first or by pace, but never in a set proportion: two team keys meant to split 75/25 could only go evenly or one after the other. Routing now has By weight (routing "weight") for a provider with keys: each key that's on shows its weight and its share beside it in the editor (Weight 3 · 75%), a click edits it in place and posts keys/weight (provider.SetKeyWeight, 1 to 1000; none or 0 counts as 1, kept out of the file), and providers.json takes it as "weight" on each of keys (keyWeight for the first). The gateway picks as NGINX's smooth weighted round robin does (weighted.go): 3, 1 and 4 go round in every 8 requests in that ratio, never bunched; the others follow in their order for failover. A key resting after a failure is left out of the round while it rests, the others taking its share by their weights, and goes last. A conversation still stays with its key as Stays says (cache-warm across turns by default, within a turn always), and a request kept so is counted against the key that took it, not the one the round picked, so new conversations make up the share; Stays Off gives the ratio request by request. The weight goes with its key through Make first, the editor's Save, backups and WebDAV sync. The Routing page offers By weight for key pools and explains a pick by it; sink (Goes to the back) is off for it as for In turn. zh, ja, de strings. TestWeightedKeys (3:1:4 exact in each of 50 rounds of 8 through perKey and route; with one resting the other two 75/25 and the resting one last; a kept request made up by the next picks) and TestSetKeyWeight fail on the old code (no Weighted); key-weight.test.cjs (WebKit, en and zh: By weight shows the weights and shares, a click sets one and the shares redraw, Smart hides them, no scroll) fails on the old code (no By weight). key-import, panel-routing, routing-save, rate-limited-sink, routing-reset-deadline, account-arrange, gui-ja, gui-de and click-scroll pass on WebKit. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
2026-10-05 08:50:57 +08:00
sync: it can sync by itself less often or only when asked, and the server's setup can be restored over this computer's, undoably (Sun1090, #847: 同步太快了,需要设置个开关控制自动同步,还有恢复按钮) — the gateway's magpie synced every 3 minutes with no way to change it, and a free 坚果云 account is allowed only so many WebDAV requests (600 per 30 minutes, 1 GB up and 3 GB down a month); and there was no way to take the server's setup back over a computer's own, which the next sync would otherwise push up. Settings › Sync now has Sync by itself, the app's segmented control (no <select>): Off, 3 min (the default, as before), 15 min, 30 min, 1 hour; Off syncs only on Sync now, and the sync row says only when asked. The loop reads the setting each minute (no request), so a change takes effect without a restart, and a server limiting requests is still waited for as backoff says. Restore from the server asks first in the app's own dialog, naming the parts replaced; it reads the server's file as it is now, seals this computer's setup (keys and all) into the sync folder as <time>-before-restore (newest 5 kept), brings in each part that differs as a sync brings one changed only there, and leaves the state as such a sync would, so the next sync pushes nothing back. Its notice says what was restored, with Undo, which brings the kept setup back (the next sync then takes it to the others); OK, or a later sync's own notice, drops the undo. The form's Save keeps the setting (SetAuto alone sets it). CLI: magpie webdav|s3 auto off|3|15|30|60, restore, undo; the status says how often it syncs by itself. zh, ja, de strings. TestAutoSync (off: no request by itself; back to 15 min: one sync, then none; Save keeps it; 7 refused) and TestRestore (b's own changes replaced by the server's with no push, the kept copy holds b's provider and key, Undo puts it back and a gets it, nothing differing restores nothing) pass -race; sync-auto-restore.test.cjs (WebKit, en and zh: Off posts -1 and the rows say so, Cancel posts nothing, Restore and Undo post, no click moves the page) fails on the old code. sync-other-kind, sync-spin, s3-sync, webdav-settings-refresh, usage-computers, click-scroll, gui-ja, gui-de and settings-sections pass on WebKit. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
2026-10-05 10:36:46 +08:00
windows: an account list read while another magpie writes it is waited for, not taken for no accounts (Packing1 on Discord, Windows 11: 早上更新重启后之前登录的Antigravity订阅供应商和账号都没了 … 整行都没了,供应商里面antigravity直接消失了) — on Windows a file being renamed over (every write magpie makes: a temp file renamed onto it) is, for a moment of up to ~2ms, "The system cannot find the file specified" to anyone else looking at it, with MoveFileEx and with POSIX-semantics renames alike. filememo.Read gave that back as an error, readLogins as no accounts, and whatever change came next (any account's read-modify-write) wrote the empty list back: every built-in account gone, its subscription with it. Measured on Windows 11 (10.0.0.132): one process replacing a 200 KB file, another stat-ing and reading it, saw the file "not there" in a few windows each 20 s. Now steady.Stat and steady.ReadThere wait out a moment of a file not there (15 tries a millisecond apart, Windows only), and filememo looks through them, remembering a file found not there so one that really isn't costs the wait once. A magpie started by an update now waits for the one it replaces before its start-up moves write any file (AwaitPredecessor at the top of run, not only in the GUI). TestLoginsKeptWhileAnotherMagpieWrites (Windows: two writers replacing logins.json, readLogins never without the Antigravity account) failed 1 in 3 and 2 in 8 runs on the old code ("read as no accounts 8 times"), passed 11 of 11 on this; filememo's TestReadWhileReplaced the same (1180 misses on the old code); TestStatWaitsOutAReplace everywhere. A real update in a sandbox home on the Windows box, magpie-cli 0.1.880 → 0.1.942 from the release, kept the account and logins.json byte for byte. go vet (darwin, and windows nogui), builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
2026-10-05 10:57:45 +08:00
2026-10-05 12:59:32 +08:00
gui, proc: the Gateway card quits an older magpie serving the gateway and takes it over, or restarts the gateway (leslie_luo on Discord) — the card said an older magpie served the gateway and to quit it, with no way to tell which process that was. With an older magpie on the port it now has "Quit magpie {v} and take over": the process listening on the gateway's port is found (lsof on a Mac, /proc on Linux, Get-NetTCPConnection on Windows), its program path checked to be magpie's (magpie, magpie-*, dial, .exe/.old), asked to quit (SIGTERM, then SIGKILL after 5 s; TerminateProcess on Windows) and this magpie serves the gateway at once instead of within 15 s. Otherwise the card has "Restart gateway" ("Start gateway" when none runs); a magpie of this version or newer gets no button. Anything else on the port is never ended: a program that isn't magpie, one of another user or an administrator, one that won't quit, or a magpie a LaunchAgent/systemd unit/scheduled task starts again is said under the button with its path and pid, in en/zh/ja/de. TestGatewayFixQuitsOnlyAMagpie (copies of the test binary on a port of its own: one named other-server answering as magpie is left running, one named magpie is quit and the gateway is served here, then restarted) and gateway-takeover.test.cjs (WebKit and Chromium, en and zh, no scroll, no left border) fail without the change; TestListeningOn/TestIsMagpie pass on macOS and in a Linux container. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass.
2026-10-05 15:05:31 +08:00
gui, sessions: a moved subscription's other sign-in ways are a link away, ZCode's app's own account among them (Jinyu), and a session in a WSL distro is deleted as this computer's are (TJHHHH) — Jinyu asked to add a ZCode account from the one the ZCode app is signed in to rather than signing in again. The ZCode plugin (0.1.8) already does that (its "ZCode app's sign-in" reads ~/.zcode/v2/credentials.json), but a moved subscription's Add went straight to the plugin's first way (#777: one click), so it couldn't be reached. Now ZCode's site step shows its other ways beside the sites (the app's account, the GLM Coding Plan API key), and while a moved subscription's sign-in waits, Other ways to sign in… (when it has ways other than the one under way and Use an API key instead) cancels it and shows the plugin's ways; the owner's suggestion on #777 (suyu-ops). Factory, whose two ways are its device code and its key, still goes straight through with no extra link. No plugin release or mover change. With a sandbox copy of the owner's real ~/.zcode the plugin's way signed in (account, key, site zai). TJHHHH asked to delete sessions listed from WSL: they were resumable only, the page saying to delete them in WSL. Now they have their box and are deleted the same way: moved to magpie's trash over \\wsl.localhost (copied and removed across the device boundary), Claude Code's file history from the distro's ~/.claude, never this computer's; dropped from the distro's listing at once, which is listed again next; Restore puts the files back in the distro and lists it again. zh, ja, de strings; the WSL note's strings are gone. TestWSLSessionDeleted fails on the old code. On the Windows box against a sandbox folder in a running WSL2 distro (magpie-desktop), Delete moved the session, its subagents and its file history out of the distro into a %APPDATA% trash, and Restore put them back and listed it again (removed afterwards). plugin-other-ways.test.cjs (ZCode's ways at the site step and from the waiting box, Factory's one click) and sessions-wsl-delete.test.cjs (the WSL row's box, Delete in magpie's dialog posts its id, no note) fail on the old code and pass on WebKit in en and zh. sessions-folder-delete, sessions-manage, checkbox-look, plugin-moved-signin, gui-ja, gui-de and click-scroll pass on WebKit. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
2026-10-05 11:26:08 +08:00
windows: an account list read while another magpie writes it is waited for, not taken for no accounts (Packing1 on Discord, Windows 11: 早上更新重启后之前登录的Antigravity订阅供应商和账号都没了 … 整行都没了,供应商里面antigravity直接消失了) — on Windows a file being renamed over (every write magpie makes: a temp file renamed onto it) is, for a moment of up to ~2ms, "The system cannot find the file specified" to anyone else looking at it, with MoveFileEx and with POSIX-semantics renames alike. filememo.Read gave that back as an error, readLogins as no accounts, and whatever change came next (any account's read-modify-write) wrote the empty list back: every built-in account gone, its subscription with it. Measured on Windows 11 (10.0.0.132): one process replacing a 200 KB file, another stat-ing and reading it, saw the file "not there" in a few windows each 20 s. Now steady.Stat and steady.ReadThere wait out a moment of a file not there (15 tries a millisecond apart, Windows only), and filememo looks through them, remembering a file found not there so one that really isn't costs the wait once. A magpie started by an update now waits for the one it replaces before its start-up moves write any file (AwaitPredecessor at the top of run, not only in the GUI). TestLoginsKeptWhileAnotherMagpieWrites (Windows: two writers replacing logins.json, readLogins never without the Antigravity account) failed 1 in 3 and 2 in 8 runs on the old code ("read as no accounts 8 times"), passed 11 of 11 on this; filememo's TestReadWhileReplaced the same (1180 misses on the old code); TestStatWaitsOutAReplace everywhere. A real update in a sandbox home on the Windows box, magpie-cli 0.1.880 → 0.1.942 from the release, kept the account and logins.json byte for byte. go vet (darwin, and windows nogui), builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
2026-10-05 10:57:45 +08:00
gateway, gui: a provider's keys can be routed by weight, a key weighing 3 taking three requests for every one a key weighing 1 takes (dengtongcai, #841) — Routing already spread requests over a provider's keys in order, in turn, least used first or by pace, but never in a set proportion: two team keys meant to split 75/25 could only go evenly or one after the other. Routing now has By weight (routing "weight") for a provider with keys: each key that's on shows its weight and its share beside it in the editor (Weight 3 · 75%), a click edits it in place and posts keys/weight (provider.SetKeyWeight, 1 to 1000; none or 0 counts as 1, kept out of the file), and providers.json takes it as "weight" on each of keys (keyWeight for the first). The gateway picks as NGINX's smooth weighted round robin does (weighted.go): 3, 1 and 4 go round in every 8 requests in that ratio, never bunched; the others follow in their order for failover. A key resting after a failure is left out of the round while it rests, the others taking its share by their weights, and goes last. A conversation still stays with its key as Stays says (cache-warm across turns by default, within a turn always), and a request kept so is counted against the key that took it, not the one the round picked, so new conversations make up the share; Stays Off gives the ratio request by request. The weight goes with its key through Make first, the editor's Save, backups and WebDAV sync. The Routing page offers By weight for key pools and explains a pick by it; sink (Goes to the back) is off for it as for In turn. zh, ja, de strings. TestWeightedKeys (3:1:4 exact in each of 50 rounds of 8 through perKey and route; with one resting the other two 75/25 and the resting one last; a kept request made up by the next picks) and TestSetKeyWeight fail on the old code (no Weighted); key-weight.test.cjs (WebKit, en and zh: By weight shows the weights and shares, a click sets one and the shares redraw, Smart hides them, no scroll) fails on the old code (no By weight). key-import, panel-routing, routing-save, rate-limited-sink, routing-reset-deadline, account-arrange, gui-ja, gui-de and click-scroll pass on WebKit. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
2026-10-05 08:50:57 +08:00