Files
magpie/internal
..
gateway: each gateway key can have its own limit, a token total and/or an estimated cost per day, week or month, and a spent key is refused before any provider is asked (margbug01 on #585, v0.1.671: 每个网关密钥都能设置自己的用量上限) — a gateway key named who called and the Usage page grouped its calls, but nothing held a key to an amount, so one client shared on the network could spend a subscription's whole window. A key now carries limit {period day|week|month, tokens, cost, cacheReads} in caller-keys.json, set with the new "limit-key" change. Windows are calendar ones in local time (from midnight, from Monday, from the 1st) and a call counts in the window it started in. Tokens are a call's uncached input, output and cache writes, plus cache reads when the limit says so; cost is an estimate at the Usage page's effective prices (usage.NewCoster), a call with no known price adding none and counted as unpriced. What a key used is summed from usage.jsonl (usage.Visit), so it survives a restart; the new internal/budget keeps the sums in memory per window and appendUsage writes a key's record through budget.Append, which appends and counts under one lock so a window read meanwhile never counts a call twice. Before a keyed POST reaches the mux, keyLimited reserves for it: its body's size in tokens (4 bytes a token) plus the key's mean output per call this window, at the window's cost per token or the named model's input price; it is let in while used + reserved is under the cap, so requests sent at once overshoot by about one call, and the reservation is released when the request ends, a streamed reply being settled then with the usage its vendor reported. A spent key gets a 429 in its API's error shape (Anthropic, OpenAI chat/responses, Gemini) naming the key, the cap and the reset time, with Retry-After, X-Should-Retry: false and X-Magpie-Limit-Reset, recorded as a rejected call (gateway_key_limit); other keys go on. Requests from this computer without a gateway key are not limited; a key used from this computer is. count_tokens and bridge callbacks are never held. GET /v1/magpie/limit tells a key its own status. Gateway keys rows show a Limit badge (what is used of it, per what, Spent in red) and a line under a limited key (used of limit, left, resets / refused until, in flight; unpriced calls in its title); Limit opens an editor under the row (window, tokens with k/m/万/亿, cost in US$, Count cache reads too, what is counted) staged until Save, marked unsaved, Cancel/Escape dropping it, a bad amount disabling Save, No limit removing it; en and zh; no left-border accent; no click scrolls. The CLI gains magpie gateway-key limit <id> [off | day|week|month --tokens N --cost USD --cache-reads] and a LIMIT column in list. TestKeyLimitRefusesBeforeUpstream (two calls within 700 tokens, the third a 429 the fake upstream never sees, message/type/headers, the rejected record, Anthropic's shape on /v1/messages, another key and a keyless loopback request let in, still refused after budget.Forget as after a restart, a raised and a removed limit let in, /v1/magpie/limit), TestKeyLimitHoldsParallelRequests (20 at once against 2000 tokens: some refused, at most one call over, no reservation left; without the reservation all 20 got in and 7350 were used), TestKeyLimitWindowResets, TestLimitWindow, TestLimitValid and TestGatewayKeyLimitCLI are new; gateway-key-limit.test.cjs (WebKit, en and zh) is new and passes, as do gateway-caller-keys, gateway-connect-keys, api-key-theme and click-scroll (whose request-pick case flaked once under a parallel run and passes alone, as on main). go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui -count=1 ./... under a temp HOME pass (TestFXInState's known TempDir-cleanup flake aside).
2026-10-03 00:18:16 +08:00
codex, claude: a model with a window above 272K is compacted at 272K, as OpenAI's and Anthropic's own are (X: Chen — through magpie on DeepSeek flash, Codex and Claude Code both "wait" before every answer, and more the longer the task) — magpie told both agents a 1M model's whole window, so neither compacted until near 1M and every turn sent the whole conversation again: the live ledger had turns of ~550K tokens waiting 70–90s for a first token, while magpie's own time before the vendor stays under 0.2s. OpenAI runs its own 1M-capable models at 272K in Codex (context_window 272000, max_context_window 872000) and Anthropic its at 200K unless a [1m] one is picked; magpie now does the same by default. Codex's catalog gives such a model context_window 272000 and its whole window as max_context_window; Claude Code gets CLAUDE_CODE_AUTO_COMPACT_WINDOW=272000 while it runs on magpie (it compacts at the smaller of that and the model's window, a [1m] model's too), kept apart from a value the user set and taken out with magpie. Settings → Long conversations ("Compact at 272K" / "Full window", en and zh) turns it off (settings.FullContext), and both agents' lists are written again at once. In a sandbox with the owner's real accounts: Codex's /models listed group/test and group/auto-glm-5-3-flash at 272000 with max 1000000 (stepfun's 32K and 16K as they were, gpt-6-* still 272000/872000), Claude Code on deepseek/deepseek-flash[1m] answered and its /context said 16.3k / 272k, and Full window turned both back to 1M (/context 16.3k / 1m). TestClaudeCompactWindow and the extended TestCodexCatalogToolSearch fail on the old code. go vet (darwin, GOOS=windows), builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
2026-10-04 18:27:09 +08:00
portable: a portable magpie on Windows keeps WebView2's profile in data\webview2 and makes no Start-menu shortcut, and an installed one makes the shortcut once and never puts back one the user deleted (#508, suixinex: 便携模式时还是会创建 C:\Users\<username>\AppData\Roaming\magpie.exe\ 文件夹; 开始菜单的快捷方式删除后再打开magpie会重新添加) — the AppData\Roaming\magpie.exe folder is WebView2's user data folder: Wails v3 passes WindowsOptions.WebviewUserDataPath to go-webview2 as its DataPath, and left empty it is %APPDATA%\<exe name>, which portable mode (appdir) never set. The app's Windows options (windowsOptions) now give it appdir.WebView(): data\webview2 when portable, "" (WebView2's default, so the pages' storage and cookies there are kept) when installed. The shortcut came back because shortcut.Ensure ran at every start of the desktop app and wrote Magpie.lnk whenever it was missing or stale, with nothing recording that it had been made. Ensure now does nothing when portable (no shortcut, no App Paths entry); installed, it makes the shortcut on the first start and records that in start-menu-shortcut in magpie's folder (with the exe), keeps one that is still there pointed at the exe (moved exe, update), and makes none once the file is recorded and the shortcut is gone. A user who deleted it under an older magpie isn't given it back on updating either: a missing shortcut with no record but an App Paths\magpie.exe entry already there (every older magpie wrote it at each start) counts as deleted and is recorded as such. The Start menu, the shortcut writer and App Paths are behind function variables, so the decision is tested with fakes on any OS. Checked for other leaks: autostart only refreshes an entry the user turned on (Refresh never enables), the magpie:// link registration is needed for import links and stays, as appdir's doc says, as do temp folders; the update stages beside the exe, caches already follow appdir. Not changed: the Mac's WKWebView and Linux's WebKitGTK keep their own data in the profile (Wails has no option for it), which #508 didn't report. Nothing ran on Windows (the test box isn't reachable): no real WebView2 was started with the data path and no real shortcut was made or deleted. TestEnsureOnce, TestEnsureAfterOlderMagpie, TestEnsurePortable (fake Start menu in a temp folder), TestWebView and TestWindowsOptionsWebViewData fail without the change. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build and vet ./..., GOOS=windows go test -c for shortcut, gui and appdir, go test -tags nogui -count=1 ./... under a temp HOME pass (TestUsageKeepsTimeToFirstToken and TestUserPath failed once on timing, pass rerun alone; untouched).
2026-10-02 14:39:49 +08:00
gateway: each gateway key can have its own limit, a token total and/or an estimated cost per day, week or month, and a spent key is refused before any provider is asked (margbug01 on #585, v0.1.671: 每个网关密钥都能设置自己的用量上限) — a gateway key named who called and the Usage page grouped its calls, but nothing held a key to an amount, so one client shared on the network could spend a subscription's whole window. A key now carries limit {period day|week|month, tokens, cost, cacheReads} in caller-keys.json, set with the new "limit-key" change. Windows are calendar ones in local time (from midnight, from Monday, from the 1st) and a call counts in the window it started in. Tokens are a call's uncached input, output and cache writes, plus cache reads when the limit says so; cost is an estimate at the Usage page's effective prices (usage.NewCoster), a call with no known price adding none and counted as unpriced. What a key used is summed from usage.jsonl (usage.Visit), so it survives a restart; the new internal/budget keeps the sums in memory per window and appendUsage writes a key's record through budget.Append, which appends and counts under one lock so a window read meanwhile never counts a call twice. Before a keyed POST reaches the mux, keyLimited reserves for it: its body's size in tokens (4 bytes a token) plus the key's mean output per call this window, at the window's cost per token or the named model's input price; it is let in while used + reserved is under the cap, so requests sent at once overshoot by about one call, and the reservation is released when the request ends, a streamed reply being settled then with the usage its vendor reported. A spent key gets a 429 in its API's error shape (Anthropic, OpenAI chat/responses, Gemini) naming the key, the cap and the reset time, with Retry-After, X-Should-Retry: false and X-Magpie-Limit-Reset, recorded as a rejected call (gateway_key_limit); other keys go on. Requests from this computer without a gateway key are not limited; a key used from this computer is. count_tokens and bridge callbacks are never held. GET /v1/magpie/limit tells a key its own status. Gateway keys rows show a Limit badge (what is used of it, per what, Spent in red) and a line under a limited key (used of limit, left, resets / refused until, in flight; unpriced calls in its title); Limit opens an editor under the row (window, tokens with k/m/万/亿, cost in US$, Count cache reads too, what is counted) staged until Save, marked unsaved, Cancel/Escape dropping it, a bad amount disabling Save, No limit removing it; en and zh; no left-border accent; no click scrolls. The CLI gains magpie gateway-key limit <id> [off | day|week|month --tokens N --cost USD --cache-reads] and a LIMIT column in list. TestKeyLimitRefusesBeforeUpstream (two calls within 700 tokens, the third a 429 the fake upstream never sees, message/type/headers, the rejected record, Anthropic's shape on /v1/messages, another key and a keyless loopback request let in, still refused after budget.Forget as after a restart, a raised and a removed limit let in, /v1/magpie/limit), TestKeyLimitHoldsParallelRequests (20 at once against 2000 tokens: some refused, at most one call over, no reservation left; without the reservation all 20 got in and 7350 were used), TestKeyLimitWindowResets, TestLimitWindow, TestLimitValid and TestGatewayKeyLimitCLI are new; gateway-key-limit.test.cjs (WebKit, en and zh) is new and passes, as do gateway-caller-keys, gateway-connect-keys, api-key-theme and click-scroll (whose request-pick case flaked once under a parallel run and passes alone, as on main). go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui -count=1 ./... under a temp HOME pass (TestFXInState's known TempDir-cleanup flake aside).
2026-10-03 00:18:16 +08:00
codex, claude: a model with a window above 272K is compacted at 272K, as OpenAI's and Anthropic's own are (X: Chen — through magpie on DeepSeek flash, Codex and Claude Code both "wait" before every answer, and more the longer the task) — magpie told both agents a 1M model's whole window, so neither compacted until near 1M and every turn sent the whole conversation again: the live ledger had turns of ~550K tokens waiting 70–90s for a first token, while magpie's own time before the vendor stays under 0.2s. OpenAI runs its own 1M-capable models at 272K in Codex (context_window 272000, max_context_window 872000) and Anthropic its at 200K unless a [1m] one is picked; magpie now does the same by default. Codex's catalog gives such a model context_window 272000 and its whole window as max_context_window; Claude Code gets CLAUDE_CODE_AUTO_COMPACT_WINDOW=272000 while it runs on magpie (it compacts at the smaller of that and the model's window, a [1m] model's too), kept apart from a value the user set and taken out with magpie. Settings → Long conversations ("Compact at 272K" / "Full window", en and zh) turns it off (settings.FullContext), and both agents' lists are written again at once. In a sandbox with the owner's real accounts: Codex's /models listed group/test and group/auto-glm-5-3-flash at 272000 with max 1000000 (stepfun's 32K and 16K as they were, gpt-6-* still 272000/872000), Claude Code on deepseek/deepseek-flash[1m] answered and its /context said 16.3k / 272k, and Full window turned both back to 1M (/context 16.3k / 1m). TestClaudeCompactWindow and the extended TestCodexCatalogToolSearch fail on the old code. go vet (darwin, GOOS=windows), builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
2026-10-04 18:27:09 +08:00
accounts: on Linux an account put first is read first at once, and on Windows a file being replaced isn't read as none (goal: 插件订阅 provider 跟内置的所有逻辑都要一模一样, run on Windows and Linux) — the provider tests run on the Windows test box and in WSL found two bugs of magpie's own files. Linux stamps a file's time from a clock a tick (a few milliseconds) coarse, and filememo took same time and size for the same file: two accounts swapped (Devin, Grok, WorkBuddy, ZCode, Claude…) read back in the old order, so magpie kept using the account the user had just moved down, and a moved-back plugin's accounts read as before. A file written within the last two seconds is now also compared by its bytes. On Windows a file being renamed over refuses to open for a moment ("Access is denied"), and one held open refuses to be renamed over: plugin-auth.json, plugins.json and the plugin providers read as nothing (a usage fetch right after a move showed no cards), logins.json failed to save, and host.js, reading plugin-auth.json as {}, would have written the other accounts away on the next sign-in. New internal/steady waits such a read or rename out (half a second at most, Windows only); filememo, edit.WriteAtomic, writePrivate, Kiro's writes and the plugin store go through it, host.js retries its read and rename, and the plugin store writes its files by a rename. Tests: USERPROFILE beside HOME in the test homes, the fake devin (a shell script) skipped on Windows, plugin-auth.json's mode bits unchecked there. TestReadSameSizeSameTick fails on Linux and TestRenameOverAFileHeldOpen/TestReadAFileHeldOpen on Windows without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; on Linux (WSL) the provider and plugin packages pass whole (13 failed before), on Windows the plugin package and the move/plugin/Grok/Devin/usage tests pass 10 times over (TestMoveToPlugin failed one run in a few before); 15 built-in tests that assume Unix (mode bits, Keychain, shell-script CLIs) still fail there, as before.
2026-10-01 13:27:12 +08:00
claude: Claude Code runs share a folder only the user owns, the caller's system prompt is cached apart, and auto memory is off (#626, yg3415) — main already ran every Claude Code run in one $TMPDIR/magpie-claude so Anthropic's prompt cache is read again; this takes yg3415's version of that: the folder is magpie-claude-work-<uid> (the uid where the temp folder is shared, as /tmp is on Linux), made 0700 and used only when it is a real folder the user owns that no one else can write to (else the run works in a folder of its own, as before), so nobody else can put a CLAUDE.md in front of every run; the caller's system prompt goes to Claude Code as a block of its own with a 1h cache_control breakpoint (CLAUDE_CODE_PROMPT_CACHE_TTL=1h, since Anthropic refuses a 1h breakpoint after a 5m one), so requests with one system prompt and different conversations share its cache entry; and CLAUDE_CODE_DISABLE_AUTO_MEMORY=1, so a memory Claude Code writes in one caller's conversation doesn't show up in every other one in the shared folder. Merged onto main by hand: the run still starts through binary.command/binary.env, so a Claude Code in WSL works in the shared folder too; on Windows the folder is in the user's own %TEMP% and Go reports every folder there as 0777, so the mode check is skipped there (it refused every run before). Tests from the PR (TestClaudeRunsShareAWorkDir with a home kept in git, TestClaudeWorkDirOthersCanWriteIsAvoided, TestClaudeInstructionsCachedApart) pass; go vet (darwin, GOOS=windows), builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
2026-10-04 18:25:44 +08:00
codex, claude: a model with a window above 272K is compacted at 272K, as OpenAI's and Anthropic's own are (X: Chen — through magpie on DeepSeek flash, Codex and Claude Code both "wait" before every answer, and more the longer the task) — magpie told both agents a 1M model's whole window, so neither compacted until near 1M and every turn sent the whole conversation again: the live ledger had turns of ~550K tokens waiting 70–90s for a first token, while magpie's own time before the vendor stays under 0.2s. OpenAI runs its own 1M-capable models at 272K in Codex (context_window 272000, max_context_window 872000) and Anthropic its at 200K unless a [1m] one is picked; magpie now does the same by default. Codex's catalog gives such a model context_window 272000 and its whole window as max_context_window; Claude Code gets CLAUDE_CODE_AUTO_COMPACT_WINDOW=272000 while it runs on magpie (it compacts at the smaller of that and the model's window, a [1m] model's too), kept apart from a value the user set and taken out with magpie. Settings → Long conversations ("Compact at 272K" / "Full window", en and zh) turns it off (settings.FullContext), and both agents' lists are written again at once. In a sandbox with the owner's real accounts: Codex's /models listed group/test and group/auto-glm-5-3-flash at 272000 with max 1000000 (stepfun's 32K and 16K as they were, gpt-6-* still 272000/872000), Claude Code on deepseek/deepseek-flash[1m] answered and its /context said 16.3k / 272k, and Full window turned both back to 1M (/context 16.3k / 1m). TestClaudeCompactWindow and the extended TestCodexCatalogToolSearch fail on the old code. go vet (darwin, GOOS=windows), builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
2026-10-04 18:27:09 +08:00
imagemcp: an image that takes longer than the agent gives a tool call is answered "still being made" before the agent gives up, and is saved all the same (#575: 图片生成的mcp经常遇到超时问题 — "Tool execution timed out after 30000ms" in one agent, "Error: Request timed out" in another; the agent then looked for the file, found none, and asked for the image again, and again timed out) — generate_image held its tools/call open until the gateway's image came back, and an image model (gpt-image at quality high, 1024x1536, through a subscription) often takes a minute or more, while agents give an MCP tool call as little as 30 s, the MCP SDK's default is 60 s, and only Codex and Goose have a timeout magpie writes for them (tool_timeout_sec, timeout). So the call was cut short, the generation went on unseen, and the agent's retry started another one that was cut short too. Now each generate_image and generate_video call runs as a job that goes on to its end whatever becomes of the call, and the call waits for it at most answerWithin (20 s, under the shortest of those limits): what is ready by then is answered as before (errors too); what is not is answered, not as an error, that it is still being made, where it will be saved, not to ask for it again, and an id for the new tool generation_result, which waits on the job the same 20 s at a time and answers its paths once it is saved. A call given a progressToken is sent notifications/progress every 5 s while it waits, for clients that wait longer on progress, and magpie mcp image waits for jobs still running when the agent closes it (up to ToolTimeout) so what is being made is saved. TestSlowImageAnswersBeforeTheAgentGivesUp (a gateway taking 700 ms against a 150 ms answerWithin: the call answers in time with an id and progress notifications, generation_result then answers the saved file, the gateway was asked once, an unknown id is a tool error) fails without the change (the call took 703 ms and answered only when the image was saved). go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui -count=1 ./... under a temp HOME pass (TestFXInState's known TempDir cleanup flake aside); go test -race ./internal/imagemcp passes.
2026-10-02 22:55:07 +08:00
gui, mcp: Sign in in an MCP server's editor signs in to the server the form shows, saving it first, and a server that needs no sign-in is said to work as it is (lc on Discord: exa mcp 无法OAuth使用,x-api-key + key也无法使用 agent使用的是omp) — Exa signs in only at mcp.exa.ai/mcp?login (401 with resource_metadata, auth.exa.ai, S256, dynamic registration); plain /mcp answers without one. Sign in sent only the name, so the backend probed the saved URL: a URL just changed to ?login, unsaved, got "this server answers without a sign-in: there is nothing to sign in to". Now Sign in saves the form first when its name, transport, URL or headers aren't the saved server's, leaves the dialog open, and signs in to it; a server being added, or one switched to HTTP, has Sign in too, and a later Save is of the server as saved. mcpauth.ErrNoSignIn says the server already works without signing in, so the agents can use it as it is, a key goes in Headers and a server that signs in at another address (Exa's ?login) needs that URL; the GUI shows it in Chinese. The gateway's /mcp/<name> relay already kept the signed-in URL's query (?login reaches the server) and passed the Library's headers (x-api-key) on, dropping only the gateway's own key: TestMCPProxyKeepsQueryAndHeaders holds it (the fake now records the query). omp reads magpie's mcp.json entry as written (type/url/headers, discovery/builtin.ts) and sends headers on every request (mcp/transports/http.ts): with omp 18.4.9's own client under a temp HOME, mcp.exa.ai/mcp with x-api-key listed Exa's tools and ?login without a sign-in got the 401, so the encoder is unchanged. mcp-signin.test.cjs (webkit, en and zh): an unchanged Exa says it works as it is, with nothing saved; its URL changed to ?login, Sign in saves it then signs in, the dialog stays and no click moves the page; old code started the sign-in without saving. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass but TestProvidersAnswerWhileListsComeIn, a timing test that passes on rerun; library and mcp GUI tests pass in webkit (chromium didn't launch here). Not tried: a real Exa OAuth sign-in in magpie, or omp through magpie's relay with a real account.
2026-10-04 01:52:46 +08:00
update: install.sh and magpie update take --proxy and --mirror, and magpie update mirror keeps one for the app's updates too (akic404 on Discord: 安装和update如果没有梯子也是麻烦事,能否在install.sh和magpie update后加子选项?--proxy等可自定义或默认ghproxy) — install.sh had no options and fetched with curl alone, and magpie update took none; without a way out to GitHub both failed. Now both take --proxy <url> (http, https, socks5, socks5h; install.sh also MAGPIE_PROXY=) for the feed and the download, and --mirror <prefix> (or MAGPIE_MIRROR=), a GitHub download mirror the release file is fetched from as <prefix>https://github.com/…; only github.com / githubusercontent.com URLs are put through it. No mirror is used unless one is given: magpie names none and has none by default. The feed with each file's SHA-256 is always fetched from usemagpie.ai, never through the mirror, so a file the mirror changed is refused and deleted ("… from the mirror X does not match its checksum from https://usemagpie.ai; not installed"); macOS still checks the app's signing team. magpie update mirror <prefix>|off saves Settings' updateMirror (http(s) only), which the app's own background update follows too; magpie update --mirror off skips it once; the Settings page's save keeps it. The app's update already followed Settings → Proxy (#294); magpie update --proxy overrides it for one run. The environment's ALL_PROXY / all_proxy is now honoured when HTTPS_PROXY / HTTP_PROXY are unset (Go's ProxyFromEnvironment ignores it), with NO_PROXY as curl reads it. magpie update takes --flag value or --flag=value, and an unknown word is an error instead of an update. The help, README and the docs' FAQ (en/zh/ja) say how. install.test.mjs's proxy, mirror and tampered-mirror tests, TestAllProxyFromTheEnvironment, TestUpdateThroughAMirror, TestUpdateThroughAProxyGiven and TestUpdateFlags fail on the old code and pass; TestSettingsSaveKeepsWhatItDoesNotSend covers updateMirror. A dry run of install.sh as Linux amd64 into a temp HOME installed the real 0.1.773 with its SHA-256 checked, refused a local mirror serving a wrong file, and with --proxy to a closed port failed reaching the site through it. go vet, builds (darwin, GOOS=linux, GOOS=windows), sh -n, node --test site/install.test.mjs and go test -tags nogui -count=1 ./... under a temp HOME pass but TestClaudeIdentityServedWhileAsked, a timing test that passes alone. Not tried through a real proxy server or a real public mirror; no install.ps1 exists for Windows, and the app has no field for the mirror.
2026-10-04 00:19:47 +08:00
site, omarchy: the site in Omarchy's style follows a theme set in Omarchy without a reload (owner: 现在系统换完主题后网页需要手动刷新一下才能切换到对应主题) — a browser that hasn't let usemagpie.ai reach this computer (Chromium's local network access, which nothing on the page asked for) left the page on Omarchy's default for light or dark, chosen from prefers-color-scheme once at load, and a page that had been let through could lose magpie for 30 s when one answer was slow. Now: while not let through, the page repaints as prefers-color-scheme turns (omarchy-theme-set turns GNOME's color-scheme, so Chromium's), and a "Follow my Omarchy theme" / 跟随 Omarchy 主题 button beside the chip asks the browser in one click; a permission turned to granted (the prompt, site settings) starts following at once, denied stops it; once magpie has answered, one failed or slow answer (5 s, was 3 s) keeps the page following and it asks again in 3 s (two in a row mark it absent, still asked every 3 s), and two permissions turning together poll once. The gateway's /v1/magpie/omarchy no longer asks omarchy-font-current and hyprctl again on every theme change, only when the font's or Hyprland's own files change, since a theme set reloads Hyprland and hyprctl asked meanwhile can stall (2 s timeouts each); TestCurrent checks a theme switch keeps the font without asking and fonts.conf changing asks again (fails on the old code). Tried in the Omarchy VM's Chromium with the new omarchy.js/css: not let through, the page went tokyo-night ↔ catppuccin-latte with the desktop; the button raised Chromium's "Access other apps and services on this device" prompt and the page went live on vantablack once granted, no reload; with access allowed, catppuccin-latte, tokyo-night, rose-pine and vantablack came through within 1.5–3 s each, no failed request. go vet, builds (darwin, GOOS=linux -tags nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. The prompt's Allow itself wasn't pressed (granted through the profile and CDP instead).
2026-10-04 04:17:42 +08:00
plugin: Bun is given the roots the system trusts — installing @magpie-community/opencode-kiro-auth failed with "error: UNABLE_TO_VERIFY_LEAF_SIGNATURE downloading package manifest" (chenyyi001 on X): Bun checks TLS against the roots it ships, not the system's, so behind a box that re-signs HTTPS with a root the user installed (a company proxy, a VPN, Surge's or Proxyman's MITM) magpie gets through and bun add, and a plugin's own requests, don't. bunCommand, which starts every bun (installs and the host), now sets NODE_EXTRA_CA_CERTS to the system's roots unless the user set it: on macOS the certificates in the System and login keychains the user or an admin marked as trusted (security trust-settings-export; one denied, or only kept in a keychain, stays out; Apple's own aren't added), written once a run beside Bun's cache; on Linux the distribution's CA bundle (SSL_CERT_FILE first); on Windows the ROOT store. NODE_USE_SYSTEM_CA does nothing for bun 1.4.2's installs. Reproduced with a local registry behind a re-signing proxy: bun add through bunCommand fails with the reporter's error on the old code and installs opencode-kiro-auth@0.1.7 with the proxy's root trusted; TestBunIsGivenTheSystemsRoots fails on the old code and passes, TestTrustedIn and TestSystemRootsAreTheTrustedOnes cover the trust settings. Not tried: a real keychain-trusted root (changing trust settings is left to the user), and the Windows store on a Windows machine (the test box was unreachable; it builds). go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui of internal/plugin and internal/proc under a temp HOME pass.
2026-10-04 17:18:25 +08:00
proc: the login-shell probe asks nushell through /bin/sh, and a value the shell printed instead of expanding counts as unset (#738: with nushell as the login shell, shellEnv's printf '…' "$PATH" "$CLAUDE_CONFIG_DIR" … came back as the text "$PATH", "$CLAUDE_CONFIG_DIR", … — a double-quoted string is literal in nushell — and setUnset exported every one of agentenv.Vars with its own name as the value; Claude sign-in then failed with "mkdir $CLAUDE_CONFIG_DIR: read-only file system" (putClaudeLogin's MkdirAll of that relative name under the GUI's cwd /), and the account lapsed minutes later as "Claude Code is no longer signed in", its sign-in looked for under that folder; Codex, Pi, omp and the rest were pointed at "$CODEX_HOME", "$PI_CODING_AGENT_DIR", "$OMP_PROFILE" the same way, in the GUI alone, which is the one caller of UserPath) — shellProbe builds the command: for a shell named nu it is ^/bin/sh -c '<the same printf>' (nushell has no POSIX quoting to ask with and its PATH is a list, while a child of it gets the environment as any child does, PATH joined back into one string, and sh expands the references; the outer string in nushell's single quotes where a backslash is a backslash, the format in sh's double quotes where \0 reaches printf), any other shell keeps the printf it had; and parseShellEnv takes a value that is the variable's own reference ("$CODEX_HOME", or "$PATH" for PATH) for unset, so a login shell with other quoting than POSIX leaves a variable alone rather than setting it to its name. TestShellEnvNushell runs the probe through a nushell on the machine (skipped where there is none) and checks the command's shape anyway; TestParseShellEnvUnexpanded feeds the unexpanded answer. Verified against nushell 0.116.0: PATH comes back, CODEX_HOME from the environment reaches sh, unset variables are "". (#739)
2026-10-04 11:13:50 +08:00
test: one list of the agent folders a sandbox clears, so no sandbox misses an agent (yetone, on #522: A shared list of env vars for the other sandboxes would be a welcome follow-up) — every sandbox kept its own list of the variables that point at an agent's folder, and each had drifted: the gateway's cleared 5 of the 32 magpie reads, the provider package's 2, the tui's 10, the library's 20. A sandbox clearing 2 meets whatever Grok, Kimi, Hermes, MiniMax, Qoder or ZCode the developer has installed, which is how #522 came about — Qoder's two were in the library's list and in none of the four that failed. internal/agentenv is now the one list, and the 21 sandboxes that fake an environment with no agent in it clear it, each keeping beside it the variables that are not an agent's: APPDATA and LOCALAPPDATA, which a sandbox sets to a folder of its own rather than clearing, VISUAL and EDITOR. A name in the list is one magpie reads, and TestVarsAreRead walks the sources to say so, which is how two names a sandbox had been clearing turned out to be read by nothing at all — GEMINI_CLI_HOME and OPENCODE_CONFIG in internal/backup's, while OPENCODE_CONFIG_DIR, the one internal/agent reads (agents.go:545), was not in it. Where a sandbox set an agent's folder to a fixture of its own, the clearing now comes first, since a list of 32 wipes what a list of 5 left standing. Not converted: the omp tests' four variables, which are the ones omp's own lookup consults (a Pi fork) rather than every agent's, and the warm-up's Claude Code traffic flags, which are not folders — CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC stays out of the list for that reason too, its one test setting it on purpose. On Windows 11 the whole suite answers as it did before the change: 31 packages pass, and the same 15 tests in internal/library, internal/gateway and internal/agent fail for what they failed for before (symlinks this machine has no privilege for, and TestSuffixModesReachAgentFiles). go vet ./..., go build -tags nogui (windows, GOOS=linux, GOOS=darwin), and go vet -tags nogui for linux and darwin over every package touched pass. (#543)
2026-10-02 17:22:16 +08:00
providers: each model's credit rate shows by it in the provider's model list and an agent's model picker, Qoder's 0.5× and WorkBuddy's 0.03×, a discount's price before it struck through (01huadalang on Discord: qcoder 或者 workbuddy 能不能实时抓取或者按一段时间抓取能展示列表模型的积分消耗倍率,这样不用打开软件查看,能选择到便宜的模型) — magpie read each model's price only to tell free from not: Qoder's price_factor (06584e15) and WorkBuddy's "credits" ("x0.03", wbFreeCredits), so the lists said FREE or nothing, and picking a cheap model meant opening Qoder or WorkBuddy. catalog.Model now keeps the price, Rate (the credits a request costs, as a multiple; 0 is not listed, or Free) and RateWas (the price before a discount running now), and carries both through provider.Entry, agent.Option and the providers page's models. Qoder (and Qoder CN): Rate is the price_factor; RateWas an active promotion's before_promotion_price_factor, else an original_price_factor above the price (Qwen3.8-Flash: free, 0.1× struck through), and a promotion's 0 that isn't free is its price before times its discount_factor. WorkBuddy and WorkBuddy AI (the CodeBuddy plan; both builds read the same /v3/config): Rate is the credits (wbCredits; x0.00 stays FREE, an unreadable or infinite rate is none). A plugin's model gives it as it gives free: rate and rateWas on the model its provider.models hook lists, a number (0.5) or as the vendor writes it ("x0.03", "0.5×"), which host.js reads (rateOf) into plugin.Model.Rate/RateWas and pluginCatalog keeps; the moved Qoder and WorkBuddy users get it once their plugins send it. The badge is small and grey (the context badge's colours, no uppercase), beside FREE where that shows, its title saying what a request costs (每次请求消耗 0.5× 积分;折扣前 / 限时免费,原价). Rates are as of the provider's last list: a built-in's is fetched at start-up when it has none, on Refresh and when its key changes, a plugin's when its plugins are asked again (start-up, a sign-in, a plugin change); nothing asks on a timer. Kiro, Cursor, ZCode and Devin list no per-model rate magpie reads, so they show none. TestParseModelsRate (qoder) and TestWBCreditsRate (provider) are new and don't build without the change; TestFakePlugin now checks a plugin's rate given as a number and as "x0.03" (0 for both with host.js not reading them). model-rate.test.cjs (WebKit, en and zh: the chips' badges and titles, struck-through prices, grey not green, no stripe, a click picks and scrolls nothing, the picker's badges) fails without the change and passes with it; click-scroll, models-free-only, model-pick and favorite-star pass in WebKit. No request was made to Qoder, WorkBuddy or any vendor. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui -count=1 ./... under a temp HOME, go test ./internal/gui pass.
2026-10-02 22:48:45 +08:00
gateway: a request archive, off unless turned on, keeps each call's headers and bodies in the user's own S3 bucket and a recent call reads its own back (Jorben on Discord: 请求存档) — the Gateway page's recent calls only ever held the last bodies in memory, the first 256 KB of each, with no headers, gone at a restart. A switch over Recent calls ("Request archive", POST /api/settings/archive, settings' requestArchive) turns it on, refused with a reason while sync isn't set up with an s3:// bucket; with it on, each call the gateway serves gets an id (UTC HHMMSS-<16 hex>, said back in X-Magpie-Archive-Id) and, once it is recorded, its method, path and query, request headers and body and response status, headers and body go as one JSON file to <prefix>/magpie/archive/<YYYY-MM-DD>/<id>.json in the bucket sync keeps its backup in, signed with the same client (davsync.Bucket, no new SDK). Before anything leaves, Authorization, x-api-key, Cookie, Set-Cookie, any header or JSON field or query parameter named for a key, token, secret or password, and anything that looks like a key or token anywhere in the text (the redaction rules' patterns, in a stream's data lines too) become [REDACTED] — gone, no placeholder kept; the archive is plain JSON, readable in the bucket, not sealed with the passphrase. Uploads go one at a time on their own goroutine from a queue of 16 calls, each body capped at the 256 KB the call already keeps and marked truncated; a full queue drops the call with a log line, a failed upload is logged and shown under the switch until the next one goes, and no request waits on any of it. A recent call the archive kept shows its date and id under its bodies, and "Fetch from archive" (GET /api/archive?date=&id=, only a date and an id of that shape) shows the request's and response's headers and bodies as the bucket has them, or says it isn't there yet. The hook is in record() and the Call literal, so PR #239's request log lines are not touched; the archived call carries its usage, and with #239 its vendor request id. Only calls through serve() are archived, not Codex's backend, image or video calls. TestArchiveOn, TestArchiveOff, TestArchiveName, TestRequestArchiveToS3 (a real gateway request through the signature-checking fake S3), TestScrub and request-archive.test.cjs fail without the change; TestSettingsSaveKeepsWhatItDoesNotSend now sets requestArchive. A sandbox magpie web with a fake vendor and a fake S3 server uploaded nothing while off; on, it uploaded a signed archive with none of eight planted secrets in it, read it back through /api/archive and in the page, and with the S3 server down served the call in 0.1 s and said why the upload failed. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (Claude account tests and TestClaudeRunEndsWhenTheClientLeaves pass alone); request-archive, click-scroll, old-webkit, sse-body, gateway-fold pass.
2026-10-01 17:13:34 +08:00
codex, claude: a model with a window above 272K is compacted at 272K, as OpenAI's and Anthropic's own are (X: Chen — through magpie on DeepSeek flash, Codex and Claude Code both "wait" before every answer, and more the longer the task) — magpie told both agents a 1M model's whole window, so neither compacted until near 1M and every turn sent the whole conversation again: the live ledger had turns of ~550K tokens waiting 70–90s for a first token, while magpie's own time before the vendor stays under 0.2s. OpenAI runs its own 1M-capable models at 272K in Codex (context_window 272000, max_context_window 872000) and Anthropic its at 200K unless a [1m] one is picked; magpie now does the same by default. Codex's catalog gives such a model context_window 272000 and its whole window as max_context_window; Claude Code gets CLAUDE_CODE_AUTO_COMPACT_WINDOW=272000 while it runs on magpie (it compacts at the smaller of that and the model's window, a [1m] model's too), kept apart from a value the user set and taken out with magpie. Settings → Long conversations ("Compact at 272K" / "Full window", en and zh) turns it off (settings.FullContext), and both agents' lists are written again at once. In a sandbox with the owner's real accounts: Codex's /models listed group/test and group/auto-glm-5-3-flash at 272000 with max 1000000 (stepfun's 32K and 16K as they were, gpt-6-* still 272000/872000), Claude Code on deepseek/deepseek-flash[1m] answered and its /context said 16.3k / 272k, and Full window turned both back to 1M (/context 16.3k / 1m). TestClaudeCompactWindow and the extended TestCodexCatalogToolSearch fail on the old code. go vet (darwin, GOOS=windows), builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
2026-10-04 18:27:09 +08:00
portable: a portable magpie on Windows keeps WebView2's profile in data\webview2 and makes no Start-menu shortcut, and an installed one makes the shortcut once and never puts back one the user deleted (#508, suixinex: 便携模式时还是会创建 C:\Users\<username>\AppData\Roaming\magpie.exe\ 文件夹; 开始菜单的快捷方式删除后再打开magpie会重新添加) — the AppData\Roaming\magpie.exe folder is WebView2's user data folder: Wails v3 passes WindowsOptions.WebviewUserDataPath to go-webview2 as its DataPath, and left empty it is %APPDATA%\<exe name>, which portable mode (appdir) never set. The app's Windows options (windowsOptions) now give it appdir.WebView(): data\webview2 when portable, "" (WebView2's default, so the pages' storage and cookies there are kept) when installed. The shortcut came back because shortcut.Ensure ran at every start of the desktop app and wrote Magpie.lnk whenever it was missing or stale, with nothing recording that it had been made. Ensure now does nothing when portable (no shortcut, no App Paths entry); installed, it makes the shortcut on the first start and records that in start-menu-shortcut in magpie's folder (with the exe), keeps one that is still there pointed at the exe (moved exe, update), and makes none once the file is recorded and the shortcut is gone. A user who deleted it under an older magpie isn't given it back on updating either: a missing shortcut with no record but an App Paths\magpie.exe entry already there (every older magpie wrote it at each start) counts as deleted and is recorded as such. The Start menu, the shortcut writer and App Paths are behind function variables, so the decision is tested with fakes on any OS. Checked for other leaks: autostart only refreshes an entry the user turned on (Refresh never enables), the magpie:// link registration is needed for import links and stays, as appdir's doc says, as do temp folders; the update stages beside the exe, caches already follow appdir. Not changed: the Mac's WKWebView and Linux's WebKitGTK keep their own data in the profile (Wails has no option for it), which #508 didn't report. Nothing ran on Windows (the test box isn't reachable): no real WebView2 was started with the data path and no real shortcut was made or deleted. TestEnsureOnce, TestEnsureAfterOlderMagpie, TestEnsurePortable (fake Start menu in a temp folder), TestWebView and TestWindowsOptionsWebViewData fail without the change. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build and vet ./..., GOOS=windows go test -c for shortcut, gui and appdir, go test -tags nogui -count=1 ./... under a temp HOME pass (TestUsageKeepsTimeToFirstToken and TestUserPath failed once on timing, pass rerun alone; untouched).
2026-10-02 14:39:49 +08:00
plugins: a 「国内镜像」 switch on the Plugins page downloads the list, plugins and Bun from mirrors in China first (Discord, akic404: GitHub/npm are slow from the mainland; asked for ghproxy) — Plugins › Mirrors in China, off by default, kept in settings (chinaMirror). On, source asks a copy of the very file before its official address: npm's registry (packages, versions, search) at registry.npmmirror.com, Bun's release zips at registry.npmmirror.com/-/binary/bun, and the community registry.json at cdn.jsdelivr.net/gh/magpie-community/plugins@<branch>; bun add gets BUN_CONFIG_REGISTRY=npmmirror unless the environment names a registry. No proxy: gh-proxy (MAGPIE_MIRRORS' GitHub fallback) handles nothing the switch sends, since a proxy could hand back other code. A mirror asked first that can't answer (404/403/5xx, e.g. a version it hasn't synced) falls through to the official address; npm's weekly downloads stay api.npmjs.org's (npmmirror counts its own). Checked by hand: npmmirror's bun-v1.4.2 darwin-aarch64 zip has the SHA-256 pinned in bun.go, jsDelivr's registry.json is byte-identical to raw.githubusercontent's, and bun 1.4.2 add through npmmirror locks the same sha512 integrity npmjs gives. TestChinaFirst, TestChinaComesFirst, TestChinaMirrorAnswers, TestChinaMirrorsMissIsAskedOfficially, TestDoFaithful, TestBunInstallsFromChinaMirror and TestPluginsMirrorSwitch cover it. A live run with the switch on fetched the list from jsDelivr, npm info from npmmirror and Bun from npmmirror (checksum passed); a sandbox magpie web showed the switch in the Plugins head, saved it, kept it after a reload, and a click didn't move the page. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried from inside mainland China.
2026-10-03 15:14:45 +08:00
accounts: on Linux an account put first is read first at once, and on Windows a file being replaced isn't read as none (goal: 插件订阅 provider 跟内置的所有逻辑都要一模一样, run on Windows and Linux) — the provider tests run on the Windows test box and in WSL found two bugs of magpie's own files. Linux stamps a file's time from a clock a tick (a few milliseconds) coarse, and filememo took same time and size for the same file: two accounts swapped (Devin, Grok, WorkBuddy, ZCode, Claude…) read back in the old order, so magpie kept using the account the user had just moved down, and a moved-back plugin's accounts read as before. A file written within the last two seconds is now also compared by its bytes. On Windows a file being renamed over refuses to open for a moment ("Access is denied"), and one held open refuses to be renamed over: plugin-auth.json, plugins.json and the plugin providers read as nothing (a usage fetch right after a move showed no cards), logins.json failed to save, and host.js, reading plugin-auth.json as {}, would have written the other accounts away on the next sign-in. New internal/steady waits such a read or rename out (half a second at most, Windows only); filememo, edit.WriteAtomic, writePrivate, Kiro's writes and the plugin store go through it, host.js retries its read and rename, and the plugin store writes its files by a rename. Tests: USERPROFILE beside HOME in the test homes, the fake devin (a shell script) skipped on Windows, plugin-auth.json's mode bits unchecked there. TestReadSameSizeSameTick fails on Linux and TestRenameOverAFileHeldOpen/TestReadAFileHeldOpen on Windows without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; on Linux (WSL) the provider and plugin packages pass whole (13 failed before), on Windows the plugin package and the move/plugin/Grok/Devin/usage tests pass 10 times over (TestMoveToPlugin failed one run in a few before); 15 built-in tests that assume Unix (mode bits, Keychain, shell-script CLIs) still fail there, as before.
2026-10-01 13:27:12 +08:00
group: a routing group's members can be patterns, so a group follows a provider's catalog (#766: magpie group add my-free-pool models='openrouter/*:free,opencode-zen/*-free') — a group names its models one by one, so a pool of a vendor's free models went stale as the vendor added and dropped them. A group now keeps patterns apart from the models it names (Group.Match): a glob, * any run of characters (/ too) over the provider/model id in any case, or re:<regexp> over the whole id. groupsIn, which every reader goes through (Groups, FindGroup, /v1/models, MemberGroups, GroupsWith, RenameGroup, rules, Off, Pick, the GUI, TUI and CLI), puts after the named models every enabled provider's model a pattern matches, in catalog order, deduplicated, the named one keeping its place; a pattern never matches a group. What was matched is derived (Group.Matched) and never stored: store() and SaveGroup keep only the named members, so a save never freezes matches. Off and rules may name a matched model. A group with only patterns is allowed; one matching nothing routes nowhere and says so: magpie group <id> lists each pattern with how many models it matches now ("matches nothing now" in amber), the group list and the TUI too, and the GUI card shows a line per pattern with its count. CLI: models= takes patterns among the ids, models+= adds them (naming a matched model moves it to where it is named), models-=<pattern> drops one, models-= of a matched model is refused naming its pattern; the help documents patterns. GUI: the editor has a Patterns box (Add pattern / Remove, validated as the server does), matched models follow those named marked "by pattern" with their off switch and no Remove, and saves send members without the matched ones plus match; zh strings added. Tests: TestGroupPatterns, TestGroupPatternMembersOffAndRules, TestGroupPatternMatchingNothing, TestPatternSyntax (provider), TestPatternGroupRoutes (gateway: a request reaches a matched model, a model a provider adds joins, Off on a matched one, /v1/models lists the group), TestGroupPatternsCLI, and group-patterns.test.cjs (WebKit, en+zh); they fail on the old code (the Go ones don't build, the GUI one finds no pattern lines). GUI: group-patterns, group-found-off, group-member-fast, group-member-off, settings-groups, agent-model-groups, remote-group-served, routing-manual pass on WebKit; group-from-model-add has the same 2 failures (600x420) as origin/main. A request to a group whose patterns match nothing now is a 404 naming the group and its patterns (emptyGroupError), not 'magpie knows no model' and every model; TestEmptyPatternGroupSaysSo. With the owner's real config under a sandbox HOME: group/ds-pool of models='deepseek/*' listed deepseek-flash and deepseek-v4-pro by pattern, stored only the pattern, was in /v1/models and answered from deepseek-flash; none-pool of 'nobody/*' said its pattern matches nothing. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
2026-10-04 16:52:15 +08:00
update: install.sh and magpie update take --proxy and --mirror, and magpie update mirror keeps one for the app's updates too (akic404 on Discord: 安装和update如果没有梯子也是麻烦事,能否在install.sh和magpie update后加子选项?--proxy等可自定义或默认ghproxy) — install.sh had no options and fetched with curl alone, and magpie update took none; without a way out to GitHub both failed. Now both take --proxy <url> (http, https, socks5, socks5h; install.sh also MAGPIE_PROXY=) for the feed and the download, and --mirror <prefix> (or MAGPIE_MIRROR=), a GitHub download mirror the release file is fetched from as <prefix>https://github.com/…; only github.com / githubusercontent.com URLs are put through it. No mirror is used unless one is given: magpie names none and has none by default. The feed with each file's SHA-256 is always fetched from usemagpie.ai, never through the mirror, so a file the mirror changed is refused and deleted ("… from the mirror X does not match its checksum from https://usemagpie.ai; not installed"); macOS still checks the app's signing team. magpie update mirror <prefix>|off saves Settings' updateMirror (http(s) only), which the app's own background update follows too; magpie update --mirror off skips it once; the Settings page's save keeps it. The app's update already followed Settings → Proxy (#294); magpie update --proxy overrides it for one run. The environment's ALL_PROXY / all_proxy is now honoured when HTTPS_PROXY / HTTP_PROXY are unset (Go's ProxyFromEnvironment ignores it), with NO_PROXY as curl reads it. magpie update takes --flag value or --flag=value, and an unknown word is an error instead of an update. The help, README and the docs' FAQ (en/zh/ja) say how. install.test.mjs's proxy, mirror and tampered-mirror tests, TestAllProxyFromTheEnvironment, TestUpdateThroughAMirror, TestUpdateThroughAProxyGiven and TestUpdateFlags fail on the old code and pass; TestSettingsSaveKeepsWhatItDoesNotSend covers updateMirror. A dry run of install.sh as Linux amd64 into a temp HOME installed the real 0.1.773 with its SHA-256 checked, refused a local mirror serving a wrong file, and with --proxy to a closed port failed reaching the site through it. go vet, builds (darwin, GOOS=linux, GOOS=windows), sh -n, node --test site/install.test.mjs and go test -tags nogui -count=1 ./... under a temp HOME pass but TestClaudeIdentityServedWhileAsked, a timing test that passes alone. Not tried through a real proxy server or a real public mirror; no install.ps1 exists for Windows, and the app has no field for the mirror.
2026-10-04 00:19:47 +08:00
usage: a reply that came in one burst at its end tells no tok/s, and is left out of the summed speed (#731, Arcadia822: a Gemini turn through Antigravity that was one write tool call, out 8264, ms 24360, ttft_ms 24359, read 8,264,000 tok/s and lifted the Usage page's average) — a reply's speed is out ÷ (ms − ttft_ms) since #196, and the sums are Σout ÷ Σ(ms − ttft_ms) (Totals.DecodeOut/DecodeMs). codeassist hands a native functionCall over whole, so a turn whose only content is one big tool call has its first content event a millisecond or two before the end: the window covers delivery, not writing, and its tokens over next to no time read millions a second and pull the summed speed up (the test's three calls: 100 tok/s each in truth, 616 with the burst counted). A decode window now counts only when it timed the writing: usage.DecodeWindow gives 0 when it is under MinDecodeMs (100 ms) or the tokens over it would be past MaxDecodeSpeed (10,000 tok/s), both far from any real stream (the fastest vendors write a few thousand a second); Totals.add (records) and Totals.addRow (ledger rows, which the Usage page sums) both go through it. Such a reply still counts as timed and its TTFT in the mean; only its speed is left out, so a model with only bursts shows no tok/s (the Usage page and the CLI already skip a speed with no decode window). The Routing page's story, which works out a try's speed itself (routing.js speedOf), applies the same bounds, so the burst says "first token in 24 s" and no tok/s. app.js's figure is decode_out ÷ decode_ms from the Go sums and needed no change. Tests: TestBurstTellsNoSpeed (the report's record with two 100 tok/s calls: 100 tok/s summed through summarize and addRow, TTFT mean keeps the burst; DecodeWindow's edges: 1 ms, 0 ms, 3 tokens in 50 ms, 8264 in 500 ms, exactly 10,000 tok/s over 100 ms, nothing written, not streamed) fails on the old rule with 616 tok/s; TestSummarizeTimesFirstTokens unchanged. routing-burst-speed.test.cjs (webkit, en and zh: the report's burst and a 500 ms one tell no speed, 100 and 50 tok/s replies keep theirs, picking a request doesn't move the page) fails on the old routing.js with "8264000 tok/s". routing-sessions, routing-served, routing-effort-change, usage-accounts, panel-usage and click-scroll GUI tests pass in webkit (routing-served failed once in a parallel run and passes on its own twice; its fixture has no TTFT). go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried with the reporter's ledger or a live Gemini stream; a short reply under 100 ms (a title, a few tokens) no longer counts toward the speed either, which moves a summed figure by next to nothing.
2026-10-04 11:28:50 +08:00
claude: on Windows with no Claude Code of its own, the gateway runs the one in WSL (X: kyotiger, Claude Code installed in WSL only, magpie on Windows found no claude) — claudeBinary and the sign-in's findClaudeCLI now fall back, when no claude is on Windows, to one in a running WSL distro (internal/wslrun: the default distro first, Docker Desktop's left out, a stopped one never started; found by a login shell, then an interactive one for nvm's node, then ~/.local/bin and /usr/local/bin, never Windows' own under /mnt). A run is wsl.exe -d <distro> --exec /usr/bin/env PATH=<its PATH> <claude> …, so no shell rc runs and Claude Code there uses its own sign-in and quota; what the bridge sets goes in through WSLENV, CLAUDE_CONFIG_DIR (a saved account's folder) and the sign-in's BROWSER as paths (/p), the proxy variables with a loopback proxy pointed at the Windows host under NAT networking. The MCP tools server stays magpie.exe on Windows, started from WSL by its /mnt path. Killing wsl.exe leaves the Linux process running, so each run is marked (MAGPIE_WSL_RUN) and a canceled one is ended inside the distro by that mark. The Claude provider's editor says "Runs through WSL · <distro>" under Accounts (en, zh); the page never waits on WSL for it. A claude on Windows itself is still used first. With the owner's real Claude account on the Windows test box (Claude Code 2.1.289 in WSL only, magpie in a sandbox): /v1/models listed the Claude models, a haiku request answered from WSL with the MCP server at /mnt/c/…/magpie.exe, a tool_use round trip worked, the quotas came from its headers (5h 13%, 7d 37%), "Add another Claude account" opened claude.com's OAuth page through the BROWSER hand-off with its localhost callback reachable from Windows, canceling it ended claude auth login in WSL, and the editor showed the line in en and zh. Not tried: finishing a browser sign-in (needs a person). Tests: internal/wslrun (the probe's parsing, path and WSLENV translation, the proxy rewrite, Find's distro order and cache, a run's argv). go vet (darwin, GOOS=windows), builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass (the gateway package flaked once under load and passes alone).
2026-10-04 17:49:53 +08:00
docs: every built-in subscription a plugin serves once moved says so in its code, and AGENTS.md says where a fix for it goes (user: 对内置的订阅 provider 在代码层面和文档层面加上注释,防止又有别的 agent 在改内置订阅 provider 的代码而不知道生效的其实是对应的插件代码) — a moved Cursor, Devin, Grok, Command Code plan, Kiro, Zed, Qoder, Factory, Xiaomi MiMo, ZCode or WorkBuddy (provider.Moved; since v0.1.642 also the default for a new sign-in, provider.Adopt) is signed in, listed, requested and metered by its @magpie-community plugin, and none of the built-in's code in internal/provider/<name>*.go, internal/gateway/<name>.go, internal/zed or internal/qoder serves it; a fix made there alone never reaches those users. Each of those files now opens with a PLUGIN-SERVED notice naming the subscription's id, its plugin package, where the plugin lives (magpie-community/plugins, packages/<name>) and the mover whose min to raise so moved users get the fixed plugin (keepMovedCurrent); Qoder's adds that Qoder CN isn't moved and still runs there. AGENTS.md (CLAUDE.md imports it) lists the moved built-ins with their packages and movers, how to tell whether a user's is moved (move.state "plugin", onPlugins), the order a fix goes in (the plugin, publish, raise min, the built-in only for those not moved), the magpie code that does run for a moved one (internal/plugin, plugins.go, plugin_usage.go, pluginsignin.go, migrate*.go, app.js's subOf/pluginSubs/startPluginSignIn), and the parity tests. movers in migrate.go and SUBS in app.js point there too. TestMovedBuiltinsSayTheirPlugin fails for a mover whose code carries no PLUGIN-SERVED notice naming its id and package (it failed while a wrapped package name was split). Comments and docs only, no behaviour changed. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui -count=1 ./... under a temp HOME pass (TestProvidersAnswerWhileListsComeIn and TestClaudeSignedOut failed once on their timing under load, pass alone; untouched).
2026-10-02 19:37:02 +08:00