The editor took its picks from the models marked on, and with no pick saved magpie marks on the ones agents are served: the vendor's first 24 (Exposed). So a provider with none picked opened with those 24 ticked as if picked, never dashed as v0.1.689 draws the served ones, and the next Save wrote them as picks. Select none and Save did write none, but opening the editor again showed 24 ticked, and a Save from there made them picks. That is also why Free only looked applied without a Save: the buttons only change the editor's draft, and Cancel, Escape or a click outside drop it, but what opened afterwards was the 24 served, ticked, not what was saved. Now the editor starts from the saved picks (the provider's chosen, those of them still served), so none saved opens with none ticked and the served 24 dashed, with the hint saying agents see the vendor's list. Since a fresh provider now opens dashed rather than ticked, a click on a served chip leaves it out as a click on a ticked one does, the rest of what is served picked in its place; a chip not served is picked as before.
provider-draft-picks.test.cjs (30 models, 3 free, none saved: opens with none ticked and 24 served; Free only then Cancel or Escape writes nothing and opens as before; Free only and Save writes the 3; Select none and Save writes [] and opens with none ticked; saved again untouched it stays []) and provider-none-picked.test.cjs (now: opened with none picked, both drawn as served) fail on the old code; model-rate.test.cjs's chip click now leaves the served chip out. go build -tags nogui ./... passes; the provider and model GUI tests under WebKit pass but provider-levels-scroll, which fails on main as well. Chromium doesn't launch on this machine, so WebKit only, and not tried in the app with a real provider.
magpie had no shortcut for Settings: only the gear in the header opened it. Now Cmd+, on the Mac and Ctrl+, elsewhere opens the Settings page from any page of the window, from a text box too, as a Mac app's does; in the tray panel it opens the window on Settings, as the gear does there. Another modifier with the comma (Ctrl on the Mac, Alt, Shift) doesn't, and a browser tab (magpie web) leaves the keys to the browser. The gear's tooltip says the shortcut, "Settings (⌘,)" or "Settings (Ctrl+,)" (data-keys, kept through a language change). magpie has no app menu of its own (Wails' default Mac menu has no Settings item), so the shortcut is the page's, beside the text size's Cmd/Ctrl + − 0.
settings-shortcut.test.cjs (window, a focused text box, the panel asking for window/main?view=settings, other modifiers, the browser tab, the tooltip; en and zh) fails on the old code. go vet ./internal/..., go build -tags nogui ./... and GOOS=windows go build ./... pass; the GUI tests under WebKit pass but those that fail on main as well (33 there: cli-update, group-from-model-add, menu-scroll, old-webkit, provider-levels-scroll, redact-rule-row, routing-compact/manual/time, seg-contrast, session-identity, signin-callback, zcode-site). Chromium doesn't launch on this machine (icudtl.dat missing), so it was tried in WebKit only, not in the app's own webview.
A group with effort "auto" asks its decision model (Jev or a model asked in words) how hard each user turn is as it begins, and the turn's requests ask for that level. Within a turn it holds, but from one turn to the next it moved freely (xhigh → high → xhigh …), and the reasoning effort is part of what the vendor caches a conversation under: a turn asking for another level reads none of the cache back. Kenoia's production data shows it: 237,632 uncached input tokens on a 26 s xhigh → high switch that had just read 230,912 from cache. The agent kept its own effort fixed; magpie changed it.
Now a new turn keeps the conversation's level when the decision model picks a lower one while the cache is warm (keepsEffort): a higher pick is still taken at once, so a hard turn gets the reasoning it needs and a conversation pays for at most one miss per level. A lower pick is taken on a turn whose cache is lost anyway: one after cacheCold (5 min) with neither a request nor an answer (ruleAnswered now keeps when the vendor last answered), or one after the agent compacted the conversation (under half as long as its last request). Tool-result rounds keep their turn's level as before. The trace's rule shows the level kept (pick) and the one the decision model wanted (wanted, beside classified.effort). Where the change would go as a configuration_update that keeps the cache (the MAGPIE_EFFORT_UPDATES preview: Responses, a GPT-6 model, a ChatGPT account or an OpenAI key), the wanted level is sent and the update carries it, so with the preview on auto still follows every pick.
TestAutoEffortKeepsTheCache (high, xhigh up at once, high held at xhigh on the turn and its tool rounds, medium taken after the cache went cold) fails on the old code (the held turn sent high); TestKeepsEffort covers warm, cold, answered since, compacted, up, same and no pick; TestAutoEffortGoesAsUpdate (a group/core of codex/gpt-6-luna, MAGPIE_EFFORT_UPDATES=on, Responses) sends xhigh at the top and u:high before the next user message. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried against a real account.
A group whose 推理档位 is set to its own levels (Group.Levels, #295) only listed them to agents: nothing bounded what was sent. The reporter's group offers none…high with 推理强度 "每轮判断" (effort auto, GPT-6-Luna deciding), and gpt-6.1-sol went out at xhigh: the decision model picks among low, medium, high and xhigh, and that pick was fitted to the member's own levels (fitLevel) only, never to the group's. The same held for an agent asking for a level the group doesn't offer (Pi or Codex keeping xhigh from another model, Claude Code's output_config max or a 60000-token thinking budget) and for a model asked for at an effort by its suffix (group/x:max).
Now every try of a group's member is bounded by the levels the group offers for it (groupLevels: the group's own, else the outermost group in it that names some): the turn's pick, the suffix's effort and the agent's own level (agentEffort, in Chat's, Responses' or Anthropic's words, thinking budgets included) each become the nearest level the group offers (withinLevels, fitEffort's rule) before the member's own fitting; ultra counts as offered where max is. A member the user fixed at an effort ("provider/model:xhigh") is the user's explicit choice and keeps it. A group without its own levels is unchanged.
TestGroupLevelsCapEffort (a none…high group: auto picking xhigh, on the turn's first request and its tool rounds; the agent asking xhigh, max or ultra in Chat and Responses; Anthropic adaptive effort max and a 60000 budget; group/r:max; a member fixed at xhigh) fails on the old code (auto xhigh sent as xhigh) and passes. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried against a real account.
With Codex signed in to ChatGPT through magpie (openai_base_url at
/backend-api/codex), Codex reads the backend's model entries from magpie's
/backend-api/codex/models, which passed them on as the backend gave them:
a window the user set on the codex provider (magpie model context
codex/gpt-6.1-sol 600k, or "*" for all) was in /v1/models but Codex still
got context_window 272000. The model_catalog_json path did the same: an
own model's entry, taken from Codex's models_cache.json, kept the cache's
window instead of the one magpie resolved for it.
Now each native entry takes the window the user set (Provider.ContextOf,
as /v1/models says it) — context_window, and max_context_window raised to
it where the window is past it, so the entry never says less than its
window — and nothing else in it changes; a model with none set keeps the
backend's. The ETag carries the windows set, so setting or taking one away
has Codex ask again. A list made from Codex's cache (the backend not
answering), which keeps what magpie last handed Codex, takes the account's
own list's window back for a model with none set, so a window taken away
doesn't stay. The model_catalog_json entry takes the window magpie
resolved. base_instructions and the rest of each entry are untouched.
TestCodexNativeModelsTakeSetWindows (set windows on, other models as the
backend says, max raised, ETag changing on reset, the cache restored) and
TestCodexCatalogOwnEntryTakesResolvedWindow fail on the old code and pass.
Against the owner's real ChatGPT account in a sandbox gateway under a temp
HOME (codex provider only, gpt-6-sol 600000 and gpt-5.5 400000 set):
/backend-api/codex/models gave gpt-6-sol 600000/872000, gpt-5.5
400000/400000 (max raised from 272000), the other five 272000/872000 as
the backend lists them, all 52 fields and prompts kept; /v1/models agrees
for the two set; no token rotated. go vet, builds (darwin, GOOS=linux,
GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME
pass. A Codex session's usable window wasn't checked.
Kenoia: Pi's X-Magpie-Session and session_id were lost on the hop to a remote magpie, so the remote recorded no session and keyed its affinity by the first user message, which two sessions can share. As reported: forwardOnce builds a fresh request and copies the client's own headers only for Codex (fromCodex) and Claude Code, and passOnCaller, the remote-magpie step, set only X-Magpie-Agent and X-Magpie-Via; sessionOf, nativeSessionOf and conversationID on the remote then found nothing. A Responses body's prompt_cache_key was never touched.
withCaller now also notes the caller's X-Magpie-Session and the first of the agent's own session headers nativeSessionOf reads (session_id, x-session-id, x-opencode-session, ...), by its name, trimmed and at most 128 bytes, and passOnCaller sets them on the request to a remote magpie, so Chat, Responses, Messages, drawings and videos passed on all carry them. passOnCaller is called only for a remote magpie provider, so no vendor is sent them.
TestRemoteMagpieKeepsTheSession (two gateways in-process: a Pi request with X-Magpie-Session and session_id to a Remote magpie provider, on /v1/chat/completions and /v1/responses with a prompt_cache_key) fails on the old code (the remote was sent neither and recorded no session) and passes: the remote is sent both, records session and native session, and the vendor behind it sees neither header. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried between two real computers.
With 17 subscriptions and 29 accounts, a card with several accounts showed one of them and hid the rest behind "Show N more accounts". Opening one showed every account at full size, with resets, credit days and its own trend line, so a page that once fit most accounts filled several screens. The open state was kept per subscription, and ARNO had to open the cards again after quitting.
Now every account of a card is on the page. The one in sight (the last served, else the first) is shown in full. The others are shown in brief: the account row and its windows' names, figures and bars, with the reset times only in the bar's tooltip, and no trend line, credit days, "read" line or resets row (the balance stays). A chevron at the end of each account row opens or folds that account alone. The card's button, now in its head beside the name, opens the rest in full or puts them back in brief. Each account's choice is kept in localStorage magpie.usageAccounts (provider + account), so it survives a restart and syncs with the tray panel. A subscription opened under the old magpie.usageOpen starts with all its accounts in full. A brief account's separator sits where a full one's does, so folding doesn't move the account clicked, and the page doesn't scroll (backToReader). The tray panel shows the accounts open in full (or the one in sight) with the same button. A jump to an account from the tray opens that account.
Tested: usage-fold.test.cjs checks the full/brief split, the per-account chevron (no scroll, the account stays put), the head button, that the choice survives a reload, Hide accounts, and the panel following the window. A new case with 17 subscriptions and 29 accounts at 1280x800 with Trends off has all 29 accounts' bars on the page and at least 15 fully on the first screen (16 now; 15 before the curves at 9ccabdfb^; 12 on v0.1.752, with 12 more only behind buttons). It fails on the old code. tray-cell-click now expects the opened account in magpie.usageAccounts. quota-curve, click-scroll, panel-usage, account-mask, mobile-web, tray-cell-click, quota-families and quota-pools pass in WebKit. Screenshots at 1280x800 in zh: Trends off, 19 of 29 accounts' bars fully on screen (scroll height 1360 vs 2005 with Cycle). go vet ./internal/... and go test -tags nogui -count=1 ./internal/gui/ under a temp HOME pass. Not tried in the built app with real accounts.
magpie never wired Goose to its gateway: its model field offered only Goose's own providers, so a user had to add magpie by hand as a custom provider. Goose then treats every declared model as one that doesn't think (ModelInfo.reasoning defaults to false). With no context_limit, it guesses each window from its own registry by the model's name, or falls back to 128K. Goose's model field now offers magpie's models. Picking one writes custom_providers/magpie.json next to Goose's config.yaml (~/.config/goose, or %APPDATA%\Block\goose\config on Windows), then sets GOOSE_PROVIDER=magpie and GOOSE_MODEL. The file holds only keys Goose reads, from DeclarativeProviderConfig (crates/goose-providers/src/declarative.rs) and ModelInfo (crates/goose-provider-types/src/base.rs):
- engine openai, base_url set to the gateway's /v1, Authorization: Bearer magpie as a header, api_key_env "" and requires_auth false;
- dynamic_models false, so Goose uses this list rather than /v1/models, which has no windows;
- skip_canonical_filtering, so models Goose's registry doesn't know stay in the list;
- for each model, its context_limit (ContextLimitResolver takes it before anything but GOOSE_CONTEXT_LIMIT) and reasoning.
reasoning is true only for the models Goose actually sends a level to on an OpenAI-compatible provider. Those are OpenAI's reasoning models, as is_openai_responses_model in crates/goose-provider-types/src/formats/openai.rs tells them (gpt-5/6, o-series). For every other model (Claude, GLM, DeepSeek, Gemini…) Goose keeps thinking_effort to itself and never sends it, so a Thinking Level shown for them would do nothing. Sync rewrites the file as the catalog changes. Check reports a base_url or header someone else rewrote. Picking no model removes the file. The Notice asks for a restart, because Goose loads custom_providers at start-up. GOOSE_CONTEXT_LIMIT is left alone: it is one global override, and it would beat every per-model window.
What Goose itself can't do:
- On an OpenAI-compatible provider, Goose sends no effort for a model that isn't OpenAI's.
- The in-session thinking menu follows Goose's name heuristics and its registry, not the provider's reasoning.
- Before a session starts, the desktop app's context ring prefers Goose's registry to the declared window.
TestGooseRunsOnMagpie covers the options, the file's keys, the windows, reasoning for gpt-5.5 but not glm-4.6, Check, Sync and removal; the old code fails it. go vet, the builds (darwin, GOOS=linux -tags nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass, except TestProvidersAnswerWhileListsComeIn and TestSavedButSignedOutClaudeServed: these timing tests pass when run alone. Not tried against a real Goose.
* gui: navigate menu-bar quota cells to allowance cards
* gui: fix quota navigation in lightweight and folded views
Route quota hits through the existing status-item monitor rather than relying on local monitor ordering. Recreate released panels through panelWin, and expand hidden accounts before focusing them. Keep panel headers non-draggable and cancel pending navigation after five seconds or purposeful scrolling.
Add a native AppKit subprocess regression that fails with the former nil-panel dereference and verifies runtime-queued focus, closing, and recreation. Extend browser checks for folded accounts, expiry, cancellation, and panel drag state.
Return the original rename error unless the move crosses filesystems.
Permission and sharing errors must not start a copy-and-delete operation
that can leave the destination behind or remove source contents.
Preserve the source Windows symlink's file or directory kind rather than
inferring it from a target that may not exist at the destination yet.
Keep relative targets, Developer Mode handling and long-path support.
Cover rename failures, Linux cross-device moves and Windows link kinds,
and run the focused session tests natively in Windows CI.
Moody-Sin: the Routing page showed a WorkBuddy account as "已用 71%" only, which for a plan counted in credits says little. A quota window now carries its own count when the vendor counts in amounts — amount of limit, in unit (QuotaWindow, QuotaSpan for quota --json and /v1/magpie/quotas, and the routing trace's Weighed, from the window its share is of: Allowance.Count). WorkBuddy's built-in fills it from the resource summary, and @magpie-community/opencode-workbuddy-auth 0.1.8 tells it from auth.usage (amount / limit / unit, passed through host.js); the workbuddy mover's min is 0.1.8. The Routing page's stage and its list of what each account did, the Usage page's meter, the menu bar's tooltip and the TUI say "3,550 / 5,000 credits · 71% used", or "1,450 / 5,000 credits · 29% left" with Left on (积分 in Chinese); a window counted in shares alone reads as before.
quota-credits.test.cjs (WebKit, en/zh, used/left, Routing and Usage pages) fails on the old assets and passes; TestQuotaWindowCount, TestAllowanceCount, TestTraceTellsTheCount, TestPluginUsage (the count through the host) and TestWorkBuddyAccounts cover the Go side. Tried with the real WorkBuddy and WorkBuddy AI accounts in a sandbox magpie: moved onto the local plugin, /api/usage/quotas gives 33.29 / 3900 and 13.47 / 2250 credits and the Usage page reads "33.29 / 3,900 积分 · 已用 0.9%", "3,866.71 / 3,900 积分 · 剩余 99.1%" after a click on the count; not moved, the built-in gives the same for WorkBuddy. The Routing page with two real accounts of one provider wasn't tried (one account each). go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
tedestar found it: anthropicBody adapted a role:"system" message only when its
content was a string, so Claude Code 2.1.288's context as an array of text
blocks reached Factory unadapted and came back 403 Forbidden (shown by Claude
Code as Please run /login). @magpie-community/opencode-factory-auth 0.1.14
(magpie-community/plugins 16ec1b8) adapts each text block as the string one,
keeping the blocks' other fields and non-text blocks; keepMovedCurrent moves
every moved user onto it. bun test in packages/factory passes (the new #658
test fails on 0.1.13). No live Factory account: tried with the captured shape
against a stubbed fetch only. go vet, builds (darwin, GOOS=linux,
GOOS=windows) and go test -tags nogui -count=1 ./internal/provider/ under a
temp HOME pass.
A card's logo is its handle. A grip shows by it only while the pointer is on the card, as on Agents and Providers. The card is dragged across the grid: dragCards is dragRows in two directions, the slot nearest its middle is where it goes, and the cards between move a slot over. Alt+arrows move it from the keyboard and keep the focus on it. The order is settings usageOrder, by provider id, saved by POST /api/usage/arrange. It is apart from the Providers order, which is also the order providers are tried in. POST /api/settings keeps it. A card it doesn't name follows in magpie's own order, and an order naming one not on the page now keeps it after them. The tray panel's Usage tab follows the same order.
usage-arrange.test.cjs covers the hover-only grip, a drag across the grid, Alt+arrows, a click that moves nothing, the order after a reload, and the panel. It passes in Chromium and WebKit, en and zh. TestSettingsSaveKeepsWhatItDoesNotSend covers usageOrder. go test ./internal/gui ./internal/settings under a temp HOME, go vet, and builds (darwin, GOOS=linux, GOOS=windows) pass. Also tried on a sandbox magpie web with faked quotas: the drag, the keyboard and a reload kept the order in WebKit and Chromium. The other Usage/panel UI tests pass, except credit-days.test.cjs, which fails about 1 run in 4 on main too.
Geequlim's account names were masked on Routing and they couldn't tell how to see them: the Routing and Usage "Hide accounts" buttons (and the request log's "Clear filter") carried their tooltip as data-tt="…" with no title, and translateStatic only read title, so no tooltip ever showed. A data-tt with a value is now the title's English; and while accounts are hidden the button's tooltip reads "Accounts are masked for a screenshot — click to show them" (账号已打码,方便截图分享,点一下即可显示). account-mask.test.cjs checks both and fails on the old code; it and mask-emails.test.cjs pass in WebKit (en, zh). go vet ./internal/gui, builds (darwin, GOOS=linux, GOOS=windows) and go test ./internal/gui under a temp HOME pass.