Commit Graph
Select branches
Hide Pull Requests
main
Select branches
Hide Pull Requests
main
-
23eb6c5f32
gateway: a Clef asked through magpie's System One API gets the images the request embeds (ARNO on Discord: clef模型是支持图像输入的) — /v1/systemone already passed a request's fields to the Clef as they were, images among them, but refused any request over 1 MiB with a 413, so a single embedded screenshot didn't fit. The limit is now 16 MiB (Workers AI takes up to four images, 13 MiB of request in all). TestSystemOneClefImages (a 3 MiB data-URL image to cloudflare-jev/@cf/cloudflare/clef: answered, asked at the Clef's own path with model clef, the image reaching it unchanged) fails on the old code. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
yetone
2026-10-04 22:36:56 +08:00 -
12acf07a30
gui: checkboxes are drawn as magpie's own, not the system's (ARNO on Discord: 会话面板下的checkbox的样式和软件整体的设计风格有点割裂) — the Sessions page's boxes, and every other checkbox (Library picks, import apps, caller-key limits), were the system's: white squares on the dark page. They are now a 14 px rounded box on the card's colour with a faint border, filled with the accent and a check in its foreground when ticked, a bar when partly ticked (a folder with some of its sessions picked), a ring on keyboard focus; pages keep their own margins. checkbox-look.test.cjs (WebKit, en and zh, light and dark: unticked on the card, ticked and partly ticked filled with the accent, a click still ticks and unticks) fails on the old code; Chromium doesn't launch in this sandbox. Looked at in light and dark on WebKit. sessions-manage, -folder-delete, -toolbar, -purge, -bar-names, gateway-caller-keys, gateway-key-limit, library-items, -all, -desktop-skills pass on WebKit; go test ./internal/gui/ passes.
yetone
2026-10-04 22:32:19 +08:00 -
a94cc3c950
provider: Workers AI's decision models (Jev, Clef) are tested from their right-click as System One's are (ARNO on Discord: cloudflare-jev provider模型无法右击测试) — a model test of a cloudflare-jev provider said its models couldn't be tested, since the test sent System One's question as it is and Workers AI wants it wrapped. DecideAsk and DecideAnswer move from the gateway to the provider package, and AskSystemOne (which TestModels uses) now wraps the question for the provider's API and reads the answer out of Cloudflare's envelope, as routing's own decisions do: Jev is asked {model, input}, a Clef at /run/@cf/cloudflare/<clef> with its own model selector. TestCloudflareDecideModelsTested (an httptest Workers AI: both models pass, the bodies are each one's, a refused key fails) fails on the old code; TestModelTest's cf case now expects testable. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass (TestProvidersAnswerWhileListsComeIn flaked once, passes alone).
yetone
2026-10-04 22:22:18 +08:00 -
25826ea19f
gui: the Usage chart's days are written in the page's language (the owner: 用量图表的日期也按语言格式化) — the backend labels a day "Sep 5" in every language, and the Overview chart put that label under its bars and in their titles in Chinese and Japanese too. The chart now writes each point's time as the page's language does (toLocaleDateString with intlLang, month short, day): "Sep 5" in English as before, "9月5日" in Chinese and Japanese, and a week's bar "9月12日 起的一周" / "9月12日 の週". An hour's bar keeps "15:00". usage-chart-dates.test.cjs (Chromium and WebKit, en, zh and ja: the days under the bars, a day's, a week's and an hour's title) fails for zh and ja on the old code. The usage-*, number-units, gui-ja and panel-usage GUI tests pass, but for usage-refresh-flight's two Chromium cases, which fail the same on origin/main. go build (darwin, GOOS=linux nogui) passes.
yetone
2026-10-04 22:13:09 +08:00 -
2dc8e249cb
agent: dsh on a DeepSeek model picked in its own /model stays connected while magpie's route is in its list (Fate on Discord: DSH went back to Not connected) — magpie connects dsh by putting its route among dsh's custom providers and starting it on one of magpie's models; a model of DeepSeek's own picked in dsh is saved in its settings.yaml over the patch list, and the Agents page, which called an agent connected only when its model field was magpie's, showed it Not connected, though the route, its key and magpie's models in dsh's /model were all still there. dsh now has Joined (the first profile's patch list has magpie's route), as Codex signed in with ChatGPT does, so it reads Connected · pick magpie's models with /model, and a pick off magpie while joined isn't drift ("changed outside magpie"). Disconnect in that state has an Unwire that takes the route, the start magpie wrote and the key out of every profile and .env and leaves the model picked in dsh; on one of magpie's models it does nothing and the model set back does it all, as before. TestDshOwnPickStaysConnected (connect, a DeepSeek pick in settings.yaml: wired, no drift; Disconnect: the template as it was, no key, the pick kept) fails on the old code. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
yetone
2026-10-04 22:03:41 +08:00 -
ee3fca6375
decide: Cloudflare's Clef and Clef Flash are offered on Workers AI beside Jev (ARNO on Discord: cloudflare workers-ai 现在有了新的 decision 模型) — Workers AI now serves Cloudflare's own decision models, @cf/cloudflare/clef (27B) and @cf/cloudflare/clef-flash, which answer System One's questions as Jev does. The cloudflare-jev provider (now named Cloudflare Workers AI · Jev, Clef) lists them beside typesafe/jev, so a routing group can pick either as its classifier (cloudflare-jev/@cf/cloudflare/clef), and magpie's System One API routes to them by name. Each is asked at its own run (…/ai/run/@cf/cloudflare/clef), with the request as it is and its model named clef or clef-flash, as Cloudflare's docs give it; Jev is still a run of typesafe/jev with the questions as its input, and stays the default. TestCloudflareClef (offered, routed, each one's URL, a Clef id outside Workers AI no decider) and TestClefOnCloudflare (a group classified by Clef Flash through Workers AI) fail on the old code. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried against Cloudflare: no Workers AI key in the owner's config.
yetone
2026-10-04 21:51:36 +08:00 -
56d5341636
routing: an open group folds back into its row from its editor's heading (ARNO on Discord: 路由组详情现在可以单击展开,是否可以再加上单击收起) — a group's row opened its editor on a click, and only Cancel closed it. The editor's heading of an existing group is now a button (pointer, "Click to fold it", aria-expanded, Enter or Space from the keyboard) that folds it back while nothing in it has changed; with a change it stays open and says to Save or Cancel first, so a stray click never throws an edit away. Folding saves nothing. zh and ja strings. group-fold.test.cjs (WebKit, en and zh: click, Enter, a change kept with the warning, Cancel, nothing posted) fails on the old code. group-first-token, group-arrange, group-member-off, group-patterns and gui-ja pass on WebKit; group-from-model-add's 600x420 case fails the same on origin/main. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./internal/gui/ under a temp HOME pass.
yetone
2026-10-04 21:50:16 +08:00 -
0b8fca7380
gateway: a tool schema's oneOf goes to Cline, Kilo and OpenRouter as anyOf (H20 on Discord: Cline answered 400 "unsupported schema keyword: oneOf" for Codex's automation_update tool) — some of the models those routers serve sit behind a grammar compiler (xgrammar; OpenRouter's ModelRun) that refuses oneOf "with overlapping or non-provably-disjoint branches" and takes anyOf, which to a model choosing what to call says the same. Every oneOf key in a tool's parameters/input_schema (through properties, $defs, items, allOf/anyOf and the other schema keywords) is renamed in place, the same length, so nothing else of the body changes and the prompt cache keeps its prefix; a property called oneOf and a description that says it are left alone, and every other provider is sent oneOf as before. With the owner's real OpenRouter key in a sandbox gateway, qwen/qwen3.8-27b:free pinned to ModelRun with a oneOf tool: before, 400 from xgrammar; now a tool call {"id":"7","change":{"paused":true}}. TestToolOneOfAsAnyOf (Cline, OpenRouter renamed; nested, items; a property named oneOf and the description kept; another provider unchanged) fails on the old code. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
yetone
2026-10-04 21:38:58 +08:00 -
64a4e1014b
fix(gui): a reply that broke off is told as failed everywhere (#772)
TryWorld
2026-10-04 21:27:46 +08:00 -
13742b1cb4
library: a repository's skills are turned on or off for an agent at once from its group's heading (#787, mintonight: 我记得昨天还能直接一次性开关一个作者的所有技能包,怎么今天不行了) — the heading of a repository's group (owner/repo, N skills) showed which agents had its skills as plain icons, which looked like the rows' chips but did nothing, so its skills could only be switched one row at a time (or every skill in the library at once). The icons are now chips, as on a row, with All ahead of them: a chip is lit when its agent has every one of the group's skills, half lit (dashed, the icon dimmed) when it has some, and a click gives that agent every skill of the group or, when lit, takes them all; All gives them to every agent shown, or takes them from every one when all have them. Other groups' skills and agents not clicked keep what they have; a click doesn't fold the group. The page sends the group's skill names to a new library/skills/agents-some, library.SomeSkillsAgents, which is EverySkillAgents for the skills named alone (one write; a name not in the library is refused). zh and ja strings. TestSomeSkillsAgents (on and off for named skills only, the agents' folders follow, an unknown or empty name list refused) and group-skill-chips.test.cjs (WebKit, en and zh: lit/half-lit, a click on a half-lit chip, a lit chip, All on and off, the requests sent, the group not folded) — the latter fails on the old code. gui-ja, list-sort, own-skill, shared-skills, every-skill, library-all, library-toggle-status, library-items, remove-all-skills, import-all-skills, library-row-open-scroll and click-scroll pass on WebKit. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
yetone
2026-10-04 21:23:31 +08:00 -
c744a097b6
agent: avoid panics in disconnect preview masking (#788)
Zhizhen He
2026-10-04 21:13:31 +08:00 -
30c40e2f4a
library: Droid's and Grok Build's own AGENTS.md take the shared instructions (frankittee on Discord: Droid, Grok Build 没有用户级指令文件, 但其实是有的) — the Library's Instructions said both keep no user-wide instructions file, so they couldn't be switched on or edited there. Droid reads ~/.factory/AGENTS.md as personal instructions that project files override (docs.factory.com/cli/configuration/agents-md), and Grok Build loads $GROK_HOME/AGENTS.md (else ~/.grok) first, as its global base; both are now their instructions file, magpie's block written after what the user has. TestDroidGrokInstructions (GROK_HOME honoured, the user's text kept) fails on the old code. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
yetone
2026-10-04 21:04:49 +08:00 -
1d404db710
site: the Japanese docs show magpie in Japanese (the owner: 日文文档也要有日文截图) — the ja pages of Getting started, Intent routing and Import showed the English window. Each of their nine shots is taken again with the GUI in Japanese (v0.1.851): once in magpie's own dark and light look (/img/<name>-ja-dark|light.png, drawn by Chromium on the Mac like the en and zh ones) and once for every one of the 22 Omarchy themes (/img/omarchy/<theme>/<name>-ja.webp, lossless, in the VM's Omarchy). omarchy.js takes -ja as it takes -zh, with the sizes of the Japanese shots that differ. Alt texts name the tabs as the Japanese UI does. Locally, with the light look and three Omarchy themes, all nine images on the three pages are the Japanese ones, loaded, at their own ratio.
yetone
2026-10-04 20:51:04 +08:00 -
9d3d68eb05
i18n: the purpose filter (#771) speaks Japanese — the Usage and Routing pages' purpose picker landed with its zh strings only, written before 日本語 was added, so gui-ja failed on main (six strings missing in ja). They are added: すべての用途, 用途, 未記録, 不明な用途, its tooltip and the picker's title. gui-ja and purpose-filter pass on WebKit; go test ./internal/gui under a temp HOME passes.
yetone
2026-10-04 20:47:47 +08:00 -
57e80efbf4
feat: filter usage and routing requests by purpose (#771)
zzzpyyy
2026-10-04 20:46:46 +08:00 -
8026e3ce15
claude: Claude Code's auto mode checks read their prompt from the cache, and a request with thinking off runs with it off (0xAncientTwo on X: Claude 额度在 magpie 下掉得特别快) — behind a gateway Claude Code's auto mode classifier is a request of its own for each action it checks, billed to the subscription (on Claude Code's own sign-in Anthropic runs it server-side), with a ~37k-token security-monitor prompt. Its system starts with x-anthropic-billing-header: cc_version=….<hash>, the hash taken from the request's first user message, which for the classifier differs on every check; the bridge put the system, header first, into the cached instructions block, so every check wrote all ~37k tokens to the cache again (2x the input price at the 1h TTL) and read 2303. renderClaudePrompt now leaves the header out (Claude Code's runs send their own). Claude Code's small asks and its classifier also send thinking disabled, and the bridge ran them at Claude Code's default, which on Haiku thought 1.8k–5k output tokens before a 64-token verdict or a title; a request with thinking off now runs with --thinking disabled, and that is part of the run's key, so it never resumes a run that thinks or the other way round. With the owner's real Claude subscription, the captured classifier request replayed twice with two hashes: on origin/main each check wrote 36482 and read 0; now the second wrote 343 and read 36086 (Sonnet 4.6, 10 tokens out). On Haiku 4.5 a check had 2599 reasoning tokens before and 0 now. TestClaudeInstructionsWithoutBillingHeader and TestAutoModeClassifierOnTheClaudeBridge's --thinking disabled check fail on the old code; the gateway's TestClaude* and TestAutoMode* pass.
yetone
2026-10-04 20:29:30 +08:00 -
7ec7c8e0cd
agent: Claude Code's Haiku tier stays on Haiku when its model comes through magpie (0xAncientTwo on X: Claude 额度在 magpie 下掉得特别快,跟直接用 CLI 相比体感明显) — Claude Code sends its titles, its Explore subagents and other small asks to the haiku tier (ANTHROPIC_DEFAULT_HAIKU_MODEL, ANTHROPIC_SMALL_FAST_MODEL), which on its own sign-in is Haiku. Wired to magpie, every tier followed the main model, so with Opus or Sonnet picked all of those ran on Opus or Sonnet and spent the subscription at its rate. Now when the main model is a Claude one, the haiku tier (and SMALL_FAST) follows it to the newest Haiku served where it is: the same provider, the same group/auto-claude-… group, or OpenRouter's anthropic/claude-haiku-…, the undated id first. A model that isn't Claude, a Haiku main model or a place with no Haiku leaves the tier on the main model as before. Picking the main model for the haiku tier yourself is kept (claude.tier_own.haiku) across model changes; clearing it goes back to Haiku. Follow() moves a wiring an older magpie left with the haiku tier on the main model. TestClaudeHaikuTierOnClaude (opus and sonnet get anth/claude-haiku-4-5, deepseek follows, an own pick stays, Set("") returns, effort field, an older wiring moved) fails on the old code; TestClaudeLight covers the group, OpenRouter and none cases. With the owner's real Claude account in a sandbox Claude Code session on Sonnet, its title request went to claude/claude-haiku-4-5.
yetone
2026-10-04 20:29:18 +08:00 -
7f6e2c99ed
routing: a group's stack of logos stays in its handle, inside its card and clear of its name (ARNO on Discord: the new routing groups' icons spilled past the left edge, over the text) — the handle #779 made of a group's logos had width auto in .rt-group .rt-ghandle, but body.window .ag-handle (28px, from the Agents page) outweighs it, so in the app's window a stack of two or more logos was held to 28px and centred over its edges. The selector now has .ag-handle too. The narrow web layout's 20px first column is auto, for the same reason. group-arrange.test.cjs (WebKit, en and zh) checks a two-logo stack lies inside its handle, the handle in its row and left of the name; it fails on the old CSS. agent-grip, click-scroll and the group-*/routing-* GUI tests pass in WebKit, but for group-from-model-add's two narrow-window cases, which fail on main as well.
yetone
2026-10-04 20:31:30 +08:00 -
81625282f5
gui: magpie speaks Japanese — Settings › Language offers 日本語, and a system in Japanese is followed (the owner: 日文文档也要有日文截图; the ja docs showed the English window for want of a Japanese one)
yetone
2026-10-04 20:21:46 +08:00 -
5a6b90f74f
gui: the only account on, at its usage cap, says Claude Code or Codex signed in to it still uses it (𝕏 on Discord: a Claude account capped at 90% ran its five hours to 100%) — the cap holds back what goes through magpie and moves the agent's sign-in to another account (NextLogin), but Claude Code with its own models (or Codex) asks Anthropic (OpenAI) itself, and with no other account on to move it to it went on past the cap while the row said At its cap beside In use. That row's note now reads At its cap · Claude Code still uses it, in amber, and its title says requests through magpie are refused until the window renews while the agent signed in to it goes on, and what holds it: another account, or the agent's models via magpie. The cap pill's title says the same before the cap is reached. With another account on, nothing changes. zh strings added. account-cap.test.cjs (WebKit, en and zh): the lone account's note and its title, the pill's title, the warning colour, and none of it with the spare on; the new case fails on the old code. account-arrange, -mask, -models, -proxy, -return, keep-login and login-import pass on WebKit; go test -tags nogui ./internal/gui/ under a temp HOME passes.
yetone
2026-10-04 20:17:18 +08:00 -
14f59920f0
zcode: moved accounts are brought to the plugin's 0.1.8, which spends an account's ZCode gift plans before its GLM Coding Plan (magpie-community/plugins#19, erlinerd) — an account with both a coding plan and a gift (a Trust Build grant, say) sent every request to the coding plan, and the gift lapsed unspent. 0.1.8 sends a model the gift serves to it first, replays to the coding plan on any refusal (a quota answer keeps that model off the gift for a minute), and shows the gift's buckets on the coding card. With the owner's real GLM Coding Lite account (no gift) the card and a glm-4.6 request were as before; the gift route is covered by the plugin's tests only. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
yetone
2026-10-04 20:09:16 +08:00 -
9aa7a3dca6
tray: the Mac menu bar can show the allowances without magpie's icon (KevinXC on Discord: 能否支持隐藏状态栏里的图标,只显示各个供应商的剩余额度?) — Settings → Usage has a Menu bar magpie icon row (On by default) under Menu bar logos, on a Mac with a card in the menu bar. Off (settings.TrayNoBird) draws the cards alone: the image starts at the first card, with no room left for the bird; a click on a card still opens the panel at it, a modified click opens it as before, and the flap leaves the image alone. With no cards up the bird is shown as before, so the item never goes blank. It is the computer's own, like the other menu-bar settings (KeepOwn), and a change redraws at once. Menu bar logos is 菜单栏订阅图标 in Chinese now, so the two rows read apart. TestTrayImageNoBird (the image 48 px narrower at 2x, the rest the same cells, ink at its left edge, logos and plain, light and dark) fails on the old code; tray-bird.test.cjs (WebKit, en and zh) and tray-logos pass; the birdless image was looked at in light and dark. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass (TestClientGoneRecordedAsCanceled failed once under the full run's load and passes alone, five times, and in the gateway package's own run). Not tried in the real menu bar.
yetone
2026-10-04 20:01:57 +08:00 -
f24ee8b06d
fix(agent): installs without Node ask for it the way that works (#774)
TryWorld
2026-10-04 19:49:29 +08:00 -
25f67437c2
claude: a conversation whose run was let go goes on from its saved session, the prompt cache read, not written again (X, AncientTwo: a Claude subscription's limits went much faster through magpie than in Claude Code itself; Codex didn't) — the bridge keeps at most idleMost (6) Claude Code runs waiting for their conversations' next turns. With more conversations going (subagents, several agents), the one waiting longest was let go, and its next turn started a new run told the whole conversation in one message: a prefix Anthropic had never seen, so all of it was written to the cache again at 2x the input price (1h TTL), every turn, where a run kept reads it at 0.1x. In usage.jsonl three Sonnet 5.5 requests each wrote 273823 tokens and read none. Runs now keep their Claude Code session on disk (no --no-session-persistence where they work in the shared folder, claudeWorkDir; not in WSL or a run's own folder). A run let go past idleMost leaves its session on the bridge's shelf (savedSession, keyed by turnKey as the idle runs are) for as long as it would have waited (idleLongest, the cache's hour), at most 64. The conversation's next turn starts Claude Code with --resume on it and tells it only the messages since (renderClaudeTurn), so Anthropic is asked the same prefix the run would have asked. A session's file goes with its run when nothing will resume it (a one-off ask, a failed or ended run, a rewound one), when its turn takes it and that run ends, when it expires, or when its model changed (retire); a gateway that stopped leaves them to the next one's sweep (sweepSessions: files untouched for over an hour, so another gateway's on the same account stay). With the owner's real Claude subscription through the bridge (claude-haiku-4-5, a 9000-word system prompt, then 6 other conversations): conversation A's run was let go, and its next turn resumed it, writing 318 and reading 13881 (before, a new run wrote it all again); turn 3 wrote 106 and read 14199; no session file was left after. TestClaudeLetGoConversationResumesItsSession, TestClaudeSessionFilesAreRemoved and TestClaudeOldSessionsAreSwept (a script standing in for Claude Code, keeping sessions as it does) fail on the old code; the Claude tests pass -race. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
yetone
2026-10-04 19:44:41 +08:00 -
209dc69b59
providers: Tencent Cloud is one preset, its regions Plan · China, Pay as you go · China and Pay as you go · Global (Discord, Jorben: Tencent Cloud's Token Plan, TokenHub and TokenHub China were three tiles of one vendor) — tencent-token-plan, tencent-tokenhub-cn and tencent-tokenhub become tencent-cloud's regions plan, cn and intl, as Xiaomi MiMo's are, each made as its own preset made it: the plan's /plan endpoints with no Responses, its own models and no catalog; TokenHub's hosts with Responses, models.dev's tencent-tokenhub and its own keys page and docs. A region now carries its Website, Catalog and Models, and a saved provider's region is found by its endpoints (regionOf, by exact URL before path, since TokenHub China and global share their paths): normalize gives it that region's keys page, docs and catalog (a catalog the user picked is kept), and planModels its models. The old ids are aliases, as qianfan-token-plan is of baidu-qianfan: Preset and FromPreset take them, FromPreset builds at their region, normalize rewrites a saved provider's preset to tencent-cloud, and a provider's own id never changes, so its key, models, routing groups, usage and prices stay where they were; magpie://import links by an old id land at its region, and region= now also gives a region's docs and catalog. Imports from other apps match a region's endpoints. The editor's docs link follows the region picked with the segmented control, en and zh ("按量 · 中国", "按量 · 国际"); the add sheet has one Tencent Cloud tile; the site's import docs list tencent-cloud with its regions and import.html still names the old ids. TestTencentCloud (each region, old ids, FromPreset, imports, import links) and TestTencentCloudOldPresetsKeepWorking (a providers.json saved by an older magpie under all three old presets, with a routing group over them, read, re-saved as the editor saves and switched between regions) fail on the old code; TestTencentTokenHubRoutes routes through old ids and tencent-cloud alike. tencent-cloud.test.cjs (WebKit en and zh: one tile, the three regions, no select, the docs and Get a key links following the region, an old id's provider opening at its region and saved under its id) fails on the old app.js; add-sheet, zhipu-team, system-one, strip-scrollbar and click-scroll pass (Chromium doesn't launch in this sandbox). Not tried with a Tencent account. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
yetone
2026-10-04 19:29:33 +08:00 -
6f64956f27
routing: a group can let a member go that hasn't begun answering after a set time and ask the next, and a request's detail splits its first token into magpie's time and the vendor's (Chen on X: answers through magpie felt slow, with no way to tell whether magpie or the vendor was the wait) — a group's editor has a Slow to start row (Wait, 30 s, 1 min, 2 min; Wait, the default, keeps today's behaviour). With a time set, a streamed request to a member that has sent no text, reasoning or tool call by then is cancelled before any of its bytes reach the agent (the held start is dropped; keepalives go on meanwhile) and the next member is asked; the try is marked slow ("slow to start", narrated in the route), and the member doesn't rest, since nothing is wrong with it. The last member left is always waited for. The card says "Next after 1 min without a first token". Each request now records when it went to the vendor (httptrace WroteRequest, sent_ms), and its detail shows In magpie and Vendor's first token beside First token; after a failover In magpie includes the time spent on the members let go. With the owner's real DeepSeek key in a sandbox, a group [a local member that sends its role chunk then stalls 60 s, deepseek-flash] with 3 s: the answer came from DeepSeek in 4.4 s, none of the stalled member's stream reached the client, its try read slow / "no first token in 3s" with no rest, and the ledger had ttft 3622, sent 3003. TestGroupFirstTokenFailsOver (failover, the last one waited for, one in time kept; passes -race) and group-first-token.test.cjs and usage-ledger-detail.test.cjs (WebKit, en and zh) fail on the old code. group-from-model-add's narrow-window case fails the same on origin/main; Chromium doesn't launch in this sandbox. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass (TestProvidersAnswerWhileListsComeIn flaked once, passes alone).
yetone
2026-10-04 19:33:52 +08:00 -
855e7985f2
routing: the groups are put in an order of the user's, as the Agents page's rows are, and /v1/models lists them so (#779)
yetone
2026-10-04 19:18:32 +08:00 -
4127787182
agent: Crush's models picked in magpie go to its data file, which it reads after crush.json (#172)

kevinandClaude Opus 5.5
2026-10-04 20:27:23 +09:00 -
77860f7e45
claude: a turn stopped mid-reply is taken back in the same Claude Code, so the resend reads the cache it wrote (#780, Kenoia) — a 499 (the user pressed Esc and reworded the message) ended the Claude Code run, and the resend found no run waiting, started a new one and told it the whole conversation in one message: a prefix the cache had never seen, written again whole (~119k tokens in the report). A turn resumed in a run kept for it now sends its user line with a uuid; when the client goes away, magpie tells that Claude Code a rewind_conversation control request to that uuid with interrupt_if_running (as its SDK's rewindConversation does), which stops the reply and leaves its conversation as it was before the turn, and the run waits again where it waited before, so the resend goes on in it with only its new message. A run started for the turn, one with tool calls in the client's hands, a continuation after tool results (the turn's id is dropped when its reply ends), or a Claude Code that refuses or doesn't answer in 10s is ended as before; a reply that ended as the client went away is left to the rewind. With the owner's real Claude account in a sandbox (Claude Code 2.1.289, haiku 4.5): two turns, a third streamed and dropped after 3 deltas, then the resend reworded: the same Claude Code pids throughout, the log said "the turn the client gave up on was taken back", and the resend read 6667 tokens from the cache (the whole prefix before) and wrote 91. TestClaudeTurnGivenUpOnIsRewound, TestClaudeTurnGivenUpOnNotRewound (refuse, silent) and TestClaudeFirstTurnGivenUpOnEnds; the Claude tests pass under -race. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
yetone
2026-10-04 19:22:20 +08:00 -
1e271896d1
gui: a model added by id has its own Add model button, and Built-in icons offers 92 more vendors' icons, found by name (Tom on X: 希望在 provider 的 models 里面增加手动添加模型选项;另外就是 Built-in icons 太少了) — a model the vendor's list leaves out could be added by typing its id in the box under the list and pressing Enter, but the box alone said nothing of that and Tom didn't find it. It now has an Add model button beside it, off until an id is typed, with a title saying what it does (several ids, comma separated); a click picks the typed model as Enter does and a Save sends it. Built-in icons listed only the icons some preset uses, about 60; it now also offers MORE_ICONS, 92 vendors' and platforms' marks from lobehub/icons 1.95.1 (MIT, where magpie's other icons come from): Alibaba Cloud, Bailian, Doubao, Hunyuan, Wenxin, Hugging Face, Perplexity, Cohere, Cerebras, SambaNova, Novita, PPIO, Together's peers, AWS, Vertex AI and the like. A colour icon that draws part of itself in currentColor (black in an <img>, so lost on a dark tile: aws, cerebras, longcat, poe, yi, zeroone, llmapi) is taken in its monochrome version, drawn as a mask in the text colour. The grid, now well over a hundred, scrolls in its own box (196 px) under a Find an icon… box that filters by name. zh strings added. models-add-and-icons.test.cjs (Chromium and WebKit, en and zh): Add model off/on, a click and Enter pick the id, every built-in icon draws (none falls back to the cube, no broken image), find narrows to one, and Save sends the models and the icon picked; it fails on the old code. Looked at in dark mode on WebKit. model-test-why, model-test-one, provider-detect(-models), provider-levels-save, provider-same-as, provider-typed and provider-picks-stay pass on WebKit; model-api-preset (2) and provider-levels-scroll (1) fail the same on origin/main. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./internal/gui/ under a temp HOME pass.
yetone
2026-10-04 19:19:57 +08:00 -
a13923a1e3
fix(routing): automatically group Codex title requests with their chats (#768)
Zhu Wang
2026-10-04 18:56:31 +08:00 -
831041ad9d
pi: magpie's models carry their cost, and a prompt-cache lifetime where Pi's request is relayed as it is (#781, Kenoia: Pi's cacheWarming never warmed a magpie model, "cache lifetime unavailable", and every cost was zero) — Pi weighs warming a model's cache by its cost and promptCache, and magpie's models.json entries had neither. Each entry now has cost (input, output, cacheRead, cacheWrite in USD per million) at what a call costs the user, as the usage pages count it (provider.EffectivePriceIn: the user's own price, else the provider's list price, else its maker's); a group is priced when every member costs the same and none runs in a fast mode, else it is left without. promptCache is declared only where the gateway relays Pi's request as sent, at the short end of what the vendor publishes: a Claude on Anthropic's Messages API {short 300, long 3600} (5 minutes, or an hour with a 1h cache_control), a GPT on OpenAI's Responses API {short 300} (24h retention is only some models'); a Claude subscription, which runs through Claude Code and caches on its own, and other vendors declare none. With a copy of the owner's real config, 109 of 201 entries got a cost (OpenAI API GPTs, OpenRouter, DeepSeek, groups such as auto-gpt-6-sol, auto-claude-opus-5-5, auto-kimi-k3), the OpenAI API's 24 GPTs on openai-responses got promptCache {short 300}, and groups with mixed members (test, semantic) none. TestPiCostAndPromptCache 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.
yetone
2026-10-04 18:53:41 +08:00 -
71d96e28e8
system one: a decision provider's own models-list URL is asked as given, and its models can each be tested (Discord, ARNO: OpenRouter added as a System One provider with the list https://openrouter.ai/api/v1/models?limit=500&output_modalities=decisions didn't get its models, and a model typed in by hand couldn't be tested from its right-click) — a System One-only provider went to fetchDecide before its ModelsURL was read, so magpie asked …/models, which at OpenRouter lists the 466 chat models, and kept only Jev's ids of those: typesafe/jev-router alone. With a ModelsURL, fetchDecide now asks it as written, query included, and keeps every id in it, since that list is of decision models already (OpenRouter's answers 13: pplx-decider, liquid/d1, cloudflare/clef, respan/span-01, typesafe/jev-1.13 …). Without one nothing changes, Bailian included. Testing a decision model was refused outright (the chip's Test this model off, Test models leaving them out); where the API takes System One's own body (System One, TypeSafe, OpenRouter, Vercel's TypeSafe API) each model is now sent AskSystemOne's yes-or-no question, the one Detect already asks, and the editor offers Test for it (providerJSON.decideTest); Vercel's evaluation models and Workers AI, which wrap it their own way, still say why not. TestDecideModelsURLAndModelTest (a fake OpenRouter: the plain list, the decisions list, a hand-added model through /systemone, an unknown one failing) and TestModelTest's Workers AI case fail on the old code; model-test-why.test.cjs gains a hand-added System One model tested from its chip and Test models, and a Workers AI one still off (WebKit en and zh pass, with model-test-one and model-copy-id; Chromium doesn't launch in this sandbox). OpenRouter's live decisions list returns the 13 models. Not tried with an OpenRouter key: the /systemone question itself. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
yetone
2026-10-04 18:40:13 +08:00 -
040ae5675a
factory: requests say droid 0.233.0, the current release, and moved users get plugin 0.1.16, which does the same (Discord, JL on magpie 0.1.824 with plugin 0.1.15: every model a 403 "Factory: Forbidden" from pi and Claude Code, while droid itself works) — droid 0.233.0's model requests have the headers, paths and body shape of 0.231.0's but for the version (X-Client-Version, User-Agent factory-cli/<ver>), and its binary reads a server-side min_cli_version; magpie and the plugin said 0.231.0, two releases behind. That Factory refuses an old version with a 403 is not confirmed (no Factory account to try with); it is the one difference found. The mover's min is 0.1.16 (published on npm), so keepMovedCurrent updates moved users. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
yetone
2026-10-04 18:32:29 +08:00 -
e950eb080a
codex, claude: a model with a window above 272K is compacted at 272K, as OpenAI's and Anthropic's own are (X: Chen — through magpie on DeepSeek flash, Codex and Claude Code both "wait" before every answer, and more the longer the task) — magpie told both agents a 1M model's whole window, so neither compacted until near 1M and every turn sent the whole conversation again: the live ledger had turns of ~550K tokens waiting 70–90s for a first token, while magpie's own time before the vendor stays under 0.2s. OpenAI runs its own 1M-capable models at 272K in Codex (context_window 272000, max_context_window 872000) and Anthropic its at 200K unless a [1m] one is picked; magpie now does the same by default. Codex's catalog gives such a model context_window 272000 and its whole window as max_context_window; Claude Code gets CLAUDE_CODE_AUTO_COMPACT_WINDOW=272000 while it runs on magpie (it compacts at the smaller of that and the model's window, a [1m] model's too), kept apart from a value the user set and taken out with magpie. Settings → Long conversations ("Compact at 272K" / "Full window", en and zh) turns it off (settings.FullContext), and both agents' lists are written again at once. In a sandbox with the owner's real accounts: Codex's /models listed group/test and group/auto-glm-5-3-flash at 272000 with max 1000000 (stepfun's 32K and 16K as they were, gpt-6-* still 272000/872000), Claude Code on deepseek/deepseek-flash[1m] answered and its /context said 16.3k / 272k, and Full window turned both back to 1M (/context 16.3k / 1m). TestClaudeCompactWindow and the extended TestCodexCatalogToolSearch fail on the old code. go vet (darwin, GOOS=windows), builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
yetone
2026-10-04 18:27:06 +08:00 -
17363b7e30
claude: Claude Code runs share a folder only the user owns, the caller's system prompt is cached apart, and auto memory is off (#626, yg3415) — main already ran every Claude Code run in one $TMPDIR/magpie-claude so Anthropic's prompt cache is read again; this takes yg3415's version of that: the folder is magpie-claude-work-<uid> (the uid where the temp folder is shared, as /tmp is on Linux), made 0700 and used only when it is a real folder the user owns that no one else can write to (else the run works in a folder of its own, as before), so nobody else can put a CLAUDE.md in front of every run; the caller's system prompt goes to Claude Code as a block of its own with a 1h cache_control breakpoint (CLAUDE_CODE_PROMPT_CACHE_TTL=1h, since Anthropic refuses a 1h breakpoint after a 5m one), so requests with one system prompt and different conversations share its cache entry; and CLAUDE_CODE_DISABLE_AUTO_MEMORY=1, so a memory Claude Code writes in one caller's conversation doesn't show up in every other one in the shared folder. Merged onto main by hand: the run still starts through binary.command/binary.env, so a Claude Code in WSL works in the shared folder too; on Windows the folder is in the user's own %TEMP% and Go reports every folder there as 0777, so the mode check is skipped there (it refused every run before). Tests from the PR (TestClaudeRunsShareAWorkDir with a home kept in git, TestClaudeWorkDirOthersCanWriteIsAvoided, TestClaudeInstructionsCachedApart) pass; go vet (darwin, GOOS=windows), builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.

yetoneandyg3415
2026-10-04 18:25:44 +08:00 -
ee508d0ce9
claude: on Windows with no Claude Code of its own, the gateway runs the one in WSL (X: kyotiger, Claude Code installed in WSL only, magpie on Windows found no claude) — claudeBinary and the sign-in's findClaudeCLI now fall back, when no claude is on Windows, to one in a running WSL distro (internal/wslrun: the default distro first, Docker Desktop's left out, a stopped one never started; found by a login shell, then an interactive one for nvm's node, then ~/.local/bin and /usr/local/bin, never Windows' own under /mnt). A run is wsl.exe -d <distro> --exec /usr/bin/env PATH=<its PATH> <claude> …, so no shell rc runs and Claude Code there uses its own sign-in and quota; what the bridge sets goes in through WSLENV, CLAUDE_CONFIG_DIR (a saved account's folder) and the sign-in's BROWSER as paths (/p), the proxy variables with a loopback proxy pointed at the Windows host under NAT networking. The MCP tools server stays magpie.exe on Windows, started from WSL by its /mnt path. Killing wsl.exe leaves the Linux process running, so each run is marked (MAGPIE_WSL_RUN) and a canceled one is ended inside the distro by that mark. The Claude provider's editor says "Runs through WSL · <distro>" under Accounts (en, zh); the page never waits on WSL for it. A claude on Windows itself is still used first. With the owner's real Claude account on the Windows test box (Claude Code 2.1.289 in WSL only, magpie in a sandbox): /v1/models listed the Claude models, a haiku request answered from WSL with the MCP server at /mnt/c/…/magpie.exe, a tool_use round trip worked, the quotas came from its headers (5h 13%, 7d 37%), "Add another Claude account" opened claude.com's OAuth page through the BROWSER hand-off with its localhost callback reachable from Windows, canceling it ended claude auth login in WSL, and the editor showed the line in en and zh. Not tried: finishing a browser sign-in (needs a person). Tests: internal/wslrun (the probe's parsing, path and WSLENV translation, the proxy rewrite, Find's distro order and cache, a run's argv). go vet (darwin, GOOS=windows), builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass (the gateway package flaked once under load and passes alone).
yetone
2026-10-04 17:43:56 +08:00 -
58dbd06289
linux: the window no longer flickers under Wayland — WebKitGTK draws the pages without accelerated compositing there (myxxts on Discord: Fedora 44, KDE, Wayland, AMD Radeon 780M, the UI flickered on every interaction). Wails turns DMA-BUF off for NVIDIA only; the reporter tried both WEBKIT_DISABLE_DMABUF_RENDERER=1 and WEBKIT_DISABLE_COMPOSITING_MODE=1, both stopped it and the second was smoother. Run now sets WEBKIT_DISABLE_COMPOSITING_MODE=1 before the first webview when the session is Wayland (XDG_SESSION_TYPE or WAYLAND_DISPLAY) and the user set neither variable; X11 is left as it was, and WEBKIT_DISABLE_COMPOSITING_MODE=0 turns compositing back on. TestWebkitDefaults (Wayland, X11, a user's own setting) passes in a linux/arm64 container. Not tried on a Linux desktop here; the fix is the reporter's own tested setting. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass (TestProvidersAnswerWhileListsComeIn, TestUserPath and TestClaudeSignedOut flaked under load and pass alone).
yetone
2026-10-04 17:48:54 +08:00 -
dd4801e92a
fix(gateway): forget a broken-off reply's stick by the account alone (#776)
TryWorld
2026-10-04 17:41:39 +08:00 -
21428865bd
codex titles: a title schema that requires a description beside the title gets one (#743, DuGuaiYA: through magpie's gpt-5.5 Codex kept the first message as the thread's name, the thread_title call a 200 with 27 output tokens) — their Codex's title request asks, strictly, for {"title" (1–36 chars), "description" (at least 1)} and no other field, and magpie handed back {"title"} alone, which fails that schema, so Codex dropped it. The answer now takes the shape from the request's text.format.schema: the title cut to its maxLength, and each other string the schema requires given the model's own value when its JSON had one, else the title, cut to its bounds; a schema with only a title gets {"title"} as before. TestTitleJSONFillsTheSchema (their schema: a plain answer, a JSON answer with a description, a title over 36 characters) fails on the old code. Not tried with that Codex build: the 0.160.0 installed here asks for the title alone. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass (TestClientGoneRecordedAsCanceled flaked once under load; the gateway package passes alone).
yetone
2026-10-04 17:41:02 +08:00 -
10c2f07eba
claude: every Claude Code run works in the same folder, so Anthropic's prompt cache is read again (X: Chen — through magpie Claude "got heavy", long waits between the agent's request and the answer) — the bridge started each run in a temp folder of its own (magpie-claude-XXXX), and Claude Code writes its working directory into the system prompt, so every new run's prompt was new to the cache: the whole conversation was written to the cache again on each run and never read (the live ledger: an identical 270k-token request written each time, cache_read 0). Runs now work in $TMPDIR/magpie-claude, made once and kept; a run's tools.json stays in its own folder, removed with it. With the owner's real Claude account under a sandbox HOME: a 97k-token conversation sent twice read 97368 tokens from the cache the second time and its first token came in 2.4s instead of 5.4s. TestClaudeRunsShareAWorkingDirectory fails on the old code (two runs in two folders). Codex was checked too and reads its cache; magpie's own time before the vendor is ~50ms. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
yetone
2026-10-04 17:32:00 +08:00 -
47eb27296f
settings: a search API's saved key can be shown, hidden and copied in its row, as a provider's can (OnurBen on Discord: the Providers page shows a key, Settings → Models → Search APIs only listed Firecrawl/Tavily/Exa keys masked, so copying one meant going back to the vendor's site)
yetone
2026-10-04 17:29:08 +08:00 -
564033d044
plugin: Bun is given the roots the system trusts — installing @magpie-community/opencode-kiro-auth failed with "error: UNABLE_TO_VERIFY_LEAF_SIGNATURE downloading package manifest" (chenyyi001 on X): Bun checks TLS against the roots it ships, not the system's, so behind a box that re-signs HTTPS with a root the user installed (a company proxy, a VPN, Surge's or Proxyman's MITM) magpie gets through and bun add, and a plugin's own requests, don't. bunCommand, which starts every bun (installs and the host), now sets NODE_EXTRA_CA_CERTS to the system's roots unless the user set it: on macOS the certificates in the System and login keychains the user or an admin marked as trusted (security trust-settings-export; one denied, or only kept in a keychain, stays out; Apple's own aren't added), written once a run beside Bun's cache; on Linux the distribution's CA bundle (SSL_CERT_FILE first); on Windows the ROOT store. NODE_USE_SYSTEM_CA does nothing for bun 1.4.2's installs. Reproduced with a local registry behind a re-signing proxy: bun add through bunCommand fails with the reporter's error on the old code and installs opencode-kiro-auth@0.1.7 with the proxy's root trusted; TestBunIsGivenTheSystemsRoots fails on the old code and passes, TestTrustedIn and TestSystemRootsAreTheTrustedOnes cover the trust settings. Not tried: a real keychain-trusted root (changing trust settings is left to the user), and the Windows store on a Windows machine (the test box was unreachable; it builds). go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui of internal/plugin and internal/proc under a temp HOME pass.
yetone
2026-10-04 17:18:22 +08:00 -
e673d50aea
devin: moved accounts get @magpie-community/opencode-devin-auth 0.1.7 (plugins#17, NieiR on Windows: "Add another Devin account" named account B's key after the devin CLI's own account A and replaced A's key with it) — the plugin named a key's account by running
devin auth statusin a data folder of its own, which the Windows devin CLI ignores (it reads %APPDATA%\devin\credentials.toml whatever APPDATA it is given), so every sign-in was named after the CLI's account. 0.1.7 asks Devin's GetUserStatus with the key itself, takes the CLI's word only when it read the key's own folder, and lists models from the CLI only when it answers for the key's account. The mover's min goes from 0.1.5 to 0.1.7, so keepMovedCurrent updates every moved user. Checked with the owner's real Devin key: GetUserStatus names it as the account it is labelled (plan Devin Teams, as the CLI words it). go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
yetone
2026-10-04 17:12:21 +08:00 -
a668938ce6
gateway: a routing group's members are tried in turn for /v1/embeddings and /v1/rerank (#773, dbydd: group/tmpembed of an OpenRouter :free embeddings model out of its daily quota and an NVIDIA one that answers gave OpenRouter's 429, where the same group on /v1/chat/completions moved on to its next member) — retrieve resolved a group to its first member and sent the request there alone. It now takes the group's members as its routing orders them (planGroup, every account or key of theirs, an Off member left out, those capped aside) and asks the next when one fails: a vendor without the API (404), one out of quota (429), any refusal or a failure to reach it; a model asked by its own id is asked alone, as before. The body is masked once, before the first is asked, so a member passed over leaves nothing written. The answer is the first member's that answers, recorded under it with the members passed over as the call's fallback (the Requests page); when every member fails, the last one's refusal comes back with its code, those tried first named. With the owner's real keys under a sandbox HOME: group/embpool of openrouter/openai/gpt-6-luna (400 from OpenRouter) then aihubmix/text-embedding-3-small returned aihubmix's 4-d vector, in the ledger as aihubmix's for group/embpool. TestRetrievalGroupFailsOver (an embeddings group past a 429, a rerank group past a 404, a group whose only member fails gives its 429) fails on the old code (the 429 comes back). go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass (TestProvidersAnswerWhileListsComeIn and TestClaudeSignedOut flaked once under load and pass alone).
yetone
2026-10-04 17:01:14 +08:00 -
5569275465
group: a routing group's members can be patterns, so a group follows a provider's catalog (#766: magpie group add my-free-pool models='openrouter/*:free,opencode-zen/*-free') — a group names its models one by one, so a pool of a vendor's free models went stale as the vendor added and dropped them. A group now keeps patterns apart from the models it names (Group.Match): a glob, * any run of characters (/ too) over the provider/model id in any case, or re:<regexp> over the whole id. groupsIn, which every reader goes through (Groups, FindGroup, /v1/models, MemberGroups, GroupsWith, RenameGroup, rules, Off, Pick, the GUI, TUI and CLI), puts after the named models every enabled provider's model a pattern matches, in catalog order, deduplicated, the named one keeping its place; a pattern never matches a group. What was matched is derived (Group.Matched) and never stored: store() and SaveGroup keep only the named members, so a save never freezes matches. Off and rules may name a matched model. A group with only patterns is allowed; one matching nothing routes nowhere and says so: magpie group <id> lists each pattern with how many models it matches now ("matches nothing now" in amber), the group list and the TUI too, and the GUI card shows a line per pattern with its count. CLI: models= takes patterns among the ids, models+= adds them (naming a matched model moves it to where it is named), models-=<pattern> drops one, models-= of a matched model is refused naming its pattern; the help documents patterns. GUI: the editor has a Patterns box (Add pattern / Remove, validated as the server does), matched models follow those named marked "by pattern" with their off switch and no Remove, and saves send members without the matched ones plus match; zh strings added. Tests: TestGroupPatterns, TestGroupPatternMembersOffAndRules, TestGroupPatternMatchingNothing, TestPatternSyntax (provider), TestPatternGroupRoutes (gateway: a request reaches a matched model, a model a provider adds joins, Off on a matched one, /v1/models lists the group), TestGroupPatternsCLI, and group-patterns.test.cjs (WebKit, en+zh); they fail on the old code (the Go ones don't build, the GUI one finds no pattern lines). GUI: group-patterns, group-found-off, group-member-fast, group-member-off, settings-groups, agent-model-groups, remote-group-served, routing-manual pass on WebKit; group-from-model-add has the same 2 failures (600x420) as origin/main. A request to a group whose patterns match nothing now is a 404 naming the group and its patterns (emptyGroupError), not 'magpie knows no model' and every model; TestEmptyPatternGroupSaysSo. With the owner's real config under a sandbox HOME: group/ds-pool of models='deepseek/*' listed deepseek-flash and deepseek-v4-pro by pattern, stored only the pattern, was in /v1/models and answered from deepseek-flash; none-pool of 'nobody/*' said its pattern matches nothing. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
yetone
2026-10-04 16:42:15 +08:00 -
76fb7879e8
gateway: /v1/embeddings and /v1/rerank go to the model's provider (#765, dbydd: a local RAG stack had to send its embeddings around magpie, with a second base URL and key, outside the usage ledger) — magpie answered both with its catch-all 404. retrieve (embeddings.go) takes the body as the agent sent it, resolves its model as a turn's is, puts the provider's own model id in, masks secrets as the settings say (a document a reranker gives back has them again), and posts it to the provider's OpenAI-style base + /embeddings or /rerank, signed as the provider signs; the vendor's answer comes back as it said it, its refusal with its code and words. The call is on the Requests page and in the usage ledger (operation embeddings / rerank) with the tokens the answer counted (prompt_tokens, total_tokens, or Cohere's billed input). /embeddings and /rerank answer too, and the 404's list and GET / name them. With the owner's real keys under a sandbox HOME: aihubmix/text-embedding-3-small with dimensions 4 returned two 4-d vectors and 3 tokens, aihubmix/jina-reranker-m0 ranked the magpie document first (37 tokens, recorded as rerank), and OpenRouter's 402 for no credits came back as its own words. TestEmbeddingsGoToTheModelsProvider, TestRerankGoesToTheModelsProvider, TestRetrievalFailures and TestRetrievalRedacts fail on the old code (404). go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass (TestClientGoneRecordedAsCanceled flaked once under load and passes alone).
yetone
2026-10-04 16:24:08 +08:00 -
a7cd091ac6
agents: an agent's model list has its providers down the left, one picked showing its models alone (蓝猫 on Discord: picking models in the list meant scrolling down one at a time; they asked for providers on the left, then their models) — with three or more providers (routing groups count as one) and room for it, the list widens to 580px and gains a rail: All providers, then each provider with its icon and how many of its models are shown. One picked shows only its group, open even if long; All brings back the folded list as before; a search still looks in every provider; the counts follow each click. Narrow windows and lists of one or two providers stay as they were. agent-models.test.cjs checks the rail's entries and counts, picking OpenRouter (24 rows, open), a click updating its count, a search past the picked one, All, and that the rail sits left of the list; its click-away now clicks outside the wider box. GUI: agent-models, agent-models-loading, agent-model-groups, agent-models-all-hidden, desktop-model-menu and click-scroll pass on WebKit; go test ./internal/gui passes.
yetone
2026-10-04 16:03:00 +08:00 -
0e409a04b7
quota: the trend chart says what its dashes are (John on Discord asked what the dashed line under an account's meters is) — each window's dashed line is an even pace, full at the cycle's start to empty at its reset, but nothing on the card said so. Hovering the chart now says: solid is what was left, dashed the even pace; a line above its dashes lasts the cycle, one below runs out before the reset (zh too). quota-curve.test.cjs checks the hover text in English and Chinese; it fails on the old code. GUI: quota-curve and panel-usage pass on WebKit; go test ./internal/gui passes.
yetone
2026-10-04 15:50:51 +08:00 -
6fa7a8c30b
routing: Codex's home-page suggestions are named, and the story says where Codex turns them off (#705, DuGuaiYA: ambient_suggestions calls on gpt-6-luna every few seconds, 58.5k tokens each, shown by their raw name) — the Codex app drafts the suggested prompts on its home page by itself (thread_source ambient_suggestions, on gpt-6-luna, searching the project's files and connected apps) and checks them with a second call (ambient_suggestion_safety); magpie's Routing and Usage pages showed both by their raw names with the generic "made this call itself" story. Both now read Suggestions (提示词建议), and the story says what the calls are and that Codex's Settings › Configuration › Suggested prompts (设置 › 配置 › 提示词建议) turns them off, as the Codex app 26.x bundle names the setting (settings.agent.ambientSuggestions, key ambient-suggestions-enabled). routing-kind.test.cjs checks both tags and the story in English and Chinese; it fails on the old code. GUI: routing-kind, routing-side-calls and the routing/account tests pass on WebKit (group-from-model-add's two failures are origin/main's); go test ./internal/gui passes.
yetone
2026-10-04 15:38:52 +08:00 -
92ebdb4104
fix(provider): mark a window one model's own as capped, as routing holds it (#760)

TryWorldandyetone
2026-10-04 15:43:00 +08:00 -
824d3c21cb
fix(gateway): say when a capped drawing may come back (#761)
TryWorld
2026-10-04 15:39:33 +08:00 -
4e0bf88370
gateway: Gemini on a Google sign-in searches with Google Search and draws with its image models (#757, on Antigravity: gemini-3.8-flash's searches went to the Codex account and spent its allowance, and neither the searcher picker nor the image model picker offered Antigravity's models) — a Google sign-in (Gemini CLI's or Antigravity's, not a plugin's) was never said to search by itself, and buildCodeAssistSent sent no googleSearch, so a Claude Code WebSearch on an Antigravity model was handed to magpie's search, which the searcher magpie picked (Codex first) answered.
yetone
2026-10-04 15:31:23 +08:00 -
b415c0a570
group: a routing group takes images when one of its members sees them, and an image goes to the members that do (#756, HuHong-Tao on v0.1.813: group/auto-deepseek-v4-1-flash turned 1198 image requests away with 400 "model does not support image input" in a day, where commandcode's deepseek-v4.1-flash of the group took them) — groupEntries said a group took images only when every member did (Images ANDed, ImageInput through sharedImageInput, where one explicit text-only answer wins), and the gateway's membersImageInput said the same of a request, so one text-only member turned away every image before any member was tried, and /v1/models gave the group input ["text"]. Since a rule for images can send one past a text-only member, the gateway already dropped text-only members for a request with a new image; it now does so for every group without a model to describe images: a new image goes to the members that see it (an explicit answer, or models.dev's estimate as blindTo reads it for a model of its own), to members magpie knows nothing of when none is known to see, and is turned away (400) only when every member is text-only. With a describer the members keep their order, a text-only one given the image described, as before. groupEntries ORs Images and takes ImageInput through anyImageInput (true when a member's is, false when all are, else unknown); membersImageInput drops the rule's members (the rule's member first changes nothing: one that can't see is passed over). With the owner's real accounts (a copy of the config, Vision off): an image to group/auto-deepseek-v4-1-flash was 400 on origin/main and is answered by the Devin account's deepseek-v4-1-flash ("White"); group/auto-kimi-k3, whose only member that sees is an OpenCode Go key without a subscription, went from 400 to that member's 403. TestGroupImages (a group with one member that sees takes images, all text-only doesn't), TestGroupImagesFromAMemberThatSees, TestKnownTextOnlyModelRejectsImagesBeforeUpstream (an ordered group with its text-only member first sends the image to the one that sees), TestRuleImages and TestRuleImagesUnlistedMember now expect the image answered by the member that sees; they fail on the old code. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
yetone
2026-10-04 14:52:39 +08:00 -
c28924091e
fix(gui): open Help Learn More in the system browser (#759)

Liucwandyetone
2026-10-04 15:01:01 +08:00 -
c530fce7ae
agent: AtomCode follow-ups from the #736 review (#758)

Leslieandgoogya
2026-10-04 15:00:56 +08:00 -
4a5227d91a
fix(gateway): recognize unquoted unsupported parameters (#755)

Guuuu233andDavid Liu
2026-10-04 15:00:51 +08:00 -
1565bb9ce1
usage: the cache hit rate is of the whole prompt, what was written to the cache in it too (Chapin on Discord: with Claude Code started anew at every AmpCode tool round, writing its prompt to the cache again each time, the Usage page still showed a hit rate near 100%) — the Usage page's Overview divided cache_read by input + cache_read, as did the Sessions tab's totals, the TUI's Sessions page and magpie sessions' hitRate; input is what was neither read from the cache nor written to it (the gateway's Usage.prompt() is input + cache_read + cache_write, and the sessions readers subtract both, as alma's and cline's do), so a prompt mostly re-written showed as mostly read. Each now divides by input + cache_read + cache_write, as the tray panel's Usage tab and the Requests tab's strip and rows already did. number-units.test.cjs checks the Overview's rate on its fake ledger (2B read of 1B + 2B + 30M written: 66%, where the old formula said 67%; fails on the old code), TestHitRateCountsCacheWrites checks hitRate (20% of 10 + 20 + 70), and the TUI Sessions page's fixture, which has writes, now says 67% where it said 69%. GUI: number-units, panel-usage, usage-ledger-chart, usage-refresh and mobile-web pass on WebKit; go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
yetone
2026-10-04 14:13:03 +08:00 -
2ef38a381a
claude: a client that names Claude Code's tool calls by ids of its own keeps its run (Chapin on Discord, magpie 0.1.757 on Linux: AmpCode on Opus 5.5 through the Claude subscription started a Claude Code anew at every tool round, cache reads stuck near 20K while cache writes grew, a hit rate near 7% against 99% with the ids kept) — AmpCode rewrites every tool_use id it was told, and its result's tool_use_id, to a TU-… of its own in each request after, consistently, so none of its results named a call the run's Claude Code was waiting on: findRun looked the results up by id in b.calls, missed, and the request started a run told the whole conversation in one message, a prefix the cache had never seen, while the old one kept waiting on results that never came (until parkedMost or its 30 minutes). The conversation's keys hashed the ids too (hashMessages, under historyKey/follows, turnKey and conversationKeys), so even a run found would have been taken for one whose conversation was rewritten. hashMessages now tells a call by its place among the conversation's calls and a result by the place of the call it answers ("#n", "#?" for one whose call isn't in the messages), never by the ids, so the same conversation under other ids is the same key, and results answering other calls still are not. findRun keeps the exact-id path first; when no result names a call any run waits on, match/remap looks for the one run that is waiting on its reply's calls (parked, not closed, the same model), whose reply's client calls (shown, recorded as the run emits KToolStart/KToolArgs since its reply's KStart) are the client's last assistant turn's one for one, in order, each the same tool with the same arguments (sameArgs: the same JSON value whatever the spacing and key order), and whose last-heard conversation (told) is one the request goes on from (a prefix of its messages, hashed once per request); the client's ids must be distinct and every fresh result must answer one of them. Only one run may match — two conversations the same so far, down to their calls, are "ambiguous" and a run is started anew, neither handed the results; a run whose calls match but whose conversation the request doesn't go on from (compacted, edited, another conversation) is left alone, not aborted, as it may be another conversation's. The results are then named by the run's own ids by position and go through continueWith as before, so the call the agent waits on is answered at once and a call it hasn't made yet (Claude Code makes them one after another) is kept in early under its own id; out-of-order results and two identical calls each go to the call in their place. A run that already had its results (a request sent again) has no call waiting and is not matched. No mapping is kept on the run: with the keys id-free, each later round is paired the same way. The gateway's log says how a request's tool results found their run when it wasn't by exact id, or why none did: "remapped", "history changed", "ambiguous", "process expired" (continueWith found the run ended), "no run waiting" — never what was said. TestRewrittenToolIDsKeepTheRun (three rounds under TU- ids, arguments respaced: one run; fails on the old code with a second run started at the second turn), TestRewrittenToolIDsParallelAndRepeated (two parallel calls, results in reverse order, the second kept for the call not yet made; two identical read calls each answered in their place), TestRewrittenToolIDsNotCrossWired (two conversations whose replies made the same call each get their own result; two the same so far are ambiguous, a new run answers and neither old run is handed anything), TestRewrittenToolIDsNeedTheSameConversationAndCalls (compacted → history changed and a new run; other arguments or one call more → no run) and TestHistoryKeyLeavesOutToolIDs pass, with the existing bridge tests (the Pi compaction rewrite, early results, ToolSearch). go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried with AmpCode itself or a real Claude Code: the tests play the agent's side as the rewrite test does; arguments a client changes between being told a call and sending it back (not only respaced) still start a run anew, as before.
yetone
2026-10-04 14:12:54 +08:00 -
775612f6eb
gui: one list of sessions — the Sessions page shows what each session spent, and Usage → Sessions' latest sessions open there (#752, hisiling on v0.1.814: the top bar's 会话 listed every session by agent and folder with resume, delete and the trash but no tokens or cost, while 用量 → 会话's 最近的会话 had folder, model, tokens and cost and resumed too but couldn't delete or group by folder; both opened to the same time, folder, session id and resume command, so the same list was drawn twice with half missing from each). /api/sessions/manage already carried each session's tokens, cost and models (sessions.Managed embeds Session, and ListAgent assembles it priced), the page just never drew them; its rows now end in the same two columns Usage's list has (sessSpent: tokens with in/out/cached under them, ≈cost, "—" with its reason where there is no known price), the sub line names the models and the gateway's route (sessModelsText, shared with Usage), the details gain the per-model breakdown and the Routed lines (sessUsageDetail, which sessionDetail now uses too, so the top-sessions card is as before), and its filter also matches a model. manage now also sends each session's via (usage.Vias since the agent's oldest session, as /api/sessions does), so routing isn't lost; a null models list is read as empty. Usage → Sessions keeps its statistics, charts, top sessions and its latest-sessions list, as that list follows the range, agent, model and folder filters the Sessions page doesn't have: its rows keep title, folder, models, tokens and cost, lose Resume, Open in session terminal and the in-place details, and open on the Sessions page instead (role=link, Enter/Space too, a chevron, title "Open in Sessions" / 在会话页打开): the session's agent is picked, its folder unfolded, the row opened, and once the list is drawn the row is brought into sight — a scroll the reader's click asked for (scrollOnPurpose(e) at the click, then readerScrolls and readerLeaves when the row arrives, as the quota cards' focus does). An All sessions / 全部会话 button by the list's search opens the page on the agent picked there. At 560px and below a Sessions page row drops its tokens column, keeping the cost, so the title keeps room beside Resume, terminal and delete. The other way round (deleting and the trash moved into Usage) was not taken: the Sessions page's per-agent, per-folder tree with picking and the trash is the page made for it, and Usage's list is a drill-down of its filters. No <select>, no left-border accent, no click scrolls on its own, the Agents grip untouched. The GUI has en and zh tables only; both have the new strings. TestSessionsManageCarriesUsage (a Claude session from testdata plus a gateway record: tokens, models and via in sessions/manage) fails on the old handler; sessions-usage-open.test.cjs (WebKit, en and zh) fails on the old assets and passes, and sessions-*, click-scroll, usage-fold and routing-sessions pass; Chromium doesn't launch on this machine. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried with real session files beyond the fixtures.
yetone
2026-10-04 14:10:38 +08:00 -
3eae3ee7fb
gateway: a Claude stream held for its reasoning keeps the agent alive with SSE comments, and a request whose agent went away is recorded as canceled, not as a 200 done (#751, margbug01 on v0.1.679: Alma → magpie → Claude Code (Claude Max, claude-opus-5-5) ended with "No LLM streaming data for 240000ms", while the routing record said 200, about 240000 ms, TTFT about 4.3 s, 8 tokens out, done with no error) — for a Claude model, refusesAfterThinking, a stream that holds nothing but reasoning is held for up to holdThinking (4 minutes) so that a refusal after it can still go to another account (#248). Everything written meanwhile was held with it: message_start, the thinking deltas and the protocol keepalives relay sends every 15s (#436). The agent got no byte at all, and Alma's idle timeout for a reasoning model (60s ×4 = 240s, reset by any SSE line, comments included) ran out first. The hold is on for nearly every first try (!last || again < retries || …), so one Claude account alone was enough. The TTFT on the record is the held stream's first event, not a byte delivered. Now, once a stream has been held for its thinking or safety buffering past keepHeldAfter (holdLongest, 15s), holdWriter.keepAlive sends the agent the stream's 200 and headers once, then a ": keepalive" comment every keepaliveEvery while the stream stays held. The comment is not an event in any protocol and says nothing of the reasoning, and no model text is made up. The keptAlive is the request's and is shared by its tries. A refusal after the comments still goes to the next member: its pass() skips the headers already sent, and its stream follows in the same response. Once headers are out, a failure can no longer be a status, so the gateway's writeErrors inside the try loop go through failTo and holdWriter.release goes through streamError, which tell it as the stream's own error event (Anthropic event: error, Responses response.failed, Chat's error chunk). The last try's writer is held too once headers are out. A Responses agent (Codex) doesn't count comments, but its 300s timeout outlasts the 4-minute hold. When the agent went away, serveSubscription's AfterFunc stopped Claude Code, but relay still called enc.finish(), which wrote message_stop to the gone writer. holdWriter.see took that as the reply made whole (ended), so serve's
r.Context().Err() != nil && !hw.endedcheck, which records 499 "the agent canceled the request" with Fail canceled, was skipped and the request was recorded as 200 done. see now ignores a stream end written once the request's context is done, and the bridge logs "Claude Code run on <model> stopped: the client went away <t> into the reply". TestHeldThinkingKeepsTheAgentAlive sends claude-opus-5-5 thinking that goes on (pings every 10ms until the client hears a keepalive, else 3s) through a group over a real HTTP gateway. It covers three cases: a refusal after it, where the client gets ": keepalive" before b's answer in the same stream, with one message_start, no reasoning text and Tries[0] refused; an answer after it, which arrives whole after the comments; and a refusal with nobody left, which arrives as a 200 stream with event: error. TestClientGoneRecordedAsCanceled checks a holdWriter whose context is canceled before message_stop, and a fake Claude Code run (fakeClaudeCalling's hang) stopped mid-reply through serveClaudeSubscription writing to a holdWriter. With keepAlive's call and see's context check taken out, both fail: the client heard nothing until the upstream gave up waiting at 3s, and ended was set. go vet, builds (darwin, GOOS=linux, GOOS=windows), go test -tags nogui -count=1 ./... under a temp HOME, and -race on the gateway's refusal, thinking, keepalive and cancel tests all pass. Not tried with real Claude Code, a real Claude Max account or Alma. Not changed: --thinking-display summarized is passed only when the request names an effort, so Claude Code's own thinking display is otherwise left at the API's default; relay's keepalives stop 5 minutes after the last event (keepaliveLongest); and Gemini clients get no keepalive.
yetone
2026-10-04 14:08:35 +08:00 -
8031edc78b
agent: AtomCode support — one provider account, one model table per catalog model, as its own sign-in writes (#736)

Leslieandgoogya
2026-10-04 14:11:14 +08:00 -
2e77c546d7
routing: label Codex background memory groups (#753)
Zhu Wang
2026-10-04 14:06:29 +08:00 -
bc5c45e02c
provider(copilot) + gateway: a Copilot model listed with no supported_endpoints (GPT-4o, GPT-4.1, the models a Student/Education plan is served) is asked on chat completions, and Copilot's /v1/messages "no model endpoints available given user constraints" goes on to the model's other APIs (#754, on v0.1.813: copilot/gpt-4o from Claude Code answered "Copilot: 400 Bad Request: no model endpoints available given user constraints", which read as the plan refusing the model) — Copilot's /models gives the newer models their supported_endpoints, and magpie sends each request to an API listed there (
6f19f60b), but GPT-4.1, GPT-4o, GPT-4o mini, GPT-3.5 Turbo and their snapshots have none, which copilotAPIs took for "not known": every API the provider speaks was tried, an Anthropic client's request went to Copilot's /v1/messages first, and Copilot answers a model it serves elsewhere there with that plain-text line, which wrongEndpoint didn't know, so the request was never tried on chat completions. Checked with a real github.com account (free_github_star_quota) against https://api.individual.githubcopilot.com: gpt-4o and gpt-4.1 answer on /chat/completions (streamed and not), refuse /responses ("not supported via Responses API", unsupported_api_for_model) and refuse /v1/messages with "no model endpoints available given user constraints" (gpt-5.6-terra and gemini-3.8-flash say the same there); a model Copilot doesn't serve at all is model_not_supported on /v1/messages too, so the line means the API, not the plan. copilotAPIs now gives a model with no supported_endpoints chat alone, and copilotSeen keeps it for every chat model the list names, so a model asked for by hand that no picker lists (gpt-4o) is asked on chat too; wrongEndpoint knows the line, so a model on no list (or a list magpie hasn't fetched) still gets past /v1/messages to the next API; and Copilot's errors gain an explain: a refusal that still reaches the user with that line says "Copilot doesn't serve this model on Anthropic's Messages API; it isn't about your plan" after Copilot's own words. The issue's other claims didn't hold in the code: the model list is the account's own GET {endpoints.api}/models with Editor-Version and the other editor headers (copilotModels, filtered by model_picker_enabled/category and policy), the three gpt-5.6 ids in its providers.json are the user's picks kept under the account's entry, not a fixed list; requests go to the token's endpoints.api (apiBase), never endpoints.proxy, so no request met proxy's '"stream": false is not supported' (it does say that, and api.githubcopilot.com still answers both ways); and the message the user saw was Copilot's own, passed on unchanged. TestCopilotModelsWithoutEndpointsOnChat (a fetched list with gpt-4.1 listed and gpt-4o not: both chat, Claude chat and anthropic; the explain is added to that line and not to model_not_supported), TestCopilotAPIs (nil and [] are chat; it expected nil before), TestAnthropicClientFallsBackPastCopilotNoEndpoints (/v1/messages answers the plain-text line, chat completions answers: 200, and the second request goes straight to chat) and a TestWrongEndpoint row fail on the old code and pass. Verified with the owner's real Copilot account through the gateway's handler in-process (temp HOME with a copy of the editor sign-in): on the old code /v1/messages for copilot/gpt-4o and copilot/gpt-4.1, streamed or not, gave the reporter's exact 400; with this change all four answer 200 "OK" through chat completions, claude-haiku-4.5 still goes to /v1/messages. Not tried with an Education account, whose list may differ; gpt-4o and the other models no picker offers stay out of the list as before (VS Code doesn't offer them either), they are only asked on the right API when picked by hand. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
yetone
2026-10-04 13:59:29 +08:00 -
6942904d59
site: the Omarchy screenshots are drawn in the reader's Omarchy theme and turn with it (owner: 我发现一个很严重的问题,就是里面截图的样式不会跟着变)
yetone
2026-10-04 13:49:50 +08:00 -
81879b90f3
providers: a model chip's right-click menu copies the model's id (Discord, ARNO on v0.1.815: 希望provider的模型右击菜单除了测试模型外,还能添加拷贝模型id的功能) — the menu a chip opens in a provider's editor had Test this model (and Asked on … where the provider has more than one API) and nothing to take the id with; a chip shows the model's name where it has one, so the id (zai/glm-5.3-flash under "GLM 5.3 Flash") had to be read off its tooltip and typed. The menu now has Copy model ID / 复制模型 ID under Test this model, through the app's own menu (openRowMenu) and copy(): magpie's /api/copy, the page's clipboard and the copy command after it, as every other copy in the app; the footer says "Model ID {model} copied" / 已复制模型 ID {model}. It is on where the model can't be tested (an own-API subscription's, a classifier's) too. The app has English and Chinese strings only (i18n.js has no Japanese table). model-copy-id.test.cjs (a relay's prefixed, plain and hand-picked ids and an own-API account's: the id, not the name, is posted to /api/copy, the footer says so, nothing is tested, picked or scrolled, no left-border accent; en and zh) fails on the old code and passes in WebKit; model-test-why expects the second item beside the disabled test one. model-test-one, model-test-why, provider-typed, provider-detect-models and click-scroll pass in WebKit; Chromium doesn't launch here, so not tried there. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried in the app's own window (its /api/copy path is the one the other copies use).
yetone
2026-10-04 13:35:46 +08:00 -
a0d41044bb
providers: a provider's model list says the context window agents are told — the one set on it, else models.dev's for a prefixed id — not only its vendor's list's (Discord, ARNO on v0.1.815: provider的上下文窗口覆盖功能好像失效了。cline插件的模型都没有默认的上下文窗口大小 … 如zai/glm-5.3-flash,cline-free/mimo-v2.6-flash) — the Providers page's models (providerInfo's modelJSON.Context) took catalog.Model.Context as the vendor's list or the plugin gave it, while the catalog's entries (entryFor: /v1/models, every agent's config, routing) take the window the user set (Provider.ContextOf) over it, and models.dev's for the id (catalog.ContextOf, which already reads zai/glm-5.3-flash as glm-5.3-flash) where the list says none. So a window set in the editor or with magpie model context reached agents but not the page, the agents' model pickers (which read the same list) or a chip's 1M tag, and a model listed with no window — Cline's plugin gives every model 0 (fixed in its 0.1.2), a relay's prefixed ids — showed none. The window is now one function, Provider.WindowOf (the user's, else ListedWindow: the list's, else models.dev's), which entryFor and providerInfo both read; the page is also told the window before the user's (listed), which the context field's "Each model's most" pick compares against, so a model set to its most still offers it. Lookups by a prefixed id were not broken: in the owner's config 14 of 794 models had no window on the page and one from models.dev for agents (Gemini's, Kiro's auto), the same split. TestProviderModelWindowSaid (a relay serving zai/glm-5.3-flash and own-model: the page says 1M before a window is set and 200K/64K after, as Served does) fails on the old code (the page said 0 where agents were told 1000000); TestPluginModelWindow (Cline's plugin with cline-pass/glm-5.3-flash, cline-free/mimo-v2.6-flash: models.dev's window, then the one set) guards the plugin path, which was already right for agents. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass; model-rate and editor-short-window pass in WebKit. Not tried with the reporter's Cline account; an agent already running keeps the window it started with until it rereads its config.
yetone
2026-10-04 13:29:04 +08:00 -
7cc44b68a9
agent: Codex on a routing group's model name is still connected, not "switched off it outside magpie" (#750, Windows 11, magpie 0.1.812, Codex 0.160.0: Codex set to group/auto-gpt-6-1-sol in magpie, its config.toml then reading model = "gpt-6.1-sol" with openai_base_url still magpie's gateway, and the Agents page kept saying 在 magpie 之外被从 … 切走了 with 重新接入, coming back a while after each reconnect) — the gateway takes a model's name without a provider in it as the group of that model (provider.GroupFor: gpt-6.1-sol is group/auto-gpt-6-1-sol), so Codex's requests still went to the group; but Drift compared the field with what magpie recorded setting (applied.json) by string, and gpt-6.1-sol is neither that nor one of magpie's models by magpieValue (isMagpie wants a provider/model id, and Codex's options list it as its own or not at all), so it read as "replaced". What writes gpt-6.1-sol into config.toml is not magpie (no agent path writes a group's model name; set writes the group id as picked) and wasn't found — the Codex app persisting its thread's model by its own name is the likely one — which is why it came back after each reapply. Drift now passes over a field whose value and magpie's recorded one reach the same routing group, one by the group's id and one by its model's name (sameGroup, groupNamed: a group/… id as it is, "magpie/" taken off, else GroupFor); Codex's internal wired() and Agent.Wired (through a new Agent.Routed hook, Codex's routed(): openai_base_url or magpie as its provider) count the group's model name as magpie's only when magpie set that group on the field and Codex's config still sends its model to the gateway, so a Codex taken off magpie on gpt-6.1-sol is not connected, and a Codex's own gpt-5.5 that happens to have an auto group is no more magpie's than before (failover's base URL is untouched). Disconnect takes the field off the group's model name as it does off the group's id (back to Codex's default), rather than leaving Codex asking OpenAI for gpt-6.1-sol. Another group's model (gpt-6.1) or one no group has (gpt-5.5) is still "replaced". The reporter's ask to say which setting differs: the drift's detail already names it ("model is …, not … as magpie set it", the base URL or provider table for unwired) in the status line's and the Apply again pill's tooltip; that is left as it is. Tests: TestCodexGroupByItsModelName (two providers serving gpt-6.1-sol and gpt-6.1, signed in with ChatGPT and with an API key: group/auto-gpt-6-1-sol set, then config.toml's model rewritten to gpt-6.1-sol — Wired and no drift; no route to magpie — not wired; gpt-6.1 and gpt-5.5 — replaced) and TestCodexGroupByItsModelNameDisconnect fail on the old code (not wired, drift replaced, gpt-6.1-sol and the base URL left after Disconnect) 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 Windows or with the reporter's Codex and groups; not checked: what in Codex rewrites the model to gpt-6.1-sol.
yetone
2026-10-04 13:10:54 +08:00 -
548774584f
gui: a credits count stacked under its window's name breaks at its · in WebKit too, rather than being cut to "剩余 100…" (#694, found beside Hu9956's screenshot of the Usage page's credits rows) — stacked, .quota-n was a block with two nowrap spans and a space between them; WebKit leaves that space out when it checks whether a line fits, so a count a few pixels too wide ("100 / 100 积分 · 剩余 100%" at 139.7px in a 137–139px card) stayed on one line and was cut with the ellipsis, where Chromium breaks it at the ·. Stacked, the count is now a wrapping flex row of its parts with a 4px gap (the gap quotaFit already counts), each part whole or, on a card narrower than one part, cut on its own. quota-count-wrap.test.cjs puts WorkBuddy, WorkBuddy AI and Trae CN credits cards at widths 600–1100 in WebKit and Chromium and checks no count or part is cut; its WebKit half fails on the old CSS at 990px. quota-credits, quota-families, quota-pools and quota-curve pass in WebKit.
yetone
2026-10-04 13:10:55 +08:00 -
d6096e2fb1
routing: a failed request whose route has no order opens when clicked (Discord, mythfish on v0.1.810: on the Routing page's 实时 list, clicking "11:28:08 AM • workbuddy-ai/deepseek-v4.1-flash • 400 · 失败 6.5 秒" did nothing; the request was refused with WorkBuddy AI 400 {"message":"WorkBuddy AI: Invalid request parameters","type":"invalid_request_error"}) — Route.Order has no omitempty and the trace's snapshots (Trace, the saved copy in update, sessionLatest) copied it with append([]Weighed(nil), r.Order...), so a route begun with no seats weighed reached the page as "order": null, while its tries were there. routing.js reads r.order as a list throughout: a row's click runs pick → sync → rebuild, whose setOf(r) is r.order.map(seat), and renderActs runs r.order.forEach, so pick threw (webkit: "null is not an object (evaluating 'r.order.map')") before the story was drawn and the click looked dead. Reproduced with a live trace fixture: a 400 with order null fails to open; the same 400 with order [] or a one-seat order, every failure kind, and an order whose seats don't match the try all open, so it is the null and not the 400 or the WorkBuddy error. The page now fills in a route's lists where routes come in (whole: order, tries and left default to []), for the live trace, a kept day's history and openRoute's fetched route (the tray panel's and Sessions' links), and the gateway writes Order as [] when it is empty in all three snapshots, so an older page reading a newer gateway is fine too. Tests: routing-failed-open.test.cjs (webkit, en and zh, live and a kept day: a request that answered and the report's 400 with "order": null; clicking the 400 presses its row, shows the story "How the request at … was routed" / "… 的请求是怎么路由的" with "It said: WorkBuddy AI: Invalid request parameters" / "原话:…", doesn't move the page, and the answered one opens again after; no page errors) fails all four on the old routing.js and passes; TestTraceOrderIsAList (a route begun with no order and refused with a 400: Trace's JSON has "order":[], sessionLatest's copy a non-nil Order) fails on the old trace.go with "order":null and passes. The routing-*.test.cjs and click-scroll GUI tests pass in webkit (routing-time's zh case failed once in a 4-way parallel run and passes on its own). go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not found: which gateway path began the reporter's route with no order — the plain chat path weighs at least the provider it was asked for — so the fix is to the shape, not to that path; not tried with the reporter's WorkBuddy AI account. The 400 itself (WorkBuddy refusing the request's parameters) is a separate question this doesn't touch.
yetone
2026-10-04 12:22:45 +08:00 -
8c74f935fd
gui: Codex's title reconsideration and title_generation calls are tagged Title in Routing and Usage, not shown by their raw names (#742, a first step: what Codex's own calls are should be plain to see) — magpie keeps every x-openai-subagent and thread_source Codex sends as the call's kind (guardian/guardian_review/auto_review, review, compact, memory_consolidation/memgen, thread_title, collab_spawn…), and routing.js's KIND map names each, but thread_title_reconsideration (the turn in which Codex's TUI reconsiders a thread's title) and title_generation were left out, though the gateway's isTitleKind treats both as titles (they inherit the chat's session and follow the Codex titles setting), so their tag read "thread_title_reconsideration" under a "made this call itself" tooltip. Both map to Title (标题) now; Codex's recap turn (thread_source "system") keeps its raw name, being no title, review or memory. routing-kind.test.cjs adds a thread_title_reconsideration route, expected as Title/标题, which fails on the old map; its /api/providers fixture gains presets and excluded (app.js read them and put an error in the footer status) and its viewport is 100px taller, so the seventh row isn't under the footer when it is clicked. BROWSER=webkit node --test internal/gui/tests/routing-kind.test.cjs passes 6 runs in a row; chromium could not launch on this machine (Chrome for Testing: icudtl.dat not found in bundle), before and after. go test -tags nogui ./internal/gui/... passes.
yetone
2026-10-04 12:18:02 +08:00 -
6525e88001
gateway: Codex gets a title it can read from a magpie model, with magpie as its provider too, and a title answer with none in it shows as failed in Routing and Usage (#743, DuGuaiYA on v0.1.807, Windows: with Codex's session-title model set to a magpie model or group, Codex sessions got no title) — two gaps. Codex's TUI writes a thread's title in a hidden ephemeral turn (thread_source thread_title) that asks for {"title": …} through text.format's json_schema, and parse_thread_title takes the reply only when it starts with { and reads as that object, dropping anything else without a word. magpie keeps text.format only for a Responses upstream, so a Chat or Anthropic model answers in plain text ("Fix login bug"), which Codex turns down. codexTitle (#705) wraps such an answer as {"title": …}, but codexTitlesTo was consulted only on /backend-api/codex/responses, Codex's ChatGPT-sign-in path. With magpie wired as Codex's model provider (model_provider = "magpie", /v1/responses: no ChatGPT sign-in, or an API key) the title request came to handle(), the setting was never read, and the conversation's magpie model's plain answer went back as it was; on the backend path the same happened to a title on a magpie model (a model id with "/") whenever the setting was left empty. Reproduced with codex-cli 0.160.0 in a sandbox HOME/CODEX_HOME against a test magpie and a fake Chat upstream: the title request reached /v1/responses (usage kind thread_title, /v1/responses → /v1/chat/completions), the model said "Fix login bug" and the thread's name in state_5.sqlite stayed empty. codexTitlesTo(h, body, ours) now gives, for a title request: the setting when it is set ("off" or a model), else the model Codex asked for when it is magpie's (every model on /v1, a "/" id on the backend path), else "" (gpt-5.6-luna on ChatGPT goes as before). handle() sends a Responses title request to codexTitle as codexBackend does. titleJSON also drops a leading <think>…</think> a reasoning model leaves in its text, where it made "<think>" the title. When the answer holds no title (empty, or JSON with no title), Codex still gets a finished reply with no title, and the call is no longer a silent 200: codexTitle hands serve a check through the request's context (titleCheckKey/replyCheck) that serve runs on the captured reply before it records the call, so the Routing view's route and the usage row carry "title: no title in the model's answer, so Codex got none: …" (or "…answered with no text…") and count as failed, the vendor's tokens still billed. Codex's base instructions and identity are untouched. TestCodexTitleOnMagpieProvider (Codex's title request on /v1 and on the backend path with a magpie model and the setting empty, "off" heeded on /v1, a plain /v1 turn left alone, an empty answer recorded as failed in both views) and TestTitleJSON's <think> case fail on the old code and pass. go vet, builds (darwin, GOOS=linux -tags nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Rerun with codex-cli 0.160.0 in the sandbox: the thread is named "Fix login bug". Not tried on Windows or with the reporter's group; a group's member that answers with the schema (a Responses upstream) is read as JSON first, as before.
yetone
2026-10-04 12:09:23 +08:00 -
680ca5508f
library: RTK's savings are read from rtk gain's stdout alone, a count rtk couldn't give says why instead of "Nothing saved yet", and the page says whose commands the count is (#741 part 2, on Windows with magpie 0.1.807, RTK 0.51.0 and Codex: the RTK card said 还没有节省记录 while
rtk gainin a terminal listed savings) — ReadRTK ranrtk gain --daily --format jsonwith CombinedOutput and json.Unmarshal'd the lot, and took any error, from rtk or from the JSON, for nothing saved. rtk writes warnings to stderr with the JSON on stdout — on Windows the console code page it has no decoder for (warn_unmapped_codepage), a command it couldn't resolve, a history table it found damaged — and any of them in front of the "{" made the output unreadable, so the card said nothing was saved however much was. rtkGain now runs it with stdout and stderr apart (stdin closed, 30s at most), parses stdout from its first "{" to its last "}" (a notice printed ahead of it is passed over), and when rtk fails or prints no summary it can read, gainErr says so with what rtk said ("rtk gain: <last lines>", "rtk gain printed no summary magpie can read: …"), shown on the card as "magpie couldn't read what RTK saved: <why>" and by magpie library as "what rtk saved isn't known". magpie counts no agent itself: the figure is rtk gain's, global, every command any agent or terminal ran through RTK on this computer, and the card now says that after it, so an empty one isn't read as magpie counting only some agents. On Windows, Codex with its Windows sandbox on (config.toml's [windows] sandbox = "elevated"/"unelevated", or the older elevated_windows_sandbox / experimental_windows_sandbox features, and not danger-full-access) runs its commands as its CodexSandbox account, whose own %LOCALAPPDATA%\rtk\history.db keeps what RTK saved there, or with a restricted token that can't write the user's — either way not in the rtk gain the user runs; ReadRTK reports that as codexSandbox, and the card (and magpie library) says why what Codex saves isn't counted. The count is read again each time the RTK tab is opened, so commands run since show without restarting magpie. TestRTKGainRead uses a fake rtk: a code-page warning on stderr with JSON on stdout (read — the old CombinedOutput code says nothing saved), a notice line before the JSON, zero commands, an rtk that exits 1, and one that prints no JSON, with the exact gainErr; TestCodexSandbox covers each config form. library-rtk-gain.test.cjs (webkit) checks the reason and the scope line, the Codex sandbox note, and that reopening the tab re-reads the count, in English and Chinese. go vet, builds (darwin, GOOS=linux -tags nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass, as do the Library and click-scroll GUI tests. Not tried on the reporter's machine: which stderr line broke it there isn't known (the card now shows it if it's still there), and the Codex sandbox account's separate history is from Codex's and RTK's source, not seen on a real Windows Codex.
yetone
2026-10-04 12:13:51 +08:00 -
5e3343c0ad
library: a failed RTK upgrade says what went wrong — the tool, its exit code and what it last said — on the RTK card, and a finished one says so (#741 part 1, on Windows with magpie 0.1.807, RTK 0.51.0: Library → RTK → 升级 only flashed a red, cut-off WinGet command line) — runInstaller's error was "<the whole command line>: <its last 4 lines of output>", and the GUI showed it in the status pill, which is one line wide and times out, so a long
winget upgrade --id rtk-ai.rtk --exact --silent --accept-source-agreements --accept-package-agreementsfilled it and nothing of why it failed was ever seen; winget also writes its spinner (-\|/) and progress bars between carriage returns to a pipe, so the "last lines" were mostly those. runInstaller now returns installFailed: "<tool> failed (exit code N)" — winget's HRESULT exit codes as 0x8A15…, whether Go reports them as the uint32 or a negative int, with what AppInstallerErrors.h means by the common ones (no newer version, package in use, no network, not found, installer failed, blocked by policy, …) — or "<tool> didn't finish in <timeout>" / "<tool> couldn't run: <err>", then " — " and the last 3 meaningful lines the tool printed (installerSaid: ANSI codes and bad UTF-8 dropped, split at \r and \n, spinner frames, progress bars and winget's licence boilerplate left out, 400 runes at most). winget's "no applicable upgrade" (0x8A15002B) isn't a failure: UpgradeRTK re-reads RTK and says winget has no newer RTK than the one installed yet (or, as before, that winget hasn't got the release GitHub has). In the GUI, the upgrade under way says "Running <cmd> — this can take a few minutes." on the RTK card; one that failed leaves "The upgrade failed: <why>" there (red, selectable, wraps), the pill only says the card has the reason, and the card is read again so the version the installer left is shown; a later try clears it, and a success says "RTK is now <v>" with the new version, as before. TestRTKUpgradeFailureSaid feeds installFailed winget-like output (spinner, progress bar, licence lines, 0x8A150101 both as 2316632321 and -1978334975) and checks the exact message, plus 0x8A15002B, a curl exit 6 through sh, and real processes: one that exits 3, one that isn't there and one that times out. library-rtk-upgrade.test.cjs (webkit) checks the running line, the failure on the card and the short pill, the re-read after a failure, and a retry that clears it and shows v0.51.0, in English and Chinese. go vet, builds (darwin, GOOS=linux -tags nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass, as do the Library GUI tests. Not run against a real winget: the exit codes and output shape are from winget's source and docs, not the reporter's machine, and nothing was installed or upgraded here.
yetone
2026-10-04 12:13:04 +08:00 -
cb2e3bcca4
provider: a held All gives each caller its own providers, Keys and Headers included — 463462cd held All for a request and gave callers a slices.Clone of it, which shares each provider's slices and maps: SetKeyOn, RemoveKey and RenameKey change p.Keys[i] of what Find returns, so a key switched while a GUI GET or Codex's /models held the list changed every other caller's copy before it was saved. All now clones each provider (Was, Keys, Fallback, Models, Headers, AccountProxies, AccountModels, AccountCaps, Contexts, MaxConcurrency, ZhipuTeam; Account, the sign-in's runtime, stays shared). TestHeldAllUnshared.
yetone
2026-10-04 12:12:07 +08:00 -
cb7f442785
gateway: Codex's /models builds magpie's providers once, and cursor-agent's Keychain token is read once a while (#746, sperwe: /backend-api/codex/models called All() over and over, rescanning accounts and running /usr/bin/security some 200 times, 5.3–5.6 s against about 1 s straight to the backend, past Codex's 5 s model refresh, so Codex fell back to the official list) — after the ChatGPT backend's list, codexModels read magpie's: CodexListed (CatalogFor's catalog, and AutoVision through codexListed's descriptions), CodexNativeWindow, CodexNativePicked and CodexListTag (CodexListed again, codexWindowsTag), seven builds of All() in a test with one provider, each going through Accounts() and every agent's sign-in; a plugin's accounts ask pluginOwnUser, and for a Cursor moved onto its plugin that is cursorSignedOut, a
security find-generic-passwordeach time. All() now goes through the hold the GUI's reads use (heldOf,dff14ff4): while a request holds the catalog it is built once (a clone each call, so a caller's change stays its own; resolveIn's own heldOf("all", All) is plain All() now), and codexModels takes the hold (provider.Hold) for what follows the backend's answer. A hold is dropped by a write, as before (store → catalog.Touched → Changed, a plugin's sign-in), and now also by writeLogins and forgetAccountCaches, so an account signed in, switched or removed while one is held is seen at once; a sign-in changed outside magpie shows within the hold's second (heldFor). cursor-agent's Keychain token (readCursorToken, cursorToken) is read at most once in 10 s (cursorTokenTTL, as claudeCacheTTL); forgetCursorStatus, which a sign-in or out in magpie calls, drops it, and CursorToken reads it afresh after asking cursor-agent to renew it, so a renewed or new token isn't missed. AllBuilt counts builds for tests. TestCodexModelListBuildsProvidersOnce counts one build for one /models request (7 on the old code), TestHoldBuildsProvidersOnce one build for two All() calls under a hold, a caller's change not leaking, a provider saved and logins written while held being seen, and TestCursorKeychainReadOnce, with a fakesecurityon PATH, one run for three reads, a fresh one after forgetCursorStatus and after a fresh read. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not timed with the reporter's accounts: here the request built the providers 7 times and builds them once, so their ~200 security runs should come to about a seventh, Cursor's from one a build to one in 10 s; the GUI's held reads (GET /api/*) now share one All() too.
yetone
2026-10-04 12:08:17 +08:00 -
c7f693b506
quota: an Antigravity pool with only its week keeps its models' 5-hour windows (#745, werldl517-cyber: PooledWindows drops per-model 5h windows tagged with a pool when the pool's summary has only the 7-day aggregate) — pools() took each of retrieveUserQuotaSummary's buckets on its own, a disabled or unreadable one skipped, and then gave every model of a group that had any bucket left its Pool; PooledWindows put a pool's own windows in place of every model window tagged with that pool once the pool had any window of its own. So a summary with a group's weekly bucket and not its 5-hour one (disabled, or not sent) left the group's "7 days" and dropped the models' 5-hour readings from fetchAvailableModels, the only 5 hours to be had, from the CLI's and TUI's quota lists, /v1/magpie/quotas and quota alerts (dueAlerts reads PooledWindows too). pools() now puts a group's models in its pool only when the group has a 5-hour window, the one that stands in for theirs (the GUI's poolWindows, which keys on the models' pool, then shows them as families beside the pool's week); and PooledWindows drops a model's window only for a pool window of its span (modelSpan, 5 hours, for a model window without one, as fetchAvailableModels' are), so a window tagged by other means isn't lost either. A full summary (5 hours and week) reads as before. TestPooledWindowsPartialAggregateKeepsModelFallback is the reporter's repro plus the cases they listed (both windows, week alone, 5 hours alone, none), and TestAntigravityQuotaPools gains a summary with Gemini's week and its 5 hours disabled, read end to end through quota(): the models' four windows and Gemini · 7 days; both fail on the old code. 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 Antigravity account answering a partial summary.
yetone
2026-10-04 11:55:38 +08:00 -
1594544041
provider: moved Command Code takes commandcode-auth 0.1.8, whose models take pictures as models.dev says, not none of them (ARNO on v0.1.810, Discord: command code provider 的所有模型都没有自动勾上支持图片输入) — Command Code's /provider/v1/models gives no modalities, and the plugin (0.1.7 and before) answered every model's capabilities.input.image with false, which the plugin host reads as said (host.js imageSaid) and pluginCatalog keeps as the model's ImageInput, over catalog.SeesImages. So every model of a moved plan showed no image input; the built-in, whose list says nothing either, answered from models.dev and still does (not-moved users were not affected). Plugin 0.1.8 (magpie-community/plugins 42e762b) says image only when it knows: the list's modalities, its default list, the CLI's Go table, or a Claude model; of any other it says nothing, and entryFor answers from models.dev as it does for the built-in. The mover's min is 0.1.8, so keepMovedCurrent updates moved users. TestMoveCommandCode checks the min and fails on 0.1.6. Tried through the plugin host with the real Command Code account under a temp HOME (a copy of the models.dev cache, the key read from migrations.json, nothing live written): 0.1.7 gave 0 of the shown models image input, 0.1.8 gave 22 of 24 — Claude, GPT, DeepSeek V4 Flash Vision — with DeepSeek V4 Pro and Flash, text-only on models.dev, left without. go vet, builds (darwin, GOOS=linux) and go test -tags nogui -count=1 ./... under a temp HOME pass.
yetone
2026-10-04 12:00:26 +08:00 -
cdc3140ace
provider: moved Kiro takes plugin 0.1.7, which asks a Builder ID sign-in with Builder ID's service profile as the built-in does (magpie-community/plugins#16, YuanZhe-99: a Kiro Student account was refused the move, the plugin serving no claude-opus-5.5 or claude-sonnet-5.5, which the built-in listed) — the plugin had no counterpart of
40102f74(#422): a Builder ID sign-in names no profile, Kiro answers List-Available-Profiles with a 403 for it, so the plugin listed Auto alone and its usage and chat failed. kiro 0.1.7 (plugins e9d684f) takes the service profile for kiro-cli's, the IDE's and its own Builder ID sign-ins and on that 403; the mover's min now names it, so keepMovedCurrent updates every moved user. Only once 0.1.7 is on npm. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
yetone
2026-10-04 12:01:37 +08:00 -
5f4c3c081f
provider: moved Qoder takes qoder-auth 0.2.6, whose request window holds the reply's max_tokens too (#700 follow-up, kyzhouxu: a group's DeepSeek V4.1 Flash refused near 207K until Qoder's DeepSeek-Flash was taken out of it) — 0.2.5 sent a model's default window (200K for one context_config lists 200K/400K/1M for) while the prompt's estimate alone fit it, so a 180K conversation asking 32000 for the reply went out with context_length 200000 while magpie reports 1M. 0.2.6 (magpie-community/plugins a839119) counts the reply's max_tokens in, sending 400K there; the mover's min names it so keepMovedCurrent updates every moved user. 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.
yetone
2026-10-04 12:06:30 +08:00 -
04d4bae323
agent: ZCode's magpie provider pointed at a magpie on another machine keeps that address and key when magpie syncs its models (Discord, 悠悠哥: ZCode connected in a local magpie for its model list, then its base URL and API key changed by hand in ZCode to a magpie deployed on a NAS, kept going back to the local gateway's, they guessed on each update) — ZCode's Sync, which runs whenever magpie's models change and brings the provider in ~/.zcode/v2/config.json and the rule in provider_config.json up to date, wrote options.baseURL/apiKey and config.api.baseUrl/access.apiKey as gateway.URL() and gateway.Token every time. A sync now keeps an address the provider holds whose host isn't this machine's loopback (onAnotherMachine: an http(s) URL with a host other than localhost, a loopback or unspecified IP), with its key (magpie's token where it has none), and refreshes only the models; one on loopback still follows the gateway, so a magpie on another port keeps rewiring it. Picking magpie in the agent's provider field (Set) points it at this magpie as before. TestZCodeKeepsRemoteAddress: a NAS address kept by a sync in both files while a new model arrives, a loopback one moved to the gateway, connecting in magpie resets a NAS one, and onAnotherMachine's cases. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried in ZCode itself with the reporter's NAS; the models listed are this magpie's, so one the NAS's magpie doesn't serve is refused there.
yetone
2026-10-04 11:45:08 +08:00 -
91fbf15fec
usage: a reply that came in one burst at its end tells no tok/s, and is left out of the summed speed (#731, Arcadia822: a Gemini turn through Antigravity that was one write tool call, out 8264, ms 24360, ttft_ms 24359, read 8,264,000 tok/s and lifted the Usage page's average) — a reply's speed is out ÷ (ms − ttft_ms) since #196, and the sums are Σout ÷ Σ(ms − ttft_ms) (Totals.DecodeOut/DecodeMs). codeassist hands a native functionCall over whole, so a turn whose only content is one big tool call has its first content event a millisecond or two before the end: the window covers delivery, not writing, and its tokens over next to no time read millions a second and pull the summed speed up (the test's three calls: 100 tok/s each in truth, 616 with the burst counted). A decode window now counts only when it timed the writing: usage.DecodeWindow gives 0 when it is under MinDecodeMs (100 ms) or the tokens over it would be past MaxDecodeSpeed (10,000 tok/s), both far from any real stream (the fastest vendors write a few thousand a second); Totals.add (records) and Totals.addRow (ledger rows, which the Usage page sums) both go through it. Such a reply still counts as timed and its TTFT in the mean; only its speed is left out, so a model with only bursts shows no tok/s (the Usage page and the CLI already skip a speed with no decode window). The Routing page's story, which works out a try's speed itself (routing.js speedOf), applies the same bounds, so the burst says "first token in 24 s" and no tok/s. app.js's figure is decode_out ÷ decode_ms from the Go sums and needed no change. Tests: TestBurstTellsNoSpeed (the report's record with two 100 tok/s calls: 100 tok/s summed through summarize and addRow, TTFT mean keeps the burst; DecodeWindow's edges: 1 ms, 0 ms, 3 tokens in 50 ms, 8264 in 500 ms, exactly 10,000 tok/s over 100 ms, nothing written, not streamed) fails on the old rule with 616 tok/s; TestSummarizeTimesFirstTokens unchanged. routing-burst-speed.test.cjs (webkit, en and zh: the report's burst and a 500 ms one tell no speed, 100 and 50 tok/s replies keep theirs, picking a request doesn't move the page) fails on the old routing.js with "8264000 tok/s". routing-sessions, routing-served, routing-effort-change, usage-accounts, panel-usage and click-scroll GUI tests pass in webkit (routing-served failed once in a parallel run and passes on its own twice; its fixture has no TTFT). go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried with the reporter's ledger or a live Gemini stream; a short reply under 100 ms (a title, a few tokens) no longer counts toward the speed either, which moves a summed figure by next to nothing.
yetone
2026-10-04 11:14:20 +08:00 -
4dc0f4aa2e
gui: MiMo's icon is mimo.mi.com's logo (magpie-community/plugins#14, mintonight: the Discover card for Xiaomi MiMo still showed the old icon after MiMo plugin 0.1.7 took mimo.mi.com's) — the plugin's own magpie.icon carries the new logo, but the market's card, the Xiaomi MiMo subscription tile and the MiMo Code agent all draw magpie's built-in mimocode icon (registry.json names it), which was a square-in-a-rectangle glyph. icons/mimocode.svg is now mimo.mi.com's mimo-code logo (the "Xiaomi MiMo" wordmark over the MiMo mark) with its black square taken out, so it is drawn as a mask in the text colour as every other monochrome icon, light and dark, and its viewBox cut to the marks so they fill the tile rather than its middle band. Checked by rendering it as a mask in webkit at 32 and 20 px on light and dark tiles. No test covers an icon's picture.
yetone
2026-10-04 11:28:47 +08:00 -
bc8d0f8381
gui: in Chinese a large count is shortened in K / M / B by default, 万 / 亿 kept as Settings' other choice (#740, hisiling: the Usage page and tray panel said 9557.8 万 and 104.31 亿 beside a model's 1M and context buttons of 128K / 200K / 1M, two scales of four and three digits to convert between, and few find the switch) — fmtN said a count in 万 and 亿 when magpie spoke Chinese unless westernUnits was set; it now does so only when chineseUnits is, so Chinese counts read as English's (1.54B, 80M) and as the prices per million tokens and context windows around them. The setting is a new one, chineseUnits, rather than westernUnits turned around: westernUnits=true chose what is now the default, and an unset one can't tell someone who never looked from someone who chose 万/亿, so an old file's westernUnits is left unread (whoever wants 万/亿 picks it once). Settings → Usage's Number units lists K / M / B first, picked by default, then 万 / 亿; the row is still Chinese's alone. number-units.test.cjs (webkit) now checks K/M/B by default in both languages, picking 万 / 亿 saves chineseUnits without scrolling, the Requests tab, Overview, Sessions tab and tray panel then say 15.4 亿 / 8000 万 and keep it after a reload, and Routing's "today" turns back to 1.54B when the setting does; it 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.
yetone
2026-10-04 11:14:11 +08:00 -
fa1e3947f9
plugin: isolate stalled streams and guard GUI refreshes (#664)
Snowyy
2026-10-04 11:22:31 +08:00 -
50b1aff9a7
fix: normalize call ids for responses relays (#737)
Zheng Li
2026-10-04 11:22:26 +08:00 -
1aef0f3f2d
proc: the login-shell probe asks nushell through /bin/sh, and a value the shell printed instead of expanding counts as unset (#738: with nushell as the login shell, shellEnv's printf '…' "$PATH" "$CLAUDE_CONFIG_DIR" … came back as the text "$PATH", "$CLAUDE_CONFIG_DIR", … — a double-quoted string is literal in nushell — and setUnset exported every one of agentenv.Vars with its own name as the value; Claude sign-in then failed with "mkdir $CLAUDE_CONFIG_DIR: read-only file system" (putClaudeLogin's MkdirAll of that relative name under the GUI's cwd /), and the account lapsed minutes later as "Claude Code is no longer signed in", its sign-in looked for under that folder; Codex, Pi, omp and the rest were pointed at "$CODEX_HOME", "$PI_CODING_AGENT_DIR", "$OMP_PROFILE" the same way, in the GUI alone, which is the one caller of UserPath) — shellProbe builds the command: for a shell named nu it is ^/bin/sh -c '<the same printf>' (nushell has no POSIX quoting to ask with and its PATH is a list, while a child of it gets the environment as any child does, PATH joined back into one string, and sh expands the references; the outer string in nushell's single quotes where a backslash is a backslash, the format in sh's double quotes where \0 reaches printf), any other shell keeps the printf it had; and parseShellEnv takes a value that is the variable's own reference ("$CODEX_HOME", or "$PATH" for PATH) for unset, so a login shell with other quoting than POSIX leaves a variable alone rather than setting it to its name. TestShellEnvNushell runs the probe through a nushell on the machine (skipped where there is none) and checks the command's shape anyway; TestParseShellEnvUnexpanded feeds the unexpanded answer. Verified against nushell 0.116.0: PATH comes back, CODEX_HOME from the environment reaches sh, unset variables are "". (#739)

ylornandylorn
2026-10-03 23:13:50 -04:00 -
f8a3f16dc6
plugin: the community's plugins are looked for updates every hour, not every six (#681, ChenstarMr on v0.1.808: told TraeCN 0.1.5 would come by itself, installed it by hand eleven minutes after it was out) — KeepUpdated ran CheckUpdates 30 s after the gateway's magpie started and then every updateEvery, six hours, so a fix published to a @magpie-community plugin and announced on its issue could take most of a working day to reach the reporter. updateEvery is now an hour: one look at npm's metadata for the plugins installed (no download unless a community plugin, unpinned and switched on, has a newer version; someone else's still waits for a click), cheap at that pace. Bun's own check (KeepBunUpdated, a GitHub release lookup with a two-day hold) keeps six hours under its own bunEvery. The Update button's tooltip on a community plugin says "within the hour" / 一小时内 where it said within a few hours. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass (TestProvidersAnswerWhileListsComeIn, TestUserPath and TestClaudeSignedOut failed once under load with four other test runs going and pass on their own); the plugin-*.test.cjs GUI tests pass in webkit.
yetone
2026-10-04 11:12:15 +08:00 -
ed79a72927
provider: a subscription account can be capped at a share of its usage windows, and routing treats it as used up there (Discord: hold a new Claude account at 70% of its five hours and its week to keep its ban risk down, and let magpie go on to the other accounts) — an account's cap (1–99, keyed by the provider id and the account in lower case, AccountCaps in providers.json beside AccountModels and AccountProxies) holds it once any window that applies to the model is at or past the cap and hasn't reset (Allowance.CapHeld; Aside windows and windows kept for another model are left out). One share for every window rather than one per window: the windows an account has differ by vendor (five hours and week for Claude and Codex, a month or a day for others, per-model weeks), the request was one figure for both, and a lower share for one window would be set by lowering the cap. The gateway's perKeyBarred moves a capped account to the candidates left out (barredOf marks it Capped with its use and when it is back, so the routing trace says "held at its 70% usage cap"), so groups, fallback, Ordered and Spread routing and the Codex/Claude Code switch list (NextLogin: a capped account is moved off, not returned to, and isn't a spare) all go on to the next account; a pinned capped account and a draw over only capped accounts answer 429. When every account that serves the model is capped, the client is turned away as when all are used up — 429 rate_limit_error with Retry-After at the soonest reset — and told the cap was reached, which accounts, at what use and cap, and how to lift it. A cap never spends a Codex reset: weekUsedUp, usedUp, BackAt and spendExpiringNow still go by ChatGPT's own 100%, so an account held at 80% with auto reset on is held and nothing is spent; a lone Codex account with a cap goes through serve() instead of being relayed as it came (codexAccounts), so the cap holds it. The Providers page shows a cap pill on each account's row (on hover until set) that opens the app's own menu — No cap, 50/60/70/80/90%, Other… (typed inline, focused without scrolling) — posting provider/accountcap; each window's bar gets a tick at the cap (from the right when the bar shows what is left), and an account held by its cap says so and until when. zh strings in i18n.js. CLI: magpie provider account-cap <id> [account [percent|off]] shows or sets caps. Tests: TestCapHeld, TestCapReached, TestParseCap; TestCapNeverSpendsACodexReset (80% windows, 70% cap: not used up, no reset due, routing held); TestCodexSwitchedAtCap (the active account at 75% with a 70% cap is switched off to the one at 20%, not back while capped, back once lifted; 100 and an unknown account refused); gateway TestAccountCapMovesOn (first account at 75% capped at 70%: the second is tried and the trace says 75/70; lifted: the first), TestAccountCapEveryAccount (429 rate_limit_error naming both caps, nothing sent, logged 429) and TestAccountCapSpendsNoCodexReset (one account at an 80% week, auto reset on: 429, nothing tried or spent; lifted: 200, nothing spent) — the three gateway tests fail with the cap check taken out; TestAccountCapCmd; account-cap.test.cjs (webkit, en and zh: pill, menu, Other…, No cap, cap ticks, held note, no <select>, no border stripes, no scroll on click). account-models, click-scroll, keep-login, codex-auto-reset, account-return, routing-blocked, routing-account-order, routing-quota-left, routing-auto-reset, routing-manual, quota-pools, quota-families and usage-accounts GUI tests pass in webkit. 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; a cap acts on usage as last read, so a burst between reads can run a little past it.
yetone
2026-10-04 10:15:49 +08:00 -
d51d314ac5
agent: with no Node.js on the machine, Install another agent's npm commands install Node first, in the same line (#727, Sun1090 on v0.1.801: "没写curl的命令… curl xxx && npm xxxx", confirmed as option 1 — Pi, Copilot CLI, Crush and the rest had only
npm install -g …, which a new machine without Node can't run) — installCommands now takes whether npm is here (nodeHere: exec.LookPath("npm"), then the folders proc.UserBinDirs knows — nvm's, fnm's, mise's, volta's, Homebrew's, the ~/.npmrc prefix — and on Windows also the registry's PATH (proc.LoginPath) and %ProgramFiles%\nodejs; npm.cmd there), and where it isn't, npmInstall gives on a Mac or Linuxexport NVM_DIR="$HOME/.nvm" && mkdir -p "$NVM_DIR" && curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.8/install.sh | bash && . "$NVM_DIR/nvm.sh" && nvm install --lts && npm install -g <pkg>(nodejs.org's nvm route; NVM_DIR is set because the installer goes by $XDG_CONFIG_HOME/nvm when that is set, and nvm.sh is read in the same shell since the profile line only reaches new terminals; nvm's Node is the user's, so no sudo), and on Windows, for PowerShell,winget install -e --id OpenJS.NodeJS.LTS; $env:Path = <Machine + User Path from the registry>; npm.cmd install -g <pkg>(";" as Windows PowerShell 5.1 has no "&&", PATH re-read since the MSI only adds Node to new terminals, npm.cmd since npm.ps1 is refused under the default execution policy). Each such command carries node (nvm, nvm-mac or winget); the row labels it "Node.js + npm" and the note says the npm commands install Node with nvm/winget first — on a Mac that nvm's installer stops without Xcode's Command Line Tools (run xcode-select --install), on Windows to run them in PowerShell and open a new terminal if npm still isn't found. With Node here everything is as before. magpie still only shows commands to copy (the list never had a one-click install; the page's Update all/update pill run only for CLIs already installed, unchanged). The via column is 84px so "Node.js + npm" stays on one line. TestInstallCommandsWithoutNode (Pi on darwin/linux/windows, Claude's installer untouched, npm alone with Node), TestNpmIn and TestInstallWithoutNodeParses (bash/zsh/sh -n read the line) are new; agent-update-all.test.cjs gains "no Node.js here" (label, note with/without Xcode, the whole line copied, no sideways scroll, winget note in Chinese) and passes in WebKit (8 tests). go vet ./internal/..., builds (darwin, GOOS=linux -tags nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass but for TestCursorSignInJoinsAWrappedLink, which timed out under load and passes alone. Nothing was installed: the commands were only parsed, not run, and not tried on a machine without Node.
yetone
2026-10-04 09:59:23 +08:00 -
c21cd8c881
sessions: reconcile replayed Claude message usage without persisting message maps (#697)
Fanbuz
2026-10-04 10:01:52 +08:00 -
b69d9089e6
fix(gateway):保留413跨成员回退并说明请求体限制 (#711)
SadWood
2026-10-04 10:01:47 +08:00 -
6a4b860e52
fix(gui): 修复断开确认框的配置预览布局 (#735)
SadWood
2026-10-04 10:01:43 +08:00 -
077e7e0b79
gui tests: routing-manual and routing-time follow the group editor's hints under Routing —
a802d98b(#576) added a second hint under the editor's Routing segs (a group among the models keeps its own routing), so routing-manual's row.locator(".hint") matched two and failed strict mode, and the taller editor put Add a rule below the fold in routing-time, where Playwright's scroll into view is put back by the app (only the reader moves the view) and the click timed out. The UI change was deliberate: routing-manual reads the routing's own hint as the row's first .hint, and routing-time wheels Add a rule into the middle of the view first, as routing-manual already does for its rows. All 30 routing-*.test.cjs (85 tests) pass in WebKit; no app code changed.
yetone
2026-10-04 09:37:57 +08:00 -
7cfb6d35e7
gateway: a reply that breaks off after it began isn't booked as an answer, and the conversation's retry goes to another member (#733, congee949 on v0.1.801: Codex on group/grok4-7 got Cursor's "Unable to reach the model provider" eleven times in a row, each retry kept on Cursor by the turn's affinity, last:cursor kept:true why:turn, while the group's Grok member was fine) — after hw.release() the call.Status < 400 branch took any try whose status was under 400 for an answer, and a stream that has sent its 200 and some text and then fails comes back from translate/relay as 200 with call.Error set: servedCandidate ended the member's rest and its failure count, answered() remembered it as the conversation's answerer, and ruleAnswered booked it for the rule, so the agent's retry went straight back to the member that had just failed. Such a try (status under 400, error set) now books none of that: unanswered() forgets the conversation's stick when it names that account and model (and rewrites affinity.json), and when the error reads as the vendor failing, not a conversation too long for the model (tooLong) or a request it refuses as it reads (refusedWords) — lateRests — and the agent hasn't gone, the member rests as restAfter says for the error at that status (a minute's cooldown, longer each time it fails again; out of credit or quota as those read), so the next request in the conversation goes to another member first; a group of one still asks it, at the back. A reply the agent stopped itself (its request context ended) is booked as before, its stick kept. The partial stream stays as it was, its error event included: nothing is spliced in or replayed. The trace's try keeps status 200 and its error, and gains a Fail (the rest's why) and the Rest; the Routing page reads a try with status under 400 and a fail as broken off (tryOk), not answered: its line says it began answering and broke off, and how long it rests, with the vendor's words below it (zh strings), the row and the member's tally count it as failed, and its rest isn't taken for lifted. Usage is kept as it was, error included, as the tokens were spent; the trace's Served/Swapped/Routed still name the model that began the reply. TestLateStreamErrorNotKept (a group of two, the first member streams text then an error with status 200, for a Chat and a Responses client; the same conversation's next request must go to the second, with no stick left and the try told as failed and resting) fails on the old code (tried broken,broken; no Fail, no Rest) and passes. go vet ./internal/..., builds (darwin nogui, GOOS=windows, GOOS=linux nogui) and go test -tags nogui -count=1 ./internal/gateway/ ./internal/provider/ under a temp HOME pass; node --check on routing.js and i18n.js, the Playwright GUI tests not run (no playwright in the worktree). Not tried with the reporter's Cursor plugin; why Cursor's backend broke off is not known.
yetone
2026-10-04 09:18:29 +08:00 -
b56e1ff931
agent: a model picker says which way each model goes, a pick that takes a connected agent off magpie is asked first, and the switch says what it started the agent on (#726, EZN7L2C3 on v0.1.804: with Claude Code connected, Opus 5.5 moved it under 未设置 with 接入 off, Sonnet 5.5 turned it back on, and neither said "via magpie") — the connected Claude Code picker lists Claude Code's own Anthropic models (claudeOwn, direct "Anthropic") under the "Claude Code" header with nothing saying they are not through magpie, and the ones on its own account through magpie fold into "N models via magpie"; the reporter's Opus 5.5 was the own one, and Set with a model not magpie's unroutes Claude Code (unroute puts back the pre-magpie endpoint and token), so Wired() went false and the row folded under Not set up as v0.1.798 does with an agent not on magpie — what was written and the grouping agreed, nothing said why. Not connected, the row's picker lists magpie's models only, and picking one connects first (Agent.Pick), which is why Sonnet 5.5 turned 接入 on; that pop is the narrow one, where "<email> · via magpie" could be cut off at the end (the reporter at 00:56: the 未设置 dropdown did say via magpie).
yetone
2026-10-04 09:11:13 +08:00 -
5a0559d37f
agent: JetBrains Air reaches magpie's models through a Magpie ACP agent (OnurBen on Discord: Air's model picker should list magpie and its routing groups, as AI Assistant's OpenAI-compatible provider does) — Air's own agents (Claude Agent, Codex, Gemini CLI, Junie) run on the accounts under Settings | Account | AI Providers, a sign-in or an API key with no base URL to point at magpie (AIR-4541 asks for one); what Air does take is any Agent Client Protocol agent listed in acp.json in its config folder (~/Library/Application Support/JetBrains/Air, $XDG_CONFIG_HOME/JetBrains/Air, %APPDATA%\JetBrains\Air) under agent_servers, and the models an agent reports at session/new are its model menu. The new air agent's model field adds one there, Magpie: OpenCode's ACP server (opencode acp, by its full path when found on PATH or in ~/.opencode/bin, as Air may not have the shell's PATH) with OPENCODE_CONFIG naming magpie-opencode.json beside acp.json, which holds magpie's provider, the model picked as the one a task starts on (and small_model) and enabled_providers [magpie], so the menu lists magpie's models and routing groups only; the user's own acp.json agents are kept, and disconnecting takes out that entry and that file. Sync keeps the provider's models current, Check reports the entry taken out by hand (set again puts it back) or the wiring changed, the notice says to pick Magpie in a new Air task, or to install OpenCode first when there is none; a model that isn't magpie's is refused, as Air's own agents are picked in Air. Icon: Air's wordmark from jetbrains.com; zh strings for the notices; README row. Checked with OpenCode 2.0.20's opencode acp under a sandbox HOME, as Air would start it: session/new's model option lists magpie/group/gpt-6-luna and magpie/openai/gpt-6.1-sol only, current the configured one (OPENCODE_CONFIG, OPENCODE_CONFIG_DIR, OPENCODE_CONFIG_CONTENT and the global config all do). Known gap, OpenCode's: with a provider's key set in the environment it starts with (OPENAI_API_KEY, ANTHROPIC_API_KEY, DEEPSEEK_API_KEY, even a dummy one), 2.0.20's ACP server lists that provider and none of its config's providers, model or enabled_providers, from any config file, project one included; an empty key or a credential in its auth.json doesn't. Not tried in Air itself. TestAirAddsMagpieAgent, TestAirWithoutOpenCode, TestAirCheckAndSync; go vet, builds (darwin nogui, GOOS=windows, GOOS=linux nogui) and go test -tags nogui -count=1 ./internal/agent/ ./internal/gui/ under a temp HOME pass.
yetone
2026-10-04 08:55:30 +08:00 -
97b5a386a1
tests: cli-update's tight agent row is looked for where a row is tight now — the subtest made the window 620px and 604px and expected the update pill to keep only its arrow and then the version to go, as they did while an agent's fields stood beside its name. Bisected: it first failed at
f3ba9184(#391, magpie web on phones), where magpie web 760px wide or less puts the fields under the name; the fixture boots as magpie web (bootPrefs.web), so the name's line spanned the row at 620px (~378px of room) and the pill kept its words. Booted as the app's window it would have failed fromd0d098c6(the 「接入」 rows): a connected agent's fields fold away under the row, so the name's line is 378px at 620px (164px before it) and 318px at the window's narrowest, 560px. No behaviour regressed: the container rules (pill to its arrow under 250px, version gone under 150px, name last) are as before, and in magpie web the pill keeps only its arrow from about 490px down, the name never cut, to 320px; the name's line never gets under 150px now, so the version stays. The fixture can boot as the window (open(lang, posts, web)); the subtest checks the window wide, at 620px and at 560px shows everything, and magpie web shows everything at 620px and 540px and the arrow alone, the name whole, at 480, 430, 400, 375, 340 and 320px. It fails with the pill's container rule taken out. Webkit cli-update passes (5/5); agent-update-all, agent-connect, agent-layout, agent-grip, cli-behind, click-scroll and agent-connected-unfolded pass (click-scroll on its own; run in one batch with the others it failed once under load). No app code changed.
yetone
2026-10-04 08:58:51 +08:00 -
b42ff72297
gateway: a Claude subscription's reply goes on when Anthropic's stream is cut short and Claude Code asks again, instead of ending in an empty end_turn (#726, EZN7L2C3 on Claude Code 2.1.288 at effort max: a 601 s turn ended with only 74,427 chars of thinking, output_tokens 5, end_turn, and Claude Code appended "Your previous response had no visible output"; the next turns each wrote ~467k tokens to the cache again, five of them 2.2M, and the 5h window went from 84% to 0) — when Anthropic's stream drops or stalls, Claude Code 2.1.28x closes the open block and writes its own message_stop with no message_delta before it, emits a system api_retry and asks again, streamed (a new message_start); cut short again, it asks once more without a stream and tells that answer only as assistant envelopes, one per content block, each carrying the whole message's id, model, stop_reason and usage (checked against a fake Anthropic API under a sandbox HOME, with the bridge's own flags: one cut, two cuts, two cuts then tool_use blocks, every stream cut). readOutput took that message_stop for the reply's end: relay ended with no stop, which anthropicEncoder.finish writes as end_turn, with the usage message_start had told (output 5, cache write 467,708), and ended() aborts a run whose stop isn't "stop", killing Claude Code's retry and the run kept for the next turn, so the next turn started a new run with the whole conversation in one message, written to the cache again (the 38,623 read at 05:10 is Claude Code's own prefix). Now a message_stop with no message_delta before it marks the attempt cut short and ends nothing: the next message_start goes on in the same reply, its usage added to the attempt's as after Claude Code's own tool call, and an assistant envelope under an id other than the streamed message's (not Claude Code's <synthetic> error message) is that unstreamed answer: its thinking (with signature), text and tool calls are passed on, client calls handed over as from a stream, and the reply ends with its stop on the next line of anything else, the result, or once nothing has come for unstreamedQuiet (300 ms), as Claude Code writes all the blocks at once and then calls the client's tools and waits. A cut short after words or a client call were shown fails the reply (502, "Claude Code's stream from Anthropic was cut short after the reply had begun") rather than repeating the answer after half of it; one with nothing to go on in (the output or the turn ends) fails too, never a false end_turn. readOutput's loop body moved into a closure run under a mutex, which the quiet timer takes as well; toolStart, text, begin, end and failed are shared by the stream and the unstreamed paths. Output accounting itself was right: message_delta's output_tokens reach the client (TestClaudeStreamOutputCounted, which passes on the old code too); the 5 was the cut attempt's message_start count. TestClaudeCutStreamGoesOnInRetry (answer, end_turn, output 12005, run kept), TestClaudeCutStreamAnsweredUnstreamed, TestClaudeCutStreamUnstreamedToolCalls, TestClaudeCutStreamWithNoAnswer and TestClaudeCutStreamAfterWords fail on the old code and pass; the real Claude Code 2.1.289 output captured against the fake API (retry, unstreamed answer, unstreamed tool calls, every stream cut) gives the answer, its stop and a kept run through readOutput and relay. go vet, builds (darwin, GOOS=linux, GOOS=windows), go test -race on the gateway's Claude tests and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried with the reporter's account or a real Anthropic cut. What this doesn't change: a hot switch to another account starts a new run, and Anthropic's cache is per organisation, so the whole conversation is written once on the new account; sonnet and opus caches are separate too, and the sonnet served at 04:56 came from inside Claude Code (its own fallbacks; magpie passes no --fallback-model and maps no opus to sonnet). PR #626 (one work dir, the caller's system prompt cached on its own) would help a new run read more of its prefix; it isn't part of this.
yetone
2026-10-04 08:33:18 +08:00 -
a370877ca4
agents: VS Code's built-in chat (Copilot Chat) — switched on, magpie's models join Chat's model picker as a Custom Endpoint group in chatLanguageModels.json, with no key to enter and no Copilot sign-in needed (wani on Discord: 可以添加vscode自带的agent的配置吗)

yetoneandClaude Opus 5.5
2026-10-04 08:22:47 +08:00