Commit Graph
Select branches
Hide Pull Requests
main
Select branches
Hide Pull Requests
main
-
588e51ea4c
agent, gui: an agent connected by its config to an address where nothing answers is told, not shown connected (#1013, cjyrainbow: Codex Desktop's WSL environment reads Windows' ~/.codex/config.toml, whose openai_base_url and [model_providers.magpie].base_url the reporter pointed at Windows as WSL sees it, forwarded to 127.0.0.1:3425 by a portproxy of their own; after a reboot the portproxy listened no more and the Agents page still said Codex was connected) — Drift looked only at the config, which was right. Now an agent pointed at an address off loopback (this machine's Codex at an address the user kept, #816, through Agent.reach; every WSL twin under NAT, at Windows as the distro sees it, through Agent.Gateway) has that address tried from here (reach.go: GET /v1/models, any HTTP status an answer, 2 s timeout, in the background, at most every 20 s per address, never waited for), and one that doesn't answer while the gateway answers on loopback is a new drift, "unreachable", with Addr and the advice in Detail: under WSL (or on Windows) magpie with Settings › Share on local network on, or the user's netsh interface portproxy, which may need setting up again after a restart; Windows' firewall allowing the port for the WSL network; on Windows 11, networkingMode=mirrored; elsewhere, the forward to start again or gateway.URL() to point at. An answer not had yet, and a gateway down on loopback too, are no drift. The config is never changed for it, except where the address is one a WSL distro reached Windows at before (distro.Was, the last 4 kept in wsl.json as a re-probe finds Gateway changed) and the distro reaches Windows at another now: Drift's Move names that one, and Reapply moves the config there (Agent.move; Codex's codexMoveGateway rewrites openai_base_url and magpie's own provider's base_url, only where they begin with the old address). An address no distro was seen at is the user's own and is never offered a move. In the GUI the row's line says "{agent} is set up, but nothing answers at the address it reaches magpie by" in red with the advice as its tooltip, and its pill is "How to fix" (an i, not the reapply arrow), which opens the advice in a dialog (too long for the status line; it wraps and can be copied) and sets nothing — or "Use <address>" (the existing "Use {url}" string) when there is a Move, which reapplies; two new t() strings with zh, ja and de. The probe runs from Windows, not from inside WSL, so a firewall keeping WSL out isn't seen; the advice names it. TestKeptAddressNotAnswering (the reporter's case: Codex at 172.28.96.1, unreachable with the address and loopback in the detail; no drift once it answers, none with the gateway down too), TestKeptAddressProbeNeverWaitedFor, TestKeptAddressMovedWithWSL (Move from a Was address, Reapply rewrites both URLs, Check empty and no drift after; a 10.0.0.7 address is told but never moved) and TestWSLAgentAddressNotAnswering (Codex in a NAT distro told, with Share on local network, portproxy and firewall; a mirrored one never probed) fail without the Drift change and pass; TestLoopbackNotProbed, TestReachProbe (httptest: a 401 is an answer, a closed port none) and TestWSLWas pass. TestMain stubs reachProbe so no test dials out. -race -count=10 on them; go test -tags nogui ./internal/... under a temp HOME, go vet (darwin, windows), gofmt -l, linux and windows (nogui) builds pass. agent-unreachable.test.cjs (the window at 560px and the tray panel at 440px: the red line and tooltip, How to fix's dialog posting nothing, Use <address> posting the reapply, no sideways scroll) fails on main and passes on Chromium and WebKit in en, zh, ja and de; agent-connect, agent-layout, agent-field-names, aside-native, gui-de, gui-ja and i18n-keys pass on both. Not tried on a real WSL NAT setup (the Windows test box didn't answer ssh). docs/subsystems/agent-wiring.md says it.
yetone
2026-10-06 19:17:52 +08:00 -
c5a54bc74b
agent: VSCodium's magpie group is written for the provider its installed Copilot Chat declares, OpenAI Compatible (customoai) on the Marketplace's 0.48.1 (#1014, xybio: VSCodium's Chat picker showed only Auto) — VSCodium has no Copilot Chat of its own, and the last Marketplace Copilot Chat it can install, 0.48.1, declares no Custom Endpoint provider (its contributes.languageModelChatProviders: copilot, copilotcli, claude-code, anthropic, xai, gemini, openrouter, openai, ollama, customoai, azure), so the customendpoint group magpie wrote in chatLanguageModels.json was never read. magpie now reads the package.json of the github.copilot-chat installed under ~/.vscode-oss/extensions (vscodeChatOf: the one extensions.json lists, else the highest github.copilot-chat-<version> folder not in .obsolete) and writes customendpoint when it is declared, customoai when only that is: each model's window as maxInputTokens beside maxOutputTokens (0.48.1 adds them up), and the token as x-api-key, since 0.48.1 drops an Authorization in requestHeaders and its apiKey has to be a ${input:} secret; the gateway already takes a key from x-api-key, past an empty Authorization, so Usage still counts VSCodium. No extension found, or a package.json that can't be read, keeps customendpoint as before; one declaring neither is said in Notice and Check. Writing the group removes magpie's group for the other provider in the default's and each profile's file, and Disconnect removes both; the user's own groups, under either provider, stay. Sync (at magpie's start and on catalog changes) rewrites a group written for the other provider, and Check reports it until then. VS Code and Insiders, whose chat is built in, keep customendpoint unchanged. The Notice says 0.48.1 registers its providers only while signed in to GitHub with a personal Copilot plan (its BYOK registration is gated on the Copilot token's individual/BYOK flag). TestVSCodiumCopilotChatOpenAICompatible (the real 0.48.1 provider list installed in a temp ~/.vscode-oss: Connect writes customoai with x-api-key magpie-vscodium and no Authorization or contextWindow, the user's groups kept, Check empty; a Custom Endpoint group an older magpie wrote is flagged and rewritten by Sync; a Copilot Chat declaring customendpoint switches it back; one declaring neither is reported; Disconnect removes both) and TestVSCodeChatInstalledWithoutExtensionsJSON (folder scan, .obsolete, unreadable package.json) fail on main, which writes customendpoint, and pass; -race -count=10 on the VS Code tests, go test -tags nogui ./internal/..., go vet, gofmt -l, linux and windows (nogui) builds pass. No GUI change. Not tried with a real VSCodium. docs/subsystems/agent-wiring.md and docs/reference.md say it.
yetone
2026-10-06 19:09:22 +08:00 -
b0a96cbbd7
gateway, provider, gui: a Sub2API key given 5h/1d/7d limits is weighed and rested by its own windows, like an account (#1004)
forever
2026-10-06 03:57:47 -07:00 -
87487c5ef0
plugin, provider: a plugin can press its vendor's daily check-in itself (auth.checkin), and magpie runs it as its own check-ins (Lemon on Discord #feedback: provider 每日签到的方法能放到插件里面去做吗; ARNO earlier asked for Qoder's) — the check-ins magpie had (WorkBuddy, Trae CN, MiniMax Code, Qoder) are each written in magpie against the vendor's endpoint, even for a subscription that moved to its plugin, so a plugin author had no way to give their provider one. A plugin's auth hook may now have checkin(getAuth, provider) beside usage: the host lists the provider with checkin: true (plugin.Provider.Checkin) and answers checkin({provider, account}) (plugin.AccountCheckin), refreshing the account as usage does, with {outcome, credit, streak, message}; outcome is claimed, done, ineligible, inactive, captcha or failed, and a throw or an outcome outside these is failed with the reason. internal/provider/plugin_checkin.go runs these through checkin.go's checkiner, as the built-ins are: each signed-in account in use once a Beijing day (first look 2 minutes after start, then every 30), a failure asked again after wbCheckinRetry, every other outcome settled for the day, results kept in plugin-checkin.json by provider|account; a failure is recorded as failed and resets nothing. captcha is new (CheckinCaptcha): the vendor wants a captcha, which magpie never solves, so it is said as "check in in its own app", not as a failure, and not asked again that day. The results mark the account's Usage card (checkinBy "plugin:<provider>", WithCheckins), so the card's check-in row, its Auto check-in and Check in now work for it (POST /api/settings/plugin-checkin {provider, on}, POST /api/usage/plugin-checkin {provider}); Settings → Usage gains a Plugins tab, shown while such a plugin is signed in, a Daily check-in row per provider with Off/On and how each account went; magpie accounts checkin and c on the TUI's Usage page include them (the TUI's "no account" line names check-in plugins). The switch (settings.PluginCheckins, by provider) is off by default; for WorkBuddy, Trae CN, MiniMax Code and Qoder it follows that vendor's existing switch until set on its own, and magpie's own check-in through the plugin's fetch leaves a provider whose plugin checks in to it (pluginChecksIn), so no account is checked in twice. The Settings page's save carries PluginCheckins over (TestSettingsSaveKeepsWhatItDoesNotSend's fixture names it). No plugin declares the hook yet; Qoder's check-in stays magpie's own here. TestPluginCheckinRunsAsTheBuiltinsDo (the fake plugin with FAKE_CHECKIN, five accounts answering claimed, done, captcha, a throw and an unknown outcome: off presses nothing; on, each outcome kept with its vendor; a minute on nothing pressed again; past the retry only the two failures; the next day all five; PluginCheckins and the cards marked; another provider's press asks none) and TestPluginCheckinTakesOverTheVendors (a qoder plugin without the hook leaves magpie's Qoder check-in its account, with it none; PluginCheckinOn's fallbacks) pass -race -count=10, and the latter fails with pluginChecksIn stubbed false; TestTUIChecksPluginsIn passes -race -count=20. plugin-checkin.test.cjs (the card's row with the captcha said, Auto check-in posting {provider, on}, no page move; the Settings Plugins tab at 440px with the row fitting, no sideways scroll, On posting) passes on Chromium and WebKit in en, zh, ja and de; usage-checkin, i18n-keys, gui-ja, gui-de and settings-* pass. go test -tags nogui ./internal/..., go vet, gofmt -l, linux (nogui) and windows builds pass. Not tried with a real plugin or account: no published plugin has the hook yet. docs/subsystems/provider-plugins.md (the hook's contract) and docs/reference.md say it.
yetone
2026-10-06 18:40:45 +08:00 -
b56bfb5a94
gateway: an upstream that answers 200 with a web page, with nothing at all, or with JSON that isn't, has failed as a 502 does, so a group asks its next member (#1012, cth123456: Codex on group/medium, agentrouter/deepseek-v4-flash now and then answered 200 text/html; the log said status=200 done=true in=0 out=0 served=null, codex/gpt-6-luna was never asked, and Codex failed with "stream disconnected before completion: stream closed before response.completed") — the candidate loop judged a try by its status alone, and holdWriter passes a 200 that is neither text/event-stream nor application/json straight to the agent, so the page went to Codex as the reply. notAnAPIReply now reads every 2xx (but 204) an upstream gives, in forwardOnce (every relayed and translated request, every API, plugins included) and in the built-in clients' own requests (the ChatGPT backend's /responses, Kiro, Zed, Qoder, Devin, Cursor, Command Code): a text/html reply, a body that begins as a web page whatever its Content-Type, an empty body, or one sent as JSON that doesn't begin with { or [ is turned into the 502 it stands for, with a JSON error saying what came back ("answered 200 OK with a web page (text/html), not an API reply: “Just a moment...”", the firewall hint for a block page). It then fails over, rests a minute and is logged as any 502 before the reply begins does; a provider with nobody after it hands the agent that 502 rather than the page. Only the first bytes are peeked, which a stream waits for anyway; a read that fails is left to the reader, a compressed body and anything else as it came. TestNotAnAPIReplyFailsOver (Chat, Responses and Messages, streamed and not, a member answering a Cloudflare page as text/html, as JSON and as SSE, an empty body as JSON and as SSE, and plain text as JSON; the next member must answer and the log name why) fails without the change in all 36 cases (the page relayed to the agent, or b never asked, or no reason logged) and passes; TestNotAnAPIReplyAlone (a 502 with the reason, logged 502) and TestCodexBackendWebPage (the ChatGPT backend's page reached Codex as a 200) fail without it and pass; TestAPIReplyUntouched keeps JSON after whitespace, SSE with and without a Content-Type, gzip, XML, a 204 and a 404 as they came. -race -count=10 on them; go test -tags nogui ./internal/gateway ./internal/provider, go vet (darwin, windows), gofmt -l, linux and windows (nogui) builds pass. Not tried with the reporter's relay (agentrouter) itself; the page is a Cloudflare challenge's, as the issue has only its Content-Type. docs/subsystems/gateway-routing.md says it.
yetone
2026-10-06 18:26:22 +08:00 -
82c8735a94
library: a float in a Codex server's entry is written back as a float, and inf and dates as TOML has them (Lullaby on Discord: an internal MCP server that worked configured in Codex looped with its tool arguments lost after it was taken into the Library; they suspected tool_args / strict) — every key Codex reads for a server (codex-rs/config/src/mcp_types.rs RawMcpServerConfig: command, args, env, env_vars with { name, source } tables, cwd, url, bearer_token(_env_var), http_headers, env_http_headers, http_headers_helper, environment_id, auth, oauth.*, scopes, oauth_resource, startup_timeout_sec/_ms, tool_timeout_sec, enabled, required, startup_readiness, supports_parallel_tool_calls, tool_input_schema_max_bytes, omit_tools_from, default_tools_approval_mode, enabled/disabled_tools, tools.<tool>.approval_mode/output_token_limit, name) was checked through ImportServer and a SaveServer that adds an argument and gives the server to Claude Code too: all of them come back with their values, args with JSON and spaces in them verbatim, tables written inline. The one change was tomlValue writing a float with fmt.Sprint, so tool_timeout_sec = 120.0 came back the integer 120 (Codex reads either, as serde's f64 takes an integer, so this is not the reporter's loop), and +Inf, which isn't TOML, for inf; a date or time the user put in an entry went back as a string. tomlFloat now keeps a float one (".0" where it has no point or exponent; inf, -inf, nan), and go-toml's LocalDate/LocalTime/LocalDateTime and time.Time are written as TOML's own. Grok Build's entries go through the same writer. TestCodexServerKeysSurviveTheLibrary (the fixture above, compared value and type for every key) fails without the change (tool_timeout_sec float64 → int64) and passes; TestTOMLValueKeepsTypes (floats, an exponent, inf, a local date, time and date-time, an offset date-time) fails without it (+Inf doesn't parse) and passes. -race -count=10 on them with TestServerEveryFormat, TestServerKeepsUsersKeys and TestPutCodex*; go test -tags nogui ./internal/library ./internal/edit, go vet, gofmt -l, linux and windows (nogui) builds pass. The Library's MCP sign-in proxy (gateway /mcp/{name}) relays the body untouched. Not tried with the reporter's server; what broke their tool calls is not found here.
yetone
2026-10-06 18:22:41 +08:00 -
38cc30b09a
tray: with the menu bar's magpie icon off, a click opens the panel on the tab left last, not Allowances (the owner: 每次都会自动跳转到 allowances,我记得之前是可以的)
yetone
2026-10-06 18:03:57 +08:00 -
a87dc1ff2b
gateway: a WebSocket upgrade on /v1/responses is answered 426 "magpie relays Responses over HTTP only", not 404 (#1005, Clarence12138) — Pi's Responses WebSocket extension opens GET /v1/responses with Upgrade: websocket, and only POST was routed there, so the catch-all's 404 ("magpie serves …") read as a broken gateway and the extension retried five times a session before falling back to SSE. GET /v1/responses and /responses now tell an upgrade 426 with the reason and the way that works (POST, streamed as SSE) — the answer a client falls back to HTTP on at once, as the Codex backend path already gives (TestCodexWebSocketUpgradeRequired) — and a plain GET 405 with Allow: POST. Relaying Responses over a WebSocket itself is not done here (a long-lived connection whose model is named per response.create, outside the per-request routing, failover, key limits and usage the gateway has). TestResponsesWebSocketUpgradeRequired fails without the change (404) and passes; go test ./internal/gateway, go vet (darwin, windows), gofmt -l and the linux build pass. Not tried with Pi's extension itself. docs/reference.md and gateway-routing.md say it.
yetone
2026-10-06 17:55:37 +08:00 -
6ba82d2f07
gateway: reasoning magpie made isn't relayed to Azure OpenAI or OpenAI's API, and a summary request refused for an item not found is asked again as text (#1008, zhouxiong100: Codex on an Azure OpenAI deployment failed every compaction with "Azure OpenAI: Item with id 'rs_18dbe693b3dce4800000003c' not found", Reconnecting 5/5, store=true by a middleware no help) — Codex offers web search on every turn, so its turns on Azure went out translated, and the translated replies gave Codex reasoning items magpie made: rs_ and newID (the reporter's id is one, a UnixNano in hex and a counter), a summary and nothing sealed. Codex hands them back on every turn. buildResponses replays no reasoning to Azure, so the turns worked; Codex's compaction offers no tools, so it isn't translated but relayed as it is, and Azure, with store false, looked rs_… up among stored items, found none and answered 400 for the whole request. codexCompact asked again as text only on a 404 (#866), so the 400 went back to Codex. passthrough now drops reasoning with no encrypted_content from a Responses request to OpenAI's API or Azure OpenAI unless store is true (withoutBareReasoning): both look such an item up and can't have it, its summary being the model's notes no model reads back; their own sealed reasoning still goes, and every other host gets the request as before (a relay may read the summary for a vendor that wants reasoning back). The same holds for any turn relayed as it is, Codex's with web search off for one. codexCompact's fallback also takes a 400 naming an item not found (itemNotFound: unreadableItem and "not found"), as a relay in front of either passes on; any other 400 still goes back as it came, and the local summary names the status it got. TestAzureCompactionWithoutMagpiesReasoning (the Azure preset at a fake /openai/v1/responses answering a storeless request with a bare reasoning item Azure's 400 word for word; the compaction and a relayed turn, each asking Azure once with the sealed reasoning kept and magpie's dropped) fails without the passthrough change (the turn 400 with the reporter's exact message, the compaction asking twice) and passes; TestCodexCompactItemNotFoundAsksAgainAsText fails without the codexCompact change (400 to Codex) and passes; TestCodexCompactOther400Stays guards other 400s. -race -count=10 on them, TestCodexCompact* and TestAzureRoutes. go test -tags nogui ./internal/gateway ./internal/provider, go vet, gofmt -l, linux and windows (nogui) builds pass. Not tried with a real Azure OpenAI resource (none on this machine); the 400's text is from the reporter's screenshot and issue. docs/subsystems/gateway-routing.md says it.
yetone
2026-10-06 17:53:09 +08:00 -
b95ce8c6f3
provider: ZCode's mover asks for @magpie-community/opencode-zcode-auth 0.1.11, which names a gift plan ZCode holds but hasn't granted on the usage card ("1 to claim · claim it in the ZCode app") — #1001 (MiFaZhan): a day's ZCode Trust Build is not on the account until the ZCode app claims it, and an account signed in through magpie alone let it lapse. The plugin (magpie-community/plugins#30) reads ZCode's own GET /api/v1/zcode-plan/billing/preview with the card and adds one window set aside, so routing, caps and the menu bar pass it over; it never claims (the claim needs the Aliyun captcha only the app's renderer makes). A failed or empty preview adds nothing and an error card asks nothing. keepMovedCurrent updates every moved ZCode user to it. go test ./internal/provider and gofmt pass; the plugin's bun test (72) and check.mjs pass, 4 of its new tests fail on 0.1.10. Not tried with a real ZCode account here.
yetone
2026-10-06 17:50:39 +08:00 -
5361a2178e
agent, gateway: Cursor Private Inference's row has an effort, which magpie's gateway asks its requests for (#1003, gjf7) — other agents' rows pick a reasoning effort that magpie writes into their config, but Cursor Private Inference reads only its base URL and key from the environment, and its app offers no Reasoning control for most of magpie's models, so there was no way to set one short of a routing group with a member fixed at a level, which the reporter used as a workaround. Its row now has an effort field like the others': the default (as Cursor asks) first, then every level the models it lists have (cursorLocalEfforts: their levels in MemberEfforts order, low/medium/high where a model reasons with levels unknown, none at all when nothing it lists reasons or it isn't connected). The pick is kept in magpie, settings' agentEfforts by agent (provider.AgentEffort / SetAgentEffort, a level of MemberEfforts or "" for none), and the gateway's candidate loop asks a cursor-local request for it with withFixedEffort, fitted to the model's levels and bounded by a group's, on a model that reasons (the new Provider.Thinks: levels, its list's Reasoning, or models.dev's). Precedence: a group member's fixed effort, then a :effort suffix, then a routing rule's turn pick, then the agent's pick, then the request's own; the pick wins over a level Cursor sent, as Cursor sends its own default as often as a choice, and Default hands the choice back to Cursor. A model that doesn't reason and every other agent's requests go as they came. The Settings page's save carries agentEfforts over as it does fastPicks (TestSettingsSaveKeepsWhatItDoesNotSend's fixture names it). In the window the field sits under "Settings" rather than "New sessions" for this agent alone, as it applies to every request from then on, not to what a session starts on; no new t() strings (Settings, effort and default are translated in zh, ja and de already). TestAgentEffortOnCursorLocal (Chat and Responses with and without a level of their own, Claude on Messages made to think, OpenCode's request untouched, a non-reasoning model untouched, a member fixed at low kept, the pick taken off) fails without the gateway change and passes; TestCursorLocalEffort covers the options (none with no reasoning model, the union of levels, the low/medium/high fallback, none while disconnected), Apply and a bad level refused; both pass -race (-count=20 gateway, -count=10 agent). cursor-local-effort.test.cjs (the window at 560px and the tray panel at 440px: the field under Settings, the slider's five stops with Default first, a pick posting the field, no sideways scroll) passes on Chromium and WebKit in en, zh, ja and de; claude-effort, subagent-effort, agent-field-names, agent-connect, agent-layout, agent-tweak-fold, agent-models, cursor-local-launch and i18n pass on both. go test -tags nogui ./internal/..., go vet, gofmt -l, linux (nogui) and windows builds pass. Not tried with the real Cursor Private Inference: whether it sends a reasoning level of its own on models it shows a Reasoning control for is unverified, which the precedence makes moot. docs/subsystems/gateway-routing.md and agent-wiring.md say it.
yetone
2026-10-06 17:28:24 +08:00 -
ec6df1de57
gateway: a request offering web search is translated on the client's own API where the provider serves it, not on Chat (#997, wlj521: Codex on Xiaomi MiMo's pay as you go showed "Responses → Chat" on v0.1.1082 with all three URLs filled) — Codex offers its web_search tool on every turn, and a provider that doesn't search by itself can't be relayed it: magpie searches for it, which takes a translated request. That translation went to usable's first API, Chat for every model but OpenAI's and Claude's, so on any provider serving Responses (MiMo, whose api.xiaomimimo.com/v1/responses answers 401 to a made-up key, a wrong path 404) each Codex turn went out as Chat, and Claude Code's WebSearch on a provider with an Anthropic URL the same. The translation now stays on the client's API when the provider serves it there and neither ResponsesFirst nor MessagesFirst prefers another (firstElsewhere); the OnMessages check moved before the search check so a Claude model still goes to Messages. The search tool itself isn't sent to the provider (forwardTranslated drops it where the provider doesn't search), and magpie's own web_search function is given on Responses as it was on Chat. TestSearchOfferedStaysOnTheClientsAPI (the preset's three URLs; Codex's Responses with a function and web_search, Claude Code's Messages with web_search_20250305, Chat's web_search_options) and TestCodexSearchAnsweredOnResponses (a searcher configured: the model is asked on /v1/responses twice, the search runs once, Codex gets the answer) fail without the change (/v1/chat/completions) and pass; -race -count=5 on them and TestWebSearch*, TestClaudeFromChat*, TestAutoRelays*. go test ./internal/gateway ./internal/provider, go vet, gofmt -l, linux/windows (nogui) builds pass. Not tried with a real MiMo key (none on this machine) or a real Codex. docs/subsystems/gateway-routing.md says it.
yetone
2026-10-06 17:16:35 +08:00 -
28c58adcb3
feat(gui): discover local provider configurations with persistent dismissal (#879)
zzzpyyy
2026-10-06 17:16:39 +08:00 -
4fb8e887c2
claude: a reply that fills its segment before the request reads it doesn't hold the run's lock (CI Test 37434913069 on
8fbd39f8: TestClaudeLastWordsAreRead hung 20m on macOS -race) — emit sent each event to the segment, a 64-event channel, with r.mu held, and the request calls run.heard, which takes r.mu, before it starts reading the segment. A Claude Code that wrote more than 64 events first (the test's 300 deltas, or a quick long reply) left emit blocked on the send holding the lock and heard waiting on it: the run hung for good, and abort, stderr and every other r.mu user with it. emit now takes the segment and updates shown under r.mu, lets it go, and sends under a lock of its own (sending); the two places that close a segment (endSegment, finish) take that lock first, so a send never meets a closed channel. Events keep their order, emit being serialised by sending. TestEmitOnFullSegmentLeavesTheRunFree (200 events emitted before anyone reads, then heard) fails on the old code (heard waits) and passes; it, TestClaudeLastWordsAreRead, TestClientGoneRecordedAsCanceled and Keepalive pass -race -count=20; the whole gateway package passes -race; go vet, linux(nogui)/windows builds pass. The other sends in the file (answered, the MCP waiter) already send after unlocking.
yetone
2026-10-06 17:06:38 +08:00 -
e6b0827179
gateway, provider: a Claude model is asked on Messages from Chat and Responses too, where its provider has an Anthropic URL (ReturnTrue on Discord, after #997/050e629b: OpenCode on supergpt@claude-opus-5-5 had no cache with Chat, Responses and Anthropic URLs filled; Chat → Anthropic with Anthropic alone wrote 16,318 to the cache) — OpenCode speaks Chat to magpie for every model, Claude's too, and a request on an API the provider serves was relayed as it is, so with a Chat URL beside the Anthropic one OpenCode's Chat went on as Chat and the cache breakpoints the translation to Messages adds never reached the relay; Codex's Responses at a relay with a Responses URL the same. MessagesFirst only ordered the APIs a request is translated to. Provider.OnMessages (MessagesFirst, no "Asked on" set for the model, and no vendor list naming its APIs) now stops the pass-through for Chat and Responses when Messages is usable, and the request is translated there. A model the user pinned to Chat stays on Chat; Copilot's Claude, whose list names Chat and Messages, and every non-Claude model are relayed as before; Messages is relayed as it is. The reporter's second setup (Responses and Anthropic URLs, Chat → Responses in the screenshot) already went to Messages on v0.1.1081 — Chat isn't served there and
050e629bput Messages first — so that screenshot was from before it. TestClaudeFromChatIsAskedOnMessages (the reporter's three setups plus Chat+Anthropic, OpenCode's streamed Chat with a system prompt and a tool and Codex's Responses, each asserting /v1/messages upstream with cache_control in the body) fails without the change on the Chat+Responses+Anthropic and Chat+Anthropic setups and passes; TestClaudePinnedToChatStaysOnChat guards the pin; TestClaudeGoesOnMessagesWhereTheRelayHasThem and TestAutoRelaysOnTheClientsOwnAPI now expect Messages for Claude (GLM and GPT unchanged). -race -count=5 on them and the Copilot tests. go test ./internal/gateway ./internal/provider, go vet (darwin, windows), gofmt -l, darwin, linux (nogui) and windows builds pass. Not tried with the real SuperGPT relay or a real OpenCode. OpenCode's config still names @ai-sdk/openai-compatible for every model; a per-model Anthropic SDK was not added, as it isn't tried with the real OpenCode and the gateway now does the same. docs/subsystems/gateway-routing.md says it.
yetone
2026-10-06 16:42:04 +08:00 -
51bec20c09
gui: a gateway key's menu puts its accounts beside their provider's models, not one block at the end (a follow-up to #932, July, LCorleone) — the menu the key's badge opens listed every provider's models and then all the accounts and keys together after them, so "which models" and "which plan" of one provider sat in two far-apart places, hard to give someone one vendor's plan alone. Each provider's accounts and keys now follow its own models, inside its block — the order the /api/caller-keys/models comment already described — an account by who is signed in with the plan in its note, a key by its fingerprint; a provider with accounts and no model in the list (one kept unlisted, or serving none now) keeps its accounts after the models; what the CLI kept that no provider has now stays behind every live entry, a model gone before an account signed out. Nothing of the picking changes: the same proto menu, the same opts and literalName, the same accountsSet that splits what comes back into "models-key" and "accounts-key", and no new strings to translate. gateway-key-accounts reads the order off the real menu now — relay's two accounts between its models and OpenAI's, the key behind GPT-5 — and pins the dead tail on a key held to a gone model and a signed-out account; its last badge read waits for the row to be drawn with the answers (the wait gateway-key-models already makes): without it a slow engine read the hold one action behind, a flake this sandbox shows on main's own test 5/6 runs on WebKit. gateway-key-accounts passes Chromium and WebKit, en and zh; gateway-key-models, gateway-key-groups, gateway-caller-keys, gateway-connect-keys, gateway-key-limit, i18n-keys and usage-keys-fold pass on Chromium — gateway-key-models' scroll assertion doesn't survive this machine's frozen WebKit build on main either. No Go change (the assets are embedded; no Go test reads their content). docs/reference.md's sentence on the menu follows the new order. (#988)
july_lu
2026-10-06 16:39:33 +08:00 -
c394097e02
gateway: a key held to some accounts may not compact on the sign-in Codex is on either, the last path #967 left open (a follow-up to #967, on the maintainer's invitation, LCorleone) — /responses/compact for one of Codex's own models relays as it came, deliberately (#876): the encrypted history it carries is readable only by the ChatGPT backend that sealed it, so native compaction keeps its own sign-in. But that relay spent the account Codex is signed in to with nothing of the calling key asked, so a key held to some accounts (#905) could spend an account it may not use through compaction alone, after #967 held its turns. compactSigninHeld asks, before the relay, whether the sign-in (resolved as codex/<model>, the id the catalog gives Codex's own) is one of the key's accounts — and fails closed, as the review asked: a Codex switched off in magpie, or a sign-in nowhere resolvable, is refused as an account the key may not use is, the relay reaching the sign-in all the same; the one allowance is Codex signed in with its own API key, told from auth.json's auth_mode (provider.CodexAPIKeySignedIn — the request's own Authorization can't tell it, magpie's token already in it for a keyed request). A refusal carries the API's own 403 shape naming the model and the accounts the key may use, and the two ways out: add the signed-in account to the key, or use one of magpie's models, whose compaction goes through a compaction_trigger on /responses and the key's accounts — native compaction has no other route, its history bound to that account. The account in use compacts on it as it always did, the relay untouched; a key that names no Codex account (AllowsAccount's rule), one held to some models alone, and a free key are relayed as they were. TestGatewayKeyAccountsHoldCodexCompact (held to a spare account: refused with nothing asked of OpenAI; held to the sign-in: the compaction relays as it came; naming no Codex account: allowed as ever; a free key: unchanged; and the provider switched off: refused with nothing upstream, the review's repro) fails without the gate and passes, TestGatewayKeyCompactOnCodexAPIKey pins the allowance; go test ./internal/gateway/ ./internal/provider/, go vet, linux(nogui)/windows(nogui) builds pass. (#976)
july_lu
2026-10-06 16:38:16 +08:00 -
a6d32e9abf
provider: one GroupIDOf reads a group's id from a ref, in place of five copies of the same CutPrefix(TrimSuffix(TrimSpace(ref), "[1m]"), GroupPrefix) — #1000 (
6f391bc8) taught the [1m] mark to each group lookup by pasting the expression into AutoStandIn, GroupFinder and DisabledGroup in group.go, groups_cli.go and the agent's reseat.go; a sixth lookup would have to find and copy it again. Behaviour is unchanged. go test ./internal/provider/ ./internal/agent/ and the root package pass, as do the gui and gateway Group/1m tests; build and vet pass.
yetone
2026-10-06 16:27:51 +08:00 -
5fb5cdacc4
ci: Test fails on a Go file gofmt would change — unformatted files reached main 8 times from 10-01 to 10-06 (#427's toml_literal_test.go, fdf8b01e's subscription_usage.go today), and each took a gofmt commit of its own (
aac0cd24,8fbd39f8). The step runs gofmt -l on every tracked .go file, on Linux only, as Windows checks files out with CRLF. gofmt -l is empty on main now, and it lists a file with a misformatted func. No app change.
yetone
2026-10-06 16:24:50 +08:00 -
8fbd39f8b6
provider: gofmt subscription_usage.go (a long withUser line from
fdf8b01e)
yetone
2026-10-06 16:15:30 +08:00 -
aac0cd2448
edit: gofmt toml_literal_test.go (from #427: comment alignment and the missing final newline)
yetone
2026-10-06 16:14:50 +08:00 -
efd501ec74
agent: add Reasonix Studio with separate Executor and Plan models (#427)

TryWorldandh4rK8s
2026-10-06 16:14:35 +08:00 -
f109b270c0
gui: the main window opens as it was last left — maximised again if it was, at its last size otherwise (#995)
forever
2026-10-06 01:09:58 -07:00 -
8754221203
gateway, usage: when the credits switch holds every account, the record and 429 say so accurately; the switch's text says it follows the latest reading (#999)
forever
2026-10-06 01:09:00 -07:00 -
6f391bc8f2
provider, agent, cli: a routing group's id carries Claude Code's [1m] mark (#1000)

wizzy-yangandwizzy-yang
2026-10-06 16:02:03 +08:00 -
5610f1e68a
gui: Save shows a spinner and "Saving…" while the provider's model list is fetched (zola_xynb on X: "做个转圈或者提示,不然让人觉得没反应")
yetone
2026-10-06 15:55:29 +08:00 -
56b770ec99
settings: keep awake can keep the display on too (#975, Hu9956: magpie's keep-awake held the system up, but the screen still went black and locked, and their agents record the screen to check their work, which a locked screen stops) — Settings › General's "Keep awake while agents work" row now has three segments, Off / On / Screen on too, saved as keepAwake and the new keepAwakeDisplay (default off, so On and every existing settings.json behave as before). awake.Keep takes a Level (Off, System, Display) in place of a bool; a level changed while held releases and takes the hold anew. On a Mac the display level runs
caffeinate -i -d -w <pid>(pmset -g assertions on a caffeinate of the same flags shows PreventUserIdleDisplaySleep beside PreventUserIdleSystemSleep); on Windows SetThreadExecutionState gets ES_DISPLAY_REQUIRED too; on Linux logind's idle inhibitor (systemd-inhibit --what=idle:sleep, unchanged) is all there is, and whether the screen stays on is up to the desktop. keepAwakeDisplay is this computer's own (KeepOwn) and goes in prefsKeep, so another setting's save keeps it. The row's line says the display stays on when it's chosen; it keeps its English in data-en, so a language change re-translates it. zh/ja/de strings added. No TUI or CLI has keep-awake, so none changed. TestKeepDisplay (taken once, taken anew with the display, not again at the same level, back to System, released at Off), TestCaffeinateArgs, TestExecutionState (GOOS=windows test build) and TestKeepAwakeKeptAndOwn cover it; go test ./internal/awake ./internal/settings ./internal/gui ./internal/gateway pass, -race -count=5 on awake. keep-awake.test.cjs now runs en/zh/ja/de at 900px and 440px in Chromium and WebKit (display posts keepAwake+keepAwakeDisplay true, the line changes, another setting's save keeps it, segments fit the row, On and Off clear it, no click moves the page): 32/32 twice; gui-ja, gui-de, i18n-keys and click-scroll pass. go vet (darwin, windows, linux), darwin, linux (nogui) and windows builds pass. Not tried with an agent recording the screen; the reporter is asked to confirm.
yetone
2026-10-06 15:33:16 +08:00 -
050e629b0a
gateway, provider: a Claude model is asked on Anthropic's Messages where its provider has an Anthropic URL, not Chat (#997, wlj521: 自动好像只会走chat; ReturnTrue on Discord: a relay with an OpenAI and an Anthropic URL had 0% cache hits from Claude models until the OpenAI URL was left empty) — every API's order was Chat first. A request on an API the provider serves was already relayed as it is (a fake relay recording each path: Messages → /v1/messages, Responses → /v1/responses, Chat → /v1/chat/completions, for GLM, GPT and Claude alike, with one URL for three APIs and with an OpenAI URL plus an Anthropic one), but two places fell back to Chat for Claude: usable(), which a request on an API the provider lacks is translated to (Codex's Responses for a Claude model at a Chat+Anthropic relay went to /v1/chat/completions, losing the cache breakpoints the translation to Messages adds), and Provider.Native, the API Pi, omp and droid are wired to ask each model on (route.go's magpieModels), which named Chat for every Claude model at such a relay, so those agents spoke Chat and their cache_control never reached the relay. MessagesFirst (a Claude model by name, where the provider has an Anthropic URL) now puts Messages first in both, beside ResponsesFirst for OpenAI's models. A model the user set "Asked on" for, or one the vendor's list names its APIs for, is as before; GPT and other models keep Chat first; Copilot's Claude, which its list serves on Chat and Messages, is now native on Messages (TestNative updated), the API Claude Code already reaches it on. Not reproduced: a Claude Code /v1/messages request translated to Chat — with either relay layout, a list as new-api answers it, and a key per protocol, it went to /v1/messages with cache_control kept; ReturnTrue is asked for the request's Protocol row. TestClaudeGoesOnMessagesWhereTheRelayHasThem and TestNativeClaudeIsMessagesWhereServed fail without the change and pass; TestAutoRelaysOnTheClientsOwnAPI guards the pass-through for the three APIs; -race -count=5 on them. go test ./internal/gateway ./internal/provider ./internal/catalog ./internal/agent, go vet, darwin, linux (nogui) and windows builds pass. docs/subsystems/gateway-routing.md says the order.
yetone
2026-10-06 15:33:04 +08:00 -
a711f428ad
agent: CodeBuddy Code (Tencent's CLI, codebuddy/cbc, the China edition too) is an agent that uses magpie's models (#980, yujianbinm) — the CLI (npm @tencent-ai/codebuddy-code 2.161.4, one package for codebuddy.ai and codebuddy.cn, CODEBUDDY_INTERNET_ENVIRONMENT=internal) takes models of one's own from ~/.codebuddy/models.json ($CODEBUDDY_CONFIG_DIR), the format WorkBuddy reads, hot-reloaded, and starts a session on settings.json's "model". magpie now writes its models there as WorkBuddy's are written (vendor magpie, key magpie-codebuddy, url the gateway's /v1/chat/completions), keeping the user's own entries, their order, a model turned off and availableModels (magpie's ids added when the list is there), and sets "model" to the pick, the user's other settings kept; one of CodeBuddy's own models, or default, takes magpie's models out again. A new models.json is {"models":[…]} as CodeBuddy's docs give it. workbuddyWrite becomes buddyWrite(agent), the same writer for both. CODEBUDDY_CONFIG_DIR joins agentenv.Vars; cliupdate knows the npm package. TestCodeBuddyCode (user's model, availableModels and settings kept; magpie pick; sync; back to own; default) and TestCodeBuddyCodeNewFile fail without the agent ("no codebuddy agent") and pass; -race -count=5 on the CodeBuddy/WorkBuddy tests. End to end, the real CLI 2.161.4 under a temp HOME with CODEBUDDY_INTERNET_ENVIRONMENT=internal and no CodeBuddy sign-in:
magpie codebuddy magpie/mock/proagainst a sandbox gateway on 3462, thencodebuddy -pwith no --model, answered through the gateway from a mock upstream, and magpie's usage counted it under CodeBuddy Code (the CLI sends "CLI/2.161.4 CodeBuddy/2.161.4", so it is told by its key);magpie codebuddy defaulttook it out. A bare-list models.json is read by the CLI too. Not tried with a real CodeBuddy account or a real model vendor. go test ./internal/agent/ ./internal/agentenv/ ./internal/usage/ ., go vet, darwin, linux (nogui) and windows builds pass.
yetone
2026-10-06 15:32:19 +08:00 -
a7c4e5ddde
provider: a Codex account the Codex app still sends for keeps Codex signed in — spend cap or overage with credits, limit_reached alone; a hold survives a failed read (#996)
forever
2026-10-06 00:28:41 -07:00 -
b0aef80f6c
claude: Claude Code on a magpie model is told the longest reply the model can write, CLAUDE_CODE_MAX_OUTPUT_TOKENS (H20 on Discord: DeepSeek through WorkBuddy stopped at "Claude's response exceeded the 32000 output token maximum")
yetone
2026-10-06 15:28:27 +08:00 -
94db53a847
goose: the model is read and written in the layout Goose's config is in, active_provider and the providers map as well as GOOSE_PROVIDER/GOOSE_MODEL (a follow-up to #987: the reporter's Goose 1.53 has active_provider: magpie and its model in providers.magpie.model) — Goose 1.53 reads active_provider before GOOSE_PROVIDER and the active entry's model before GOOSE_MODEL (get_active_provider/get_active_model, crates/goose/src/config/providers.rs at v1.53.0), and on every write migrates the flat keys into the map and, once a map is there, deletes them (migrate_provider_config/cleanup_legacy_provider_keys, config/migrations.rs; load_write_config, config/base.rs). magpie read and wrote only the flat keys: on a migrated config it showed no model, its Check and the restart notice never saw magpie in use, and a model picked in magpie went beside a map goose read first, so goose kept its own and deleted magpie's at its next write. Now a config with a providers map or active_provider is written as goose's set_active_provider writes it (active_provider, providers.<id>: enabled, model, configured), other providers' entries and the user's other keys and comments kept, stale flat keys removed; an old config keeps the flat keys, which an older goose reads and a newer migrates. Taking Goose off magpie clears active_provider and the flat keys as goose's clear_active_provider does, and drops magpie's own entry with its custom provider. TestGooseModelInEitherLayout (the reporter's layout with another provider; switching to a native model, to magpie's and back off; a stale flat key beside the map; the old layout left old) fails on the old code ("model = \"\", want magpie/zhipu/glm-5.3") and passes; go test ./internal/agent/ ./internal/edit/, -race -count=5 on the Goose/pick/effort tests, go vet (darwin, windows) and darwin/linux(nogui)/windows builds pass. Not tried with a real Goose Desktop.
yetone
2026-10-06 14:53:43 +08:00 -
8005f17551
provider, codex: a ChatGPT account at 100% with credits left keeps Codex signed in (#966)
forever
2026-10-05 23:50:39 -07:00 -
09b235d8b3
goose: the model list offers Goose's own providers only when this Goose is set up with them (#987, EZN7L2C3: a Goose on magpie alone listed OpenRouter and its hundreds of models, which magpie's show/hide list doesn't have and so couldn't hide) — Goose's model Options always added anthropic, openai, google and openrouter, whatever Goose had, with their models.dev lists. They now come from gooseConfigured, which reads what Goose itself records (block/goose source): the providers map's entries marked configured (set_active_provider, config/providers.rs), legacy <name>_configured markers (migrate_provider_config, config/migrations.rs), active_provider / GOOSE_PROVIDER, and a native provider whose API key is in the environment, which Config::get_secret reads before the keyring. A key only in Goose's keyring isn't read (magpie stays out of the keychain), but every provider Goose has been set up with has its configured entry. No other agent passes providers it doesn't have: Crush passes its config's providers, the rest none. TestGooseOffersOnlyItsConfiguredProviders (the reporter's config: magpie alone → no native groups; a configured entry and a legacy marker → those two, not an unconfigured openai or openrouter; OPENROUTER_API_KEY set → OpenRouter) fails on the old code with all four offered and passes. go test ./internal/agent/, -race -count=5 on the Goose tests, go vet (darwin, windows) and linux/windows builds pass. Not tried with a real Goose Desktop.
yetone
2026-10-06 14:48:59 +08:00 -
e520849b74
appdir: a folder named data in /Applications isn't taken for magpie's portable data while ~/.config/magpie has its files (#989, Treyang: after updating 0.1.1077 → 0.1.1078 magpie opened with no providers, sign-ins or settings; ~/.config/magpie was intact and /Applications/data had appeared) — portable mode (#508) took any folder named data beside the folder holding Magpie.app, and on a Mac that folder is /Applications, which every app shares. The reporter's /Applications/data began with nodes/, providers/, tools/, skills/ and an initialized marker, none of which magpie makes (magpie keeps providers.json, not a providers folder); magpie's cli-identity.json, install-id and last-version came after, written once a magpie had taken the folder for its own. The updater didn't make it: Stage only unpacks and codesign-checks the new app in .magpie-update, Install renames, Relaunch opens the bundle, and nothing runs the staged copy; appdir never creates a data folder without a .portable marker either. So a folder another app left there turned an installed magpie portable and empty at its next start. Now, in /Applications or ~/Applications, a data folder is magpie's portable data only while the installed folder (~/.config/magpie, or $XDG_CONFIG_HOME/magpie) holds no providers.json, logins.json or settings.json — one who keeps magpie portable there never ran it installed, and keeps it — or with a .portable marker beside the app, which makes it portable as before. A data folder beside magpie anywhere else (a USB stick, a Windows or Linux folder of its own, an AppImage's) is taken as before. A user already hit is back on ~/.config/magpie at the next start, and the data folder is left as it is, never deleted or moved; the log says it was passed over. TestResolveSharedFolder (/Applications stood in by a temp folder: a data folder with nodes/ and install-id, installed state present/absent, .portable, a data folder on a folder of its own) fails without the change (Resolve and Config give the shared data folder) and passes; TestSharedFolderIsApplications pins which folders count. go test -race ./internal/appdir/ (-count=3), go test ./internal/settings/ ./internal/shortcut/ ./internal/update/, go vet (darwin, windows), darwin/linux(nogui)/windows builds pass. Not tried with the reporter's /Applications or the updater on a real install.
yetone
2026-10-06 14:48:39 +08:00 -
2cd25e2e88
codex: each ChatGPT account can have its own daily warm-up time (#957) — two Plus accounts both warmed at the one codexWarmAt (07:00) started and ran out their 5-hour windows together. settings.codexWarmAtOf holds an account's own time ("HH:MM" or "off"), by its name in lower case; an account not in it follows codexWarmAt as before. warmNow picks each account's time, and the loop wakes for an account's own time even with the global one off. Settings › Usage › Warm-up, Codex: with two accounts or more, each has a row under Daily warm-up — Same (the time above), Own (a time field) or Off — saved through POST /api/settings/codex-warm-at, and the full Settings save keeps the map. zh/ja/de added.
yetone
2026-10-06 14:27:54 +08:00 -
da5f7a8115
wsl: background work never touches a WSL distro that has stopped since it was last seen running — it booted it again (TJHHHH on Discord: while they were repairing WSL, magpie kept starting it). Detection already listed only running distros, but the running set was kept for a minute (wslListAge), agents built while a distro ran kept its home and read its files from their Get/Sync/Check/Reached/Routed closures later, the sessions listing kept a distro's Running for 30s+ and read its \\wsl.localhost files, and wslrun.Find reused a tool it had found in a distro for a minute and ran wsl.exe -d on it. Any wsl.exe -d or \\wsl.localhost read boots a stopped distro, so a
wsl --shutdownwas undone within a minute, and the distro, running again, kept it going. Nowwsl.exe -l --running -q(it starts nothing; UTF-16 with or without BOM, or UTF-8 under WSL_UTF8) is asked again after 3s, and a distro not in it is not touched: a WSL agent's Get gives the last values seen, Sync/Check/Reached do nothing and Routed/Joined/Beside/Added say no; sessions aren't listed or read from it (wslCold says cold); wslrun.Find (Claude subscription sign-in and usage) doesn't reuse or run a tool there; the Library gives skills to no target in it and its MCP probe says it isn't running. An explicit action still starts it: setting a WSL agent's field, a Set on a running agent, resuming a WSL session. TestWSLStoppedSinceNotOpened (agent, sessions), TestWSLStoppedSinceNoTarget (library) and TestFindStoppedSince (wslrun) fail without the change and pass; TestUp covers the -l --running parse. On the Windows box (10.0.0.132) a test binary that had seen magpie-fedora running ran Values/Check/Reached/wslDistros/wslAgents/sessions.List/wslrun.Find against it for 4 rounds while it was stopped: it stayed Stopped andwsl -l -vwas unchanged; wslrun and agent WSL tests pass there. No distro was terminated for the check (magpie-desktop is in use, the stopped ones aren't ours). go test ./... (nogui), -race on the WSL tests, go vet, windows and linux (nogui) builds pass.
yetone
2026-10-06 14:25:38 +08:00 -
b2b72ea026
menu-scroll test: its fixture answers /api/upstream — the test failed 24/24 on main (Chromium and WebKit, every case "Target page, context or browser has been closed") since
2a990175(#971), which has the Usage and Providers pages ask /api/upstream for the vendors' status pages. menu-scroll's route handler asserts on any API it doesn't serve; that assertion ("Unexpected API: /api/upstream") was an unhandled rejection, which ended the test, and its after hook closed the browser under the first gesture, so every later case failed on page.goto. The fixture now answers it with no vendor readings, as a page that couldn't be read marks nothing. No other GUI test throws on an unknown API without serving /api/upstream. No app change. menu-scroll passes 24/24 three runs in a row (it fails 12/12 Chromium at2a990175and passes 12/12 at its parent62f9612b); click-scroll passes 18/18.
yetone
2026-10-06 14:25:14 +08:00 -
a4553cec17
gui: with reduced motion asked for, a page fades in without sliding up — sessions-talk's WebKit en and zh cases failed with "opening Continue in moved the page" (179 vs 184). No click scrolled: a probe saw no scroll event and no scroll call. The Sessions page itself was still coming in: every .view plays view-in, a fade with a 5px rise over .24s, and in WebKit the test's first row was drawn and measured inside it (main#view-sessions at 55, then 50; every ancestor moved by the same 5px and no sibling changed height), so the row reached its place after the click. view-in took no notice of prefers-reduced-motion, which the test asks for and which the app's other motions honour; there the page now only fades (fade-in), so nothing moves under the reader's pointer. Without reduced motion it is unchanged. sessions-talk passes 40/40 (10 runs, Chromium and WebKit, en and zh) with Playwright 1.62.1 and 1.63, where it failed 1–2 of 2 WebKit cases a run before; click-scroll, view-scrollbar, every sessions-*/session-*/sess-* test and the tests that look at animations (add-sheet, by-agent, library-all, claude-usage-asked, removed-*, ux-safety …) pass. menu-scroll fails 24/24 ("Target page, context or browser has been closed") with and without this change; not this commit.
yetone
2026-10-06 14:21:00 +08:00 -
b37ac53bef
codex: a switch for whether an account spends its credits (#974)
forever
2026-10-05 23:20:09 -07:00 -
5839e08189
dsh: the gateway key also goes into dsh's own key store, which the desktop app reads (#969, pan17: in the DeepSeek Harness desktop app every turn failed with
llm-pi-ai: no credential for provider route "magpie"; its profile resolves MAGPIE_GATEWAY_KEY, which is not set — store MAGPIE_GATEWAY_KEY through the credentials service (the web Models page writes it) or export it(MISSING_CREDENTIAL), while magpie showed dsh connected) — the route was in the desktop profile (magpie writes every profile; the GUI names only the first, profiles/web, which read as "magpie wrote the npm dsh's config"), but magpie put the key only in ~/.dsh/.env. In dsh 0.2 the .env files are layers the product CLI's boot (dsh-app-boot) loads; dsh-credentials-local resolves inherited environment > $DSH_HOME/.credentials.yaml > project .env > home .env, and "a composition the product CLI did not boot has only the inherited environment as its layer". So the desktop app never saw the key. magpie now also writes refs.MAGPIE_GATEWAY_KEY into ~/.dsh/.credentials.yaml, dsh's own store, which its Models page writes and every dsh reads. Details: a new store is made owner-only, version 1 (dsh refuses a store other users can read); the user's other refs, records and comments are kept; a different key of the user's under magpie's name is left alone (the check already reports it); a store in dsh's pre-release flat layout isn't touched; going back to dsh's own models takes magpie's key out. The check now asks for Apply again when the store lacks the key; a dsh wired by an earlier magpie gets it from the catalog sync magpie runs as it starts. The output of a fresh write and a merge into a store with refs, a grant record and a comment both parse with dsh 0.2.0-rc.2's own parseCredentialsDocument. TestDshDesktopGetsTheKeyFromItsStore (web+desktop profiles as on the reporter's machine) and TestDshStoreOfTheUsersOwn fail without the change and pass; -race -count=5 on the dsh tests. Fixtures that stood for "wired" with .env alone now hold the store too. Not tried with the real desktop app (not installed here; the reporter is on Windows); they're asked to confirm. go test ./internal/agent/, go vet, darwin, linux (nogui) and windows builds pass.
yetone
2026-10-06 14:12:57 +08:00 -
14b6b2b2ad
settings: the model Codex writes its memories with is picked in Settings › General too, under "Codex auto-review model" (#985, Hu9956: it was only the notebook square on Codex's card, unlabeled beside the subagent squares, and wasn't found; on a Free account two rounds of memory consolidation took the 30-day window from 100 to 34) — a "Codex memories model" row with a line that says what happens: unset, Codex's own gpt-5.6-luna and gpt-5.6-terra whatever model the conversation is on; set, this model. Its button opens the same app model picker as the square, on Codex's own memories field, and a pick goes through the same setPick and /api/set, so the row and the square are one setting: renderAgents draws the row again after every pick, from either place. No Codex with the field to pick (or gateway mode): no row. The square stays; its tooltip and aria-label now read "Codex memories model: …" in place of the bare "memories: …". zh/ja/de strings added. codex-memories-settings (Chromium and WebKit, 440px and 1100px, en/zh/ja/de: the row beside auto-review, no native select, fits the row, a pick posts {codex, memories} and lights the square, Default on the square puts the row back to Codex's own, no click scrolls Settings; no Codex, no row) fails on origin/main
97c2fac1(no row) and passes 18/18; codex-memories (its square label updated), codex-auto-review, codex-titles, i18n-keys, gui-ja, gui-de and click-scroll pass. No Go change.
yetone
2026-10-06 14:13:19 +08:00 -
97c2fac127
usage: the Sessions calendar refits on the next frame, not inside its ResizeObserver's call — sessions-overview's WebKit en case failed about 2 runs in 3 with the page error "ResizeObserver loop completed with undelivered notifications". sessCalWidth called the chart's fitCal in the observer's own call; when the room for the weeks changed, the calendar was put again with more or fewer leading cells, the card's height moved (1028x187 → 1028x190, read from a probe that logged each observer's entries), and the observer was owed that change in the same frame, which WebKit reports as the loop error. The refit now waits for the next animation frame, once per chart however many sizes arrive; a calendar drawn or redrawn still fits at once (renderSessChart calls fitCal itself). sessions-overview passes 10/10 in WebKit and 10/10 in Chromium, en and zh (it failed 3 of 4 WebKit runs before); sessions-calendar-fill, sessions-today, sessions-overview and the other sessions-* tests and click-scroll pass in both, except sessions-talk's WebKit "opening Continue in moved the page" (179 vs 184), which fails the same on origin/main
b45fe28cand is not this change.
yetone
2026-10-06 14:03:10 +08:00 -
b45fe28c9d
usage-sessions-agents-fit test: in WebKit the last agent is clicked as an element — the test failed ~70% of runs in WebKit, en and zh (a timeout waiting for Zed to be picked), and had since
c6e318e1added it: no commit turned it red. Playwright's WebKit stops hit-testing a scroller's children once it has been scrolled: on a bare page with a display:flex, overflow-x:auto strip, after scrollLeft is set every click on its buttons lands on the strip (57/57 lost; Chromium 0/58), elementFromPoint gives the strip from the next frame on, and in this test a wheel scroll does it too. The system's WKWebView, which the Mac app uses, takes the click: the same page in a WKWebView in an AppKit window, scrolled the same way, hit-tests and clicks the button. So it isn't the app. Chromium still clicks with the mouse; WebKit clicks the button through the DOM, which runs the same onclick and redraw. What the test guards is kept: with the button reuse and the bring-into-view both taken out of app.js, the WebKit cases still fail ("the picked agent stays in view"). No app change.
yetone
2026-10-06 13:51:35 +08:00 -
c0c3b20b95
agent test: OpenHanako's magpie provider names relay/glm-4.6 GLM-4.6, as the catalog names it —
e0bf3e61names a model its provider's list gives only by id as the other providers name it, and the sandbox catalog has zai's glm-4.6 as "GLM-4.6", so TestHanakoFiles and TestHanakoLive (macOS and ubuntu CI on85fd35ab) still expected "glm-4.6 · Relay". Their expectation and the plugin fixture of a current provider now say "GLM-4.6 · Relay". No app change. go test ./internal/agent/ passes.
yetone
2026-10-06 13:48:18 +08:00 -
85fd35abcb
claude: Claude Code's output is read to the end before its run ends, not cut by Wait closing the pipe (CI Test run 37415124103 on
963a8d8f: TestClientGoneRecordedAsCanceled gotevent: error … "read |0: file already closed"in place of message_stop on ubuntu, race shard) — the run read Claude Code's stdout from cmd.StdoutPipe, and launch's goroutine called Wait as soon as Claude Code exited, which closes that pipe. A run stopped as its client went away (letGo → abort → Kill) could have Wait close the pipe under readOutput before abort's finish closed the segment: the scanner failed with os.ErrClosed, readOutput emitted it as a KError, and relay wrote it to the client as the stream's error. The same cut lost what Claude Code wrote last whenever it exited: whatever was still in the pipe never reached the client. Its stdout and stderr now come through os.Pipe pipes of the run's own, which Wait doesn't touch; once Claude Code has exited and its group been ended, the run waits for both readers (up to outputDrain, 2s, for a process that left the group and still holds them), closes the pipes, then finishes. A read cut by that close is taken as the output's end, not an error. TestClaudeLastWordsAreRead (a fake Claude Code writes ~300KB and exits) fails on the old code on every run (the reply's last delta lost) and passes; TestClientGoneRecordedAsCanceled|Keepalive|TestClaudeLastWordsAreRead pass -race -count=50 under GOMAXPROCS=2 with 4 busy cores; the gateway's race suite through build/race-shards.sh (4 shards, as CI) and go vet pass. The CI failure itself didn't reproduce on macOS. Not changed: the "http: panic serving …: invalid concurrent Body.Read call" lines in the same log are in shards that pass and in green runs too (5 in 37416877275), near the Copilot auto / count_tokens tests; their handler wasn't found.
yetone
2026-10-06 13:40:52 +08:00 -
c7e4f3fe3f
claude: an account's saved sign-in isn't overwritten with the account Claude Code was just switched to (netfishx on X: Pro, Max and Team accounts, "Keep Claude Code signed in to" on; after routing moved once or twice the Usage page showed one account's plan and allowance on another's card) — magpie switching Claude Code (to the kept account, or back to it) writes the new sign-in and profile, and a Claude Code started on the account before then writes that one's profile into ~/.claude.json: the credential was the Team's, the profile the Pro's. rememberLogins paired them, and upsertLogin, matching the Pro's record by the profile, wrote the Team's sign-in and plan into it ("pro@x.com · Team"). The Pro's runs, in its own config directory seeded from that record, then spent the Team account, and their rate_limit_events and plan showed on the Pro's card. upsertLogin now refuses a Claude sign-in another saved account holds (access or refresh token) while the record the profile names doesn't: the account the profile names holding it already, or Claude Code having renewed it, it is saved as before, so a record crossed by an earlier version doesn't hold the owner back (re-adding the crossed account puts its own sign-in back). TestClaudeUsageUnavailableAfterAccountSwitch switched by the profile alone; it now writes the new account's sign-in as a switch does. Not tried with real Claude accounts (one here). claude_crossed_test (switch to Team, the Pro's profile written after, switch back: each record and each saved account's directory keeps its own sign-in and plan; a crossed record doesn't stop the Team's own being saved, renewed or not) fails without the fix on the first case and passes with -race -count=5; go test ./internal/provider/ ./internal/gateway/, go vet, linux(nogui)/windows builds pass.
yetone
2026-10-06 13:39:44 +08:00 -
fcebf8fac0
netproxy: a host on the user's own network goes around the proxy (#982, jiz4oh: a Remote magpie's model list failed with "no model list: http://server…:3425/v1/models: 502 Bad Gateway", and Test with "no model to try", while curl with the key got the list) — the 502 had no body, which magpie's own errors always carry, and the other magpie logged nothing for the refresh: the request went to the proxy magpie takes from its settings, the environment or the system (Clash and the like), which couldn't reach the LAN name and answered 502. netproxy.Func now sends a request directly when the global proxy would take it and the host is on the user's network: a private, link-local, loopback or 100.64/10 (Tailscale) address, a .local name, or a name whose every address is one of those (looked up for at most 400ms, kept a minute; a name that doesn't resolve still goes through the proxy). A provider's own proxy is still taken as it is. Models, chat, count_tokens and the shared quota cards to a Remote magpie all go through Func (http.DefaultClient and the gateway's transport), so all are covered; so is any other provider on the LAN (an Ollama on a NAS). TestLANModelListNotThroughProxy (a proxy that answers 502 to everything, set in magpie, and a fake remote magpie on this computer's LAN address) fails without the change with the reporter's bare "502 Bad Gateway" and passes; TestLANGoesAroundTheProxy (IPs, LAN/Tailscale names, a vendor, a provider's own proxy) fails without it. Not tried on the reporter's network; they're asked to confirm. go test ./internal/netproxy/ ./internal/catalog/ ./internal/provider/ ./internal/gateway/ ./internal/update/ ./internal/usage/ pass; -race -count=5 on the new tests; go vet, darwin/linux(nogui)/windows builds pass.
yetone
2026-10-06 13:35:29 +08:00 -
07326a3c26
usage test: usage-ledger-detail expects each gateway row's protocol badge and the details' Protocol (red on main since
c5b57b90) —c5b57b90(蓝猫 on Discord) tagged every gateway row of Usage › Requests with the protocol it was sent upstream in (a "src proto" badge: "Responses", "Anthropic → Chat") and added Protocol to a request's details after Endpoint. The test's "only the session file's rows are marked" read any .src, so it found the protocol badges (['Responses', 'Anthropic → Chat', …] against ['', '', …]) and failed 6/6 in Chromium and WebKit; that hid two more stale checks behind it: the details' labels and value positions without Protocol, and "the click moved the page" (50 vs 30): a row with badges is two lines tall (51px, was 32), so the first row was no longer wholly in sight at the wheel's 30px and reader.click's own wheel brought it there before clicking. The test now reads the local-session badge (.src.local) and the protocol badges (.src.proto) apart, expects Protocol (协议) in both details with its value (Responses, Anthropic → Chat), and takes the page's scroll after reader.inView of the first row, so a scroll by the app is still caught. No app change: c5b57b90~1 passes the old test 6/6, and with the row badge turned off the page doesn't move. usage-ledger-detail passes in Chromium and WebKit, en and zh; ledger-*, usage-* and click-scroll pass (257 cases), except usage-sessions-agents-fit's WebKit cases, which time out alone too, on origin/main's app (not in this commit).
yetone
2026-10-06 13:31:44 +08:00 -
0a49ffa9df
usage tests: TestOTelRetryAfterBound times the retries from the first attempt (#973) — on Windows every attempt came 30ms late in the synctest bubble: a settings file not there is waited on (steady, 1ms × 15 per path) before it is taken as gone, and the bubble's clock counts those sleeps, so the first attempt was at 30ms, not 0. The spacing between attempts, what the test is about, was exact. No app change. Passes on the Windows box and on the Mac (-race -count=5).
yetone
2026-10-06 13:32:03 +08:00 -
64a0e6c393
library tests: TestAgentsSkillsFolders gives Alma a data folder on Windows (#973) — sandbox clears APPDATA, so os.UserConfigDir failed there: Alma (whose data folder is %APPDATA%\alma) was never there, and the test's MkdirAll made an "alma" folder in the working directory. The test now sets APPDATA in its sandbox, and puts Goose's config where Goose reads it on Windows (%APPDATA%\Block\goose\config), as Alma reads the shared folder Goose's skills are in. No app change. Passes on the Windows box with and without the symlink privilege, and on the Mac.
yetone
2026-10-06 13:32:03 +08:00 -
ce9fc6758a
library: on Windows without the right to make symlinks, skills are linked with junctions (#973) — with Developer Mode off and magpie not elevated, os.Symlink fails with "A required privilege is not held by the client": a skill installed from a folder became a detached copy, and bringing in a skill from ~/.agents/skills failed outright. The library's folder links (an agent's link to the library's skill, a folder source, a skill brought in from a link or the shared folder, a carried skill's folder, relink) now go through dirLink, which on that error makes a directory junction instead: one every user may make, followed by every program as a symlink to a folder is, made natively (FSCTL_SET_REPARSE_POINT, no mklink). A target a junction can't reach (a network share) still gets the old copy. Go reports a junction as irregular, not a symlink, so ours, unlink, carry's clearing of the library, a project's place and the CC Switch update now tell a link by linkEntry (symlink or junction) and take it away with os.Remove, leaving what it points at, even when where it points can't be read. On the Windows box with every privilege stripped from the token (CreateRestrictedToken DISABLE_MAX_PRIVILEGE, whoami /priv shows SeChangeNotify only) and an explicit sandbox environment, the library's tests went from 16 failures to none (TestSkillHow, TestProjectSkillsLinked, TestCarry, TestSkillCopyEditReachesTheOthers, TestImportSharedSkillForKimi …); with the privilege they pass too. TestJunctionIsALink (Windows: magpie's own, read through, unlinked without its target, none to a share) is new; the folder fixtures that ignored a failed os.Symlink now use dirLink, and the tests' link checks accept a junction. go test -race ./internal/library/ on the Mac, go vet, windows/linux(nogui) builds pass.
yetone
2026-10-06 13:32:03 +08:00 -
7f45f45cb5
codex: a sign-in kept on ChatGPT when its account is used up (#981)
forever
2026-10-05 22:31:16 -07:00 -
e0bf3e61ed
catalog: a model the provider's catalog doesn't list is named as the other providers name it (John on Discord #feedback: GLM Coding Plan and ZCode name the same models in different case, in the provider list and the group picker) — the Coding Plan's /models (open.bigmodel.cn and api.z.ai, read with a real key) gives ids alone, glm-5-turbo, glm-5.3-flashx …; catalog.Decorate names only those Zhipu's models.dev entry (zhipuai) lists, so glm-5-turbo kept its id as its name beside ZCode's GLM-5-Turbo. models.dev's load now keeps each model's name as most providers serving it give it (a name that is only the id casts no vote), catalog.NameOf reads it by bare id in any case, and catalog.Named fills it in for a model still named by its id: in Available's live list and for a pick the list doesn't have. Ids, and what is sent upstream, are unchanged; a name the user gave stays theirs (Catalog's Name, with the new one as Default); an Azure provider is left out, its deployments named as their owner named them. TestModelNamedAsElsewhere (the Coding Plan's real ids, a user name, an Azure deployment) fails without the provider change and passes; TestNameOf is new. TestSearcherChosen read whatever catalog an earlier test had left loaded, and now resets it: with none, its lists' models go by their ids, as it expects. go test ./internal/catalog/ ./internal/provider/ ./internal/gateway/ ./internal/gui/ . pass; -race -count=5 for the two new tests; go vet, linux(nogui)/windows builds pass. No GUI change (the GUI and the group picker show name || id).
yetone
2026-10-06 13:09:40 +08:00 -
b0558485fb
library test: TestWSLSkillsCopied expects copies where no symlink can be made (#973, TryWorld2026) — on Windows without SeCreateSymbolicLinkPrivilege (Developer Mode off, not elevated) it failed with "got a copy, not a link", "changed: []" and "the copy wasn't made again". Both come from InstallSkills: where os.Symlink fails it copies the source folder into the library, so this machine's Claude Code gets a marked copy and an edit made in the source folder never reaches the library, and Sync has nothing to give the WSL agents. The test now asks whether a symlink can be made here: if so, as before; if not, this machine's entry must be magpie's marked copy, and the edit is made in the library's own copy, which Sync must then give to codex@wsl. No app change. Reproduced on the Windows box (3/3, the reporter's three errors) by running the test binary with a restricted token holding no privileges (CreateRestrictedToken DISABLE_MAX_PRIVILEGE); with the fix it passes there 3/3, passes with the privilege 3/3, and on the Mac with -race -count=5; go test ./internal/library/ and go vet (darwin, windows) pass. Without the privilege 16 other library tests still fail on the box (symlink-only assertions, and ImportSkill of a skill in ~/.agents/skills, which returns the symlink error); not in this commit.
yetone
2026-10-06 13:07:29 +08:00 -
88586975b2
usage test: the append budget is what 8000 records add, not a fixed count (#972, TryWorld2026) — TestRequestPageAppendDoesNotRepriceHistory failed on Windows at 3005–3028 allocations against 3000. pprof of the measured call on the Windows box: the extra over the Mac (2076 → ~2475 there) is a fixed per-query cost, provider sign-in lookups reading the environment and registry (syscall.Getenv, UTF16FromString, registry.ExpandString, LoginPath), not per block, so it varies with the machine. The test now subtracts the same append+query over a 10-record history: re-reading the 8000-record history costs ~65000 more (8 per record), the incremental path ~720 (~50 per block of 500); the budget is 2000 more. The sealed-block pointer check (no re-pricing) is unchanged. No app change. With the log snapshot forced to re-read, the test fails (66865 vs 1448); it passes on the Mac (-race -count=20) and on the Windows box (count 10). The sibling budgets in incremental_log_test.go measure 528/633 and 50/52 (Mac/Windows) against 1500 and are left.
yetone
2026-10-06 13:00:23 +08:00 -
fdf8b01e04
claude: a Team seat and a personal plan of one email keep their own usage (netfishx on X: with several Claude Code accounts the Usage page's allowances and plans got mixed up) — ClaudeCodeMovedOff matched "me@x.com · Acme" and "me@x.com" as one account, so once Claude Code was switched from one to the other, a /usage run asked for the first (by a caller that looked before the switch) and a rate_limit_event of a run started on it were kept as the first's: the seat's card showed the personal account's figures, and the other way round. Claude Code's account named from its profile (~/.claude.json) is now told by its whole name; the email-alone match stays only for a name from
claude auth status, which gives no org. The single-account Usage card reads its allowance under the name magpie gives the account (claudeAccount), not status's email, so a seat's own reading and what it said answering are found under one key. Not tried with two real Claude accounts of one email (only one here). claude_seats_test (Claude Code moved seat → personal and back; the seat keeps its 5% after /usage reads 100% on the personal account) fails without the fix and passes with -race -count=3; go test ./internal/provider/ ./internal/gateway/, go vet, linux(nogui)/windows/nogui builds pass.
yetone
2026-10-06 12:54:17 +08:00 -
391c45e367
cleanup: gofmt internal/gui/caller_keys.go and internal/provider/logins.go — whitespace only (git diff -w is empty).
yetone
2026-10-06 12:47:47 +08:00 -
cc89448e69
gui tests: group-arrange reads the grips in one go, after the move's answer is drawn — moveGroup draws the new order at once and again from groups/arrange's answer, and the test measured the grips (a locator's evaluateAll) between the two: in Chromium the rows were detached by then, read as no style and no place (x NaN), and mouse.move failed with "Input.dispatchMouseEvent: Invalid parameters" (5 of 6 Chromium runs on origin/main; WebKit's timing let it through). grips() now reads the rows in one page.evaluate, and the drag waits for the answer and a frame. No app change. group-arrange passes 20/20 (5 runs × Chromium, WebKit, en, zh).
yetone
2026-10-06 12:47:46 +08:00 -
963a8d8f59
provider: Command Code's refusal of a Go plan's key says where the key works (#969, pan17) — a Go key added as the Command Code API-key provider (the preset, on api.commandcode.ai/provider) can never be served: Command Code's Provider API answers every request "Your Go plan doesn't include API access. Upgrade to Provider or higher …". APIError now adds that a Go key works through the Command Code plugin (Plugins › Command Code, signed in with the key), which asks as Command Code's CLI does. TestAPIErrorShapes and the new TestCommandCodeGoKeySaysWhereItWorks (chat, messages and responses through the gateway) fail without it and pass; go test provider/gateway/plugin/gui, vet, linux/windows builds pass.
yetone
2026-10-06 12:44:13 +08:00 -
daeb108b4e
commandcode: the mover's min is 0.1.9 (#969, pan17) — @magpie-community/opencode-commandcode-auth 0.1.9 takes a key the Provider API refuses with "Your Go plan doesn't include API access" as a Go plan's (kept in memory and saved with the key) and sends its chat completion again to /alpha/generate, where Go is served; before, a Go key whose plan wasn't known yet (billing/subscriptions slow or failing, nothing saved yet) got that refusal. keepMovedCurrent brings every moved account's plugin up to it. The plugin's go-refused.test.mjs (4 of 5 fail on 0.1.8) and check.mjs pass; 0.1.9 resolves on npm. Not tried with a real Command Code Go account (none here).
yetone
2026-10-06 12:44:13 +08:00 -
8c71cd27f9
quotas: a computer using another's magpie (remote-magpie) shows that magpie's quota cards (莫 on Discord: the second computer had the models and groups but /v1/magpie/quotas was empty, so Usage, the menu bar and magpie quota showed nothing) — each named with the remote ("Codex · Office", id office/codex, from: Office) and kept in its kind (subscription, plan, balance), on the Usage page, the tray, the TUI and magpie quota (which matches by the remote's id or name); their history comes with the local one. Only the computer holding the sign-ins asks vendors: it answers the new GET /v1/magpie/quotas/cards from what it has kept (CachedCards, never a read, whatever the age), asked here at most once in 30s; a card's refresh here POSTs /v1/magpie/quotas/refresh (that card, or every card from the remote's own card), which the remote does at most once in 30s per card. A remote keeps showing its last cards when it can't be reached; with none its one card says why (nothing read there yet, or a magpie too old to share — "update magpie there", zh/ja/de added). Cards from a third magpie aren't passed on, and the refresh path refuses them (400); a Codex reset or check-in isn't offered on a remote card. Same auth as /v1/magpie/quotas (loopback, or LAN with the key).
yetone
2026-10-06 12:38:49 +08:00 -
2a990175e5
upstream: the vendors' own status pages say when their API is down (#971, emo172: an Anthropic or OpenAI outage looked like a sign-in or quota problem) — a provider that calls Anthropic's, OpenAI's or Moonshot's API (by its base URL's host: api.anthropic.com, which the Claude subscription calls too; api.openai.com and chatgpt.com, Codex's; api.moonshot.cn/.ai) carries "API outage" (red), "API degraded" or "API maintenance" beside its name on its Providers row and its Usage card while the vendor's page says so; its title names the parts and the open incidents, and a click opens the incident (or the page), not the row. internal/upstream reads each page's Statuspage-style summary.json (status.claude.com; status.openai.com, incident.io's; status.moonshot.cn), checked by hand on 2026-10-06, and only the API's own parts (Claude API and Claude Code, not claude.ai; Chat Completions, Responses, Codex, Codex CLI, not "Codex in ChatGPT Desktop", which was degraded that day); an incident naming no parts (OpenAI's) counts only while those parts are affected. Pages are read only when the Providers or Usage page asks (GET /api/upstream), at most once in five minutes, never on a request's way; a page that fails, isn't JSON or no longer lists the API's parts is unknown and marks nothing. A relay serving the same models is never marked. DeepSeek's page (Flashduty) has nothing readable and is only known for its link; OpenRouter, xAI, Mistral and z.ai have no page read. The gateway's failure messages, TUI, CLI and tray aren't changed. zh/ja/de added. upstream_test (fake pages from the real summaries: API down, claude.ai down isn't the API, Claude Code degraded, OpenAI's app isn't the API, 503/HTML/changed page unknown, read once in Fresh and Of never waits, hosts; -race -count=5) and gui's TestUpstreamRoute pass; upstream-status (Anthropic down: its provider and the Claude subscription marked, relay/OpenAI/unread Moonshot not, click opens the incident not the row, all well marks nothing; 900 and 440px, the badge whole, the name giving way) passes in Chromium and WebKit, en and zh, and fails 8/8 without the app.js change. i18n-keys, gui-ja, gui-de, click-scroll, account-return, account-models, usage-keys-fold, deprecated-subscriptions and add-sheet pass; go vet, go test ./internal/gui/, linux/windows/nogui builds pass.
yetone
2026-10-06 12:36:02 +08:00 -
62f9612ba6
groups: a routing group's own id keeps a model's dots (#968) — a group named GPT 6.1 Sol was group/gpt-6-1-sol, so a harness pricing or tuning by the model's name didn't know it (dbydd). provider.GroupSlug is Slug with the dots kept (a run of them one, none at the ends); SaveGroup, RenameGroup, the backup's import and export, the GUI's save and rename, the CLI's group add and edit, and the Routing editor (groupSlug) derive and check a group's id by it. The ids magpie finds stay Slug's (auto-gpt-6-1-sol), so no id saved before changes, and an agent sending the bare name gpt-6.1-sol reaches a group of that id first (GroupFor). groupid_test (ids, save, rename, bad ids, found ids unchanged) and gateway's TestDottedGroupID (group/gpt-6.1-sol and the bare name served, falling through as the group's order says; -race -count=5) pass and fail without the fix; group-id-dots (hint and Add's body, groupSlug cases) passes in Chromium and WebKit, en and zh, and fails without it. The other group-* GUI tests, agent-model-groups and i18n-keys pass, except group-arrange, whose Chromium cases fail the same on origin/main (mouse.move: Invalid parameters at line 165). go test . provider/gui/gateway/agent/tui, vet, linux/windows builds pass.
yetone
2026-10-06 12:32:06 +08:00 -
a525510a79
gateway: Codex's own models, asked for by their bare names, are held to the calling key (a follow-up to #932, LCorleone) — a request to Codex's backend for one of its own models, with one account signed in and no other on, no pin and no cap, was relayed to the ChatGPT backend as it came: the one path a gateway key's holds never saw. A key held to some accounts (#905) could ask on an account it may not use, and one held to some models (#882) for a model it may not, neither asked — the relay spends Codex's own sign-in, which no list of the key's governs. codexAccounts now takes the request and serves a held key through routing always, alone no less: the model is asked as codex/<model>, the id the catalog gives Codex's own, so the key's models hold it, and the plan's candidates are held to the key's accounts as any other provider's — a key held to an account that is gone (signed out, its id kept) is refused with a 403 in the API's own error shape naming the model and the accounts the key may use, with nothing asked of OpenAI; one held to the account in use is served by it alone, asked for the model Codex asked for; a key that lists neither is relayed as it always was, its sign-in its own. Three tests, one a path: the gone account's refusal and the account in use serving alone, a models list refusing a bare native name and serving one it lists, and a free key's relay unchanged. (#967)
july_lu
2026-10-06 12:28:40 +08:00 -
5bff5a261d
gui: Routing › Requests' day bar shows the days that fit and keeps the rest under its last pill, "Earlier days" (#961, emo172: Windows 200%, text size 150%, the bar cut off at the right and the older days out of reach) — the bar scrolled with its scrollbar hidden, so with the 30 days the gateway keeps the later pills were cut at its edge and a mouse wheel (vertical) couldn't scroll it. fitDays hides the oldest pills until the bar fits (on each redraw and on resize), and the last pill opens the app menu (openProtoMenu) listing each hidden day with its count; the day looked at stays in sight: picked from the menu, the pill is named by it and pressed. A pick in an app menu (in body, outside any view) now counts as a click on the pill that opened it for the click hold in app.js, so what the day brings in above the bar doesn't push it down (~138px before). zh/ja/de added. routing-days-fit (30 days at 1100, 733 = 1100 at 150%, and 440px: nothing cut, the menu holds the rest, the oldest picked is asked for, names the pill, the bar held; 3 days no pill; narrowed then widened) passes in Chromium and WebKit, en/zh/ja/de, and fails without the fix (10/12; with only app.js reverted, 8/12 on the bar moving). click-scroll, menu-scroll, proto-menu-overflow, purpose-filter, routing-purpose-state, i18n-keys, gui-ja, gui-de and the other tests opening an app menu pass, except sessions-talk's WebKit omp case, which fails the same on origin/main (184 vs 179), and gateway-key-accounts, which failed 2 of ~26 runs while other test runs loaded the machine (badge read before its redraw) and then passed 25/25 (origin/main 20/20).
yetone
2026-10-06 11:49:18 +08:00 -
c6c7f8574a
gui tests: usage-checkin's Qoder Settings tab runs at 440x900 again — the tabs are below the fold there, and WebKit failed with the footer intercepting the click because Playwright's own scroll-into-view is not the reader's and backToReader puts the view back to scrollTop 0 within a frame (by design; Chromium's timing let it through). The test now scrolls with the wheel as a reader does, as add-button and others do, until the tab is clear of the footer, then clicks. No app change: a wheel scroll keeps its place in WebKit and the tab clicks. usage-checkin passes 24/24 in Chromium and WebKit, en and zh.

yetoneandClaude Opus 5.5
2026-10-06 11:45:46 +08:00 -
c313314f8e
gateway tests: a test run again asks its own fake Copilot and Jev — provider keeps each Copilot account's session (with the API endpoint the token named), Auto's session, terms, picks, seen and refused models by GitHub token for the process's life, and decide.go's levelled keeps Jev's answer of whether a set of intents are levels by model and intents; fresh() cleared neither. Under -count=2 the second run of TestCopilotSolOffersUltra, TestCopilotUltraSubagentWithWebSearch and the TestCopilotAuto* tests sent requests to the first run's closed server (connection refused, 502), and TestMixedDecisionGateway and TestJevLevels/TestJevLevelsSayWhatFor saw one System One call fewer. fresh() now clears levelled and calls the new provider.ForgetCopilotForTest. The tests pass with -race -count=20; the full go test -count=2 ./internal/gateway/ passes, as do go vet and go test ./internal/provider/.
yetone
2026-10-06 11:42:35 +08:00 -
8b52c521fa
qoder: daily check-in for Qoder and Qoder CN (ARNO on Discord) — while Settings › Daily check-in › Qoder (or the card's Auto check-in) is on, the gateway claims each plugin account's claimable daily credits (campaigns with actionType CLAIM_BENEFIT on openapi.qoder.sh / openapi.qoder.com.cn, as wallechfox/qoder-checkin claims them) once a Beijing day, through the plugin's fetch on its device token (@magpie-community/opencode-qoder-auth 0.2.7, now the mover's min). One claimed already is done, none listed is no event, an expired sign-in or an old plugin says so. Kept in qoder-checkin.json by site and uid; shown on the usage card, in the TUI's and CLI's check-in. Built-in (unmoved) accounts aren't checked in. Not tried with a real Qoder account. qoder_checkin_test (fake campaigns, both sites; -race -count=3) and usage-checkin (Chromium and WebKit, en and zh, fails without the GUI change) pass; go test provider/gui/tui/settings/gateway, vet, linux/windows/nogui builds pass.
yetone
2026-10-06 11:41:08 +08:00 -
e1df9135ad
gateway tests: a test run again starts routing afresh — routed (each provider's turn, each candidate's failures and tokens) and wrr's rounds live for the process, and fresh() didn't clear them, so under -count=2 TestGroupInGroupKeepsItsOwnRouting's second run began a turn on (c d a b) and TestOrderedKeepsAnAccountAt98's 502 was its second failure (a 4m backoff, not 1m). fresh() now forgets them (forgetRouting), and TestOrderedKeepsAnAccountAt98, which builds its own Server, calls it too. Both pass with -race -count=20 and fail on main under -count=2; go vet and go test ./internal/gateway/ pass.
yetone
2026-10-06 11:31:47 +08:00 -
ccf5e5a1cb
docs: VSCodium's row in the agent reference (#926) — VS Code Insiders had one, VSCodium (merged in
2f15623e) didn't: its files under VSCodium/User, its own token magpie-vscodium, and the Chat features it needs turned on, as its Notice says.
yetone
2026-10-06 11:28:50 +08:00 -
2f15623e35
feat: add VSCodium to vscode kinds (#926)
Yibo Xi
2026-10-06 11:28:31 +08:00 -
9fcfbbdfee
provider: back up unreadable saved accounts with valid JSON (#965)
Zhizhen He
2026-10-06 11:28:26 +08:00 -
3cc017cd11
proc: IsMagpie knows the name an update leaves a running exe under (#963)
TryWorld
2026-10-06 11:28:04 +08:00 -
0fc6544e53
gateway: a 429 from a key's queue is in the usage ledger, as any other turn-away (#956)
TryWorld
2026-10-06 11:27:57 +08:00 -
ac478f5884
agent: Zed is told which models think (#964) — magpie wrote Zed's openai_compatible available_models without reasoning_effort, and Zed offers its thinking switch and levels only for a model whose reasoning_effort is set and not "none" (open_ai_compatible.rs, default_thinking_reasoning_effort), so no magpie model showed it (jarod). A model that thinks is now written with reasoning_effort: high, or the strongest of its own levels when all fall short of high; only minimal–high are written, since max and none came after Zed 0.233 and a value a Zed doesn't know is a settings error. The gateway fits the level Zed sends to the model's own. A model that doesn't think gets no field, and a reasoning_effort the user set (none included) is kept through Sync and model changes. TestZedReasoningEffort fails without the fix (10 errors) and passes -race -count=20; go test ./internal/agent/, vet, windows/linux builds pass.
yetone
2026-10-06 11:26:35 +08:00 -
2533ccdaf9
gui: a model with a fast mode is sent fast, or not, from its row in the agent's picker (#954, Gyarados4157) — Fast was only a routing group member's (Group.Fast), so a GPT on a ChatGPT account picked straight in Codex or OpenCode could only go fast through a group of one. A model provider.CanFast says has a fast mode (a ChatGPT account's GPT, an OpenAI key's Responses GPT/o-series, Cursor's -fast, Claude Opus on an Anthropic key) now has a bolt beside its star in the agent's picker and in the same picker opened from a provider's row; a click posts /api/agent-fast and switches it for that agent (settings FastPicks, by agent, "<provider>/<model>", moved by a provider rename and kept by the Settings page's save) without changing the pick or closing the picker; a refusal puts it back. The field shows a bolt after a model sent fast. The gateway asks for the vendor's fast mode (service_tier "priority" / speed "fast", as for a group member) on that agent's requests for that entry, every account and key of it, not its fallbacks, another agent's requests or the same model at another provider; the Routing trace marks the try Fast. A routing group is never switched: its members say it. zh/ja/de added. fast-pick (window and 440px panel, en/zh/ja/de, Chromium and WebKit; the bolt only on models with a fast mode, the post, the refusal, the provider row's picker) passes and fails without the fix; TestFastPick (gateway, Chat/Responses/Anthropic in, fails with the gateway hunk off), TestSetFastPick, TestFastPickOptions and the settings-keep test (fails without the carry) pass with -race; i18n-keys, gui-ja, gui-de, favorite-star, agent-models-picker, claude-picker-fold and click-scroll pass; go test ./internal/gui/ ./internal/provider/ ./internal/agent/ ./internal/gateway/ ./internal/settings/ pass.
yetone
2026-10-06 11:15:52 +08:00 -
e8e97d163f
gui: a card's resets, hovered, list each one left and when it runs out, a line each, soonest first (#960, Magixyne) — the tooltip only said when the first runs out, the date the card already shows. ResetCredits.Each carries them: a Codex account's from its credits (available ones, listed only when they match the count; one that never runs out last), a GLM team's with each one's window. Codex lines are numbered ("Reset 2 runs out …"), a team's say five-hour or weekly; a plugin's, with no list, keep the one line. zh/ja/de added. reset-each-title (Usage page at 900 and 440px, the panel) passes in Chromium and WebKit, en/zh/ja/de, and fails without the fix; TestCodexResetsCounted and the team test check the order. i18n-keys, gui-ja, gui-de, codex-auto-reset, reset-reminder, routing-auto-reset, panel-usage pass; go test ./internal/provider/ ./internal/gui/ ./internal/tui/ and vet pass.
yetone
2026-10-06 10:58:03 +08:00 -
0c2dd5915e
gui: an answer read while the accounts are read again is followed by the new one (#959) — usage/quotas answers at once with the kept copy and reads the accounts again behind it, so the tray panel, opened, showed the old usage (40%) while the window's next timed read had the new (used up), until refreshed by hand (Magixyne). Such an answer now says so (X-Magpie-Reading: 1, provider.SubscriptionUsageReading), and loadQuotas asks again every 1.5s, twenty times at most, until it has landed; an answer the same as the last isn't drawn again. quota-reading-again (panel and window, en and zh, Chromium and WebKit; a read that never lands stops after twenty) passes and fails without the fix; TestSubscriptionUsageReadingWhileStale passes with -race -count=5. claude-usage-asked, panel-usage, tray-usages, usage-card-refresh, usage-stale-answer, quota-curve and usage-fold pass; go test ./internal/provider/ ./internal/gui/ and vet pass.
yetone
2026-10-06 10:50:05 +08:00 -
e564e5a309
gateway: a quit gateway's Claude bridge temp folders are swept (#958) — a run's $TMPDIR/magpie-claude-<n> (its tools.json, its work folder when it has none shared) is removed when the run ends, but a gateway quit for an update, or killed, ends none, and nothing removed them (sweepBridgeProjects only cleans Claude Code's project folders). The first run of a gateway now also removes magpie-claude-<digits> folders untouched for tempLongest (24h), as session temp files are swept: another gateway on the account (a dev build) makes its runs' folders there too, and a live run's are newer; the shared magpie-claude-work-<uid> and other names are left. TestBridgeTempsSwept fails with the sweep stubbed out; -race -count=20 passes; go vet, go test ./internal/gateway/, and windows/linux builds pass.
yetone
2026-10-06 10:53:20 +08:00 -
47c2382467
gateway: a gateway key can be held to some accounts (#905, LCorleone) — a key could burn any account a provider had: two coding plans of one vendor, and a key given to someone had them both. A key now keeps an optional list of accounts and keys (caller-keys.json "accounts", none = every account, so older files read as before): "<provider>/<account>", an account by its stable id — the login's, set once it is written and kept through renames (logins.json's savedLogin id, written by every write and stood in for by one made of the name until then, so an entry made before stands), a key by its fingerprint — every one a provider has when the list is set, and one gone later (a key rotated, an account signed out) kept as it is, matching nothing, holding the key closer, never wider. The command line and the menu name an account by who is signed in and keep its id. Routing holds a plan to the key's accounts beside its models: a candidate whose account the key may not use is left out — through a routing group the key names no less, for naming a group is not naming its accounts — the plan's order keeping step and its left-out list marking what the key held out (Weighed's held); magpie's own calls for the key — a web search, a picture described, a Codex title — are of the models the user picked, so the models don't hold them, but their spend lands on an account all the same, and the accounts hold them too. When every account or key a model has is one the key may not use, the request is refused with a 403 in the API's own error shape naming the model and the accounts the key may use, logged as a rejected call: after a usage cap's 429 and before an account barred for the model's, itself before the no-member 404. /v1/models and the Anthropic and Gemini lists show the key only the models an account or key it may use serves (a provider's account set not to serve one, #474, is none of the key's); a provider with no account and no key (a local ollama) is one the key names no account of — its models stay listed and its asks pass — while an entry kept after its provider lost its last account or key serves none, holding the key closer. The embeddings, images and videos endpoints hold their candidate to the key's accounts the same way, and counting tokens counts on those alone (the local estimate where it may use none). System One asks its decision model of an account or key the key may use — and sends on that key, of those in use, not the provider's first, which a key held to a later one was spent on; the videos and images keyed paths send on one the key may use too, held to a later key no longer refused for the first alone. Set it in the app's menu the models are picked in — the accounts after the models, an account by who is signed in with their plan in the note, a key by its fingerprint, what the CLI kept that no provider has now kept as "Not signed in now" — or
magpie gateway-key accounts <id> codex/me@example.com openai/<key id>(alltakes it off);gateway-key listhas an ACCOUNTS column. en/zh/ja/de, the German "{n} accounts" main already had left to it rather than written twice. The images API's fan-out over a subscription's accounts is held to the key's accounts as one: one of the key's whose plan refuses to draw no longer hands the pen to an account it may not use, and the second in the order, when it is the key's, draws what the first may not (drawOnAccounts filters the candidates, the account pinned to with the account header told plainly when the key may not use it). The Routing view's left-out list says an account the key held out for what it is, not that its plan lacks the model, in Chinese, Japanese and German too; the key's badge names its accounts by who is signed in, not by the stable ids it keeps. (#932)
july_lu
2026-10-06 10:51:50 +08:00 -
6f1da2c585
agent: DeepSeek Harness's own models say they go straight to DeepSeek (#955) — #834 had every agent's own models say which way they go, Codex's, Grok Build's and Antigravity CLI's custom ones; dsh's three (DeepSeek-V41-Flash, V4-Pro, V4-Flash) still said nothing (EZN7L2C3: DeepSeek Harness 未看到同样的直连入口). Since dsh 0.1.5 magpie is a custom provider beside dsh's own llm-deepseek row, which is left to dsh, so its own models go to DeepSeek on its own key: they now say direct, DeepSeek. Before 0.1.5 magpie took that row over, so nothing is said there. TestOwnModelsSayTheirPath fails without it. go vet, go test ./internal/agent ./internal/gui, GOOS=windows build pass; agent-own-path, agent-path-same and claude-picker-fold pass in Chromium and WebKit, en and zh (no GUI code changed).
yetone
2026-10-06 10:44:29 +08:00 -
6c13b00cd5
provider: a model its vendor lists with no name has one (#955) — Antigravity names none of its -tiered ids (gemini-3.6/3.7/3.8-flash-tiered: one id that thinks at the level the request asks, beside the -low/-medium/-high ids folded into gemini-3.x-flash), so the catalog carried them with an empty name: /v1beta/models gave displayName "" and agy's list and magpie's picker showed the bare magpie/antigravity/gemini-3.6-flash-tiered (EZN7L2C3). A -tiered id is now named for the model it is a tier of, "Gemini 3.6 Flash (Tiered)", and any model a vendor leaves unnamed is called by its id in the catalog (entryFor), so no agent's list is left with an empty name; a name the user gave still wins. TestAntigravityUnnamedModels and TestEntryForUnnamedModel fail without it; collapsing the real account's cached fetchAvailableModels list (27 models, three unnamed -tiered) names all three. go vet, go test -tags nogui -count=1 ./... under a temp HOME, GOOS=windows/linux builds pass. No GUI code changed.
yetone
2026-10-06 10:38:47 +08:00 -
d418b25424
fix(provider): scope quota deduplication to GLM and ZCode (#953)
tsuixl
2026-10-06 10:35:11 +08:00 -
e001d95acd
gui: a subscription's many accounts fold on the Usage page — a card of more than eight accounts (Linx on X: twenty-odd on one plugin for its daily check-ins, "顶成擎天柱") shows the first five, any opened in full and the one carrying the check-in switch, the rest behind "Show N more accounts" at its foot, as a card of many keys does; it opens them in place without moving the page and is remembered by provider (magpie.usageAcctsAll). Eight or fewer, cards of keys and the tray panel are as they were. The strings were already in zh/ja/de. usage-accounts-fold (22 WorkBuddy AI, 9 Trae with one in full and a check-in switch past the fifth, 8 Codex; open and fold by mouse, reload, 440px) passes in Chromium and WebKit, en/zh/ja/de, and fails without the fix. usage-keys-fold, usage-accounts, usage-checkin, usage-fold, usage-card-refresh, usage-quota-head, usage-arrange, usage-refresh, panel-usage, tray-usages, click-scroll, quota-read-at, quota-pools, quota-families, i18n-keys, gui-ja and gui-de pass. On a sandbox with the real accounts (Command Code's two, threshold set to one for the run) the button folds and opens the real card in place in Chromium and WebKit.
yetone
2026-10-06 10:10:06 +08:00 -
db55bc8b7a
gui: reject unknown Codex auto-review models from known providers (#938) (#943)
tsuixl
2026-10-06 09:26:04 +08:00 -
28049bd290
gateway tests: the rewind tests wait for the rewind to be asked before the resend — letGo runs on its own after the request given up on returns, and keeps the run for the resend just before it tells Claude Code to rewind; the tests sent the resend at once, so under load it found no run kept and started a new one (TestClaudeTurnGivenUpOnIsRewound: "the resend went to another Claude Code"; NotRewound/refuse: "2 runs"), and the refuse case's rewind still read rewindLongest as the silent case set it (the data race in CI run 37395880309 on
2bd49cc5). rewindAsked waits for the rewind_conversation line in the stand-in's log; the resend then waits on the rewind as a real one does. Test-only: a user rewording a message is never that quick. Before, go test -race -count=30 -cpu 1,2 of the four rewind tests failed IsRewound once; with this, -count=40 -cpu 1,2 passes.
yetone
2026-10-06 09:10:31 +08:00 -
2bd49cc545
plugin:
magpie plugin updatein a terminal restarts the app's plugin host (#952, mxzs123) — the running magpie noticed another magpie's plugin change only by plugins.json, which an update leaves as it was (the spec stays, bun installs a newer version), so the app's host went on answering with the old code (0.1.17 after the update to 0.1.18, still 403) until it was killed. The stamp checkList compares now also covers what is installed: Dir()'s package.json and bun's lockfile; and Restart takes the stamp as it is, so this magpie's own install isn't taken for another's and restarted twice. In a sandbox (serve on 3476 under its own HOME, the Factory plugin at 0.1.17, unpinned spec; a request, thenmagpie plugin updatein another process, then a request): on2d85a086the host process stayed the same (58657) with 0.1.18 installed; with this change it was replaced (60004 → 60583) and the old one stopped. TestTerminalUpdateRestartsTheHost is new and fails without the change; go vet and go test -race -tags nogui ./internal/plugin/ ./internal/provider/ under a temp HOME pass.
yetone
2026-10-06 08:47:59 +08:00 -
2d85a08635
factory: moved users get plugin 0.1.18, which cuts pi's opening sentence to its first half (#952, mxzs123) — pi 1.0.4 through the plugin got 403 "Factory: Forbidden" for "hi" on DeepSeek (chat completions) and Claude (Messages); the reporter's replays found Factory refusing "You are an expert coding assistant operating inside pi, a coding agent harness." whole, and pi's full request answered with it cut to "You are an expert coding assistant.". 0.1.18 (magpie-community/plugins 4d6dcca, on npm) does that in system and developer prompts on every route; the mover's min is raised so keepMovedCurrent updates moved users. The built-in, which has none of the plugin's Claude Code adaptations either, is unchanged. Not tried against Factory itself (no Factory account here). go vet and go test -tags nogui ./internal/provider/ under a temp HOME pass.
yetone
2026-10-06 08:10:48 +08:00 -
9abfbff607
cleanup: gofmt the 15 Go files main had unformatted — whitespace, comment alignment and three one-line funcs gofmt opens up; nothing else. go build and vet -tags nogui pass.
yetone
2026-10-06 07:43:47 +08:00 -
237d216b52
provider tests: TestUnreadLoginsNotWrittenOver counts only the copies kept from its second step — the first add also keeps the half-written file aside (writeLogins always does), named by the second; on a Mac both adds fell in one second and shared a name, on Linux CI (run 37380281215,
8278ccf2) they crossed a second and the test saw two. The test now removes the first step's copy. With a 1.1s pause before the second step the old test fails as CI did and this one passes; -race -count=20 passes.
yetone
2026-10-06 07:12:00 +08:00 -
2cc663d0a2
gui: protect unsaved edits and confirm destructive actions (#820)


2026-10-06 06:57:49 +08:00 -
9e22ed4461
gui: a provider's many keys fold on the Usage page — a card of keys, each its balance (KeyBalances: OpenRouter, DeepSeek, any provider whose balance is read per key), listed every key, a very long card (361 on Discord). It shows the first three (and any in full), the rest behind "Show N more keys" at its foot, which opens them in place without moving the page and is remembered by provider (magpie.usageKeysAll); the keys' sum in the head when each is one amount in one currency; the head's accounts button counts the keys in sight. Four keys or fewer, and a subscription's accounts, are as they were. zh/ja/de added. usage-keys-fold (12 OpenRouter keys, 5 DeepSeek with one unread, 4 Moonshot, 5 Codex accounts; folded, opens and folds by mouse without the page or card moving, reload, 440px nothing cut) passes in Chromium and WebKit, en and zh, and fails without the fix (all 12 keys listed). usage-unpriced-names, i18n-keys, gui-ja, gui-de, usage-fold, usage-accounts, usage-arrange, usage-card-refresh, usage-quota-head, panel-usage, click-scroll, balance-curve, balance-parts, api-key-usage, quota-read-at and tray-usages pass; vet, darwin/linux/windows builds and go test ./internal/gui/ pass.
yetone
2026-10-06 06:46:56 +08:00 -
1e0436250a
gui: a cost note names a model an agent recorded no name for — tokens under model "" are kept (nonzero) and unpriced, so Usage › Sessions' cost header read "未计入:,没有已知价格" / "Not counted: , with no known price" (tony, Discord). sessModelNames lists the names for every cost note (header, Overview cost tile, a row's cost and models, the detail's model list) and calls a blank one "a model with no name" (zh/ja/de added). usage-unpriced-names checks the header, a row's cost and models in Chromium and WebKit, en and zh; it fails without the fix ("未计入:,没有已知价格"). i18n-keys, gui-ja, gui-de, sessions-usage-open, usage-sessions-agents-fit, currency and sessions-overview pass.
yetone
2026-10-06 06:35:24 +08:00 -
745dde65b0
gui: the gateway watch ends with the backend, and TestGatewayTakenOver no longer leaks it — watchGateway looped forever, so each run of the test left its watch (and the gateway it took up) running after the test: under -count the old watches read gatewayWatch while the next run wrote it (3 DATA RACEs in 20 runs), and once a run's cleanup put HOME and MAGPIE_ADDR back an old watch could serve the gateway on the default address under the real HOME, which set served in the next run before its other magpie was gone ("took the gateway while the other had it", 3 of 20 with -race -count=20 on 8e853a63; the #948 review saw 3 of 10). watchGateway now takes a context and an interval and returns when the context ends; startBackend gives it backendCtx, so it also stops with stopServing instead of starting a gateway after the backend has ended. The test runs the watch on its own context and, at cleanup, stops it and the gateway it took up before its environment is restored. gatewayWatch is a const. TestGatewayTakenOver passes 50 of 50 with -race -count=50; go test -race -tags nogui ./internal/gui/ ./internal/tui/ pass; vet, darwin, linux (nogui) and windows builds pass.
yetone
2026-10-06 06:45:48 +08:00 -
8e853a63e0
cleanup: staticcheck SA4006 — the GitHub token test now checks the status GET /api/settings answers (its code was read and dropped), and BackAt declares at rather than assigning a value every branch overwrites. The gui token test and provider tests pass; SA4006 is clean in gui and provider.
yetone
2026-10-06 06:39:17 +08:00 -
8fb09d03c6
cleanup: drop redact.Known and the quotaWire type — nothing calls either (deadcode -test -tags nogui, git grep). vet on darwin, linux, windows; redact and provider tests pass.
yetone
2026-10-06 06:06:52 +08:00 -
8278ccf290
gui tests: plugin-move scrolls Zed's editor with the wheel and presses Move where it is — the test set .ebody's scrollTop to 120, which took the Move button out of sight, so Playwright's click scrolled the body back first (120 → 60 or 0, "the failure moved the editor", Chromium, failing in zh on
6e623d16and in both languages after). It now wheels only as far as the button stays in sight, and presses it with the mouse, checking it is in sight; "the failure/the move moved the editor" are unchanged. Passes in Chromium and WebKit, en and zh, twice.
yetone
2026-10-06 05:52:32 +08:00 -
9c13625c34
gui tests: currency clicks ¥ CNY where it is, with the mouse — settings rows were added, so in zh the wheel to the page's foot leaves the currency row 0.3px under the header, and Playwright's own scroll into view before its click moved the page a pixel (156 → 155, WebKit); a mouse click there moves nothing (probed: down and up keep 156). The "picking a currency must not scroll" check is unchanged. Passes in Chromium and WebKit, en and zh, twice; failed in WebKit zh on
6e623d16.
yetone
2026-10-06 05:52:32 +08:00 -
2d837c693a
gui: a "Saved" (or any outcome) in the footer no longer makes the page above it 1.5px shorter — ef13e07e's pill (12.5px text, 3px padding) stood 23.5px in the footer's 22px, so each outcome that came grew the footer to 29.5px and the view shrank under the reader; in WebKit a settings page scrolled to its foot moved a pixel on a pick. The pill's line is 16px, so it fits. currency checks #view-settings keeps its height when picking ¥ CNY saves; it fails without the fix (400.5 !== 402, Chromium) and passes with it in Chromium and WebKit, en and zh. cli-update, group-member-info, keep-awake, model-copy-id, model-test-one, panel-profiles, panel-usage, project-servers, rate-limited-sink, routing-levels, routing-compact, sse-body, tray-*, usage-alert, usage-ledger, old-webkit and click-scroll pass in both.
yetone
2026-10-06 05:52:24 +08:00