Commit Graph

  • 5604c411a9 usage, gui: Usage › Requests' All opens at once after magpie has been up a few seconds, and in half the time before (the owner) — Requests on All stayed a skeleton for 39 s on the owner's magpie right after it started: the first All parses every session file (12.3k here: 11k Codex sessions, 1.2k Claude Code projects' files), one after the other, each a wait on the disk. Those a query has to parse are now read 8 at a time ahead of the loop that takes them in order (prefetchSources), at most 32 waiting, so the history is never all held at once; and magpie reads the whole history in the background once it is up, right after the Sessions page's own warm-up and not beside it on the disk, so the page opens on the kept index. Tested in a sandbox HOME on a copy of the owner's real ~/.codex and ~/.claude history (magpie app, dev UI): the first All right at startup took 10.6 s before and 4.6–4.9 s now; opened 20 s after startup it took 10.0 s before and 0.01 s now; later Alls 0.3–0.4 s as before. TestPrefetchSources (each source handed back as its own, read in parallel, no more than the window ahead, every one read once, an unwanted one read when asked) is new. go vet, go test -race ./internal/usage, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. yetone 2026-10-05 20:18:22 +08:00
  • c426994196 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). yetone 2026-10-05 19:54:43 +08:00
  • 9e13f18fb9 gateway, provider: a request turning reasoning off stops Factory's DeepSeek V4.1 Flash, Kimi K3 and GLM-5.2 thinking (#899, tedestar) — they offered only low/high/max (GLM-5.2 high/max), so an agent's none (or Claude Code's thinking disabled) was asked for low, or nothing, and Fireworks thought anyway. droid 0.231.0 lists off for them and sends reasoning_effort "none"; through OpenRouter pinned to Fireworks, none came back with 0 reasoning tokens on DeepSeek V4.1 Flash (2 completion tokens; 19 at the default, 16 reasoning at low) and Kimi K3 (8; 71 at the default), and on Baseten's GLM-5.2 (2; 114 at the default), while GLM-5.3 and GLM-5.3-Flash turn none away ("Reasoning is mandatory for this endpoint and cannot be disabled") and stay at low, their least (Qwen3.8 Max and MiniMax have no off in droid either). The three now have none among their levels, in the built-in and in @magpie-community/opencode-factory-auth 0.1.17, which the mover's min is raised to; and a translated request that turned reasoning off (Effort reads it as low) is asked for none on a model with none among its levels, whatever the provider, else as before (a refusal still falls back to the lowest level on). No Factory account was at hand to send through Factory itself. TestFactoryThinkingOff (the built-in: chat, Responses and Anthropic's disabled; GLM-5.3 still low) and TestPluginThinkingOff (the plugin host, a model whose variants have none and one without) are new and fail without the change. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass (TestSavedButSignedOutClaudeServed timed out once under load and passes alone). yetone 2026-10-05 20:01:32 +08:00
  • 15102c809f provider: a ZCode account's spent gift bucket doesn't hold up a model another of its plans still has (ganlerk on Discord) — an account with ZCode Trust Build (GLM-5.3-Flash 14.5% used) and the Start Plan (GLM-5.3-Flash all used) read GLM-5.3-Flash, and so the account, as 100% used: every bucket's window counted its model, and routing takes the fullest. The server picks the bucket (ZCode sends no plan with a request), so a model now counts on one bucket, a live one before a spent one, the least used; a bucket no model counts on stays on the card set aside. The plugin does the same from 0.1.10 (magpie-community/plugins 6b13dc0), and the mover's min is raised to it. TestZCodeStartSpentSiblingAside fails without the change. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. yetone 2026-10-05 19:57:18 +08:00
  • 3fad3d21eb agent: Cline's advice on Windows names both the desktop app and the CLI/VS Code extension (#898 follow-up) — since #898 Running answers yes on Windows, where it can't ask, so Cline's first branch (its desktop app) always won there and one using the VS Code extension was never told to reload its window. On Windows the advice now says both. TestDshClineAdviceOnWindows (run by the Windows CI job) checks it; go vet (darwin, GOOS=windows) and go test ./internal/agent under a temp HOME pass. yetone 2026-10-05 20:02:08 +08:00
  • bf9bd9c935 agent: an agent's "restart it" advice reaches Windows, where a model picked in magpie looked like it had done nothing (#898) wizzy-yangandwizzy-yang 2026-10-05 19:59:34 +08:00
  • d7a8ddc84d gateway: a key held to some models counts no tokens and decides on none it may not use (#882, LCorleone) — counting tokens asked a provider's help or estimated locally whatever the key was held to, and System One asked its decision model of any key. Both now meet the hold the model endpoints do: count_tokens (Anthropic's shape) and Gemini's :countTokens — which the gateway answers itself, so no shared path covered it — resolve the model named and are refused with a 403 in the API's own error shape when the key may not use it, a group judged by its members, a bare group's name and an auto group's stand-in resolved first (countHeld in key_models.go); System One is held to the decision model its prefix names, after RouteDecider. TestGatewayKeyModelsHoldCountTokensAndSystemOne (403s by id, bare name and through a group on both counting APIs, Anthropic's and Gemini's error shapes, a held count or decision reaching no provider, an allowed model counted, a free key going on, the decider among the key's models) fails without the change. (#902) july_lu 2026-10-05 19:59:28 +08:00
  • 3c7ceb68f4 library, gui, cli: skills can be given to the agents as copies rather than links, for every agent or one (#896, ADabbler) — the Library linked each skill into the agents (a copy only where it couldn't link: Windows without the right to make links, WSL, Claude Desktop), and some want each agent to have a folder of its own. Skills › under the list, "Give skills as: Links | Copies" sets the library's way (library.json copySkills), and By agent gives an agent its own way (skillHow: Library's way / Links / Copies; Claude Desktop and WSL only ever take copies and have no switch). A copy is the whole skill folder with the .magpie-library marker, made by the same swap as before (.name.magpie-next, the old one set aside), so it is known for magpie's: taking a skill away removes magpie's copy and never a folder of the user's by that name. Switching makes each enabled skill again in place: a link becomes a copy; a copy becomes a link (made beside it and swapped in; a copy edited by hand is kept with the backups first). Where a link can't be made (Windows without the right) it stays a copy, as before. A copy follows the library's skill on every sync (at start, and after any change in the Library) as before; one that differs now (the library's folder edited by hand) is marked "Copy out of date" on the skill, saying in which agents, and a click copies it again. CLI: magpie library skill how link|copy [agent], magpie library skill how default <agent>. en/zh/ja/de. TestSkillHow (temp HOME: links at first, bad ways refused, copies marked, Read's how and copySkills, behind after a hand edit and cleared by a sync, codex its own way, an edited copy kept in the backups, back to the library's way, the user's own same-name folder and own skill untouched both ways, a copy removed with its skill) fails without the change; TestSkillCopyFollowsTheLibrary and TestSkillCopySwapSurvivesAnUndeletableFile now ask for copies rather than leave a copy where links are given, which is made a link again now. skill-how.test.cjs (WebKit, en and zh: the bar, Links/Copies posted with the note and status, the out-of-date tag with its agents and a click syncing without opening the skill, By agent's sheet with Claude Desktop's copies-only row and no switch, an agent's way posted and back to the library's, By agent (1), no select, no scroll, no left border) fails without it. Every Library and skill GUI test 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. yetone 2026-10-05 19:42:25 +08:00
  • ae23ad83f7 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. yetone 2026-10-05 19:45:59 +08:00
  • 9ca64d104f 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. yetone 2026-10-05 19:43:10 +08:00
  • eebd0b4222 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). yetone 2026-10-05 19:14:28 +08:00
  • eafb1845e8 agent, gui: Codex's row has a square for the model it writes its memories with (Yc on Discord) — Codex sums each thread up with gpt-5.6-luna and consolidates its memories with gpt-5.6-terra (medium), whatever model it runs, and nothing in magpie could change that. Codex reads both from config.toml's [memories] extract_model and consolidation_model (codex-rs/config/src/types.rs; the defaults are memory_extraction_preferred_model / memory_consolidation_preferred_model in model-provider). Codex's row now has a memories square beside the subagents' one, opening the app's model picker: a model picked writes both keys, Default removes them (and a [memories] table left empty), the user's other [memories] keys stay. A model of magpie's is offered and taken only while Codex runs through magpie, as for subagents, and is removed when Codex steps out of magpie or is disconnected, since Codex could no longer find it. Unset, its title says what Codex uses. en/zh/ja/de. TestCodexMemoriesModel and TestCodexMemoriesUnwire (temp HOME) fail without the change; codex-memories.test.cjs (WebKit, en and zh: the square and its unset title, no square with nothing to pick, the model picker with Default first, a pick posted and lit, Default posts "", no select, no scroll, no left border) fails without it. subagent-effort, click-scroll, codex-titles, agent-start-pick and desktop-tiers pass in WebKit. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. yetone 2026-10-05 19:11:34 +08:00
  • 5b7e82f6fe gui: Settings › Web search's "search for other models" lists a provider's small model once, without "via magpie" (Player on Discord) — Z.ai showed GLM-5.3-Flash twice, "GLM-5.3-Flash, its small model" (value z.ai's id: magpie's small pick) and "GLM-5.3-Flash" from its model list (zai/glm-5.3-flash), and Player asked what told them apart. The gateway searches just the same with either (chosenSearcher: "<pid>" is searcherModel(p), "<pid>/<model>" that model when searchable, else the small one), so the model row equal to the small model is no longer listed; a searcher saved as "<pid>/<small>" ticks the small row (it keeps that value, so opening and closing posts nothing) and the button still names the model. The model rows' "via magpie" tag came from pathTag, given a ref and the picker's placeholder agent; no agent asks for these, so no row has it now. searcher-pick.test.cjs (WebKit, en and zh: each small model listed once, no via magpie badge, a pinned small model ticked with its name on the button, the relay picked posts "relay") fails without the change; click-scroll, search-api and provider-search 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). yetone 2026-10-05 19:11:06 +08:00
  • 390db187c1 docs: introduce subsystem references and semantic change review (#889) Junjie Zhou 2026-10-05 19:10:38 +08:00
  • 39af4adda5 gateway, gui, cli: each account or key can have its own limit on requests at once, with a bounded queue that turns requests away with a 429 (#892, margbug01) — a provider's Concurrency applied to every key and account alike, the queue past it had no bound and no wait, and nothing showed what ran or waited. A key or account now may have its own limit over the provider's (Provider.AccountConcurrency, by lowercased account or KeyID; 0 none; unset takes the provider's, as before), counted in one lane across every model, routing group and agent. A provider's Queue size bounds how many wait for each of its lanes (one more is turned away at once) and Queue wait how many seconds one waits (then it leaves the queue); both 0 by default, as before. A request turned away goes on to the next key, account or group member without resting anything, else gets a 429 in its API's own shape (Anthropic rate_limit_error, OpenAI's, Gemini's) with Retry-After: 1, saying the limit and the queue. A slot is given back when the reply is read to the end, fails or the agent goes away; a waiter that goes away leaves the queue, and one granted as it gave up hands the slot on. In a routing group, or among a provider's keys and accounts, a full one gives way once to another with a free slot now, so a group spreads before anyone waits; a fallback provider is never asked for want of a slot, and with all full the request waits for the one routing chose. The Claude subscription path and plugin providers take their slot the same way, around the attempt; a parked Claude Code tool turn has already answered and holds none. GUI: each account and key row has a pill — "1/1 running · 2 queued" while requests are out (GET /api/lanes, read every two seconds and written into the pill in place), else its limit; none set shows on hover only — opening the app's menu (provider's, no limit, 1/2/3/5/10, Other… typed in place), posting provider/accountconcurrency; the editor has Queue size and Queue wait under Concurrency. CLI: magpie provider account-concurrency <id> [account|key [n|off|default]] and magpie provider queue <id> [size [seconds]]. en/zh/ja/de. TestAccountLimitSharedAcrossGroups, TestQueueFullAndWaitedOut, TestLaneReleasedOnFailure and TestFullMemberGivesWay (pass with -race, with TestMaxConcurrency and TestLanesLimitLifted), TestLaneLimitRoutes and TestAccountConcurrencyCmd are new and fail without the change; lane-limit.test.cjs (WebKit, en and zh: a key's busy/queued pill updated in place from /api/lanes, set from the menu, provider's posts null, Other… typed, an account's own limit, none on hover only, queue fields saved and refused out of range, labels on one line, every string in zh/ja/de, no select, no scroll, no left border) fails without the change. key-weight-fit, account-cap, provider-concurrency, click-scroll, account-models and api-key-usage pass in WebKit; the other WebKit failures fail the same on main. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. yetone 2026-10-05 18:53:02 +08:00
  • dab887db57 update, gui: Settings › About › Download source sets the update mirror, and a download that failed through it offers GitHub (#893, M3chD09) — the mirror updates could come through was only magpie update mirror in a terminal, and the GUI never said where updates came from or that a failed download was the mirror's. About now has a Download source row: GitHub, or the mirror set (the same UpdateMirror setting the CLI writes); Use a mirror… takes a full https:// address (http, a bare host or a malformed one refused, with the reason in the row and the draft kept), Change edits it, Use GitHub takes it away in one click; Enter saves, Esc cancels, no click scrolls. POST /api/settings/update-mirror sets it; the Settings page's own save keeps it. A download through the mirror that doesn't answer, answers other than 200, or sends a file whose SHA-256 isn't usemagpie.ai's feed's is an update.MirrorError (never installed, the file removed, as before); /api/update's error carries the mirror, and the Version row says the download failed through that mirror and offers Download from GitHub, which takes the mirror away and downloads again. magpie names no mirror or CDN of its own. en/zh/ja/de. TestMirrorFailuresAreTheMirrors (tampered, 404 and closed mirrors each the mirror's, no file kept; a direct failure no mirror's; CheckMirrorURL) and TestUpdateMirrorSetting (set trimmed, http/bare/ftp/empty-host refused, Settings save keeps it, off, an updater failing through a 404 mirror reports it with retry, a new check clears it) are new; update-mirror.test.cjs (WebKit, en and zh: GitHub shown, http refused with the reason, https saved with Enter, Use GitHub posts "", a failed download through the mirror offers GitHub, which posts "" then update/install; no select, no left border, no scroll) fails without the change; update-auto, cli-path, cli-behind, settings-sections 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. yetone 2026-10-05 18:49:04 +08:00
  • b9d20856ef gateway, gui: a gateway key can be held to routing groups (Magic_zero on Discord) — a key's models menu on the Gateway page listed providers' models only, so a key couldn't be limited to the routing groups an agent was meant to use. The menu now lists the routing groups first, with Every routing group (group/*), and a key may keep group/<id>. A key that names a group may use it with every member in it, the next member too when one fails, but not those members asked for by name, a group it doesn't name, or a fallback outside the group; a group named inside one it doesn't name counts for its own members. A group the key doesn't name still passes only when it may use every member, as before. /v1/models and the Anthropic and Gemini lists show the key its groups. en/zh/ja/de; docs/reference.md. TestGatewayKeyModelsNameAGroup (group/both lists and serves only the group, falls to the next member, members by name and other groups refused; group/* every group; a nested named group; a member's own fallback outside the group refused) fails without the change; TestCallerKeyModelsRoutes lists the groups. gateway-key-groups.test.cjs (WebKit, en and zh: groups first after All models, Every routing group, a group picked sends group/coding, a held key's group ticked, no select, no scroll, no left border) fails without the change; gateway-key-models, gateway-key-limit 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. yetone 2026-10-05 18:44:21 +08:00
  • f3c05b5ed8 tui, cli: a plugin's account whose model list failed says why on the Providers page and in magpie providers (gnayiab on X) — v0.1.1001 had the app's editor say why Cursor's plugin account listed Auto alone, but gnayiab runs magpie's TUI in WSL, where the Providers page still showed "1 models" and nothing else (only m, asking again, said why). Each provider's row now ends in "couldn't list its models: <why>" (red, as other faults there) when provider.ListError has one, and magpie providers says the same in amber after the row. TestProvidersSayWhyAListFellBack fails without the change. go vet, go test -tags nogui ./internal/tui . under a temp HOME, and go build (GOOS=linux nogui, GOOS=windows) pass. yetone 2026-10-05 18:44:12 +08:00
  • 1534382ff9 gui, web: gateway mode — magpie web on a server that is only a gateway shows no Agents, Sessions or Library (Player on Discord) — a magpie web run on a server or NAS for other machines' agents showed the Agents, Sessions and Library pages and the settings written into this machine's agents, none of which do anything there. In gateway mode the page has Providers, Gateway, Routing, Usage, Plugins and Settings; an address kept for a page left out opens Providers. Settings leaves out provider in model names, Codex subagents, long conversations, Codex thread titles, the usage/balance alerts and reset reminders, on top of the tray, open at login, Dock, text size and lightweight rows magpie web already hid; Language, theme, currency, proxy, redaction, vision/image models, sync, updates and the rest stay. magpie web is in it by itself when it finds no agents on its machine (the Docker image, a NAS) and with the new magpie web --gateway; Settings › General › Gateway mode (Automatic / On / Off, saying why it is on) wins over both and Off brings every page back at once. The choice is this machine's own (KeepOwn: never synced or restored from another computer), set only by POST /api/settings/gateway-mode, never by the Settings page's save. The app's own windows are never in gateway mode. boot.js tells the page before it paints. en/zh/ja/de; docs/reference.md (Docker › NAS, CLI list). TestGatewayMode (temp HOME: no agents → on, agents → off, --gateway → on, Off wins over --gateway, the Settings save leaves it, On with agents, Automatic, an unknown mode refused, the app window never told, KeepOwn) is new; TestSettingsSaveKeepsWhatItDoesNotSend sets it. gateway-mode.test.cjs (WebKit, en and zh: the tabs, ?view=agents/sessions/library open Providers, the switch and why, the seven rows hidden, Off posts "off" and brings tabs and rows back with Settings kept open and Agents opening, Automatic hides them again, the Settings save never sends it, no scroll, no select, no left border; web with agents and the app window keep every tab, the switch is magpie web's alone) fails without the change. mobile-web's desktop comparison differs from origin/main on Settings only, by the new row. settings-sections, settings-groups, click-scroll, cli-path, plain-names, codex-titles and agent-grip pass in WebKit. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. yetone 2026-10-05 18:06:37 +08:00
  • 8339e82c08 gui: a weighted key's name is no longer squeezed out, and First / Make first stay on one line (#841, dengtongcai) — with a relay's routing set to By weight, each key's row gained a weight chip, and the row's hover-only controls (All models, the protocol picker, Remove) kept their full width while hidden, so the key's name was squeezed to "…" or to nothing, and "首选" / "Make first" broke over two lines in a narrow window. In a key's row these controls now take no room until the row is hovered or they have focus or are open (max-width 0); on hover the room comes from the row's free space, else the masked key's, and the All models badge gives its own up first. First and Make first never wrap. Nothing else in the row moves, and a click still scrolls nothing. key-weight-fit.test.cjs (WebKit, en and zh, 900/640/520/440px: the name whole, the masked key whole where it has room, everything inside the card, First and Make first on one line, Remove whole on hover, no row overflows, no left border) fails without the change (en 900, 520, 440 and zh 440). account-models and api-key-usage now hover a key's row before using its All models / Remove, as a user does. key-weight, account-arrange, account-cap, account-return, gateway-key-*, key-import, routing-*, click-scroll and the rest of the account-row GUI tests pass in WebKit. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. yetone 2026-10-05 17:49:57 +08:00
  • d6daa40ce3 update, gui: Settings › About › Command line says what magpie runs in a new terminal and adds it (PAMI on Discord) — the app downloaded from the site, and every Windows one, left terminals without the magpie command, and nothing in the GUI said whether it was there. A Command line row now asks each shell installed (zsh, bash, fish and the login shell; PowerShell and cmd on Windows) for its PATH and says, with a dot each, whether its magpie runs this app, another copy (with that copy's version, read from its .app's Info.plist or Homebrew Cellar folder, never by running it), or none. Add to PATH links this app's program (Homebrew's opt link, the AppImage) into the folder on the default shell's PATH whose magpie runs first, else ~/.local/bin or the like when on PATH, replacing another copy there as install.sh's ln -sf does; on Windows it puts the app's folder first in HKCU\Environment Path and broadcasts WM_SETTINGCHANGE. A shell whose PATH lacks that folder gets its own button naming its profile (~/.zshrc, ~/.bash_profile on the Mac or ~/.bashrc, fish's config.fish), which links into ~/.local/bin and adds that folder with one marked line, once; no profile is written otherwise. A translocated or disk-image app is told to move first and gets no button. It says new terminals take the change (on Windows: new PowerShell/cmd windows, restart Windows Terminal). GET/POST /api/cli; en/zh/ja/de. TestCLIOnPath (temp HOME, stubbed shells: link, fish profile once, a shadowing copy replaced, no folder → zsh's profile kept and appended, an unknown shell refused, plist version, translocated refused) and TestPathFirst are new; cli-path.test.cjs (WebKit, en and zh: per-shell dots, Add posts {} and turns green, fish's button posts fish alone, Windows lists PowerShell and cmd, translocated gets no button, no scroll, no select, no left border, translations) fails without the change; cli-behind and update-auto pass. Not tried on a real Windows machine. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass (TestOTelWholeWaterfallFallback and TestDevinSignedInOnlyInMagpie failed once each under load and pass alone). yetone 2026-10-05 17:47:52 +08:00
  • 5076bc1777 gui tests: the member row's Remove is wheeled into view as a reader would yetone 2026-10-05 17:41:08 +08:00
  • afc6623ce5 gui: a routing group's members say whether they see images (#885) wizzy-yangandwizzy-yang 2026-10-05 17:42:25 +08:00
  • 358cfbae79 gui: the narrow editor's labels are over their fields, and Add is reached (#890) wizzy-yangandwizzy-yang 2026-10-05 17:42:04 +08:00
  • 4788cf2bbd plugin, gui: a plugin's account whose model list failed says why, rather than listing Auto alone (gnayiab on X) — with magpie in WSL, Cursor's plugin account offered only Auto: the plugin's models hook couldn't get the account's list and magpie, with no list kept from before, showed the plugin's defaults with nothing saying so, its editor even calling them "vendor list", and Refresh answered "1 models". The host now keeps why a models hook fell back (what it threw, else how its last fetch failed) for the provider and each account; plugin.Cached carries it with the accounts it reads again from the sign-ins; provider.ListError gives it for a plugin's account. The editor says "<provider> couldn't list its models: <why>" under the models (amber, no left border) and no longer calls the defaults the vendor's list, and Refresh fails with it. A list kept from before, or one that comes in, says nothing. en/zh/ja/de. Tried with the real Cursor plugin (0.1.9) in a sandbox: a key Cursor refuses shows "Cursor couldn't list its models: Cursor didn't take this API key" and Refresh says it; the owner's real account lists 72 models with nothing said, and Claude Code -p through the gateway ran a Bash tool on cursor/auto, composer-2.5 and claude-opus-5-thinking. The plugin itself, in WSL (magpie-desktop), listed 254 models and streamed chats with tools. TestFallenBackListSaysWhy (hook returns its defaults, throws, or hands back its own) fails without the change; plugin-list-error.test.cjs (Chromium and WebKit, en and zh) too; TestFallenBackListKeepsTheLastOne, account-models, plugin-signin and click-scroll pass. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. yetone 2026-10-05 17:37:09 +08:00
  • a45b3e0986 provider: TestCloudflareDecideModelsTested's server keeps what it was asked under a lock — Workers AI's Jev and Clef are asked at once, and the test server's handler wrote one map from both requests, which failed the provider package now and then (concurrent map writes) while the full suite ran. go test -race -count=5 -run TestCloudflareDecideModelsTested and go test -tags nogui ./internal/provider/ under a temp HOME pass; go vet passes. yetone 2026-10-05 17:38:00 +08:00
  • 9bfb7a5870 fix(gui): keep the full-screen window's title bar when the Dock is set to Hide lainbo 2026-10-04 15:58:37 +08:00
  • 585f7f7dfa sessions, gui: Codex sessions made with another provider are moved to Codex's own from the Sessions page, and back (#887, cjyrainbow) — Codex's history lists only the sessions of the provider it uses now, so sessions made through CC Switch (model_provider "custom") vanished from it once Codex used magpie, though their files were still there and the Sessions page listed them. The Codex tab now reads each session's provider from its session_meta (cached with the rest) and the one Codex in that session's home uses (config.toml's model_provider, or its profile's; "openai" when none), says how many were made with another and which, and tags each. "Move to <provider>…" opens magpie's own dialog listing them, each ticked; Move rewrites only the model_provider string in each session's files (.jsonl and .jsonl.zst, in its own Codex home, so WSL's too, mtime kept) and in Codex's state_*.sqlite threads row: messages, id, title and archive are left as they are. A copy of the files goes to magpie's trash folder (trash/codex-provider, with a move.json) first, a failed rewrite is put back from it, and a session written to in the last minute is left alone and said so. Undo moves each back to the provider it had, byte for byte. Nothing is moved by itself. POST /api/sessions/codex-provider; en/zh/ja/de. TestMoveCodexProvider (listing, bytes, mtime, the DB row and nothing else, backup, byte-identical undo, an active session, a bad id), TestMoveCodexProviderPacked and TestCodexProviderIn fail without the change, as does sessions-codex-provider.test.cjs (WebKit, en and zh: magpie's dialog, Cancel posts nothing, an unticked one stays, Undo, no click moves the page, no left border, translations); sessions-manage, -toolbar, -purge, -folder-delete, -wsl-delete and click-scroll pass. Not tried against a real CC Switch history. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. yetone 2026-10-05 17:22:15 +08:00
  • f55a13f23e gateway: a translated reply cut off mid-stream goes on where the upstream has a real prefill (#853) sundjly 2026-10-05 17:25:15 +08:00
  • 55be06ea24 gui: whether "· routing group" follows a group's name is set over the groups on Routing, for all of them, not in one group's editor (PAMI on Discord) — #868 put the agents' lists names setting (Settings' Provider in model names) in each group's editor too, where it read as that group's alone though it is one setting for every group and every provider's models. It now sits over the groups, under Find groups on their own, with its three choices and what it sets; a group's editor says only what its group will be called and that "· routing group" is set above, and follows a pick there with its unsaved name kept. Provider Names & levels keeps its choices. name-suffix-where-named (WebKit, en and zh) fails without the change; group-found-off and group-fold follow the new row. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass (internal/provider crashed once on a concurrent map write in TestCloudflareDecideModelsTested's test server, and passes alone). yetone 2026-10-05 17:22:01 +08:00
  • 6fe8169502 gateway, agent: Codex draws with its native image tool through magpie (#870) maYandyetone 2026-10-05 17:19:42 +08:00
  • f506d08d28 usage, gui: the Requests tab ranks one model at each provider it went to (inaction on Discord) — the trend's ranking already gave each model its speed, first token and requests, but "Model" put a model's requests at every provider together, so glm-5.3 at Zhipu and at ZCode couldn't be told apart. The split has "Model · provider" now: a "modelAt" dimension (provider/model) whose rows are named by both with the provider's icon, each with its own tokens/s, first token and requests, ranked by any metric (Speed: the fastest first). A click lists that provider's requests of that model, again all of them; the others stay ranked. The trend's bar wraps at the narrowest window, "Split by" with its choices. The key of each provider and model is built once per page (TestRequestPageAppendDoesNotRepriceHistory's allocations). TestRequestPageRanksModelAtEachProvider, TestLedgerNamesModelAtProvider and usage-model-at-provider (WebKit, en and zh) fail without the change. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass (TestProvidersAnswerWhileListsComeIn and TestUserPath failed under load and pass alone). yetone 2026-10-05 17:15:29 +08:00
  • 4c001cef85 gui: a model with no list price is given its price one box at a time (PAMI on Discord) — in Names & levels, a model with no list price (a custom provider's, or one added by hand) couldn't be priced: its Input typed first, the editor said "has no list price: give its input and output prices" and emptied the box again, and the same with Output first, so both were never there at once. A part typed now stays, the row marked unsaved, until the other is given; a Save before then is held, the empty box focused and the same message beside its buttons. model-price-edit.test.cjs (WebKit, en and zh) fails without the change. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. yetone 2026-10-05 17:16:55 +08:00
  • 509e8444d1 gateway: a tool schema with an enum and no type goes to Kimi with the type its values have (#886) — Kimi Code refused a request whose tools had cua-driver's parse_visual_regions, 400 "tools.function.parameters is not a valid moonshot flavored json schema … At path 'properties.options.properties.kinds.anyOf.items': type is not defined": its items were {"enum":["text","icon"]}, and the whole request failed though the tool was never called. To Moonshot and Kimi Code (api.moonshot.ai/.cn, api.kimi.com/.ai, or a moonshot/kimi preset) each schema in the tools with an enum or a const and no type is now given the type its values all have (string, integer, number, boolean), written in place after its brace, through properties, items, anyOf/oneOf/allOf, $defs and the rest; values of mixed types or with a null are left as they were, and other providers are sent the tools unchanged. TestKimiToolEnumTypes fails without the change. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. yetone 2026-10-05 17:09:45 +08:00
  • 20c086d1f6 provider: a ClinePass API key's card shows its 5-hour, weekly and monthly limits and its credits (zRain on Discord) — a ClinePass added with a key had no usage card: we took GET /users/me/plan/usage-limits for a signed-in account's alone. AxonHub (looplj/axonhub, its Cline channel; verified there against a live account) reads it, /users/me and /users/{id}/balance with the channel's key as a Bearer, so magpie now does the same for a Cline key with no Balance URL of its own: windows 5 hours, Weekly and Month as the Cline plugin names them, percentUsed and resetsAt, and the balance in dollars (millionths). An account without ClinePass (usage-limits 404) shows its credits alone; a key Cline refuses says so on its card. Not tried with a real Cline key. TestClineKeyUsage fails without the change; go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. yetone 2026-10-05 17:02:13 +08:00
  • 1d92f52595 gui: Privacy's Mask personal data hides the accounts on screen too, and Privacy has the Hide accounts switch (inaction on Discord) — on another computer with privacy on, Usage still showed the email address, until Usage's own Hide accounts was found and turned on: it is kept on each computer, apart from the settings. Until Hide accounts is chosen on a computer it now follows Mask personal data; turning Mask personal data on turns it on as well (off leaves it as it is); and Settings › Privacy has a Hide accounts row bound to the same switch. A choice made with the button or the row is kept over the setting, so an address shown on purpose stays shown. privacy-hides-accounts.test.cjs (WebKit, en and zh) fails without the change; account-mask.test.cjs passes. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. yetone 2026-10-05 17:01:38 +08:00
  • 1d7eac3125 gui: every installed magpie on a config is one at a time, so a Mac restart can't put two birds in the menu bar (inaction on Discord) — the lock that keeps a second magpie from running (v0.1.879) was keyed by the config dir and the resolved executable, so two copies of the app each took their own: Open at login starting the copy it was turned on for while the Mac reopened the one last run (another magpie.app, or one run translocated from Downloads, whose path changes each launch), or Homebrew's, whose path has its version in it. Every magpie.app and Homebrew copy now shares the lock; a bare binary elsewhere (the owner's magpie-dev) still has its own. A second launch for the window opens the app by its bundle id (com.yetone.magpie) rather than its own path, so it reaches the running copy. TestOneMagpieAtATime fails without the change (the translocated and Homebrew copies had locks of their own). go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass (TestProvidersAnswerWhileListsComeIn and TestUserPath timed out under load and pass alone). yetone 2026-10-05 16:59:30 +08:00
  • 46f03154ff 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. yetone 2026-10-05 16:45:57 +08:00
  • 4dab45e3ff gui, provider, gateway: a group's models are dragged into order, a group can be switched off, several models are added at once, and a page shows a scrollbar on the Mac (PAMI on Discord) — in a group's editor a model went up one step a click and only up; its number is now a handle (grip on hover) that drags it, Alt+↑/↓ moves it from the keyboard, and Up and Down stay beside it; a pattern's members follow the named ones. A group of the user's has a switch on its card: off, agents aren't offered it, a request to it is turned away with 404 "the routing group … is switched off", a group that has it in it goes on to its next member, a save from the editor leaves it off, and one that classifies for another can't be switched off (a found group is removed instead, as before). Each member already had its own switch. The editor's Add a model picker stays open: each model clicked is added and ticked, clicked again taken out, and each says the other groups it is in. Pages, dialogs and framed editors had their scrollbar hidden everywhere (width 0); on the Mac they now have a thin 8px thumb, elsewhere the 10px one other lists have. TestSwitchGroup, TestSwitchedOffGroup, group-member-drag, group-switch-add-many and view-scrollbar (WebKit, en and zh) fail without the change. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass (TestProvidersAnswerWhileListsComeIn is flaky on origin/main as well). yetone 2026-10-05 16:36:17 +08:00
  • 1cf07610c2 gateway: a Remote magpie searches the web for its own models, and Settings may name it to search with (Player on Discord) — Claude Code's WebSearch on remoteMagpie/glm-5.3 was judged unsearchable here (its host is no search vendor's) and went to this magpie's searcher, another model, and Settings' Searcher never listed the remote's models. Its list now tells another magpie, of each model, how it searches (web_search: native, by the vendor, or magpie, with its own searcher); a web search offered to such a model goes on to it as offered (Chat's as web_search_options), and the remote magpie, with the models it searches natively for, is a searcher. A search run for another model is marked (X-Magpie-Searching) and the other magpie then lets only the model search, so two magpies naming each other never ask each other in turn. TestRemoteMagpieSearches fails without the change; go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass (4 load-timeouts in agent/gui/provider pass alone). yetone 2026-10-05 16:41:38 +08:00
  • 8b7c4efbf8 gui: an average first token reads whole milliseconds under a second and one decimal above (John on Discord) — the Usage speed trend's tooltip read "258 token/秒 · 首字 761.9796610169492 毫秒": an average (ttft_ms over the replies timed) was shown unrounded. ledTook now rounds before it formats, so 762 ms, 1.2 s; the trend's tooltip, the ranking and the KPI all go through it. Speeds were already rounded. usage-ttft-rounding.test.cjs (WebKit, en and zh) fails without the change. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. yetone 2026-10-05 16:33:35 +08:00
  • f1f516cd73 fix(gateway): an account Antigravity turned away doesn't rest (#873) TryWorld 2026-10-05 16:31:11 +08:00
  • 6d5893d400 fix(rules): reject nan/inf and overflowing token lengths (#877) kevin 2026-10-05 17:31:07 +09:00
  • b525f27bfd provider: a custom provider's Balance field left empty is read from a Balance URL whose reply magpie knows (#881, Singrun) — new-api's /api/usage/token ($data.total_available / 500000) and /api/user/self ($data.quota / 500000), a sub2api panel's /api/v1/user/profile ($data.balance) and OpenAI-style /dashboard/billing/credit_grants ($total_available) each said "no balance path" with the field empty; the field typed still wins, any other URL still asks for it. The editor's field shows the one read as its placeholder, following the URL as it is typed. TestKnownBalanceFieldLeftEmpty fails without the change; balance-field-default.test.cjs (WebKit) passes. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass (gui/proc/provider timing tests that failed under load pass alone). yetone 2026-10-05 16:29:48 +08:00
  • 387afd0779 gateway: Cursor Private Inference asking with another key of magpie's is shown its own model list (mamba on Discord) — after v0.1.980 its Reasoning control was still missing and models taken out on its row still listed: a key typed in its Open configuration or a model's settings ("magpie") overrides magpie-cursor-local, and its User-Agent (Cursor-CLI/…, cursor-agent-exec's default) made it the cursor agent, which is shown the plain list. cursor-agent talks to Cursor's backend only, so a Cursor User-Agent at the gateway is now taken as Cursor Private Inference's: its /models carries api_types and capabilities and follows its row's model picks, and its use is counted under it. Its notice now says to leave both the base URL and the key empty (or use magpie-cursor-local) and to quit the app, not only its window, as it keeps the model list it was first given. TestCursorLocalModels fails without the change. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. yetone 2026-10-05 16:26:21 +08:00
  • 602d4ec6cc agent: magpie's default compaction window gives way to one the user set in Claude Code (#876, hisiling) yetone 2026-10-05 16:24:23 +08:00
  • 9139b19224 gui: the Update pill says "Downloading…" until the download has a percent, never "NaN%" (inaction on Discord) — /api/update leaves done out while it is 0 (omitempty), so the first reads of a download that had a total gave undefined/total = NaN and the pill flashed "Downloading… NaN%". downloadingText takes a missing done as 0 and shows a percent only when total is known and the result is a finite number (clamped to 0–100), else "Downloading…". update-download-percent.test.cjs (WebKit, en and zh: size known and nothing in → 0%, no size → Downloading…, then 50%; a MutationObserver checks the pill never said NaN/undefined/Infinity; the click doesn't scroll; no left border) fails without the change with 'Downloading… NaN%' / '正在下载… NaN%'. update-wait/hide/check/notes-ask and click-scroll tests pass. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. yetone 2026-10-05 16:21:35 +08:00
  • 07137b9f71 gui: a group's Save no longer leaves the Routing page blank (#884) wizzy-yangandmagpie-fix 2026-10-05 16:14:26 +08:00
  • 2e57619ae0 test: TestAccountsOfAPlugin expects forgetting the account in use to put the other first — since 30d952cf (v0.1.976) forgetSideLogin no longer refuses the account a subscription uses first; magpie accounts forget goes through the same ForgetLogin, so a plugin's in-use account is forgotten and the remaining one becomes the one in use. Test-only; go vet and go test -tags nogui . ./internal/provider/... pass. yetone 2026-10-05 15:39:40 +08:00
  • 7118aaabcc gateway: Cursor Private Inference shows its Reasoning control for magpie's models (mamba on Discord) — its picker (cursor-agent-exec 3.23.12) keeps a /models row's capabilities only when they say the model calls tools, streams and writes text, and magpie's said neither, so every model was listed with no Reasoning control. The rows Cursor Private Inference is shown now carry supports_tool_use, supports_streaming, output_modalities [text], supports_reasoning and the model's levels (reasoning_effort), and a Claude served on Anthropic Messages is listed there alone, the only API Cursor sends a Claude's level (output_config.effort) on. Which levels it offers Cursor still decides from the id (Claude Opus/Sonnet 4.5+, GPT-5.x, Grok 4.x, Gemini); it has no Max toggle for a gateway's models. TestCursorLocalModels fails without the change. go vet, go build (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass but for TestAccountsOfAPlugin, which fails on main as well. yetone 2026-10-05 15:38:44 +08:00
  • 1aaac93b52 provider, gui: Compact at — where Codex and Claude Code compact a long conversation, set per provider or per model in its editor and to any size in Settings → Long conversations (#876, hisiling) yetone 2026-10-05 15:20:05 +08:00
  • e77a373792 agent: every agent in a WSL distro under NAT is given the LAN sharing key, not "magpie" (whqtian on Discord) — v0.1.972 gave it to omp only, and OpenCode in WSL was still written "apiKey": "magpie" with Windows' address, which the gateway takes from loopback only. keyAt(gw) is the key for a gateway URL: the LAN sharing key when the URL isn't this machine's and sharing is on, else gateway.Token; OpenCode (and MiMo Code), Crush, Pi/omo, Codex, Claude Code, Kimi Code, Hermes, Grok, MiniMax Code, Droid, Morph, dsh, Qoder/Qoder CN and omp write it, their Checks compare against it (an old "magpie" there shows as drift), and ourKey still sees a LAN-keyed config as magpie's. TestGatewayKeyBeyondLoopback fails without the change. go vet ./..., go test -tags nogui ./internal/agent ./internal/gui ./internal/gateway ./internal/access ./internal/provider under a temp HOME, GOOS=linux/windows go build pass. yetone 2026-10-05 15:19:09 +08:00
  • 2c801b6f9a 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. yetone 2026-10-05 15:05:28 +08:00
  • 30d952cf0c provider: removing the account a subscription uses first puts the next one first, and Sign out… takes every account (歧路亡羊 on Discord) — Copilot's Sign out… stopped at "magpie uses wwjxhy first; put another account first" and kept the account yetone 2026-10-05 15:02:11 +08:00
  • 9fb51c7742 docs: a new README, in English and Chinese, that shows what magpie does yetone 2026-10-05 15:02:06 +08:00
  • 322842c1c0 provider: ClinePass signed in through the Cline plugin shows the reasoning levels its API key does (zRain, Discord) — the plugin gives no levels of its own, and only a moved built-in borrowed its models' makers', so every plugin-served Cline model had none while the ClinePass built-in borrowed them. Cline's plugin now borrows as the built-in does. TestClinePluginBorrowsEfforts fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux, GOOS=windows), go test -tags nogui ./... under a temp HOME pass. yetone 2026-10-05 14:56:15 +08:00
  • 9e77092173 gui: clarify search provider help (#878) cjyrainbow 2026-10-05 14:59:02 +08:00
  • 37ec725916 provider, gui: a removed subscription's one account can be signed out, a failed sign-out keeps its error on screen (#874); a custom provider's Max output has the usual sizes under it and More endpoints' labels stay on one line (#875) yetone 2026-10-05 14:36:20 +08:00
  • 31626c1543 agent: an omp in WSL under NAT gets the gateway's network key, and one bun installed is found and asked its version (whqtian on Discord: a WSL omp connected only once auth: none was swapped for the key by hand, and Kimi K3's max still read xhigh) — a distro not in mirrored networking reaches the gateway at Windows' address (172.23.80.1), where a request without a named key is turned away, yet magpie wrote auth: none there too. Its models.yml now carries the key the gateway is shared on the network with (access.LANSecret: the default LAN key, "" while nothing is shared or it is off) in place of auth: none; an omp on loopback stays keyless. The probe asked omp its version only when sh -l found it on PATH, but bun's and nvm's installers put theirs on PATH in ~/.bashrc, which sh -l doesn't read: the version stayed unknown and max was offered as xhigh, as for an omp older than 16.4.0. It now also looks in ~/.bun/bin, ~/.local/bin, ~/.npm-global/bin, ~/.volta/bin, pnpm's, nvm's, mise's and asdf's folders, and runs --version with the command's folder on PATH, where bun's #!/usr/bin/env bun finds bun. TestOmpKeyBeyondLoopback (NAT address shared or not, loopback, localhost) and TestWSLProbeFindsBunOmp (the probe script run with a bun-installed omp off PATH; fails on the old script: no bin, no version). go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass but for TestSavedButSignedOutClaudeServed, which timed out once under load and passes 3 of 3 alone. yetone 2026-10-05 14:35:18 +08:00
  • 4a97acf12a refactor(agent): separate Aside provider connection from model settings (#827) Junjie Zhou 2026-10-05 14:37:44 +08:00
  • 6e500cb520 provider, gui: a reset reminder, a notification before a long window renews with much of it unused or before resets expire (thedavidweng, #720) yetone 2026-10-05 14:23:36 +08:00
  • 6fc0afe817 gui, provider: a model's price is set in the provider editor's Names & levels, beside its name, with its list price shown (ITea312, #819) yetone 2026-10-05 14:17:46 +08:00
  • 2350ea7aa7 provider: moved ZCode users get plugin 0.1.9, where the gift's models are trial entries the user picks (plugins #21) — 0.1.8 spent a dual account's gift before its GLM Coding Plan for every model the gift served, so a user who meant to spend the plan had no say. 0.1.9 lists each live gift model again as <model>-Trial (GLM-5.3-Flash-Trial): that entry spends the gift and answers 429 locally when its bucket is spent or blocked, and a plain model always spends the coding plan. Reviewed line by line and run against a fake upstream (a coding-only account's trial entry is a local 400, a streamed trial request reaches zcode.z.ai with the raw model, a 1113 inside a 200 becomes 429 and keeps the entry off for a minute, a plain model still goes to api.z.ai); bun test (65 pass) and bun scripts/check.mjs zcode pass. go vet and go test -tags nogui ./internal/provider/ under a temp HOME pass. yetone 2026-10-05 14:29:15 +08:00
  • b06d8247b5 fix: keep Claude mid-turn text with tool results (#872) Guuuu233andDavid Liu 2026-10-05 14:29:27 +08:00
  • e957230629 fix: keep purpose-filtered routing state consistent (#784) zzzpyyy 2026-10-05 14:26:23 +08:00
  • e5981040dc gateway, gui: a request an aggregator passed on says which provider answered it behind it, "Upstream: DeepInfra" (leslie_luo on Discord: 像 CPA 那样指出实际供应商) — through OpenRouter a request was only "OpenRouter · deepseek/deepseek-chat", with no word of the provider (DeepInfra, StreamLake, Novita…) OpenRouter had sent it on to. OpenRouter names it in its replies, as tried with the owner's real key: Chat's body and every chunk of its stream have a top-level "provider", Messages' body has one and its stream's message_start a message.provider; its Responses replies and every reply's headers name none. The gateway now reads it (upstreamOf) from a body relayed as it came and from each event of one it translated, Chat, Messages or Responses, streamed or not, and keeps it on the request's try and route (upstream) and in the usage ledger (Record.Upstream; the CSV export's last column, searched as the rest). The Routing page's Requests list says Upstream: DeepInfra beside where it went, giving way after the provider's name when short of room, with its story saying the aggregator passed it on; a request's details on the Usage page have Upstream provider. A reply naming none says nothing. zh, ja, de strings. ClinePass and Cline weren't tried (no account); Vercel's gateway documents no such field in its replies, and a vendor whose reply has a top-level "provider" string is read the same way. In a sandbox magpie with the owner's real OpenRouter key: Chat, Messages and Responses (translated to Chat), streamed and not, all six said StreamLake in the trace, the ledger and on the Routing page. TestUpstreamRecorded (6 paths × streamed or not: the route, its try, Recent and the ledger) fails on the old code, TestUpstreamOf; routing-upstream.test.cjs (Chromium and WebKit, en and zh: the tag inside its row, the story, the Usage details, a click moves nothing, no left-border accent) fails on the old assets. ledger-hscroll, request-archive-usage, usage-ledger and usage-ledger-detail pass on WebKit; routing-served fails the same on origin/main (its swap wording). go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass but for TestTranslatedStreamKeepsClientAlive, which passes alone 5 times here and on origin/main. yetone 2026-10-05 14:04:44 +08:00
  • bbb9729748 gui, provider: where a model or routing group is named, it says what agents' lists will call it and offers dropping the suffix there; a group the user made counts as a name they set (#868) yetone 2026-10-05 14:03:43 +08:00
  • af25cc6977 gateway: Codex's compaction on a magpie model goes on when the provider answers the summary 404 (#866) — a relay (flyapi) that serves the conversation turn by turn answered the summary request 404 "Upstream request failed", which went back to Codex as it came: "Error running remote compact task", a reconnect loop and the compaction aborted. Now a 404 is logged with the provider, model, base URL and message, and the summary is asked for again with the conversation as plain text in one message (no item ids, sealed reasoning or call ids for the relay to look up); when that fails too magpie compacts without a model — the user's messages (the latest 80k characters), the last reply and why — so Codex gets its compaction item and goes on. 401, 429, 500 go back as they came. Codex's base_instructions are untouched. TestCodexCompact404AsksAgainAsText and TestCodexCompact404CompactsLocally fail on the old code; TestCodexCompactOtherFailuresStay. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. yetone 2026-10-05 13:58:08 +08:00
  • a717902822 provider: a plugin's provider beside a provider of the user's own with its id is id-plugin, listed rather than hidden (lishouxian, #867) — a custom zenmux saved before @zenmux/pi-zenmux-oauth was installed took the id, so the plugin's ZenMux, once signed in (168 models), was skipped by allProviders and the sign-in ended in "signed in, but magpie can't list it"; and magpie provider rm zenmux hid the plugin's account rather than removing the custom entry. PluginID now gives id-plugin when a provider with an endpoint in providers.json has the id (customIDs, held per request like the rest), as it does for a built-in's or a preset's: the user's own keeps its id, models and agent/route references, the plugin's lists as zenmux-plugin, and rm zenmux removes the user's own. TestPluginIDBesideCustomProvider (fake plugin under bun) fails on the old code (PluginID(fakeco) = fakeco). go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. yetone 2026-10-05 13:58:03 +08:00
  • f082b8eb05 gui tests: routing-served reads the swap's story as 4a87b306 words it — the tooltip and the story now say "The vendor was asked for gpt-6-sol, but its reply says gpt-6-luna answered: likely another model" (zh 向服务商请求的是 gpt-6-sol,但回复说是 gpt-6-luna 作答,很可能换成了别的模型), reworded on purpose for vincentzhang, and the test still looked for the old "answered it: another model", so its en and zh cases failed on main. It now matches the new text, both models named in it. routing-served, remote-group-served, routing-shape, routing-proxy and routing-log-icons pass on WebKit, en and zh; go vet and go test -tags nogui ./internal/gui/ under a temp HOME pass. yetone 2026-10-05 14:00:50 +08:00
  • 116a85f270 gui tests: the Routing page's tests pick a day from the date bar, not the By request button (#860 flake) — remote-group-served's zh case timed out 2 in 9 after 5464a362, but it did so as often before it: under the same load old and new each passed 20 of 21 (18 of 18 run one at a time). The By request / By session buttons are .rt-day too, so .rt-day nth(1) could be the pressed By request button, which WebKit sometimes never calls stable, and the click timed out. remote-group-served and 11 other routing tests now click .rt-days .rt-day, as click-scroll and purpose-filter do. remote-group-served zh in WebKit: 18 of 18 under three loops at once; the 12 tests and purpose-filter, click-scroll pass on WebKit except routing-served, whose tooltip assertion fails the same on origin/main. go vet and go test -tags nogui ./internal/gui/ under a temp HOME pass. yetone 2026-10-05 13:57:34 +08:00
  • 2e8d1a5317 edit: keep a CRLF file's line endings when SetJSONItem adds or replaces an element (#869) kevin 2026-10-05 14:56:48 +09:00
  • 3b2df39db8 gui: the Routing page's magpie flies on to the member a request went to when it was sent at an effort the member isn't fixed at (uiots, #865: 路由页飞鸟停在 magpie 门口不飞向成员) — a row is keyed by its seat, whose fixed is the member's own effort, but a try's fixed is the effort it was sent at by force: the member's own, or one the agent asked in the model's id (a Claude Code tier at high). So a member following the group's effort, tried at high, had a try seat "id\0model:high" and a row seat "id\0model": play() found no row and skipped every flight, the magpie stood at magpie's door (or the canvas' origin) until the answer, and the row never said answering… / answered this request. A try's row is now looked up through tried() (tryseat): the exact seat, else the same key and model, else the key — in play() and in what each row says (trying, answered, gave). routing-try-fixed.test.cjs (WebKit, en and zh): a try with fixed "high" to a member with none — its row says answering… and the request is held at that row; it fails on the old routing.js. The other routing-*.test.cjs and panel-routing pass on WebKit (auto-reset, burst-speed and failed-open passed on rerun; routing-served fails the same on origin/main); Chromium doesn't launch on this machine. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./internal/gui/ under a temp HOME pass. yetone 2026-10-05 13:43:57 +08:00
  • 5464a362f6 usage: the Requests tab says how fast replies came, and fits the reporter's window; a wide window keeps Agents and Settings readable (huoranxuanyuan, #860) yetone 2026-10-05 13:32:32 +08:00
  • c058ee1d87 devin: a moved Devin gets plugin 0.1.8, whose models take images as Devin tells its CLI (面条 on Discord: swe-2 takes images) — @magpie-community/opencode-devin-auth 0.1.7 listed swe-2, swe-1.7 and the like as text-only (models.dev doesn't know Devin's ids), so agents dropped their images; 0.1.8 asks GetCliModelConfigs, as the built-in already does (devin_images.go), and its declared list says what Devin said on 2026-10-05. The mover's min is 0.1.8, so keepMovedCurrent updates every moved user's plugin. With the owner's real Devin account, through the plugin: swe-2 and swe-1.7 described an inline image's colours, and provider.models listed them with image input, with the devin CLI and without it. go vet ./internal/..., builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. yetone 2026-10-05 13:25:06 +08:00
  • 4183f516bf agent, provider: a CLI whose --version fails isn't run again until it changes, and an npm CLI's version is read from its package (#864) — macOS put up 「已阻止恶意软件:未打开 "codex"」 each time magpie's window opened: the codex magpie finds was asked its version every ten minutes (the model list's client version) and again with the Agents page (a failure retried after ten minutes), and macOS stops a program XProtect flags with that alert on every start. proc.Version is the one way both ask now: an npm CLI (…/node_modules/@openai/codex/bin/codex.js) has its package.json's version read and isn't run at all; any other is run with --version, and a run that ran and said nothing, or couldn't start, isn't made again until the binary's path, size or mtime changes (a reinstall); one cut off by the timeout is. A CLI that answered is asked as before. TestCodexVersionNotRerun (a codex that exits 137 run once over three ten-minute refreshes; an npm codex's version read, not run) fails on the old code (run 3 times); TestVersionRunsOnce. TestUpdateCLI and the agent and provider tests pass. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass but for TestClientGoneRecordedAsCanceled, which passes alone (-count=3). yetone 2026-10-05 13:23:57 +08:00
  • a77883ec91 fix(routing): label Codex background prompt suggestion groups (#859) Zhu Wang 2026-10-05 13:19:49 +08:00
  • dad4b98729 gateway, gui: the Cline plugin's provider has ClinePass's Upstream tick too (ARNO on Discord: 这个功能可以也加到cline的插件里面吗) — "DeepSeek models only from DeepSeek's own API" was the built-in ClinePass's alone: the Cline plugin's provider (@magpie-community/opencode-cline-auth) is an account, whose editor had no such tick, whose save didn't keep pinUpstream and whose requests clinePin didn't touch. Now ClinePinnable is ClinePass, a cline.bot provider or the Cline plugin's; its editor shows the Upstream tick (one upstreamField for both editors), its save posts pinUpstream, the account keeps it (Save, allProviders), and a DeepSeek model's request reaches the plugin with "providerOptions":{"gateway":{"only":["deepseek"]}} on Chat and Anthropic Messages, as ClinePass's does. The plugin 0.1.3 has the same as an option of its own for OpenCode. TestClinePluginPinsDeepSeek (the fake plugin as cline, with a deepseek-v4-pro): unticked nothing is pinned; ticked and saved, the tick is kept and both DeepSeek requests are pinned, fake-1's not; it fails on the old code (not pinnable; the tick not kept). clinepass-pin.test.cjs: the plugin's editor shows the tick as saved and Save posts it off when unticked (WebKit, en and zh); it fails on the old app.js. model-first-pick, plugin-moved-signin, click-scroll, gui-ja and gui-de pass on WebKit. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. yetone 2026-10-05 12:59:31 +08:00
  • 4a87b30610 usage, gateway: a model the vendor spells with other separators isn't marked as another one (vincentzhang on Discord: 这个说明有点奇怪。看不明白。) — Volcengine Ark answers deepseek-v4.1-flash as deepseek-v4-1-flash, the dots written as hyphens, and the Usage page's Requests and the Routing page marked every such row amber 「实际 deepseek-v4-1-flash」, saying another model had answered, not the one asked for under a dated name. Swapped now compares the bare names without their separators (. - _ and spaces) and case, so deepseek-v4-1-flash, deepseek_v4_1_flash, GLM-4-6 for glm-4.6 and gpt-4-1-2025-04-14 for gpt-4.1 are the model asked for, while deepseek-v4-flash or deepseek-v4.1-pro for deepseek-v4.1-flash still are another. Rows and routes kept marked swapped before are read back as not swapped when the names differ only so (usage.SameSpelled, in rowChunk.row and Route.routedAgain). The tag's explanation says it in plainer words: asked for {sent}, the reply says {served} answered, likely another model; the same model under a dated name or spelled otherwise isn't marked (zh, ja, de). TestSwappedIgnoresSeparators fails on the old code; TestKeptRowSameSpelledNotSwapped, TestRouteKeptSpelledOtherwiseNotSwapped. usage-ledger (its expected title updated), remote-group-served, panel-routing, gui-ja and gui-de pass on WebKit. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass (TestCodexVersion timed out once under load and passes alone). yetone 2026-10-05 12:49:53 +08:00
  • 7d92013a15 claude: a tool the client's framework ran after a reply keeps the run, so the cache is read, not written again (#858) TechnoHouse 2026-10-05 12:46:22 +08:00
  • 1d70277385 agent: Codex keeps an openai_base_url the user wrote by hand (sbtxdy, #856) — openai_base_url = "http://127.0.0.1:3425/backend-api/codex" written in ~/.codex/config.toml by hand, Codex on a model of its own and signed in to ChatGPT, was gone again each time magpie started (after every update): Sync's failover, which points Codex's own models at magpie while another of its ChatGPT accounts is on there and takes the base URL away when none is, took it for its own and removed it. Failover now marks the base URL it writes (codex.failover in the stash) and takes away only that one; one already there while failover is on is taken as its own, as a magpie from before the mark wrote it. Connecting Codex in magpie (Join), a magpie model, and disconnecting are as before. TestCodexHandWrittenBaseURLStays fails on the old code (Sync left only model = "gpt-5.5"); TestCodexOwnModelThroughMagpieWhileAccountsOn and the other TestCodex* pass. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass, but for TestClaudeAccountNamedAsItsLogin and TestClaudeIdentityServedWhileAsked (5 s timeouts under the full run's load), which pass run alone. yetone 2026-10-05 12:45:10 +08:00
  • 50d6c9a394 library: Claude Desktop's skills list shows again, each skill's updatedAt an ISO time (mingxitang, #863) — magpie wrote a skill's updatedAt in the kind lastUpdated had, milliseconds where it is a number, but Desktop's own entries are ISO times or null and listLocalSkills checks the whole list at once: one number and the skills page stayed loading (failed to pass validation). Now updatedAt is always an ISO time and lastUpdated keeps its own kind; an entry written as milliseconds before is written as the same moment in ISO at the next sync, its other keys as they were. TestClaudeDesktopSkills: the entry of a manifest with millisecond lastUpdated, and an old numeric entry fixed at a sync; it fails on the old code. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass (five timing tests under load 20 passed when run again). yetone 2026-10-05 12:43:18 +08:00
  • 69a7744a9f fix(agent): preserve Zed reply limits (#857) Zhizhen He 2026-10-05 12:42:46 +08:00
  • b78a1ce0b2 redact: hold a split placeholder of a long custom kind in a stream (#862) kevin 2026-10-05 13:42:42 +09:00
  • eb1d97895c gui: ZCode's site step keeps its question readable (361, Discord) — the plugin's other ways (the ZCode app's sign-in, the API key) were added to the question's row beside Z.ai and 智谱 BigModel, four buttons that left 「你要用的 ZCode(GLM Coding Plan)账号在哪个站点?」 a strip a character or two wide and the box very tall (0px wide at the tray panel's 440, 85px at 700 in zh). Now the question keeps the row with Cancel beside it, the two sites are one group under it, and the other ways a line of links under them (data-site / data-method kept), each wrapping. zcode-site-layout.test.cjs (WebKit, en and zh, at 440, 700 and 960px): the question at least 55% of the box and at most ~4 lines, the box under 260px tall, no button out of the box, the sites on one line, the app's sign-in still signing in with way 2 and the click not scrolling; it fails on the old code at every width. plugin-other-ways, plugin-moved-signin, add-sheet, click-scroll, gui-ja and gui-de pass on WebKit; zcode-site fails the same on origin/main. Looked at in screenshots. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. yetone 2026-10-05 12:15:31 +08:00
  • 273be22cac agents: Codex's /model lists its models in the order dragged on the Agents page (M3chD09, #855) — Codex lists the models by the priority magpie gives them: a ChatGPT account's own first, as the backend has them, then magpie's in the catalog's order, with no way to change it. Codex's model list (Pick on its row) now has a third view, Order: the models shown as Codex lists them, numbered, each dragged elsewhere (its grip shows on hover) or moved with Alt+↑/↓; it is saved at once (settings orderedModels, by agent and entry id), and the account's own models and magpie's go in one order. A model not in the order (one added later) goes after those that are, as it stood; the hidden stay hidden and out of the order view, hiding keeps the order, and a provider renamed renames its ids in the order too. Default order puts magpie's back. Codex gets it at once: signed in to ChatGPT, the gateway's /models gives the backend's and magpie's models priorities 1..n in that order and the list's ETag carries the order so Codex asks again; not signed in, the catalog file magpie writes for Codex (ListedFor → InOrder) is rewritten in it. Only Codex's list can be ordered; base_instructions are untouched. zh, ja, de strings. TestCodexModelListInUsersOrder (gateway: default order, an order across the account's and magpie's, the ETag changing, CatalogFor, opencode's untouched, none restoring it) and TestAgentModelOrderAPI (gui: orderable, an order read back, a new model last, hidden kept, hiding keeping it, reset, opencode refused) fail on the old code (no SetModelOrder/ModelOrder); codex-model-order.test.cjs (WebKit, en and zh: Order lists the own first and leaves the hidden out, Alt+↓ and a drag post the order, Default order posts none, nothing scrolls; a list that can't be ordered has no Order) fails on the old code (no Order). agent-models, agent-models-all-hidden, agent-models-loading, agent-model-groups, click-scroll, gui-ja and gui-de pass on WebKit. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. yetone 2026-10-05 11:44:22 +08:00
  • bd456fa51e fix: keep Claude continuations and routing stats bound to accounts (#854) Haogao Gu 2026-10-04 23:52:23 -04:00
  • 2880b95b60 settings, gateway: the gateway's port is set in Settings › Network › Gateway, and every agent magpie connected moves with it (Magic_zero on Discord: 能否添加自定义端口号 — 3425 is taken on their machine, and they want a port of their own that is easy to remember and tell apart) yetone 2026-10-05 11:24:46 +08:00
  • 653cb8ee30 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. yetone 2026-10-05 11:26:08 +08:00
  • 460057a225 gui: a hidden agent put back to its defaults goes under Not set up at once (Hu9956, #842), and a restart to update shows what changed before it restarts (Hu9956, #844) — #842: an agent hidden by hand stayed under Hidden with its Show (the eye) after its model was set back to Default; only turning its switch off and on moved it, since nothing ever dropped its hiding (the server leaves agentsHidden alone). Now when a hidden agent's settings change (wired, added, drift or a field's value) so that nothing is set on it, its hiding is dropped and saved with the arrangement at once, it goes under Not set up without the eye, and the status line says so; a state answer read before that save, still naming it hidden, doesn't hide it again. One hidden while nothing was set on it, and left alone, stays hidden. #844: the header's Update pill and Settings › About's Restart to update restarted on the click, and what the update brought was only in Settings › About › What's new. Now in the window they open a sheet first: Update to v…, What changed since v<current>, every release after this one up to the new one, newest first (the update's own notes at once, the full list when GET /api/update/notes answers: the site's notes feed for (current, latest], the update's notes when the feed is out of reach), Later and Restart to update; only the latter restarts. A failed swap still restarts again at once; the panel keeps its one-click pill. zh, ja, de strings. hidden-agent-cleared.test.cjs (the groups, the hiding saved once, the late answer, no scroll) and update-notes-ask.test.cjs (the pill and the Settings button ask first, both releases' notes, Later sends nothing, the sheet's button restarts, no scroll, no left border) pass on WebKit in en and zh and fail on the old code; looked at in screenshots. TestUpdateNotesRoute (the releases between, the fallback, none) fails on the old code. update-wait.test.cjs now confirms the sheet; it and update-hide, update-auto, update-check, header-centre, header-zoom-fit, show-unset-agent, fold-show-grip, agent-grip, agent-start-pick, agent-path-same, removed-hide, profile-confirm, agent-update-all, gui-ja, gui-de and click-scroll pass on WebKit (click-scroll's pointer subtests are flaky the same on origin/main). go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. yetone 2026-10-05 11:16:33 +08:00
  • 12cba74ab3 agent: Zed's max_tokens is the window a prompt and its reply share, so a prompt keeps the room magpie's catalog gives it (#850) — Zed's openai_compatible max_tokens is the whole window: it keeps max_output_tokens of it for the reply and lets the prompt fill only the rest before it compacts (its thread's input_token_capacity = max_tokens - max_output_tokens). magpie wrote its catalog's Context there, which is what a prompt may hold, so Zed took the reply out of it: glm-4.6 (204800 / 131072) left a prompt 73728, deepseek-chat (128000 / 128000) none at all, gpt-5 (input 272000 / 128000) 144000 of its 400000 window. max_tokens is now Context + max_output_tokens: 335872, 256000, 400000 (OpenAI's own), and a model with no output limit still gets its Context alone. In a sandbox HOME with the real models.dev catalog, setting Zed to magpie wrote glm-4.6 335872/131072, gpt-5 400000/128000, deepseek-chat 256000/128000, claude-sonnet-4-5 264000/64000, kimi-k2 229376/98304. TestZedWindowHoldsPromptAndReply (gpt-5 400000 with a 272000 prompt; a model without an output limit) and TestZedRestoresProviderAndModelLimits fail on the old code. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass (TestProvidersAnswerWhileListsComeIn, TestUserPath and TestClaudeSignedOut timed out under load and pass on their own). yetone 2026-10-05 11:05:49 +08:00
  • 969e54f23b gateway: a turn on one of Codex's own models logs the reasoning Codex asked for (Karen, Discord #feedback: ChatGPT 账号登录,升级到最新版推理强度直接不显示了) — Codex signed in with ChatGPT sends its own models (gpt-6-astra, gpt-6.1-sol) on to chatgpt.com through /backend-api/codex, and that relay (codexUpstream) wrote its request-log row, its Routing trace and the trace's try without the effort, so the log's 推理强度 column was — on every one. It now records reasoning.effort from the (zstd-decoded) body as it goes on, unchanged, in all three. With the owner's real ChatGPT account in a sandbox magpie, codex exec at xhigh through openai_base_url: the row says openai · chatgpt.com, gpt-5.5, effort xhigh. TestCodexOwnModelLogsEffort (a zstd body at xhigh: the ledger's Effort, the route's and its try's) fails on the old code. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass (TestProvidersAnswerWhileListsComeIn, TestUserPath and TestClaudeSignedOut timed out under load and pass on their own). yetone 2026-10-05 11:04:17 +08:00
  • d5619d5a92 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. yetone 2026-10-05 10:55:12 +08:00
  • e85a803eaa fix(gui): skip scheme registration inside Flatpak (#849) TryWorld 2026-10-05 10:57:22 +08:00
  • 721e882267 provider: keep newer usage readings when older requests finish (#848) Zhizhen He 2026-10-05 10:41:07 +08:00
  • 76f3b97c09 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. yetone 2026-10-05 10:34:46 +08:00
  • 3ce0a0bf27 fix: 收敛中文界面文案 (#846) FanbuzandClaude Opus 5.5 2026-10-05 10:27:19 +08:00
  • b96d55fc99 sessions: a session's conversation can be read in magpie, and a Pi session carried on in oh-my-pi (sxwedo, #845) — the Sessions page could resume a session only in the agent that wrote it, and showed nothing of what was said. A session's details now have Show conversation: magpie reads the agent's own session file (Claude Code, Qoder, Qoder CN, Codex, Pi, oh-my-pi; found by agent and id, never a path from the page; read only, no copy kept, 400 KB at most with "There was more than is shown here") and lists the user's words, the model's, each tool call by name and its result, thinking and summaries folded. For Pi and oh-my-pi it follows the branch the session is on (parentId from the last entry), not the turns left on an abandoned one. A Pi session also has Continue in, the app's menu (no <select>): oh-my-pi's omp --fork <file> in the session's folder, copied or opened in the session terminal (the server makes the command from the session it finds; the page sends agent, id, in), and the details say it. omp --fork loads the Pi file, migrates it and writes a new oh-my-pi session, leaving Pi's untouched; with the owner's Pi sessions copied into a sandbox HOME, oh-my-pi's own SessionManager forked them whole (4→5 and 46→47 messages, its system line added; text, thinking and tool calls kept) and the source files' sha256 didn't change. Only that pair is offered: Pi can't read an oh-my-pi file (it wants the session header first, oh-my-pi writes a title line first), oh-my-pi takes Claude Code and Codex only from its own import picker, not a given session, and the other agents' formats differ, so carrying them on would mean converting them, which isn't done. zh, ja, de strings. TestTranscriptClaude, TestTranscriptCodex, TestTranscriptPiBranch, TestTranscriptOmp, TestTranscriptUnsupported, TestTranscriptCut, TestCarryPiToOmp; sessions-talk.test.cjs (WebKit, en and zh) fails on the old code. sessions-*, click-scroll, gui-ja and gui-de pass on WebKit; session-terminal and session-identity fail the same on origin/main. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. yetone 2026-10-05 10:08:34 +08:00
  • b6b149031e provider: a provider not found says when XDG_CONFIG_HOME has this magpie read another folder than the app's (蒙面人 on Discord: magpie provider refresh antigravity said no provider "antigravity" while the window showed it signed in) — magpie keeps its files in $XDG_CONFIG_HOME/magpie when the variable is set, and a shell can set it where the app opened from the Dock or Start menu has none, so a terminal's magpie read an empty folder and saw no Antigravity. With the owner's real config copied into a sandbox, v0.1.932 and main both list Antigravity and refresh it (Google returned 33 models, Claude [claude-opus-4-6-thinking claude-sonnet-4-6] on free-tier; nothing in magpie drops a Claude id), and with XDG_CONFIG_HOME pointed elsewhere the error now names both folders and says to run it with env -u XDG_CONFIG_HOME. appdir.Redirected; TestFindSaysFilesAreElsewhere (fails to build on the old code). go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. yetone 2026-10-05 09:55:12 +08:00
  • bfcb7b0647 agent, gui: every agent's picker lists its own models and says which way each goes; a hidden row's Show is centred and grips show whole (EZN7L2C3 #834, Hu9956 #842) yetone 2026-10-05 09:31:57 +08:00