Commit Graph
1126 Commits
Author SHA1 Message Date
yetone 5df609e525 usage: an Antigravity account shows each group of models' 5-hour and weekly allowance, one row a group (a user on Discord: 这里显示5h 和一周剩余比较好吧,现在是只显示5h,这3个模型都是一样的,没必要分开…多加个7day 条就好) — fetchAvailableModels gives each model only its 5-hour figure, the same for every model drawing on one quota, so the Usage page, the panel and the account rows showed a figure a family (Gemini, Claude, GPT-OSS) and no week. Antigravity's retrieveUserQuotaSummary reports the quota a group ("Gemini Models"; "Claude and GPT models", Claude and GPT-OSS sharing one), each with a 5h and a weekly bucket; magpie now reads it beside the models' (best-effort: without it the windows are as before) and adds each group's windows, named "5 hours" and "7 days" with their pool (Gemini, Claude & GPT), each model's window naming its pool. The Usage page shows Gemini · 5 hours, Gemini · 7 days, Claude & GPT · 5 hours, Claude & GPT · 7 days, the pool's models in each tooltip; the panel four rings (Gemini 5h/7d, Claude 5h/7d); the account rows each pool with its 5h and 7d meters; the TUI the pools' windows in place of the models'. "Every model" lists the models' own windows. The pools' windows are aside for routing, which reads the models' as before; the tray's text is unchanged. TestAntigravityQuotaPools and quota-pools.test.cjs (Chromium, WebKit; en, zh) fail without the change; quota-families, click-scroll, old-webkit, tray-usages, panel-usage pass. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-02 07:03:17 +08:00
yetone cb761abdd1 commandcode: a reply is asked for within Command Code's 200000 tokens, and the agents are told no more (Discord: every Command Code model, free or paid, failed with "Too big: expected number to be <=200000 at params.max_tokens") — magpie passed the client's max_tokens to /alpha/generate as it came, and told the agents each Command Code model's reply limit as models.dev gives it: DeepSeek V4's 384000, MiniMax M3's 512000, Space Bunny Alpha's 524288, Kimi K2.x's 262144, so an agent asking for what it was told had the whole request refused. params.max_tokens is now the asked number within the model's own limit as models.dev gives it and within 200000; less is sent as it was, and none asked is still the CLI's 64000. The Command Code subscription's models are listed to the agents (their configs, /v1/models) with a reply limit of at most 200000, a user's own limit included. Prompts and headers are unchanged. TestCommandCodeGoMaxTokens and TestCommandCodeOutputCapped fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-02 06:58:18 +08:00
yetone 134a788821 settings: magpie's own update checks can be turned off, and run every 30 minutes, hour, six hours or day (#472: 更新行为可配置) — the app asked the feed five seconds after start and every six hours, a constant, and downloaded any newer version at once; with releases out most days, a version out just after a check waited up to six hours, and there was no way to stop the checks or the downloads they start (Update button only hides the pill). Settings → About has Automatic updates (on by default) and Check every (30 min, 1 h, 6 h, 24 h; 6 h with nothing set), kept in settings.json as noAutoUpdate and updateEvery, and magpie update auto [on|off] [30m|1h|6h|24h] sets them from the terminal. The updater reads them each minute and asks once the interval since the last check, its own or a click on Check, is up, and never by itself while off; the version row, not asked yet, then says automatic updates are off and offers Check. Check every stays in place, dimmed, while off, so the click doesn't shorten the page under it. TestUpdateDue, TestUpdateAskedAt and TestUpdateAutoCmd fail without the change, as does update-auto.test.cjs (Chromium, WebKit; en, zh). go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; update-hide, update-check, whats-new, settings-groups, click-scroll, old-webkit pass. 2026-10-02 06:42:15 +08:00
yetone 03ab46dda3 library, plugins: the installed lists are sorted as the reader picks, and remember it (#481: the installed MCP servers, skills and plugins had no order the user could choose; the plugins came in the order they were installed) — the Library's MCP servers and the installed plugins now have A→Z and Z→A beside their list, A→Z until one is picked, so the plugins are no longer in the order they were installed in; the Library's skills have By source (the groups by repository and their parts, as before, and still the default), A→Z and Z→A, the last two one flat list of every skill by name, the filter still finding a skill by its repository. Each list keeps its own pick in the browser's storage (magpie.sort.<list>, read and written in try/catch), and a pick redraws only its list, the control left where it was on the screen. The skills' heading, the fullest, goes on to a second line in a narrow window rather than past the page's side. Sorting by use or by when a thing was installed waits for the library to keep those. list-sort (Chromium, WebKit; en, zh) fails without the change; library-items, library-all, own-skill, every-skill, the plugin tests, click-scroll and old-webkit pass; go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-02 06:33:46 +08:00
yetone e3bf9d05d4 library: one agent's skills or MCP servers are turned on or off at once, and a hidden agent's taken out of it (#475: turning Copilot CLI's 32 skills off was a chip on every row, and once it was hidden on the Agents page it had no chips at all, so what it had been given stayed with no way to take it out) — By agent, beside "In the library" on the Skills and MCP tabs, opens a sheet of the agents that take them, each with how many it has, a Turn all on and a Turn all off; each writes that one agent, every other keeping what it has, and reports what couldn't be written as a row's chips do. An agent hidden on the Agents page is listed while it has any, tagged Hidden and given nothing new; its instructions row is shown while it reads the shared instructions, so it can be switched off. Skills use the existing skills/agents-all; servers get servers/agents-all (EveryServerAgents), which turns on only the servers the agent can reach (no SSE for Codex, no remote one for Claude Desktop). TestEveryServerAgents and by-agent.test.cjs (Chromium, WebKit; en, zh) fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; every-skill, library-all, library-items, library-toggle-status, remove-all-skills, click-scroll, old-webkit pass. 2026-10-02 06:33:40 +08:00
yetone fe86fb3a4d gui: the page is first drawn in its saved theme, not the system's (seg-contrast failed in Chromium on the release branch: in dark the view shown read at 3.96:1, the other views at 4.69:1) — the saved theme was put on the page by app.js, at the foot of index.html, so a window kept dark under a light system had its header parsed and styled light first, and its buttons' colour transitions (the switch's views, Sync, Settings) then faded them to dark as the page loaded; the test read them mid-way, the more so as #471 made the page longer to parse. theme.js, run in the head after boot.js and before the styles, sets it from bootPrefs (or ?theme=) for the first paint; app.js still sets it as before. seg-contrast.test.cjs now holds app.js back and checks the page was already drawn in its theme (fails without theme.js, Chromium and WebKit, en and zh, dark), and reads colours once the page's transitions are over. old-webkit lets theme.js (ES5, URLSearchParams) run before compat.js; api-key-theme, from the release branch, expects a text button in --ctl-fg as #477 set. seg-contrast, settings-sections, header-zoom-fit, header-centre, click-scroll, old-webkit, api-key-theme, omarchy, agent-grip, web-page, gateway-fold, sessions-toolbar pass; go vet, go build -tags nogui, go test ./internal/gui pass. 2026-10-02 06:12:01 +08:00
yetone 1ea43274b0 gateway: Mistral's thinking, sent as typed parts in a Chat reply's content, reaches a Chat client as reasoning_content and a string content, and a translated one as thinking (#483: OpenCode's 'Invalid magpie/openai-compatible-chat stream event' with GLM on api.mistral.ai) — Mistral streams a reasoning model's thinking as delta.content = [{"type":"thinking","thinking":[{"type":"text","text":…}]},{"type":"text","text":"ok"}], thinking and text sometimes in one delta, and message.content so when not streamed; magpie relayed it as it came, the Chat contract has a string or null there, and the AI SDK OpenCode reads Chat with turned the first such chunk away and gave the request up (499 in the usage log). A relayed chunk or whole reply whose content is parts now has its thinking in reasoning_content, after any already there, and its text as a string content; a delta of thinking alone goes without content. Spoken to Claude Code's API or Codex's, such a chunk wasn't read at all, its thinking and text lost; it is now thinking and then text. Replies of every other upstream pass byte for byte, streamed or not. Prompts and headers are unchanged. TestMistralPartsStreamFlattened, TestMistralPartsWholeFlattened and TestMistralPartsTranslated (Anthropic, Responses) fail without the change; TestChatRelayWithoutPartsUntouched keeps other replies byte for byte. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-02 06:07:57 +08:00
yetone 872e26e2d4 models: a favourite's star is filled, and a model not a favourite shows an outline one on hover (#482: 收藏后切换成实心星) — the model picker's star, in the window and the tray panel, was the same outline star whether the model was a favourite or not, only its colour changed, so which state it was in wasn't clear. A favourite's star is now filled in its own amber, light and dark, and shows always; one not a favourite is still an outline, shown on hover. The button says which with aria-pressed as well as its title. favorite-star (window and tray panel, light and dark, en and zh, Chromium and WebKit) fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build pass; click-scroll, old-webkit, model-pick pass. 2026-10-02 06:07:14 +08:00
yetone 10da2936da gui: Settings is a page per part, under a tab each, the part kept in the address (#471: Settings is one long scroll of some 45 rows in 9 sections) — the page now opens with a tab list in the warm-ups' look (role=tablist, a tab each with aria-controls/aria-labelledby): General (appearance, language, text size, the icons, model names, login, Dock, session terminal), Usage (the menu bar's usage, refresh, logos, what usage shows, currency, number units, the two alerts, then the warm-up and check-in tabs), Network and sharing (proxy, local network), Models (images, web search), Privacy, Observability, Sync and backup, About; the part picked shows its rows alone. Every row, its id and what it saves are as they were; the duplicate heading of a one-section part and the Preferences heading are gone. The part is kept as ?view=settings&tab=<part> (dropped on any other view), so a reload or a link lands there, and remembered (magpie.settingsTab) for Settings opened again without one, as the tray's window/main?view=settings; an unknown one falls back to it. A part whose rows are all hidden on this system has no tab, and one picked falls back to the first shown. The arrows, Home and End move along the tabs, one in the Tab order; a click on a tab moves nothing on the page, the tabs staying where they were; a pill drawn while its part was hidden has its thumb put under its pick when shown (the warm-ups' fix, now shared). The tabs wrap to a line more when they don't fit (English in the 560x420 window, at 110 and 125%, a phone), no left-border stripe. The click hold also holds a click not yet held before taking the next (keys pressed within a frame on a tab list whose part shrank to the window: the second click was taken at the clamped top and the page stayed there). New strings General and Network and sharing are in i18n.js (常规, 网络与共享). Tests that reach a row now open or click its part: api-key-theme, currency, gateway-caller-keys, gateway-connect-keys, lan-docker, number-units, otel, redact-rule-row, s3-sync, search-api, settings-groups, sync-other-kind, tray-inuse, tray-logos, tray-usages, update-check, update-hide, usage-alert, webdav-settings-refresh, whats-new; those needing a scrolled page wheel a shorter window or in smaller steps, as a part is shorter than the whole page was. settings-sections.test.cjs (Chromium, WebKit, en, zh) fails without the change (10 of 10). go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestAccountProxy as on main); settings-sections, settings-groups, click-scroll, old-webkit, text-size and every settings test listed above pass. 2026-10-02 05:50:24 +08:00
yetone 69edf9f6e4 profiles: a chip's ↻ and × act on a second click, and a profile's details close with their own ×, or Escape, without hiding the tray panel (emo172, #478: ↻ overwrote and × deleted a profile on the first click; the details closed only by clicking the chip again, and Escape on them in the tray panel hid the whole panel; closed by hand they came back with the list, closed by Apply they didn't) — a first click on ↻ or × now changes nothing and reads "Overwrite?" or "Delete?" (覆盖?, 删除?) in red, and a second does it; it goes back after 3.5s, on Escape, or when the other one is clicked, with no browser dialog. The details have a × beside Apply, which closes them, the chip where it was. Escape takes back a question, then closes the details, then the panel's list, and only then hides the panel (both the panel's Escape and the list's ran on one press, so it hid the panel with the list open). The details close with the list, so the list opens again on the chips alone, however they were closed, as after Apply. profile-confirm.test.cjs (Chromium, WebKit, en, zh, window and panel) fails without the change; panel-profiles clicks × twice. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; profile-details, panel-profiles, old-webkit, click-scroll pass (click-scroll's routing Replay case flaked once on WebKit, passes alone). 2026-10-02 05:50:23 +08:00
yetone 973bf4eb2c profiles: a profile's details list all four of Claude Code's tiers, one left to follow the main model saying so (#480: opus and fable, following it, were missing from the details, so they read as not set) — a tier is a quiet field, empty while it follows the main model, and the details left every empty quiet field out. A field now says which one it follows (Follows, "model" for the tiers), and the details keep such a field, empty, with it; the panel shows it as "follows the main model (<model>)" (跟随主模型(…)), or "follows the main model" when the main is the agent's default. Other quiet fields (Codex's sign-in, subagents) are still left out while empty, and what applying a profile writes is unchanged. TestDetails and profile-details.test.cjs (Chromium, WebKit, en, zh) fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-02 05:50:23 +08:00
yetone 01d595817c providers: each account of a subscription, and each key, can be kept for some of the provider's models only (#474: 增加账号级别的模型选项, a low-quota but verified account kept for the cheap models) — every account or key a provider had served every model it did, so one couldn't be spared for some. An account's or key's row in its provider's editor now has a models badge: a click opens the provider's models as chips, Save keeps the account for those picked, All models gives it every one again; one kept for some says how many, always shown. magpie provider account-models <id> [account|key [models…|all]] does the same and lists them. It is kept in providers.json as accountModels, an account by its name in lower case, a key by its id; one not in it serves every model, as before. The gateway never sends a model to an account or key whose list leaves it out, in every routing mode, in drag order, in a group and as a fallback, and never falls back to it all the same; the trace says it was left out as set to serve other models. When every account or key there is leaves the model out, the request is refused with a 403 saying so and how to change it, not "none of X's models is ready". TestKeyModelsNarrowCandidates, TestAccountModelsNeverChosen, TestAccountModelsCmd and account-models.test.cjs fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; account-models, click-scroll, old-webkit, account-arrange, account-proxy, account-return, routing-account-order, panel-routing, unlisted-models pass. 2026-10-02 05:50:23 +08:00
yetone 7de2b62191 gui: an agent row shows a faint grip in its margin, and a hidden agent's Show is always there (#479: hiding an agent was found only by right-clicking) — the row's handle is its logo, which turned into a grip and opened Move up, Move down, Hide on a click, but only showed it was a handle with the pointer on it, and a hidden agent's way back, the Show button in the fold at the foot of the list, came up only with the pointer on its row. Every row in the list now carries a faint grip (two columns of three dots, --faint, --muted with the pointer on the row) in its left margin, beside the logo: it is the handle's own ::before, so it takes no room from the row, a drag on it moves the row and a click on it opens the menu. The Show button on a row hidden by hand is drawn without the pointer on it. Rows in the fold carry no grip, since they aren't dragged. agent-grip.test.cjs (window and tray panel, Chromium, WebKit, en, zh) drags a row by its grip, hides one from the grip's menu and shows it again from the fold, and fails without either half of the change. account-arrange, agent-layout, panel-fold, rollup-short, click-scroll, old-webkit, seg-contrast, header-centre, header-zoom-fit pass. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass but for TestClaudeSignedOut, which fails on its own as well and doesn't build on internal/gui (TestUserPath and TestSubscriptionUsageEachAccount flaked under load, pass alone). 2026-10-02 05:50:17 +08:00
yetone aa8e8cee2f gui: the header's other views and the text buttons read at WCAG AA, and the view shown stands off the switch (#477: in light mode an unselected tab read at 3.25:1, a text button at 3.33:1 on the page; dark passed by 0.08) — both were drawn in --muted (#85858d light, #8b8b94 dark), meant for captions, not controls, and the thumb on the view shown was white on a pill-grey track (1.13:1) or a 10% white on a 4.5% one (1.36:1). A control's quiet text now has its own colour, --ctl-fg (#63636b light, #a1a1aa dark), for the switch's other views, the Accounts and Sessions switches on the same track, and every .text button: 4.79:1 on the light track, 5.4 to 6.0:1 on the page, a card and the inset editor, 6.1:1 and up in dark. The light track is a shade deeper (#e6e6eb) and its thumb's edge a little firmer, 1.24:1 against it; dark's track is 3.5% white and its thumb 15%, 1.61:1. Sizes and weights are unchanged, so the header fits as before (#442/#457). seg-contrast.test.cjs (Chromium, WebKit, en, zh; light, dark, system dark) reads the computed colours, each background laid over the ones under it, and fails without the change. header-centre, header-zoom-fit, text-size, sessions-toolbar, click-scroll, old-webkit pass. 2026-10-02 05:50:17 +08:00
yetone 2d5905ffc8 usage: every count on the Usage page and in the panel follows Settings' number units (#476: with 万 / 亿 picked, the Overview, the Requests summary line, Sessions and the panel's Routing "today" still said K/M/B, two units on one page) — the setting reached only ledShort, which the Requests tab's totals and axis and the panel's Usage tab used, while fmtN, behind the Overview's tiles and chart peak, the Requests summary (#ledSum), every Sessions figure and Routing's "today N tokens", always said K/M/B. fmtN now says a count in 万 and 亿 in Chinese unless K / M / B is picked, ledShort is gone and its callers use fmtN, and the panel's Routing redraws when the setting changes, not at the next request. Small counts (the scan's file counts, a picker's search count) are as they were below 10000 and in 万 above it in Chinese. English is unchanged. number-units.test.cjs (Chromium, WebKit, en, zh) now checks the Overview, the Requests summary, Sessions and the panel's Routing in both units and fails without the change; sessions-overview, sessions-today, panel-routing and api-key-usage expect 万 in Chinese. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; number-units, sessions-overview, sessions-today, panel-routing, api-key-usage, usage-chart-axis, panel-usage, usage-ledger, usage-ledger-chart, currency, sessions-bar-names, click-scroll, old-webkit pass. 2026-10-02 05:46:16 +08:00
yetone 8043e9436d tests: cli-update's tight agent row is measured just above 600px, where the row is still beside its fields — since #440 (946c1d3b) a window 600px wide or less puts an agent's fields under its name, so at 540px, where the test looked for the version to have gone, the name's line spans the whole row and shows the name, the version and the whole pill again, as #440 meant. The version now has to be gone at 604px (the row about 148px), the tightest it gets beside its fields, and at 540px everything has to be back. No behaviour changes. cli-update.test.cjs (Chromium, WebKit) failed on main; click-scroll, old-webkit pass. 2026-10-02 05:29:14 +08:00
yetone 156b72ebf3 gui: the account or key a provider is on reads "In use" (使用中) again, not "Current" (在用) — 3df75514, which added each agent's model list and its 在用 tag for the model an agent is set to, also renamed the badge on the Accounts and keys rows from "In use" to "Current", which its message doesn't mention. The rest of magpie calls what is in use "In use": the Library's active set reads In use, the S3/WebDAV sync's chosen kind "· 使用中", so an account row reading 在用 said the same thing in other words. The rows say "In use" again; the model list's "Current" tag for a model stays. duplicate-pause.test.cjs (Chromium, WebKit, en, zh) failed on main for it. account-arrange, account-return, api-key-usage, agent-models, click-scroll, old-webkit pass; go vet, go build -tags nogui pass. 2026-10-02 05:29:13 +08:00
yetone 409fd21fe7 gateway: a stream that says it's rate limited before saying anything is let go at once, and the next account asked (Discord: after the second account's 429, Hermes kept waiting, 回答中 with nothing shown and no error, until the preferred account was changed) — a key or account that answered 200 and then a 429 as its stream's error event, before any content, was failed by the hold writer, but magpie read on until the vendor hung up; one that kept the connection open kept the agent waiting with nothing sent, not even headers, and the next account was asked only then, or never within the agent's timeout. The try's request to the vendor is now ended as soon as its stream's error comes before any content, so the next key or account answers at once, and with none left the agent gets the error at once. A refusal still reads on, for what it cost. A 429 status with a Retry-After of up to 8s, every account limited, still waits it out twice for the last one as before; one without, or longer, is the agent's at once. TestStreamErrorKeptOpenFailsOverAtOnce (Chat, Responses, Anthropic; one and both limited) fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-02 05:22:14 +08:00
yetone 4ae76dfa83 gateway: a test that a Chat reply relayed to a Chat client hands on its reasoning as it comes (Dazzle-sys, #473: dsh on WorkBuddy AI's deepseek-v4.1-flash at max heard nothing for 300s, then pi-ai's stream idle timeout canceled it) — looked into, magpie isn't what kept dsh waiting: a DeepSeek model's reasoning_content goes to the client as each piece arrives (refusesAfterThinking is false for it, so the hold for a refusal lets the stream through at its first thought), and what is held before it (the role chunk, empty deltas, SSE comments) is held 15s at most. A WorkBuddy reply that says nothing for five minutes has nothing magpie could hand on. No behaviour changes. TestChatReasoningRelayedAsItComes fails when the reasoning is held for a refusal as Claude's is. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-02 05:08:06 +08:00
yetone efa6fe65b4 dsh: a model through magpie that lists no thinking levels offers none, rather than dsh's own four it then turned away (Dazzle-sys, #473: on LongCat-2.5-Preview, every step of DeepSeek Harness's thinking slider failed with "takes an effort of for this model, not \"low\"") — the Agents page's effort field fell back to dsh's own off, low, high and max when a gateway model's catalog entry listed no levels, while setting one checked the model's own levels, which were none, so the slider offered four stops and each was refused with an empty list. Such a model has no levels in dsh either (its route gives it no reasoningEfforts), so none is offered now and the Agents page shows no thinking control for it, as it does for Claude Code on Haiku 4.5; an effort asked for anyway (magpie dsh effort low) says the model has no thinking levels in magpie, never an empty list. dsh's own models and gateway models with levels are unchanged; no other agent's effort field offers levels its setter refuses. TestDshEffortModelWithoutLevels fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; panel-effort, claude-effort, click-scroll, old-webkit pass. 2026-10-02 05:08:06 +08:00
yetone afe0014ebe routing: the live stage seats a provider's accounts and keys in the order they were dragged into, at once (Koohoko, #217: after dragging, the live routing animation kept the old account order) — the stage held each route's rows still by seating them by name, so a provider's accounts read alphabetically (by email, a key by its name) whatever order the provider's page lists them and the gateway tries them in, and a drag moved nothing there. The trace now tells each account's or key's place in its provider's own list (Weighed.Rank, from the order the provider lists them, the one signed in to first), and the stage and its Accounts and keys list seat a provider's rows by it, read from the providers as they are now, so after a drag the stage moves to the new order within a second of Routing being shown, with no reload or new request; a plugin's accounts, listed by its provider's id, follow it too. The order routing weighs them in, and who goes first, are unchanged. TestWeighedRankIsDraggedOrder and routing-account-order.test.cjs (Chromium, WebKit, en, zh) fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestAccountProxy fails on main as well; TestFXInState flaked once under load, passes alone); account-arrange, click-scroll, old-webkit, routing-steady, routing-columns, routing-shape, panel-routing pass. 2026-10-02 04:32:36 +08:00
yetone 06caa0961d gateway tests: a Codex sign-in a test makes is listed at once, whichever test ran before (TestAccountProxy failed in the full package, passed alone) — usage: own was sent [], want [acct-1]: TestArrangedKeysAreGatewayCandidates, run just before, looked at the accounts in its own home, and provider.rememberLogins (internal/provider/logins.go:461) looks again only once 30s have passed since, so codexSignedIn's ~/.codex/auth.json for me@example.com was never taken into logins.json; the turn still went to acct-1 by Codex's own sign-in, but LoginUsage("codex") listed only the two saved accounts and never asked for acct-1's usage. codexSignedIn now calls provider.ForgetAccounts, made for a test's home, and again on cleanup, as fallback_test and refusal_accounts_test do. No production change: the 30s wait is magpie's own, and a sign-in or switch in magpie already clears it. go test -tags nogui -count=1 ./internal/gateway/ passes three times in a row; go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build pass. 2026-10-02 04:22:18 +08:00
yetone e4c8316e3f gateway: a stream whose connection dropped mid-reply is said to have lost its connection, which an agent retries (#470: OpenCode Go: unexpected EOF) — with several dsh sessions on OpenCode Go, a dropped connection ended each session's stream with Go's own read error, "OpenCode Go: unexpected EOF"; dsh's pi-ai adapter places a failure by its words (429, 5xx, timeout, network/connection/socket…) and took this one for PI_AI_ERROR, which it never retries, so the turn failed. Every session at once, as magpie's requests to one host share one HTTP/2 connection and all its streams end so when it drops. A relayed or translated stream whose read fails mid-reply now ends with "<provider>: connection lost mid-reply (<error>)", which dsh retries as TRANSPORT; one cut before anything was said is still magpie's to try again, as before. TestStreamCutSaysConnection fails without the change; TestStreamCutBeforeContentTriedAgain guards the try again. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestAccountProxy fails on main as well, passes alone). 2026-10-02 04:08:38 +08:00
yetone f22218a092 gui: after an update the window shows what changed since the version last run (a Discord user: 点更新后能弹个窗显示更新了啥吗, to see whether their issue was fixed) — magpie updated itself silently, and its release notes were only on GitHub. The version last run is now kept in magpie's own folder (last-version); started on a newer release, the window or magpie web's page asks usemagpie.ai's new /api/notes (beside /api/latest; site/worker.js lists the releases' notes from GitHub, cached as the feed is) for every release since, newest first, without their Install section, and shows them once in a What's new dialog. A fresh install, the same version, a downgrade or a build from source shows nothing; a magpie from before the version was kept, already in use, shows its own version's notes; offline nothing is shown and nothing waits on it; until the site is deployed, the update feed's newest notes stand in when they are the current version's. The tray's panel shows no dialog. The notes are drawn as text, never as HTML: headings, bullets, bold, code, http(s) links, and #NNN links to the issue, opened in the browser as the app's other links are. Settings has a What's new row under Version that opens them again, with a waiting update's notes first, marked Not installed yet; /api/update's notes leave out the Install section too. TestShowNotes, TestStripInstall, TestBetween, TestNotesBetween, TestWhatsNewAfterUpgrade, TestWhatsNewFromBefore, TestUpdateNotesWithoutInstall and whats-new.test.cjs (Chromium, WebKit, en, zh) fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; click-scroll, old-webkit, update-check, update-hide pass. 2026-10-02 03:57:55 +08:00
yetone 78aba760aa gui: an agent's instructions opened at the foot of the Library's list leave the page where it is (emo172, #458) — the Library draws its whole page again on a click, so the click's hold (the element clicked, its neighbours and its parents, kept where they were on the screen) found none of them left and held nothing; the page went where the browser took it: down by what opened under the row (scroll anchoring), or clamped as it shut. A held element gone, with every one of its neighbours and parents, is now stood in for by what is at its place in the view's tree, of the same tag and first class, so the row clicked stays put. library-row-open-scroll.test.cjs (Chromium, WebKit, en, zh; a long set in a short window, shorter sets in a tall one) fails without the change (12 of 14); click-scroll, old-webkit, header-zoom-fit pass. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-02 03:38:15 +08:00
yetone 7e31cf7438 gui: a zoomed header with room to spare is drawn as at 100% (emo172, #457) — under the webview's page zoom (text size 110/125%) the header's buttons, which sit against its right padding, measure a hair past it (0.00004 CSS px in Chromium at 110%); fitTop took that for buttons running off and drew the header in step by step, at 110% all the way to packed, whatever the window's width. The comparison now allows half a pixel. header-zoom-fit.test.cjs zooms Chromium's page as the webview does and fails without the change; header-centre, text-size, brand, click-scroll, old-webkit pass. 2026-10-02 03:38:06 +08:00
yetone 187df65b16 provider: a plugin provider's accounts can be dragged into a new order too (#216) — dragging them failed with "accounts changed; reopen the provider and try again": the account a plugin's provider holds names its agent "plugin", which no list goes by, while the page lists and switches its accounts by the provider's id and logins.json keeps them as plugin:<id>. SetAccountOrder now lists and switches a plugin's accounts by the provider's id and writes their ranks on its plugin:<id> rows, so the order dragged is the order shown and tried. TestAccountOrderPluginAccounts fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestAccountProxy flaked once under load, passes alone). 2026-10-02 03:32:42 +08:00
关俊江 e230b9f31a feat: support handle-free account reordering in all routing modes (#216) 2026-10-02 03:32:41 +08:00
yetone cbd697693a agents: Claude Code's subagents get no square while there is no model to pick (#468) — before Claude Code runs through magpie its subagents field offers nothing, yet the agents page drew a square that could set nothing; it is left out, as an effort with no levels is. subagent-effort.test.cjs fails without the change. 2026-10-02 03:18:35 +08:00
yetone 4c69ff336b agent: Codex's subagents can start at an effort of their own, and Claude Code's subagents can have a model of their own (#468, #469) — Codex reads [agents] default_subagent_reasoning_effort, the effort a spawned subagent starts at when the spawn names none (openai/codex core/src/agent/child_config.rs, config_toml.rs; in codex 0.159.2), and magpie never wrote it. Codex gets a subagent effort field: a square beside its subagents' one that opens the effort slider, unset said to mean the session's effort, or the subagent model's own default. Codex refuses a spawn at a level the subagents' model lacks, so only one it has is taken, and one a new subagent or main model lacks is dropped. Claude Code takes the Agent call's model, then the agent's frontmatter, then CLAUDE_CODE_SUBAGENT_MODEL, then the session's (2.1.287); magpie wrote that env only by itself, the main model while every tier followed it. It gets a subagents field, offered once routed through magpie, whose model outlives a new main model and a tier of its own; unset, the env is written as before. Claude Code has no setting for its subagents' effort beyond each agent's own frontmatter, and omp's task role already takes a thinking level, so neither changes. TestCodexSubagentEffort, TestClaudeSubagentModel and subagent-effort.test.cjs fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; subagent-effort, omp-roles, claude-effort, panel-effort, agent-layout, model-pick, click-scroll, old-webkit pass. 2026-10-02 03:18:26 +08:00
yetone 818621b75e profiles: a click on a profile shows what it holds, and its Apply applies it (#467 emo172: a saved profile could only be seen by applying it over the setup in use) — a profile's chip applied it at once, and all the page knew of it was a one-line summary of its models. The state now carries each profile's fields by agent, under the agents' names and the fields' labels, in the order magpie lists them, with the Library's servers, skills and instructions each agent gets (not the instructions' text); a value that reads as a key or a token (a field named for one, a known key prefix, a long run of letters and digits) is sent as hidden, never its text. A click on a chip opens its details under its line in the window, and over it in the tray panel with Apply at their foot by the chip, the chip clicked staying where it is; opening them changes nothing, a second click closes them, Apply applies the profile; ↻ and × stay on the chip. In English and Chinese. TestDetails and TestStateProfileDetails fail without the change, as does profile-details.test.cjs (Chromium and WebKit, en and zh, window and panel). go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestFXInState's TempDir cleanup flakes with or without the change); profile-details, panel-profiles, click-scroll, old-webkit pass. 2026-10-02 03:09:42 +08:00
yetone 623307e978 i18n(zh): plainer words where they read as slang, the rest kept (#465) — the provider editor's Forget (dropping the vendor's fetched list) is 清除, not 忘掉; the Providers page's Take a look is 查看, not 看看; an account's forget tooltip says magpie 移除 its sign-in, as its siblings do; and the routing explanations that said {who} 先上 say 排第一, as every other "goes first" there already does. A session's details named its id line 整个会话, the routing affinity option's word for "Session": the line now uses the Session ID key (会话 ID, as the usage ledger's detail has it), and 整个会话 stays where it means the whole session, beside 一轮之内. 知道了 (OK) and 还有 {n} 个 (+{n} more) are kept: standard and clear where they show. plugin-move and provider-picks-stay assert the new words; sessions-manage and sessions-overview fail without the id line's label. plugin-move, provider-picks-stay, sessions-manage, sessions-overview, routing-steady, routing-manual, click-scroll, old-webkit pass (Chromium, WebKit, en, zh); go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-02 02:50:38 +08:00
yetone 16f14fdc10 providers: the models picked in one provider's editor never go to another's (#464: Claude models picked on one relay showed in another relay's editor, and its Forget and Refresh kept them) — a Refresh or Forget took the open draft's picks once the vendor answered and, after the list came back, wrote them into whatever draft was open by then; an editor left for another provider's in between was given the first one's picks, shown there as added by hand, and that editor's own Refresh and Forget then kept them as its own. The redraw already keeps the open draft, picks and all, so nothing is put back by hand any more: the other editor has its own picks only, while in the editor the button was pressed in a pick not yet saved and an id the vendor doesn't list (added by hand) still outlive both. provider-picks-stay.test.cjs (Chromium, WebKit, en, zh) fails without the change, for Refresh and for Forget each; click-scroll, old-webkit, provider-typed, unlisted-models, model-test-one, nokey-editor pass. 2026-10-02 02:50:38 +08:00
Charan Rathore d21f2f6021 agent: read TOML literal-quoted Kimi model keys (#446)
* agent: read TOML literal-quoted Kimi model keys

* agent: match Kimi model tables by decoded key
2026-10-02 02:48:47 +08:00
Charan Rathore 02276e676d edit: escape backslashes when quoting dotenv values on write (#450)
* edit: escape backslashes when quoting dotenv values on write

* edit: write dotenv values single-quoted when possible
2026-10-02 02:48:40 +08:00
yetone a409e1552a groups: the groups magpie finds on its own can be switched off, in the Routing view and with magpie group auto off (蓝猫 on Discord: 路由分组会自动创建,可以关闭掉吗) — a model two or more providers serve always became an auto-<model> group, and one could only be removed at a time. A switch right under the routing groups' head, with what it does in words, and magpie group auto [on|off] now keep noAutoGroups in providers.json: off, no found group is listed, catalogued or served, and the groups the user made or changed stay; on, they are back, along with any the user had removed still removed. An agent set to a found group is moved to its model from one provider (the status line and the CLI say which), and a request that still names group/auto-<model> goes to that provider's model rather than failing. Switching off is refused while a group of the user's holds a found group as a member or classifier, so none is left pointing at nothing. TestAutoGroupsOff, TestAutoStandIn, TestAutoGroupsOffHeld, TestFoundGroupOff, TestReseatFoundGroupsOff and TestGroupsFoundSwitch fail without the change; group-found-off.test.cjs (Chromium, WebKit, en, zh), click-scroll and old-webkit pass. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestUserPath a load flake, passes alone; TestClaudeSignedOut fails alone on main as well). 2026-10-02 02:24:49 +08:00
yetone 456208945b plugins: a plugin can be installed from a git repository — github:owner/repo, owner/repo, a GitHub URL or a git+https:// one (Lemon on Discord: can plugins be installed directly from git?) — magpie plugin add and the Plugins field took only an npm name or a path, and turned a git spec away as "isn't an npm package name". A git spec now goes to bun add as given and is kept as given in plugins.json; the package it installs is named by its repository's package.json, which bun records as the dependency whose spec it is, so the plugin is known (list, on/off, rm, its version, its README) by that name or by the spec. update and the row's Update run bun update on it, which fetches the repository's commit now where bun add keeps the one bun.lock has, the spec unchanged; npm is never asked for a newer version of it, nor auto-updates it. Adding the same package from another URL, or from npm, takes its entry's place. The Plugins field says it takes a GitHub repo (en+zh); an installed git plugin shows its package's name, an Update button, and a page with its README, no npm link and a Source link to the repository. Nothing that installed before is turned away. TestGitPlugin (a fake bun fetching from local bare repositories, the real one loading the plugin when on PATH) and plugin-git.test.cjs fail without the change; TestIsGit checks what counts as a git spec. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; plugin-git, plugin-local, plugin-market, plugin-updates, click-scroll, old-webkit pass. 2026-10-02 02:24:49 +08:00
yetone 84eadbad00 claude: a run waiting for its conversation's next turn is kept an hour, as long as the prompt cache it wrote (#463: large cache writes after a conversation sat idle past 20 minutes) — Claude Code writes Anthropic's prompt cache for an hour on a subscription within its limits (its own help: "Unset = automatic: 1 hour on a Claude subscription within its usage limits", 2.1.286), but the bridge let an idle run go after 20 minutes. The next turn then went to a new run told the whole conversation in one message, a prefix never cached, so all of it was written again while the old run's cache could still have been read. idleLongest is now claudeCacheTTL (an hour); at most idleMost (6) runs are still kept, oldest let go first. A cancelled turn still ends its run: Claude Code's history then holds a turn the client may not have, and without session persistence there is no way to take it back. Prompts, identity lines and headers are unchanged. TestClaudeIdleRunKeptWhileItsCacheLasts fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-02 02:24:49 +08:00
yetone febbf29b0f usage: an Antigravity call at a level answered under its model's own name is no swap (#462: gemini-3.8-flash at medium marked Swapped, answered by gemini-3.8-flash) — on an Antigravity account a call at an effort goes out as that level's id (AntigravitySentID: gemini-3.8-flash-medium), and Cloud Code's reply names the family in modelVersion (gemini-3.8-flash), so Swapped compared gemini-3.8-flash-medium with gemini-3.8-flash and the request list and the Routing view tagged the model as swapped for itself. A sent id whose last word is an effort word (provider.EffortFamily: low, medium, high, xhigh, minimal, extra-low, extra-high) now matches a reply naming the id without it; a reply naming another level, or another model, is still a swap. TestLedgerAntigravityReplyNamingTheFamilyIsNoSwap fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestClaudeRunEndsWhenTheClientLeaves, a load flake, passes alone). 2026-10-02 02:24:48 +08:00
yetone 4db035682a zcode: a Z.ai and a BigModel (智谱) account on the same phone number, or two accounts ZCode names alike, are kept apart (Bandit on Discord: authorising one replaced the other) — magpie kept ZCode's accounts by name alone, the name being the email, or the phone number of a phone sign-in (<phone>@phone.local), so a sign-in whose name an account already had overwrote that account's key, on whichever site; one named as ZCode's own account was taken for it and not kept at all. A sign-in now keeps the account's user id beside its key, and an account is the same only on the same site with the same id (one kept before ids, by its key's id); another account with the name is kept as '<name> (BigModel)' or '<name> (Z.ai)', numbered when that is taken, and signing in to an account again still updates it in place, by id. There is no cap on the number of accounts. TestZCodeAccountsKeptApart, TestZCodeOldAccountKeptByKey and TestZCodeOwnAccountKeptApart fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestUserPath, a known load flake, passes alone). 2026-10-02 02:24:48 +08:00
TryWorld 876bec1b89 feat(gui): collapse overflowing Sessions agent tabs into a picker (#461) 2026-10-02 02:25:17 +08:00
yishu 25194cbc0d fix(provider): a Step Plan's credits show when they never reset (#460)
A monthly Step Plan (Pro, plan_family 2) has no 5-hour or weekly window,
only credits that run out with the plan. QueryStepPlanRateLimit tells them as
subscription_credit_left_rate 0.9019661 with subscription_credit_reset_time
"0", and its credit_buckets entry says next_reset_at "0". readStepPlan dropped
every window whose reset doesn't parse, so the card showed the plan's name
and end date with no window at all, while StepFun's own usage page showed
90% of the credits left.

Credits with no reset are now kept when the answer lists credit_buckets: a
window with no ResetsAt, as other credit windows already are. The 5-hour and
weekly windows are unchanged: 0 left and 0 for the reset is still no window.

TestReadStepPlan gets the real answer from a Pro account (it fails on main
with []) and a plan whose credits are 0/0 with no bucket (still no window).
With a signed-in Pro account, magpie quota stepfun-cn on main shows
"Pro · expires Oct 15  plan" with nothing after it; with the change it shows
"Credits 10%". go vet and go test -tags nogui ./internal/provider pass.
2026-10-02 02:25:11 +08:00
geoqiaoandClaude Opus 5.5 a613a9de7d gateway: restart the Claude Code run when the client rewrites the conversation mid-turn (#459)
A request carrying tool results was handed to the run that made the calls
by their ids alone. Pi compacting between a tool call and its result
rewrites the conversation, but the run's Claude Code kept the one from
before: it answered from it and reported a context as large as ever, so Pi
compacted again at every tool call (four times in three minutes, 250k ->
261k tokens). A run now keeps the conversation of the request it last
answered; tool results that do not go on from it start a run anew, told the
conversation as the client now has it.

TestRunLetGoWhenTheClientRewroteTheConversation fails without the change.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 02:25:05 +08:00
TryWorld 946c1d3bf6 fix(gui): keep agent names visible in narrow windows (#440) 2026-10-02 01:50:38 +08:00
Aliceandraper 0260716c9d feat(usage): 可选的 OTLP 元数据导出(Trace/Metrics,支持 Langfuse) (#412)
* usage: add opt-in OTLP HTTP metadata export

Export gateway traces and optional duration/token histograms asynchronously with a bounded queue, retries, proxy support and a bounded shutdown. Add bilingual Settings controls and MAGPIE_OTEL_* overrides. Keep prompts, replies, sessions and provider credentials local; use the same OTLP path for Langfuse.

Validated: full Go tests (nogui), usage race tests, focused go vet, and Chromium/WebKit settings tests in English and Chinese.

Refs #213

* otel: protect collector credentials in backups and settings

Remove all OTLP headers from keyless backups and restrict both new and existing settings files to 0600 before saving. Clarify SIGTERM export behavior. Addresses the two blocking review findings in #412.

* otel: preserve local credentials and read-only settings on restore

---------

Co-authored-by: raper <raper@raperdeMacBook-Air.local>
2026-10-02 01:51:04 +08:00
SadWood e775ea3a72 fix(gateway): 修正取消请求的供应商与模型计量配对 (#456)
供应商在每次尝试开始时更新,模型却在循环末尾更新,导致切换路由后取消的请求把备用供应商与原模型写入同一条计量记录。将模型更新移到供应商旁,保持每次尝试的计量字段一致。

增加本地假上游回归测试,覆盖普通 fallback 与有序模型组,验证实际发送的模型、499 状态及原始请求模型。
2026-10-02 01:50:16 +08:00
yetone da1ee0f81a sync: the menu bar's usage is each computer's own, not synced (yoooo on Discord: usage in the menu bar turned off on a Mac came back on from a Windows box that shows it, over WebDAV) — a sync and a restored backup kept only the window's size, the proxy and the Dock from this computer, so which subscriptions show beside the icon, how often they refresh and their logos came from whichever computer synced last. They are now kept like the others (Settings.KeepOwn, used by both), and left out of what tells a change. TestSync fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-02 01:13:29 +08:00
yetone 0faa0c696d gateway: a refusal behind a prefix that isn't JSON, like [HTTP 400], is still read by its structure (#451 follow-up) — the scan for a vendor's JSON error stopped at the first bracket it couldn't decode, so [HTTP 400] {"detail":[…]} fell back to text matching: an echoed metadata in the input was taken for a refusal and a real extra_forbidden was missed. The scan now looks on past such a bracket. text-prefixed-extra and text-prefixed-input-echo in TestRefusedOptionalNamesOnlyUnsupportedFields fail without the change. 2026-10-02 00:58:21 +08:00
yetone 3c04877cfc archive: a request the archive kept is opened and downloaded from the Usage page, with its bodies whole (jorben, #447: 请求存档 reachable from usage history, full bodies, download) — the archive's name was only on the Gateway page's recent calls, gone once a call left the list or the gateway restarted, and the archive kept the first 256 KB of each body, the recent list's cut. The usage ledger's row now names the call's archive (archive in usage.jsonl, kept through the Requests page's packed rows), and its details on Usage → Requests show the Request archive panel, Fetch from archive and Download; with the archive on, a request it has no copy of says "Not archived". The bodies are kept whole up to requestArchiveMaxMB in settings.json (32 MB unless set, at most 1024): the response is teed to a temp file as it is sent, the request body written to one when the call ends, so the upload queue holds file handles, not bodies, and one upload at a time reads them; a body past the limit is kept to it and marked cut, with its whole size. What goes upstream is unchanged, the archive stays off unless turned on, headers and bodies are scrubbed as before, and the temp files are removed after the upload or when the queue is full. The page is sent a body to show only when it is 256 KB or less (a longer one is said by its size), an archive over 80 MB is only said to be; Download streams the object whole, to Downloads in the app (archive/export, -2, -3… for a name taken) and as the browser's download in magpie web (archive/file). TestArchiveWholeBodies, TestArchiveLimit, TestRowKeepsArchive and TestArchivePreviewAndDownload fail without the change; TestArchiveOffUsage, TestArchiveTooLargeToPreview, TestArchiveOldPreview keep the rest. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; request-archive-usage (new), request-archive, click-scroll, old-webkit, usage-ledger, usage-ledger-detail, usage-ledger-content, web-page, sse-body pass. 2026-10-02 00:57:51 +08:00
TryWorld 3c6818a833 fix(gateway): recognize optional-field errors after status prefixes (#451)
* fix(gateway): skip status prefixes in optional-field errors

* test(gateway): match validation errors to the echoed input
2026-10-02 00:57:55 +08:00