Files
magpie/internal
TryWorldandTryWorld2026 116ca957cc agent: a provider's Compact at reaches Codex's on-disk catalog (#876) (#924)
Where Codex compacts a long conversation reached the gateway's /models
(provider.CodexListed sets catalog.Model.Compact) and Claude Code
(claude.go calls provider.CompactSet), but not the list magpie writes for
Codex on disk: magpieModels left Compact unset, so codexcat fell back to
the threshold for every model. A threshold the user set on a model or on
its provider did nothing while Codex read that list — the case where Codex
is not signed in to ChatGPT and model_catalog_json is what it reads.

magpieModels now carries it, as the gateway's list and Claude Code already
do. provider.CompactSetIn is compactSet for a caller holding the settings
and the group finder already, as EffectivePriceIn is for prices: this loop
runs for hundreds of models at magpie's start.

TestCodexCatalogCompactAtReachesDisk fails without the change (204800, want
100000) and passes with it: relay's "*" and then relay/glm-4.6's own
threshold reach the file, with the model's window kept as the maximum, and
the whole window comes back when they are cleared.

Co-authored-by: TryWorld2026 <tryworld@users.noreply.github.com>
2026-10-05 23:45:52 +08:00
..
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: a Claude subscription conversation the client sends back rewritten goes on in the run that had its last turn, and Claude Code's temp images go with their session (#912, margbug01) — Alma takes the [Context: …] block out of its last user message once the turn is over, the <alma_notification> block out of the end of the reply, and picks the tools for each turn, so turnKey, which hashes all of it, never matched and every turn started a new claude -p told the whole conversation, written to the prompt cache again with its images. ended keeps a looseTurn beside the key: the settings without the tools, the conversation before the last turn hashed, and the last turn's messages one by one. When the exact key misses, rewritten takes the one idle run or saved session whose looseTurn has the same owner, settings and earlier turns, the same roles, tool calls and results in the last turn, where a user message only had one part taken out (cutFrom) and the reply is a non-empty start of what the run said. Two that could be it start a new run, as does an idle run not told a tool offered now (offers) or a saved session one of whose tools is gone (toolsCover). Claude Code's <CLAUDE_CODE_TMPDIR or /tmp>/claude-<uid>/<work project>/<session> folder, where it keeps the images it was given, is removed with the session (removeSession), and the sweep removes those a stopped gateway left once their session file is gone and nothing in them changed for a day; CLAUDE_CODE_TMPDIR joins agentenv.Vars. Old images are still sent to a new run: replacing them would change what the model sees. docs/subsystems/claude-subscription-bridge.md is new. claude_rewritten_test.go has a case for each relaxation (failing before the change) and one for each rule that keeps another conversation out (each failing with that rule taken out). Tested for real with the owner's Claude Max account in a sandbox HOME: turn one asked with a [Context] block and two tools, turn two sent it back with the block taken out, the reply cut short and one tool, and went on in the same claude process ("goes on in its run", cache_read 1932). go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass.
2026-10-05 22:56:30 +08:00
awake, gateway, gui: Settings › General › Keep awake while agents work keeps the computer from sleeping by itself while agents work through magpie (xiao_wang24004 on X) — the computer went to sleep in the middle of an agent's task. With the new setting on (off by default, this computer's own: KeepOwn), the magpie serving the gateway holds the system's idle sleep off while a request is in flight or a Claude Code turn waits on its tool results, and for ten minutes after the last request ended (an agent running a build or the tests between requests), looking every 5 s, and lets go once idle or turned off. On a Mac it runs caffeinate -i -w <magpie's pid> (PreventUserIdleSystemSleep, gone with magpie), on Windows SetThreadExecutionState(ES_CONTINUOUS|ES_SYSTEM_REQUIRED) from a thread of its own, on Linux systemd-inhibit --what=idle:sleep (Pdeathsig; said once in the log and nothing else where it is missing). The display may still turn off, and closing the lid still sleeps. en/zh/ja/de. Tested with a real DeepSeek account through magpie web --gateway in a sandbox HOME: a streamed request had caffeinate asserting on behalf of magpie's pid in pmset -g assertions, and turning the setting off through /api/settings released it within a tick. TestKeep (held once while busy and within the grace, let go past it, when off and when Keep ends; -race) and TestKeepAwakeKeptAndOwn are new; keep-awake.test.cjs (WebKit and Chromium, en and zh: off by default, On/Off post keepAwake, another setting's save keeps it, no scroll, no left border, shown on a browser's page) fails without the change; lightweight and click-scroll pass in WebKit. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass.
2026-10-05 19:46:02 +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
library, imagemcp: magpie's own image server is given to agents in WSL, started through WSL's interop with the paths crossing over (#900, cjyrainbow) — on Windows the Library names magpie-image by this machine's binary ('D:\tools\Magpie\magpie-windows-amd64.exe' mcp image), and turning it on for "Codex · WSL Ubuntu" or "Claude Code · WSL Ubuntu" failed: a Windows program was refused for every agent in WSL. WSL's interop starts a Windows .exe from inside the distro, so magpie's own server (args mcp image, whatever its binary) is now written for an agent in WSL by the binary's path in the distro, asked of the distro itself (wslpath -u, so a custom automount root is followed; asked once in ten minutes; /mnt/<drive>/ when it can't be asked), with --wsl <distro>, --mount where the drives aren't under /mnt/, --home the distro's $HOME, and WSLENV naming the variables it is given (MAGPIE_ADDR), which a Windows program otherwise doesn't get. A distro whose interop is off (no WSLInterop in binfmt_misc) is told so: turn it on in /etc/wsl.conf. A user's own Windows program is still refused, as before: its paths and variables wouldn't cross over. A Linux magpie in the distro isn't looked for: the Windows one reaches the gateway at 127.0.0.1 and reads this machine's settings, the same version as the app, which a Linux one would need the gateway's address from inside WSL for. With --wsl, magpie mcp image opens the paths the agent gives (its roots, reference images, where to save, ~/) as the distro's — /home/me/x through \\wsl.localhost\<distro>\home\me\x (\\wsl$\ where that isn't), /mnt/c/x as C:\x — answers the saved paths as the distro's (/home/me/app/generated-images/…, /mnt/c/…), and with no project falls back to the distro's ~/Pictures/Magpie. Tested on the Windows 11 test box: in a WSL2 Ubuntu 24.04 distro the library's probe gave /mnt/c/…/magpie.exe and interop:on, and this build, started from the distro as the library writes it (the .exe's /mnt/c path, mcp image --wsl, MAGPIE_GATEWAY through WSLENV to a stand-in gateway in the distro), answered initialize and generate_image with a root of /home/yeton/m900proj: a relative path and an absolute reference image (sent as /v1/images/edits), ~/m900proj/home.png and /mnt/c/… were each saved there and answered as the distro's paths. TestWSLMagpieImage (the /mnt/d command, --wsl, WSLENV, nothing rewritten or asked again at the next sync, a custom mount, interop off as a problem) and TestLinuxHome in internal/library, TestWSLPaths in internal/imagemcp are new and fail without the change; TestWSLServers (npx.cmd still refused) passes. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass (internal/provider failed once under load and passes alone).
2026-10-05 20:09:34 +08:00
plugin, gateway, gui: a plugin can be gateway middleware — onRequest, onEvent and onResponse run in magpie's gateway on what agents send and get back (moejs) — a plugin could only be a subscription provider, run in the Bun host, a process away from the gateway. A plugin may now declare middleware (package.json magpie.middleware, or a *.middleware.js / .mjs file), run in-process by moejs (pure-Go JS engine): compiled once, one runtime per request from a pool, so every hook of a request shares ctx.state. onRequest(body, ctx) sees the agent's body in its own API before routing; what it returns is the body the next middleware and the gateway get, undefined leaves the bytes as they were, ctx.reject(status, msg) answers with an error in the agent's API's shape. onEvent(ev, ctx) sees each SSE event's JSON (export const events = [...] filters by name; an object changes it, null drops it, undefined passes the bytes through untouched; data: [DONE] is no JSON and passes by). onResponse(body, ctx) sees a whole JSON reply, with ctx.status. ctx has protocol, model, stream, path, agent, the plugin's options and state. A hook that throws, returns a non-object or runs past its limit (250ms request/response, 50ms an event, 1s load; interrupted) leaves the request or reply as it was and is counted and logged (60 lines a minute); a compressed reply passes as-is. Files are reloaded within a second of a change. Only agents' requests run through it (serveAgent at the gateway's agent entry points and the Codex backend), never magpie's own titles, classifier or stand-ins; it sees bodies before redaction. A middleware-only package is skipped by the Bun host. The Plugins page marks a middleware with a chip, its hooks, calls and µs per call (JSON in and out counted), failures in red with the last error, and a load error. en/zh/ja/de; docs/reference.md and usemagpie.ai/docs/plugins#middleware (en/zh/ja) with an alias example. Tested with a real DeepSeek account through magpie web --gateway in a sandbox HOME: an alias middleware served "fast" and named it in message_start, a reject came back as 403 permission_error, a Chat stream uppercased; real Claude Code (sandbox CLAUDE_CONFIG_DIR) did Read, Bash and Glob tool turns through three middlewares with 0 failures: ≈1–1.6µs an event, 0.2–0.7ms an onRequest that parses and rewrites Claude Code's ≈80KB body. Real use found two bugs fixed here: [DONE] parsed as JSON (SyntaxError on every Chat stream) and stats leaving out the JSON time. internal/middleware tests (chain, undefined keeps bytes, reject, fail-open on throw/spin/string with the runtime reused, async hook, load errors, off and Bun skip, reload, split/CRLF/filtered/dropped stream events, [DONE] untouched, whole reply and gzip, 64 in parallel; BenchmarkEvent ≈0.9µs, 11 allocs) and TestMiddlewareThroughGateway (alias routed, events by protocol, redaction still upstream, reject in Anthropic's shape; fails without the change) are new; plugin-middleware.test.cjs (Chromium and WebKit, en and zh: chip, hooks and stats, failures with last error, broken and off, no left border) fails without it. The other plugin GUI tests and click-scroll pass in WebKit. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass (internal/provider failed once under load and passes alone).
2026-10-05 19:14:31 +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
update, gui: a new version Windows won't start is never left in place — tried before it goes in, and a restart into one that doesn't say it is up puts the running one back; that version isn't downloaded again by the clock, and Settings › About › Version says why (#894, hisiling) — on Windows 11 with Smart App Control on, the update put the new unsigned exe in place of the running one and restarted into it; CodeIntegrity (3033/3077) refused it, magpie never started again and the working version sat in .old. Now, on Windows, the downloaded exe is first started suspended with no window and ended at once (Windows checks a program when its process is created), and one refused stays out: the running exe isn't moved. At the restart the new process is given MAGPIE_STARTED, a file it writes first thing (AwaitPredecessor); the old one waits up to 20s for it and, if the start fails (ERROR_SYSTEM_INTEGRITY_POLICY_VIOLATION 4551, 1260 or any other), the new process quits before saying so, or it says nothing in time (then it is ended), puts the running exe back over the new one (the new one renamed .blocked if it can't go) and keeps running instead of quitting; nothing notes it as updated in the app. The version is noted in the config folder (update-blocked.json, this machine's only, never synced; gone once a version at least as new runs); the clock's checks leave it alone and show why, a Check clicked downloads and tries it once more. Settings › About › Version: for an application control policy, "0.1.x didn't start on this computer: Windows' Smart App Control (or another app control policy) blocked the new version, which isn't signed. magpie stays on 0.1.y. Download it and run it yourself, or turn Smart App Control off and check again", else the reason, with Download and Check; the error in full on hover. en/zh/ja/de. A user who double-clicks the refused exe is refused by Windows before magpie runs; only signing helps there. TestRelaunchRefusedPutsBack (4551 at the start), TestRelaunchSilentPutsBack (no word in time, killed; quit at once), TestRelaunchReportedStays, TestReportStarted, TestBlockedNote and TestUpdateThatWontStart (the restart doesn't quit, the note, the clock not downloading it again, a Check that does) are new and fail without the change; on Windows TestInstallBinaryRefusedStaysOut, TestTrialStart and TestStartCheckedPutsBackForReal (real processes: not a program, quits at once, runs on silent) pass on the Windows 11 test box with the whole internal/update package (Smart App Control itself isn't on there, so the refusal is simulated). update-blocked.test.cjs (Chromium and WebKit, en and zh: the reason and both versions, full error on hover, Download opens the release, Check downloads, the other reason, no select, no scroll, no left border) fails without the change; update-check, update-mirror, update-auto, update-wait and update-download-percent pass in WebKit; click-scroll's request-list case fails now and then the same on main. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass.
2026-10-05 19:43:31 +08:00