Commit Graph
1429 Commits
Author SHA1 Message Date
yetone bd45fa452a gui: Linux tray quotas reach the bar's tooltip (atie on Discord: on Omarchy, hovering magpie's tray icon showed no quota with 设置 › 用量 › 菜单栏显示额度 on, after opening the panel and restarting) — Wails v3.0.0-beta.24's Linux SetTooltip is a "TBD" no-op: the StatusNotifierItem's ToolTip stayed the {title: "magpie"} it was made with and no NewToolTip was ever sent, so every bar showed "magpie" (Omarchy's bar shows tooltipTitle before Title, waybar ToolTip before Title too, and waybar reads ToolTip again only on NewToolTip). magpie now sets the ToolTip itself after Wails' SetTooltip: through the prop.Properties Wails exported (reached by reflection, so the (sa(iiay)ss) type it was made with is kept and PropertiesChanged goes out), then sends org.kde.StatusNotifierItem.NewToolTip from the same session-bus connection. The quotas go in the title, the part both Omarchy's bar and waybar show; waybar reads it as Pango markup, so &, < and > become look-alikes (sniTip) rather than losing the whole tooltip. Until the tray is up (or should a Wails update move the field) the loop tries again next time. TestSNITipIsSafeMarkup covers the markup. In the omarchy VM (Omarchy 4, Quickshell bar), built there with -tags "production gtk3", in a sandbox HOME with the VM's DeepSeek and Kimi balance keys: the released magpie's item kept ToolTip "magpie" (Title "¥79.15 | $8.83"); this build's ToolTip is (sa(iiay)ss) "DeepSeek · ¥79.15\n\nKimi · $8.83", dbus-monitor shows PropertiesChanged then NewToolTip from the item's connection, and hovering its icon in Omarchy's bar shows both balances. Waybar itself wasn't run (the VM's Omarchy 4 has none); what it does is from its source (sni/item.cpp). go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass but TestProvidersAnswerWhileListsComeIn and TestClaudeSignedOut, timing tests that pass alone. 2026-10-03 20:57:39 +08:00
yetone 58edc00bba gui: a provider with no model picked opens with none ticked, so Select none and Save stays none (#578, #614: Sun1090 — 我点了全不选,并且点击了保存,但是还是显示24个。重新打开供应商,里面也是勾选了24个free模型; 我没点保存,点了只选free 那个按钮,关闭的时候已经生效了)
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.
2026-10-03 20:57:39 +08:00
yetone a55c17f397 gui: Cmd+, opens Settings, Ctrl+, on Windows and Linux (#670: App进入设置没有快捷键吗?mac一般是command+逗号)
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.
2026-10-03 20:57:39 +08:00
yetone 23eed59f7a gateway: a group's auto effort keeps a conversation's reasoning while its cache is warm, going up but not down (#617: Kenoia — with group/core effort "auto", 40 of 42 effort changes between a Pi session's turns on gpt-6.1-sol read 0 cached tokens, against 4 of 834 turns without one)
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.
2026-10-03 20:54:46 +08:00
yetone f0b0153e92 gateway: a routing group's own reasoning levels are the most its members are asked for, whoever asks (#671: 我在设置里限制了最高到high,但是他突破了这个限制)
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.
2026-10-03 20:54:46 +08:00
yetone 72fd0cb42e gateway: a Claude that thinks only adaptively is never sent a thinking budget, whoever wrote it (Keenc on Discord: claude opus 5.5 已经不支持旧的 thinking.type.enabled 了,必须用 thinking.type.adaptive + output_config.effort,magpie 还在按老格式发参数,会导致 API Error: 400) — magpie turned thinking adaptive only where it built an Anthropic request itself, from the model's name in magpie. An Anthropic client's own thinking {type: enabled, budget_tokens} was relayed as it came, so an agent that doesn't know the model (asked by a relay's or magpie's name for it) got the vendor's 400 "claude-opus-5-5 requires adaptive thinking"; and a model named otherwise in magpie than at the vendor (an upstream name, e.g. "opus" for claude-opus-5-5[1m]) was built with a budget, the check reading magpie's name before the vendor's went in. Now forwardOnce, which every request to an Anthropic endpoint goes through (relayed, built, a group's effort fitted, count_tokens), reads the model the body goes with and, for one from 4.6 on, sends thinking {type: adaptive} (its display kept) with the budget as the effort it is nearest in output_config.effort (<=4096 low, <=12000 medium, <=24000 high, above max, as buildAnthropic asks), unless the request names an effort already; "disabled" and adaptive go as sent, and older Claudes (4.5 and before) keep enabled with their budget. claudeVersion also reads opus-5.5 without "claude-" and the old order claude-N-N-family, and caps the major at two digits so a dated id is never taken for a version. TestAdaptiveOnlyModelsNeverGetABudget (a fake Anthropic upstream answering 400 to enabled for 5.5: a relayed budget, Bedrock's spelling, an effort beside a budget, a name mapped after, Chat's reasoning_effort onto a mapped name, disabled kept, Sonnet 4.5 kept on its budget) fails on the old code and passes. Against the owner's real relay in a sandbox gateway under a temp HOME: yylx/claude-opus-5-5 with thinking enabled, budget 10000 answered the 400 with the old build and 391 with this one; budget 31999 (effort max) and Chat's reasoning_effort high answered too. AiHubMix and OpenRouter took enabled on the old build already (they convert it themselves). go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass but TestCodexVersion, a timing test that passes alone. 2026-10-03 20:51:25 +08:00
yetone 64665bb249 codex: a window set on a ChatGPT account's model reaches Codex's own model list (#674)
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.
2026-10-03 20:45:58 +08:00
TryWorld 07644556a7 fix(davsync): scope settings credentials during partial sync (#673) 2026-10-03 20:43:13 +08:00
yetone 64ad965e71 gateway: a request passed on to a remote magpie keeps the session its agent named (#672)
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.
2026-10-03 20:42:16 +08:00
yetone 0d78acd62a gateway: WorkBuddy's plans draw with the image models their config lists (freecss on Discord: let WorkBuddy's subscription image models be magpie's image models) — Settings › Models › image model, the Magpie Image MCP and /v1/images/* now offer a WorkBuddy plan's image models, for the China build (workbuddy) and WorkBuddy AI (workbuddy-ai), built-in or moved onto @magpie-community/opencode-workbuddy-auth. Which models are taken from GET <site>/v3/config, signed as a chat is, by their tags (text-to-image, image-to-image), with no ids written in: today the China desktop sign-in lists hunyuan-image-v3.0-art, WorkBuddy AI gpt-image-2.5-sunburst (which edits too). The list is kept an hour per account (5 min after a failed ask); Drawers answers at once and asks in the background. A drawing goes as WorkBuddy's CLI sends it, to <site>/v2/images/generations or /v2/images/edits in JSON: GPT Image with quality, background, output_format and b64_json; Hunyuan with model, prompt, n and size only; an edit's images as data URLs, to the model itself when it takes images or to its -edit beside it, else a 400 that says the model draws from a prompt only. WorkBuddy's {code, msg, data} is unwrapped (a non-zero code is a 502 with its msg); Hunyuan's COS URL is passed on as the other URL drawers' are. These models are never AutoDrawer's pick, since each image costs the plan's credits. For a moved plan the plugin's fetch signs and sends it: pluginFetch now also takes an absolute URL, only to the plugin base's own host, and Provider.Origin gives a provider's site. TestWorkBuddyDrawsWithItsConfigsModels, TestWorkBuddyAIDrawsGPTImage and TestWorkBuddyPluginDraws (fake upstream: config, generation, edit, envelope error, plugin path, foreign host refused) fail on the old code. Against the owner's real accounts (moved onto the plugin) in a sandbox gateway under a temp HOME: workbuddy/hunyuan-image-v3.0-art drew a 1024x1024 PNG (URL), workbuddy-ai/gpt-image-2.5-sunburst one in b64, an edit of it with gpt-image-2.5-sunburst turned the bird red, and an edit asked of hunyuan-image-v3.0-art got the 400 without a call; no token rotated. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. The GUI's model picker wasn't looked at (nogui sandbox). 2026-10-03 19:55:07 +08:00
yetone 2d5f9c748e gui: the Usage page shows every account again, each as compact as before the trend lines, and remembers which ones are open (ARNO on Discord: 以前每个账号所占的空间很小,基本能一页看到所有账号的情况,现在要一个个手动展开账号,展开后占的空间又特别大,余额走势的线条也分散在各个地方很乱。而且关了magpie后又得一个个展开)
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.
2026-10-03 19:45:33 +08:00
yetone 6e915d794d search: a searcher is asked on the API it searches on, and one that finds nothing gives way to the next searcher and the search APIs (#669: with Automatic resolving to deepseek/deepseek-flash and Tavily set up, every client's web_search got web_search_tool_result.content [], the routing log saying 200) — magpie asks its searcher on Anthropic's API, and DeepSeek's Anthropic API wasn't counted among those that search, so the request was translated onto DeepSeek's first API, Chat; translate chose the API a provider searches on (DeepSeek's Responses) only for a client's request, never for magpie's own search, so DeepSeek got no search tool at all, and deepseek-flash said it couldn't search or wrote its own DSML tool-call markup as text. That text went to the model as what was found, with no page, and was taken as a success, so Tavily was never asked. Now translate sends a searcher to the API it searches on as it does a client; api.deepseek.com searches on Anthropic too (tried: web_search_20250305 there answers web_search_tool_result blocks of real pages with DeepSeek's encrypted_content, and takes magpie's own blocks back in a later turn); a searcher's results are read one by one, title, address and page_age (now carried to the client's block), never its encrypted_content, which stays "" in what magpie sends; a reply with no page and no address in its text is "found nothing", and the search goes to the next searcher magpie would pick (up to 3, within the 2 min), then to the search APIs. TestSearcherAskedWhereItSearches (DeepSeek searching on Responses only: old code asked Chat and gave the DSML text and []), TestDeepSeekNativeSearchResults (DeepSeek's native reply: pages, their ages, no encrypted_content passed on) and TestSearcherFindingNothingGivesWay (a 200 with nothing found goes to Tavily, or to the next searcher first) fail on the old code; TestDeepSeekSearchesOnResponses now expects the Anthropic API to search. With the real DeepSeek key in a sandbox gateway (temp HOME, copy of providers.json, searcher deepseek/deepseek-flash) the issue's curl on aihubmix/glm-5.3-flash got 10 web_search_result pages (weather.com.cn, moji, …) and an answer from them; deepseek/deepseek-flash asked directly with that reply in its history searched natively and answered. No Tavily key here, so the fallback to it was tried only against the fake. go vet ./internal/..., builds (darwin -tags nogui, GOOS=linux -tags nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass but TestClaudeRunEndsWhenTheClientLeaves, a timing test that passes alone 3/3. 2026-10-03 19:38:13 +08:00
yetone dc3deaf09a sessions: Alma's chats are on the Sessions page, read only (mamba on Discord: magpie现在是不是还不能管理alma的会话呢) — Alma keeps every chat in one SQLite database in WAL mode, chat_threads.db in its data folder (~/Library/Application Support/alma on a Mac, $XDG_CONFIG_HOME/alma on Linux, %APPDATA%\alma on Windows): chat_threads (title, "New Chat" until named; model; workspace → its folder; incognito), chat_messages (AI SDK UIMessages in a tree, subagents' under parent_tool_call_id) and usage_records / aux_usage_records (a reply's tokens, cache reads and writes included in input). magpie now lists each chat that has something in it, incognito ones left out, and so are the chats Alma's scheduled tasks run in (thousands of them, which would bury every other agent's sessions), told the way Alma itself tells them: metadata.isCron, or a title starting "⏰ Cron:" (the title is set before the flag; 39 here have only the title): its title (else its first prompt), its folder, prompts and replies, tools and skills (tool-<Name>, dynamic-tool, Skill's skill), and models and tokens from the usage records (the model without Alma's provider id or plugin:<plugin>:<provider>:), priced like the others. It reads through a read-only connection, one read transaction a chat, and reads a chat again only when its own rows changed (updated_at, its messages' count, time and bytes, its usage rows), since the database's files change with every write to any chat. Alma's chats can't be deleted, moved or resumed from magpie: a chat is rows in Alma's live database, with triggers, a full-text index and other tables pointing at it, that only Alma can change safely, and Alma has no command to pick a chat up again; its data folder is in Dirs for discovery. Against a copy of the real database (6126 threads, 25424 messages, 11038 usage rows): 928 chats listed (3763 scheduled ones and the empty ones not), 18300 messages, all titled, every token total equal to the database's sums for those chats; the first listing takes 2.4 s, later ones 0.1 s. TestAlmaSessions (with a chat flagged isCron and one titled "⏰ Cron:" only, each failing without its rule), TestAlmaChatReadAgainWhenItChanges (a title and a usage row changed with no newer time) and TestAlmaModel fail without the change; tests set APPDATA so Windows never reads the real Alma. go vet, builds (darwin, GOOS=linux -tags nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried in the GUI against the live app. 2026-10-03 19:18:46 +08:00
yetone 2eb9266395 goose: runs on magpie as a custom provider that hands Goose each model's window and says which models Goose can send a thinking level (wani on Discord: goose gui不支持设置推理等级,而且上下文也不对)
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.
2026-10-03 19:18:46 +08:00
yetone b43544b27a gui: the quota curves can be turned off, picked once in the allowances' head, and the cards drawing them are calmer (ARNO on Discord: 新版本加上了额度走势后,额度面板变得非常混乱和难以观看了) — since 9ccabdfb every account on the Usage page had its own "2 days | Cycle" segmented control over its curve, so a card with two accounts carried two, there was no way to have none, and the tray's cards drew a thin line each. The range is now one pick in the allowances' head, beside Hide accounts, in the app's own menu (Trends: Off / 2 days / Cycle; 走势:关闭 / 2 天 / 本周期), remembered in magpie.quotaRange as the range was. Off draws no curve on any card, the tray panel's thin lines included; the panel follows a pick made in the window at once (storage event) and keeps it after a restart. The cards themselves: an account after a curve's legend wasn't set apart by the rule every other section gets (the next email sat right under the legend), now it is; the plot is 52px rather than 64; a window far shorter than the span drawn (a 5-hour one over a week's cycle, an all but upright comb) is drawn faint and without its upright even burn; a legend key never breaks inside (a long name ends in …); the curve's head names its range in small print instead of holding the control. quota-curve.test.cjs fails on the old code (no pick in the head, a control on every card, no Off, the 5-hour window's even burn over the week, no rule before the second account) and passes: Off removes every curve without moving the page or the pick, survives a reload, and the tray's line goes when Off is set from another window and stays gone after its reload. In WebKit: quota-curve, click-scroll, panel-usage, usage-fold, account-mask and mobile-web pass; before/after screenshots with seven subscriptions (two Claude, two Codex, Antigravity's five windows, GLM, Kimi) in en and zh, window, tray and a 390px web view. go vet, builds (darwin, GOOS=linux -tags nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass but TestProvidersAnswerWhileListsComeIn, TestUserPath and TestClaudeSignedOut, timing tests that pass alone. 2026-10-03 19:18:46 +08:00
yetone 1baad0d2d4 plugin: pi packages are plugins as OpenCode's are — a pi coding agent package (its extensions) adds providers to magpie: their models, their sign-in, renewals and requests (owner: 我希望全面兼容 pi 插件!!!!!!!!!!) — a package is pi's when its package.json has a "pi" manifest, the "pi-package" keyword, or pi-coding-agent among its dependencies (IsPi, mirrored in host.js). Adding or updating one installs @earendil-works/pi-coding-agent beside it, and the plugin host loads every pi package through pi's own loader (pi.js: discoverAndLoadExtensions and ModelRuntime, with the credentials kept in plugin-auth.json), so an extension's registerCommand, registerTool and pi.on are taken and left unused, and its registerProvider calls become magpie providers. A provider's models keep their context, output, images and the reasoning levels their thinkingLevelMap allows; its OAuth login runs as pi runs it, each onPrompt asked in magpie's sign-in in turn, onAuth's page opened, a later prompt being the code pasted back; a provider with apiKey takes a key; magpie renews through refreshToken and saves the result. Requests arrive as Anthropic Messages and go to pi's stream: system and tools, images, tool results, thinking budgets and effort as the nearest level the model has (and the least it allows when it can't turn thinking off: Antigravity's gpt-oss refused requests without one), max_tokens held to the model's; the answer comes back as Messages SSE or JSON with thinking and its signature, redacted thinking, tool calls (their thought signatures kept for the next turn), usage, and a failure before anything is said as the vendor's status with Retry-After. The Plugins page's search also finds pi packages that bring a provider. Tried with the owner's real Antigravity account through pi-antigravity, next to the Grok OpenCode plugin in the same host: 14 models listed, Gemini streaming with usage, Claude Sonnet thinking with signatures, gpt-oss, tool round trips on gemini-3.8-flash, gemini-3.1-pro and claude-sonnet-4-6, a 400 and Antigravity's 429 passed on with their status. A Claude Code turn didn't get through: Antigravity answers 429 to any system prompt with Claude Code's billing header or its "Claude agent" line, through magpie's built-in Antigravity just the same. TestPiPackage (a fake pi package: models, an OAuth login asking twice with a wrong code then a right one, a key, streaming and JSON answers, thinking and tool signatures back, a 429, a renewal) and TestSearchFindsPiPackages pass. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass but TestClaudeRunEndsWhenTheClientLeaves, TestProvidersAnswerWhileListsComeIn and TestSavedButSignedOutClaudeServed, timing tests that pass alone. 2026-10-03 19:12:19 +08:00
yetone a742ee10b3 antigravity: a Gemini reply that says nothing is an error, asked again, not a turn ended as it should (#667: antigravity/gemini-3.8-flash handed pi content [] and zero usage as a normal stop, the agent's run ending mid-task; #636 Bug 3) — Antigravity's Code Assist stream (and any Gemini one) now and then ends with a STOP and no parts, with only reasoning, or with no candidate at all. translate passed the decoder's KStop on and finished the client's stream as it does any reply: Chat's finish_reason "stop" and [DONE], Anthropic's end_turn and message_stop, Responses' response.completed, and a non-streamed reply of 200 with no content — nothing an agent can tell from an answer, and nothing the holdWriter took for a failure, so nobody asked again. Now a reply from a Gemini upstream (CodeAssist, Gemini) holds its end (KStop, KUsage) until something is said in it — text, a call, a search, an image — and one that ended with nothing said, not cut at its length nor by a safety filter (still a refusal, #248), ends with an error event, "<provider>: an empty reply (the model answered nothing; try again)", or a 502 when not streamed. As a 502 before any content it is held, asked again of the same account (lastRetries) or of the next one, and an agent told it retries rather than ends its turn, as Cursor's and Devin's empty replies already are. Other upstreams aren't changed: Claude ending a turn with nothing after a tool's result is its answer. TestGeminiEmptyReplyIsAnError (Factory's Gemini route, which shares the decoder and translate: STOP with no parts, only reasoning, no candidate; Chat, Anthropic and Responses streamed and Chat not; asked again; text and reasoning cut at MAX_TOKENS still go as they came) fails on the old code with the issue's wire shape and passes. Against the owner's real Antigravity account in a sandbox gateway (temp HOME, port 3488): a prompt asking gemini-3.8-flash-low for no output gave, on the old build, finish_reason stop with no content 3/3, and on this one the error event after the retries 3/3; ordinary replies on Chat (streamed and not), Anthropic and Responses, and a bash tool call, are unchanged. go vet, builds (darwin, GOOS=linux -tags nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass (a first run had TestClaudeAccountNamedAsItsLogin and an internal/gui fetching test fail, timing tests that passed on the rerun). 2026-10-03 19:07:36 +08:00
yetone e61d1da9ce gateway: a model that won't be made to call a tool is asked again with the call left to it (#668: OmO's first turn forces its todo tool, and Claude Opus 5.5 answers 400) — the issue reads the 400 as Anthropic wanting additionalProperties:false on every object of a named tool's schema; it doesn't. Against Anthropic's API (the owner's Claude subscription) the same two tools, schemas without additionalProperties, with tool_choice {type:"tool"} are taken by claude-sonnet-4-5, claude-opus-4-8, claude-opus-5 and claude-sonnet-5, and refused by claude-opus-5-5, claude-sonnet-5-5 and claude-fable-5-1 with tool_choice: type "tool" and "any" are not supported for this model., with or without additionalProperties:false on every object; OpenRouter (Anthropic, Bedrock, Azure, Claude Platform on AWS) answers the same. Anthropic says "tools.N.custom: For 'object' type, 'additionalProperties' must be explicitly set to false" only of a tool sent with strict:true, auto or forced alike, and buildAnthropic sends no strict; the reporter's own capture shows none either, so it is the vendor behind their Claude Pro endpoint holding the forced tool to strict mode. Either way the 400 came from forcing the call on a model that can't be forced, and it was relayed as is, with no fallback. forwardTranslated now asks again when a forced tool_choice (a named tool, or required) is turned away with either error: the call is left to the model (auto), only the named tool offered, as the Claude subscription's bridge does, and the provider isn't sent a forced choice for that model again (unfit, as reasoning-off refusals are). Schemas are unchanged. TestForcedToolChoiceRefused (Chat to an Anthropic-only provider, with each error) fails on the old code. Through a sandbox gateway on an Anthropic-only copy of the owner's OpenRouter provider, a Chat request forcing todo to anthropic/claude-opus-5.5 answered 400 with the old build and a todo call with this one, twice. go vet ./internal/..., builds (darwin, GOOS=linux -tags nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass but TestProvidersAnswerWhileListsComeIn, a timing test that passes alone. A Chat client on a Chat endpoint (passthrough, e.g. OpenRouter's chat) is not changed. 2026-10-03 19:04:19 +08:00
lizhuo ebcd5281c2 gui: open matching allowance cards from macOS menu-bar quotas (#662)
* 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.
2026-10-03 19:01:13 +08:00
yetone 8c652a83f7 antigravity: its 429 to Claude Code's system prompt says it isn't a quota (#666: Claude Desktop 调用 antigravity 恒定报 429) — Antigravity answers "Resource has been exhausted (e.g. check quota)" to any request whose systemInstruction holds Claude Code's billing line (x-anthropic-billing-header: …) or the Agent SDK's identity line (You are a Claude agent, built on Anthropic's Claude Agent SDK.), on every model (gemini-3.8/3.7 flash, 3.5 flash lite, 2.5 flash, claude-sonnet-4-6, gpt-oss), whatever the quota left, while the same words in a message go through. Claude Desktop's chats run the Agent SDK and carry both, so they always failed, while its title requests (no system prompt) and Codex on the same account answered. Not the session id (magpie already sends request.sessionId as -<uint63>), thinking signatures (never sent back) or the User-Agent. magpie doesn't hide the lines from Google; the gateway now adds to such a 429 that it is no quota and that Antigravity turns away Claude Code's and the Agent SDK's system prompt, so no one chases a quota. Reproduced on the owner's real Antigravity account with Claude Code 2.1.288 through a sandbox gateway (11/11 429; system field removed 200; either line alone 429). TestAntigravityTurnsAwayClaudeCodesSystemPrompt. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. 2026-10-03 18:39:29 +08:00
yetone a85b2434ab models: reasoning levels are borrowed again for models Azure also hosts, and a routing group keeps its members' levels when one thinks with none to pick from (ARNO on Discord: routing group在接入harness的时候,没有设置reasoning_efforts。我发现是之前很多能自动推导reasoning_efforts列表的模型现在都推导失败了) — the Azure OpenAI preset (c35471cf, Catalog "azure, openai") was taken for a maker, so models.dev's azure list was the maker's word on every model it hosts: Kimi K2.5, DeepSeek V3.2 and R1, Grok 4.20/4.1 Fast Reasoning, listed there with no levels, took none on a relay or custom provider (and the gateway capped them at high as levelless), where the providers listing any give low…high, none/high and the rest. Azure is now marked Hosts, as Groq and Ollama Cloud are; its own provider still takes its own list's word. And groupEntries narrowed a group to the levels every member has, so one member that thinks with no levels (a thinking switch, a Claude 4.5 with a budget alone, OpenRouter's qwen3.8-omni-flash) left the group none and dsh's route wrote it without reasoningEfforts; such a member is now skipped as an unknown one is, since the gateway sends it the effort asked, no more than high (fitFor). The same Entry.Efforts feeds dsh, Codex's catalog, Pi and OpenCode. On a copy of the owner's config and models.dev cache: no plain model loses levels; group/auto-claude-haiku-4-5 and -sonnet-4-5 get low/medium/high/max again (they had them on 09-30), group/auto-qwen3-8-omni-flash none/low/medium/xhigh; across models.dev, the 34 ids Azure emptied (kimi-k2.5, deepseek-v3.2, deepseek-r1, grok-4-20-reasoning …) borrow again. TestHostListIsNoMakersWord (provider) and TestDshGroupHasItsMembersLevels (agent: the group's dsh entry has reasoningEfforts and the thinking field offers low/high/max) fail on the old code. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. 2026-10-03 18:17:11 +08:00
yetone dd03e431c1 mcpauth: a sign-in asks for offline_access when the authorization server offers it, so Vercel's MCP stays signed in (#615: "vercel老是掉"; Neon, Notion and Supabase stay) — mcp.vercel.com's resource metadata names only the scope "openid" (its 401 names none), and magpie asked for exactly that; its authorization server, https://vercel.com, is an OpenID provider whose scopes_supported lists offline_access, and Vercel's docs say that scope is what issues a refresh token (access tokens last an hour; refresh tokens 30 days, single use, rotated). So magpie's Vercel sign-in had no refresh token, and an hour later Token found it due with nothing to renew it by and marked it dead: "sign in again". Neon (no scopes, refresh by default), Notion ("default") and Supabase (its own scopes) give a refresh token without it. Now discovery adds offline_access to the scope asked for (authorize and registration) when the authorization server's metadata lists it and the scope named any; a sign-in that names no scope keeps the server's default. A rotated refresh token was already saved. The test fake gains OpenID, a Vercel-like server (resource names only "openid", no scope in the 401, refresh token only with offline_access); TestSignInAsksForOfflineAccess fails on the old code (scope "openid", no refresh token, ErrExpired at the first renewal) and passes. A Vercel sign-in made before this has no refresh token and has to be made again once. go vet, builds (darwin, GOOS=linux -tags nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass but TestProvidersAnswerWhileListsComeIn and TestUserPath, timing tests that pass alone. Not tried with a real Vercel account. 2026-10-03 18:09:21 +08:00
yetone 9416a6040a site: the Omarchy intent hit shot reads as its caption — the classifier in 964 ms, glm-5.3 in 5.6 s (owner: 改一改吧) — the shot said 961/972 ms and 5.5 s; re-shot in tokyo-night and catppuccin-latte, retried until the routing card said 964 ms and 5.6 s, en and zh from the same request. Same sizes, so SHOTS is unchanged. 2026-10-03 18:07:42 +08:00
yetone a345af91e5 gui: a plugin card's Update chip updates the plugin (莫 on Discord: 显示更新但是好像没有更新按钮) — on Plugins › Discover a card whose plugin has a newer version showed an "Update" chip that looked like a button and did nothing (a click opened the plugin's page); the Update button was only on the Installed tab. The chip is now that button: it names the version ("Update to v0.0.9" / "更新到 v0.0.9"), updates the plugin without opening its page, shows Updating… meanwhile and goes once it is current, and is disabled while another plugin action runs or the plugin is off. plugin-card-update.test.cjs (en, zh) fails on the old code (no button) and passes; plugin-updates, plugin-market and plugin-check-updates still pass (WebKit). 2026-10-03 18:04:41 +08:00
yetone 191700c4fa gateway: a reply that only thought and then failed is the failure, not an empty 200 — a client asking for the whole reply (not a stream) got 200 with null content and zero usage when the provider thought, then failed with nothing said: the collector's error counted only when no parts came at all, and thinking is a part. Cursor's plugin ends such a turn with "an empty reply" (composer-2.5 with no system prompt, 1 in ~5 on a 45k-token prompt): streamed, the client saw that error; whole, an empty answer it didn't ask again. Now an error with nothing but thinking before it is answered as the error (502, or its own status), in translated replies and Claude Code's. TestThinkingThenErrorIsAnError fails on the old code. Against the real Cursor account in a sandbox: 8 whole requests (chat and Messages) to cursor/composer-2.5, the one that only thought answers 502 "Cursor: an empty reply", the rest as before. Also found while testing Cursor's overflow: a 303,700-token prompt to composer-2.5 / gpt-5.2 (200k) comes back 400 context_length_exceeded after one try (Cursor answers 502 "No system prompt found"); 34k–52k prompts answer with usage. go vet, builds (darwin, GOOS=linux -tags nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. 2026-10-03 18:01:00 +08:00
yetone ac1a8a4f85 gui: Trae CN's plugin shows Trae's logo (owner: trae cn plugin 没有 logo) — the registry gives it the icon "trae", which magpie didn't have, so its tile and provider row had none. Adds lobe-icons' mono TRAE mark as icons/trae.svg (taking the text colour, as qoder's does). TestMarketIconsAreBundled checks every icon the built-in list, the older plugins and the registry name is among the GUI's pictures; it fails without trae.svg. Seen on the Plugins page of a sandbox built from this tree. go vet, builds (darwin, GOOS=linux -tags nogui, GOOS=windows) and go test -tags nogui ./internal/plugin ./internal/gui pass. 2026-10-03 17:47:07 +08:00
yetone 33e59f93da images: a ChatGPT account that won't draw hands the drawing to the next account on (#545) — /v1/images/generations and /v1/images/edits asked only the account a subscription is pinned to, so when chatgpt.com answered its plan 403 {"detail":"Forbidden"} magpie gave up, though a Plus account further down drew it (moving that one to the top fixed it for the user). A subscription's drawing now goes over its accounts in the order its text requests do (candidates, without fallback models, as count_tokens takes them): a 401, 402, 403 or 429 from one asks the next, and only when every one refuses is the last refusal returned. The account that drew is the one recorded in usage. An account set aside for text because its model list lacks the image model while another's is unknown is asked last. A refusal of images alone rests no account for text, and API-key providers draw as before. TestCodexDrawMovesToNextAccount (acct-1 403, acct-2 200) fails on the old code (403) and passes; no account rests after it, and the next drawing asks acct-1 first again. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./internal/gateway/ ./internal/provider/ under a temp HOME pass (gateway and provider, then the whole module). Not tried with real accounts. 2026-10-03 17:31:39 +08:00
yetone ba1e3f2803 site: the Omarchy picker shot shows what its alt says — Codex's picker listing DeepSeek, Kimi and GLM models (owner: 其他页面的截图也检查一下有没有对不上的) — the old shot opened the picker on Routing groups and covered the Codex row, so neither Codex nor Kimi/GLM showed; it is re-shot in tokyo-night and catppuccin-latte with the Codex row (DeepSeek V4 Pro) above the menu and the list at DEEPSEEK, KIMI and ZHIPU GLM, 880x1320. SHOTS picker follows. Checked in the omarchy VM's Chromium: index.html takes /img/omarchy/picker-*.png at its own ratio in both themes and the hero lays out as before. The other Omarchy shots (add, agents, import, panel, providers, routing, usage, nested routing, intent) already matched their alts. 2026-10-03 17:28:13 +08:00
yetone c96a4ba9ac site: /download links work again — since the release notes change (e5f8ff60) latest() returns { rel, degraded }, and /download still read .assets off that, so every download link (/download, /download/mac, /download/linux, /download/windows…) answered 500 (Cloudflare 1101) while /api/latest worked. /download now takes the release from got.rel. A test for /download/linux (302 to the asset) and an unknown platform (404) fails on the old code and passes; node --test site/*.test.mjs passes. 2026-10-03 17:20:58 +08:00
yetone 96e3ef6948 site: the update feed reads only what this worker kept in the edge cache — e5f8ff60 changed the shape of the cached newest release and release list but kept their names, so the entries the previous worker had put there were read as the new shape and /api/latest and /api/notes answered 500 until they ran out (up to 5 minutes after the deploy). The cache names now carry a version (__v2/), a hit is used only in the shape expected, and a cache that can't be read or written is a miss, not an error. A worker test seeds the old entries (and malformed new ones): it fails on e5f8ff60 and passes. node --test site/ passes. 2026-10-03 17:17:37 +08:00
yetone ed586dab15 site: the Omarchy intent-hit shot shows what its caption says — Live's aihubmix/glm-5.3 answering, deepseek-flash telling the intent (owner: 能否对上) — the Omarchy version of intent-routing-hit had Zhipu GLM answer and DeepSeek's deepseek-v4-flash classify, while the page's caption (en/zh/ja) and its plain shot name a custom provider Live with aihubmix/glm-5.3, deepseek/deepseek-v4-pro and deepseek/deepseek-flash. Reshot in the omarchy VM with Live (a pretend relay) holding those three, Coder in that order, rule 2 "writing or fixing tests" sending the turn to live/aihubmix/glm-5.3 and live/deepseek/deepseek-flash telling it in about 0.96 s; tokyo-night and catppuccin-latte, en and zh. The English shot is taller (1220), omarchy.js has its size. Checked in the VM: /docs/intent, /docs/zh/intent and /docs/ja/intent load it at the image's own ratio in both themes. 2026-10-03 17:16:07 +08:00
yetone e5f8ff6026 site, gui: release notes survive GitHub limiting usemagpie.ai, and a failure to get them says so, apart from a release without any (#661) — /api/notes answered 503 {"error":"no releases"} with Cache-Control public, max-age=300 whenever GitHub's API limited the worker's shared IP, and /api/latest fell back to the release page's redirect with notes "" (kept 300 s too), so the update window had nothing to show. The worker now: never keeps an error (503 is no-store); takes the notes from GitHub's releases feed (releases.atom, not API-limited) when the API says no, turning its HTML back into the markdown the app draws and putting back the <!-- lang:zh --> marker GitHub strips (before the first Chinese heading after an English one); keeps the last whole answer for 30 days in the edge cache and serves it (merged with the feed's newer releases) when GitHub fails; keeps a degraded answer 60 s instead of 300; and sends a GITHUB_TOKEN secret to the API where one is set (none is yet). In the app, /api/whatsnew says error (and the release page's url) when the notes couldn't be had, and Settings' What's new opens a dialog saying "Couldn't load the release notes" with Try again and "Open the release page" (through the app), or "No release notes provided." with the release page and no retry for a version with none, instead of a passing toast; en/zh. Tests that fail on the old code and pass: site/worker.test.mjs (API limited: notes from the feed in either language, briefly kept; all of GitHub down: 503 no-store, then the last whole answer; the token sent), TestWhatsNewTellsFailureFromNone, and whats-new-failed.test.cjs (WebKit, en/zh: failure with retry and link, retry showing the notes, none without retry, no click moves the page). The feed parser was tried on the real releases.atom (v0.1.730–739: English and Chinese split as the API's bodies are). go vet, builds (darwin, GOOS=linux, GOOS=windows), go test -tags nogui -count=1 ./... under a temp HOME, node --test site/ and whats-new.test.cjs pass. 2026-10-03 17:12:48 +08:00
Zhizhen He 0c6b4c290a sessions: preserve Windows link kinds and only copy cross-device moves (#663)
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.
2026-10-03 17:11:26 +08:00
yetone a9368c301f site: the nested-routing and intent-routing shots in Omarchy's look too (owner: 把嵌套路由和意图路由的截图也补上) — on Omarchy the docs' Start page (en/zh/ja) shows nested-routing, and the Intent page shows intent-rules, intent-routing-hit and intent-trace (en/zh, ja takes the English) as magpie draws them there, in tokyo-night (dark) and catppuccin-latte (light). They were shot in the omarchy VM's Chromium against a sandbox magpie with pretend vendors: the Coder group with an intent rule judged by Groq llama-3.1-8b-instant, a request routed by it (the trace's timings real), and Coder holding the group Fast. omarchy.js now matches shot names with a hyphen (intent-rules-zh-dark: name intent-rules, -zh) and knows these four, each with its own Chinese height. Checked in the VM: /docs/start, /docs/zh/start, /docs/ja/start, /docs/intent, /docs/zh/intent and /docs/ja/intent under ?omarchy=tokyo-night and catppuccin-latte load /img/omarchy/<name>(-zh)-<mode>.png with the image's own ratio. 2026-10-03 17:05:29 +08:00
yetone ae6b4c9335 gui: a subscription with several accounts shows one of them on Usage and in the tray panel, the others behind "Show N more accounts" (whqtian on Discord: 我这个workbuddy只有4个账号,其实大多数情况下,我不需要显示全部的账号信息,你只要显示一个账号信息即可,其他的可以点击展开再看) — every provider's card listed each account's meters, credits and curve one under another, so four WorkBuddy accounts took the page. Now a card with several accounts shows the one that answered through the gateway last (lastServedAt), else the first, with "Show 3 more accounts" / 展开其余 3 个账号 under it; opened, all are shown in their order with "Show fewer accounts" / 收起其余账号. Opened is remembered by provider (localStorage magpie.usageOpen), for the window and the tray panel alike, each following the other as it is turned; a provider with one account has no button. The folded accounts stay in the page, hidden, so Hide accounts masks them as before; the line between accounts is drawn only after one in sight. The click keeps the page where it is (backToReader). usage-fold.test.cjs (WebKit, en and zh: 4 accounts show the last-served one and the button, opening shows all without moving the page, remembered after a reload, one account has no button, masking covers them, the panel folds the same and the window follows it) fails on the old code and passes; quota-families and quota-pools open the panel's second Antigravity account first. account-mask, quota-curve, click-scroll, mask-emails, credit-days, balance-parts, usage-arrange, panel-usage, tray-usages and usage-accounts pass; the GUI tests failing here fail the same on origin/main. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. 2026-10-03 16:50:31 +08:00
yetone 37809ac722 usage: WorkBuddy's credits are said beside their share — on the Routing page, the Usage page and the menu bar, used or left as the share is (#659)
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.
2026-10-03 16:45:48 +08:00
yetone 901084e3cc site: on Omarchy the screenshots are magpie on Omarchy (owner: 各个截图能否也换成 magpie 在 Omarchy 下面的截图?) — magpie's own windows shot in the omarchy VM (magpie web, a sandbox HOME, pretend vendors behind a local proxy) in tokyo-night for the dark look and catppuccin-latte for the light one: add, agents, import, routing and usage in English and Chinese, panel, picker and providers in English, 26 images under img/omarchy/. In the Omarchy look omarchy.js puts them in place of the page's own, keeping each image's width and taking the new one's height; nested-routing and the intent shots keep the page's. Tried in the VM's Chromium against the site served locally: home, start (en, zh, ja) and import (en, zh) in both themes load the Omarchy shots at their own proportions, intent keeps its own. 2026-10-03 16:43:55 +08:00
yetone 52e2af1a75 factory: the mover takes plugin 0.1.14, which adapts Claude Code 2.1.288's runtime context sent as a system message of text blocks (#658)
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.
2026-10-03 16:34:07 +08:00
Kagari Mimiandhazukisendai e7cd1e6e98 gateway: GLM searches on its Responses API (#660)
Co-authored-by: hazukisendai <251835060+hazukisendai@users.noreply.github.com>
2026-10-03 16:28:57 +08:00
yetone b3ab3e42e9 gateway: a request well past the model's window that a vendor turns away without saying why reaches the agent as too long, at once; Qoder's plugin to 0.2.4 for its token usage (found testing real accounts) — Devin answered a conversation of about 300k tokens for swe-1.7-lightning (a 202,752 window) with a 502 "We are currently experiencing capacity issues…", Qoder one of 1.8M for qmodel (1M) with a 400 "Error in upstream response", each answered at a size the model holds; magpie asked Devin's again in place three times and handed the agent a 502 api_error, so Claude Code and Codex never compacted. tooLongUnsaid (fallback.go) takes a 400/413/422/5xx that says nothing of quota, a rate limit, the account, the model or the conversation's length, for a request whose estimate is 1.2 times the model's window or more (the user's context, else the catalog's; estimate counts four bytes a token, which most text runs under: Devin counted 173,954 tokens of one estimated at 151,851), as one too long: "prompt is too long: about N tokens, more than <model>'s context window of M (<vendor> answered <status>: …)", sent the agent's way (context_length_exceeded, Anthropic's "prompt is too long"), not asked again of it or another account, and nobody rests. TestOverflowTheVendorDidntSay fails on the old code (a 502 asked three times, a 400 handed on) and passes; a request near the window, a small one, a 429, a 401 and a used-up balance keep their errors. Qoder's plugin read its stream only up to the finish, and Qoder sends the usage in a chunk of its own after it, so every Qoder request was counted as 0 tokens: @magpie-community/opencode-qoder-auth 0.2.4 reads it, and the mover's min is raised to it. Tried with real accounts in a sandbox magpie: Devin (chat and Anthropic) and Qoder now give 400 context_length_exceeded after one try, a WorkBuddy and Kiro overflow as before, small requests to all three answer, and Qoder reports its tokens (streamed, not, and with a tool call). go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass but a provider timing test that passes rerun. 2026-10-03 16:14:38 +08:00
yetone f1224a055e sync: a backup on a slow line is no longer cut off at 2 minutes; a server that stops moving is (#657, irx999: a 43 MB backup with many skills to a WebDAV server on a 2.4 Mbps VPS, 305 KB/s, needs 2.4 minutes and every sync failed) — a sync had a fixed deadline in all (2 minutes from the gateway's loop and magpie webdav/s3 sync, 90 s from Settings › Sync now), so a large backup on a slow line never got through, however steadily it moved. WebDAV and S3 requests now go through a watchdog instead (stall.go): the backup's GET and PUT fail once nothing has moved either way for 60 s, each byte of the body sent or read putting it back, and after a PUT's body is all handed over the answer gets 60 s on top of as long as the write took (at least 4 minutes), for what still sits in the system's buffers to leave; the other requests (PROPFIND, MKCOL, HEAD, MOVE, DELETE) have 30 s in all. A stall says so ("the server sent and took nothing for 1m0s (PUT)"). Sync now and the CLI have no deadline; the gateway's loop keeps an hour as a backstop; a wait for another magpie's sync stays 30 s. TestSlowTransferIsNotCutShort (20 MB up and down at a steady trickle, over 5 times the watchdog each way), TestStalledTransferFailsPromptly (a server stopping mid-PUT and mid-GET fails within a second) and TestMetadataRequestHasALimit fail on the old code (no watchdog: the stalled ones hang) and pass. 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 slow WebDAV server. 2026-10-03 16:10:02 +08:00
yetone b6467c9551 codex: Ultra on GPT-6.1 Sol wherever it is served, Copilot's too, with Codex's agents taking parts of the task (#656) — Codex offers Ultra on a model whose entry lists "ultra", and on a thread at multi-agent V2 hands parts of the task to agents of its own; its catalog (models-manager/models.json) lists it for gpt-6.1-sol, gpt-6-sol, gpt-6-astra, gpt-5.6-sol and gpt-5.6-terra, not the Lunas, which stop at max. magpie listed Copilot's gpt-6.1-sol only up to max, so Codex had no Ultra for it. Now an entry for one of those models (by the id the vendor serves: openai/…, dated snapshots) that reaches max lists ultra too, in Codex's catalog and /v1/models, and its Codex entry says multi_agent_version v2, as Codex's own does; GPT-6 Luna still stops at max. A ChatGPT account's entry stays as the backend lists it, and a group with a ChatGPT account in it offers ultra but isn't told V2 (its lead's subagent tasks come sealed, #141); the "subagents on any model" setting still stamps v1 on OpenAI's entries. Ultra is no API's level: the gateway now always sends max for it (fitEffort), even to a model whose list says ultra. Subagent tasks with web_search kept (the translated path) were fixed in v0.1.689 (#613); TestCopilotUltraSubagentWithWebSearch now covers it against a stub Copilot: the lead's spawn_agent comes back with plain arguments and encrypted_function_args [], no "encrypted" goes upstream, and the subagent's agent_message reaches Copilot as text. TestCopilotSolOffersUltra (sol has ultra in /v1/models and Codex's catalog with v2, luna not; ultra goes to Copilot as max, with and without web_search) and TestUltraIsSentAsMax fail on the old code; TestUltraOnModelsThatOfferIt and TestUltraEntriesSayV2 cover entries, groups and the catalog. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried in the Codex App with a real Copilot account. 2026-10-03 16:08:26 +08:00
yetone 22e305b481 site: the Omarchy chip shows on the first visit too — Omarchy was found (the font loaded) before <body> existed, so the chip had nowhere to go and nothing drew it later; render now waits for the body. Tried in the omarchy VM's Chromium with a fresh profile: tokyo-night and the chip on the first load. 2026-10-03 16:06:48 +08:00
yetone 6cab11673e vision: a model whose list says nothing of images is given the image described, and the description is in Routing as its own call (Discord, Fate: since v0.1.725 images reach a text-only model in Codex, but it reads them badly and Routing shows no call to the Vision model) — 19e239c7 tells agents every model takes images while a describer is set, but the gateway described an image only for a model whose list says explicitly it takes none (ImageInput false); a model the vendor's /models says nothing of (ImageInput nil — most API keys' models, which magpie counts text-only and agents were told so before) had the image sent on as it came, so the text-only upstream got the raw image (dropped, or read by whatever it does with it) and the Vision model was never asked. Now, while a describer is set, a model that isn't known to see (its catalog entry's Images: the list's answer, the user's override, else models.dev) is given the description, on every API (Responses and Codex's /backend-api/codex, Chat, Anthropic, Gemini), for its current turn, older turns and tool results (function_call_output's input_image), and as a routing group's member (blindTo). Without a describer an unknown model is passed images as before. The description was already a call through the gateway (agent magpie-vision) but untied to anything; it now has kind "vision" ("Image description" in Routing, zh 图片描述), For naming the agent and model it described for, and the request's session, so it shows in that conversation's Routing beside the turn. The describer's prompt (every text transcribed, layout, values) and the image (sent whole, never downscaled) were already right. TestModelNotKnownToSeeIsGivenTheDescription (data: and https input_image on /v1/responses and the Codex backend path, Chat), TestModelNotKnownToSeeToolAndGroupImages and TestDescriptionIsInRouting fail on the old code and pass. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass but TestUserPath, TestCodexVersion, TestClaudeRunEndsWhenTheClientLeaves and davsync, timing tests under load that pass alone and in their packages rerun. Not tried in the Codex App with a real account. 2026-10-03 16:05:17 +08:00
yetone 27925fc266 provider: OpenCode Zen's Jev routes groups on System One, and a preset's or a subscription's model can be asked on the API picked (Discord, 01huadalang: Zen's "Jev free" was asked as a chat model and failed with no way to switch; OpenCode Go's DeepSeek V4.1 Flash answers on Responses too, and magpie asked it on chat alone, while a custom provider could pick) — Jev isn't an Anthropic model: OpenCode's docs (anomalyco/opencode packages/web/src/content/docs/zen.mdx) list jev-1.13 and jev-1.13-free at https://opencode.ai/zen/v1/systemone alone, and models.dev has no entry for it; asked keyless, Zen answers it there and errors on chat, messages and responses. The opencode-zen preset now has Decide (https://opencode.ai/zen/v1), making Zen a mixed decision provider (#609): Jev is left out of agents' models and is a routing groups' classifier, the free one when Zen is used without a key; a Zen provider saved before gets Decide back in Normalize. Zen's and Go's other models already take their API from models.dev's npm package (catalog.APIOf), which matches the docs. The per-model API custom providers have (settings ModelAPIs, the gateway's Provider.APIs) now reaches presets and subscriptions with more than one API: ModelAPI no longer refuses an account, APIs() is the pick or ListedAPIs (what the vendor's list says, sent to the editor as a model's auto); the editor's model chip has "Asked on: Auto…" on its right-click, opening the app's menu (Auto says what the vendor's list says, then each API the provider has), tagging the chip with the pick, staged and sent with Save as modelPrefs, the page not moved; Names & levels' API row shows for subscriptions too. English and Chinese. TestOpenCodeZenJevDecides and TestPresetAndSubscriptionModelAPI fail on the old code and pass; internal/gui/tests/model-api-preset.test.cjs (WebKit, en/zh) covers the menu, no <select>, no left-border accent, no scroll, the Save's modelPrefs, a subscription's tag and a single-API provider with nothing to pick, and fails on the old assets. A sandbox magpie (keyless Zen) answered POST /v1/systemone with opencode-zen/jev-1.13-free (noul 0.44) and chat on mimo-v2.6-flash-free, Jev not in /v1/models. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass but TestUserPath, a timing test that passes alone; provider-levels-scroll.test.cjs fails the same on origin/main. Not tried with a paid Zen/Go key. 2026-10-03 16:03:32 +08:00
yetone 8f385e113b site: usemagpie.ai draws itself in Omarchy's look on Omarchy, in the theme Omarchy has on (owner: 整个官网和文档页面能否智能识别是不是 Omarchy 在访问,如果是 Omarchy 在访问,那么整个页面风格能否变成 Omarchy 风格?而且能够适配当前 Omarchy 就更牛逼了) — every page (home, /import, the docs in en/zh/ja) loads omarchy.js in a Linux desktop browser only; it tells Omarchy by its font (a page may load local("omarchy"), which Omarchy installs system-wide and nothing else has), remembers it, and from then on paints before the page shows: the theme's palette, the system monospace (Omarchy's font), square corners, no shadows or glass, a solid bar, buttons and what's chosen in the accent, images framed in it. Until magpie tells the theme it takes Omarchy's default (tokyo-night, or catppuccin-latte on a light desktop). magpie's gateway answers GET /v1/magpie/omarchy (name, mode, stamp and the CSS variables magpie's own windows use) to this machine only, readable only by usemagpie.ai (CORS + Private Network Access; MAGPIE_SITE_ORIGIN adds an origin to try the site locally), 404 off Omarchy. The page asks it by itself only once the browser allows it to reach this computer; a chip in the corner (Omarchy's logo and the theme) asks once on a click, lists the 22 built-in themes to pick one, and turns the look off; ?omarchy=1|0|<theme> sets it anywhere. Followed while the page shows: a theme set in Omarchy's menu comes through in about 3 s. Tried in the omarchy VM's Chromium 152 with this magpie (nogui serve): detection, the home page and zh docs in tokyo-night, the chip's menu, following the computer, and omarchy-theme-set catppuccin-latte repainting the open page light. TestOmarchyThemeForTheSite covers 404 off Omarchy, the theme and its CORS for usemagpie.ai only, LAN refused and the preflight. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. The permission prompt from the public site wasn't clicked through in a real browser. 2026-10-03 16:01:53 +08:00
yetone 123594489a plugin: a host stopped mid-renewal saves the new token first, and two hosts never write each other's sign-ins away — a stopped host (quit, plugin update, restart) was killed 2 s after stdin closed even while it renewed a sign-in through auth.refresh, after the vendor had spent the old refresh token, so the account was signed out; and while a host restarts the old one and the new one both rewrote all of plugin-auth.json from what each had read, so one host's save could drop the other's accounts or newer token. host.js now tells magpie how many renewals are under way (event "renewing"), and stop gives a host still renewing after the 2 s up to 15 s in all, until the renewal is saved; with none it stops as fast as before. Every change to plugin-auth.json (setAuth, removeAuth, settle, take, auth.set, a renewal's save, and magpie's SignOut when no host runs) is made under plugin-auth.json.lock (O_EXCL, broken when older than 10 s), reading the file afresh and changing only its own accounts; a renewal's save checks under the lock that the stored sign-in is still the one it renewed, so an older host never writes a stale token over a newer one. TestStopLetsARenewalEnd (a 4 s refresh, the host restarted in the middle: old code kept the spent token) and TestTwoHostsKeepEachOthersSignIns (two hosts saving 40 accounts each at once: old code lost 38 of 80) fail on the old code and pass; TestStopWithoutRenewalIsQuick keeps the quick stop. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass but TestProvidersAnswerWhileListsComeIn and TestClaudeSignedOut, timing tests that pass alone and in their packages rerun. Not tried with real plugin accounts. 2026-10-03 15:47:10 +08:00
yetone 8b23ed03c6 antigravity: models as Antigravity lists them — its picker's order, its context, output and images (0000FF on Discord: Antigravity offers Claude Opus 5.5 and Sonnet 5.5, magpie still shows 4.6) — magpie already lists Antigravity's models live from fetchAvailableModels, with no Claude allowlist; the backend doesn't offer 5.5 yet: the signed-in account's list today (prod and daily, hub 2.19.1) has only claude-opus-4-6-thinking and claude-sonnet-4-6, generateContent answers 404 for claude-opus-5-5, claude-sonnet-5-5 and their -thinking ids, CLIProxyAPI's antigravity registry and Antigravity-Manager have no 5.5, and the AI Developers Forum still asks Google to add it (2026-09-30); agy 1.2.16 names claude-opus-5-5@default only for the enterprise Vertex gateway. So no ids are guessed in; what changes is how a model Google adds arrives: the list was sorted by id (Opus 4.6 before any 5.5) and took context from magpie's catalog by id (Anthropic's 1M, where Antigravity gives Claude 250k) and images from "not gpt-oss". Now models() follows agentModelSorts (the picker's order, newest first; models not in it after, by id), and each model keeps the maxTokens, maxOutputTokens and supportsImages Antigravity gives it; quota windows keep their order. TestAntigravityModelsAsItListsThem fails on the old code (id order, no limits, a text-only model said to see). Against the real account in a temp HOME: Gemini 3.8/3.7/3.6 Flash first, Claude at 250000/64000, GPT-OSS 131072 without images. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass but TestProvidersAnswerWhileListsComeIn and TestUserPath, timing tests that pass alone. 2026-10-03 15:45:01 +08:00
yetone e144db09c2 gui: the Usage page's cards are arranged as the Agents page's rows are, by dragging their logos (owner: 用量页面能否支持供应商排序?像 agents 页面一样)
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.
2026-10-03 15:42:25 +08:00
yetone 2c2ba3eb60 gui: the Hide accounts buttons have their tooltip, and once on it says how to show the accounts again (#653)
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.
2026-10-03 15:33:04 +08:00