Commit Graph

  • 0920a11590 agent: Alma is told which of magpie's models reason, so it offers thinking for every Codex model (#730, Magixyne: Alma's magpie provider listed codex/gpt-5.5, gpt-5.6-luna/-sol/-terra and gpt-6-astra with vision and tools only, no reasoning, and no thinking to pick) — magpie put its catalog into Alma with PUT /api/providers/:id/models as ids and names alone, and Alma (0.4.155, checked read-only in its app.asar out/main/index.js) works a model's capabilities out by its id: a models.dev entry (provider:id or bare id), a fuzzy one by substring only at chat time, then heuristics (a gpt-5 id gets vision); magpie's ids (codex/gpt-5.5) aren't models.dev's, so a Codex model came out reasoning false, and its getModels/getProviderModels add reasoningLevels only to a model that reasons. A model whose id models.dev happens to hold (openrouter-style ids) did reason, hence "部分模型". Alma's PUT keeps each availableModels entry's capabilityOverrides as sent (the ones it had when none are), and Lf lays them over what it worked out, in the provider list, the model picker and the chat request (Lt/Mt read capabilityOverrides.reasoning and reasoningLevels). almaModels now sends each model's capabilityOverrides (almaCaps): reasoning true for a model that thinks (Reasoning or levels), reasoningLevels as those of its levels Alma knows (low, medium, high, xhigh, max, Alma's own list for a provider's /models, in its order; none sent for a model with a thinking switch alone), vision when magpie takes its images, contextWindow and maxOutputTokens (maxTokens) when known; a model magpie knows nothing of gets none, so Alma keeps its own. An override the user set in Alma that magpie says nothing of (functionCalling, pricing) is sent back with magpie's laid over it (almaWithOverrides), and almaSameModels also compares magpie's keys, so a provider an older magpie filled is told on the next Sync and one already told isn't written again. The fake Alma in alma_test.go keeps overrides as Alma's handler does. TestAlmaToldReasoning (a gpt-5.5 with xhigh, minimal, low, medium, high, ultra → low, medium, high, xhigh; a switch-only thinker → reasoning alone; glm-4.6 → no reasoning, its window; nothing written when told; an older magpie's provider told again with the user's functionCalling and pricing kept) fails on the old code (gpt-5.5: null) and passes. go vet ./internal/..., go build -tags nogui ./..., GOOS=windows and GOOS=linux builds and go test -tags nogui ./internal/agent/ (its TestMain sandboxes HOME) pass; the full go test under a temp HOME was not run here (the sandbox refused the HOME override). Not tried in the reporter's Alma: magpie writes the overrides on its next sync of Alma's provider, and Alma shows them when its provider page is opened again. yetone 2026-10-04 08:13:23 +08:00
  • e85886c760 gui: the Agents page updates every agent's CLI at once, and lists the agents not on this machine with their vendors' install commands (#727, Sun1090: on a new machine with no agent installed magpie gave no install commands or how-to, and agents updated only one at a time, where cc-switch has both) — Update all: with two or more CLIs behind (#202's cliInfo update), a line above the list says which and offers Update all, which runs the existing per-agent update (POST /api/agents/cli/{id}, updateCLI, now returning why it failed and quiet for the run) for each, one after another (two global npm installs at once can trip over each other's folders), each row's pill busy in its turn, then says how it went ("2 of 3 agents updated · 1 didn't: Gemini CLI", the errors in its tooltip and the status pill). Once shown the line stays until the agents are read again (load), saying what is left ("Crush has an update", "Every agent is up to date"), so neither its click nor a row's own pill moves the list under the reader. Install: agent.Installs (internal/agent/install.go) lists the agents magpie knows that aren't detected (not WSL twins) with their commands: the vendor's installer first where its docs give one (Claude Code's claude.ai/install.sh, install.ps1 on Windows; Codex's chatgpt.com/codex/install.sh, install.ps1, and brew --cask codex on a Mac; OpenCode's opencode.ai/install; Gemini CLI's brew formula), then npm install -g of the package cliSpecs already updates from (pi, omp, copilot, crush, cline, omo too); GET /api/agents/install serves it. Under the list, "Install another agent (n)" unrolls them (open when no agent is here at all), each command with a copy button and a note that npm needs Node.js and magpie lists the agent once installed. magpie doesn't run the installs: a new machine may have no Node.js or Homebrew yet and the update machinery derives its command from where an installed binary is, so the commands are to copy. zh strings for every new one. TestInstallCommands and TestInstallsOnlyMissing pass; agent-update-all.test.cjs (webkit: Update all runs three in order never two at once, a failure named, the page and list not moved, a row's own pill leaves the line in place, one behind gets no Update all, the install list folded under the list without the installed Codex, copy sends the command, open with no agents, Chinese) passes and fails on the old code. Webkit agent-connect, agent-connected-unfolded, agent-disconnect, agent-grip, agent-layout, agent-tweak-fold, click-scroll, copy-command, show-unset-agent, panel-fold, cli-behind pass; cli-update's tight-row subtest fails the same on the old code. go vet ./internal/..., go build -tags nogui ./..., GOOS=windows and GOOS=linux builds pass; the full go test under a temp HOME was not run here (the sandbox refused the HOME override) — internal/agent's install/version tests and internal/gui's asset tests pass. Not tried in the reporter's app. yetone 2026-10-04 07:57:01 +08:00
  • 67004a7e8f sessions: count Codex RECORD and compaction usage without a history parent (#695) Fanbuz 2026-10-04 07:57:49 +08:00
  • 5fb7799b11 agents: Mister Morph (morph) — picking a magpie model points ~/.morph/config.yaml's llm at the gateway ($MISTER_MORPH_CONFIG names another file) (#728) Lyric WaiandClaude Opus 5.5 2026-10-04 08:57:44 +09:00
  • 4f25ed83d3 library: Discover finds an MCP server by its address — a repository, page, endpoint or package pasted in (lc on Discord: https://github.com/crystaldba/postgres-mcp in Library › MCP › 发现 said 没有与 … 匹配的结果) — MarketServers matched featured servers on their words and asked the MCP Registry with the search as typed, and the registry's search is a part of a server's name (checked: "postgres mcp" lists 0, "crystaldba" 0), so a URL found nothing anywhere. A search that reads as an address (address: a scheme, git@, www., host/path, owner/repo or @scope/pkg; words with a space, or a bare word, are searched as before) is normalised (normAddr: lower case, no scheme, user, www., query, fragment, trailing slash or .git; npmjs.com/package/x, pypi.org/project/x, hub.docker.com/r/x and docker.io/x are the package, an image's tag and a package's @version dropped) and matched (addrScore: equal, under it, or a file under one's repository; owner/repo on any host; never a bare host or an owner's page standing for every repository) against each server's addresses: a featured one's page, endpoint and package; a registry one's name, websiteUrl, repository.url, remotes' URLs and packages' identifiers. The registry is asked for the names the address suggests (addrTerms: the repository's or package's name, then its owner's; a site's name for an endpoint, context7 for mcp.context7.com/mcp), and what it lists at the address comes first and alone, marked match; when nothing is, what it lists for the first name is shown, as that search would. Searched against the live registry: github.com/Eszetael/postgres-mcp-hardened.git, ghcr.io/eszetael/postgres-mcp-hardened:0.1.10, github.com/upstash/context7, https://mcp.context7.com/mcp (featured Context7), npmjs.com/package/@playwright/mcp (featured Playwright), github.com/modelcontextprotocol/servers (the reference servers) and an endpoint of ai.getvda's each find theirs. crystaldba/postgres-mcp itself isn't in the registry (no name, repository or package of it in the 124 servers a "postgres" search pages through), so it shows the 12 others called postgres-mcp, and above them "None of these is at {q} — they're found by its name." (以下都不在 {q},是按它的名字找到的。; "Nothing listed is at {q}." 没有列出位于 {q} 的服务器。 when none) with "+ Add it yourself…" (+ 自己添加…): the market's answer carries custom (CustomAt) when the search is an address nothing found is at, and the button opens the existing new-server form filled in — named for the repository (postgres), an endpoint's URL as HTTP, npx -y / uvx for an npm or PyPI page. TestAddress, TestAddrScore and TestMarketFindsByAddress (a fake registry that searches names: by repository found through its owner's name, featured by endpoint, repository, package and a repository folder, registry by endpoint, by name with one to add by hand, words as before) and market-address.test.cjs (webkit, en and zh: the note over the name matches, the form named postgres with no command, an endpoint's URL in it, words that match nothing as before; fails on the old library.js) pass, as does market-have. go vet ./internal/..., builds (-tags nogui, GOOS=windows) and go test -tags nogui -count=1 ./internal/library/... ./internal/gui/... under a temp HOME pass. Not tried in the app. yetone 2026-10-04 07:52:35 +08:00
  • 359831951f agent: 重开 Codex 后生效 goes once Codex is reopened, whatever Codex itself writes to config.toml after, and a Codex daemon of another home isn't counted (#729, Sun1090 on Codex 0.160.0: the Codex row stayed "已接入 · 重开 Codex 后生效" after ChatGPT and magpie were both restarted) — Agent.Stale counted every process matching (^|/)codex( |$) that started over a second before config.toml's or magpie-models.json's mtime, and Codex writes config.toml itself (each project it is opened in gets a [projects."…"] trust_level, a notice seen, the model picked in its app; this Mac's has 10919 project tables), so a Codex reopened after magpie's change was stale again at its first such write, as was one that never missed anything; and the pattern also took the app-server daemons Codex 0.160 leaves running with ppid 1 for another CODEX_HOME (<home>/.codex/packages/app-server-daemon/…/codex app-server --managed-daemon, days old on this Mac). Stale now compares a copy's start with when magpie last changed what the agent reads at start: a digest of it (Codex: model_provider, model_catalog_json, openai_base_url, the [model_providers.magpie] table and magpie-models.json; Claude Code: env and model in settings.json) is kept in the stash with the files' latest write at the time it was first seen, so neither the agent's own writes nor magpie starting again move it; a magpie change (a new list, connecting) still gives a new time and the notice. A process whose command runs from another home's .codex (or .claude) dir is skipped. pgrep/ps sit behind a var (running), ps now giving etime and command. TestCodexStaleOnlyForMagpiesChange (ChatGPT's Codex reopened an hour ago and another home's daemon: 0; Codex trusting a project: 0; magpie's list changed: 1, and 1 again from a fresh Agent) fails on the old logic at both the daemon and Codex's own write, and passes. go vet ./internal/..., builds (darwin -tags nogui, GOOS=linux, GOOS=windows internal/agent) and go test -tags nogui ./internal/agent pass (its TestMain sandboxes HOME; the HOME override itself was refused here). Not tried in the reporter's app: a Codex already flagged clears once it is reopened after this build has read the digest, one more reopen at most after updating. yetone 2026-10-04 07:46:31 +08:00
  • d07b8a8d92 gui: an agent connected to magpie is never under 未设置, and one not connected goes there whatever its own files say (#726, EZN7L2C3 on the Agents page: Codex, 已接入 with its model on 上次的选择, sat under 未设置 below 收起, while Antigravity CLI and DeepSeek Harness, 未接入 with their switches off, stayed up top — "ones I've turned on go down there; the logic feels chaotic") — arrangeAgents folded what agentUsed called not set up, and agentUsed was "added, or any field with a value (not an effort or ultracode)" from cedc86db/48c496d0, before 「接入」's switch: an agent's fields are read from its own files, so one not connected (wired false) with a model of its own (agy, dsh) counted as set up, and a connected Codex starting on its last choice has no model value and counted as not. Now an agent the switch can connect (connectable) is set up when it is on magpie (onMagpie: wired, added, or drifted — one magpie set up that came apart stays up to be put right), and only then; one with no switch (Cursor, Copilot, an app with no magpie models) goes by what is set on it, as before. Folding starts once one is on magpie while any agent can be connected, so a Cursor on its own "auto" doesn't fold every agent away on a fresh magpie (agentsLead's welcome still says none is connected); with nothing to connect, once any is set, as before. Show on a hidden agent with a switch says it stays under Not set up "until it is connected" (zh 接入前,它会留在「未设置」里). agent-connected-unfolded.test.cjs (the report's rows: Claude Code, OpenCode, Codex on its last choice connected and in view, Cursor in view for its own model, Antigravity, DeepSeek Harness and Kimi Code folded under Not set up/未设置, every row in view switched on and every folded one off; with none connected, nothing folds) fails on the old code (agy and dsh up, codex folded) and passes. Fixtures that are on magpie models but said wired false (agy-launch, claude-direct, model-pick fillers) are wired now, as Agent.Wired would read them; agent-disconnect unrolls the fold for Claude Code, not connected; agent-tweak-fold checks ultracode moves no row with Claude Code connected, and the efforts alone on agents not connected. Webkit: those and show-unset-agent, panel-fold, agent-grip, agent-connect, agent-layout, agent-start-pick, agent-expand-steady, claude-picker-fold, subagent-effort pass; the full webkit suite's other failures (cli-update, routing-time, zcode-site, menu-scroll and others) fail the same on the old code. go vet ./internal/gui and the asset tests pass; the full go test ./internal/gui under a temp HOME was not run here (the sandbox refused the HOME override) — the change is JS only. Not tried in the reporter's app. yetone 2026-10-04 07:26:40 +08:00
  • c20a072026 agent: OpenCode is told a magpie model reasons when it thinks with no levels to pick, or has low, medium and high (#725, Sun1090: OpenCode's tooltip for mimo-v2.6-flash-free · opencode-zen said 推理: 不支持推理, where OpenCode's own Zen says it reasons) — the opencode block magpie writes into opencode.json never set reasoning, and OpenCode 1 (1.18.34, npm's latest) takes a model of a provider models.dev doesn't list (magpie) as reasoning only when the config says so (provider.ts: model.reasoning ?? false). models.dev lists Zen's mimo-v2.6-flash-free reasoning: true with no reasoning_options, so it has no levels, and magpieModels didn't hand agents the entry's Reasoning (the flag #402 added for /v1/models), so nothing could say it thinks. magpieModels now carries Reasoning, and OpenCode's block (and its WSL, mimocode and OpenChamber forms) sets "reasoning": true through openCodeReasons: a model that thinks with no levels, or one whose levels hold low, medium and high. OpenCode 1 adds low, medium and high of its own to a reasoning model's variants (ProviderTransform.variants, merged under the config's), checked under a sandbox HOME with 1.18.34's opencode models magpie --verbose: reasoning true with variants {} lists low, medium, high, and with low and max lists low, medium, high, max. So a model with other levels (deepseek-v4-flash-free's low, high, max) is left unmarked, as before, rather than offered levels it doesn't have; a "disabled" variant would hide them in 1 but OpenCode 2 (OpenChamber's) reads it as a variant of its own. A levelless thinker gets low, medium and high, as under OpenCode's own provider, and the gateway asks a model with a thinking switch alone no more than high (fitFor). OpenCode 2 doesn't read the flag. TestOpenCodeZenModelReasons (Zen's models from a models.dev cache: mimo-v2.6-flash-free and laguna-s-2.1-free marked, deepseek-v4-flash-free and ling-2.6-flash-free not) fails on the old code and passes. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried in the reporter's OpenCode app; OpenCode reads its config at start, so one already open shows it after it is reopened. yetone 2026-10-04 07:21:07 +08:00
  • da46cd21f1 provider: a Codex reset about to run out is spent half an hour before it does, not three hours, and at once only when the account is held up past then (#718, thedavidweng: account A, held up for its five hours until 17:09 with 32% of its week used, spent a reset expiring that afternoon at 15:20 — within resetExpiryLead (3h) and its windows used — losing the 68% of its week it could have used between 17:09 and the reset running out). Spending a reset starts the windows again and loses what they have left at that moment, while the fresh windows are the same whenever it is spent, so check now spends it as late as is safe: resetExpiryLead is 30 minutes; an account holding a reset that runs out is looked at every 30 minutes (resetExpiryWatch), never past the start of the reset's last hour, and every 5 minutes within it (resetExpiryClose, nextResetLook), a failed read (by the reset last read, kept per account) or spend there tried again in 5, not 30; the loop goes over the accounts every 5 minutes (each read only when due), and check compares wall-clock times (Round(0)), so a Mac asleep past the lead spends it on the first pass awake. It is spent at once when the account is held up now (usedUp: a window that stops it at 100%) and won't be free again (BackAt; not known counts as not) before the reset would be spent: waiting can use nothing more, and spending it frees the account — any held reset that runs out, however far off. Windows not used still spend nothing; the week-used-up AutoUseCodexReset path is as before. expiringResetSpent takes whether the account is stopped and when it is back, and routing's Allowance.Restarts passes the same from its limits (heldUp), so Weekly pace's and Smart's deadline (dueRestart) is when check spends it. The GUI's tooltips (en, zh: 到期前约半小时, 若账号正被限流、要到那之后才解封,则立即用掉), quota auto-reset's help and settings' comment say so. TestResetSpentAsLateAsSafe walks the report's afternoon in 5-minute passes (reset running out 18:15: five hours free at 17:09 or 17:40 → spent 17:45; free at 17:50, after it runs out, not saying when, or the week used up past it → spent 15:20; not held up → 17:45; nothing used → never; at most 20 reads), TestResetExpiryLastHour (look cadence, a failed read and spend retried in 5 minutes, spent after sleeping past the lead) and TestRestartsWhenHeldUp fail on the old code; the existing tables move from 3h to the new times. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass (TestDevinSignedInOnlyInMagpie failed once under load, passes alone and in the package rerun); codex-auto-reset and routing-reset-deadline pass in webkit. Not tried with the reporter's accounts. The spend reads the account again under autoReset's lock (stillExpiring, spendExpiringNow) and spends nothing if the week's used-up spend has started its windows again since the look, so a held-up account isn't given two resets; TestExpiringSpendReadAgain. yetone 2026-10-04 07:01:48 +08:00
  • 9f94b2156b copilot: an account on GitHub Enterprise Cloud with data residency (*.ghe.com) signs in, is read from copilot login --host and is served at its enterprise (#723, guitaoliu: magpie skipped a Copilot CLI sign-in whose host wasn't github.com and its own sign-in was fixed to github.com) — a Copilot account now remembers its host (copilotApp.Host, "host" in its logins.json auth; none for github.com) and every URL it uses follows it, as VS Code's Copilot Chat, copilot.lua and avante do (checked against the issue's references and GitHub's docs): the device flow at https://<name>.ghe.com/login/device/code and /login/oauth/access_token with the same Copilot OAuth app, the device page there, whose account at https://api.<name>.ghe.com/user, its plan and usage at api.<name>.ghe.com/copilot_internal/user, its session token at api.<name>.ghe.com/copilot_internal/v2/token, and its requests, model list, Auto and terms at the session's endpoints.api, or https://copilot-api.<name>.ghe.com when it names none (the provider's base too). A github.com account has no host and goes where it always did, the same vars and the same bytes. CopilotHost (copilot_ghe.go) takes only github.com or one <name>.ghe.com label (a scheme and a trailing slash are dropped; a port, path, user, deeper subdomain or any other host is refused), in the sign-in, the CLI and a host read back from logins.json, so a GitHub token is never sent to a host of the user's choosing. The Copilot CLI's sign-in: a github.com account first, as before; then one copilot login --host <name>.ghe.com signed in, its token under "https://<name>.ghe.com:<login>" in copilotTokens or the keychain; a GitHub Enterprise Server host is still left out. An editor's apps.json entry keyed "<name>.ghe.com:…" is read after the editors' github.com one and the CLI's. GUI: a Copilot device code's panel has "Account on GHE.com?" (zh 账号在 GHE.com 上?), which ends that sign-in and asks "Your enterprise on GHE.com" (你的企业在 GHE.com 上的地址) in a field (acme.ghe.com; Sign in stays off until it reads as <name>.ghe.com), then signs in there; that sign-in's panel offers "Sign in on github.com instead" (改为在 github.com 登录), as does a failed one. CLI: magpie accounts add copilot [--host <name>.ghe.com]. TestCopilotHost, TestCopilotGHESignIn (an httptest server standing in for acme.ghe.com, api.acme.ghe.com and copilot-api.acme.ghe.com through copilotGHEOrigin: sign-in, what is kept, models, a signed request, usage, nothing sent to github.com, a bad host refused) and TestCopilotCLIGHELogin are new and fail on the old code; copilot-ghe.test.cjs (webkit and chromium, en and zh: the link, the field refusing evil.com, a.b.ghe.com, ports and the like, the posted site, the way back, cancels, no native select, no left-border accent) fails on the old code. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass; signin-link, signin-close, signin-again and routing-signin pass in webkit, signin-callback fails before and after the change alike. Not tried with a real GHE.com account: the URLs follow the references, and the CLI's token key for a ghe.com host is taken to be the github.com one's form. Accounts are still told apart by login alone; an EMU login carries its enterprise's suffix. yetone 2026-10-04 06:53:03 +08:00
  • f980ae31b5 qoder: moved accounts update to the Qoder plugin 0.2.5, whose models' context is the largest window Qoder offers them (#722, Moody-Sin: qoder 的 qwen 3.8 flash 只有180K上下文) — the plugin took a model's context from the listing's max_input_tokens (180000 on Qwen3.8-Flash, Qwen3.8-Max, Kimi-K3, GLM-5.3, MiniMax-M3, Sonus, Cantus), what a request naming no window gets; 0.2.5 reads the windows a model offers from context_config as Qoder's client does (200K default, 400K, 1M on qfmodel in the owner's real listing), reports the largest, and sends context_length = the default window while a request fits it, a larger one only when the conversation needs it. Against the owner's real account through the plugin's fetch: qfmodel, Qwen3.8-Max and performance answered with context_length 200000/200000/272000, and 400000/1000000 when the request was counted larger. The mover's min goes to 0.2.5 so keepMovedCurrent updates every moved user. yetone 2026-10-04 06:56:04 +08:00
  • 5c1160a8cc library: each MCP server in the Library shows whether it works — a dot and a few words beside its name (12 tools, needs sign-in, can't start: uvx not found, exited (1), no answer, connection refused, the server's HTTP status), the whole reason (the server's last stderr line, the status line) in the tooltip; checked once a session when the MCP list opens, and again by clicking it (Discord, lc: a status display for MCP servers in the Library page) — POST /api/library/mcp/check {names, fresh} runs library.CheckServers: up to four at once, 15 s each, by really connecting. A stdio server is started with its command, args and env (proc.Command + StartTree, so the whole tree is ended after, Windows too), sent initialize (2025-06-18, clientInfo magpie), notifications/initialized and tools/list over newline JSON; a missing command is "not found", an exit before the reply gives its code and last stderr line. A Streamable HTTP server is POSTed the same with Accept: application/json, text/event-stream, a JSON or SSE reply read, Mcp-Session-Id and the negotiated MCP-Protocol-Version carried, magpie's own sign-in token sent when it has one and the session DELETEd after; 401/403 is "needs sign-in" (OAuth when WWW-Authenticate says so). A legacy SSE server is read through its endpoint event. A server without tools/list (-32601) is fine with 0 tools. Results are kept in memory for the session, keyed on the server's definition and sign-in state, so an edited server is checked again; nothing is written to any agent's config. The GUI keeps a status per server name, paints it on the row (a grid in .who so .who > .name stays as other tests read it), a checking state pulses, the click stops propagation so it neither opens the server nor scrolls, and the strings are in i18n.js (zh). TestCheckStdio (ok with 3 tools after a junk stdout line and a notification, exit 3 with its stderr, not found, timeout ending the server and its child), TestCheckHTTP (JSON and SSE replies with the session and token carried, OAuth 401, key 403, 404, broken JSON, timeout, refused), TestCheckSSE and TestCheckServersOnce (cached, fresh, re-run on a changed env, no file under HOME changed) use a fake server re-executed from the test binary and httptest, -race; library-mcp-health.test.cjs (en and zh: one check for all servers on opening, texts, tooltips, dot not border, row height, no re-check on returning, a click re-checks only that server without opening it or moving the page, no <select>) fails on the old library.js and passes in webkit with the other Library tests. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried against real MCP servers from a user's config. yetone 2026-10-04 06:40:02 +08:00
  • 8ebf7b0df3 gateway: list routing groups to Claude Desktop under their own names (#724) Haogao Gu 2026-10-03 18:43:52 -04:00
  • 0df1798b7d gui: a Codex account's Auto-use of its banked resets is a standing setting on its card, held resets or not, and says both times it uses one (#719, thedavidweng: the Usage card's Auto-use only showed with resets held, said only "when the week is used up" while the use before a reset expires (#624) was in the tooltip alone, was hidden in the multi-account brief rows, and zh called both the window's 额度重置 and the credit 重置) — every named Codex (ChatGPT) account's full card, on the Usage page and in the menu bar panel, now has a row of its own under its windows: "Auto-use resets: when the week runs out, or before one expires" (zh 「自动使用重置卡: 周额度用完时 · 重置卡到期前」) with a switch (lib-switch, role=switch, aria-checked) that posts settings/codex-auto-reset as the old toggle did, so codexAutoReset in settings keeps every existing user's on/off; it shows with 0 resets, so it can be set ahead of one. Holding resets, the row of them keeps "Use a reset" / "Use one…" and says after the nearest expiry whether that one is protected: "· auto-used before it expires" (zh 「· 到期前自动使用」, accent) or "· not auto-used" (「· 不会自动使用」), its tooltip saying how (about 3 hours before, if the account's windows have been used) or that it is lost unless used by hand. An account in brief says it beside its name: "↺ 2 · Auto-use: on" / "Auto-use: off" (zh 「自动用卡:开/关」), its full say in the title. The switch's tooltips name both triggers, one a week at most and the five hours never using one; the status after turning it on is "{who} uses a reset by itself when its week runs out, or before one expires". zh: the credit is 重置卡 throughout (1 张重置卡, 用一张重置卡, 用一张…, 用一张 Codex 重置卡?, the routing notes, outcomes and the GLM team's 5 小时/每周重置卡), the window's renewal stays 重置. A GLM team's resets, a plugin's and an account with no name get no switch, as before. magpie quota auto-reset's help and its on/status lines say the expiry use too. codex-auto-reset.test.cjs is rewritten (window and panel, en and zh: the say's visible words with both triggers, the switch for an account holding no reset, the quick switch with resets and the kept/not kept words turning, the brief rows' off/on and resets held, posts per account, no scroll, no left border, no native select, every string's zh, the credit 张/重置卡 in zh) and fails on the old code; routing-auto-reset and zcode-site expect the new zh words; quota-curve expects the Auto-use row between the curve and the next account. go vet, builds (darwin, GOOS=linux -tags nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass; codex-auto-reset, routing-auto-reset, quota-curve, usage-fold, click-scroll, account-mask, agent-models and routing-served pass in webkit; the rest of internal/gui/tests fails only where a clean checkout fails too, or passes alone; zcode-site and old-webkit fail before and after the change the same way (zcode-site times out at its sign-in step before reaching the reset words). Not tried with real Codex accounts. yetone 2026-10-04 06:33:21 +08:00
  • 6f13697ba2 usage: a request's details on the Usage page say what a call that isn't a turn of the conversation was for — Called for: Subagent, Title, Approval check… (#714, Moody-Sin: ChatGPT desktop on gemini-3.8-flash interleaved gpt-5.6-terra calls on a thread of their own, and opening one in Requests showed its endpoint and session but nothing of why it was made) — the request log has kept the kind Codex names (x-openai-subagent, its turn metadata's thread_source) all along, and only the Routing page's trace showed it; ledDetail now adds it under Session ID, named as the trace names it (routing.js's kindName, now on window). usage-ledger-detail.test.cjs gives the answered row kind collab_spawn and expects Called for · Subagent (en) / 用途 · 子代理 (zh) after Endpoint; it fails on the old assets. yetone 2026-10-04 06:34:06 +08:00
  • bc816db2d0 routing: Weekly pace and Smart put first a Codex account whose allowance an auto-used reset is about to wipe (#717, #718, thedavidweng: with resets' Auto-use on, account A — 80% of its week left, renewing in five days, a reset running out in four hours — went behind B with 50% left renewing in two, and an hour later the reset spent before it ran out started A's windows again, its 80% lost) — since #624 a reset about to run out is spent by itself resetExpiryLead (3h) before it does, when the account's windows have been used, so for such an account that moment is when what it has left is lost, as at its own reset. The time is now one helper, expiringResetSpent (codex_expiring.go), which the spending (check) and routing both go by, so the lead isn't kept twice; resetRunsOut says whether it counts, mirroring check: auto-use on for the account (AutoResets, Codex only), a reset held (Count > 0) that runs out, and its windows used (windowsUsed, on-demand aside). Allowances and lastAllowances mark each window of such an account with the reset's expiry (Limit.ResetRunsOut); Allowance.Restarts is when it is spent. Allowance.Pace takes the hours until then in place of the hours until the window renews when that is sooner (a five hours alone too), and due is then that time; Allowance.Renewal, which Smart ranks by, takes it for each window's renewal when sooner. Auto-use off, no reset, a reset that runs out after the week renews or has run out: nothing changes. The 90%/98% tiers, the learning-first rule, the pace bands and session affinity are untouched. The trace gains dueBy "reset" (pace's due came from the reset) and restarts (Smart's first renewal is the reset); the Routing page's story says so — "Weekly pace: {who} has the most remaining allowance per hour until one of its Codex resets about to run out is used by itself — …" and Smart's "its allowance starts again soonest — one of its Codex resets about to run out is used by itself in {d} …", with zh (the GUI has no ja strings); its cmpRenews goes by the same renewals. TestPaceUntilAutoUsedReset (with/without a reset, week sooner, within the lead, run out, five hours alone), TestResetRunsOutForRouting (auto-use off/on, none held, count none, never runs out, windows unused, on-demand only, another vendor, and through Allowances) and the gateway's TestResetSpentIsTheDeadline (pace and Smart: the issue's case, no reset, week sooner, the 90% and 98% tiers kept, both holding one, the trace) fail with Restarts returning zero and pass; TestResetSpentBeforeItRunsOut passes unchanged on the refactored check. routing-reset-deadline.test.cjs (en, zh: pace and Smart told by the reset, and by the week as before) fails on the old routing.js and passes in webkit; the other routing GUI tests pass, except routing-manual and routing-time, which fail the same on the old assets. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried with real accounts holding a reset about to run out. yetone 2026-10-04 06:21:17 +08:00
  • 9db0d155bc cli: magpie quota wait <provider|account> blocks until that subscription (any of its accounts) or that account has allowance again, then exits 0 (#720, thedavidweng: a long Codex task stopped when every account ran out, and nobody came back to it hours later; step 1 of the maintainer's plan, for until codex exec …; do magpie quota wait codex || break; done) — it reads the vendors itself as magpie quota does, gateway running or not (provider.Allotted: the subscriptions' and plans' readings, no key balances; AskUsage has each look wait for a new reading, and only a wait for Claude runs Claude Code's /usage, through AskClaudeUsage, which now calls it). An account has allowance when routing's usedUp says so (no window that stops it, every model, at 100%; exported as provider.UsedUp), and BackAt is when the last of those windows starts again. While all are used up it sleeps to the soonest reset plus 30s, at least a minute and at most ten, saying on stderr what it waits for and until when; an allowance it can't read (an error, or only the last reading kept in its place) is asked again after 1, 2, 4, 8 then every 10 minutes. A provider's accounts switched off or signed out in magpie are left out, as the agent is never moved to them; an account is named by its user, or <provider>/<user> when two subscriptions share it. --timeout <duration> exits 1 when it passes first, --quiet says nothing, an unknown name or flag exits 2 (main now honours an exitError's code) and Ctrl+C 130. The loop takes its reading, clock and sleep as functions: quota_wait_test.go runs it on a fake clock (already available → no sleep; used up → 10m, 10m, then to just past the reset; one account named ignores another's room; a reset under a minute away is a minute; --timeout ends at the deadline after its last look; unreadable usage backs off and the wait goes on once read; an untold reset is looked at every 10 minutes; an off account is passed over; Ctrl+C), plus resolving names and the usage errors. Help in magpie quota's and magpie's usage, and the README. go vet, builds (darwin, GOOS=linux, GOOS=windows), the new tests and ./internal/provider pass; not run against real accounts. yetone 2026-10-04 06:12:16 +08:00
  • 18773d56eb gui: text cut short with an ellipsis shows its full text when hovered (#721, thedavidweng: the Agents page cut an agent's status line to "已接入 · 在 /model 里切换 opus · son…" and hovering it showed nothing more) — one mouseover listener on document finds the hovered element, or its nearest ancestor within four levels, whose computed text-overflow is ellipsis; when its scrollWidth is past its clientWidth it gets a title of its text (trimmed, whitespace collapsed), and once it fits again that title goes. Which lines are cut depends on the window's width, so it is measured on each hover rather than at render. A title the app wrote is never touched: data-full-tip keeps the text the helper put there, and a title that no longer reads the same is the app's. truncate-title.test.cjs puts a cut line, a line that fits and a cut line with its own title in a 160px column on the loaded app, and checks hovering gives the full text, no title and the app's title, and that widening the column takes the helper's title away; the first fails on the old code. The whole internal/gui/tests suite under WebKit fails the same 35 tests with and without the change, a few timing-dependent ones trading places run to run (agent-expand-steady, remote-magpie, remote-group-served pass when run alone); go vet ./internal/gui/... and go build -tags nogui ./... pass. Not tried in the Wails window itself. yetone 2026-10-04 06:10:45 +08:00
  • b66f128d2b library: a greyed-out agent chip says why when it is clicked (蓝猫 on Discord: the MCP server's Claude Desktop chip was grey and clicking it did nothing) — a chip whose agent can't be given the item (a remote server for Claude Desktop, which runs only a command from its settings; an SSE server for an agent without SSE) or has it whatever is ticked was a disabled button: its click went nowhere, and WebKit shows a disabled button no tooltip, so the reason in its title was never seen. It is now aria-disabled instead: a click puts the reason in the status line (for Claude Desktop, add a remote server in its Connectors instead) and changes nothing; All, the toast after All and morphChips read aria-disabled where they read disabled, and the pressed look is left out for it. library-all.test.cjs gains the case (Claude Desktop's chip for an HTTP server and Codex's for an SSE one, en and zh: not disabled, the click's toast, nothing posted, the page not scrolled, All passing over it) and fails on the old code; it, library-items, library-toggle-status, library-desktop-skills, mcp-signin, library-own-folder, by-agent, library-row-open-scroll, click-scroll, library-loading and library-rtk-path pass in webkit. yetone 2026-10-04 05:23:43 +08:00
  • dec9e70660 修正 Claude 登录失效后的账号判断与提示 (#716) Haogao Gu 2026-10-03 17:16:43 -04:00
  • 03421f9f42 agent: Claude Desktop's row picks which magpie models Desktop's own model menu lists, several at once, and the menu names them by the model's own name (蓝猫 on Discord: wanted to pick a few models and switch among them in Desktop, and Desktop showed "DeepSeek V4.1 Flash (opencode-go)" where they want "DeepSeek V4.1 Flash") — Desktop already read its menu from the gateway's /v1/models for claude-desktop (desktopModels over provider.CatalogFor, i.e. every visible model minus the agent's HiddenModels, each under a claudeLooking alias), but its row offered no way to pick them: the start button stood the opus tier there as 「上次的选择」, and display_name was "Name (provider/model)" with that id again in description. Now Desktop's row (MENU_FROM_LIST in app.js) puts a model button where another agent's start pick stands: it names the first two picked models (+N, or "No models"), its title says Desktop reads the list as it starts so it must be quit and reopened after a change, and it opens the existing per-agent model list (openAgentModels, the am-pop multi-select posting {hidden} to /api/agent-models/claude-desktop), which stays open while several are ticked; the button, the line under the name ("Connected · n models in Claude Desktop's model menu") and, in the tray panel, the row's summary are refilled on each save instead of the row being redrawn; the panel's opened row has the same button under the provider, the expand link says Details, and the opened row's "In Claude Desktop" says the menu lists the models picked here and that Desktop reads it as it starts. The provider switch (default vs magpie) and the tiers are as before. /api/state's model count carries the first three shown models' names (modelCountJSON.Names). desktopNames gives each model its own name and keeps a suffix only where two picked models share one, case-insensitively: the provider id, or the whole id if that still collides; the description is gone. Models added later still show by default, as for every agent's list. zh strings added (no ja). TestClaudeDesktopPickedModels (two providers' DeepSeek V4.1 Flash read "(opencode-go)"/"(relay)" while both are listed, and after relay's, glm-5 and the routing groups are hidden Desktop lists exactly the three left, the DeepSeek plain, while another agent still lists all) and TestDesktopNames are new; the effort and tier tests map Desktop's rows by DesktopCatalogID instead of the description. internal/gui/tests/desktop-model-menu.test.cjs (window and panel, en and zh: the button's names and title, no 「上次的选择」, the list's ticks, unticking GLM-5 posts the whole hidden list and updates button, line and summary while the list stays open, ticking Kimi K3 back, no scroll, no native select) passes in webkit, as do desktop-tiers, agent-models, agent-connect, agent-start-pick, agent-layout, agent-grip, click-scroll, agent-models-all-hidden, agent-model-groups, agent-models-loading, agent-expand-steady, agent-disconnect, agent-tweak-fold, show-unset-agent, tray-cell-click, library-desktop-skills and by-agent. go vet ./internal/..., builds (darwin, GOOS=linux -tags nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried in real Claude Desktop: that its menu shows the plain names and only the picked models after a restart is from the /v1/models it is served, not seen in the app. yetone 2026-10-04 05:00:35 +08:00
  • 23c26c2dba 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). yetone 2026-10-04 04:17:39 +08:00
  • 858281521c fix(gui): use provider model names for web searchers (#715) 2026-10-04 04:04:06 +08:00
  • 0c7e6d834d provider: Trae CN's daily check-in (每日签到) is pressed once a Beijing day for each Trae CN account, as WorkBuddy's is (#694, Hu9956: TRAE cn也有每天签到送100积分活动,这个会考虑支持吗) — Trae CN is served only by its community plugin (trae-cn, no built-in, no mover), so magpie asks Trae through the plugin's fetch, which from trae 0.1.4 (magpie-community/plugins, branch trae-checkin) sends api.trae.cn's /trae/api/ pages as the account (Cloud-IDE-JWT renewed when near its end, its device id): POST /trae/api/v2/ug/checkin_credits/status with {}, and /claim with {} only while enable is set and checked_in isn't. These are the endpoints autumnsentiment/Trae2api-cn (the relay the plugin's protocol came from) and qilimixingkong/trae-checkin both call; an unauthenticated POST to /status answers 200 {code:1001, enable:false, checked_in:false} and a made-up sibling path 404s. Answers: not enabled → inactive, checked_in → done with its credits, a claim → claimed +credits(+extra_credits), 9095 (this device is in today) → not eligible for the day (no other device id is sent to get round it, as qilimixingkong's does), 1001 or the plugin's X-Magpie-Sign-In: expired → failed, sign in again; 9074 and anything else → failed and tried again; a plugin before 0.1.4 (400, not JSON) → failed, update it. The day loop and its file are WorkBuddy's, made generic: checkin.go's checkiner (accounts as checkinAcct{key, do}) holds checkinNow, which wbCheckiner now wraps; keepCheckedIn and wbCheckinLoop.tick take a checkinRunner and a log label; withCheckins goes through markCheckins over checkinCards. Trae's is kept in trae-checkin.json by trae-cn|uid; settings.TraeCheckin (traeCheckin) is its own switch; the gateway runs KeepTraeCheckedIn beside KeepWorkBuddyCheckedIn; WithCheckins marks Trae CN cards too, with SubscriptionQuota.CheckinBy and WorkBuddyCheckin.By "trae" (never kept), so the Usage card's row uses POST /api/settings/trae-checkin and /api/usage/trae-checkin and its first Trae card holds Trae's own Auto check-in and Check in now; Settings' Usage part gains a Trae CN tab (traeList) shown while an account is signed in or the switch is on; magpie accounts checkin and the TUI's c check Trae CN accounts in too (the footer says daily check-in). Tests: trae_checkin_test.go (once a day: status then claim, nothing sent again that day, done the next; inactive, 9095 held for the day, 9074 retried, expired, an old plugin; Trae cards marked, another plugin's not), TestTUIChecksTraeIn, usage-checkin.test.cjs's Trae CN card (its own switch and press, WorkBuddy's untouched) and settings-groups.test.cjs's Trae CN tab; the WorkBuddy check-in tests pass unchanged. go vet ./internal/..., builds (darwin, GOOS=linux -tags nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass; the GUI tests pass on WebKit (Playwright's Chromium here fails to start, icudtl.dat missing, for the old tests too). Not tried with a real Trae CN account: none is signed in to the owner's magpie (no trae-cn in plugin-auth.json), so the authenticated status and claim answers' fields are as both open-source clients read them, not seen here. yetone 2026-10-04 03:53:39 +08:00
  • 8323b0ae87 agent: OpenCode and OpenChamber list a magpie model's reasoning levels weakest first, not alphabetically (#713, Hu9956 on v0.1.780: 思考 offered 默认, High, Low, Max, Medium, Ultra, Xhigh, where other providers in the same OpenCode go by strength and MiniMax Code had magpie's in order) — openCodeVariants returned a map[string]any, which encoding/json writes with its keys sorted, so each model's variants object in opencode.json came out high, low, max, medium, ultra, xhigh, and OpenCode builds its variant list from the object in key order (checked with OpenCode 1.18.34: opencode models --verbose under a sandbox HOME/XDG dirs keeps a config's variants in the file's order, alphabetical or by strength; it doesn't sort them itself). openCodeVariants now returns an orderedJSON (a key/value list whose MarshalJSON writes keys as given) of the model's efforts put weakest first by gateway.ByStrength, the gateway's effortRank order (none, minimal, low, medium, high, xhigh, max, ultra; a level it doesn't know after them, stably), so a level appended after max (ultra) lands last too; edit.SetJSON keeps the custom marshaller's order when it indents the block. An opencode.json an older magpie wrote would have stayed alphabetical, as Sync's sameJSON compares decoded values and took it for the same block: OpenCode's (and its WSL and mimocode forms', through openCodeLike) and OpenChamber's Sync now use syncJSONInOrder, which also rewrites the block when its tokens, keys included, don't read in the same order as magpie's (sameOrder), and leaves one in order alone; the other agents' syncs are as before. TestOpenCodeVariants now checks the marshalled bytes (weakest first, an out-of-order list sorted, none as {}), and TestOpenCodeVariantsInOrder writes opencode.json under a temp HOME and checks the raw file lists low, medium, high, xhigh, max, ultra, that a block in the old alphabetical order is put in order by Sync and that a second Sync leaves the file byte for byte; both fail on the old code, and the Sync half fails with plain syncJSON. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried in OpenCode's or OpenChamber's own picker with the reporter's setup; OpenCode reads its config at start, so one already open shows the new order after it is reopened. yetone 2026-10-04 03:18:10 +08:00
  • c8278a8691 docs: plugins are OpenCode's provider plugins or pi's packages — the Plugins page's intro, magpie plugin's help, the README and the plugin docs (en, zh, ja) said OpenCode only, though pi packages that register a provider run since v0.1.772; the docs gain a pi packages section (what makes a package pi's, what is loaded and left alone, sign-in through onPrompt/onAuth and ctx.ui, pi's files under <config>/pi or PI_CODING_AGENT_DIR, Anthropic Messages requests), the pi-package search and a FAQ entry yetone 2026-10-04 03:09:54 +08:00
  • a11b74883e group: a routing group tells agents its largest member's context window, not its shortest's (#712, Moody-Sin on v0.1.779: Pi on group/deepseek showed 82.4%/200k, where the group's DeepSeek V4.1 Flash members hold 1M) — groupEntries took the smallest window any member had (since 09c635cf/c83e03d5, "otherwise its shortest model's"), and the group 腾讯DeepSeek has OpenCode Zen's deepseek-v4-flash-free among its ten members, which models.dev gives 200000 (the rest: deepseek-v4.1-flash 1000000 at WorkBuddy and OpenCode Go, muse-spark-1.3-contributor 1048576), so /v1/models, Pi's models.json and every agent's config said 200k and Pi compacted there. No commit changed this code lately; what brought the window down is that member: added to the group, or its figure on models.dev — which of the two is not known. The shortest made sense while a too-long conversation was never sent past the member it overflowed; since b5cfca54 (v0.1.771, #700) an overflow goes on to the members left not known to be too small for it (withRoom), and the agent is told to compact only when none has room, so the group's window is its largest member's. A context set on the group (magpie group set … context=) still wins, below the members' too; a length rule (ruledEntry) never lifts past the largest, so its comment says so. The CLI's and TUI's help say "largest model's". TestGroupToldItsLargestWindow (the reporter's members, one window unknown: old code 200000, new 1048576) and TestContextSetByUser (largest by default, a lower set one wins) fail on the old code and pass; TestRuleImages now expects the group's 1M. 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 accounts: a conversation past 200k reaching the free member first costs it one refused request before the next member answers. The model tooltip in the Gateway page says so too: "Context: n tokens (the largest of its models')" / 上下文(组内模型中最大的), where it said the least. yetone 2026-10-04 02:43:55 +08:00
  • 22b1ed5b7a docs: document headless NAS deployment (#710) Yibo Xi 2026-10-04 02:37:42 +08:00
  • be832ff01b gui: pi-default-note's agents are connected, so their row pickers list every choice — #709's test was written against the row before 「接入」, where the model field stood in the row; now the model a connected agent starts on is picked beside its switch, and Default there says Pi picks the model itself, as before yetone 2026-10-04 02:14:54 +08:00
  • df27f0882a agent: the 接入 switch and a model picked in the row, both (owner: 对于那种想在 magpie 中一键设置 Agent 模型的用户能否也兼容一下,鱼和熊掌都要兼得); a row opens in place and slides as on iOS (owner: 展开的时候,上/下 padding 变了,导致抖动;展开和折叠都没有 iOS 那种丝滑优雅精致的动画) yetone 2026-10-04 01:14:12 +08:00
  • f3f055001b agent: a disconnect leaves the user's own Claude effort, and a YAML table emptied by one is filled in block style when switched on again — found on the omarchy VM (Linux): with the connection made by a magpie before this branch (no <id>.connect.was), disconnecting Claude Code took magpie's CLAUDE_CODE_EFFORT_LEVEL max out of the env and with it the effortLevel xhigh under it, the user's own, so Claude Code came back on its default; and Hermes's providers: {} / model: {} and omp's modelRoles: {} left by a disconnect were filled on one line (providers: {magpie: {...}}) by the next connect. Claude's Unwire now takes the env's level away alone when it is the effort magpie applied; setYAML writes into an empty flow table in block style (a flow table that holds something stays as it is). TestClaudeDisconnectKeepsTheUsersEffort and TestSetYAMLFillsAnEmptyTableInBlocks fail on the old code and pass. On the VM in a sandbox HOME (magpie web, nogui, linux/amd64): claude, codex, gemini, opencode, pi, crush, omp, hermes, muse and grok connect, disconnect and connect again twice with the right state, every disconnect preview line is in the real change, a round trip leaves Claude's effortLevel, Hermes and omp as they were; all ten CLIs answer PONG through it (opencode, pi and omp on DeepSeek: the VM's Zhipu and MiniMax keys are refused upstream). go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. yetone 2026-10-03 23:02:47 +08:00
  • 42a2b1113a agent: switched off and on again, an agent is back on the magpie models it was on, and Goose keeps its own model through a round trip — found running connect/disconnect twice for every agent against real configs in a sandbox HOME: turning one on again picked magpie's default instead of what the user had picked (OpenCode's model and small_model, Codex's model and effort, Pi, dsh, Hermes, Grok), and Goose lost GOOSE_PROVIDER/GOOSE_MODEL (its field clears both keys; on main too). Disconnect now stashes what it took off (<id>.reconnect: the values, which were routed through magpie, and what it left), and Connect puts them back when the agent is still as Disconnect left it — models on magpie first, so Gemini's model signs it in before its provider, and a value the model brought along (Codex's effort) is recorded as magpie's; changed since, Connect picks as before. Connect stashes what the agent was on (<id>.connect.was), and Disconnect sets back a field it left empty when that was the user's own (Goose's anthropic/claude-opus-5-5). TestReconnectKeepsTheLastPick, TestGooseRoundTripKeepsItsOwnModel and the new reconnect check in TestGeminiConnectRoundTrip fail on the old code and pass. Sandbox matrix: all 20 agents with a switch connect, disconnect and reconnect twice with the right state, every disconnect preview line is in the real change; 13 CLIs (claude, codex, gemini, opencode, pi, goose, crush, droid, grok, cline, omp, agy, commandcode) answer through the sandbox magpie. yetone 2026-10-03 22:27:17 +08:00
  • 661d27b560 agent: the disconnect preview right on Windows, and the Codex panel says what connecting does (owner: 去 Windows 测试机上测一下 / 是不是需要界面上解释一下现在的逻辑) — on the Windows test box Claude Code's and OpenCode's disconnect previews were empty though both were connected. Claude: the dry run's temporary home has no ~/.codex/auth.json, so a codex/… model wasn't magpie's there; the magpie asking now writes the providers it has on into that home and the dry run takes their models as magpie's (TestDryRunKnowsTheAskersProviders). OpenCode: magpie/codex/gpt-6.1-sol, a model its copy of magpie's list didn't have yet, read as not connected and Disconnect left it on magpie; a magpie/… value whose model magpie routes is magpie's (TestOpenCodeOnAModelNotInItsList). The preview pairs a line going back only with one as deep (Claude's model, not a modelPicker row) and doesn't mask …_TOKENS counts. The Codex panel, joined: 「重开 Codex 后,/model 里自己的模型照旧,magpie 的模型也在里面。」, and 新会话默认 says 「和 Codex 的 /model 是同一个选择,在哪边改都行」 rather than 「不设就用 Codex 自己上次的选择」 beside a value (/api/state: joined). On Windows (sandbox USERPROFILE/APPDATA, real accounts): previews for Claude, OpenCode, Codex and Gemini; disconnecting all four restored them (Claude back to opus, Gemini to Google sign-in, Codex keeps its [model_providers.magpie] by design), real ~/.claude untouched; real Claude Code requests via yylx/claude-opus-5-5 and codex/gpt-5.5 answered. The new tests fail on the old code. go vet, builds (darwin, GOOS=linux, GOOS=windows), go test -tags nogui -count=1 ./... under a temp HOME and the 34 agent GUI tests pass but cli-update, menu-scroll and old-webkit, which fail on main too. yetone 2026-10-03 20:42:30 +08:00
  • c47fee0b26 agents: Codex keeps its own last pick when connected, and every connected line starts 「已接入 ·」 (owner: 让 Codex 记住上次的选择,要统一) yetone 2026-10-03 19:40:49 +08:00
  • d0d098c69f agents: the 「接入」 boards filled in — the opened row as designed, real accounts tried yetone 2026-10-03 18:55:15 +08:00
  • 4c7ab667e5 agents: Claude Code's /model pick holds for new sessions (owner: 去掉 ANTHROPIC_MODEL,让 /model 的选择对新会话也生效) — magpie wrote the main model as env.ANTHROPIC_MODEL too, which outranks settings.json's model, so a pick in /model lasted one session. The main model is now settings.json's model alone (an alias opus/sonnet/haiku/fable reads as its tier's model); magpie remembers the one it wrote (claude.main), and the tiers and subagents equal to it follow it. Agent.Follow, run when the GUI builds its state, moves the followers onto an in-agent pick and takes an older magpie's ANTHROPIC_MODEL out; it writes nothing with nothing to follow. Subagents that follow leave CLAUDE_CODE_SUBAGENT_MODEL unset and run on the session's model. TestClaudeModelPickedInClaudeCode. Real Claude Code 2.1.288 in a sandbox: a legacy settings.json lost ANTHROPIC_MODEL on the GUI's refresh, /model → m-beta answered from m-beta, a new session started on m-beta, and the tiers followed it on the next refresh. go vet, GOOS=linux/windows builds, go test -tags nogui ./... under a temp HOME pass. yetone 2026-10-03 11:25:15 +08:00
  • 1b02092718 gui: the Agents view leaves holding the reader to the page, not WebKit's scroll anchoring — with the connect line under each name, a pick redrew the rows and WebKit anchored the list 4–8px down for a frame before the page put it back (claude-direct.test.cjs, under load). Agent GUI tests: no failure on the branch that base lacks, webkit and chromium. yetone 2026-10-03 03:58:53 +08:00
  • 90d884ee60 agents: a switch connects an agent to magpie, and its models are picked in the agent (prototype, the owner: 在 agent 页面不应该是为 agent 选择某个模型,而是应该有个「接入」的开关) — agent.Connect sets the agent's main field to one of magpie's models (the one serving the model it is on now, else the first) or to magpie itself for an app whose one setting is magpie as its provider; that writes the provider and the whole catalog, so the agent's own list has every model. POST /api/agents/connect/{id}. Each row with magpie models to offer gets a switch and a line under its name (已接入 · 在 Codex 里用 /model 选 magpie 的模型 / 未接入 · Codex 用它自己的设置); off goes through the existing Disconnect dialog. Claude Code's /model lists only its tiers, so while routed magpie also writes modelPicker (2.1.287) with every magpie model, taken out on disconnect; one the user wrote is left alone. Checked in a sandbox HOME against a fake upstream: Codex 0.160 /model lists and uses magpie's models, OpenCode models, Pi --list-models, Crush's providers, Claude Code /model after connect; disconnect puts Codex back on gpt-5.4/high. yetone 2026-10-03 03:19:30 +08:00
  • 7f2e380f55 search: a Kimi Code plan searches the web for its own models with its own search service, before the Codex account or whatever magpie picks, and Settings may name it to search for every model (Discord: on Claude Code with the Kimi Code plan, why does web search use Codex's model?) — kimi-cli's SearchWeb (kimi_cli/tools/web/search.py, 1.52.0) POSTs {text_query, limit, enable_page_crawling, timeout_seconds:30} to the plan's search_url, which auth/platforms.py makes <base_url>/search (https://api.kimi.com/coding/v1/search) with the same key as the models (setup.py and oauth.py write services.moonshot_search with the plan's key or sign-in), User-Agent KimiCLI/<ver> and X-Msh-Tool-Call-Id, and reads search_results [{site_name,title,url,snippet,content,date}]. provider.KimiCodeSearch finds a plan by api.kimi.com/api.kimi.ai /coding or its kimi-code preset; magpie asks it the same way (no model asked, pages as the search APIs' are, date as page_age). A model served by a plan is given magpie's web_search even with nothing else set up, and its searches go to the plan first; failing, magpie's pick, then the search APIs, as before. For other providers' models a plan is never picked automatically (it would spend the plan's allowance and change today's searcher): Settings › 联网搜索 offers it once, by its web search with no model, and says it searches for its own models first. TestKimiCodePlanSearchesForItsModels fails on the old code (no search tool with nothing else set up; with a searching API and a search API, the old code asked them and never Kimi) and passes; TestKimiCodePlanNamedToSearch and TestKimiCodeSearchAddress cover naming it and the address; searcher-pick.test.cjs (webkit, en/zh) the picker. Against the real account's key from a temp copy of the config: api.kimi.com/coding/v1/search exists (404 on a wrong path, 400 on a bad body) but answers 403 access_terminated_error for search and chat alike, as that Kimi Code subscription has lapsed, so a real search result wasn't seen. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. yetone 2026-10-04 01:59:50 +08:00
  • 5d126a0db7 agents: Pi's model picker lists the models of its openai-codex sign-in from Pi's own registry, and its Default says it lets Pi pick (#709) — Pi's native options came from models.dev by provider id, and models.dev has no openai-codex (Pi's "OpenAI Codex (legacy)" ChatGPT sign-in), so a Pi signed in to both openai and openai-codex was offered openai/… only. Pi's (and OmO's) own options now come first from the model registry in the package Pi was installed from: the pi command on PATH leads to @earendil-works/pi-coding-agent, and pi-ai's dist/providers/data/<provider>.json (Pi 1.0) is read as is, in its order, chat models only (no gpt-image-2), memoized by the file's stamp — the list Pi's /model offers, which moves with every Pi update. A provider the registry doesn't have keeps models.dev's list; openai-codex with no registry in reach falls back to Codex CLI's models_cache.json (the same ChatGPT backend); models.dev's openai list isn't mapped onto it. The rows are openai-codex/<model> under "OpenAI Codex" with OpenAI's logo, a row each beside openai/<model> of the same name; picking one writes defaultProvider=openai-codex, defaultModel=<model> as before. omp shared the gap for its current model's provider (ownOptions): it now reads omp's own catalog (@oh-my-pi/pi-catalog src/models.json, non-chat kinds left out); omp's signed-in providers that aren't the current one are still #341's. Default in Pi's and OmO's model picker now says "clears the default model; Pi picks one on its own" (zh 清除显式默认模型,由 Pi 自动选择; no ja table exists), other agents' still say what they ship with. TestPiOffersItsOwnOpenAICodexModels (a fake npm-linked Pi 1.0 install, temp HOME and PI_CODING_AGENT_DIR, auth.json with openai and openai-codex oauth test metadata), TestPiOpenAICodexWithoutItsRegistry and TestOmpOffersItsOwnOpenAICodexModels fail on the old code (no openai-codex rows) and pass; pi-default-note.test.cjs (webkit, en/zh) fails on the old app.js and passes; agent-disconnect, agent-models and agent-model-groups GUI tests pass. Read-only against this machine's real installs: Pi 1.0.0 gives openai-codex 9 models and openai 44, omp's catalog openai-codex 8. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass but TestProvidersAnswerWhileListsComeIn, TestClaudeSignedOut, TestClaudeAccountNamedAsItsLogin and TestClaudeIdentityServedWhileAsked, timing tests that pass alone. Not tried in the GUI with real Pi sign-ins, nor with a Pi older than 1.0 (no data folder: models.dev and Codex CLI's list as before) or a Windows npm shim. yetone 2026-10-04 01:52:27 +08:00
  • 778dc8dc41 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. yetone 2026-10-04 01:52:10 +08:00
  • b9a38a83ba tui: c on the Usage page presses WorkBuddy's daily check-in (签到) for every WorkBuddy (China) account signed in here at once, and the WorkBuddy lines say how today's went (akic404 on Discord: the TUI had no way to check in) — the app had its Usage card's Check in now and the CLI magpie accounts checkin, the TUI nothing. Now c on the Usage page runs provider.CheckInWorkBuddy, as the CLI does, for all the accounts on this computer at once (the built-in's, or the plugin's when moved or under workbuddy-plugin), and says each in the status line: "旅行者 checked in today +100 · a 4-day streak", "· already", "isn't eligible", "no check-in event now", or in red "couldn't check in: <why>"; with none signed in it says so. The footer lists c, and each WorkBuddy line on the page carries its check-in (签到 ✓ +100 · 4-day streak, 签到 not yet today · c, 签到 failed · c tries again), read from workbuddy-checkin.json via WithCheckins (provider.CheckinDay names the Beijing day) and redrawn after c without asking the vendors again. Local only: a remote magpie's accounts are checked in on that magpie, from its own app, TUI or CLI; no gateway endpoint is added. WorkBuddy AI has no check-in event, so only WorkBuddy (China) is checked in, as before. README says so. TestTUIChecksWorkBuddyIn (none signed in, claimed, already + refused, ineligible + no event, with a stand-in check-in) and TestCheckinOnTheUsageLine pass. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried against a real WorkBuddy account, nor in a real terminal. yetone 2026-10-04 01:47:22 +08:00
  • 29a14c9a26 quota: pool windows stand in for their models' in the alerts and the CLI (#523) werldl517-cyberandwerldl517-cyber 2026-10-04 01:44:27 +08:00
  • 2719dd6ac3 tui, cli: adding a provider says how many models its list had, or why it had none, and m (magpie provider refresh <id>) fetches the list again (akic404 on Discord: 还是tui cli问题,没有获取上游模型功能,新建之后模型数是0) yetone 2026-10-04 01:21:05 +08:00
  • af046769fb routing: an account rate limited with quota left can go to the back of the order, so several accounts take turns (01huadalang on Discord: with several WorkBuddy accounts, one rate limited rests and then goes back to the front, so it takes everything until it is limited again) — a new opt-in setting, "Rate limited" (zh 被限流时), sits next to Routing for a provider (sink in its JSON) and for a routing group (sink on the group). It has two options: "Keeps its place" (the default, unchanged) or "Goes to the back". With "Goes to the back", an account or key that answers 429 while its subscription still has quota (failRate, not a quota or credit failure, and not a used-up allowance) rests as before. Once the rest ends it stays behind every one not rate limited since; those sunk are ordered by when they sank, so the load goes a,b,c → b,c,a → c,a,b. In a group it covers the members' accounts and keys together. The sink is kept in memory until magpie restarts or the account sinks again. It applies to every routing but In turn (which goes round already) and Manual, and is hidden there. yetone 2026-10-04 01:14:15 +08:00
  • ba2128b7e5 Explain sealed subagent routing and clarify quota messages (#703) Haogao Gu 2026-10-03 13:20:44 -04:00
  • 00b5879c06 gui: a removed account's row in Add a provider has Add it back and Sign out… side by side, no menu and no hiding (#694, mintonight: 不要点击后再弹一个选项卡面板,最后直接两个按钮就行; hiding is 掩耳盗铃) yetone 2026-10-04 00:56:01 +08:00
  • 1d3ad6ad78 provider: WorkBuddy's check-in reaches the plugin signed in under its own id, workbuddy-plugin (#694, Dazzle-sys: 0.1.774 签到没看到) yetone 2026-10-04 00:55:58 +08:00
  • cab8a8e450 fix: refresh first-request discovery and retain late Pi timing (#708) mofelee 2026-10-04 00:49:15 +08:00
  • 65e78e459e codex: Settings decides where Codex's thread-title requests go — Codex's own ChatGPT as before, off, or a magpie model (#705, DuGuaiYA: Codex signed in with ChatGPT sends thread_title / thread_title_reconsideration requests that magpie relays through ChatGPT even when the conversation is on a magpie provider) — a new setting codexTitles ("" = as Codex sends them, the default; "off"; or a model's id such as deepseek/deepseek-v4-flash or a group) is read on /backend-api/codex/responses for a request whose x-openai-subagent or turn metadata thread_source is a title kind. Off answers at once with an empty completed response (Codex then keeps no title, quietly, as its source shows), contacts nothing, and is still recorded: a Routing trace "Titles off" and a usage row of Kind thread_title marked rejected (left out of stats). A model gets the request through serve (so it is recorded as a title request on that provider), and its answer is given back as one Responses message holding {"title":…}, since Codex's text.format schema does not reach Chat or Anthropic upstreams. Settings has a row "Codex thread titles" (zh: Codex 会话标题) with the app's model menu (Codex's own, Off, magpie's models and groups), saved on its own through POST /api/settings/codex-titles, which refuses a model magpie doesn't serve; the page's other saves keep it. TestCodexTitlesSetting (gateway) fails on the old code (off still went to ChatGPT) and checks the ledger kinds, rejected rows and trace; TestTitleJSON; TestCodexTitlesSetting (gui) for the endpoint; codex-titles.test.cjs (en/zh, webkit) fails on the old assets: no select, the picks post, the subtitle follows, no click scrolls. go vet, builds (darwin nogui, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried with a real Codex TUI: no end-to-end run of Codex's title generation against magpie. yetone 2026-10-04 00:21:35 +08:00
  • 7806395c74 antigravity: a Google AI Pro account asks for its models with its plan named, as Antigravity does, and the log says what Google answered (Discord: a Pro account on magpie 0.1.770 had no Claude after Refresh while Antigravity 2.19.1's picker had Claude, 5.5 too) — magpie asked fetchAvailableModels with only {project}. Antigravity's language server (2.19.1, read from its binary) sends FetchAvailableModelsRequest{project, request_id, entitlement{user_tier}, location}, entitlement being the tier it keeps for the account (SetUserTier, with the plan's name) and the only v1internal request that has one. A Pro account's loadCodeAssist (CLIProxyAPI #6186) has currentTier standard-tier (usesGcpTos), paidTier g1-pro-tier "Google AI Pro", project aicode-consumers; asked with no tier, Google most likely serves it as standard-tier, Code Assist Standard, which has no Claude. Google checks the field: on our free account free-tier/bogus ids are INVALID_ARGUMENT, and standard-tier, g1-pro-tier, g1-ultra-tier and the other paid ids are FAILED_PRECONDITION, so paid ids are known and checked against the account. Ruled out on the free account: the project (none, random, aicode-consumers all the same), daily/prod/sandbox (all list Claude), and magpie's filters (only chat_/tab_ and six fixed ids, none Claude). Now fetchAvailableModels sends entitlement.userTier: the paid tier, then the current tier, skipping free-tier and legacy-tier, and on a refusal tries the next and at last none, as before; a free account sends none, so its request is unchanged. The tiers come from loadCodeAssist, which is now asked even when a project was kept with the sign-in (import, magpie accounts project), which used to skip it and so never learned the tier; if that fails the kept project is used as before. Logged: loadCodeAssist's current and paid tiers (with gcp-tos), project and ineligible tiers with reasons; each refresh's endpoint, User-Agent, project, tiers, entitlement sent, models listed and kept, the ids left out and why (not for chat, completion-only), those Google marks disabled (still listed), and the Claude ids; a tier refused, with Google's error. TestAntigravityProAccountNamesItsTier (Pro fixture: Claude only with g1-pro-tier named), TestAntigravityTierRefusedFallsBack and TestAntigravityTierFreeAndKeptProject fail on the old code (Gemini only) and pass. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried with a real Pro account: whether Google serves Claude to a Pro account only with its tier named is inferred from the client and the fixtures, not seen. yetone 2026-10-04 00:20:26 +08:00
  • bc141ad834 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. yetone 2026-10-04 00:19:22 +08:00
  • 38a0c1f053 tui: a Remote magpie is added with its address, and w changes it (akic404 on Discord: tui里的remote magpie…gui里会填写url,但是tui没有填写位置,我只能退化为用指令添加) — the Providers page's a picked a vendor and asked only its key, so a preset reached at the user's own address (Remote magpie, Azure OpenAI) was saved with no URL and refused ("a provider needs a base URL"); only magpie provider add remote-magpie … url=… or the app's editor could add one. Now picking such a preset asks its address first, as the app's editor does (the preset's example, http://192.168.1.20:3425, as the placeholder, its hint under it, nothing typed answered with "The other magpie's address is needed"), then the key; the address goes on Chat and Responses as the editor puts it, and a remote magpie's every API is put on it when saved (host:port, …/v1 or the Anthropic root all work). w on the Providers page changes the address of such a provider, with the one it has in the line; other providers say theirs is the vendor's. The footer names w. README says the TUI adds one too. TestTUIAddsARemoteMagpieWithItsAddress fails on the old code (the key asked first) and passes. Remote magpie has no default target: the address is the other computer's magpie as its Settings → Share on local network shows it; the example is only a placeholder. yetone 2026-10-04 00:05:02 +08:00
  • 04bc3461cc gui: with no model picked, a click on a model picks just it (#681, #702) — the provider editor's chips, with none picked, draw the models agents are served (the vendor's first 24) as served; a click on one left it out and picked every other served one in its place (#614's choice), so ChenstarMr's click on DeepSeek V4 Flash in Trae CN ticked the other 23 and not it ("我想选其中一个模型,但是点击之后它却选了其他的所有模型"), and Dazzle-sys's Cline (22 models, all served) clicked on each of its 4 free models till it showed ticked ended with all 22 picked, which Save wrote (#702: 选了4个免费模型保存出来显示22个). Now a click picks the model, alone when none is picked, then each next one joins it; a served chip is drawn without the accent (dashed grey border, no tick) so it doesn't read as picked, its title and the hint say a click picks just that one. Nothing changes on the Go side: no picks is still an empty list, Exposed still serves the first 24, and an editor saved untouched writes none, for key and plugin providers alike (same code). zh strings added. model-first-pick.test.cjs (webkit, en/zh: a 54-model plugin provider and a key provider, untouched Save writes [], one click picks it alone with the page not moving, Save writes it alone; Cline's 4 free ones clicked till ticked save the 4) fails on the old code (22 saved) and passes; provider-none-picked and model-rate now expect the click to pick. provider-draft-picks, click-scroll, add-sheet, model-pick, models-free-only, provider-picks-stay/models-rest/add-models/typed/detect-models, model-test-one/why, account-models, editor-short-window, fallback-more, model-api-preset pass. go vet ./internal/... and go test -tags nogui -count=1 ./internal/gui/... ./internal/provider/... under a temp HOME pass. Not tried with a real Trae CN or Cline account. yetone 2026-10-04 00:13:31 +08:00
  • 8b8f91ba8f usage: a WorkBuddy account's card says how its daily check-in went, and turns it on (#694, Dazzle-sys: 签到设置放到用量卡片上,卡片显示今日是否已经签到;要去设置里看才知道签没签是不对的) — the check-in ran and was kept (workbuddy-checkin.json) but only Settings' Daily check-in said anything of it. It runs in magpie for a moved account too (through the plugin's fetch, wbCheckinAccounts), so the card can say it for both. GET /api/usage/quotas now adds, uncached, to each WorkBuddy (China) account checked in each day (provider.WithCheckins, matched by user name, an unnamed card only when there is one account) checkins and its last record; each such account's meters end in a line with a dot: Checked in today +credit · N-day streak (green), Not eligible for the daily check-in, No check-in event now, Check-in failed; magpie tries again later (red, the vendor's message in its tooltip), or Not checked in yet today (amber) / Auto check-in is off, with · last checked in {day}. The card's first such line has Auto check-in, the same setting as Settings' Daily check-in, which stays (POST /api/settings/workbuddy-checkin, written as Codex's auto reset is), and Check in now while an account isn't in today (POST /api/usage/workbuddy-checkin, CheckInWorkBuddy, then the cards are read again). Another vendor's card has none of it; no native select, no left stripe, the page doesn't move. zh strings added. usage-checkin.test.cjs (webkit, en/zh: three accounts' lines and states, none on Codex, the switch once and off, turned on with no scroll and the line turning amber, Check in now posted and the cards re-read) fails on the old code and passes; TestWorkBuddyCheckinOnTheCard covers the matching and that the cache isn't changed. credit-days, codex-auto-reset, quota-*, usage-arrange/fold/alert, settings-sections, tray-usages, balance-parts, click-scroll, add-sheet, removed-forget/hide and plugin-two-subs still pass. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried with a real WorkBuddy account. yetone 2026-10-03 23:48:54 +08:00
  • ee0e899f37 providers: a removed account can be hidden from Add a provider too, and found again behind "Show N hidden" (#116, marsxxl on v0.1.768: 「已从 magpie 移除 · 仍处于登录状态」 lists a removed Gemini CLI with only 加回来, 不再提示 is gone) — "Don't remind me" was never gone: it is still on the Providers page's reminder line and still kept (quiet in providers.json, through a refresh or a restart), but choosing it is what puts an account under Add a provider's "Removed from magpie", where nothing could put it away, so the reminder read as having moved there without its button. Now that row's menu (the app's menu from #694) has Don't show here beside Add it back and Sign out…: POST provider/tuck keeps it in providers.json (tucked, quiet as well), the sheet stays open with the page where it was, and the row goes; the section's heading then says "Show 1 hidden", which lists it again, dimmed and marked hidden, where Show here again (provider/untuck) puts it back in the list; a search naming it finds it too, and Add it back clears both, so removing it again reminds again. zh strings added. removed-hide.test.cjs (webkit, en/zh: the menu's three items, tuck posted with the sheet open and no scroll, the row still hidden after a reload, Show hidden without a scroll, untuck, a search finding a hidden one, the reminder's Don't remind me posting quiet) fails on the old code and passes; removed-forget.test.cjs expects the third item; add-sheet, click-scroll and plugin-two-subs still pass. TestAccountsAreProviders covers tuck/untuck through a save and Add it back clearing it. yetone 2026-10-03 23:36:53 +08:00
  • 643264c24f sessions: the Sessions page and local usage read the agents in WSL too, and resume one there through wsl.exe (lgtm on Discord: magpie can't detect the session lists of agents inside WSL?) — on Windows magpie already finds the WSL distros an agent is set up in (internal/agent's wslDistros, the distro user's home as \\wsl.localhost\<distro>\home\<user>), but the session reader only looked at this computer's own ~/.claude, ~/.codex and ~/.pi. Now internal/agent hands those homes to the sessions package (sessions.WSLHomes, set in init, no second discovery), and the sessions of Claude Code, Codex and Pi in each distro are listed beside this computer's, with Session.WSL naming the distro: a "WSL <distro>" badge on the Sessions page's rows and the management page, a WSL line in the details, " · WSL <distro>" in the API's name. Resume runs in the distro: wsl.exe -d '<distro>' --cd '<cwd>' -e sh -lc 'exec ${SHELL:-sh} -lic ''claude --resume <id>''' (the user's own shell, so the agent's PATH is there; no double quotes, which PowerShell 5.1 mangles). Their calls count in local usage (callFiles). Reading a distro over \\wsl.localhost is slow and opening a file in a stopped distro starts it, so the files are listed in the background, at most every 30 s, the listing kept in sessions-wsl.json; the page waits for it only the first time ever (up to 3 s); a stopped distro's sessions stay listed as last read and nothing in it is opened (the parse cache and call shards keep them). magpie doesn't delete a session in WSL: the checkbox and Delete are off for them, with a note to delete them there, and sessions.Delete refuses. OpenCode in WSL isn't read (its SQLite over \\wsl.localhost isn't reliable). zh strings added. TestWSLSessionsListed, TestWSLStoppedDistroKept and TestWSLNoneWithoutHomes (internal/sessions) and TestWSLSessionsOfTheDistro (internal/agent, FakeWSL: a Claude Code session in a fake Ubuntu is listed with its distro and the wsl.exe resume line) fail on the old code and pass. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried on a real WSL with agent sessions: the Windows test box's distros have no Claude Code, Codex or Pi sessions; the resume line wasn't run in PowerShell, and the pages weren't opened in a browser. yetone 2026-10-03 23:44:33 +08:00
  • 0ae7330242 plugin: a pi package's sign-in screen on pi's terminal UI is asked in magpie, and an empty reply a package makes of a vendor's error says the error (all pi plugins: Kiro's login hung, pi-zcode's quota ran out as an empty answer) — pi-provider-kiro asks how to sign in through ctx.ui.custom, a component of pi-tui (a SelectList, then Input fields for a start URL and region), and magpie gave pi's runner no UI, so custom came back undefined and the sign-in waited forever. pi's runner now has a UI for the sign-in's questions: select, confirm, input and editor become magpie's sign-in prompts, and custom draws the package's component and asks its list or field the same way, answering through the component's own onSelect/onSubmit, until the package calls done. A component may keep its parts to itself (Kiro's gives only render and handleInput) and each package loads its own copy of pi-tui, so the parts are found by what drawing walks: every container goes through its children, so the arrays iterated while it renders are what it shows, in order; the text above the list is its question. Outside a sign-in the UI answers as pi's no-UI context does. session_start now reaches every package at startup, which Kiro needs to keep the context it asks through. Separately, pi-zcode turns ZCode's 1005 quota error into a reply with no content; magpie keeps the small JSON or text bodies a package's fetch gets while it streams, and a stream that ends empty answers that body's message and code (429 for quota or rate limits, else 502) instead of an empty message. TestPiPackage drives a fake package that asks on real pi-tui classes (a list, then a start URL field) and fakes ZCode's empty quota reply; it fails on the old code (no first prompt) and passes. With real accounts in a sandbox HOME: Kiro's "Kiro Login" (API Key, Web Login, Device Code) came as a select, Web Login gave the app.kiro.dev sign-in, and after signing in kiro-plugin/claude-sonnet-4-5 called get_weather and answered with its result. pi-cursor (1.4.38, latest) answers plain turns but its models say no get_weather tool exists, though the package sends it (toolCount 1, its MCP schema) and Cursor's stream carries fields the package doesn't know; that is between pi-cursor and Cursor. The ZCode quota path wasn't tried with a real account. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. yetone 2026-10-03 23:47:15 +08:00
  • b5cfca54ad gateway: a conversation too long for one member of a group goes on to a member with room for it, as before (#700: ynky007 — Kimi Code on DeepSeek V4.1 Flash through magpie showed a 176k context after updating to 0.1.768) — magpie still writes max_context_size = 1048576 for it into ~/.kimi-code/config.toml (checked in a temp HOME with the models.dev cache). 176k is Kimi Code's own: on a context overflow it takes floor(0.85 × the request's tokens) as the model's window for the session (observeContextOverflow, OVERFLOW_CONTEXT_SAFETY_RATIO, 2.1.1), so it was told a request of about 207k overflowed. Since 11a09d41 an overflow (a 400/413/422 saying so, which tooLongRe now knows in many more words) is not retryable and goes to the agent at once, and b3ab3e42 tells some unexplained failures as one; before, a 400 with "exceeded" failed over to the next member, which answered. That is right for other accounts of the same model, not for a group member of another model or vendor, whose window may be larger. Now an overflow goes on to the members left that are not the same provider and model and not known (windowOf) to be smaller than the request; nobody rests (Fail "overflow", shown as "too long for its model"), and if they fail on quota the agent is still told the overflow, to compact. With none left the agent is told at once, as now. TestOverflowGoesToAMemberWithRoom fails on the old code (400 context_length_exceeded, the second member never asked) and passes; it also checks a member known too small and a second key of the same model are not asked. 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 providers: which member refused at ~207k is not known. yetone 2026-10-03 23:42:09 +08:00
  • d079dc6223 usage: an OpenCode session's row shows the effort and the model it asked for (#680, Sun1090: 数据不完整,推理强度是有的) yetone 2026-10-03 23:36:40 +08:00
  • 5cf4d1dae1 codex: on magpie API, Codex's own model as the default keeps magpie as its provider and magpie's models in its picker (#701, nullburn: Codex 选回原生默认模型后,第三方模型目录消失(已选择 magpie API)) — with Codex's sign-in set to magpie API, picking one of Codex's own models (gpt-6-luna) as its default in the Agents row ran unroute(): model_provider, model_catalog_json, openai_base_url and magpie-models.json all went, so every third-party model left Codex's picker while the row still said magpie API; and choosing magpie API while on Codex's own model wired nothing (login.Set only re-ran set for a magpie model). Now, on magpie API, Codex's own model goes as magpie serves it on the ChatGPT account there (codexOwnViaMagpie: gpt-6-luna -> codex/gpt-6-luna, the entries shown to Codex first), so set() takes the magpie branch and only the default changes; the picker's OpenAI group offers those ids under their own names (Label gpt-6-luna) so the value matches; one magpie doesn't serve (the ChatGPT subscription off in magpie) is refused with a message naming both ways out, the config untouched, rather than quietly unwired; choosing magpie API on Codex's own model wires it at once; Sync wires a config an older magpie left so (#701's: model = "gpt-6-luna", sign-in api). Back on ChatGPT, a codex/… default turns into Codex's own model again, straight to OpenAI as before (codexOwnOf); the ChatGPT sign-in's paths are otherwise unchanged. TestCodexAPIKeepsMagpieOnOwnModel (api on an own model, magpie model then own model again, picker values, refusal, back to ChatGPT) and TestCodexAPISyncWiresOwnModel fail on the old code and pass; TestCodexProviderAlsoSetsBaseURL's api case now expects magpie to stay wired on Codex's own model. End to end in a temp HOME with a copy of a real models_cache.json: api, fake/m1, then gpt-6-luna leaves model = "codex/gpt-6-luna" with model_provider, model_catalog_json and openai_base_url, and codex-cli 0.160.0's codex debug models with CODEX_HOME on that sandbox (gateway ports pointed at :1) lists fake/m1 beside codex/gpt-6-luna and the other codex/ models. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried in the Codex app or with a turn sent through a real ChatGPT account. yetone 2026-10-03 23:39:43 +08:00
  • de290249bb pi: a model that always thinks has off hidden on the Messages API, and its Chinese refusal of thinking off is asked again (#699, liuweifeng: 走 Anthropic Messages API 的 always-thinks 模型被删掉 thinkingLevelMap 的 off,Pi 仍提供 off 导致上游 400) — piThinkingLevels left off out of thinkingLevelMap for every model on anthropic-messages, which is right for Claude (Pi offers a level left out, and Claude takes thinking disabled whatever its levels) but had Pi offer off for ZCode's GLM-5.3-Flash too, whose levels are low/high/max and which answers thinking disabled with 400 "该模型始终支持思考,不可关闭". Now off is left out only for a model that thinks only when asked: gateway.ThinksOnlyWhenAsked, the gateway's own anthropicModel (the test that decides it leaves a Claude's request as sent and turns another vendor's thinking off unless asked), plus a relay's opus-5.5 spelling. Another vendor's model there gets "off": null unless none is among its levels (GLM-5.2's none still maps off to it), as on Chat and Responses. The gateway's alwaysThinks, which asks a refused thinking-off request again with thinking left to the model, didn't know the Chinese wording, so the 400 reached Pi (and any agent whose request the gateway turned off); it now matches 始终…思考 / 思考…不可关闭. OpenCode's variants, dsh's levels and omp's efforts name only the model's own levels, off only for none, so they don't offer a level the model refuses; omp can't hide its off, and the gateway's retry now covers it. TestPiOffOnMessagesAPI (GLM-5.3-Flash off null and not offered; GLM-5.2 off=none; Claude and opus-5.5 off left out) and TestAnthropicPassthroughAlwaysThinksInChinese (asked again once without thinking) fail on the old code and pass. Pi's models.json generated in a temp HOME with PI_CODING_AGENT_DIR in it: zc/GLM-5.3-Flash has "off": null, low/high/max; claude-sonnet-5 has no off key. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass but TestDevinSignedInOnlyInMagpie, a timing test that passes alone. Not tried with a real ZCode account. yetone 2026-10-03 23:36:20 +08:00
  • 89d305229b providers: a removed plugin subscription is named as its row was, and can be signed out for good (#694, mintonight: 点了移除,国际版账号没有消失,放在 plugin 里无法彻底删除;再添加回来还是旧账号) — Add a provider's "Removed from magpie — still signed in" listed a removed plugin account by its agent, which for every plugin's sign-in is "plugin", with the generic logo; and its only way out was Add it back, which brought the old account back, since Remove only hides an account and nothing signed it out. Now a removed account is named and drawn as its row was (accountLabel, shared with the provider row: a plugin's by the provider it signs in to, a moved built-in's by the built-in's logo; Exclusion.Name carries it to the CLI's list too). Its row in the add sheet opens the app's menu (openProtoMenu, the page left where it was): Add it back as before, or Sign out…, which asks first and posts provider/forget; the Providers page's reminder line has Sign out beside Add it back and Don't remind me. provider.ForgetAccount signs a plugin's accounts out of plugin-auth.json (the host's signOut, else under plugin-auth.json.lock), takes them out of logins.json and, for a moved built-in, out of the move's backup so moving back doesn't restore them; an agent's own sign-in that magpie only reads is hidden, as an account's Remove hides it; a built-in's saved accounts go through ForgetLogin, the one in use last. Signing in again then starts afresh. zh strings added. TestForgetRemovedPluginAccount (bun fake plugin: removed is named FakeCo, forgotten leaves plugin-auth.json, logins.json and the removed list empty, Add back brings nothing) and removed-forget.test.cjs (webkit, en/zh: the row named Qoder, the menu with no request and no scroll, Sign out asks then posts forget with the provider id, Add it back still posts show, the reminder's Sign out) fail on the old code and pass; add-sheet, click-scroll, plugin-two-subs and saved-signedout-remove still pass. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried with a real Qoder account; the plugins repo needs no change (the Qoder plugin keeps its sign-ins only in plugin-auth.json). yetone 2026-10-03 23:08:53 +08:00
  • c3cd790446 provider: a Refresh drops the picks the vendor's list no longer has (akic404 on Discord: a remote magpie offering far fewer models, one of its providers renamed from ws-ba5my… to bailian, still showed every old model here after a Refresh, until the provider was deleted) — a Refresh already replaced the fetched list (catalog.SaveLive writes it whole), but the models picked from the old list stayed in the provider's Models, and Exposed serves a pick the list lacks as one typed in by hand (#597), so every "ws-ba5myt9wad852ujp-…/qwen3.6-max" the remote no longer had stayed among the models agents see, drawn in the editor as "Added by hand". Now Fetch (through the new Refetch) drops from the saved picks those the list fetched before had and the one fetched now doesn't; a pick neither list had was typed in by hand and stays, and a fetch that fails or answers no model changes nothing. It applies to providers of a key (a remote magpie, any /v1/models provider), not to signed-in accounts, whose lists have their own rules. The editor's Refresh is told the dropped ids and takes them out of its draft too, or its Save wrote them back. TestRefreshDropsPicksTheListNoLongerHas (a remote-magpie preset against a server whose list shrinks and is renamed, then goes down) fails on the old code (all four picks kept) and passes. End to end with two sandbox magpies under temp HOMEs: A (nogui serve on 3461, a provider ws-ba5my over a fake upstream), B with A as a remote magpie and four picks (three of A's, one typed); after A's provider set ws-ba5my id=bailian and two models dropped, B's provider models left the typed pick alone, and with A stopped a Refresh failed and changed nothing. go vet, builds (darwin, GOOS=linux -tags nogui, GOOS=windows) and go test -tags nogui -count=1 ./internal/... under a temp HOME pass. yetone 2026-10-03 23:01:23 +08:00
  • 67092380fb plugin: pi packages run as pi runs them — a program a login starts can't take the host's stdin, a select is answered with its option's id, and the extensions get pi's runner, pi's auth.json and session_start (owner: 都补上,用真实账号测) — tried with real accounts, five pi packages had problems that the fake one didn't show. pi-devin-plus runs devin auth login with stdio "inherit" when it finds no Devin sign-in, and the program read magpie's next requests from the host's stdin as its answer ("Login canceled") while writing over its answers; host.js now gives every program started through Bun.spawn and Bun.spawnSync (all of node:child_process goes through them) nothing to read and stderr to write. pi-zcode asks its region with onSelect and options { id, label }, pi's own shape, and checks the id ("bigmodel"); pi.js answered with the label, so the login said "Login cancelled". Packages read their sign-in back from pi's auth.json (readStoredCredential: pi-zcode for its plans, pi-cursor for its token, pi-provider-qoder through the registry, pi-antigravity syncs its accounts through it) and register what they find on session_start (pi-zcode its plans' models, pi-devin-plus and pi-xai-oauth refresh their catalogs), and a provider registered after loading went into a queue nothing read. Now each package is bound with pi's ExtensionRunner as pi's session binds it (bindCore: no UI, tools or session of magpie's), so a later registerProvider reaches the runtime; each pi provider's first account is kept in $PI_CODING_AGENT_DIR/auth.json as pi keeps it, written when it changes; and a provider newly signed in, and each one signed in when the host starts, is sent session_start, after which magpie lists its models again if they changed. A runner that can't be made leaves the packages registered as before. TestPiPackage now has a select answered with an id, a login that starts a program on the terminal, and a session_start that reads the sign-in from pi's auth.json and adds a model; it fails on the old code (the label answered; the model never listed, auth.json empty) and passes. With real accounts in a sandbox HOME: pi-zcode signed in through Z.ai and lists 11 models (none before); its requests reach ZCode but the account's Start Plan answers 200 {"code":1005,"msg":"exceed quota limit"}, which pi-zcode reads as an empty answer (its bug). pi-provider-qoder signed in through Google, 16 models, tool round trips on Kimi-K3 and Efficient. pi-devin-plus signed in, its catalog refreshed on session_start (53 models), tool round trips on swe-2 and claude-opus-5.5; pi-xai-oauth (grok-4.7) and pi-antigravity (gemini-3.8-flash) tool round trips; @rahularya01/pi-cursor answers text (its agent doesn't call a client's tools, upstream). pi-provider-kiro wasn't signed in: it needs pi's terminal, a Builder ID or an IdC login. go vet, builds (darwin, GOOS=linux -tags nogui, GOOS=windows) pass. yetone 2026-10-03 22:51:24 +08:00
  • b7c41ea2fe feat: 单独索引 Codex 归档会话并去除物理副本 (#698) Fanbuz 2026-10-03 22:45:46 +08:00
  • c9d0d15b94 fix: 记录网关实际返回的响应 ID (#693) Fanbuz 2026-10-03 22:45:42 +08:00
  • a27bd9ec96 proc: the desktop app takes the agents' folder variables from the login shell, as it does PATH (atie on Discord: PI_CODING_AGENT_DIR set in the shell, magpie still wrote Pi's config to ~/.pi/agent/models.json) — magpie reads PI_CODING_AGENT_DIR, CODEX_HOME, CLAUDE_CONFIG_DIR and the rest with os.Getenv, but an app opened from the Finder, the Dock or the login item gets launchd's environment, with nothing a shell profile exports, so every agent was wired into its default folder in the home. proc.UserPath already asked the login shell for its PATH (sh -ilc printf with markers); the same single call now prints PATH and every variable in agentenv.Vars between the markers, parted by NULs, and each one magpie's environment doesn't have and the shell gives a non-empty value is set with os.Setenv; one magpie was started with is kept. These are read as soon as the app starts (the library is synced into the agents at once, the backend wires them), so UserPath now waits for the shell's answer up to 2 s instead of not at all; a slower profile still doesn't hold the window up longer, and what it says later is still taken. LoginPath uses the same call. Windows is unchanged (an app gets the user's environment from the registry there). Only the GUI's runGUI/runPanel call UserPath; no test but proc's own does. TestUserPathTakesAgentVars (a fake $SHELL whose profile exports PI_CODING_AGENT_DIR with a space, CODEX_HOME and an empty GROK_HOME) fails on the old code (nothing taken) and passes: Pi's folder is set by the time UserPath returns, magpie's own CODEX_HOME is kept, the empty and absent ones stay unset. Tried with a built binary calling proc.UserPath under env -i (launchd's PATH, a temp HOME): with /bin/zsh, .zshrc's PI_CODING_AGENT_DIR="$HOME/my pi" and CLAUDE_CONFIG_DIR came through, CODEX_HOME=/kept stayed, .zprofile's PATH folder was added, in 19 ms; with /bin/bash, .bash_profile's value came through. go vet, builds (darwin, GOOS=linux -tags nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass but TestClaudeSignedOut, TestClaudeAccountNamedAsItsLogin, TestClaudeIdentityServedWhileAsked and TestDevinSignedInOnlyInMagpie, timing tests on fake CLIs (5 s waits) that pass when the package is rerun; none of them reaches UserPath. yetone 2026-10-03 22:32:52 +08:00
  • 30c072713d gui: the Gateway's models say what they take — reasoning levels, images, context at each row's end, in full in a tooltip — and the search finds a group by its models (ARNO on Discord: 希望网关里的模型列表支持搜索查找,支持显示模型或者模型组更多的信息,比如该支持的推理强度,是否支持图像等) — each model row on the Gateway page now ends, before its copy button, with small muted chips: the reasoning levels agents are offered ("low–xhigh", or "low / medium" for two; the levels kept, not all it has, and none for a model whose levels aren't known), a picture glyph when it accepts images, and its context ("400K", "1M"; the 1M badge after the name moved there). Their tooltip says it in full: "Reasoning: low, medium, high, xhigh", "Accepts images" or "Text only", "Context: 400,000 tokens". A routing group's are what agents are told of it, as of a model: /api/providers' gateway.groups now carry the catalog entry's efforts, images, context and output, and members (provider/model, in its order); its tooltip says images are every model's, the context the least of its models', its output, and names its models. The search (already there, shown from 8 models) shows from 5, has a tooltip saying what it matches, and also matches a group's models, so "kimi" finds the group sending to it. Strings in zh. TestGatewayGroupsSayWhatTheyTake checks a group's listed members and that its efforts, images, context and output are the catalog's; gateway-model-info.test.cjs (webkit) checks the chips and tooltips of models and a group, the search by id, name, provider and a group's models, Escape, and that a click on the chips picks the model without moving the row. Related GUI tests (gateway-*, click-scroll, list-sort, public-url, request-archive, sse-body, web-page, api-key-theme) pass; go vet, go build -tags nogui ./... and go test -tags nogui -count=1 ./... under a temp HOME pass but TestProvidersAnswerWhileListsComeIn, a timing test that passes alone. yetone 2026-10-03 22:32:27 +08:00
  • 4c7788424c gateway: a rate limit that comes back as soon as its rest is over rests longer each time, until the account answers (01huadalang on Discord: WorkBuddy 429s a free model while the account has credits, and magpie asks it again every minute) — a 429 the vendor gave no time for rested a minute, every time: an account refused again the moment it was back was asked once a minute forever, a request lost each time before the group's next model took it. The failover is magpie's (restAfter), so this is the same for the built-in and for the plugin, which passes WorkBuddy's 429 and its headers on as they are. Now rateRest counts the rate limits that come as soon as a rest ends (within half an hour of it) and rests 1, 2, 4, 8, 16 then 30 minutes; an answer starts it over (clearRest drops the note it counts by), one long after the last rest a minute again, and one while still resting — the last one left, tried anyway, or one request's own retries — keeps the count where it is. What the vendor says still counts: Retry-After or a reset header, or a reset time in the error as keepRetry notes it, when longer than the backoff. A rest is by the account (restKey) as before, by the model for OpenRouter's shared-pool free models. A usage that says the account has credits never put a resting account first (restLast orders after weighing); the test holds that. The Routing page says "rate limited again as soon as it was back (n times in a row)" for such a rest. TestRateLimitBacksOffWhenItComesBack fails on the old code (no count, a minute every time). go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass but TestUserPath, TestSavedButSignedOutClaudeServed and one internal/gui test, which pass rerun. Not tried against WorkBuddy itself: its 429's body and headers weren't seen. yetone 2026-10-03 22:30:19 +08:00
  • 88fd366efb gateway: Gemini's thought signatures come back on its tool calls, through Pi and every other client (#687) — Pi on the built-in Google Gemini (an API key, gemini-3.8-flash at high) failed the turn after every tool call with 400 "Function call is missing a thought_signature in functionCall parts … default_api:bash, position 3". Gemini 3 signs the first call of each step it thinks through and wants the signature back; its OpenAI-compatible API gives it in tool_calls[].extra_content.google.thought_signature, which magpie relayed to Pi as it came, and Pi's openai-completions (like the AI SDK) keeps a call's id but drops extra_content, so the call went back unsigned. magpie's own translation (Claude Code, Codex, Gemini CLI on the same provider) never read the field at all. Now a call Gemini signed reaches the client with an id carrying the signature (<id>__ts__<signature as base64url>, letters, digits, _ and - only), which every client sends back as given: a Chat request relayed to Gemini has it taken off the call's and the result's ids and put in extra_content, and parse does the same for a translated request (the signature on the call's Part, given back by buildChat). A turn with no signature at all — another model's calls, or an id a client shortened — goes to Gemini with skip_thought_signature_validator on its calls, the value Google documents and the Code Assist path already sends; a later call of a signed step goes without, as Gemini signs the first. Any other upstream gets the calls' own ids, never the long ones. TestGeminiThoughtSignatureThroughPi (streamed and whole) and TestGeminiThoughtSignatureThroughAnthropic, against an upstream that checks signatures as Google does, fail on the old code with the issue's 400 and pass; TestGeminiCallsWithoutSignature and TestSignedID cover the fallback, other upstreams and the id's round trip. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./internal/gateway/... under a temp HOME pass. Not tried against generativelanguage.googleapis.com: there is no Gemini API key on this machine. yetone 2026-10-03 22:29:23 +08:00
  • 763eb60478 gateway: let a searching relay be named (#686) Kagari Mimiandhazukisendai 2026-10-03 22:34:05 +08:00
  • 39ec205faa fix: refine usage day selection and CSV filenames (#689) Leo 2026-10-03 22:33:59 +08:00
  • ee68b96788 fix: avoid duplicate Pi interaction and gateway traces (#692) mofelee 2026-10-03 22:33:54 +08:00
  • d62fecd468 plugin: reuse config parsing and avoid request body string copies (#696) Snowyy 2026-10-03 22:33:49 +08:00
  • 6c46e5561c fix(gateway): 修复 Anthropic 图片来源字段混用 (#677) SadWood 2026-10-03 22:33:43 +08:00
  • e8e317d419 gateway: a Chat Completions keepalive is a comment line alone, with no blank line after it — Crush (openai-go v2.7.1) failed every reply through magpie that had a keepalive in it with "Agent processing failed: failed to start agent processing stream: unexpected end of JSON input": its SSE reader takes every blank line as an event, data or not, and parses the empty data as JSON. The comment and blank line were ": keepalive\n\n"; now ": keepalive\n", which every reader skips (a blank line with no data is no event in the SSE spec, and openai-go's own reader skips a comment line). TestTranslatedStreamKeepsClientAlive now also wants no event without data (old code: 25). Found while testing every agent CLI through a sandbox magpie: crush run on magpie/codex/gpt-6-astra failed and passes now; claude, codex, gemini, opencode, pi, goose, droid, grok, cline, omp, agy and commandcode answered too. 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 fails on main without this change too. yetone 2026-10-03 22:26:44 +08:00
  • 57a87f0ab9 gui: code text on Windows is Cascadia Mono / Consolas with Chinese in YaHei UI, not SimSun (#690) — on a Chinese Windows 11 the Library's MCP tags (远程 · 需登录), the routing page's model chips (gpt-6.1-sol) and other small code text drew thin, unhinted and not round: the tags take --code, whose stack (ui-monospace, SF Mono, Menlo, Consolas, monospace) has no Chinese face, so their Chinese fell to the generic monospace, SimSun on a Chinese Windows; and routing.css's model chips and step lines, the sign-in sheet's link and code, the import textarea, the headers editor and What's new named stacks of their own (ui-monospace, SFMono-Regular, Menlo, monospace, or var(--mono, …) with no --mono anywhere) with no Windows face at all, so all their text was SimSun. Now every monospace text takes var(--code), and on Windows (html.win, as --font already is) --code is "Cascadia Mono", Consolas, "Microsoft YaHei UI", "Microsoft YaHei", monospace; the Mac's and Linux's stack is unchanged (the Mac's ui-monospace is SF Mono either way). win-fonts.test.cjs (no sheet names a monospace stack of its own; the badge, the id, the model chip and the sign-in link take the Mac's stack off Windows and Cascadia Mono / Consolas / YaHei UI with html.win) fails on the old CSS and passes in webkit and chromium; the routing, library, omarchy and signin-link GUI tests pass as before but routing-compact, routing-manual and routing-time, which fail the same on main. Not seen on a real Windows machine; the light grey of the ids (--faint) is unchanged. yetone 2026-10-03 22:22:45 +08:00
  • 9f7b6bd86c codex: a cached entry keeps the cache's own max when magpie's list has none, and Codex's models_cache.json is read under CODEX_HOME everywhere (follow-up to #674, from reviewing #679) — restoring a cached native entry (backend away) set max_context_window to max(MaxContext, Context) from magpie's live list, so a list saved without the max (before magpie kept it) wrote the window over the cache's own (872000 became 272000); now a list with no max leaves the cache's, and codexcat.Window still raises it to a window past it. codexcat.CacheEntries and catalog.Codex read ~/.codex/models_cache.json even with CODEX_HOME set, while codexVersion and codexInstructions followed it; all four now take catalog.CodexModelsCache() (CODEX_HOME, else ~/.codex), and provider's codexCLIHome and the copy in codexInstructions are gone. TestCodexReadsCacheUnderCodexHome, TestCodexCachedListUnderCodexHomeKeepsItsMax and TestCodexCatalogEntryFromCodexHome fail on the old code (the ~/.codex cache read, none listed from CODEX_HOME); TestCodexNativeModelsTakeSetWindows now wants a cached gpt-5.5 with no live max to keep the cache's 400000 (old code: 272000). 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 alone. yetone 2026-10-03 21:38:19 +08:00
  • 7b5c3cb797 gateway: a relay that refuses Claude Code's newest asks is asked again without them, and from then on sent none (Keenc on Discord: CHCP answered "thinking.adaptive.display: Input should be 'summarized', 'omitted'" and "messages.1.output_config: Extra inputs are not permitted", Vertex Claude "thinking.enabled.display: Input should be ...") — both shapes come from Claude Code 2.1.288 itself, not magpie: its stream-json/IDE sessions send thinking.display "updates" (its connector_text mode; "highlights" exists too), and with per_turn_effort it puts a turn's effort as output_config on a system message in messages. Strict (pydantic, extra=forbid) relays refuse both; Claude Code drops them on its own 400 but only once per conversation, so every new conversation logged the errors. magpie now reads such a 400 from an Anthropic provider (refuseShapes, alongside refuseBetas, up to three rounds), marks that provider unfit for the shape, asks again at once and, from then on, sends it a display it takes ("highlights" as "summarized", "updates" left out, or none when display itself is refused) and messages without output_config, dropping the empty system message that only carried the effort; the request's own top-level output_config and the environment system message stay. A provider that takes the shapes gets them as Claude Code wrote them. magpie writes no budget_tokens below 1024 anywhere (v0.1.758 sends enabled/2048 as adaptive + effort low), so the "≥1024" error isn't from magpie's rewrite. TestRelayRefusingClaudeCodesNewShapes and TestClaudeCodesNewShapesKeptWhereTaken fail on the old code. With the real claude 2.1.288 through a sandbox gateway to a strict mock relay: v0.1.758 passed the 400 to Claude Code each conversation; this build answers the first one by asking again itself, the resumed turn and later conversations reach the relay first time ({"type":"adaptive"}, messages with role and content only). go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried against CHCP or Keenc's Vertex relay themselves. yetone 2026-10-03 21:32:32 +08:00
  • d5591b8f8d usage: export effective model costs to Langfuse (#684) mofelee 2026-10-03 21:31:04 +08:00
  • 7e2f5d9db7 devin: send the images a tool returned (#682) Trojanking 2026-10-03 21:30:50 +08:00
  • b8fdba5d64 usage: OpenCode's own calls are counted, as Claude Code's and Codex's are (#680, Sun1090: cc-switch 会统计 OpenCode 的用量,我这样不会被统计吗?) — OpenCode used on its own providers (OpenCode Zen/Go, its own keys), not through magpie's gateway, never reached the Usage page: the local calls were Claude Code's and Codex's session files only. Now each OpenCode assistant message is a call (agent opencode): its model, provider (as the session provider), input, output with the reasoning in it (reasoning kept apart too, as Codex's), cache read and write, its end time and how long it took, the session it ran under (a subagent's in its parent's), and a reply that failed with no tokens as a failed call (a stopped one is none). They are read where the Sessions page reads OpenCode: opencode.db — OpenCode 2's session_v2/session_message (a compaction on the reply before it) or the older session/message, the old ones not yet copied while OpenCode 2 migrates — else the JSON files under storage/. A reply through magpie's gateway (provider "magpie", or "dial") is left out: the gateway logged it already. Each session is a call source of its own, read again whole only when it changed (its size is the messages' count plus the sum of their time_updated, so an older message written again tells too) and kept in the call cache on disk as a file's calls are; the database listing is kept while opencode.db and its WAL are unchanged, and a read-only handle is shared between sources and closed after 10 s unused. The Sessions listing is unchanged (allFiles still takes callFiles; Calls and CallSources take callSources). ContentOf says no place for an OpenCode call. OpenCode's own cost isn't used: calls are priced at magpie's effective prices, as the others are. TestOpenCodeCallsJSON, TestOpenCodeCallsDB (cache kept unchanged and across a restart, an updated message read again, a failed and a stopped reply) and TestOpenCodeCallsV2 (compactions follow the reply before them, magpie's skipped) fail on the old code (no OpenCode calls). With a read-only copy of this computer's ~/.local/share/opencode (JSON storage, 3 OpenCode Zen kimi-k2.5-free replies): 3 calls, input 12281, output 235 (123 + 112 reasoning), cache read 24573 — the same as jq over the message files and as SQL SUM over an opencode.db built from them. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. yetone 2026-10-03 21:25:54 +08:00
  • 48d524d65a feat: select a day in usage charts (#683) Leo 2026-10-03 21:27:06 +08:00
  • f426c08395 gui: a long model list keeps every picked model in sight, and its foot is a button that draws the rest (#685: Sun1090 — zen实际使用了10个模型,现在只能看到前7个,剩余的我都不知道模型名称,怎么搜索?) yetone 2026-10-03 21:20:16 +08:00
  • 730a163f47 cursor: a step that calls tools records what it used, the cache included (#676) — Cursor tells a turn's usage (TurnEndedUpdate) only once the client has run the tools and sent their results in the same Run, and the caller runs them after the Run is closed, so every tool step was recorded as an estimated input with no cache (2395 / 0 where Cursor counted 4265, 4224 of them cached), while text turns showed ~99% cache. After the calls Cursor sends only the checkpoint (no token details) and heartbeats; a cancelled Run ends with an error and no usage. Every Run is counted in the dashboard's usage events (DashboardService/GetFilteredUsageEvents) by its conversation_id, input apart from the cache read and written; one shows about 2.5 s after the Run closes. So a tool step with no TurnEnded now closes the Run and looks for its event (after 1.5 s, every 0.5 s, up to 6 s): its conversation's, no older than the Run, not already counted; else the estimate as before. A Run with no conversation id gets one, to be found by. The estimate is also no longer added to a turn read wholly from the cache (input 0, cache read > 0). The mover's min for cursor is 0.1.9, which does the same in the plugin (magpie-community/plugins: cursor 0.1.9). TestCursorToolStepReadsItsUsageEvent fails on the old code (5008 estimated, 0 cached) and passes. Tried with the real Cursor account: the built-in (grok-4.7-medium-fast) reports 6153 in, 6144 cached on a tool call, streamed or not; and through a sandbox magpie's plugin host with plugin 0.1.9, 6186 in, 6144 cached, 121 out, where 0.1.8 recorded 5057 estimated, 0 cached. A tool step now takes about 2.5 s longer to finish. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. yetone 2026-10-03 21:13:17 +08:00
  • bd45fa452a gui: Linux tray quotas reach the bar's tooltip (atie on Discord: on Omarchy, hovering magpie's tray icon showed no quota with 设置 › 用量 › 菜单栏显示额度 on, after opening the panel and restarting) — Wails v3.0.0-beta.24's Linux SetTooltip is a "TBD" no-op: the StatusNotifierItem's ToolTip stayed the {title: "magpie"} it was made with and no NewToolTip was ever sent, so every bar showed "magpie" (Omarchy's bar shows tooltipTitle before Title, waybar ToolTip before Title too, and waybar reads ToolTip again only on NewToolTip). magpie now sets the ToolTip itself after Wails' SetTooltip: through the prop.Properties Wails exported (reached by reflection, so the (sa(iiay)ss) type it was made with is kept and PropertiesChanged goes out), then sends org.kde.StatusNotifierItem.NewToolTip from the same session-bus connection. The quotas go in the title, the part both Omarchy's bar and waybar show; waybar reads it as Pango markup, so &, < and > become look-alikes (sniTip) rather than losing the whole tooltip. Until the tray is up (or should a Wails update move the field) the loop tries again next time. TestSNITipIsSafeMarkup covers the markup. In the omarchy VM (Omarchy 4, Quickshell bar), built there with -tags "production gtk3", in a sandbox HOME with the VM's DeepSeek and Kimi balance keys: the released magpie's item kept ToolTip "magpie" (Title "¥79.15 | $8.83"); this build's ToolTip is (sa(iiay)ss) "DeepSeek · ¥79.15\n\nKimi · $8.83", dbus-monitor shows PropertiesChanged then NewToolTip from the item's connection, and hovering its icon in Omarchy's bar shows both balances. Waybar itself wasn't run (the VM's Omarchy 4 has none); what it does is from its source (sni/item.cpp). go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass but TestProvidersAnswerWhileListsComeIn and TestClaudeSignedOut, timing tests that pass alone. yetone 2026-10-03 20:55:11 +08:00
  • 58edc00bba gui: a provider with no model picked opens with none ticked, so Select none and Save stays none (#578, #614: Sun1090 — 我点了全不选,并且点击了保存,但是还是显示24个。重新打开供应商,里面也是勾选了24个free模型; 我没点保存,点了只选free 那个按钮,关闭的时候已经生效了) yetone 2026-10-03 20:54:48 +08:00
  • a55c17f397 gui: Cmd+, opens Settings, Ctrl+, on Windows and Linux (#670: App进入设置没有快捷键吗?mac一般是command+逗号) yetone 2026-10-03 20:54:05 +08:00
  • 23eed59f7a gateway: a group's auto effort keeps a conversation's reasoning while its cache is warm, going up but not down (#617: Kenoia — with group/core effort "auto", 40 of 42 effort changes between a Pi session's turns on gpt-6.1-sol read 0 cached tokens, against 4 of 834 turns without one) yetone 2026-10-03 20:51:09 +08:00
  • f0b0153e92 gateway: a routing group's own reasoning levels are the most its members are asked for, whoever asks (#671: 我在设置里限制了最高到high,但是他突破了这个限制) yetone 2026-10-03 20:45:20 +08:00
  • 72fd0cb42e gateway: a Claude that thinks only adaptively is never sent a thinking budget, whoever wrote it (Keenc on Discord: claude opus 5.5 已经不支持旧的 thinking.type.enabled 了,必须用 thinking.type.adaptive + output_config.effort,magpie 还在按老格式发参数,会导致 API Error: 400) — magpie turned thinking adaptive only where it built an Anthropic request itself, from the model's name in magpie. An Anthropic client's own thinking {type: enabled, budget_tokens} was relayed as it came, so an agent that doesn't know the model (asked by a relay's or magpie's name for it) got the vendor's 400 "claude-opus-5-5 requires adaptive thinking"; and a model named otherwise in magpie than at the vendor (an upstream name, e.g. "opus" for claude-opus-5-5[1m]) was built with a budget, the check reading magpie's name before the vendor's went in. Now forwardOnce, which every request to an Anthropic endpoint goes through (relayed, built, a group's effort fitted, count_tokens), reads the model the body goes with and, for one from 4.6 on, sends thinking {type: adaptive} (its display kept) with the budget as the effort it is nearest in output_config.effort (<=4096 low, <=12000 medium, <=24000 high, above max, as buildAnthropic asks), unless the request names an effort already; "disabled" and adaptive go as sent, and older Claudes (4.5 and before) keep enabled with their budget. claudeVersion also reads opus-5.5 without "claude-" and the old order claude-N-N-family, and caps the major at two digits so a dated id is never taken for a version. TestAdaptiveOnlyModelsNeverGetABudget (a fake Anthropic upstream answering 400 to enabled for 5.5: a relayed budget, Bedrock's spelling, an effort beside a budget, a name mapped after, Chat's reasoning_effort onto a mapped name, disabled kept, Sonnet 4.5 kept on its budget) fails on the old code and passes. Against the owner's real relay in a sandbox gateway under a temp HOME: yylx/claude-opus-5-5 with thinking enabled, budget 10000 answered the 400 with the old build and 391 with this one; budget 31999 (effort max) and Chat's reasoning_effort high answered too. AiHubMix and OpenRouter took enabled on the old build already (they convert it themselves). go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass but TestCodexVersion, a timing test that passes alone. yetone 2026-10-03 20:50:59 +08:00
  • 64665bb249 codex: a window set on a ChatGPT account's model reaches Codex's own model list (#674) yetone 2026-10-03 20:45:34 +08:00
  • 07644556a7 fix(davsync): scope settings credentials during partial sync (#673) TryWorld 2026-10-03 20:43:13 +08:00
  • 64ad965e71 gateway: a request passed on to a remote magpie keeps the session its agent named (#672) yetone 2026-10-03 20:41:57 +08:00
  • 0d78acd62a gateway: WorkBuddy's plans draw with the image models their config lists (freecss on Discord: let WorkBuddy's subscription image models be magpie's image models) — Settings › Models › image model, the Magpie Image MCP and /v1/images/* now offer a WorkBuddy plan's image models, for the China build (workbuddy) and WorkBuddy AI (workbuddy-ai), built-in or moved onto @magpie-community/opencode-workbuddy-auth. Which models are taken from GET <site>/v3/config, signed as a chat is, by their tags (text-to-image, image-to-image), with no ids written in: today the China desktop sign-in lists hunyuan-image-v3.0-art, WorkBuddy AI gpt-image-2.5-sunburst (which edits too). The list is kept an hour per account (5 min after a failed ask); Drawers answers at once and asks in the background. A drawing goes as WorkBuddy's CLI sends it, to <site>/v2/images/generations or /v2/images/edits in JSON: GPT Image with quality, background, output_format and b64_json; Hunyuan with model, prompt, n and size only; an edit's images as data URLs, to the model itself when it takes images or to its -edit beside it, else a 400 that says the model draws from a prompt only. WorkBuddy's {code, msg, data} is unwrapped (a non-zero code is a 502 with its msg); Hunyuan's COS URL is passed on as the other URL drawers' are. These models are never AutoDrawer's pick, since each image costs the plan's credits. For a moved plan the plugin's fetch signs and sends it: pluginFetch now also takes an absolute URL, only to the plugin base's own host, and Provider.Origin gives a provider's site. TestWorkBuddyDrawsWithItsConfigsModels, TestWorkBuddyAIDrawsGPTImage and TestWorkBuddyPluginDraws (fake upstream: config, generation, edit, envelope error, plugin path, foreign host refused) fail on the old code. Against the owner's real accounts (moved onto the plugin) in a sandbox gateway under a temp HOME: workbuddy/hunyuan-image-v3.0-art drew a 1024x1024 PNG (URL), workbuddy-ai/gpt-image-2.5-sunburst one in b64, an edit of it with gpt-image-2.5-sunburst turned the bird red, and an edit asked of hunyuan-image-v3.0-art got the 400 without a call; no token rotated. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. The GUI's model picker wasn't looked at (nogui sandbox). yetone 2026-10-03 19:52:44 +08:00
  • 2d5f9c748e gui: the Usage page shows every account again, each as compact as before the trend lines, and remembers which ones are open (ARNO on Discord: 以前每个账号所占的空间很小,基本能一页看到所有账号的情况,现在要一个个手动展开账号,展开后占的空间又特别大,余额走势的线条也分散在各个地方很乱。而且关了magpie后又得一个个展开) yetone 2026-10-03 19:44:08 +08:00
  • 6e915d794d search: a searcher is asked on the API it searches on, and one that finds nothing gives way to the next searcher and the search APIs (#669: with Automatic resolving to deepseek/deepseek-flash and Tavily set up, every client's web_search got web_search_tool_result.content [], the routing log saying 200) — magpie asks its searcher on Anthropic's API, and DeepSeek's Anthropic API wasn't counted among those that search, so the request was translated onto DeepSeek's first API, Chat; translate chose the API a provider searches on (DeepSeek's Responses) only for a client's request, never for magpie's own search, so DeepSeek got no search tool at all, and deepseek-flash said it couldn't search or wrote its own DSML tool-call markup as text. That text went to the model as what was found, with no page, and was taken as a success, so Tavily was never asked. Now translate sends a searcher to the API it searches on as it does a client; api.deepseek.com searches on Anthropic too (tried: web_search_20250305 there answers web_search_tool_result blocks of real pages with DeepSeek's encrypted_content, and takes magpie's own blocks back in a later turn); a searcher's results are read one by one, title, address and page_age (now carried to the client's block), never its encrypted_content, which stays "" in what magpie sends; a reply with no page and no address in its text is "found nothing", and the search goes to the next searcher magpie would pick (up to 3, within the 2 min), then to the search APIs. TestSearcherAskedWhereItSearches (DeepSeek searching on Responses only: old code asked Chat and gave the DSML text and []), TestDeepSeekNativeSearchResults (DeepSeek's native reply: pages, their ages, no encrypted_content passed on) and TestSearcherFindingNothingGivesWay (a 200 with nothing found goes to Tavily, or to the next searcher first) fail on the old code; TestDeepSeekSearchesOnResponses now expects the Anthropic API to search. With the real DeepSeek key in a sandbox gateway (temp HOME, copy of providers.json, searcher deepseek/deepseek-flash) the issue's curl on aihubmix/glm-5.3-flash got 10 web_search_result pages (weather.com.cn, moji, …) and an answer from them; deepseek/deepseek-flash asked directly with that reply in its history searched natively and answered. No Tavily key here, so the fallback to it was tried only against the fake. go vet ./internal/..., builds (darwin -tags nogui, GOOS=linux -tags nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass but TestClaudeRunEndsWhenTheClientLeaves, a timing test that passes alone 3/3. yetone 2026-10-03 19:37:40 +08:00
  • dc3deaf09a sessions: Alma's chats are on the Sessions page, read only (mamba on Discord: magpie现在是不是还不能管理alma的会话呢) — Alma keeps every chat in one SQLite database in WAL mode, chat_threads.db in its data folder (~/Library/Application Support/alma on a Mac, $XDG_CONFIG_HOME/alma on Linux, %APPDATA%\alma on Windows): chat_threads (title, "New Chat" until named; model; workspace → its folder; incognito), chat_messages (AI SDK UIMessages in a tree, subagents' under parent_tool_call_id) and usage_records / aux_usage_records (a reply's tokens, cache reads and writes included in input). magpie now lists each chat that has something in it, incognito ones left out, and so are the chats Alma's scheduled tasks run in (thousands of them, which would bury every other agent's sessions), told the way Alma itself tells them: metadata.isCron, or a title starting "⏰ Cron:" (the title is set before the flag; 39 here have only the title): its title (else its first prompt), its folder, prompts and replies, tools and skills (tool-<Name>, dynamic-tool, Skill's skill), and models and tokens from the usage records (the model without Alma's provider id or plugin:<plugin>:<provider>:), priced like the others. It reads through a read-only connection, one read transaction a chat, and reads a chat again only when its own rows changed (updated_at, its messages' count, time and bytes, its usage rows), since the database's files change with every write to any chat. Alma's chats can't be deleted, moved or resumed from magpie: a chat is rows in Alma's live database, with triggers, a full-text index and other tables pointing at it, that only Alma can change safely, and Alma has no command to pick a chat up again; its data folder is in Dirs for discovery. Against a copy of the real database (6126 threads, 25424 messages, 11038 usage rows): 928 chats listed (3763 scheduled ones and the empty ones not), 18300 messages, all titled, every token total equal to the database's sums for those chats; the first listing takes 2.4 s, later ones 0.1 s. TestAlmaSessions (with a chat flagged isCron and one titled "⏰ Cron:" only, each failing without its rule), TestAlmaChatReadAgainWhenItChanges (a title and a usage row changed with no newer time) and TestAlmaModel fail without the change; tests set APPDATA so Windows never reads the real Alma. go vet, builds (darwin, GOOS=linux -tags nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried in the GUI against the live app. yetone 2026-10-03 19:10:52 +08:00
  • 2eb9266395 goose: runs on magpie as a custom provider that hands Goose each model's window and says which models Goose can send a thinking level (wani on Discord: goose gui不支持设置推理等级,而且上下文也不对) yetone 2026-10-03 19:10:03 +08:00