Commit Graph
1165 Commits
Author SHA1 Message Date
yetone 88cccc184e update: on Windows, an exe something holds open for a moment still goes aside for the new version, and one held for good says why in full, with the release page a click away (freecss on Discord: "0.1.628 已下载 · couldn't move magpie-windows-amd64.exe aside to put the new version in: rename C:\Us…") — InstallBinary renamed the running exe to .old once and gave up on the first error, which Go prints as "rename <exe> <exe>.old: <reason>", two full paths ahead of the reason, and the version row cut it off on one line, so what failed couldn't be read. A leftover .old was already handled (RemoveOld, then .old-2…, TestInstallBinaryBesideARunningOld), and Windows renames a running image (it is mapped with FILE_SHARE_DELETE), so the likely cause is another program holding the exe open without delete sharing — an antivirus scan, OneDrive or a similar sync client — or a folder that refuses the change (access denied); which one isn't known until the full message is read. moveAside now tries again for about 3 s (0.1, 0.2, 0.4, 0.8, 1.5 s apart), clearing a read-only attribute after the first failure, with a fresh .old name each try; a missing exe isn't retried. When it still can't, the message is the reason without the paths and what to do: ERROR_SHARING_VIOLATION/ERROR_LOCK_VIOLATION says another program has it open (often an antivirus or OneDrive), access denied names the folder Windows won't let magpie change, anything else says to download the new version; the download is kept for another try, as before. The version row wraps a failed install's or download's reason (.sub.wraps) with the whole text as its tooltip, and a ready-but-failed install offers Download, which opens the release page. The Windows test box wasn't reachable from this session (ssh key refused), so nothing ran on Windows; TestInstallBinaryHeldOpen (windows-only: an exe held open without FILE_SHARE_DELETE for 0.5 s still installs; held for good, the error says another program has it open and the download is kept) was cross-compiled with GOOS=windows go test -c but not run. TestMoveAsideTriesAgain and TestMoveAsideSaysWhy (portable, with a fake rename) and the new update-check.test.cjs case (webkit, en and zh: the reason is not cut off, its tooltip is the full text, Download opens the release page, a later state is one line again) fail without the change; Chromium couldn't launch here (its Playwright cache has no ICU data). go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build and vet, go test -tags nogui ./... pass (TestFXInState failed once on a TempDir cleanup race, passes on the rerun; untouched). 2026-10-02 13:26:54 +08:00
yetone c2947dd6e7 zed: an OpenAI model's request reaches Zed's cloud in the Responses types it reads, so Codex on a Zed GPT model is answered (nico on Discord: "failed to parse OpenAI Responses API request: unknown variant developer, expected one of user, assistant, system, tool", and with it no model switch mid-thread and no auto-review) — Zed's cloud parses the request into its own types (zed-industries/zed crates/open_ai/src/responses.rs: Role user/assistant/system/tool, items message/function_call/function_call_output/custom_tool_call(_output)/reasoning/compaction/compaction_trigger, tools function/custom, include reasoning.encrypted_content only) and refuses the whole request over anything else. The Zed plugin (@magpie-community/opencode-zed-auth) gets Codex's Responses request relayed as Codex sent it, developer messages and all — its permissions message on every request, the one a model switch adds, the auto-review's — so every Codex turn on a Zed OpenAI model failed. The plugin's account now fits the body as grokBody does Grok's (ZedBody): a developer message goes as a system one where it stands (Zed's own client sends system messages too), the shorthand {role, content} gets its type and a string content becomes a list, reasoning's null content goes, an agent_message is the user's turn, an item Zed has no type for (a web_search_call an OpenAI model ran) is left out, namespaced tools go flat and their calls come back under the namespace (as Grok's), other tool types go, include keeps reasoning.encrypted_content alone, a function tool_choice is required, and a reasoning summary Zed doesn't know goes. The built-in Zed account already folded developer messages in; its OpenAI request goes through the same fitting, so a web search offered with no search API no longer leaves a web_search tool and web_search_call.action.sources in it. Codex's prompts and instructions are unchanged. TestZedPluginGetsNoDeveloperRole (a Codex-shaped request through the fake plugin to a fake upstream that parses as Zed's cloud: fails with the exact 400 without the change) and TestZedBuiltInResponsesParse fail without it. No request was made to Zed. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-02 13:23:41 +08:00
yetone 20b172e845 gui: the request archive's switch sits over the Usage page's requests, and the Gateway page's recent calls no longer fetch from the archive (Jorben on Discord: 网关页的最近请求下遗留了取回归档和预览的逻辑,和最近请求的预览能力重叠了,建议移除;以及把是否启用请求归档开关也挪到用量页请求列表的右上方) — since #447 a request's archive is read back and previewed from its row on the Usage page, so the Gateway page's recent calls showed two previews of one call: their own bodies, and under them the archive's copy with "Fetch from archive" and "Download". A recent call now shows only its own request and response bodies; archivePanel and what it uses stay for the Usage page. The archive's Off/On row over the recent calls (#archiveList) is gone, and its switch is now a compact one — "Request archive" and a switch, role=switch — at the right of the line over the Usage page's request list, beside the requests' sum. It posts settings/archive as before; what it keeps and where, or that Sync needs an s3:// address first, is its tooltip; a refusal still goes to the footer and leaves it off; a failed last upload says "Upload failed" beside it (上传失败, the error in its tooltip). It waits for the gateway's state rather than say Off before it is in, and is changed in place on a redraw, so a switch being pressed is never swapped out (the ledger and providers both redraw it). request-archive.test.cjs now checks the Gateway page has no switch and a kept call no archive panel and no Fetch from archive, and the switch at the list's top right on the sum's line: refused with no bucket, on with one (bucket in its tooltip), off, and a failed upload said; web-page.test.cjs finds the switch on the Usage page. Playwright in Chromium and WebKit: request-archive, request-archive-usage, web-page, click-scroll, usage-ledger, usage-ledger-detail, gateway-fold, sse-body pass (72/72). go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./internal/gui/ pass. 2026-10-02 13:23:41 +08:00
yetone 11be3f31b0 zcode: a Start Plan request goes as the ZCode CLI sends it, not the desktop app (miaopasi and ARNO on Discord: Start plan还是405 感觉不是IP的问题; an OpenCode plugin sending the CLI's request works on the same plan) — dressed as the desktop app, some Start Plan requests were still turned away with 405 "request has been blocked due to unusual activity", code 3012. Following ZCode 3.14.3's own source (github.com/zai-org/ZCode, apps/zcode-cli/packages/core/src/context/builder.ts and sections/, bootstrap/src/model-config.ts and runtime-platform-headers.ts, adapters/src/model/runner-attribution.ts), the request is now the CLI's: X-Title "Z Code@cli" (was "Z Code@electron", which only the desktop app's agent server sends); the stable system block is the identity section alone, the "ZCode Desktop Context" section being added only for presentationSurface zcode_desktop; X-Platform is <platform>-<arch> and the new X-Os-Version is os.release(), as runtime-platform-headers sets them (Darwin's uname release, Windows' Major.Minor.Build from RtlGetVersion); the environment block's OS Version is "<platform> <release> <arch>" as env-info.ts writes it (was "unknown") and its powered-by line is the provider id and model as ZCode names them (account:zai-start-plan/GLM-5.3-Flash, account:bigmodel-start-plan on BigModel; was lowercased without the provider); the currentDate reminder is wrapped as system-reminder/source.ts wraps a context prefix (a newline after the opening tag, one after the closing). Model requests no longer carry X-Device-Mid (ZCode sends it only to its own API, where the balance still needs it, #282) nor x-api-key: the JWT goes as a Bearer only, as the plugin that works sends it (the official client passes it as the SDK's apiKey too, so this follows the working plugin over the source). The prompt text itself was checked byte for byte against the 3.14.3 sections (rendered with node). The plugin's own source wasn't found (pi-freeflow has no zcode code), so where it and the source might differ (X-Os-Version as release alone, not "<platform> <release> <arch>") the source wins. No request was made to zcode.z.ai. TestZCodeStartSentAsTheApp, TestZCodeStartPlanOwnAccount and TestZCodeSignInStartPlanDeviceMid are updated; with the old prompt, X-Title and x-api-key they fail (X-Title "Z Code@electron", X-Api-Key sent, the desktop section in the system prompt). go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./internal/provider/ ./internal/gateway/ pass. 2026-10-02 13:17:26 +08:00
yetone fbddf45a86 providers: ClinePass lists Cline's real free models and asks for them as Cline's desktop app (lml and ARNO on Discord: the free models didn't match Cline's) — a3962602 listed the Cline API's OpenRouter-style ":free" ids (qwen/qwen3.8-27b:free…), which are not what Cline offers free: Cline's clients take their picker from GET /api/v1/ai/cline/recommended-models (cline/cline sdk/packages/llms/src/catalog/catalog-cline-recommended.ts), whose free bucket is the desktop's FREE section ("Try with limited usage, separate from ClinePass quota": cline-free/deepseek-v4.1-flash, stealth/space-bunny-alpha, cline-free/mimo-v2.6-flash, cline-free/muse-spark-1.3-contributor) and whose clinePass bucket is the plan's. ClinePass now fetches that feed, asked as Cline's desktop app and with no key, as Cline asks it (pi-freeflow measured that X-CLIENT-TYPE cline-desktop is what the feed keys its free list on); the plan's models are its clinePass ones, the free ones are marked Free (the FREE badge) and priced at nothing, and its recommended paid models are left out, as before. When the feed can't be had the API's list is asked as before, and the free models are the desktop's list of today (from Cline's generated fallback, cline-recommended.generated.ts); the ":free" ids are dropped with FreeTag, as Cline's own clients never offer them as free and their cost on a Cline key was never shown. Requests to the Cline API (the gateway's and the Test button's) carry the desktop's headers — HTTP-Referer https://cline.bot, X-Title Cline, X-IS-MULTIROOT false, X-CLIENT-TYPE cline-desktop, X-CLIENT-VERSION 3.5.54, User-Agent Cline/3.5.54 — since the cline-free/ models answer 403 "only available via Cline product surfaces" without them (pi-freeflow, measured live); the key goes as before. ListPrice prices a model free by its cline-free/ id, by the fetched list or by the fallback at zero. No request was made to Cline and nothing was tried with a real Cline key. TestClineFeed (the feed asked as the desktop with no key; plan, free and no paid models listed; a free model the feed alone names priced at zero), TestClineClientHeaders (the headers on a cline-free request to api.cline.bot and on no other provider's) and the updated TestPlanModels fail without the change. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./internal/provider/ ./internal/gateway/ pass (TestClaudeSignInOverStaleCredentials failed once under the full run, passes alone and on the rerun; Cline untouched by it). 2026-10-02 13:07:03 +08:00
yetone e413dd8445 factory: another agent's request to Factory's GPT, Grok and open models opens as Droid's does (#506 aohun: Codex on glm-5.3-flash through a Factory account, 403 Forbidden; the user: 只要不是 claude code,都可以尝试伪装一下) — in #242 the same account's GPT, Grok and GLM answered Droid through magpie every time and refused Grok Build and Claude Code every time, on the same models, efforts, endpoint and headers, the body the only difference, and glm-5.3-flash is a model Factory serves (droid 0.231.0's registry: provider factory, generic chat completions, fireworks/baseten/databricks), sent where and as droid sends it. droid 0.231.0 (its binary, read, not run) opens every system prompt with YOU_ARE_DROID_SYSTEM_PROMPT, "You are Droid, an AI software engineering agent built by Factory.": its system blocks [that line, the agent prompt, …] go as Responses' instructions joined with "\n", and on chat completions as one system message put first, joined the same way; its small calls (the latency ping) send the line alone. A request on /api/llm/o now does too: Responses' instructions, or the first system message of chat completions (text parts joined as droid joins them), open with the line and "\n" before the agent's own; with none, the line alone goes first. A request that already opens with it (droid's own) goes on byte for byte, as does everything on /api/llm/a (Claude, MiniMax M2.7), which is left as the agent sent it. The 403 text now says magpie opens other agents' GPT, Grok and open-model requests as Droid's but Factory may still tell them apart, that Claude models from other agents are refused, to use the model from Droid, and that the org's policy or plan is left when Droid is refused too. GLM-5.3-Flash takes images (droid 0.231.0 says images: true; the rest of the table matches), and the requests say droid 0.231.0. Not tried with a real Factory account: whether the line is all Factory checks is not known. TestFactoryOpensAsDroid (Codex-shaped chat and Responses, Claude Code's system parts, no system, droid's own chat and Responses unchanged byte for byte, Anthropic's Messages unchanged) and TestFactoryGLMFlashImages fail without the change. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./internal/gateway/ ./internal/provider/ pass. 2026-10-02 13:03:41 +08:00
yetone 0cb6e13d91 gateway: a developer message Codex adds mid-thread stays where it is, so the thread's cached prefix does too (#502, Koohoko: after a memory request the next turn of the same session wrote the whole conversation to the cache again) — Codex adds a developer message to a thread where its context changed (a world-state or skills diff, a settings or model change, and so after its memory pipeline has run) and keeps it there in every later request, which leaves OpenAI's prefix as it was. parseResponses lifted every developer and system message, wherever it stood, into the system prompt, so on a translated route that turn's system changed: Anthropic's system comes before the messages, so the whole conversation was written to the cache again, and a Claude subscription's waiting Claude Code is found by its system (turnKey), so a new one was started and told the whole conversation in one message. Developer messages before the thread's first reply (Codex's initial context) are lifted as before; one after it now stays in place as a user message inside <system-reminder>, which the router's classifier already leaves out of the user's words. The memory request itself (another model, its own prompt, the same session) moves nothing in magpie: affinity is scoped by model or by a group's first words, the Claude bridge's runs by the conversation. TestCodexLaterDeveloperMessageKeepsTheSystem and TestCodexThreadKeepsItsClaudeRunAcrossAMemoryCall (a Codex thread on a Claude subscription, a memory call in the same session between, then a developer message: the same Claude Code goes on) fail without the change. No real vendor request was made. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./internal/gateway/ ./internal/provider/ pass. 2026-10-02 13:00:22 +08:00
yetone 65f3a6417a settings: portable mode — a data folder beside magpie keeps everything magpie keeps (#508, suixinex) — like VS Code's portable mode, a folder named "data" (or a file named ".portable", the data folder then made beside it) next to magpie makes it keep all its own files there instead of the user's profile. Where magpie keeps its files is now decided in one place, internal/appdir: Config() (~/.config/magpie, $XDG_CONFIG_HOME/magpie; portable: data/), Cache() (~/.cache/magpie, $XDG_CACHE_HOME/magpie; portable: data/cache) and SystemCache() (the OS's cache folder, which the library's market lists and an update staged where magpie may not write used; portable: data/cache). settings.Dir()/Path(), provider.Path(), profile.Path(), the models.dev catalog, the exchange rate and the plugin market cache go through it, and everything built on them follows: providers, logins and saved accounts' folders, usage ledger, routing history and request archive, plugins, library, backups, sync state and lock, caller keys, redact key, stats. "Beside magpie" is the folder holding the executable with links followed; for a Mac app the folder holding Magpie.app (inside the bundle would break its signature), and for an AppImage the folder holding the .AppImage, not where it is mounted. The decision is made once, at start: an update moves the running copy aside (into .magpie-update on a Mac, to magpie.exe.old on Windows) and its files must not move with it; the updated copy replaces the executable or bundle only, so the data folder beside it is left alone. A portable magpie doesn't copy an old dial install's folders in. Not moved, as they aren't magpie's own: the agents' configs (~/.claude, ~/.codex…), the sign-ins agents keep in the system keychain (a saved Claude account's Claude Code keychain item is named after its folder's path on a Mac, so a portable folder moved elsewhere or to another Mac signs that account in again), autostart and link-handler registrations, and temporary folders. The single-instance id already covers the config folder, so a portable and an installed magpie run side by side, but the gateway port is one. Settings shows the config folder as portable. TestResolve (data folder, .portable, a file named data, a Mac bundle), TestResolveSymlinkedExe, TestResolveAppImage (Linux), TestFolders and TestPortableSettings (Path() lands in the data folder, Save writes there, ~/.config/magpie untouched); TestPortableSettings fails without the change (Path() is ~/.config/magpie/settings.json), TestResolveSymlinkedExe without following links. A nogui build run from a folder with data/ under a sandbox HOME wrote profiles.json, cli-identity.json and cache/models.json into data/ and nothing of magpie's into HOME. go vet ./internal/..., go build -tags nogui ./... (darwin, GOOS=linux), GOOS=windows go build ./..., go test -tags nogui ./... pass. 2026-10-02 13:00:22 +08:00
yetone 739b3bebe3 gateway: a rate limit on the last provider left is waited out even when it doesn't say for how long (#503, ynky007) — a 429 was tried again only when it came with a Retry-After (or a ratelimit-reset header) of 8 s at most, so a relay's 429 that says nothing of when ("Rate limit exceeded, please try again later", "当前分组上游负载已饱和,请稍后再试") went straight to the agent. Where another member, key or account is left a 429 already moves on to it, as before; on the last one left (or the last that may answer, the others out of their allowance) a 429 failure() reads as a rate limit is now asked again up to 3 times (rateRetries; busy 5xx/529 keep lastRetries = 2), after 1, 2 and 4 s or what Retry-After says (each 8 s at most, under half a minute in all), while nothing has been sent to the agent and only until it goes away. A 429 that says the plan is used up (usage limit, resets in hours: failQuota), that the money is gone (creditWords), or a Retry-After past 8 s (ChatGPT's resets_at goes on as one) is passed on at once, as before. passing() now takes the error body and the try count, and the last one's reply is held for as many tries as a rate limit gets. TestRateLimitIsWaitedOut (no Retry-After, a relay's own words, three in a row, then 200; four in a row → 429 after 4 tries; usage limit, 余额不足, Retry-After: 3600 → 429 after one try, at once) fails without the change (429 after 1 try in the first three, 3 tries in the fourth); TestLastOneLeftIsTriedAgain still passes. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./internal/gateway/ ./internal/provider/ pass. 2026-10-02 13:00:22 +08:00
yetone 22358d5d6e library: a project gets its own MCP servers, agent by agent (#500) — the library's servers could only be given to agents user-wide, while its skills could already go into a project. The MCP tab now lists the same projects as Skills under "In projects" (add one by its folder, or the folder picker in the app), and a project's card opens to each server with chips for the agents that read a project's own servers: Claude Code .mcp.json, Codex .codex/config.toml, Gemini CLI .gemini/settings.json, Cursor .cursor/mcp.json, OpenCode opencode.json (opencode.jsonc when that is the project's file), each as its docs give it. Other agents have no chip and are named under the card; the project files of Copilot, Crush, Droid, Kimi and the rest weren't confirmed, so none is written. The server is written with the same encoders and edit helpers as the user-wide files, so the rest of a file (its comments, other keys, the team's own servers) is kept; magpie records what it wrote in the project (Project.Servers, Wrote, MadeFiles) and only ever takes that away again, a same-named server that isn't magpie's is left as it is and named as a problem, as is an SSE server for Codex. A file magpie made is listed in the project's .gitignore between magpie's lines (a server's env and headers can hold keys) and removed with its folders once nothing of magpie's is left in it, unless the user wrote something else into it; a file that was there already is never ignored or removed. Renaming a library server renames it in its projects, removing it takes it out of them, and removing a project takes out its servers as well as its skills. A project whose MCP file is an agent's user-wide file (the home folder) is refused, as for skills. The chips of a project row (servers and skills) now light at once and send one write at a time, the last click winning: two quick clicks posted the first's stale list before. Claude Code still asks before it starts a project's servers and Codex reads .codex/config.toml only in a trusted project; the card says so. library-all.test.cjs gets a 800px-high window: the MCP tab's new "In projects" made it overflow 700 by a pixel, and Chromium's scroll anchoring then moved it when the problems card came in above. TestProjectServers (merge into a user's .mcp.json keeping its comment, key and server; Codex TOML; .gitignore; rename; removal down to the made folders; RemoveProject keeping the user's own), TestProjectServerNotOurs, TestProjectServerFileKept, TestProjectServerChecks and TestProjectServerOpenCodeJSONC run in a temp HOME; project-servers.test.cjs (en, zh; chromium and webkit) checks the chips, the greyed Codex chip for SSE, two clicks before magpie answers, and that nothing scrolls. No real agent was started on a written project. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; the library's Playwright tests pass in webkit, and in chromium except list-sort, shared-skills and import-all-skills, which fail the same way at HEAD. 2026-10-02 12:37:07 +08:00
yetone 70a6c72a2c gui: built-in subscriptions that have a community plugin are marked Deprecated, with the reason — some subscriptions are reached in ways their vendors' terms may not allow, so to keep magpie itself from being banned they are being decoupled from magpie and run by community plugins instead. The provider list's quiet "can run on community plugins" line is now a deprecation notice (an amber card with the plugin mark: the deprecated signed-in subscriptions, the reason, that moving keeps accounts, models and agents, and Review in Plugins — or Review the move for one — beside Not now, which hides it until another deprecated subscription signs in). Their rows, their Add tiles (signed in or not; /api/providers' new movable lists every built-in with a plugin, from provider.MovableIDs) and the editor's Runs on field carry an outlined Deprecated badge whose tooltip gives the reason; the Add sheet's Subscriptions end with a line saying why, with Browse plugins. A subscription already on its plugin, or one with no plugin, carries none. deprecated-subscriptions.test.cjs (Chromium and WebKit, en and zh: the badges, their reason, none on a moved or keyed provider, the notice, Not now staying hidden), plugin-move and plugin-movable-review updated to the notice. add-button, add-sheet, mobile-web, old-webkit, plugin-move-overflow, plugin-moved-signin, plugin-market, plugin-signin, plugin-plan, provider-search, provider-picks-stay, providers-off-fold pass (click-scroll's 5 failures fail on main as well). go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./internal/provider/ ./internal/gui/ pass. 2026-10-02 11:39:05 +08:00
yetone aa42142ea5 update: What's new and the waiting update's notes follow the app's language (freecss on Discord: 更新后的公告的语言是不是应该设置里面的走) — the release notes were English alone. A release's notes are to carry a Simplified Chinese translation below a <!-- lang:zh --> marker (the magpie-releases workflow, changed separately); usemagpie.ai's /api/latest and /api/notes take ?lang=zh for the Chinese where a release has it, and give anything else, an app from before included, the English alone, everything above the marker. The page asks /api/whatsnew, /api/update and /api/update/check in its own language (?lang=); magpie asks the site in it, keeps What's new's notes per language, and asks again for the waiting update's notes when a page's language isn't the one they are held in (at most once a minute); a check by itself asks in the pages' last language, else the app's, as the tray menu has it. Notes that come back whole (a site not yet deployed) are cut in the app too; notes without the marker (every release so far) are English, in either language. The worker must be deployed (npx wrangler deploy in site/) before the workflow change ships, or apps would get both languages. TestInLang, TestFeedsAskInLang, TestAskedLang, TestWhatsNewInPageLang, TestUpdateNotesInPageLang, site/worker.test.mjs (node --test site/worker.test.mjs), whats-new.test.cjs's language check (Chromium and WebKit, en and zh); update-check, update-hide, update-auto, sync-update pass in WebKit. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestClaudeRunEndsWhenTheClientLeaves flaked once under the full run, passes alone; gateway untouched). 2026-10-02 11:31:11 +08:00
yetone 7e3b53b43c providers: a provider can be moved up or down the Providers tab (#499: 供应商 tab 希望添加调整顺序的操作,比如我后面添加的想要挪到前面使用) — they were listed only in the order they were added. A provider that is on now has its logo for a handle, as an agent row has: the grip in the row's margin is drawn only with the pointer on the row, dragging it moves the row, Alt+↑/↓ moves it from the keyboard and keeps the keyboard on it, and a click opens the row as before, without moving the page; one switched off keeps its place. The order is saved in providers.json ("order", POST /api/providers/arrange), follows a rename, and puts a provider added later after the rest. It is the order they are tried in too: the agents' pickers list them in it, a bare model id goes to the first that serves it, and a group magpie finds of a model several serve tries the higher one first, which the handle's tooltip says. dragRows put a row held at the top one place short of it, since the first row is a pixel shorter than the rest (they carry a border); held at either end it now goes there. TestProviderOrder and provider-order.test.cjs (Chromium and WebKit, English and Chinese) fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... (but TestClaudeSignedOut, which fails on the base too), go test ./internal/gui, and agent-grip, account-arrange and click-scroll (WebKit) pass. 2026-10-02 11:31:11 +08:00
yetone 8dbdcfc8b3 gateway: a Claude subscription's turn at another effort goes on in the same Claude Code, told the level, and reads its cache (#502: 经过意图路由判断改变了推理强度后,好像缓存全部要重新写入) — a router switching Opus 5.5 from high to low and back wrote the whole conversation to the cache each time (427,987 and 429,792 tokens written, 26,107 read, $2.18 and $2.20 a turn)
A Claude subscription is served through a Claude Code run that keeps the conversation for its next turn, started with --effort. The run waiting for that turn was found by turnKey, which hashed the effort: a turn the router picked another level for missed it, retire let it go, and a new Claude Code was told the whole conversation in one message, a prefix the cache had never seen (as #463 for a run let go too soon). turnKey now holds only whether an effort was asked for, and resume tells a waiting run a new level before its turn with the control request Claude Code's SDK sends for applyFlagSettings ({"type":"control_request","request":{"subtype":"apply_flag_settings","settings":{"effortLevel":"low"}}}); its control_response is read past like any other line that isn't a stream event. Checked against Claude Code 2.1.287 under a sandbox HOME and a local fake Anthropic endpoint (no real API call): started at --effort low and told high, get_settings answers applied effort "high", and the two upstream requests differ only in output_config.effort (system, tools, thinking {"type":"adaptive"} identical; the history is the same apart from where cache_control sits), which is what lets Claude Code keep its cache across a level change. A run started without an effort and a turn with one (or the reverse) still get separate runs, as --thinking-display is only set with an effort.

API-key and Responses upstreams already kept the prefix: withEffort changes only output_config.effort for an adaptive Claude (thinking left as it is) and only reasoning.effort for the Responses API (instructions, tools, input and prompt_cache_key untouched, Codex's instructions not involved), which TestWithEffortKeepsTheCachedPrefix now guards. TestClaudeRunKeptAcrossEffort (a fake claude logging its stdin) fails without the change (a second process for the turn at low) and passes with it: one process, the control request before the second turn, none for a third at the same level. go vet ./internal/..., go build -tags nogui ./... (darwin, GOOS=linux), GOOS=windows go build ./..., go test -tags nogui ./... pass (internal/gui's TestFXInState failed once in the full run and passes alone and with its package; untouched here).
2026-10-02 11:29:02 +08:00
yetone d3308f567f gui: the routing story's lines lead with the provider's logo before the account, key or provider they name (the owner: 这里在前面显示对应的 provider 图标会不会更直观一点)
The "why" line and each try's line (failed, answered, waiting), and the asides, put the named seat's icon (its own, its preset's, or the generic one) before the first account, key or provider of the request's order they mention — the longest name at the leftmost place, a whole word only. The opening line and what the vendor said word for word keep their text as is. routing-log-icons.test.cjs (Chromium and WebKit, en and zh: a Zhipu account failing, an OpenAI key answering) fails without the change. go vet ./internal/gui/, go build (darwin GUI), go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build pass; the routing tests that read the story pass in WebKit (routing-served flaked once under load and passes on rerun, as on main).
2026-10-02 11:27:03 +08:00
yetone 2d75c4883b gui: the refresh icon ends its last turn on the compositor's clock, so it no longer jumps back when the page is busy drawing the reply (a user on X: 右上角这个刷新按钮点击之后他的旋转似乎有点抽搐) — the spin waited for animationiteration and then dropped its class, and a page busy rendering the synced lists when the turn came round let the icon run on (461ms past the turn in the test: some 180°) and then snap back to 0°; at rest it still lagged a frame or two. The turn the reply catches is now told to be the last (effect.updateTiming iterations), and the class goes once it has finished, at 0°. sync-spin.test.cjs (Chromium and WebKit, en and zh) holds the page busy past the turn and fails without the change. 2026-10-02 11:15:10 +08:00
yetone 96367cb8f4 gui: Claude Code's model picker shows each model once (#496) — the rows magpie serves on the very Claude account Claude Code is signed in to fold into one, and models.dev's alias and dated id of one model are one row
Claude Code's picker had 37 rows where Codex's has 18. The Claude subscription added in magpie is, while Claude Code is signed in, the account Claude Code asks on its own, and both groups are titled "Claude Code", so every model showed twice (claude-opus-5-5, then "Claude Opus 5.5 · <account> · via magpie"); and models.dev lists claude-opus-4-5 and claude-opus-4-5-20251101 (haiku-4-5, sonnet-4-5 too) as two models. The via-magpie rows stay, as some use them (the [1m] fix, failover to their other accounts): viaMagpie now knows which provider is the agent's own sign-in (not a stand-in for a signed-out Claude Code, not paused; provider.Account.StandIn is new), and Claude Code's model field marks those options Same while its own group goes straight to Anthropic (not under a base URL of the user's, not for the tiers or a WSL distro). The picker folds them into one row, "13 models via magpie · <account> · the account Claude Code is signed in to" with a chevron, folded each time the picker opens; a click or Enter opens or closes it in place, the row staying under the pointer and the page not moving; the value set stays a row of its own, and Favorites never folds. claudeDated marks a dated Claude id whose undated id is in the same group as that one's Alias (one dated id per name only; [1m] kept), and the picker shows one row for the two: the alias, which a pick writes as before, or the dated one while that is the value set, so a choice made before still shows as picked. The option lists themselves are unchanged, so Spell, Reseat, renames and profiles still find every id. New strings have zh translations.

claude-picker-fold (Chromium and WebKit, en and zh, API fulfilled in memory) fails without the change in all 8 subtests and passes with it; TestClaudeDatedAlias covers the marking. model-pick, model-suffix-own, claude-direct, favorite-star, agent-models, unlisted-models and plain-names pass in both engines, click-scroll in WebKit; Chromium here only starts as the headless shell (the full Chrome for Testing bundle dies on icudtl.dat), where click-scroll fails the same way with the change undone. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui ./internal/agent ./internal/provider ./internal/gui (sandbox HOME) pass.
2026-10-02 11:08:14 +08:00
yetone 7ab91961d2 davsync: an unchanged file on the server costs a request, not a download (Crispin on Discord: 是不是Magpie后台一直在自动同步,导致被坚果云给限制了) — every 3 minutes sync read the whole sealed file even when nothing had changed, and 坚果云's free plan caps monthly downloads and requests per 30 minutes. The sync state now keeps the server's version beside its hash (its ETag, or a Last-Modified at least a second older than the server's Date, so a write in that same second can't go unseen), from the read or from the ETag a write answers with (none said: the next sync reads in full, as before); the next read sends If-None-Match (or If-Modified-Since) and a 304 with nothing changed here ends the sync, while a change here merges with a copy of the server's file kept beside sync-state.json (sync-server.magpie-backup, 0600, checked against the hash; gone or different, the file is read again), so it isn't read twice. A server that ignores the condition sends the file and syncs exactly as today. S3 does the same with If-None-Match on GetObject, signed with the rest (the fake S3 checks every signature). A 429 or 503 (S3's SlowDown) now says the server is limiting how often magpie may reach it, and Run waits as long as Retry-After says (up to 6 h) or else doubles from 6 up to 30 minutes, back to 3 after a sync that goes through; log de-dup is kept, as the message doesn't carry the wait. The 403 paths (there, forbidden) only run when the server answers 403, so a working setup costs one conditional GET per cycle. TestSyncUnchangedIsNotRead (ETag, ETag not on PUT, Last-Modified: three unchanged cycles read 0 bytes, a server change is read once and merged, a change here is pushed without a read, a missing copy is read again), TestSyncRateLimited and TestS3Unchanged fail without the change (with the conditions dropped: 3 full reads of 1932 bytes, 'HTTP 429' with no backoff, unconditional S3 GETs); TestSyncIgnoringConditions guards servers without conditional reads. go vet ./internal/..., go build -tags nogui ./... (darwin, GOOS=linux), GOOS=windows go build ./..., go test -tags nogui ./internal/davsync/ ./internal/gui/ pass. 2026-10-02 11:08:14 +08:00
yetone a396260204 providers: ClinePass lists the Cline API's free models beside the plan's (lml on Discord: Cline里面的免费模型是否也可以使用呢?) — a Cline key is served the API's ":free" models (qwen/qwen3.8-27b:free, google/gemma-4-31b-it:free, nvidia/nemotron-3-super-120b-a12b:free…) at no cost, so the preset's new FreeTag ":free" keeps the ids of the fetched list ending in it after the plan's cline-pass/ models (the plan's own list when the API names none of them, as before), marked Free, which the model list already badges FREE ("free: it doesn't use the plan's credits") as it does WorkBuddy's and Command Code's free models. Its paid models are still left out, and before the list is fetched only the plan's models show, as before: no free list is written down. ListPrice prices a FreeTag model at zero, where it was priced as the model without its tag (":free" is cut off when matching models.dev), so the usage ledger charges it nothing. The free models were not tried with a real Cline account (no key at hand). TestPlanModels fails without the change (the free models are dropped, none is marked Free, and one has no price). go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./internal/provider/ ./internal/gateway/ pass. 2026-10-02 11:08:14 +08:00
yetone 0f41570859 cursor: a conversation's Runs keep one conversation_id, so Grok's prompt cache on Cursor sticks, and Cursor's usage no longer counts the cache twice (#498, congee949). buildCursorRun put a new UUID in AgentRunRequest's conversation_id (field 5) on every Run, so Cursor's backend had nothing to keep a conversation on the machine holding its cached prefix — the x-grok-conv-id grokSigned sends xAI direct — and a Codex thread on cursor/grok-4.7-fast read about 2% from cache against 94% on the grok provider, its first token growing with the whole prompt. The id is now a UUID (v4-shaped, like the official client's) hashed from what names the session — the client's prompt_cache_key (Codex's thread id), else the agent's own session header (Claude Code's, OpenCode's) — together with the conversation's first user message, which every later request repeats; with neither key nor session header each Run still gets a fresh one, since a first message alone ("hi") would put strangers under one id. On concurrent subagents: Cursor's own client keeps one conversationId per agent session, and whether its backend treats the id as more than a cache-affinity key isn't known, so subagents sharing a session (Claude Code's Task agents share its session id; a Codex collab agent has its own thread and key anyway) get ids of their own through their own first message, never one id sent twice at once. TurnEndedUpdate's input_tokens is the whole prompt, cache read and written included, as Cursor's client has it; decode kept it as Usage.Input, which everywhere else is the uncached rest that prompt() and pricing add the cache to, so a cached turn was counted twice (in=137300 with cache_read=132352 is 4948 uncached). Input is now input_tokens less cache read and write, never below 0, and reasoning_tokens (field 5) is kept as Reasoning. TestCursorConversationID (same Codex key → same id across turns, other key → other id; same Claude Code session → same id, a subagent's first message → another; nothing naming it → a new id each Run), TestCursorUsageTakesTheCacheOut and the updated TestCursorDecodesTheEnd fail without the change. No real Cursor request was made. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./internal/gateway/ ./internal/provider/ pass. 2026-10-02 11:08:14 +08:00
lainbo fc036d1db4 fix(gateway): run Claude Code's /usage without CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC (#438)
With CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC set, Claude Code refuses its
own /api/oauth/usage request, so /usage only prints a snapshot another
Claude Code process left in ~/.claude.json within the last hour, and the
usage card shows "Usage unavailable" otherwise.

The /usage run now drops that flag unless the user set it, and sets
DISABLE_TELEMETRY, DISABLE_ERROR_REPORTING and DISABLE_AUTOUPDATER.
cleanClaudeEnv is unchanged, so the bridge and the model probe still run
with the flag. Without the flag, the /usage run also sends Claude Code's
startup config fetch, the org fast-mode check and the Grove notice config
request.

TestClaudeUsageSendsItsRequest checks the environment of the /usage run,
of the warm-up, and of a /usage run where the user set the flag.

Fixes #437
2026-10-02 11:08:04 +08:00
yetone 4696d14726 gui: Add provider unrolls its sheet under the list again, and closing it leaves no blank (the owner: 点击「添加供应商」为啥是弹出 modal 而不是像以前一样的交互(展开滚动)· 在添加供应商时会有两层弹窗,很丑,不符合设计原则)
#433 made the add sheet a dialog over the list, to get rid of a blank left under the list once the sheet was closed; but a vendor picked in it opens its editor, a dialog of its own, over that dialog: two layers. The sheet is back under the list, unrolling as the view goes down to it, the button at the view's foot stepping aside while its head is in sight. The blank was the page holding on to the Close clicked: with the sheet gone it held the sheet's neighbours where they were and kept its height as empty room at the view's foot. Closing the sheet (Close or Escape) now lets that hold go and takes the room away, so the list ends at the view's foot as before the sheet opened, and the focus goes back to Add provider, which comes back at once rather than at the end of its fade (it couldn't take the focus while still hidden). Kept from #433: Duplicate and Add another open the new editor without the sheet, an editor opened from the sheet gives the focus back to its option on close, a reload keeps the search's focus and caret, and the search's Escape empties it, then closes the sheet, and never hides the tray.

add-button is pre-#433's again (one click at the end of a long list unrolls the sheet and takes the view to it; scrolled back up, the button takes it there again; logos kept across a dialog) and now closes the sheet by Close twice and by Escape, checking no room is left, the height and scroll are those before it opened, and the focus is on Add provider; with the hold left in place it fails "Close left a blank under the list" in Chromium and WebKit, English and Chinese. add-sheet drops #433's focus trap and checks the sheet, not a backdrop. The 25 tests touching the sheet, Add provider, Duplicate or Add another pass, as does old-webkit; click-scroll passes in WebKit, and in Chromium fails the same four routing/usage cases as unchanged main on this loaded machine. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build and go test -tags nogui ./internal/gui/ pass.
2026-10-02 11:03:29 +08:00
yetone 7bc42a79d9 tray: every logo in the menu bar is drawn as a template image is, in the bar's text colour alone (the user: 当在 tray 上显示 agent 图标时,能否是去掉色彩满足 tray icon 样式的图标啊,而不是原样显示) — a coloured logo (Codex's blue tile, Claude's orange, Gemini's gradient, an app's PNG) was drawn as it is. Its shape is now what is opaque, less what is near white when the rest isn't, so a tile with a white glyph (Codex, ZCode, Kiro, WorkBuddy) is a tile of ink with the glyph cut out, light or dark. TestTrayImageMonoLogos (Codex, Claude, Gemini, ZCode, Alma; light and dark) fails without the change. go vet, go build (darwin GUI), go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./..., go test ./internal/gui pass. 2026-10-02 11:01:28 +08:00
yetone b8daf4013b gui: Take a look on the line naming several subscriptions that can move to plugins opens the Plugins tab, not Cursor's editor (the user: 这里点击查看为什么打开的是 cursor)
The quiet line over the Providers list names every built-in subscription a community plugin can run ("Cursor、Grok (SuperGrok)、Devin、Kiro… 可以改由社区插件运行,账号不变。"), and its 查看 opened the first one's editor, Cursor's, as if the line were about Cursor alone. With several named it now opens the Plugins tab, where each one's card has its own Move; with one named it still opens that one's editor at its Runs on, as plugin-move checks.

plugin-movable-review (new) has three movable subscriptions: Take a look opens Plugins, no editor, and a card offering each one's Move; it fails 4 of 4 without the change. plugin-move passes; go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build and go test -tags nogui ./internal/gui/ pass.
2026-10-02 10:44:51 +08:00
yetone ec10d3bd98 gui: the header tabs lose their grey track, a Plugins card's move is one word beside the name, and an agent row's grip shows only under the pointer (the user: 这个 tab 栏的背景灰色条有点丑 · 这里的紫色按钮也有点丑,而且还有的变成了两行 · 排序 icon 只在 hover 到这个条目的时候显示,logo hover 的排序 icon 可以删掉)
The header's .seg drew a grey track behind its tabs; now only the open tab's white thumb marks it (the account-mode .am-seg keeps its own). A Plugins card offering a built-in's accounts had a solid purple bar of its own at the card's foot, "Move my 2 Command Code Plan accounts", which broke onto two lines; it is now the tinted pill beside the name that Install is, reading "Move" / "迁移", its title saying whose accounts and how many and what moving does. In the Agents list the grip in a row's left margin (#479) was drawn on every row all the time, and hovering the logo turned it into a second grip; the margin grip now shows only for the row under the pointer, the one being dragged, or a handle with its menu open or the keyboard on it (kept in a rule of its own, for Safari 15), and the logo stays the logo. Dragging or clicking the grip still does what the logo does.

agent-grip now checks no grip is drawn with the pointer away, the hovered row's alone is, and the logo has no grip of its own; plugin-move-overflow checks the move button sits in the card's top row on one line. agent-grip, account-arrange, agent-layout, the agent-models tests, brand, header-centre, header-zoom-fit, phone-web, strip-scrollbar, old-webkit and the plugin tests pass in Chromium and WebKit, except plugin-move's known Chromium flake and click-scroll, which fail the same way on unchanged main on this loaded machine. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build and go test -tags nogui ./internal/gui/... pass.
2026-10-02 10:31:43 +08:00
yetone ee5f974f98 providers: PipeLLM is a built-in preset (the owner: 作为内置 provider 加上吧) — one key for OpenAI's, Anthropic's and Gemini's models at api.pipellm.ai. Chat goes to its converter (/openai/v1), which takes every family, as its own OpenCode and OpenClaw guides set it up; GPT goes to its own Responses API (/v1) and Claude to its own Messages API (the root), each of which serves its family alone. A model list's type_target (openai, anthropic, gemini) now says which APIs a model is served on when the list names no endpoints: openai on Chat and Responses (Responses first for GPT, as on OpenAI's API), anthropic on Messages alone, gemini on Chat alone. Keys at console.pipellm.ai; its icon is the site's favicon; README lists it. Not sponsored. No PipeLLM key was at hand, so no real request was made; TestPipeLLMRoutesByTypeTarget (httptest list) fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-02 10:31:23 +08:00
Arvak f3ba918491 gui: make magpie web usable on phone screens (#391)
* gui: make magpie web usable on phone screens

* gui: keep phone content readable and retain idle scroll protection

* gui: tolerate small desktop screenshot rendering differences

* tests: synchronize Zhipu team quota request records

* gui: keep every agent field visible on phones

* gui: exercise touch scrolling in the settings usage part

* gui: serve plugin parts in the phone test fixture
2026-10-02 10:15:42 +08:00
Fanbuzandfanpeng 1eb2640d7e fix: 添加供应商改用遮罩并保持列表位置 (#433)
* fix: 添加供应商改用遮罩并保持列表位置

* fix: 统一供应商浮层键盘导航并拦截搜索框退出

* fix: 保留供应商浮层滚动位置并恢复刷新焦点

---------

Co-authored-by: fanpeng <fanpeng@xintukj.com>
2026-10-02 10:15:35 +08:00
Fanbuz 5ebb053ce1 fix: 统一额度相关的中英文界面文案 (#493) 2026-10-02 09:37:00 +08:00
yetone 6b89fb22f4 gateway: an earlier assistant turn's thinking goes to Mistral as the thinking part Mistral itself returns, and a vendor that turns it away inside messages is asked again without it (#494: Mistral's 422 extra_forbidden for OpenCode's reasoning_content, after any tool call) — OpenCode sends an earlier assistant turn back with its thinking in reasoning_content, a tool-call turn too (content null), and magpie relayed it as sent; Mistral refuses any field it doesn't know, loc ["body","messages",2,"assistant","reasoning_content"], so a Mistral reasoning model (GLM on api.mistral.ai) in OpenCode failed as soon as it used a tool. The #393 retry only dropped a fixed list of top-level fields. To Mistral (its preset, or api.mistral.ai) an assistant message's reasoning_content (or reasoning, or reasoning_details' text) now goes as a leading {"type":"thinking","thinking":[{"type":"text","text":…}]} part of its content, its text after it as a text part, its tool_calls as they were, so the thinking stays in context, as the reporter found Mistral takes it; the other messages keep their bytes. Any other Chat upstream answering extra_forbidden located in messages for one of those fields is asked again with it left out of the messages, and once that works it is left out for that provider from then on; DeepSeek, Kimi and every vendor that takes it are sent it as before. Prompts and headers are unchanged. TestMistralReasoningAsThinking (the issue's request, and a tool-call turn) and TestMessageReasoningRefused fail without the change. go vet and the darwin build pass; the full test and cross-build chain could not run, the disk being full. 2026-10-02 09:23:29 +08:00
yetone 16e373b924 opencode: Zen's free models answer through magpie from Claude Code, Codex and the model test too (ARNO on Discord: mimo-v2.6-flash-free 没有响应: 403 · OpenCode's free tier can only be used from within OpenCode; the user: 只要不是 claude code,都可以尝试伪装一下) — beyond OpenCode's headers (v0.1.615), Zen's free tier takes only a streamed request offering tools named bash and read. A request to a -free model on opencode.ai now always streams upstream, a client that didn't ask for a stream getting the reply collected; a caller's Bash/Read (any case) go as bash/read, as do earlier calls in the history and a forced tool_choice, and calls in the reply get the caller's names back, streamed or not, in each search round too; when bash or read is missing a stub is offered whose description says never to call it, and a call to a stub never reaches the client (a turn with nothing else ends on its text, or a short note, as a stop). Zen lists its -free models again. Paid Zen models and other providers are untouched. Real requests to opencode.ai/zen/v1 with the anonymous key: Claude Code-style Bash/Read streamed and not, Codex-style shell on /v1/responses, no tools non-stream, and the model test all answered 200. TestZenFree* fail without the change. 2026-10-02 09:23:29 +08:00
yetone 3515d99a9c gateway: a request to OpenCode's gateway (Zen or Go) says it is OpenCode's, as OpenCode itself sends it (the user: free Zen models 403 "OpenCode's free tier can only be used from within OpenCode"; mimicking OpenCode is allowed on this route) — magpie sent User-Agent magpie/<version> and its own conversation id (magpie-…) in x-opencode-session. OpenCode (packages/opencode/src/session/llm/request.ts, for a provider whose id starts with "opencode") sends User-Agent opencode/<version>, x-opencode-session (ses_…), x-opencode-request (msg_…), x-opencode-client and x-opencode-project; every request to opencode.ai now carries them: opencode/1.18.34, the conversation as an id of OpenCode's form (one already ses_… goes as it is, any other is made from it, the same for the same conversation), a fresh msg_… id, cli and global. Prompts and body go as the caller sent them; other providers get none of it. Zen's free tier also asks for OpenCode 1.18.0 or newer (426 otherwise), a streamed request, and tools named bash and read: a live request through the gateway with the anonymous key "public" to mimo-v2.6-flash-free went from 403 to 200 for a streamed one offering bash and read (OpenCode's and Pi's), and stays 403 for Claude Code's Bash/Read or no tools, so the free models stay off the list. TestOpenCodeClientHeaders fails without the change; TestOpenCodeGetsConversationSession now expects OpenCode's form. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-02 08:28:35 +08:00
yetone 31568cb0b0 zcode: a Start Plan request goes as the ZCode app sends it, its system prompt and headers (#425; the user allowed mimicking ZCode's client, for this vendor only) — zcode.z.ai turns a Start Plan (体验套餐) request without ZCode's own system prompt away with 405 "request has been blocked due to unusual activity", code 3012, as zcode2api found (a137460387/zcode2api src/upstream/system-prompt.js, zcode-plan.js; dengyie/zcode2api app/body_transform.py), and magpie sent the agent's request as it was. A Start Plan /v1/messages request now carries ZCode 3.11's three system blocks, each cached (its opening line; its identity, harness and desktop context; "\n\n" and its dynamic sections around an environment section that leaves the working directory and OS version unknown), the agent's own system after them, and the day in a <system-reminder> before the first user turn unless one is there; ZCode's blocks take three of the four cache breakpoints, so the agent's system and tools give up theirs and only its last in the messages is kept. Model, max_tokens and the rest of the body go as sent (numbers as written). Its headers are ZCode's as zcode2api's headers.js has them: User-Agent ZCode/3.14.3 ai-sdk/anthropic/3.0.81, X-ZCode-Agent glm, X-Release-Channel production, X-Os-Category, X-Client-Language and X-Client-Timezone from the machine, a fresh X-Request-Id and X-ZCode-Trace-Id, X-ZCode-Session-Type main, no X-Session-Id or X-Query-Id, beside the X-Title, X-Platform, HTTP-Referer and X-Device-Mid sent before. The GLM Coding Plan and team seats are unchanged. The 405 hint no longer says magpie won't pretend to be ZCode: a block that still happens is the network blocking the IP or ZCode checking something new. No ZCode account was signed in; TestZCodeStartSentAsTheApp (httptest) fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; routing-zcode-blocked, click-scroll, old-webkit pass. 2026-10-02 08:20:33 +08:00
yetone 6e858c6641 sessions: a session in magpie's trash can be erased for good, one at a time or the whole trash, after magpie's own dialog (#487: no way to empty the trash but removing its folder by hand) — the Sessions page's Trash only restored, and said magpie never erases what is in it: remove the folder yourself. Each trashed row now has Delete forever and the Trash an Empty trash, with the count of what is in it; both ask in magpie's dialog, as Delete does (never confirm()), naming the sessions, and Cancel sends nothing. They post /api/sessions/purge with the rows' keys, or all for every session the trash lists; each key is checked as Restore checks it (<agent>/<name>, an agent magpie deletes for, no separators, no . or ..), and its folder, and its agent's, must be real folders in <magpie dir>/trash/sessions, not links, with the session's note (session.json) in it; only that folder goes, its note with it, so the trash lists it no more. Nothing is erased by itself: the note under the Trash now says they are kept until erased there, and magpie never erases them by itself. TestPurgeOne, TestPurgeAll, TestPurgeRefusesOutOfTrash, TestSessionsPurgeRoute and sessions-purge.test.cjs (Chromium, WebKit; en, zh) fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestFXInState failed once under load, passes alone and in its package); sessions-manage, sessions-toolbar, click-scroll, old-webkit pass. 2026-10-02 08:20:33 +08:00
yetone 01af01c7aa usage: the Overview has one refresh, the header's, and it asks for the allowances as their own Refresh did (#486: two refresh controls on one page) — the Usage page's header had its refresh icon and, a little below, the allowances' Refresh button, and the icon on the Overview read the allowances only as the timed reload does, so a Claude account's was not read again by it. The allowances' Refresh is gone; the header's icon on the Overview now loads usage/quotas?asked=1 beside the summary, as that button did (a Claude account's by running Claude Code's own /usage, still at most once in 30s), and turns until both are in; its title says it reads the allowances too. Requests and Sessions are unchanged. usage-refresh.test.cjs (Chromium, WebKit; en, zh) and claude-usage-asked.test.cjs fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; usage-ledger, quota-pools, quota-families, click-scroll, old-webkit pass. 2026-10-02 08:20:33 +08:00
yetone cfd67225c8 plugins: the Plugins page is drawn in parts, Installed at once and npm's figures after (#488: the page showed skeletons for seconds, even on Installed) — the page waited on /api/plugins/market, which answered only once the plugin host, the registry fetch and an npm request for every package (two each, 8s apiece, all at once, kept an hour in memory only) had all come back, so the slowest package decided when anything was drawn. The page now asks for each part on its own, as the Library page does: Installed is drawn from GET /api/plugins as soon as it answers; Discover's cards from the new GET /api/plugins/listings (the registry, with its cached and built-in copies), each with what npm said last; and versions, weekly downloads and an installed plugin's "v… out" fill in from the new GET /api/plugins/npm?names=…, never waited on (a card's button waits, disabled, until npm has said whether it has the package). npm is asked at most six packages at a time and for no more than 8s in all; a package it answers late is what it said last, and its answer is kept for the next time. What npm said is kept on disk (~/.cache/magpie/plugin-npm.json) beside the registry's copy, so a magpie just started shows it before asking, and GET /api/plugins gives each plugin's newest version from it. /api/plugins/market still answers the whole market as before. Once loaded the page shows what it did. TestNPMInfoKeptOnDisk, TestNPMInfoDeadline, TestNPMInfoAtOnce, TestPluginMarketInParts and plugin-progressive.test.cjs (Chromium, WebKit; en, zh; Installed while the market is held pending) fail without the change; plugin-local, plugin-updates, plugin-market, plugin-move, plugin-move-overflow, plugin-release, plugin-git, plugin-key-hint, plugin-plan, list-sort, click-scroll, old-webkit pass. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-02 08:20:32 +08:00
yetone dfc8ea4aea profiles: Apply closes a profile's details, and their × closes them after it (#489, a regression of #478) — on Apply, profileAction dropped profileOpen and then called closeProfiles, whose closeProfileDetail, finding nothing open, returned at once; renderAgents had just drawn the details again, so they stayed in the window and came back with the tray's list, and their ×, which calls the same closeProfileDetail, did nothing until the chip was clicked again. Apply now closes the details itself, and closeProfileDetail closes details on the page whether or not profileOpen still names them. profile-confirm.test.cjs (Chromium, WebKit; en, zh; window and tray) now applies a profile from its details and checks they close, the tray's list opens again on the chips, and × closes them after it; it fails without the change. profile-details, panel-profiles, click-scroll, old-webkit pass; go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build pass. 2026-10-02 08:20:32 +08:00
yetone 480df9bac0 web: magpie web on a phone has a header to read and tap, and a flick's scroll is no longer put back (jiakun_zhao on X: web 页在移动端的显示效果非常不友好诶,甚至滚动会抽搐) — on a phone the tabs were squeezed into the room between the magpie and the icons, too small to read or tap; they now take a row of their own under them, at their size, sliding sideways with the one open kept in sight. A setting's control too wide beside its name goes under it rather than cut the name, and iOS isn't let zoom into a field it focuses (a pinch still zooms). A flick goes on scrolling after the finger lifts with no touch event to say so, and those scrolls weren't counted as the reader's: each was put back, and the page jerked. Each of a flick's scrolls now keeps the next one the reader's till it comes to rest; a tap's click ends it, so what a click redraws still doesn't move the page. A desktop browser and the app's window are unchanged. phone-web fails without the change (the header, and the flick put back at 350 of 711). go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build pass; phone-web, click-scroll, brand, panel-fold, provider-typed, library-loading, old-webkit, header-centre, header-zoom-fit, strip-scrollbar, web-page, text-size, library-row-open-scroll, provider-levels-scroll pass. 2026-10-02 08:17:38 +08:00
Fanbuz 7d785c4407 fix: 将订阅设置中的用量文案改为额度 (#484) 2026-10-02 08:09:52 +08:00
yetone 5df609e525 usage: an Antigravity account shows each group of models' 5-hour and weekly allowance, one row a group (a user on Discord: 这里显示5h 和一周剩余比较好吧,现在是只显示5h,这3个模型都是一样的,没必要分开…多加个7day 条就好) — fetchAvailableModels gives each model only its 5-hour figure, the same for every model drawing on one quota, so the Usage page, the panel and the account rows showed a figure a family (Gemini, Claude, GPT-OSS) and no week. Antigravity's retrieveUserQuotaSummary reports the quota a group ("Gemini Models"; "Claude and GPT models", Claude and GPT-OSS sharing one), each with a 5h and a weekly bucket; magpie now reads it beside the models' (best-effort: without it the windows are as before) and adds each group's windows, named "5 hours" and "7 days" with their pool (Gemini, Claude & GPT), each model's window naming its pool. The Usage page shows Gemini · 5 hours, Gemini · 7 days, Claude & GPT · 5 hours, Claude & GPT · 7 days, the pool's models in each tooltip; the panel four rings (Gemini 5h/7d, Claude 5h/7d); the account rows each pool with its 5h and 7d meters; the TUI the pools' windows in place of the models'. "Every model" lists the models' own windows. The pools' windows are aside for routing, which reads the models' as before; the tray's text is unchanged. TestAntigravityQuotaPools and quota-pools.test.cjs (Chromium, WebKit; en, zh) fail without the change; quota-families, click-scroll, old-webkit, tray-usages, panel-usage pass. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-02 07:03:17 +08:00
yetone cb761abdd1 commandcode: a reply is asked for within Command Code's 200000 tokens, and the agents are told no more (Discord: every Command Code model, free or paid, failed with "Too big: expected number to be <=200000 at params.max_tokens") — magpie passed the client's max_tokens to /alpha/generate as it came, and told the agents each Command Code model's reply limit as models.dev gives it: DeepSeek V4's 384000, MiniMax M3's 512000, Space Bunny Alpha's 524288, Kimi K2.x's 262144, so an agent asking for what it was told had the whole request refused. params.max_tokens is now the asked number within the model's own limit as models.dev gives it and within 200000; less is sent as it was, and none asked is still the CLI's 64000. The Command Code subscription's models are listed to the agents (their configs, /v1/models) with a reply limit of at most 200000, a user's own limit included. Prompts and headers are unchanged. TestCommandCodeGoMaxTokens and TestCommandCodeOutputCapped fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-02 06:58:18 +08:00
yetone 134a788821 settings: magpie's own update checks can be turned off, and run every 30 minutes, hour, six hours or day (#472: 更新行为可配置) — the app asked the feed five seconds after start and every six hours, a constant, and downloaded any newer version at once; with releases out most days, a version out just after a check waited up to six hours, and there was no way to stop the checks or the downloads they start (Update button only hides the pill). Settings → About has Automatic updates (on by default) and Check every (30 min, 1 h, 6 h, 24 h; 6 h with nothing set), kept in settings.json as noAutoUpdate and updateEvery, and magpie update auto [on|off] [30m|1h|6h|24h] sets them from the terminal. The updater reads them each minute and asks once the interval since the last check, its own or a click on Check, is up, and never by itself while off; the version row, not asked yet, then says automatic updates are off and offers Check. Check every stays in place, dimmed, while off, so the click doesn't shorten the page under it. TestUpdateDue, TestUpdateAskedAt and TestUpdateAutoCmd fail without the change, as does update-auto.test.cjs (Chromium, WebKit; en, zh). go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; update-hide, update-check, whats-new, settings-groups, click-scroll, old-webkit pass. 2026-10-02 06:42:15 +08:00
yetone 03ab46dda3 library, plugins: the installed lists are sorted as the reader picks, and remember it (#481: the installed MCP servers, skills and plugins had no order the user could choose; the plugins came in the order they were installed) — the Library's MCP servers and the installed plugins now have A→Z and Z→A beside their list, A→Z until one is picked, so the plugins are no longer in the order they were installed in; the Library's skills have By source (the groups by repository and their parts, as before, and still the default), A→Z and Z→A, the last two one flat list of every skill by name, the filter still finding a skill by its repository. Each list keeps its own pick in the browser's storage (magpie.sort.<list>, read and written in try/catch), and a pick redraws only its list, the control left where it was on the screen. The skills' heading, the fullest, goes on to a second line in a narrow window rather than past the page's side. Sorting by use or by when a thing was installed waits for the library to keep those. list-sort (Chromium, WebKit; en, zh) fails without the change; library-items, library-all, own-skill, every-skill, the plugin tests, click-scroll and old-webkit pass; go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-02 06:33:46 +08:00
yetone e3bf9d05d4 library: one agent's skills or MCP servers are turned on or off at once, and a hidden agent's taken out of it (#475: turning Copilot CLI's 32 skills off was a chip on every row, and once it was hidden on the Agents page it had no chips at all, so what it had been given stayed with no way to take it out) — By agent, beside "In the library" on the Skills and MCP tabs, opens a sheet of the agents that take them, each with how many it has, a Turn all on and a Turn all off; each writes that one agent, every other keeping what it has, and reports what couldn't be written as a row's chips do. An agent hidden on the Agents page is listed while it has any, tagged Hidden and given nothing new; its instructions row is shown while it reads the shared instructions, so it can be switched off. Skills use the existing skills/agents-all; servers get servers/agents-all (EveryServerAgents), which turns on only the servers the agent can reach (no SSE for Codex, no remote one for Claude Desktop). TestEveryServerAgents and by-agent.test.cjs (Chromium, WebKit; en, zh) fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; every-skill, library-all, library-items, library-toggle-status, remove-all-skills, click-scroll, old-webkit pass. 2026-10-02 06:33:40 +08:00
yetone fe86fb3a4d gui: the page is first drawn in its saved theme, not the system's (seg-contrast failed in Chromium on the release branch: in dark the view shown read at 3.96:1, the other views at 4.69:1) — the saved theme was put on the page by app.js, at the foot of index.html, so a window kept dark under a light system had its header parsed and styled light first, and its buttons' colour transitions (the switch's views, Sync, Settings) then faded them to dark as the page loaded; the test read them mid-way, the more so as #471 made the page longer to parse. theme.js, run in the head after boot.js and before the styles, sets it from bootPrefs (or ?theme=) for the first paint; app.js still sets it as before. seg-contrast.test.cjs now holds app.js back and checks the page was already drawn in its theme (fails without theme.js, Chromium and WebKit, en and zh, dark), and reads colours once the page's transitions are over. old-webkit lets theme.js (ES5, URLSearchParams) run before compat.js; api-key-theme, from the release branch, expects a text button in --ctl-fg as #477 set. seg-contrast, settings-sections, header-zoom-fit, header-centre, click-scroll, old-webkit, api-key-theme, omarchy, agent-grip, web-page, gateway-fold, sessions-toolbar pass; go vet, go build -tags nogui, go test ./internal/gui pass. 2026-10-02 06:12:01 +08:00
yetone 1ea43274b0 gateway: Mistral's thinking, sent as typed parts in a Chat reply's content, reaches a Chat client as reasoning_content and a string content, and a translated one as thinking (#483: OpenCode's 'Invalid magpie/openai-compatible-chat stream event' with GLM on api.mistral.ai) — Mistral streams a reasoning model's thinking as delta.content = [{"type":"thinking","thinking":[{"type":"text","text":…}]},{"type":"text","text":"ok"}], thinking and text sometimes in one delta, and message.content so when not streamed; magpie relayed it as it came, the Chat contract has a string or null there, and the AI SDK OpenCode reads Chat with turned the first such chunk away and gave the request up (499 in the usage log). A relayed chunk or whole reply whose content is parts now has its thinking in reasoning_content, after any already there, and its text as a string content; a delta of thinking alone goes without content. Spoken to Claude Code's API or Codex's, such a chunk wasn't read at all, its thinking and text lost; it is now thinking and then text. Replies of every other upstream pass byte for byte, streamed or not. Prompts and headers are unchanged. TestMistralPartsStreamFlattened, TestMistralPartsWholeFlattened and TestMistralPartsTranslated (Anthropic, Responses) fail without the change; TestChatRelayWithoutPartsUntouched keeps other replies byte for byte. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-02 06:07:57 +08:00
yetone 872e26e2d4 models: a favourite's star is filled, and a model not a favourite shows an outline one on hover (#482: 收藏后切换成实心星) — the model picker's star, in the window and the tray panel, was the same outline star whether the model was a favourite or not, only its colour changed, so which state it was in wasn't clear. A favourite's star is now filled in its own amber, light and dark, and shows always; one not a favourite is still an outline, shown on hover. The button says which with aria-pressed as well as its title. favorite-star (window and tray panel, light and dark, en and zh, Chromium and WebKit) fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build pass; click-scroll, old-webkit, model-pick pass. 2026-10-02 06:07:14 +08:00
yetone 10da2936da gui: Settings is a page per part, under a tab each, the part kept in the address (#471: Settings is one long scroll of some 45 rows in 9 sections) — the page now opens with a tab list in the warm-ups' look (role=tablist, a tab each with aria-controls/aria-labelledby): General (appearance, language, text size, the icons, model names, login, Dock, session terminal), Usage (the menu bar's usage, refresh, logos, what usage shows, currency, number units, the two alerts, then the warm-up and check-in tabs), Network and sharing (proxy, local network), Models (images, web search), Privacy, Observability, Sync and backup, About; the part picked shows its rows alone. Every row, its id and what it saves are as they were; the duplicate heading of a one-section part and the Preferences heading are gone. The part is kept as ?view=settings&tab=<part> (dropped on any other view), so a reload or a link lands there, and remembered (magpie.settingsTab) for Settings opened again without one, as the tray's window/main?view=settings; an unknown one falls back to it. A part whose rows are all hidden on this system has no tab, and one picked falls back to the first shown. The arrows, Home and End move along the tabs, one in the Tab order; a click on a tab moves nothing on the page, the tabs staying where they were; a pill drawn while its part was hidden has its thumb put under its pick when shown (the warm-ups' fix, now shared). The tabs wrap to a line more when they don't fit (English in the 560x420 window, at 110 and 125%, a phone), no left-border stripe. The click hold also holds a click not yet held before taking the next (keys pressed within a frame on a tab list whose part shrank to the window: the second click was taken at the clamped top and the page stayed there). New strings General and Network and sharing are in i18n.js (常规, 网络与共享). Tests that reach a row now open or click its part: api-key-theme, currency, gateway-caller-keys, gateway-connect-keys, lan-docker, number-units, otel, redact-rule-row, s3-sync, search-api, settings-groups, sync-other-kind, tray-inuse, tray-logos, tray-usages, update-check, update-hide, usage-alert, webdav-settings-refresh, whats-new; those needing a scrolled page wheel a shorter window or in smaller steps, as a part is shorter than the whole page was. settings-sections.test.cjs (Chromium, WebKit, en, zh) fails without the change (10 of 10). go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestAccountProxy as on main); settings-sections, settings-groups, click-scroll, old-webkit, text-size and every settings test listed above pass. 2026-10-02 05:50:24 +08:00
yetone 69edf9f6e4 profiles: a chip's ↻ and × act on a second click, and a profile's details close with their own ×, or Escape, without hiding the tray panel (emo172, #478: ↻ overwrote and × deleted a profile on the first click; the details closed only by clicking the chip again, and Escape on them in the tray panel hid the whole panel; closed by hand they came back with the list, closed by Apply they didn't) — a first click on ↻ or × now changes nothing and reads "Overwrite?" or "Delete?" (覆盖?, 删除?) in red, and a second does it; it goes back after 3.5s, on Escape, or when the other one is clicked, with no browser dialog. The details have a × beside Apply, which closes them, the chip where it was. Escape takes back a question, then closes the details, then the panel's list, and only then hides the panel (both the panel's Escape and the list's ran on one press, so it hid the panel with the list open). The details close with the list, so the list opens again on the chips alone, however they were closed, as after Apply. profile-confirm.test.cjs (Chromium, WebKit, en, zh, window and panel) fails without the change; panel-profiles clicks × twice. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; profile-details, panel-profiles, old-webkit, click-scroll pass (click-scroll's routing Replay case flaked once on WebKit, passes alone). 2026-10-02 05:50:23 +08:00
yetone 973bf4eb2c profiles: a profile's details list all four of Claude Code's tiers, one left to follow the main model saying so (#480: opus and fable, following it, were missing from the details, so they read as not set) — a tier is a quiet field, empty while it follows the main model, and the details left every empty quiet field out. A field now says which one it follows (Follows, "model" for the tiers), and the details keep such a field, empty, with it; the panel shows it as "follows the main model (<model>)" (跟随主模型(…)), or "follows the main model" when the main is the agent's default. Other quiet fields (Codex's sign-in, subagents) are still left out while empty, and what applying a profile writes is unchanged. TestDetails and profile-details.test.cjs (Chromium, WebKit, en, zh) fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-02 05:50:23 +08:00