Commit Graph

  • 01d595817c providers: each account of a subscription, and each key, can be kept for some of the provider's models only (#474: 增加账号级别的模型选项, a low-quota but verified account kept for the cheap models) — every account or key a provider had served every model it did, so one couldn't be spared for some. An account's or key's row in its provider's editor now has a models badge: a click opens the provider's models as chips, Save keeps the account for those picked, All models gives it every one again; one kept for some says how many, always shown. magpie provider account-models <id> [account|key [models…|all]] does the same and lists them. It is kept in providers.json as accountModels, an account by its name in lower case, a key by its id; one not in it serves every model, as before. The gateway never sends a model to an account or key whose list leaves it out, in every routing mode, in drag order, in a group and as a fallback, and never falls back to it all the same; the trace says it was left out as set to serve other models. When every account or key there is leaves the model out, the request is refused with a 403 saying so and how to change it, not "none of X's models is ready". TestKeyModelsNarrowCandidates, TestAccountModelsNeverChosen, TestAccountModelsCmd and account-models.test.cjs fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; account-models, click-scroll, old-webkit, account-arrange, account-proxy, account-return, routing-account-order, panel-routing, unlisted-models pass. yetone 2026-10-02 05:48:22 +08:00
  • 7de2b62191 gui: an agent row shows a faint grip in its margin, and a hidden agent's Show is always there (#479: hiding an agent was found only by right-clicking) — the row's handle is its logo, which turned into a grip and opened Move up, Move down, Hide on a click, but only showed it was a handle with the pointer on it, and a hidden agent's way back, the Show button in the fold at the foot of the list, came up only with the pointer on its row. Every row in the list now carries a faint grip (two columns of three dots, --faint, --muted with the pointer on the row) in its left margin, beside the logo: it is the handle's own ::before, so it takes no room from the row, a drag on it moves the row and a click on it opens the menu. The Show button on a row hidden by hand is drawn without the pointer on it. Rows in the fold carry no grip, since they aren't dragged. agent-grip.test.cjs (window and tray panel, Chromium, WebKit, en, zh) drags a row by its grip, hides one from the grip's menu and shows it again from the fold, and fails without either half of the change. account-arrange, agent-layout, panel-fold, rollup-short, click-scroll, old-webkit, seg-contrast, header-centre, header-zoom-fit pass. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass but for TestClaudeSignedOut, which fails on its own as well and doesn't build on internal/gui (TestUserPath and TestSubscriptionUsageEachAccount flaked under load, pass alone). yetone 2026-10-02 05:46:08 +08:00
  • aa8e8cee2f gui: the header's other views and the text buttons read at WCAG AA, and the view shown stands off the switch (#477: in light mode an unselected tab read at 3.25:1, a text button at 3.33:1 on the page; dark passed by 0.08) — both were drawn in --muted (#85858d light, #8b8b94 dark), meant for captions, not controls, and the thumb on the view shown was white on a pill-grey track (1.13:1) or a 10% white on a 4.5% one (1.36:1). A control's quiet text now has its own colour, --ctl-fg (#63636b light, #a1a1aa dark), for the switch's other views, the Accounts and Sessions switches on the same track, and every .text button: 4.79:1 on the light track, 5.4 to 6.0:1 on the page, a card and the inset editor, 6.1:1 and up in dark. The light track is a shade deeper (#e6e6eb) and its thumb's edge a little firmer, 1.24:1 against it; dark's track is 3.5% white and its thumb 15%, 1.61:1. Sizes and weights are unchanged, so the header fits as before (#442/#457). seg-contrast.test.cjs (Chromium, WebKit, en, zh; light, dark, system dark) reads the computed colours, each background laid over the ones under it, and fails without the change. header-centre, header-zoom-fit, text-size, sessions-toolbar, click-scroll, old-webkit pass. yetone 2026-10-02 05:30:24 +08:00
  • 2d5905ffc8 usage: every count on the Usage page and in the panel follows Settings' number units (#476: with 万 / 亿 picked, the Overview, the Requests summary line, Sessions and the panel's Routing "today" still said K/M/B, two units on one page) — the setting reached only ledShort, which the Requests tab's totals and axis and the panel's Usage tab used, while fmtN, behind the Overview's tiles and chart peak, the Requests summary (#ledSum), every Sessions figure and Routing's "today N tokens", always said K/M/B. fmtN now says a count in 万 and 亿 in Chinese unless K / M / B is picked, ledShort is gone and its callers use fmtN, and the panel's Routing redraws when the setting changes, not at the next request. Small counts (the scan's file counts, a picker's search count) are as they were below 10000 and in 万 above it in Chinese. English is unchanged. number-units.test.cjs (Chromium, WebKit, en, zh) now checks the Overview, the Requests summary, Sessions and the panel's Routing in both units and fails without the change; sessions-overview, sessions-today, panel-routing and api-key-usage expect 万 in Chinese. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; number-units, sessions-overview, sessions-today, panel-routing, api-key-usage, usage-chart-axis, panel-usage, usage-ledger, usage-ledger-chart, currency, sessions-bar-names, click-scroll, old-webkit pass. yetone 2026-10-02 05:46:01 +08:00
  • 8043e9436d tests: cli-update's tight agent row is measured just above 600px, where the row is still beside its fields — since #440 (946c1d3b) a window 600px wide or less puts an agent's fields under its name, so at 540px, where the test looked for the version to have gone, the name's line spans the whole row and shows the name, the version and the whole pill again, as #440 meant. The version now has to be gone at 604px (the row about 148px), the tightest it gets beside its fields, and at 540px everything has to be back. No behaviour changes. cli-update.test.cjs (Chromium, WebKit) failed on main; click-scroll, old-webkit pass. yetone 2026-10-02 05:28:55 +08:00
  • 156b72ebf3 gui: the account or key a provider is on reads "In use" (使用中) again, not "Current" (在用) — 3df75514, which added each agent's model list and its 在用 tag for the model an agent is set to, also renamed the badge on the Accounts and keys rows from "In use" to "Current", which its message doesn't mention. The rest of magpie calls what is in use "In use": the Library's active set reads In use, the S3/WebDAV sync's chosen kind "· 使用中", so an account row reading 在用 said the same thing in other words. The rows say "In use" again; the model list's "Current" tag for a model stays. duplicate-pause.test.cjs (Chromium, WebKit, en, zh) failed on main for it. account-arrange, account-return, api-key-usage, agent-models, click-scroll, old-webkit pass; go vet, go build -tags nogui pass. yetone 2026-10-02 05:28:55 +08:00
  • 409fd21fe7 gateway: a stream that says it's rate limited before saying anything is let go at once, and the next account asked (Discord: after the second account's 429, Hermes kept waiting, 回答中 with nothing shown and no error, until the preferred account was changed) — a key or account that answered 200 and then a 429 as its stream's error event, before any content, was failed by the hold writer, but magpie read on until the vendor hung up; one that kept the connection open kept the agent waiting with nothing sent, not even headers, and the next account was asked only then, or never within the agent's timeout. The try's request to the vendor is now ended as soon as its stream's error comes before any content, so the next key or account answers at once, and with none left the agent gets the error at once. A refusal still reads on, for what it cost. A 429 status with a Retry-After of up to 8s, every account limited, still waits it out twice for the last one as before; one without, or longer, is the agent's at once. TestStreamErrorKeptOpenFailsOverAtOnce (Chat, Responses, Anthropic; one and both limited) fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. yetone 2026-10-02 05:21:31 +08:00
  • 4ae76dfa83 gateway: a test that a Chat reply relayed to a Chat client hands on its reasoning as it comes (Dazzle-sys, #473: dsh on WorkBuddy AI's deepseek-v4.1-flash at max heard nothing for 300s, then pi-ai's stream idle timeout canceled it) — looked into, magpie isn't what kept dsh waiting: a DeepSeek model's reasoning_content goes to the client as each piece arrives (refusesAfterThinking is false for it, so the hold for a refusal lets the stream through at its first thought), and what is held before it (the role chunk, empty deltas, SSE comments) is held 15s at most. A WorkBuddy reply that says nothing for five minutes has nothing magpie could hand on. No behaviour changes. TestChatReasoningRelayedAsItComes fails when the reasoning is held for a refusal as Claude's is. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. yetone 2026-10-02 05:07:30 +08:00
  • efa6fe65b4 dsh: a model through magpie that lists no thinking levels offers none, rather than dsh's own four it then turned away (Dazzle-sys, #473: on LongCat-2.5-Preview, every step of DeepSeek Harness's thinking slider failed with "takes an effort of for this model, not \"low\"") — the Agents page's effort field fell back to dsh's own off, low, high and max when a gateway model's catalog entry listed no levels, while setting one checked the model's own levels, which were none, so the slider offered four stops and each was refused with an empty list. Such a model has no levels in dsh either (its route gives it no reasoningEfforts), so none is offered now and the Agents page shows no thinking control for it, as it does for Claude Code on Haiku 4.5; an effort asked for anyway (magpie dsh effort low) says the model has no thinking levels in magpie, never an empty list. dsh's own models and gateway models with levels are unchanged; no other agent's effort field offers levels its setter refuses. TestDshEffortModelWithoutLevels fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; panel-effort, claude-effort, click-scroll, old-webkit pass. yetone 2026-10-02 04:49:18 +08:00
  • afe0014ebe routing: the live stage seats a provider's accounts and keys in the order they were dragged into, at once (Koohoko, #217: after dragging, the live routing animation kept the old account order) — the stage held each route's rows still by seating them by name, so a provider's accounts read alphabetically (by email, a key by its name) whatever order the provider's page lists them and the gateway tries them in, and a drag moved nothing there. The trace now tells each account's or key's place in its provider's own list (Weighed.Rank, from the order the provider lists them, the one signed in to first), and the stage and its Accounts and keys list seat a provider's rows by it, read from the providers as they are now, so after a drag the stage moves to the new order within a second of Routing being shown, with no reload or new request; a plugin's accounts, listed by its provider's id, follow it too. The order routing weighs them in, and who goes first, are unchanged. TestWeighedRankIsDraggedOrder and routing-account-order.test.cjs (Chromium, WebKit, en, zh) fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestAccountProxy fails on main as well; TestFXInState flaked once under load, passes alone); account-arrange, click-scroll, old-webkit, routing-steady, routing-columns, routing-shape, panel-routing pass. yetone 2026-10-02 04:32:11 +08:00
  • 06caa0961d gateway tests: a Codex sign-in a test makes is listed at once, whichever test ran before (TestAccountProxy failed in the full package, passed alone) — usage: own was sent [], want [acct-1]: TestArrangedKeysAreGatewayCandidates, run just before, looked at the accounts in its own home, and provider.rememberLogins (internal/provider/logins.go:461) looks again only once 30s have passed since, so codexSignedIn's ~/.codex/auth.json for me@example.com was never taken into logins.json; the turn still went to acct-1 by Codex's own sign-in, but LoginUsage("codex") listed only the two saved accounts and never asked for acct-1's usage. codexSignedIn now calls provider.ForgetAccounts, made for a test's home, and again on cleanup, as fallback_test and refusal_accounts_test do. No production change: the 30s wait is magpie's own, and a sign-in or switch in magpie already clears it. go test -tags nogui -count=1 ./internal/gateway/ passes three times in a row; go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build pass. yetone 2026-10-02 04:22:02 +08:00
  • e4c8316e3f gateway: a stream whose connection dropped mid-reply is said to have lost its connection, which an agent retries (#470: OpenCode Go: unexpected EOF) — with several dsh sessions on OpenCode Go, a dropped connection ended each session's stream with Go's own read error, "OpenCode Go: unexpected EOF"; dsh's pi-ai adapter places a failure by its words (429, 5xx, timeout, network/connection/socket…) and took this one for PI_AI_ERROR, which it never retries, so the turn failed. Every session at once, as magpie's requests to one host share one HTTP/2 connection and all its streams end so when it drops. A relayed or translated stream whose read fails mid-reply now ends with "<provider>: connection lost mid-reply (<error>)", which dsh retries as TRANSPORT; one cut before anything was said is still magpie's to try again, as before. TestStreamCutSaysConnection fails without the change; TestStreamCutBeforeContentTriedAgain guards the try again. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestAccountProxy fails on main as well, passes alone). yetone 2026-10-02 04:01:21 +08:00
  • f22218a092 gui: after an update the window shows what changed since the version last run (a Discord user: 点更新后能弹个窗显示更新了啥吗, to see whether their issue was fixed) — magpie updated itself silently, and its release notes were only on GitHub. The version last run is now kept in magpie's own folder (last-version); started on a newer release, the window or magpie web's page asks usemagpie.ai's new /api/notes (beside /api/latest; site/worker.js lists the releases' notes from GitHub, cached as the feed is) for every release since, newest first, without their Install section, and shows them once in a What's new dialog. A fresh install, the same version, a downgrade or a build from source shows nothing; a magpie from before the version was kept, already in use, shows its own version's notes; offline nothing is shown and nothing waits on it; until the site is deployed, the update feed's newest notes stand in when they are the current version's. The tray's panel shows no dialog. The notes are drawn as text, never as HTML: headings, bullets, bold, code, http(s) links, and #NNN links to the issue, opened in the browser as the app's other links are. Settings has a What's new row under Version that opens them again, with a waiting update's notes first, marked Not installed yet; /api/update's notes leave out the Install section too. TestShowNotes, TestStripInstall, TestBetween, TestNotesBetween, TestWhatsNewAfterUpgrade, TestWhatsNewFromBefore, TestUpdateNotesWithoutInstall and whats-new.test.cjs (Chromium, WebKit, en, zh) fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; click-scroll, old-webkit, update-check, update-hide pass. yetone 2026-10-02 03:52:43 +08:00
  • 78aba760aa gui: an agent's instructions opened at the foot of the Library's list leave the page where it is (emo172, #458) — the Library draws its whole page again on a click, so the click's hold (the element clicked, its neighbours and its parents, kept where they were on the screen) found none of them left and held nothing; the page went where the browser took it: down by what opened under the row (scroll anchoring), or clamped as it shut. A held element gone, with every one of its neighbours and parents, is now stood in for by what is at its place in the view's tree, of the same tag and first class, so the row clicked stays put. library-row-open-scroll.test.cjs (Chromium, WebKit, en, zh; a long set in a short window, shorter sets in a tall one) fails without the change (12 of 14); click-scroll, old-webkit, header-zoom-fit pass. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. yetone 2026-10-02 03:34:29 +08:00
  • 7e31cf7438 gui: a zoomed header with room to spare is drawn as at 100% (emo172, #457) — under the webview's page zoom (text size 110/125%) the header's buttons, which sit against its right padding, measure a hair past it (0.00004 CSS px in Chromium at 110%); fitTop took that for buttons running off and drew the header in step by step, at 110% all the way to packed, whatever the window's width. The comparison now allows half a pixel. header-zoom-fit.test.cjs zooms Chromium's page as the webview does and fails without the change; header-centre, text-size, brand, click-scroll, old-webkit pass. yetone 2026-10-02 02:14:07 +08:00
  • 187df65b16 provider: a plugin provider's accounts can be dragged into a new order too (#216) — dragging them failed with "accounts changed; reopen the provider and try again": the account a plugin's provider holds names its agent "plugin", which no list goes by, while the page lists and switches its accounts by the provider's id and logins.json keeps them as plugin:<id>. SetAccountOrder now lists and switches a plugin's accounts by the provider's id and writes their ranks on its plugin:<id> rows, so the order dragged is the order shown and tried. TestAccountOrderPluginAccounts fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestAccountProxy flaked once under load, passes alone). yetone 2026-10-02 02:38:33 +08:00
  • e230b9f31a feat: support handle-free account reordering in all routing modes (#216) 关俊江 2026-10-02 02:34:23 +08:00
  • cbd697693a agents: Claude Code's subagents get no square while there is no model to pick (#468) — before Claude Code runs through magpie its subagents field offers nothing, yet the agents page drew a square that could set nothing; it is left out, as an effort with no levels is. subagent-effort.test.cjs fails without the change. yetone 2026-10-02 03:17:50 +08:00
  • 4c69ff336b agent: Codex's subagents can start at an effort of their own, and Claude Code's subagents can have a model of their own (#468, #469) — Codex reads [agents] default_subagent_reasoning_effort, the effort a spawned subagent starts at when the spawn names none (openai/codex core/src/agent/child_config.rs, config_toml.rs; in codex 0.159.2), and magpie never wrote it. Codex gets a subagent effort field: a square beside its subagents' one that opens the effort slider, unset said to mean the session's effort, or the subagent model's own default. Codex refuses a spawn at a level the subagents' model lacks, so only one it has is taken, and one a new subagent or main model lacks is dropped. Claude Code takes the Agent call's model, then the agent's frontmatter, then CLAUDE_CODE_SUBAGENT_MODEL, then the session's (2.1.287); magpie wrote that env only by itself, the main model while every tier followed it. It gets a subagents field, offered once routed through magpie, whose model outlives a new main model and a tier of its own; unset, the env is written as before. Claude Code has no setting for its subagents' effort beyond each agent's own frontmatter, and omp's task role already takes a thinking level, so neither changes. TestCodexSubagentEffort, TestClaudeSubagentModel and subagent-effort.test.cjs fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; subagent-effort, omp-roles, claude-effort, panel-effort, agent-layout, model-pick, click-scroll, old-webkit pass. yetone 2026-10-02 03:12:20 +08:00
  • 818621b75e profiles: a click on a profile shows what it holds, and its Apply applies it (#467 emo172: a saved profile could only be seen by applying it over the setup in use) — a profile's chip applied it at once, and all the page knew of it was a one-line summary of its models. The state now carries each profile's fields by agent, under the agents' names and the fields' labels, in the order magpie lists them, with the Library's servers, skills and instructions each agent gets (not the instructions' text); a value that reads as a key or a token (a field named for one, a known key prefix, a long run of letters and digits) is sent as hidden, never its text. A click on a chip opens its details under its line in the window, and over it in the tray panel with Apply at their foot by the chip, the chip clicked staying where it is; opening them changes nothing, a second click closes them, Apply applies the profile; ↻ and × stay on the chip. In English and Chinese. TestDetails and TestStateProfileDetails fail without the change, as does profile-details.test.cjs (Chromium and WebKit, en and zh, window and panel). go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestFXInState's TempDir cleanup flakes with or without the change); profile-details, panel-profiles, click-scroll, old-webkit pass. yetone 2026-10-02 03:09:08 +08:00
  • 623307e978 i18n(zh): plainer words where they read as slang, the rest kept (#465) — the provider editor's Forget (dropping the vendor's fetched list) is 清除, not 忘掉; the Providers page's Take a look is 查看, not 看看; an account's forget tooltip says magpie 移除 its sign-in, as its siblings do; and the routing explanations that said {who} 先上 say 排第一, as every other "goes first" there already does. A session's details named its id line 整个会话, the routing affinity option's word for "Session": the line now uses the Session ID key (会话 ID, as the usage ledger's detail has it), and 整个会话 stays where it means the whole session, beside 一轮之内. 知道了 (OK) and 还有 {n} 个 (+{n} more) are kept: standard and clear where they show. plugin-move and provider-picks-stay assert the new words; sessions-manage and sessions-overview fail without the id line's label. plugin-move, provider-picks-stay, sessions-manage, sessions-overview, routing-steady, routing-manual, click-scroll, old-webkit pass (Chromium, WebKit, en, zh); go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. yetone 2026-10-02 02:50:00 +08:00
  • 16f14fdc10 providers: the models picked in one provider's editor never go to another's (#464: Claude models picked on one relay showed in another relay's editor, and its Forget and Refresh kept them) — a Refresh or Forget took the open draft's picks once the vendor answered and, after the list came back, wrote them into whatever draft was open by then; an editor left for another provider's in between was given the first one's picks, shown there as added by hand, and that editor's own Refresh and Forget then kept them as its own. The redraw already keeps the open draft, picks and all, so nothing is put back by hand any more: the other editor has its own picks only, while in the editor the button was pressed in a pick not yet saved and an id the vendor doesn't list (added by hand) still outlive both. provider-picks-stay.test.cjs (Chromium, WebKit, en, zh) fails without the change, for Refresh and for Forget each; click-scroll, old-webkit, provider-typed, unlisted-models, model-test-one, nokey-editor pass. yetone 2026-10-02 02:40:40 +08:00
  • d21f2f6021 agent: read TOML literal-quoted Kimi model keys (#446) Charan Rathore 2026-10-02 00:18:47 +05:30
  • 02276e676d edit: escape backslashes when quoting dotenv values on write (#450) Charan Rathore 2026-10-02 00:18:40 +05:30
  • a409e1552a groups: the groups magpie finds on its own can be switched off, in the Routing view and with magpie group auto off (蓝猫 on Discord: 路由分组会自动创建,可以关闭掉吗) — a model two or more providers serve always became an auto-<model> group, and one could only be removed at a time. A switch right under the routing groups' head, with what it does in words, and magpie group auto [on|off] now keep noAutoGroups in providers.json: off, no found group is listed, catalogued or served, and the groups the user made or changed stay; on, they are back, along with any the user had removed still removed. An agent set to a found group is moved to its model from one provider (the status line and the CLI say which), and a request that still names group/auto-<model> goes to that provider's model rather than failing. Switching off is refused while a group of the user's holds a found group as a member or classifier, so none is left pointing at nothing. TestAutoGroupsOff, TestAutoStandIn, TestAutoGroupsOffHeld, TestFoundGroupOff, TestReseatFoundGroupsOff and TestGroupsFoundSwitch fail without the change; group-found-off.test.cjs (Chromium, WebKit, en, zh), click-scroll and old-webkit pass. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestUserPath a load flake, passes alone; TestClaudeSignedOut fails alone on main as well). yetone 2026-10-02 02:23:40 +08:00
  • 456208945b plugins: a plugin can be installed from a git repository — github:owner/repo, owner/repo, a GitHub URL or a git+https:// one (Lemon on Discord: can plugins be installed directly from git?) — magpie plugin add and the Plugins field took only an npm name or a path, and turned a git spec away as "isn't an npm package name". A git spec now goes to bun add as given and is kept as given in plugins.json; the package it installs is named by its repository's package.json, which bun records as the dependency whose spec it is, so the plugin is known (list, on/off, rm, its version, its README) by that name or by the spec. update and the row's Update run bun update on it, which fetches the repository's commit now where bun add keeps the one bun.lock has, the spec unchanged; npm is never asked for a newer version of it, nor auto-updates it. Adding the same package from another URL, or from npm, takes its entry's place. The Plugins field says it takes a GitHub repo (en+zh); an installed git plugin shows its package's name, an Update button, and a page with its README, no npm link and a Source link to the repository. Nothing that installed before is turned away. TestGitPlugin (a fake bun fetching from local bare repositories, the real one loading the plugin when on PATH) and plugin-git.test.cjs fail without the change; TestIsGit checks what counts as a git spec. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; plugin-git, plugin-local, plugin-market, plugin-updates, click-scroll, old-webkit pass. yetone 2026-10-02 02:16:00 +08:00
  • 84eadbad00 claude: a run waiting for its conversation's next turn is kept an hour, as long as the prompt cache it wrote (#463: large cache writes after a conversation sat idle past 20 minutes) — Claude Code writes Anthropic's prompt cache for an hour on a subscription within its limits (its own help: "Unset = automatic: 1 hour on a Claude subscription within its usage limits", 2.1.286), but the bridge let an idle run go after 20 minutes. The next turn then went to a new run told the whole conversation in one message, a prefix never cached, so all of it was written again while the old run's cache could still have been read. idleLongest is now claudeCacheTTL (an hour); at most idleMost (6) runs are still kept, oldest let go first. A cancelled turn still ends its run: Claude Code's history then holds a turn the client may not have, and without session persistence there is no way to take it back. Prompts, identity lines and headers are unchanged. TestClaudeIdleRunKeptWhileItsCacheLasts fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. yetone 2026-10-02 02:13:05 +08:00
  • febbf29b0f usage: an Antigravity call at a level answered under its model's own name is no swap (#462: gemini-3.8-flash at medium marked Swapped, answered by gemini-3.8-flash) — on an Antigravity account a call at an effort goes out as that level's id (AntigravitySentID: gemini-3.8-flash-medium), and Cloud Code's reply names the family in modelVersion (gemini-3.8-flash), so Swapped compared gemini-3.8-flash-medium with gemini-3.8-flash and the request list and the Routing view tagged the model as swapped for itself. A sent id whose last word is an effort word (provider.EffortFamily: low, medium, high, xhigh, minimal, extra-low, extra-high) now matches a reply naming the id without it; a reply naming another level, or another model, is still a swap. TestLedgerAntigravityReplyNamingTheFamilyIsNoSwap fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestClaudeRunEndsWhenTheClientLeaves, a load flake, passes alone). yetone 2026-10-02 02:07:58 +08:00
  • 4db035682a zcode: a Z.ai and a BigModel (智谱) account on the same phone number, or two accounts ZCode names alike, are kept apart (Bandit on Discord: authorising one replaced the other) — magpie kept ZCode's accounts by name alone, the name being the email, or the phone number of a phone sign-in (<phone>@phone.local), so a sign-in whose name an account already had overwrote that account's key, on whichever site; one named as ZCode's own account was taken for it and not kept at all. A sign-in now keeps the account's user id beside its key, and an account is the same only on the same site with the same id (one kept before ids, by its key's id); another account with the name is kept as '<name> (BigModel)' or '<name> (Z.ai)', numbered when that is taken, and signing in to an account again still updates it in place, by id. There is no cap on the number of accounts. TestZCodeAccountsKeptApart, TestZCodeOldAccountKeptByKey and TestZCodeOwnAccountKeptApart fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestUserPath, a known load flake, passes alone). yetone 2026-10-02 02:10:21 +08:00
  • 876bec1b89 feat(gui): collapse overflowing Sessions agent tabs into a picker (#461) TryWorld 2026-10-02 02:25:17 +08:00
  • 25194cbc0d fix(provider): a Step Plan's credits show when they never reset (#460) yishu 2026-10-01 11:25:11 -07:00
  • a613a9de7d gateway: restart the Claude Code run when the client rewrites the conversation mid-turn (#459) geoqiaoandClaude Opus 5.5 2026-10-02 02:25:05 +08:00
  • 946c1d3bf6 fix(gui): keep agent names visible in narrow windows (#440) TryWorld 2026-10-02 01:50:38 +08:00
  • 0260716c9d feat(usage): 可选的 OTLP 元数据导出(Trace/Metrics,支持 Langfuse) (#412) Aliceandraper 2026-10-02 01:51:04 +08:00
  • e775ea3a72 fix(gateway): 修正取消请求的供应商与模型计量配对 (#456) SadWood 2026-10-02 01:50:16 +08:00
  • da1ee0f81a sync: the menu bar's usage is each computer's own, not synced (yoooo on Discord: usage in the menu bar turned off on a Mac came back on from a Windows box that shows it, over WebDAV) — a sync and a restored backup kept only the window's size, the proxy and the Dock from this computer, so which subscriptions show beside the icon, how often they refresh and their logos came from whichever computer synced last. They are now kept like the others (Settings.KeepOwn, used by both), and left out of what tells a change. TestSync fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. yetone 2026-10-02 01:13:29 +08:00
  • 0faa0c696d gateway: a refusal behind a prefix that isn't JSON, like [HTTP 400], is still read by its structure (#451 follow-up) — the scan for a vendor's JSON error stopped at the first bracket it couldn't decode, so [HTTP 400] {"detail":[…]} fell back to text matching: an echoed metadata in the input was taken for a refusal and a real extra_forbidden was missed. The scan now looks on past such a bracket. text-prefixed-extra and text-prefixed-input-echo in TestRefusedOptionalNamesOnlyUnsupportedFields fail without the change. yetone 2026-10-02 00:58:21 +08:00
  • 3c04877cfc archive: a request the archive kept is opened and downloaded from the Usage page, with its bodies whole (jorben, #447: 请求存档 reachable from usage history, full bodies, download) — the archive's name was only on the Gateway page's recent calls, gone once a call left the list or the gateway restarted, and the archive kept the first 256 KB of each body, the recent list's cut. The usage ledger's row now names the call's archive (archive in usage.jsonl, kept through the Requests page's packed rows), and its details on Usage → Requests show the Request archive panel, Fetch from archive and Download; with the archive on, a request it has no copy of says "Not archived". The bodies are kept whole up to requestArchiveMaxMB in settings.json (32 MB unless set, at most 1024): the response is teed to a temp file as it is sent, the request body written to one when the call ends, so the upload queue holds file handles, not bodies, and one upload at a time reads them; a body past the limit is kept to it and marked cut, with its whole size. What goes upstream is unchanged, the archive stays off unless turned on, headers and bodies are scrubbed as before, and the temp files are removed after the upload or when the queue is full. The page is sent a body to show only when it is 256 KB or less (a longer one is said by its size), an archive over 80 MB is only said to be; Download streams the object whole, to Downloads in the app (archive/export, -2, -3… for a name taken) and as the browser's download in magpie web (archive/file). TestArchiveWholeBodies, TestArchiveLimit, TestRowKeepsArchive and TestArchivePreviewAndDownload fail without the change; TestArchiveOffUsage, TestArchiveTooLargeToPreview, TestArchiveOldPreview keep the rest. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; request-archive-usage (new), request-archive, click-scroll, old-webkit, usage-ledger, usage-ledger-detail, usage-ledger-content, web-page, sse-body pass. yetone 2026-10-02 00:54:41 +08:00
  • 3c6818a833 fix(gateway): recognize optional-field errors after status prefixes (#451) TryWorld 2026-10-02 00:57:55 +08:00
  • de2530826d edit: preserve YAML entries when updating top-level keys (#452) Zhizhen He 2026-10-02 00:57:49 +08:00
  • b643231fc7 edit, provider: stop inline comments leaking into dotenv values (#439) Charan Rathore 2026-10-01 22:27:41 +05:30
  • 03b4cadb1f library: Remove all beside Turn all on/off takes every skill out of the library at once (#449) — a library of many skills could only be emptied a row's Remove at a time. Remove all, in red, asks in the page first, naming the agents that lose the skills, the projects magpie placed them in, the folders that go to magpie's backups and those of the user's own that are only unlinked; Cancel sends nothing. Confirming sends every name in one call, library.RemoveSkills, which takes each as RemoveSkill does, the folders kept aside together under one backup; one that can't be taken out stays, and is said, while the rest go. magpie library skill rm --all does the same, asking y/N on a terminal and, with none, only with --yes. TestRemoveSkills and TestLibrarySkillRmAll fail without the change, as does remove-all-skills.test.cjs (Chromium and WebKit, English and Chinese); click-scroll, old-webkit, every-skill pass. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (bar the known Claude account flake TestClaudeSignedOut). yetoneandClaude Opus 5.5 2026-10-02 00:40:29 +08:00
  • ec19b7fdc9 sessions: Codex rollouts the Codex app has compressed (.jsonl.zst) are read as the plain ones (#448) — with "compress local chat history" on, the Codex app packs older rollout-*.jsonl into rollout-*.jsonl.zst, and rolloutName ended in \.jsonl$, so those sessions, their calls and their usage were gone from Sessions, Usage and the ledger. A .zst rollout is now found, decompressed as it is read (klauspost/compress, already used for dsh's .zstd files; dshOpen now shares openLines), read whole again whenever it changes rather than read on from an offset, and its calls' places are those of its lines decompressed, so a call's content (the detail view) reads the same from either form, and one read before its file was packed still finds it. A rollout there in both forms (the app packing it) is read once, as the plain one, under the same codex:<thread id> key, so the session keeps its id and nothing is counted twice; deleting such a session moves both forms to the trash. TestCodexPackedSessions, TestCodexPackedCalls and TestCodexPackedDelete fail without the change; rollouts packed with the zstd CLI list the same as plain ones in a sandbox CODEX_HOME. yetone 2026-10-02 00:27:14 +08:00
  • bde39aef5e gateway: advertise MAGPIE_PUBLIC_URL in the console and CLIs (#373) czyt 2026-10-02 00:46:45 +08:00
  • ec4ee5a3a4 fix(proc): end what a CLI probe started, on its timeout and when magpie exits (#414) AronnaxandClaude Opus 5.5 2026-10-02 00:42:48 +08:00
  • 1cf069712e agents: dsh says when the model a profile starts on is not one its route lists (#453) Snowyy 2026-10-02 00:42:31 +08:00
  • a3704c422b tests: keep the suite out of a real DSH_HOME, as the other homes already are (#454) Snowyy 2026-10-02 00:42:23 +08:00
  • eb4c4910c3 providers: OpenCode Zen's free models aren't listed (ARNO on Discord: mimo-v2.6-flash-free 403 · OpenCode's free tier can only be used from within OpenCode) — OpenCode serves its -free models to its own client alone and answers anything else 403, while magpie listed them with the paid ones. A preset's Drop names what such models' ids end with, and they are left out of its list; magpie doesn't pass itself off as OpenCode. TestOpenCodeZenDropsFreeModels fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestFXInState flaked once under load, passes alone and on the package rerun). yetone 2026-10-02 00:40:39 +08:00
  • f232a5df45 sessions: the agent tabs scroll under the app's thin thumb on Windows, not its classic scrollbar (ARNO on Discord: 会话面板是不是被改坏了…现在变得好丑) — the strip, and a provider's region picker that shares its class, set scrollbar-width: thin, and in Chromium (WebView2) that drops every ::-webkit-scrollbar rule, so Windows drew its own bar, arrows and all, under the tabs. The strip now leaves scrollbar-width alone and takes the app's thumb, 6px high; on a Mac the overlay bar is as it was. strip-scrollbar fails without the change; click-scroll, old-webkit, sessions-manage pass. yetone 2026-10-02 00:31:16 +08:00
  • d874adbfa6 test: Claude usage tests read windows that reset days from now, not on Oct 1 — /usage's text was fixed at "resets Oct 1 at 3:30pm (UTC)", so from that afternoon on the window read as already reset (Used 0) and TestClaudeWindowsAsked failed every run. The windows now reset one and three days from the day the tests run. yetone 2026-10-01 23:39:33 +08:00
  • 7ed38530f6 gui: the header's tabs stay in the middle when closed up, and halfway between the edge and the buttons when in the row (emo172, #442) — at text size 110% on Windows the header's tabs sat about 67px left of the middle: fitTop's cramped both closed the tabs up and put them in the row, where auto margins left a third of the room to their left and two thirds to their right. The closer tabs are now tried in the middle first; only when they don't fit there does a new inrow step put them in the row, with the buttons' auto margin dropped so the room either side is the same, give or take the header's padding against the row's gap. header-centre.test.cjs fails without the change. yetone 2026-10-01 23:29:23 +08:00
  • 09ee1bbf58 library: every skill on or off at once (#443) yetone 2026-10-01 23:20:07 +08:00
  • 232448855b library: a skill in the library by its name shows as added in Discover (#444) yetone 2026-10-01 23:14:17 +08:00
  • e2e5f4bb64 agents: dsh's patch list written in flow style is edited like a block one, and a file magpie can't edit is named as itself (#445) — a profile's cordis.patch.yml whose top-level array was written as [ { id: some-plugin, disabled: false } ] (valid YAML, and what dsh's own writers keep the template's [] as once they add a row) failed every model switch with "config.yaml is not a list of entries magpie can edit": the patch-list reader took only '- ' lines, and the error named config.yaml whatever the file; since web's list goes first, one such profile stopped all of them. New edit.BlockList reads a file whose one document is a flow sequence as the same list in block style (nested mappings and lists in block style, scalars quoted and tagged as they were, the lines before it and the comments after it kept); dsh's model/effort wiring and its MCP servers (internal/library) read and write such a file through it, and its errors name the real path. dshProfiles puts web first by partition instead of a sort whose less wasn't a strict weak order. TestDshFlowPatchList (agent, library) fails without the change; TestBlockList covers one-line, folded/nested/!!js, empty, CRLF and what isn't a flow list. Sandbox HOME: magpie dsh deepseek-v4-pro on main fails with the config.yaml error, with the change both profiles get the model and web keeps some-plugin. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. yetone 2026-10-01 23:21:35 +08:00
  • bd529a555e netproxy: a host whose IPv6 takes the connection and drops it is reached over IPv4 (tony on Discord: models.dev sync and the update check failed with a bare EOF, direct or auto, while curl got through) — behind a TUN (xray) without IPv6 out, a connection to a site's IPv6 address is accepted, so the dialer's own fallback to IPv4 never starts, and closed once TLS begins. A connection to an IPv6 address that ends before a byte comes back now marks its host:port to be dialed over IPv4 for 30 minutes, and the request that met it is sent once more over IPv4 when nothing of it can have reached the site (HTTPS, or an idempotent method) and its body can be had again; proxied requests and IPv4 connections are left as they are, and TLS, ALPN/HTTP/2, headers and bodies are unchanged. Every transport made with Dispatch (http.DefaultClient, the gateway's) and the update client get it. TestIPv4Fallback fails without the change (Get "https://example.com/": EOF). go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestFXInState flaked on TempDir cleanup under load, passes alone). yetone 2026-10-01 23:17:00 +08:00
  • eeb906e1b7 plugins: the built-in list's ZCode summary says what the registry does (ZCode's free Start Plan isn't offered by the plugin) — the fallback market.json, used until the registry is fetched, still promised the Start Plan; it now reads as registry.json does. go test ./internal/plugin passes. yetone 2026-10-01 23:14:15 +08:00
  • 5200c0a61b gateway: a Cursor tool call whose arguments aren't whole JSON keeps its place in the history (follow-up to #434) — a call's arguments are sent to Cursor as they are, to keep large integers exact; arguments cut short, as the search loop's streamed ones can be, made the whole assistant message fail to marshal and drop out of the history. Arguments that aren't valid JSON are now sent as {} as before #434. TestCursorTruncatedArgsKeepCall fails without the change. yetone 2026-10-01 23:09:08 +08:00
  • 728cb83c47 routing: a request's changed reasoning keeps to its own box, cut with an ellipsis, never drawn over its numbers (#435, azir12345: 文字重叠) — the Requests list's rows share their columns, and the numbers' one is as wide as the longest numbers in the list (three tries · minutes · TTFT · a million tokens), so where a request went could be left next to no room; its reasoning would not shrink and ran on over "15 秒 · 首字 14 秒 · 6.9k token", while a short "· low" still fit. Where it went now gives way first, then the level asked for ("m… → xhigh"), each with an ellipsis, and the level sent stays whole; the reasoning cell clips as a last resort. Safari 15's styles (no subgrid, no container queries) lay each row out by itself and stay as they were. routing-effort-change.test.cjs (Chromium and WebKit, English and Chinese, dark, 1440 to 400px, and with Safari 15's styles) fails without the change; routing-effort-row.test.cjs counts the reasoning's new inner spans as the reasoning. routing-effort, routing-effort-row, routing-kind, routing-served, routing-sessions, routing-columns, remote-group-served, routing-reroute-title, routing-steady, routing-time, routing-flood, request-archive, panel-routing, click-scroll, old-webkit, text-size pass; go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build pass. yetone 2026-10-01 23:02:47 +08:00
  • 4547dafe95 models: Provider in model names has a third way, a name the user gave used just as they wrote it (#92 marsxxl: v0.1.591 的 Codex 下拉里自定义名称又带上了 · WorkBuddy / · Claude Code) — 559ce021 (#203) had a custom name keep " · <provider>" after it like any other, so "Opus 5.5" read "Opus 5.5 · Claude Code" in every list Labels feeds (Codex's model_catalog_json and the gateway's Codex /models, Pi's, OpenCode's, ZCode's, OMP's, Muse's), while #92 had been promised the name as written. Both are kept: the setting is now Off / Not on names I set (自定义名称不带供应商) / On. On stays the default and keeps #203's behaviour, so nobody's list changes unasked; own leaves a name the user gave as it is and a vendor's name with its provider's; off is #335's plain names. Stored as plainOwnNames beside plainNames, so a settings.json of before reads as it did (plainNames: true is off). POST /api/settings/plain-names takes mode on|own|off (a body of on alone still works), magpie model suffix on|own|off, a Settings save keeps it, and /v1/models' magpie_label now follows the setting through Labels as the agents' lists do. The row's hint is one line that fits (en, zh), and its zh name says 供应商 as the rest of the app. Prompts and headers are unchanged. TestSuffixMode, TestSuffixModesReachAgentFiles (Codex model_catalog_json and Pi models.json), TestModelsListSuffixModes (/v1/models magpie_label) and model-suffix-own.test.cjs (Chromium and WebKit, en and zh) fail without the change; TestModelName and TestModelNameAndLevelsReachAgentFiles keep #203's assertions. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestConfiguredModelsAdvertiseImageInput load-flaky, passes alone); plain-names, click-scroll, old-webkit pass. yetone 2026-10-01 22:56:52 +08:00
  • 41dde238dd usage: a sessions bar's name takes the width it needs, its bar what is left (John on Discord: widened, the names stayed cut short with … while the bars grew) — the Sessions overview's projects, models, tools and skills rows held their names to 9em, 10em and 11em whatever the window's width, every extra pixel going to the bar. Each card is now one grid whose rows are subgrids: the names' column is as wide as the longest (minmax(0, max-content)), the bars take the rest and are never under 5em, and every row's bar starts at the same place; narrow, the names are shortened rather than a bar. Without subgrid (before Safari 16) each row sizes its own name the same way. sessions-bar-names.test.cjs fails without the change (196px name shown in 108px at 1600 wide); it, sessions-overview, sessions-calendar-fill, sessions-today, click-scroll and old-webkit pass in Chromium and WebKit, en and zh. yetone 2026-10-01 22:54:49 +08:00
  • cbf51ec7a4 settings: Number units lets Chinese say a large count in K/M/B instead of 万/亿 (John on Discord asked for K/M/B with the UI in Chinese) — the Requests tab's totals and chart axis and the tray panel's Usage tab shortened every count with 万 and 亿 whenever the language was Chinese (ledShort, the one place either is used). A Number units row under Currency, shown in Chinese only, picks 中文(万 / 亿)(the default, unchanged) or 英文(K / M / B), kept as settings.westernUnits and sent with the rest of Settings; picked, ledShort says it as English does (1.54B, 80M), the Usage page and an open panel redraw at once, and the axis labels are still measured before the plot. number-units.test.cjs fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; number-units, click-scroll, old-webkit, usage-chart-axis, text-size, currency, panel-usage pass. yetone 2026-10-01 22:40:12 +08:00
  • 0361189f9e test: every package's tests pass on Windows, and none writes outside its sandbox (the user: 把 Windows 上那几个失败的测试修掉) — on a Windows box the suite failed in most packages, and the root package hung for 25 minutes. Tests set HOME, which os.UserHomeDir ignores on Windows (it reads USERPROFILE), so they read and wrote the runner's own home; each now sets USERPROFILE beside HOME, and Devin's, Goose's, Crush's and Claude Desktop's tests set APPDATA/LOCALAPPDATA, where those keep their files there. said() read the command's output only once it returned, and a pipe holds a few KB on Windows, so magpie model --help blocked forever; it reads while the command writes. Unix permission bits (the gateway key store's 0600 too), exec bits and symlinks Windows lacks are checked only off Windows; TOML strings hold Windows paths as literal strings, JSON as marshalled ones; a URL's path is compared as one. The provider and usage packages run in a home of their own (TestMain, as the gateway's already does), since a CLI's identity kept by a refresh still going when its test ended landed in the runner's ~/.config/magpie — and on a Mac in the user's own. Two product fixes came out of it: imagemcp took a file:///C:/… root or image for the path /C:/…, which doesn't exist on Windows, and now makes a Windows path of it (a host for a UNC share); and session discovery could keep a new Cowork session hidden on Linux, where a folder's mtime is a kernel tick and its size doesn't change, so an entry added within the tick of the last listing left both as they were — a listing made within 2s of its folder's last change is read again, as git does for racy files. TestFilePath and TestRacyDirectoryListedAgain fail without the change. On Windows 11 every package passes (32 of 32) and a run of each package in a fresh home leaves nothing in home, APPDATA or LOCALAPPDATA; on Linux (WSL) and macOS go test -tags nogui ./... passes. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build pass. yetone 2026-10-01 23:05:30 +08:00
  • 6f99e5d0ff fix(gateway): preserve optional-field validation errors (#403) TryWorld 2026-10-01 23:08:36 +08:00
  • e76f119048 gateway: report cache writes in Chat usage (#429) TechnoHouse 2026-10-01 23:08:30 +08:00
  • d5645e57ce gateway: preserve large integers in Cursor tool history (#434) Charan Rathore 2026-10-01 20:38:24 +05:30
  • a97643bcc8 gateway: a translated or relayed stream keeps its client alive while its provider is (#436, congee949: Chat → Responses dropped the upstream's ": keepalive" and Codex hit "idle timeout waiting for SSE" on a live connection) — a reply magpie translates was read event by event and written only when an event had something for the client, so a provider's SSE keepalives, and events with nothing to translate, reached no one, and a reply that thought quietly for five minutes ended in Codex's idle timeout and a retry. Now, whenever the provider is heard from and the client has heard nothing for a second, magpie sends the client a keepalive in its own protocol: response.in_progress again on Responses (Codex's timeout counts events, never a comment — a sandboxed codex 0.159.2 on a fake provider sending only comments timed out, one sending response.in_progress didn't), ping on Anthropic (as Anthropic does; Claude Code's watchdog counts it), an SSE comment on Chat Completions, nothing on Gemini (Google's SDK can't read past one). A provider that sends nothing at all is not covered for, so a dead connection still ends at the client's timeout. A reply relayed from the Claude subscription's bridge, Kiro, Cursor, Devin, search, Qoder, Zed or Command Code is kept alive every 15s while it goes quiet, for up to 5 minutes since its last event. Native passthrough is unchanged, and prompts and headers are unchanged. The routing trace no longer drops a request still going for the 60 finished after it: the oldest finished ones go first, unless more than 120 are going at once. TestTranslatedStreamKeepsClientAlive (the issue's repro: 20ms keepalives, 150ms idle timeout; Chat → Responses, Chat → Anthropic, Responses → Chat), TestRelayKeepsClientAlive and TestTraceKeepsInflight fail without the change; TestTranslatedSilenceStillTimesOut keeps a silent provider uncovered. In a sandbox CODEX_HOME, codex exec through the gateway on a provider sending 6s of keepalives with stream_idle_timeout_ms 2500 answers, and without the change fails with idle timeout waiting for SSE. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. yetone 2026-10-01 22:35:27 +08:00
  • 38f184ae0e signin: Grok and Command Code sign in from magpie in Docker (xugui on Discord: docker版登不上grok和command code) — Grok's sign-in first installs Grok Build with its installer, curl … | bash, and the image has neither bash nor curl, so it stopped at "installing Grok Build failed (exec: "bash": executable file not found)"; the community Grok plugin, beside the built-in rather than moved onto, wasn't given the CLI at all and said "install Grok Build first". Where no shell and curl/wget can run, Grok Build is now installed as its installer does, from the same x.ai/cli (then its storage bucket) address: the stable version, its binary for the platform, checked with --version, linked as ~/.grok/bin/grok and agent, installer = "internal" noted in its config.toml; the plugin's sign-in installs it first too. The device sign-in itself needs nothing back from the browser and was fine. Command Code's Studio posts the key to magpie's 127.0.0.1 port in the background, which a browser outside the container can't reach and no address carries; while it waits, its sign-in now takes an API key made on commandcode.ai/settings/keys and pasted (as Command Code's own CLI offers), checked with whoami and its plan as Studio's was, and a plugin's browser sign-in offers "Use an API key instead", its own api way (the Command Code plugin has one). No identity, header or prompt changes. TestGrokInstallsWithoutShell, TestCommandCodeSignInPastedKey and TestGrokPluginSignInInstallsCLI fail without the change, signin-paste-key.test.cjs too. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; signin-paste-key, signin-paste-codex, plugin-signin, signin-callback, commandcode-risk, plugin-key-hint, signin-link, click-scroll, old-webkit pass. yetone 2026-10-01 22:18:58 +08:00
  • 37ba4deb3d goose: a key magpie adds to config.yaml goes after the whole last entry, never between extensions: and its children (found reviewing #431) — SetYAMLTop put a new top-level key right after the last top-level key line, so on a config.yaml ending in an extensions: block, magpie goose effort off wrote GOOSE_THINKING_EFFORT between extensions: and its indented children and Goose's config no longer parsed. A top-level entry is now its key line plus its indented lines, a sequence written at column 0 under it, and blank or column-0 comment lines between them (and the trailing blank lines of a |+ block scalar); a new key goes after the last such entry, before any trailing blank lines and comments. Setting or removing a key whose value ran over several lines now replaces or removes those lines too. Every other line stays byte for byte. TestYAMLTopAfterBlockEntry (block mapping, sequences, block scalars, no trailing newline, empty file, a block value replaced) fails without the change and checks each result parses with yaml.v3 and keeps the other keys' values. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; the reviewer's goose431.sh repro in a sandbox HOME now yields a config.yaml that parses. yetone 2026-10-01 22:18:12 +08:00
  • cd201d888c sessions: a Sessions page lists an agent's sessions by folder, to resume or delete, and deleted ones wait in magpie's trash to be restored (TJHHHH on Discord: session management like CC Switch's, deleting above all, in the GUI) — sessions were only seen on Usage, with no way to remove one. The new Sessions tab shows, per agent, every session under the folder it ran in, with its title or first prompt, when, how many messages and how big, a filter, its resume command to copy and, in the Mac app, Open in terminal. Picked sessions (or one, by its own button) are deleted after magpie's own dialog: their files (Claude Code's .jsonl, its subagents folder and what it keeps by the id under file-history, session-env and todos; every segment of a Codex rollout; Qoder's, WorkBuddy's, Pi's, oh-my-pi's) are moved into magpie's folder, trash/sessions/<agent>/<when>-<id>, with a manifest, never erased; Trash lists them and Restore puts them back where they were. A session written to in the last minute is left, and said so, as the agent may still be running it. Agents' own indexes (Codex's session_index.jsonl and state database, Claude Code's history.jsonl) are not edited: they are written while the agent runs, and a stale entry there is harmless. OpenCode, ZCode, dsh, Cline and Grok keep sessions in databases magpie doesn't write, and show no delete. With nine tabs the narrowest header gets a further step that draws the tabs in, and past that they scroll sideways rather than push the page wider (560px in English, or at 150% text). The dialog uses the shared confirmAsk/closeConfirmAsk, as the gateway keys' do. TestListAgent, TestDeleteRestoreClaude, TestDeleteRestoreCodex, TestDeleteActiveRefused (fails without the running check) and TestDeleteOnlyKnownAgents run in a temp HOME; sessions-manage.test.cjs (Chromium, WebKit, en, zh) fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; sessions-manage, click-scroll, old-webkit, text-size, session-terminal, sessions-today, brand, rail-tip, web-page, omarchy, model-test-one, gateway-caller-keys, gateway-connect-keys, api-key-usage, api-key-theme pass. yetone 2026-10-01 21:26:02 +08:00
  • 563dc83eef edit: keep YAML top-level strings as strings and unescape single quotes (#431) Charan Rathore 2026-10-01 19:40:26 +05:30
  • 99b1b15d98 claude: Claude Code is told what each magpie model can do, so ultracode works on one with xhigh (#430) — Claude Code run on a gateway (CLAUDE_CODE_USE_GATEWAY, as Claude Desktop's Code tab runs it) takes no model it doesn't know for one with effort, xhigh or max, and "ultracode": true needs xhigh, so on group/…, provider/model and mythos-magpie-<number> it was silently off. While Claude Code is routed through magpie, settings.json's env now has CLAUDE_CODE_MODEL_CAPABILITIES: <model>=effort[,xhigh_effort][,max_effort] for each magpie model with reasoning levels, only the levels magpie knows it has (a Claude model's no more than Claude Code gives it, a model of no known levels not named), lowercased exact ids, the models Claude Code is set to first and the whole within 8000 characters; while Claude Desktop runs on magpie, its mythos-magpie-<number> ids too. It is rewritten as the catalog changes and when Desktop is put on or off magpie, and goes when Claude Code is taken off magpie; a value the user set is left as it is. Prompts and headers are unchanged. TestClaudeModelCapabilities and TestClaudeCapabilitiesSync fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; in a sandbox HOME, claude 2.1.286 -p '/effort ultracode' on the settings.json magpie wrote, in gateway mode: group/auto-deep (xhigh) and its Desktop alias are on, group/auto-mid (no xhigh) and the same settings without the env are not. yetone 2026-10-01 22:01:57 +08:00
  • 92760896e3 usage: the chart's side labels keep the card's padding (John on Discord: 这是bug吗?贴边了) — the plot of the Requests tab and the tray panel's Usage tab started a fixed 44px (34px in the panel) from the left, and the labels were drawn to end 6px short of it, so any label wider than that ran out leftward: "8000 万" in the panel reached past the card's 10px padding to its edge, and "200,000" or "$0.50" in the window crossed its padding too. The labels are now measured as drawn and the plot starts where the widest one ends, so every label stands as far in as the tabs above it, in English and Chinese, for tokens, cost in $ and ¥, and requests, small and large, wide and narrow. usage-chart-axis.test.cjs (Chromium and WebKit, en and zh) fails without the change. click-scroll, old-webkit, usage-ledger-chart, panel-usage, usage-ledger*, usage-refresh, usage-alert, api-key-usage, tray-usages, claude-usage-asked, currency pass; go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. yetone 2026-10-01 21:59:23 +08:00
  • ad1c8fd1ec test(provider): TestFetchNewLeavesListedPlugins waits for the refresh its sign-in started, and TestFetchNewRetriedSoon puts back what it set — signing in with a key (PluginAPIKey) shows the account, which lists the providers while the host's auth event has them stale, so Cached refreshes them in the background; that refresh could end after the test had set the list's time back, rewriting plugin-providers.json, which read as FetchNew asking the plugins again (failed 3 in 5 run after TestFetchNewRetriedSoon, and now and then after TestFetchNewAccount). plugin.Refreshed waits for those refreshes with the host left running, and the test waits before it sets the time. TestFetchNewRetriedSoon now resets newFetches, newSoonAt, wbTokens and the account caches, after waiting for a FetchNewSoon under way. The pair passes 10 in 10 (and with TestFetchNewAccount 10 in 10); ./internal/provider 3 in 3; go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. yetone 2026-10-01 21:47:33 +08:00
  • 171d8dc646 web: behind a reverse proxy, magpie web prints MAGPIE_PUBLIC_URL as the page's link (#372) — with MAGPIE_PUBLIC_URL=https://magpie.example.com the link for other machines was http://magpie.example.com:3430/?k=…, its host on the page's port over plain http, which nothing answers. A public URL with no port of its own is now the link, scheme and path kept; one with a port (a container's published ports, http://<host>:3425) still gives that host on the page's port. TestNetworkLinksPublicURL fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. yetone 2026-10-01 21:09:42 +08:00
  • 58aca0eeb8 providers: an account whose list couldn't be had is asked again a minute on, and opening the panel asks (#422, Q1CHENL: after the in-app update Kiro still offered Auto alone until magpie was restarted by hand) — the update restarts magpie as a new process, whose one try for an account still without its vendor's list was made at start-up, or by the Providers page with 8 seconds for it; one that failed left the account on its fallback (Kiro's Auto) for ten minutes, and only the Providers page asked again, never the panel and its model picker. The retry is now a minute, and GET /api/state (the panel, the window's Agents page) starts FetchNew in the background, no more than once in 15s and never two at once, so the page doesn't wait on vendors. TestFetchNewRetriedSoon fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. yetone 2026-10-01 21:09:41 +08:00
  • bb1aa3acbd feat(gateway): named gateway keys with per-client usage (#344) Ns2Kracy 2026-10-01 21:28:54 +08:00
  • 820413b87f test(gateway): handle intermediate session route poll updates (#424) TryWorld 2026-10-01 20:50:22 +08:00
  • 1adb2b571e feat(routing): group requests by session with names and estimated costs (#396) Zhu Wang 2026-10-01 20:50:15 +08:00
  • 40102f74c0 kiro: a Builder ID sign-in is asked with Builder ID's service profile, so its models are listed (#422, Q1CHENL: only Auto offered while kiro-cli lists Opus 5.5, Sonnet 5 …) — a Builder ID sign-in (kiro-cli's, the Kiro IDE's or magpie's own) names no profile, and Kiro won't list one for it: List-Available-Profiles answers 403 "AWS Builder ID is not supported for this operation" in both regions, so every call needing the account's auth failed — List-Available-Models (magpie fell back to Auto alone), Get-Usage-Limits (the account stayed "Kiro account") and chat. A sign-in kiro-cli keeps with Builder ID's start URL (or none, as kiro-cli takes it), or one saved with provider BuilderId, now takes the service profile the Kiro IDE and kiro-cli use for Builder ID (arn:aws:codewhisperer:us-east-1:638616132270:profile/AAAACCCCXXXX) without asking, and an AWS sign-in whose profile listing is that 403 takes it too. Identity Center sign-ins still have their profile listed. TestKiroBuilderIDListsItsModels and TestKiroBuilderIDFoundByKirosAnswer fail without the change (fake management API). go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestFXInState's temp-dir cleanup flaked once under load, passes alone and with its package). yetone 2026-10-01 20:37:00 +08:00
  • 986a4474fe zcode: the Start Plan's 405 block is said for what it is, not only as a firewall (#425: ZCode 405 Method Not Allowed on GLM-5.3-Flash from DeepSeek Harness, while zcode2api works with the same account) — zcode.z.ai answers a Start Plan request it turns away with 405 "request has been blocked due to unusual activity" (code 3012 "method not allowed"), which magpie read as Alibaba Cloud's firewall blocking the address and told the user to wait or switch network. zcode2api's own write-ups (a137460387/zcode2api "3012 的根因与修复", gakiyukr/zcode2api-plus request.go) find the endpoint checks what a request carries and takes only one with ZCode's own system prompt, which zcode2api injects; magpie's endpoint, method and protocol are the same as theirs, and magpie doesn't pretend to be ZCode. On a Start Plan account the 405 / 3012 / block page now reads "ZCode's Start Plan turns away requests that don't come from the ZCode app itself, and magpie doesn't pretend to be it; it can also be a network block of this IP. Use an account with a GLM Coding Plan, or add another provider to this group", in place of the generic hint, to the agent and in the trace, where the Routing page gives it on its own line in English or Chinese; the group still goes on to its next member. A Coding Plan account and every other provider keep the generic hint. Requests, headers and prompts are unchanged. TestZCodeStartBlockedSaid and routing-zcode-blocked.test.cjs fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestUserPath, a load flake, passes alone); routing-zcode-blocked, routing-blocked, routing-wb-refused, click-scroll, old-webkit pass. yetone 2026-10-01 20:39:41 +08:00
  • a999770531 plugins: a plugin's subscription does what a built-in's does, in seven places it didn't (the user: 要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样) — an installed plugin's provider id is kept for it signed in or not, as a built-in's is: a provider of the user's own isn't saved on it, renamed onto it or imported as it (freeID gives id-2). A plugin's account whose plan lacks the model is tried after the ones that list it, as a Codex Free account behind a Plus, by the list the plugin told for it. Cursor's plugin has fast mode where the built-in Cursor has it: a model with a -fast one is saved :fast in a group and sent service_tier "priority", one without is refused. Removing a plugin's provider hides it as a built-in's is, its accounts kept and shown again from Hidden or by signing in, rather than signing every account out ("stays signed in" said so already). magpie accounts lists, adds, switches and forgets a plugin's accounts by its provider's id or name. Grok's plugin beside the built-in Grok (grok-plugin) searches by itself as Grok does. A plugin's plan is named as its built-in's ("Cursor Pro", "SuperGrok"), and a plugin's resets are shown but have no Use button or Auto-use, which spent Codex's reset of the same user. TestPluginIDReservedSignedOut, TestPluginAccountLackingTheModelGoesLast, TestCursorPluginGroupFast, TestAccountsOfAPlugin, TestGrokPluginSearchesItself, plugin-plan and codex-auto-reset fail without the change; TestPluginSignIn and TestPluginAccounts now expect a removed plugin hidden. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestClaudeSignedOut failed once under load, passes alone); the gateway, provider and plugin suites and TestAccountsOfAPlugin pass on Windows, the plugin tests on Linux. yetone 2026-10-01 20:26:40 +08:00
  • b9895c2ded tests: the gateway's and Devin's tests run on Windows in a home of their own — on Windows os.UserHomeDir reads USERPROFILE, not HOME, so a test that set HOME to a temp dir still found the one USERPROFILE every gateway test shares (and the box's real agents); Devin's CLI keeps its sign-in under APPDATA. Each such test now sets USERPROFILE with HOME (setHome), TestDevinOwnMovesWithPlan sets APPDATA, and the tests whose Claude Code is a shell script skip on Windows, where it can't run and the box's real claude ran instead. Tests only. The gateway suite and TestDevinOwnMovesWithPlan pass on Windows; go test -tags nogui ./... passes on macOS. yetone 2026-10-01 20:26:26 +08:00
  • 7caba10d9c omp: a model's or group's max is offered as max to omp 16.4.0 and later (whqtian on Discord: a routing group with max ticked showed only xhigh in oh-my-pi, while Pi had max) — magpie wrote every model's thinking efforts into omp's models.yml with max taken out and xhigh in its place, since omp 16.3.5's schema stops at xhigh and turns the whole file away over a max; but a group whose levels name both xhigh and max then had no way to max at all, as the gateway keeps an xhigh asked for. omp has taken max since 16.4.0 (pi-ai's changelog; 18.4.8's EffortSchema has it), so an omp on PATH at 16.4.0 or later is now given max as it is; an older one, or one whose version isn't known, keeps xhigh standing in. omp 18.4.8 in a sandbox HOME lists the group's thinking as high,xhigh,max and sends reasoning_effort max with --thinking max. TestOmpGroupOffersMax fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. yetone 2026-10-01 20:16:46 +08:00
  • 41d50e15c8 claude: the Claude accounts saved in magpie are served while Claude Code is logged out, and none is dropped (StringKe on Discord: CC(API) -> Magpie -> CC Subscription, and as soon as I /logout in Claude Code all six accounts I manage in magpie disappear) — magpie served its saved Claude accounts only beside the one Claude Code was signed in to: claudeAccount gave no provider without Claude Code's own credentials (or with claude auth status saying no one is signed in), so the gateway had no Claude account at all and the page said the saved ones "are only offered beside the account it is signed in to". Claude Code itself tells the user to log out: with magpie's ANTHROPIC_AUTH_TOKEN beside a claude.ai sign-in it warns "ANTHROPIC_AUTH_TOKEN overriding Claude subscription login · unset it or /logout to sign it out". magpie keeps every account's sign-in in logins.json, so a logout now signs none of them out of magpie: the account seen last (the one Claude Code was signed in to until then, which magpie saw every 30s) stands in its place, run in a config directory of its own as the others on are, listed on, and it can be paused behind another as Claude Code's own could; the others on are served behind it, the Usage page reads it as a saved account, and the saved-but-signed-out notice is no longer given for Claude. An account whose saved sign-in is unreadable is listed as signed out ("its sign-in is gone; sign in again"), never dropped, and isn't picked to stand in while another has one. An account signed in in magpie while Claude Code is logged out and magpie has Claude accounts no longer signs Claude Code back in (which only brought back its warning); it is magpie's alone, on, and the one served first till then stays on behind it. Prompts, identity lines and headers are unchanged; magpie still never refreshes a Claude sign-in nor asks Anthropic anything itself. TestClaudeAccountsOutliveLogout, TestClaudeSignInWhileLoggedOut and TestSavedButSignedOutClaudeServed fail without the change; TestSavedButSignedOutCanBeRemoved now removes the account from the list. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestFetchNewLeavesListedPlugins, a bun plugin test, failed once under load and passes alone and in a rerun of the package). yetone 2026-10-01 20:09:53 +08:00
  • f45e9737b3 gateway: a conversation stays with the account that answered it across a restart of magpie, and routing goes by each account's last known allowance until the first reading since the start is back (vincentzhang on Discord: in smart routing a restart moved a session to the first account, an update and restart back to the second, and the prompt cache was read by neither) — who answered a conversation last was kept in memory only, so after a restart every conversation was "first" and went to whoever routing put first; and until the accounts' allowances were read again (a first wait of 3s, the read itself up to 30s) none was known, every account counted as unused and the first in order went first, until the reading came back and put the one renewing soonest first again. Who answered is now also kept in affinity.json next to the providers (0600, written atomically, the latest 512 conversations, none older than the 24h a conversation is remembered), read again when a conversation isn't known here and the file changed — after a restart, or written by the magpie handing over. The rules for when a conversation moves are unchanged: within a turn it stays, across turns while the vendor read enough from its cache within the last five minutes, and never to an account resting, all but used up or gone. While the first reading is out, Allowances returns each account's last reading kept in quotas.json, its windows past their reset counted empty. Prompts and headers are unchanged. TestAffinitySurvivesRestart and TestAllowancesBeforeFirstReading fail without the change; TestAffinityFileBounded keeps the file bounded. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestFetchNewLeavesListedPlugins and TestFXInState flake under load on main too, pass alone). yetone 2026-10-01 20:08:50 +08:00
  • a02076dcb8 claude: a Claude account's usage and model test read Claude Code's answer with its verbose output on (KevinXC on Discord: 新版本 claude 额度查询失败了; the card said usage couldn't be read, its tooltip began [{"type":"system","subtype":"init") — with Claude Code's own "Verbose output" turned on (its /config, kept in .claude.json, which --setting-sources "" leaves in force), claude -p --output-format json prints every message as a JSON array, the system init first and the result last, and magpie read only the one-object form: /usage's output was taken for an error and shown as one, and a model test's is_error was missed. The result is now the array's last "result" message when it is an array; the one-object form reads as before. Arguments, prompts and headers are unchanged. TestClaudeVerboseOutput fails without the change; the real Claude Code 2.1.286 with verbose on, in a sandbox config directory, gives an array that now reads to its /usage text. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./internal/gateway ./internal/provider pass. yetone 2026-10-01 20:05:33 +08:00
  • 9c54a560cc provider: a providers.json that can't be read is said where the providers are listed, never backed up or synced as none, and is replaced through a rename (PR #415's review) — #415 made edits refuse a file they couldn't read or decode, but every read for the user still took it as empty: magpie providers said "no providers yet", magpie models "no models yet", the Providers page opened the Add sheet as for a first use, and backup.Collect, so an export and a WebDAV/S3 sync, carried it as no providers, which another computer's Mirror would then take as every provider removed. magpie providers and magpie models now list what is left (the signed-in accounts) and end in the error, the TUI says it over its list, and the Providers page shows a "Your providers file can't be read" box (无法读取供应商配置文件) over the list with magpie's reason and no Add sheet; start-up, the gateway and the catalog still go on with it as empty. Stored and StoredGroups return the read error, so a backup and a sync stop on it with nothing pushed. The error, for reads and refused writes alike, names the file, says magpie left it unchanged and to fix it or move it aside (errors.Is ErrUnreadable). store writes through writePrivate, a 0600 temp file renamed over it (steady.Rename waits out a Windows reader holding it, a symlink keeps its target), instead of os.WriteFile's truncate-then-write, and the read goes through steady.ReadFile. TestFileErrorSaysUnreadable, TestWriteErrorsSayLeftUnchanged, TestStoredRefusesUnreadable, TestStoreReplacesAtomically, TestSyncStopsOnUnreadableProviders, TestProvidersCmdSaysUnreadable and provider-file-error.test.cjs fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; provider-file-error, click-scroll, old-webkit, providers-off-fold, add-sheet pass. yetone 2026-10-01 19:17:44 +08:00
  • f654bec468 gateway: a model's web search goes to a search API of the user's own when no provider can search (#419 by HwFee: let me set up Tavily, Brave or SearXNG with my key rather than lose the search tool) — with no provider that searches by itself and no provider magpie could search with, the gateway took the client's web search tool away. Tavily, Brave Search, Exa, Firecrawl and SearXNG (an address of one's own, a key only if it asks for one) can now be set up, in an order: the injected web_search tool is then offered, and a search goes to the searcher's model as before, or, with none or when it fails, to each search API in turn, called directly with no model, its results handed back as the same hits and text the searcher path gives, so clients see the same web_search_tool_result blocks. The order is provider searches itself, searcher provider, search APIs, strip. They are kept in providers.json (0600) like provider keys; a backup carries them, their keys only when it carries keys, and one restored without keys keeps the key here; sync does the same, and a backup or sync from a magpie before them leaves the ones here alone. magpie search lists them, magpie search add <api> [key] [url=…] adds or updates one, magpie search rm <api> removes one; Settings has a Web search section (联网搜索) to add and remove them. Prompts, identity lines and headers to model providers are unchanged. TestWebSearchBySearchAPI, TestNoSearchAPINoTool, TestBackupSearchAPIs, TestSyncSearchAPIs and search-api.test.cjs fail without the change; TestSearchAPIsEachAsked checks each vendor against a fake, TestSearchCLI the command. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; search-api, provider-search, click-scroll, old-webkit pass. yetone 2026-10-01 19:42:26 +08:00
  • 044f61f50f usage: a custom balance's card says when it was read, shows each of its amounts on a line of its own and a percent as a meter (#420 by emo172: 余额卡片显示读取时间、百分比进度条) — a balance read from a provider's own Balance URL showed one line, "Balance $2.00 · Today $2.50 · Used 90%", and nothing said how old it was. magpie now hands the card the amounts the user's expression names, each with its label, and a "%" one's share; the Usage page shows each on a line of its own, the user's label quiet and the first amount the larger, "Balance" only where no label was given, and a percent as the quota meter, amber from 90%. Every card that was read says so, "Updated 3 minutes ago" (3分钟前更新), and one standing in for a reading that failed just now says "As of … — couldn't be read just now" (截至 …,暂时无法获取最新用量), allowance cards too. The menu bar panel's Balances show the same amounts and meter and an amber "As of" (截至), and the tray's tooltip adds "as of …, couldn't be read just now". One amount alone reads as it did, and the balance line, the CLI and the expression syntax are unchanged. No balance URL, path or site is added: /api/usage/token and unlimited_quota are one kind of relay's and stay the user's to write. TestBalanceParts, TestTrayUsageText and balance-parts.test.cjs fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; balance-parts, click-scroll, old-webkit, quota-families, panel-usage, tray-usages, balance-fix, usage-refresh, currency, claude-usage-asked pass. yetone 2026-10-01 19:33:18 +08:00
  • 6532e018b1 web: the page asks for no Wails runtime in a browser, and its files are asked for again by their content (Jorben on Discord: in the remote web UI the Gateway page had no 请求存档 switch and the usage charts no icons, and /wails/runtime.js was a 404) — nothing in the page or the server holds the archive switch or the charts back from magpie web: the same Handler serves both, /api/settings/archive, /api/archive and /api/usage/requests are there behind the web key, and a sandbox magpie web of v0.1.578 and of main, opened in headless Chromium at 127.0.0.1 and at a non-loopback host, drew the switch with no page error. The 404 was the page's import of /wails/runtime.js, which the app's window has and a browser doesn't; it was caught, and only Close and Maximise use it, so it broke nothing, but a browser now never asks for it. What can hide a version's new parts is an older app.js: the page's files went out with no Cache-Control, no ETag and no date (an embedded file has none), so a cache in front of magpie web (a proxy, a CDN, a tunnel's) could keep an older version's app.js under the new index.html, untouched by a cleared browser cache or a private window. They now go out with Cache-Control: no-cache and an ETag of their content's hash, an unchanged one a 304; boot.js stays no-store, the web key's guard still answers first. TestPageFilesRevalidate and web-page.test.cjs fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; web-page, request-archive, click-scroll, old-webkit, usage-ledger-chart, gateway-fold, model-test-one pass. yetone 2026-10-01 19:32:24 +08:00
  • 2e94643c7b sync: moving between WebDAV and S3 keeps the other's server, and moving back finds it (ARNO on Discord: 我体验了一下s3同步,结果它把我之前配置好的webdav同步设置给删了) — sync.json held one setup, and saving S3 in Settings › Sync and backup (or magpie s3 on) wrote the bucket over it, so the WebDAV address, user and password were gone. sync.json now keeps the other kind's server beside the one synced to (other: its address, user, password or secret, and S3's endpoint, region and path-style), in the same file, readable by the user alone, and never in a backup, as before. Only one is synced to. Moving back with the password left empty uses the one kept, for the same server and user alone; magpie webdav on / magpie s3 on with nothing else given moves back to the kept one, and magpie webdav / s3 say which is kept. In Settings the row says "WebDAV settings kept" (or S3's), the form marks the kind synced to "· on", says whether saving keeps it or moves sync, its button reads "Move sync to WebDAV" when it does, and the other kind's fields show the kept server. A sync.json from before reads as it did; turning sync off still removes it all. TestConfigureKeepsTheOtherKind, TestS3Cmd and sync-other-kind.test.cjs fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; sync-other-kind, s3-sync, webdav-settings-refresh, click-scroll, old-webkit pass. yetone 2026-10-01 19:27:27 +08:00
  • 2199eaf76f claude: a tool Claude Code's ToolSearch loads mid-turn is handed to the running subscription session, not a restart with the conversation as text (StringKe on Discord: with ENABLE_TOOL_SEARCH, tool calls came out as raw XML and the turn stalled) — when the client offered a tool the hidden claude -p was never told of, magpie ended the run and started one told the whole conversation in one message, earlier tool calls and results written as text, and its model went on to write its own calls as text. The new tools now go back with the tool results: magpie's MCP helper declares listChanged, swaps its tools, sends notifications/tools/list_changed and answers the call once Claude Code has listed them again (at most 5s). Prompts and headers are unchanged. TestToolSearchLoadKeepsTheRun and TestNewToolsListedBeforeTheAnswer fail without the change; real Claude Code 2.1.286 against a fake API offers the new tool on its next request. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. yetone 2026-10-01 19:44:12 +08:00
  • a32186a6ba fix(profile): preserve default values in snapshots (#421) TryWorld 2026-10-01 19:43:32 +08:00
  • e4bf875b85 fix(backup): exclude balance tokens from keyless exports (#418) TryWorld 2026-10-01 19:43:27 +08:00
  • e1af812dbf gui: a short window's provider editor scrolls past its models, and a routing group made from a model is added (悠悠哥 on Discord: in a small window the WorkBuddy editor's model list was cut off and the wheel took it no further until the window was maximized; the new group "Hy4 preview" did nothing on 添加, its 推理档位 label at the right and 成员共有/自定 under the labels) — the model chips (.mchips) scroll on their own, 132px high, with overscroll-behavior: contain; in a short window the editor's body shows little more than the list, so the pointer was nearly always over it, and at its ends the held wheel never took the body on to the fields below (the body stopped at 539 of 660px at 800x500). Now the chips are at most 28vh high, and in a window up to 640px tall their ends pass the wheel on to the body; Names & levels is held to 40vh there. The dialog, head and Save already fit any window. A group opened from a model (newGroupWith: the picker's and the provider editor's "Make a routing group of it", the tray panel's ?newgroup=) had no fast in its draft, so Add threw on d.fast.filter and posted nothing, since 10b94023 (unlisted-models.test.cjs has failed since); the editor now fills a draft's members, fast and rules whoever opened it, a throw in Add (or Enter in the name) is said in the status line as "Couldn't save the group: …" (分组没能保存:…), and Add leaves busy (pointer-events: none) once the save is answered, so a refused save can be pressed again. The classifier row's grid cell was left in the grid when it was hidden, so every row after it slipped one cell; it is hidden with its label now. editor-short-window.test.cjs (12) and group-from-model-add.test.cjs (8) fail without the change. Chromium and WebKit, en/zh: editor-short-window, group-from-model-add, unlisted-models, group-member-fast, routing-effort, routing-effort-row, routing-levels, routing-manual, routing-time, routing-compact, settings-groups, agent-model-groups, add-button, add-sheet, library-all, model-test-one, plugin-market, plugin-move, plugin-signin, provider-levels-scroll, saved-signedout-remove, nokey-editor, model-pick, old-webkit pass; click-scroll and plugin-moved-signin timed out under load (avg 239) and pass alone. yetone 2026-10-01 19:15:58 +08:00
  • 4ceef99d4b usage: the Sessions activity calendar fills its card (John on Discord: 这个活跃度怎么没有铺满全部宽度呢?) — a calendar's week was held to 20px, so 118 days (17 weeks) took about a third of a wide card and left the rest empty. The weeks before the range now come in as empty, fainter cells, GitHub's way, as many as the card has room for up to a year, their months named over them, with no tooltip and nothing said about them; the cells grow up to a few pixels to take what is left and stay square. The calendar is laid out again as the window is made wider or narrower: a narrower card gets fewer weeks before, one narrower than the range gets none and its cells shrink as before, and nothing runs past the card. Ranges up to 100 days are bars and already took the card's width. sessions-calendar-fill.test.cjs fails without the change (38% of the card at 1400 wide); sessions-overview's fixture answers /api/usage/quotas with an array, as the Go side writes it. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./internal/gui pass; sessions-calendar-fill, sessions-overview, sessions-today, usage-ledger-chart, old-webkit and click-scroll (Chromium) pass; click-scroll in WebKit fails the same way on the unchanged code under this machine's load. yetone 2026-10-01 18:55:22 +08:00
  • 2a6ba1a20f workbuddy: an added account is told from the others by its uid, and the one WorkBuddy is signed in to stays once WorkBuddy moves on (#413: 添加已提前登录的 WorkBuddy 账号会顶掉默认第一个账号) — magpie matched a new sign-in to a listed account by its name, the nickname: another account of the same nickname replaced the one listed, or was taken for WorkBuddy's own and let go with "signed in". And WorkBuddy's own account is only read from WorkBuddy's file, so signing WorkBuddy in to the next account (to pick it on WorkBuddy's page from magpie) renamed that row, the last account gone, and the one picked, being WorkBuddy's own, was let go: one account at a time. A WorkBuddy or WorkBuddy AI sign-in is now matched by its uid (one of the same name whose uid isn't known is still taken to be it, #155); another account of the same name is added as "name (end of uid)"; the same account renews its sign-in where it is listed, and the one WorkBuddy is signed in to becomes magpie's own sign-in of it, first as it was, so it stays when WorkBuddy signs in to another. The sign-in says so: "该账号已在列表中,已更新登录" in the app, "is already listed — its sign-in was renewed" from magpie accounts add. On the plugin, the host no longer takes two sign-ins of one account id for one account when their uids differ. No request header or user agent changes. TestWorkBuddyAddSameNicknameAsOwn, TestWorkBuddyAddSameNicknameTwice, TestWorkBuddyAddWhatWorkBuddyIsSignedInTo, TestPluginSameNameOtherUID and signin-again.test.cjs fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; signin-again, click-scroll, old-webkit pass. yetone 2026-10-01 18:52:05 +08:00
  • 52082f916f devin: whether a Devin model takes images is what Devin tells its CLI, for models models.dev doesn't know (#417, HwFee: swe-2 and gpt-6-1-sol were said to take no images) — devin models list says nothing of images and models.dev has no entry for Devin's own ids, so swe-2, gpt-6-1-sol and the like were served as text-only and agents dropped the images. When it reads the list, magpie now also asks Devin's GetCliModelConfigs, the read-only call the CLI's own model picker makes, with the same metadata magpie's chats already send. Each ClientModelConfig's supports_images is put on its variant, and on the family and its tiers when all their variants agree: swe-2, gpt-6-1-sol, claude-sonnet-5-5 and kimi-k3 take images, GLM and DeepSeek don't. A model Devin doesn't name, or a failed call, is left to models.dev as before; a list saved earlier takes what Devin said, and a user's own image setting still wins. No prompt, header, user agent or existing request changes. TestDevinImagesFromModelConfigs fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestUserPath and the Claude account tests flaked and pass alone). yetone 2026-10-01 18:49:52 +08:00
  • 47fd3bcb97 agents: Claude Code's alias reads as the model it stands for, and a model it asks Anthropic for itself says it isn't through magpie (StringKe on Discord: "model": "sonnet" showed as a bare sonnet; no ANTHROPIC_BASE_URL in settings.json read as magpie having failed to set it up) — with "model": "sonnet" (or opus[1m], best, haiku, opusplan, default) in Claude Code's settings.json the row showed the word with no logo, matching none of the models offered. The alias is now offered first in the Claude Code group as the model Claude Code takes it for: "sonnet · claude-sonnet-5-5", the newest of its family in the catalog's Anthropic list (no compiled-in table), or the model the user's own env gives that tier (ANTHROPIC_DEFAULT_SONNET_MODEL); [1m] keeps its mark, best is the newest Opus, opusplan names both, default is left to Claude Code's plan. Claude's logo with it; the file is only read, never rewritten, and a model picked in magpie is written as before. Each of Claude Code's own models now carries who it goes to (Anthropic, or the host of the user's own ANTHROPIC_BASE_URL): the row's tooltip says it isn't through magpie, so settings.json names no magpie endpoint and that is expected, and picking one says "straight to Anthropic, not through magpie"; a magpie model says neither. TestClaudeAliasShown and claude-direct.test.cjs fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestUserPath and TestClaudeAccountNamedAsItsLogin, load flakes, pass alone); claude-direct, model-pick, claude-effort, agent-model-groups, plain-names, old-webkit pass; click-scroll's WebKit run fails as it does on main without the change. yetone 2026-10-01 18:47:55 +08:00
  • ee16c6576d autostart: a magpie opened at login comes back from "update and restart" on the Mac (悠悠哥 on Discord: 更新后开机自启动没了) — open at login was never lost: the Mac's launch agent runs magpie as a launchd job, and launchd ends whatever a job started once the job exits, so the shell update.Relaunch leaves to open the new version was killed as magpie quit and magpie never came back. The launch agent now carries AbandonProcessGroup, and the desktop app rewrites one an older magpie wrote in the current form at start, keeping the program path it names; with none it stays off, and a launch agent magpie didn't write is left alone. Two throwaway launchd jobs showed children, detached ones too, killed on the job's exit without the key and living with it. TestLaunchAgentLetsTheRelaunchLive and TestRefreshOlderLaunchAgent fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. yetone 2026-10-01 18:44:31 +08:00