Files
magpie/internal
yetone a334fe091c gateway: a request a proxy or tunnel on this computer forwards to loopback is answered as one from another machine, and needs the gateway key (#1022, znjhahaha: asked for a Cloudflare Tunnel in magpie, with the gateway key checked for requests through it) — local() was the request's address alone, so cloudflared, ngrok, Tailscale serve or funnel, frp's HTTP proxy, or a Caddy or nginx here, all reaching 127.0.0.1:3425 for clients anywhere, were taken for this computer's agents: whoever had the tunnel's address used the user's subscriptions with any token, read /v1/magpie/quotas (the accounts and their balances) and the remote cards, and their usage was counted as local, all without sharing on. reference.md said a proxy here had to sign its clients in itself; a quick tunnel signs in nobody. A request on loopback carrying a forwarding header (Forwarded, X-Forwarded-For, X-Real-IP, Cf-Connecting-IP, True-Client-IP, Tailscale-User-Login), which no agent sends, is now proxied, and local() is false for it, so every check built on local() takes it as from the network: lanGuard refuses it while magpie isn't shared, saying it came through a proxy or tunnel and that Settings → Share on local network and a gateway key let it in; shared, it needs an enabled gateway key and is then sharedWith, as a LAN client is, for the model APIs, quotas, remote cards, concurrency, session route and MCP sign-ins; the Omarchy theme and the Codex title link stay this machine's only, and its usage is no longer marked local. A gateway MAGPIE_ADDR opens to the network stays open to a tunnel as to anyone; one MAGPIE_ADDR keeps on loopback is closed to it. MAGPIE_TRUST_PROXY=1 says a proxy here signs its clients in itself and keeps what it forwards local, as before (listed in agentenv_test's notAnAgent). A tunnel forwarding bare TCP (frp tcp, ssh -R, socat) adds no header and can't be told from an agent: reference.md says to point one at the LAN address with sharing on. Running cloudflared from magpie with a pasted Tunnel Token is not done here: magpie would have to download or run a program it doesn't ship; reference.md says how to serve magpie through a Cloudflare Tunnel today. TestProxiedLoopbackNeedsTheKey (from 127.0.0.1 with cloudflared's, ngrok's, nginx's, RFC 7239's, Akamai's and Tailscale serve's headers: refused unshared, 401 shared without a key, with magpie's token or a wrong key, let in with the key under its identity, a disabled key refused; an agent on loopback let in with any token; MAGPIE_TRUST_PROXY; MAGPIE_ADDR on loopback and open) and TestProxiedLoopbackRoutes (quotas, quotas history, models, Omarchy and /mcp through a tunnel, unshared and shared, with and without the key) fail on main (200 for every tunnel unshared) and pass -race -count=10 with TestLANGuard, TestQuotasOverLAN, TestManagedLANGuard and TestExplicitOpenGatewayAcceptsAllRemoteTokens; go test -race -tags nogui ./internal/gateway (579 s, under -timeout 30m) and go test -tags nogui ./internal/... under a temp HOME pass; go vet (darwin, windows), gofmt -l, linux and windows (nogui) builds pass. No GUI change. Not tried with a real cloudflared (not installed here); its Cf-Connecting-IP and X-Forwarded-For are what Cloudflare documents it sends.
2026-10-06 20:35:18 +08:00
..
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> (all takes it off); gateway-key list has 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)
2026-10-06 10:51:50 +08:00
gateway: a request a proxy or tunnel on this computer forwards to loopback is answered as one from another machine, and needs the gateway key (#1022, znjhahaha: asked for a Cloudflare Tunnel in magpie, with the gateway key checked for requests through it) — local() was the request's address alone, so cloudflared, ngrok, Tailscale serve or funnel, frp's HTTP proxy, or a Caddy or nginx here, all reaching 127.0.0.1:3425 for clients anywhere, were taken for this computer's agents: whoever had the tunnel's address used the user's subscriptions with any token, read /v1/magpie/quotas (the accounts and their balances) and the remote cards, and their usage was counted as local, all without sharing on. reference.md said a proxy here had to sign its clients in itself; a quick tunnel signs in nobody. A request on loopback carrying a forwarding header (Forwarded, X-Forwarded-For, X-Real-IP, Cf-Connecting-IP, True-Client-IP, Tailscale-User-Login), which no agent sends, is now proxied, and local() is false for it, so every check built on local() takes it as from the network: lanGuard refuses it while magpie isn't shared, saying it came through a proxy or tunnel and that Settings → Share on local network and a gateway key let it in; shared, it needs an enabled gateway key and is then sharedWith, as a LAN client is, for the model APIs, quotas, remote cards, concurrency, session route and MCP sign-ins; the Omarchy theme and the Codex title link stay this machine's only, and its usage is no longer marked local. A gateway MAGPIE_ADDR opens to the network stays open to a tunnel as to anyone; one MAGPIE_ADDR keeps on loopback is closed to it. MAGPIE_TRUST_PROXY=1 says a proxy here signs its clients in itself and keeps what it forwards local, as before (listed in agentenv_test's notAnAgent). A tunnel forwarding bare TCP (frp tcp, ssh -R, socat) adds no header and can't be told from an agent: reference.md says to point one at the LAN address with sharing on. Running cloudflared from magpie with a pasted Tunnel Token is not done here: magpie would have to download or run a program it doesn't ship; reference.md says how to serve magpie through a Cloudflare Tunnel today. TestProxiedLoopbackNeedsTheKey (from 127.0.0.1 with cloudflared's, ngrok's, nginx's, RFC 7239's, Akamai's and Tailscale serve's headers: refused unshared, 401 shared without a key, with magpie's token or a wrong key, let in with the key under its identity, a disabled key refused; an agent on loopback let in with any token; MAGPIE_TRUST_PROXY; MAGPIE_ADDR on loopback and open) and TestProxiedLoopbackRoutes (quotas, quotas history, models, Omarchy and /mcp through a tunnel, unshared and shared, with and without the key) fail on main (200 for every tunnel unshared) and pass -race -count=10 with TestLANGuard, TestQuotasOverLAN, TestManagedLANGuard and TestExplicitOpenGatewayAcceptsAllRemoteTokens; go test -race -tags nogui ./internal/gateway (579 s, under -timeout 30m) and go test -tags nogui ./internal/... under a temp HOME pass; go vet (darwin, windows), gofmt -l, linux and windows (nogui) builds pass. No GUI change. Not tried with a real cloudflared (not installed here); its Cf-Connecting-IP and X-Forwarded-For are what Cloudflare documents it sends.
2026-10-06 20:35:18 +08:00
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.
2026-10-06 14:48:39 +08:00
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.
2026-10-06 15:33:28 +08:00
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).
2026-10-06 13:09:40 +08:00
…
gateway: a request a proxy or tunnel on this computer forwards to loopback is answered as one from another machine, and needs the gateway key (#1022, znjhahaha: asked for a Cloudflare Tunnel in magpie, with the gateway key checked for requests through it) — local() was the request's address alone, so cloudflared, ngrok, Tailscale serve or funnel, frp's HTTP proxy, or a Caddy or nginx here, all reaching 127.0.0.1:3425 for clients anywhere, were taken for this computer's agents: whoever had the tunnel's address used the user's subscriptions with any token, read /v1/magpie/quotas (the accounts and their balances) and the remote cards, and their usage was counted as local, all without sharing on. reference.md said a proxy here had to sign its clients in itself; a quick tunnel signs in nobody. A request on loopback carrying a forwarding header (Forwarded, X-Forwarded-For, X-Real-IP, Cf-Connecting-IP, True-Client-IP, Tailscale-User-Login), which no agent sends, is now proxied, and local() is false for it, so every check built on local() takes it as from the network: lanGuard refuses it while magpie isn't shared, saying it came through a proxy or tunnel and that Settings → Share on local network and a gateway key let it in; shared, it needs an enabled gateway key and is then sharedWith, as a LAN client is, for the model APIs, quotas, remote cards, concurrency, session route and MCP sign-ins; the Omarchy theme and the Codex title link stay this machine's only, and its usage is no longer marked local. A gateway MAGPIE_ADDR opens to the network stays open to a tunnel as to anyone; one MAGPIE_ADDR keeps on loopback is closed to it. MAGPIE_TRUST_PROXY=1 says a proxy here signs its clients in itself and keeps what it forwards local, as before (listed in agentenv_test's notAnAgent). A tunnel forwarding bare TCP (frp tcp, ssh -R, socat) adds no header and can't be told from an agent: reference.md says to point one at the LAN address with sharing on. Running cloudflared from magpie with a pasted Tunnel Token is not done here: magpie would have to download or run a program it doesn't ship; reference.md says how to serve magpie through a Cloudflare Tunnel today. TestProxiedLoopbackNeedsTheKey (from 127.0.0.1 with cloudflared's, ngrok's, nginx's, RFC 7239's, Akamai's and Tailscale serve's headers: refused unshared, 401 shared without a key, with magpie's token or a wrong key, let in with the key under its identity, a disabled key refused; an agent on loopback let in with any token; MAGPIE_TRUST_PROXY; MAGPIE_ADDR on loopback and open) and TestProxiedLoopbackRoutes (quotas, quotas history, models, Omarchy and /mcp through a tunnel, unshared and shared, with and without the key) fail on main (200 for every tunnel unshared) and pass -race -count=10 with TestLANGuard, TestQuotasOverLAN, TestManagedLANGuard and TestExplicitOpenGatewayAcceptsAllRemoteTokens; go test -race -tags nogui ./internal/gateway (579 s, under -timeout 30m) and go test -tags nogui ./internal/... under a temp HOME pass; go vet (darwin, windows), gofmt -l, linux and windows (nogui) builds pass. No GUI change. Not tried with a real cloudflared (not installed here); its Cf-Connecting-IP and X-Forwarded-For are what Cloudflare documents it sends.
2026-10-06 20:35:18 +08:00
gateway, provider: the Routing page says what a lone key or account has left, and a key bought as a plan (Kimi Code, GLM Coding Plan, MiniMax, OpenCode Go, Command Code) is weighed by its plan's windows (#1016, gphysign: Kimi Code (China) with one key on, Smart routing; the card said only "Kimi Code (China) 只启用了一个 Key,无需选择。" while /v1/magpie/quotas had its 5 hours 49 used, 51 left and its 7 days 45 used) — two gaps. weighRouted returned before reading anyone's allowance when there was one candidate, so a lone key or a lone subscription account was never known in the trace, and the Routing page, which shows a bar and "{n} used · renews in {d}" for a known row, had nothing to show; every single-account subscription said "what's left not known yet" the same way. And KeyAllowance read only a sub2api key's limits, so a key whose plan the Usage page reads (planQuotaSourceOf: Kimi Code, GLM Coding Plan and Z.ai, MiniMax Coding Plan, OpenCode Go, Command Code) was never known to routing, with one key or several. weighRouted now reads what each candidate has left before returning a lone one (one alone is still weighed against nobody). readsKeyWindows takes a plan key too; keyWindows reads it through planKeyWindows (planWindows, with Zhipu's team quota behind a key that has none of its own, as PlanQuotas does), and PlanQuotas' own read of a card is noted for routing at once (noteKeyAllowance), so the Usage page and routing never ask twice; the card kept on disk seeds it after a restart (keyCardTag keys a plan card as plan:, a sub2api one as balance:). A key at such a vendor that turns out to be pay-as-you-go, with no windows, is asked again every 30 minutes (keyNoWindowsAge), not every minute it is used. So, beyond the issue: several plan keys on one provider are now weighed by their windows under Smart, usage and pace, and a plan key the vendor refuses as out of quota rests until its window renews and comes back once read short of full, as a sub2api key does. affine no longer says a lone key or account all but used up "moved" its conversation: there is nobody to move to (at > 0). The GUI needed no change. TestLoneOneTellsWhatItHasLeft (a lone Kimi key known at 49% with both renews, the week first; a lone Codex account known; a plain DeepSeek key not), TestLoneSpentKeyKeepsItsConversation (fails with the at > 0 guard taken out), TestPlanKeyAllowance (the windows read behind the request, asked once; DeepSeek and Moonshot keys never asked), TestPlanKeyAllowanceFromItsCard (PlanQuotas' read known at once without a second ask; after a restart with the vendor down, the card on disk stands in and a failed read keeps it) and TestPayAsYouGoKeyAskedSeldom (a GLM pay-as-you-go key asked once, not two minutes on, again 31 minutes on) all fail without the change and pass -race -count=20 (provider) / -count=10 (gateway). routing-key-windows.test.cjs gains a lone Kimi Code key at 1100px and 440px (the one-key story, the row's bar at 49%, its provider row "49% used · renews in …" fitting, no sideways scroll) and passes in full on Chromium and WebKit in en, zh, ja and de; routing-quota-left and panel-routing pass. go test -tags nogui ./internal/..., go test -race on ./internal/provider and ./internal/gateway in full, go vet, gofmt -l, darwin, linux and windows (nogui) builds pass. Not tried with a real Kimi Code key. StepFun's Step Plan is read through a sign-in, not the key, so it isn't one of these. docs/subsystems/gateway-routing.md, providers-accounts.md and docs/reference.md say it.
2026-10-06 20:25:04 +08:00
library, imagemcp: magpie's own image server is given to agents in WSL, started through WSL's interop with the paths crossing over (#900, cjyrainbow) — on Windows the Library names magpie-image by this machine's binary ('D:\tools\Magpie\magpie-windows-amd64.exe' mcp image), and turning it on for "Codex · WSL Ubuntu" or "Claude Code · WSL Ubuntu" failed: a Windows program was refused for every agent in WSL. WSL's interop starts a Windows .exe from inside the distro, so magpie's own server (args mcp image, whatever its binary) is now written for an agent in WSL by the binary's path in the distro, asked of the distro itself (wslpath -u, so a custom automount root is followed; asked once in ten minutes; /mnt/<drive>/ when it can't be asked), with --wsl <distro>, --mount where the drives aren't under /mnt/, --home the distro's $HOME, and WSLENV naming the variables it is given (MAGPIE_ADDR), which a Windows program otherwise doesn't get. A distro whose interop is off (no WSLInterop in binfmt_misc) is told so: turn it on in /etc/wsl.conf. A user's own Windows program is still refused, as before: its paths and variables wouldn't cross over. A Linux magpie in the distro isn't looked for: the Windows one reaches the gateway at 127.0.0.1 and reads this machine's settings, the same version as the app, which a Linux one would need the gateway's address from inside WSL for. With --wsl, magpie mcp image opens the paths the agent gives (its roots, reference images, where to save, ~/) as the distro's — /home/me/x through \\wsl.localhost\<distro>\home\me\x (\\wsl$\ where that isn't), /mnt/c/x as C:\x — answers the saved paths as the distro's (/home/me/app/generated-images/…, /mnt/c/…), and with no project falls back to the distro's ~/Pictures/Magpie. Tested on the Windows 11 test box: in a WSL2 Ubuntu 24.04 distro the library's probe gave /mnt/c/…/magpie.exe and interop:on, and this build, started from the distro as the library writes it (the .exe's /mnt/c path, mcp image --wsl, MAGPIE_GATEWAY through WSLENV to a stand-in gateway in the distro), answered initialize and generate_image with a root of /home/yeton/m900proj: a relative path and an absolute reference image (sent as /v1/images/edits), ~/m900proj/home.png and /mnt/c/… were each saved there and answered as the distro's paths. TestWSLMagpieImage (the /mnt/d command, --wsl, WSLENV, nothing rewritten or asked again at the next sync, a custom mount, interop off as a problem) and TestLinuxHome in internal/library, TestWSLPaths in internal/imagemcp are new and fail without the change; TestWSLServers (npx.cmd still refused) passes. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass (internal/provider failed once under load and passes alone).
2026-10-05 20:09:34 +08:00
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.
2026-10-06 18:22:41 +08:00
plugin, gateway, gui: a plugin can be gateway middleware — onRequest, onEvent and onResponse run in magpie's gateway on what agents send and get back (moejs) — a plugin could only be a subscription provider, run in the Bun host, a process away from the gateway. A plugin may now declare middleware (package.json magpie.middleware, or a *.middleware.js / .mjs file), run in-process by moejs (pure-Go JS engine): compiled once, one runtime per request from a pool, so every hook of a request shares ctx.state. onRequest(body, ctx) sees the agent's body in its own API before routing; what it returns is the body the next middleware and the gateway get, undefined leaves the bytes as they were, ctx.reject(status, msg) answers with an error in the agent's API's shape. onEvent(ev, ctx) sees each SSE event's JSON (export const events = [...] filters by name; an object changes it, null drops it, undefined passes the bytes through untouched; data: [DONE] is no JSON and passes by). onResponse(body, ctx) sees a whole JSON reply, with ctx.status. ctx has protocol, model, stream, path, agent, the plugin's options and state. A hook that throws, returns a non-object or runs past its limit (250ms request/response, 50ms an event, 1s load; interrupted) leaves the request or reply as it was and is counted and logged (60 lines a minute); a compressed reply passes as-is. Files are reloaded within a second of a change. Only agents' requests run through it (serveAgent at the gateway's agent entry points and the Codex backend), never magpie's own titles, classifier or stand-ins; it sees bodies before redaction. A middleware-only package is skipped by the Bun host. The Plugins page marks a middleware with a chip, its hooks, calls and µs per call (JSON in and out counted), failures in red with the last error, and a load error. en/zh/ja/de; docs/reference.md and usemagpie.ai/docs/plugins#middleware (en/zh/ja) with an alias example. Tested with a real DeepSeek account through magpie web --gateway in a sandbox HOME: an alias middleware served "fast" and named it in message_start, a reject came back as 403 permission_error, a Chat stream uppercased; real Claude Code (sandbox CLAUDE_CONFIG_DIR) did Read, Bash and Glob tool turns through three middlewares with 0 failures: ≈1–1.6µs an event, 0.2–0.7ms an onRequest that parses and rewrites Claude Code's ≈80KB body. Real use found two bugs fixed here: [DONE] parsed as JSON (SyntaxError on every Chat stream) and stats leaving out the JSON time. internal/middleware tests (chain, undefined keeps bytes, reject, fail-open on throw/spin/string with the runtime reused, async hook, load errors, off and Bun skip, reload, split/CRLF/filtered/dropped stream events, [DONE] untouched, whole reply and gzip, 64 in parallel; BenchmarkEvent ≈0.9µs, 11 allocs) and TestMiddlewareThroughGateway (alias routed, events by protocol, redaction still upstream, reject in Anthropic's shape; fails without the change) are new; plugin-middleware.test.cjs (Chromium and WebKit, en and zh: chip, hooks and stats, failures with last error, broken and off, no left border) fails without it. The other plugin GUI tests and click-scroll pass in WebKit. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass (internal/provider failed once under load and passes alone).
2026-10-05 19:14:31 +08:00
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.
2026-10-06 13:35:32 +08:00
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.
2026-10-06 18:40:45 +08:00
gateway, provider: the Routing page says what a lone key or account has left, and a key bought as a plan (Kimi Code, GLM Coding Plan, MiniMax, OpenCode Go, Command Code) is weighed by its plan's windows (#1016, gphysign: Kimi Code (China) with one key on, Smart routing; the card said only "Kimi Code (China) 只启用了一个 Key,无需选择。" while /v1/magpie/quotas had its 5 hours 49 used, 51 left and its 7 days 45 used) — two gaps. weighRouted returned before reading anyone's allowance when there was one candidate, so a lone key or a lone subscription account was never known in the trace, and the Routing page, which shows a bar and "{n} used · renews in {d}" for a known row, had nothing to show; every single-account subscription said "what's left not known yet" the same way. And KeyAllowance read only a sub2api key's limits, so a key whose plan the Usage page reads (planQuotaSourceOf: Kimi Code, GLM Coding Plan and Z.ai, MiniMax Coding Plan, OpenCode Go, Command Code) was never known to routing, with one key or several. weighRouted now reads what each candidate has left before returning a lone one (one alone is still weighed against nobody). readsKeyWindows takes a plan key too; keyWindows reads it through planKeyWindows (planWindows, with Zhipu's team quota behind a key that has none of its own, as PlanQuotas does), and PlanQuotas' own read of a card is noted for routing at once (noteKeyAllowance), so the Usage page and routing never ask twice; the card kept on disk seeds it after a restart (keyCardTag keys a plan card as plan:, a sub2api one as balance:). A key at such a vendor that turns out to be pay-as-you-go, with no windows, is asked again every 30 minutes (keyNoWindowsAge), not every minute it is used. So, beyond the issue: several plan keys on one provider are now weighed by their windows under Smart, usage and pace, and a plan key the vendor refuses as out of quota rests until its window renews and comes back once read short of full, as a sub2api key does. affine no longer says a lone key or account all but used up "moved" its conversation: there is nobody to move to (at > 0). The GUI needed no change. TestLoneOneTellsWhatItHasLeft (a lone Kimi key known at 49% with both renews, the week first; a lone Codex account known; a plain DeepSeek key not), TestLoneSpentKeyKeepsItsConversation (fails with the at > 0 guard taken out), TestPlanKeyAllowance (the windows read behind the request, asked once; DeepSeek and Moonshot keys never asked), TestPlanKeyAllowanceFromItsCard (PlanQuotas' read known at once without a second ask; after a restart with the vendor down, the card on disk stands in and a failed read keeps it) and TestPayAsYouGoKeyAskedSeldom (a GLM pay-as-you-go key asked once, not two minutes on, again 31 minutes on) all fail without the change and pass -race -count=20 (provider) / -count=10 (gateway). routing-key-windows.test.cjs gains a lone Kimi Code key at 1100px and 440px (the one-key story, the row's bar at 49%, its provider row "49% used · renews in …" fitting, no sideways scroll) and passes in full on Chromium and WebKit in en, zh, ja and de; routing-quota-left and panel-routing pass. go test -tags nogui ./internal/..., go test -race on ./internal/provider and ./internal/gateway in full, go vet, gofmt -l, darwin, linux and windows (nogui) builds pass. Not tried with a real Kimi Code key. StepFun's Step Plan is read through a sign-in, not the key, so it isn't one of these. docs/subsystems/gateway-routing.md, providers-accounts.md and docs/reference.md say it.
2026-10-06 20:25:04 +08:00
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 --shutdown was undone within a minute, and the distro, running again, kept it going. Now wsl.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 and wsl -l -v was 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.
2026-10-06 14:25:41 +08:00
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.
2026-10-06 18:40:45 +08:00
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.
2026-10-06 12:36:05 +08:00
usage, gateway, gui: a reply that reasoned is timed by its answer, from its first text, not by all its output from its first token (tony on Discord #general: Usage › Requests read "gpt-6.1-sol 163 token/秒 · 8,036 个请求 · 首字 14 秒" and asked whether subagents were added up) — speed was every output token over the time after the first content (text, reasoning or a tool call). Output counts the reasoning, and a model whose reasoning is hidden (OpenAI's encrypted reasoning, a summary sent when it is done, Claude's omitted thinking) writes it before the stream shows anything, inside the TTFT; so a 2,000-token think and a 200-token answer over the 1.5 s after the first token read 2,200 in 1.5 s, 1,467 tok/s. Concurrency was not the cause: the aggregate is Σ tokens over Σ per-request windows, so subagents running at once never add up. In the owner's own usage.jsonl codex/gpt-6-luna turns read up to 600 tok/s (out 496, reasoning 224, 11,055 ms, ttft 10,228) and its aggregate 133; it is 80 now. usage.DecodeOf is the one rule: a reply with reasoning counts out − reasoning over ms − firstText, one that reasoned and wrote only tool calls tells no speed, one without reasoning is timed from its TTFT as before; DecodeWindow's burst cut (#731) still applies. Record.Decode feeds summarize, the ledger's Totals and its timeline Parts; a route keeps its reply's reasoning (Route.Reasoning, from gateway.go and codex_backend.go), and routing.js's decodeOf/speedOf times its rows, the story and a session's average by it (an older route takes its served usage's reasoning); app.js's ledDecode does the Usage page's rows, whose tooltip says the reasoning left out, and the Output speed help says the rule, in en, zh, ja and de. History is read the same way, as it keeps the reasoning and the first text. Routing never ranks by speed. TestReasoningReplyTimedByItsAnswer (the 2200/2000 turn, a summary 10 ms before the text, a concurrent subagent, tool calls only, reasoning over output, no reasoning, not streamed: 1,200 tokens in 11 s through summarize, addRow, LedgerSeries and Decode) fails without the change and passes -race -count=10; TestUsageKeepsTimeToFirstToken now has its paced replies report reasoning and checks the route keeps it. reasoning-speed.test.cjs (Routing rows 133, 100, none and 100, the story never 1,467, the session 105; the Usage rows, tooltip and KPI the same; no sideways scroll) passes on Chromium and WebKit in en, zh, ja and de, and fails against the old assets (1467 tok/s); routing-request-metrics, routing-burst-speed, usage-speed, usage-ledger-detail, i18n-keys, gui-ja, gui-de, ledger-hscroll and click-scroll pass on both. go test -race -tags nogui ./internal/... under a temp HOME passes but internal/proc's TestUserPath ("the login shell's PATH never came"), which failed under the full run's load, passes 3/3 alone and is not touched here; go vet, gofmt -l, linux and windows (nogui) builds pass. Not tried with tony's data. docs/reference.md says how output speed is counted.
2026-10-06 19:47:56 +08:00
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 --shutdown was undone within a minute, and the distro, running again, kept it going. Now wsl.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 and wsl -l -v was 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.
2026-10-06 14:25:41 +08:00
…