main
2515
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7ea1cb6743 |
claude: a client that goes away while the run answers its tool results takes the turn back, not the whole conversation to a new Claude Code (#1365)
What changed: ended() kept a turn's id (turnUUID, backKey) only until the reply that called a tool. From then on, a client that went away while the run answered the turn's tool results (a cut connection, or the user interrupting to send a message queued during the tool call) could not be rewound: letGo ended the run. The client's next request, its results sent again or the next turn, logged "tool results no run waiting", and a new Claude Code was told the whole conversation in one message, with every image in it, which the model in #1365 took for new. Now the turn's id lasts across its tool rounds, until a reply ends without calling a tool. letGo rewinds the run to the turn's user message, its tool rounds with it, and the run waits at the conversation before the turn. The next request is resumed there (nextTurn's key is backKey) and told the turn since its user message. resume logs "a turn taken back goes on in its run" after the "no run waiting" line. A run started anew for the turn, or one with a call still in the client's hands, is ended as before. Nothing else sent to Anthropic changes. Why: the issue's log showed "no run waiting" four times, then a whole- conversation replay. On current main a queued message with an image is sent beside the tool_result (checked with Claude Code 2.1.295 against a local mock) and match keeps it with the result, so it does not break matching by itself. The MCP call reaches magpie ~3ms after the tool reply's message_stop, well inside match's 500ms grace. What drops the run is a client going away during a tool-result continuation. The exact client trigger in the report is not confirmed. What was run: - New TestClaudeToolResultsGivenUpOnAreTakenBack (fake Claude Code that makes its call through magpie's MCP callback and answers rewind_conversation): the turn's results are given up on mid-reply, the run is rewound to the turn's message, and the resend goes on in the same process, told the turn and not turn 1's words. With ended() clearing the id as before, it fails: no rewind is asked. - Real Claude Code 2.1.295 against a local mock of Anthropic: rewind_conversation to a user message, sent while the reply to that turn's tool result is streaming, answers rewound:true, and the next request's messages drop the turn and its tool round. - go test -tags nogui ./internal/gateway/ (full package) under a temp HOME; -race -count=20 on the new test and the TestClaudeTurnGivenUpOn* / TestClaudeFirstTurnGivenUpOnEnds tests; gofmt -l; go vet for darwin, linux and windows (-tags nogui); GOOS=linux/windows go build. Not tried with a real Claude account or Claude Desktop. |
||
|
|
9f5812f3cf |
agents: an update says each agent's version from and to (37FlowAI on X: "更新后 显示一次各 Agent 的版本变更列表: 例如 Pi 从 v1.0x 更新到 v1.10")
Update all's line, when the run is done, and its toast now list every
agent it updated with the versions it went from and to ("2 agents
updated: Codex 0.155.1 → 0.159.0, Crush 0.3.0 → 0.4.0"); with some
failed, the ones that went well are listed before the ones that didn't.
The line stays one line (cut short when narrow, so the list under it
doesn't move), and its tooltip has each change and error on its own
line. A row's own pill says "Codex updated: 0.155.1 → 0.159.0".
Three new strings, in zh, zh-TW, ja and de.
Run: agent-update-all and cli-update in Chromium and WebKit (14 pass in
each); the new checks fail on origin/main's app.js (5 fail). A new
case updates two agents in en, zh, ja and de, and checks the line at
420 wide keeps its height with no sideways scroll. gui-de, gui-ja,
i18n-keys, cli-behind, click-scroll and agent-cli-missing pass in both
engines (22 each). Not tried in the real window with real CLIs behind.
|
||
|
|
1734d2b33d |
update: magpie web in a Docker container says to pull the new image instead of only linking the release page, and magpie update in one says the same instead of failing on the write (Chapin on Discord: 网页端的更新按钮…跳转到github)
The image runs /magpie as nonroot (uid 65532) in a read-only distroless root with no pkexec, so the updater's placeExe found the binary's folder unwritable and no way to elevate: stuck "not-writable", and the page said to run `sudo magpie update` or reinstall from the release page, neither of which works in a container (no sudo, and a replaced binary is lost when the container is recreated). The Download button there opens the release page, which is what Chapin saw if they run the image. Now, when the folder can't be written, nothing can elevate, and gateway.InContainer() (the existing .dockerenv / run/.containerenv / cgroup check, now exported) says this is a container, the stuck reason is "container" and the page says: pull ghcr.io/yetone/magpie:latest and recreate the container (new string in en, zh, zh-TW, ja, de). `magpie update` in such a container fails up front with the same advice, instead of downloading and then failing to replace itself. A writable binary in a container (a user's own image) still updates itself as before. Tested: TestUpdateInContainerSaysPullTheImage in internal/gui (unwritable folder in a container: stuck "container", no StuckDir; outside one: not-writable with the dir; writable folder in a container: the exe is taken) and in package main (in a container the error carries the docker pull advice; outside one it doesn't). Both fail with the container check taken out (gui: Stuck not-writable; main: "the release lists no checksum"). update-stuck.test.cjs now has the container case in en/zh/zh-TW/ja/de; with the app.js line removed it fails 11 of 20; with it, 20/20 in Chromium and WebKit. i18n-keys, gui-ja, gui-de, gui-zh-tw, click-scroll and every update-* GUI test pass in both engines. Real run: the image built from this tree (VERSION=0.1.10), `magpie web` in it with MAGPIE_UPDATE_FEED at a host feed listing 0.1.11: /api/update answered state available, stuck "container"; `docker exec … /magpie update` printed the pull-the-image message and exited 1, the binary stayed 0.1.10. gofmt clean; go vet on ., ./internal/gui/, ./internal/gateway/ for darwin, GOOS=linux and GOOS=windows -tags nogui; full go test -tags nogui ./... under a temp HOME. The page's look in a real browser against the container was not looked at (the GUI test renders the same pill). |
||
|
|
d14b2a2957 |
update: magpie web from the Mac's terminal build downloads and puts in the update itself, as magpie update does, instead of sending the page to the release page (Chapin on Discord: 网页端的更新按钮…跳转到github)
The page's updater took the binary to replace only off the Mac (runtime.GOOS != "darwin"); on a Mac it replaced the .app and nothing else. The terminal build (magpie-cli-darwin-*, nogui, no .app to be in) is what `magpie web` runs on a Mac without the app, so its updater was always "available" with no stuck reason, and the pill and Settings' Download opened github.com/yetone/magpie-releases/releases/tag/vX, with nothing saying why, while `magpie update` in the same terminal replaced the binary fine (ReplaceBinary). updater.place now takes the binary on a Mac too when this build has no GUI (update.GUI false), where BinaryAsset() is magpie-cli-darwin-<arch>, the same file `magpie update` downloads. The desktop app's binary copied out of its .app (GUI true) is still not replaced by the terminal build; the .app is still what the app replaces; off the Mac nothing changes. Which install Chapin has isn't known yet (asked); a Docker container, the other way to land there, is a separate change. The page side was already right: in magpie web the page opens the release link itself (webHost.OpenURL is a no-op, app.js window.open), so the server's own browser is never opened. The feed's url is always a full .../tag/vX (usemagpie.ai/api/latest checked: .../tag/v0.1.1131), and the page opens it only when set, so a link ending in tag/ is not something magpie makes. Tested: TestUpdateTakesTheMacTerminalBuild (bare binary on darwin with no GUI is taken and a newer version is not "available"; the app's binary out of its .app isn't; the .app is; linux either way) fails on the old condition with `the Mac's terminal build: exe "", bundle "", stuck ""`. Real run on this Mac: a nogui build at 0.1.10 as `magpie web` under a sandbox HOME, MAGPIE_UPDATE_FEED at a local feed listing a 0.1.11 nogui build with its sha256: /api/update said ready (downloaded by itself), POST /api/update/install answered 204, the process re-exec'd and /api/update said current 0.1.11, the binary on disk is 0.1.11. The same with origin/main's code: state "available", the install call downloads nothing. The osascript password path for a binary in a folder the user can't write to was not tried. |
||
|
|
3e9bbdaae9 |
gateway: a paused Codex sign-in isn't spent by native compaction either (JasonLeeForOnly on Discord: a ChatGPT account turned off in magpie still had its quota used)
The account Codex is signed in to can be paused in magpie while another of its accounts is on (#263: the user's own ChatGPT account kept for Codex's remote control and Computer Use, another doing the work). Turns on Codex's own models already passed over it (codexAccounts, routing and OwnPaused), but /responses/compact for one of those models still relayed as it came, on the paused account's own token, and on history the others had sealed. compactOn now picks, for that case only, the first of the other accounts on that isn't resting, and codexUpstreamOn relays the compaction signed as that account (its token and chatgpt-account-id, through its proxy), keeping the Accept Codex sent. A gateway key held to some accounts (#905) is asked of the account it goes to, not the paused sign-in. Not paused, nothing else on, an API-key Codex, or a request naming an account: the relay is as before. Not changed: a Codex provider switched off (Off) still relays Codex's own model requests (the Codex app's gpt-5.6-luna/terra side generations, memories, codex-auto-review, native compaction) on the sign-in as they came; that is a design question, reported separately. Tested: TestCodexPausedOwnAccountNotCompactedOn (in use: on the sign-in; paused: acct-2 with its own token; key held to the paused sign-in: 403 with nothing asked; held to the spare: acct-2; the spare off: the sign-in again) fails on origin/main at the paused step (tried [acct-1]) and passes. gofmt; go vet -tags nogui ./internal/gateway on darwin, linux, windows; go test -tags nogui ./internal/gateway ./internal/provider ./internal/agent under a temp HOME pass. Not tried with real accounts. |
||
|
|
4b21fd1a19 |
provider, gui: a MiniMax Token Plan's video windows say their count, "1 / 5 个视频", as MiniMax's own CLI does (#1366, IamMiao)
MiniMax's `mmx quota show` shows the video bucket as counts, 4 / 5 left of the day and 34 / 35 of the week, while magpie showed only a share (used 20%, 3%), and named the windows "Video · 24 hours" in English whatever the language. readMiniMaxPlan now also reads current_interval_usage_count and current_weekly_usage_count, the fields mmx-cli 1.0.27 types and shows (dist/index.d.ts QuotaModelRemain; dist/mmx.mjs). It reads them as the CLI does: usage_count has held what remains as well as what is used, so it is taken as whichever the remaining percentage agrees with within a point, and as no count when neither does or the total is 0 (the general bucket, which keeps its share alone, as in the CLI). The window carries the count as Amount of Limit, in "videos" for the video bucket, the way WorkBuddy's credits are carried, so the Usage page says it before the share, used or left as the reader picks: "1 / 5 个视频 · 已用 20%" or "4 / 5 个视频 · 剩余 80%". A used-up window (status 2) counts all of it used. The video windows aren't routing limits (Aside), so routing is unchanged. "videos", "Video · 24 hours" and "Video · 7 days" are in zh, zh-TW, ja and de. Verified: - TestReadMiniMaxPlanVideoCounts (new), built from the reporter's numbers with mmx-cli's field names: on origin/main's planquota.go it fails (video windows have Amount 0, Limit 0, Unit ""); passes now. TestReadMiniMaxPlan and the other planquota tests pass. - internal/gui/tests/minimax-video-count.test.cjs (new): Chromium and WebKit, 420 and 1100 wide, zh/en/zh-TW/ja/de, used and left: names and counts as above, the general windows share-only, nothing cut, no sideways scroll. On origin/main's i18n.js it fails in both engines (names untranslated). - i18n-keys, gui-zh-tw, gui-ja, gui-de, api-key-usage, quota-* and usage-* GUI tests: 165/165 in Chromium and 165/165 in WebKit. - gofmt -l (nothing), go vet ./internal/provider, GOOS=linux and GOOS=windows go vet -tags nogui ./internal/provider, go build, GOOS=linux go build -tags nogui, GOOS=windows go build, go test -tags nogui ./... under a temp HOME: all pass. Not verified: no real MiniMax Token Plan key here, so the reply was not read live; which way usage_count runs is taken from mmx-cli's own handling, and both ways are tested. |
||
|
|
bf4d55acdd |
gateway: a Codex task sealed by a custom Responses provider still goes back to it after magpie forgets who answered the lead — a day later, after a restart, or after the lead's reply broke off — and the refusal says how to go on (#1367)
Autsunset (#1367): Codex with the lead and its subagents all on xxxx/gpt-6-astra, a relay of ChatGPT Pro behind new-api. After quota and overload errors, continuing the task was refused before xxxx was asked: "This subagent's task was sealed by the ChatGPT backend that answered its lead ... xxxx/gpt-6-astra is neither." The cause: who sealed a task (#1109, #1237) was read only from the affinity stick of the lead's conversation, and that record is lost in three ways: it is kept 24 hours; a restart reads back only the latest 512 conversations from affinity.json; and a reply that breaks off after its 200 (response.failed server_is_overloaded) deletes it (unanswered, #733). Once lost, every later turn carrying a sealed message was refused before anyone was asked, so nothing could answer the lead again to remember it: the task stayed stuck. When the sticks don't make the asked provider a reader, the usage log's answers to the lead's thread and the request's own conversation in the last 30 days now count too (loggedAnswers). A refused or failed call, a title and Codex's own sign-in (Provider "openai" on /backend-api/codex, still told of as the ChatGPT backend) count for nothing. The sibling leadFirst, which puts the lead's ChatGPT account first in a group, reads the lead's key or account from the log the same way. The refusal names who answered the lead when the log knows, and both messages now say how to go on: use the lead's model, or have the lead spawn the subagent again. Verified: - TestSealedTaskStillGoesToTheLeadsProviderAfterItsReplyBrokeOff and TestSealedTaskStillGoesToTheLeadsProviderADayLater (fake Responses relay over httptest, Fernet-shaped gAAAAAB... payloads, lead and worker on xxxx/gpt-6-astra) fail on origin/main with the reporter's exact 400 and pass with the change; the sealed payload reaches xxxx as it came. - TestSealedTaskLogDoesNotOpenItToAnotherProvider: a lead answered by another provider names it; a lead never answered (refused twice) and an answer older than 30 days stay refused. With the 30-day bound removed it fails. - TestSealedTaskGoesToTheLeadsLoggedAccountFirst: with the leadFirst fallback removed the group's other account (acct-1) is tried first. - TestSealedTaskFromChatGPTLeadStillRefused fails if Codex's own sign-in is not left out of the log's answerers. - TestSealedReplyRefusedWhereTheLeadWasNotAnswered now expects the lead moved to another provider to be named "(third)". - go test -tags nogui ./internal/gateway passes (sandbox HOME, pinned caches); sealed tests pass under -race -count=5; gofmt -l clean; go vet -tags nogui ./internal/gateway for darwin, linux and windows. Not verified: real Codex with a real CPA/new-api relay of ChatGPT Pro. If such a relay sends the subagent's request to another ChatGPT account than the one that sealed the task, that account can't open it either; magpie can't see or fix that. |
||
|
|
a8ca790855 |
plugin: the host takes a proxy set after it started (#1363)
iamyhzhao's moved Grok account reads as signed out after nearly every reboot. The plugin host (Bun, host.js) reads MAGPIE_*_PROXY once, at its start, and nothing restarted it when the proxy changed. After a reboot magpie can start before Clash or another proxy app sets the system proxy, so the host had no proxy for its life; the Grok plugin renews by running `grok models`, which got no proxy from the spawn wrapper, couldn't reach auth.x.ai, and token() said the sign-in had expired. Moving to the built-in and back installs and removes the plugin, each of which restarts the host, which is why that worked. Now the host remembers the proxy it was started with (hostProxy of env()), and get() replaces a live host whose proxy is no longer what a new host would get (proxyMoved, looked at no more than every 15s). Run: new TestPluginHostTakesANewProxy (real host on the cliproxy fixture: no proxy, then HTTPS_PROXY set, the CLI gets it; unchanged proxy keeps the same host) passes, and fails with the check disabled. go test -tags nogui ./internal/plugin ./internal/provider ./internal/gateway in a sandbox HOME: ok. gofmt -l clean; go vet for darwin, linux and windows on ./internal/plugin: clean. Not verified: the reporter's proxy setup (Clash with system proxy is the likely case, not confirmed), a real reboot, a real Grok account. |
||
|
|
e6f81585c4 |
gui: a plugin's signed-out account keeps Sign in again and Remove (#1363)
iamyhzhao: Grok, moved onto its plugin, reads as signed out after a restart, and the row then has no Remove and no way to sign in again; the only way out was moving to the built-in and back. The account in use first never had a Remove, and a lapsed plugin account in use still read as "In use" with the reason where Remove sits. Now a plugin's account whose sign-in its vendor refused (marked lapsed, or its allowance read saying the sign-in is gone, as signInGone reads it) shows "Sign-in required", Sign in again (the plugin's sign-in) and Remove (asked first, then login/forget), whatever else it is (in use, the agent's own). Remove on a signed-out row shows without a hover. No new strings. Run: new internal/gui/tests/plugin-signed-out-account.test.cjs in Chromium and WebKit, en and zh, 420 and 1100 wide (26 pass); with this app.js/app.css reverted it fails 18 (only the healthy-account cases pass). account-*, own-signin-remove, saved-signedout-remove, plugin-moved-signin, claude-auth-status, click-scroll, routing-account-*, usage-accounts* and the rest of the 18 related files: 158 pass. Not tried in the real window or with a real Grok account. A built-in (non-plugin) account in use and lapsed is not changed. |
||
|
|
b37a082c48 |
davsync: WebDAV sync reads the backup uncompressed, so its ETag matches the write after it (#1362, laiquziru: TeraCloud synced once, then every sync said "the file on the server changed meanwhile")
What: dav.get sends Accept-Encoding: identity, and unpacks a body a server gzips anyway (Go only unpacks what it asked gzip for itself). Why: Go's client asks for gzip on every GET. InfiniCLOUD's (TeraCloud's) WebDAV is Apache mod_dav_fs, whose PUT answers with no ETag (mod_dav.c's dav_created: "### insert an ETag header?"), so the sync after the first reads the file in full and pushes over the ETag that read gave. Apache's mod_deflate (DeflateAlterETag AddSuffix, the default) gives the gzip copy "<etag>-gzip", which no If-Match on the file matches: every write after the first was 412, the retry read the same -gzip tag, and the state left the local change pending, so every sync after failed. A read asked for as it is gets the file's own ETag. nginx-style proxies that weaken the ETag (W/"...") on gzip are covered by the same change. TeraCloud behaviour assumed: seen without an account on 2026-10-09 against https://domi.teracloud.jp: OPTIONS /dav/ says Server: Apache and DAV: <http://apache.org/dav/propset/fs/1>; GET / with Accept-Encoding: gzip gives ETag "fb-5d88268abcf08-gzip" + Content-Encoding: gzip, without it "fb-5d88268abcf08"; GET with If-Match the -gzip tag is 412, with the plain tag 200. Not seen: that mod_deflate compresses files under /dav/ (needs an account). The fake (apacheDAV in teracloud_test.go) does: no ETag on PUT, gzip + "-gzip" ETag on a GET that accepts gzip, If-Match strong against the file's own ETag. Real conflict detection is unchanged: If-Match is still sent with the version read; TestSyncTeraCloudStillSeesOtherWrites lands another computer's write between a's read and write on the same fake and checks the 412, the merge over it, and b's file kept aside. Run: - new tests fail on the old code: without identity, TestSyncTeraCloudGzipETag "sync 2 after the first: the file on the server changed meanwhile (2 writes refused, 2 reads sent gzip)"; with If-Match dropped, TestSyncTeraCloudStillSeesOtherWrites "write over a replaced version: <nil>, want errChanged"; without the unpack, TestDAVGetGzipAnyway reads gzip bytes. All pass restored. - sandbox HOME, pinned caches: go test -race -tags nogui ./internal/davsync/ ./internal/gui/ . ok; -race -count=5 on the davsync sync/conditional/ short-write/Nutstore tests ok - gofmt -l clean; go vet (darwin, darwin nogui, linux nogui, windows) of internal/davsync; go build darwin, linux nogui, windows Not run: a real TeraCloud account. |
||
|
|
58ba737811 |
remote magpie: a remote magpie's model prices are this magpie's list prices for its models, so both Usage pages count a call at one cost (Sorghum on Discord: 希望本地 magpie 可以自动集成 remote magpie 手动设置的模型价格)
What changed
- The local magpie asks a remote magpie's /v1/models with a new header,
X-Magpie-Prices (provider.PricesHeader, beside the Drawers and
Videomakers headers in listHeaders).
- The remote answers each model's "magpie_price": what its own Usage page
counts the model at (provider.EntryPriceIn: the price its user set by
hand, else its provider's list price at its price rate; a routing group
only when every member costs the same). EntryPriceIn is agent/route.go's
entryPrice moved into provider unchanged, so the agents' cost and the
remote's price are one rule.
- It tells prices only to a request from its own computer or one let in
with a gateway key (local || sharedWith), and only when asked: a machine
let in without a key (MAGPIE_ADDR open, not shared) and an older magpie
or an agent that sends no header get nothing new.
- The local keeps the price with the fetched list (catalog.Model.Price; read
raw, so an odd value or a negative part drops the price and keeps the
model and the list) and uses it as that provider's list price
(ListPrice -> remotePrice). A price the local user set (provider/model,
provider/*, */model) still comes first, and the local provider's own
price rate still applies on top.
- Old remote <-> new local: no field, models priced as before.
Old local <-> new remote: no header, nothing new sent (and an unknown
field would be ignored by encoding/json anyway).
- The model editor's price tooltip and `magpie model price` say the greyed
price is what the other magpie counts the model at (remoteList; new
string in zh, zh-TW, ja, de).
- Side effect: smallModel/cheapest picks over a remote's models (image
description, web search) now see these prices, where every remote model
was unpriced before.
- docs/subsystems/providers-accounts.md: a row for it.
What was run
- New TestRemoteMagpiePrices (real gateway handler behind lanGuard as the
remote, a local remote-magpie provider fetching it): the remote's hand-set
price is the local's price; a real chat call through it costs $9 on both
the local's and the remote's usage records; the local's own price wins
and survives a refetch; an old remote's list leaves the model unpriced.
New TestRemoteMagpiePricesTold: priced for loopback asking, not without
the header, not for a keyless LAN caller with MAGPIE_ADDR open, priced
with a gateway key. New TestLiveMagpiePrice in catalog.
- Broken by hand, each red: no remotePrice in ListPrice (FAIL: priced
false); remote price over the user's (FAIL: remote's price won); the
local/sharedWith gate removed (FAIL: keyless machine priced); the header
gate removed (FAIL: told to a list that didn't ask); sane() always true
(FAIL: a/negative priced). A string-typed magpie_price first failed the
whole list ("not a model list"), hence the raw read.
- gofmt -l clean; go vet ./..., GOOS=linux go vet -tags nogui ./...,
GOOS=windows go vet ./...: clean; go build, GOOS=linux -tags nogui and
GOOS=windows builds OK; go test -tags nogui ./... green under a temp HOME
with pinned caches.
- GUI: model-price-edit (new remote-title check), model-price-tiers,
provider-price-rate, gui-ja, gui-de, gui-zh-tw, i18n-keys: 18/18 in
Chromium and WebKit; the title check goes red with the remoteList branch
removed.
- Not tried between two real magpie binaries on two computers, nor in the
real window.
|
||
|
|
5da6c24e80 |
agents, gui, cli: an agent can be shown only the models picked for it, so a model added later stays out of its list until ticked (#1337, nianlee-official)
nianlee-official asked for an agent whose model list grows only by hand:
today the Agents page's per-agent list hides models one at a time, and
every new model, of a new provider or an old one, shows up in the
agent's picker and config on its own ("New models are shown").
What changed:
- settings: PickedModels, per agent. An agent named in it is shown only
the catalog entries listed for it (after Visible); HiddenModels is not
read for it.
- provider: ModelOff(agent) is the one test CatalogFor, the Agents page
count and Codex's own ChatGPT models (CodexNativeHidden) use, so the
gateway's /v1/models for that agent's key, the lists magpie writes into
its config and Codex's /model all narrow the same way hidden models do.
SetOnlyPicked switches it on or off keeping what is shown as it is
(on: what is shown becomes the picks; off: the listed models not
picked become hidden). SetPickedModels keeps a pick whose provider is
missing for now. renameModelPrefs moves picks with a provider rename.
A model asked for by name still resolves against the full catalog.
- GUI: the list's foot "New models are shown" note is now a switch
"Only models I pick", with a tooltip saying what each side does. On,
a click sends what is ticked ({shown}), not what is hidden, so a model
that arrived after the list was opened isn't picked by accident; the
server still reads {hidden} from an older page. The model in use is
always kept. Hide all / Show all's armed state takes the switch's room.
zh, zh-TW, ja and de strings; the old note string is gone.
- CLI: magpie visible <agent> --only-picked | --show-new; magpie visible
lists such an agent as "only models picked"; magpie models <agent>
says how many are not picked, and what to do when none is.
- /api/settings keeps PickedModels like HiddenModels.
- docs/reference.md: the per-agent list bullet.
Not touched: #1302 (bulk agent visibility) and its files.
Run:
- go test -tags nogui ./... in a temp HOME: 44 packages ok.
- New: provider TestOnlyPickedModels, gui TestAgentModelsOnlyPicked,
gateway TestOnlyPickedModelsList (opencode's key: a new provider's and
an old provider's new model are left out of /v1/models, cursor's key
has them, and a chat request naming fresh/x1 reaches the upstream) and
TestCodexModelListOnlyPicked. With ModelOff made to ignore picks, all
four fail.
- GUI: new agent-models-only-picked.test.cjs, Chromium and WebKit, en/zh/de,
420 and 1100 wide (12/12). With save() sending {hidden} in pick mode it
fails. agent-model*, agent-models*, codex-model-order, cursor-local-models,
desktop-model-menu, click-scroll, gui-ja, gui-de, gui-zh-tw: 85/85.
- gofmt -l clean; go vet -tags nogui ./... on darwin, GOOS=linux and
GOOS=windows; go build, and linux/windows builds.
- The built CLI in a sandbox HOME: visible codex --only-picked, a model
added after it is left out of `magpie models codex` and kept in claude's;
--show-new keeps it out and new ones show again.
Not tried: the real window against a live gateway, or a real agent
reading the narrowed config.
|
||
|
|
e9ed294383 |
provider, tests: a history write holds no reading waiter, and a batch of cached readings notes nothing new (#1358)
TestHistoryWriteHoldsNoWaiter is named for the guarantee that a history
write never holds a caller waiting on a reading, but its second caller
arrives after the reading has landed and answers from the cache: with
the note moved back into the reading's goroutine before close(r.done)
(the regression it is for), the test still passes. The new
TestHistoryWriteHoldsNoReadingWaiter starts caller B while the reading
is still in flight, so B waits on the reading's done channel: with the
regression injected, both callers are held ("both callers held by one
history write", 5/5); on main, the reading's done channel closes before
the note, and caller B answers.
Also from the #1318 review: every LoginUsage noted its batch's history
by reading and parsing quota-history.json under quotaHistMu even when
every reading in the batch was answered from the minute's cache, so a
cached batch re-read the file for points it already had
(TestCachedBatchNoteSkipsTheRead failed with "a cached batch read the
history file 1 times"). noteQuotaHistory now skips a batch whose
readings are all no newer than what was last noted for their account;
the noted times are kept per history file and in memory, so a new file
(or a test's fresh home) starts clean, and a fresh reading is always
newer than what was noted and goes through as before.
Verified: go test -tags nogui ./internal/provider -count=1 (full
package); the five history tests under -race -count=5; gofmt -l clean
on the three touched files; go vet -tags nogui ./internal/provider;
go build and GOOS=linux/GOOS=windows go build -tags nogui.
Generated-by: GLM-5.3-Flash (ZCode)
Review-run: with the note moved into the reading's goroutine before close(r.done), TestHistoryWriteHoldsNoReadingWaiter fails 5/5 ("both callers held by one history write") while TestHistoryWriteHoldsNoWaiter still passes; on origin/main's code it passes 10/10 under -race; the skip taken out fails TestCachedBatchNoteSkipsTheRead ("read the history file 1 times"); skip always taken fails 5 history tests; no per-path reset fails TestMergeQuotaHistory; no goroutine left past the tests; go test -tags nogui ./internal/provider/... ok; history tests -race -count=10 ok; gofmt -l clean; go vet -tags nogui ./internal/provider/ on darwin, GOOS=linux and GOOS=windows ok.
|
||
|
|
40eba22141 |
provider: the partners tests fetch one list at a time, as magpie does, so a fetch Partners started can't store its older list over the one fetched after it (TestPartnersFromTheFeed red on ubuntu CI at 439cfc4a: "partners after the list emptied = acme,regioned")
Cause: TestPartnersFromTheFeed's first Partners() call (to see that
nothing is listed before a fetch) starts a background refreshPartners,
since no list was ever fetched. The test helper fetchPartnersNow called
refreshPartners directly, skipping the partnerFetches guard that
Partners and PartnersNow use, so the two fetches ran together. When the
background fetch read the full list and stored it after the helper's
fetch of the emptied list, the test saw acme,regioned. magpie itself
never runs two fetches at once (only Partners and PartnersNow start one,
both through the guard), so this is a test-only race, not a product
bug. resetPartners also cleared the guard while a fetch could still be
running, which let a late fetch store into the next test's state.
Fix: fetchPartnersNow waits for a running fetch and claims the guard
before fetching (claimPartnerFetch), and resetPartners waits for a
running fetch before it forgets the list.
New test TestPartnersFetchedLastIsHeld: the fetch Partners starts reads
the full list and answers late; the list is emptied and
fetchPartnersNow runs; the empty list must be what is held. With the old
fetchPartnersNow it fails 5/5 ("partners after the list emptied =
acme"). The CI failure itself was reproduced by injecting the delay: a
300ms first response from the feed server and a 400ms pause before the
check made TestPartnersFromTheFeed fail with CI's exact message on the
old code; with the fix it passes 5/5 under the same injection.
Run (temp HOME, Go caches pinned):
- go test -tags nogui -race -count=20 -cpu 1,2 -shuffle=on -run Partner ./internal/provider/: ok
- go test -tags nogui ./internal/provider/...: ok
- gofmt -l internal/provider/: nothing
- go vet -tags nogui ./internal/provider/ (darwin, GOOS=linux, GOOS=windows): ok
|
||
|
|
f368f709c3 |
test(usage): TestLogChangeTrustedAppendReuse counts the prefix it re-hashes, not an empty fingerprint (CI red on Windows since 439cfc4a)
What changed. TestLogChangeTrustedAppendReuse (Windows-only) took
`after.hash != ""` to mean "the read after a trusted append re-read the
historical prefix". Since #1357 (
|
||
|
|
439cfc4ac4 |
usage: the parse is the log fingerprint, dropping the build's full re-read before it (#1357)
What changed. A usage-log snapshot's fingerprint is now produced by the parse itself: each complete line read is fed into a SHA-256. When a build continues the old snapshot's blocks, it continues the old snapshot's digest too (crypto/sha256's state, kept on the snapshot as `digest`); otherwise the digest starts fresh from byte 0 with the parse. The build's before-read logRecordHash over the whole file is gone. The fingerprint covers the bytes actually parsed (`off`), not info.Size(), so a half line another writer is in the middle of appending is no longer counted as read; the unchanged check, the prefix check and the after-read check all hash `off` bytes. What stays, as #1328's tests require (ggbdpq's analysis on #1357): the unsettled unchanged check, the prefix re-verification on a non-trusted continuation, and the after-read check, which now compares the parse's own digest against the file as it is after the read. A same-tick rewrite is still caught at the build, not at a later read. On the trusted, settled path nothing is re-read, as before; that snapshot now also carries a fingerprint (the continued digest), so a later non-trusted append can verify its prefix instead of rebuilding from byte 0. Test changed: TestUsageRewriteBeforeFingerprintIsNotCached counted logRecordHash calls and rewrote the file at call 1 (after the before-read hash, before the parse) or call 2 (after the read). With the parse being the fingerprint there is no call 1 any more, so the hash=2 case could no longer fire. Its guarantee still holds and is still tested at both boundaries: "before-read" rewrites at the build's first change-time look, after its stat and before it opens and parses; "after-read" rewrites at the after-read fingerprint check. Removing the after-read check makes after-read fail. No other test changed. New tests: - TestUsageAppendReadHashesNoPreReadScan: 20,000-row settled log (2,760,615 bytes), 5 x Append + Summarize(Today), counts the bytes logRecordHash re-reads. origin/main: 7,728,984 bytes per cycle (2.80x the log), FAIL; this commit: 4,968,615 (1.80x), PASS. Wall time per cycle on an M-series Mac, 3 runs each: 3.43-3.52 ms before, 2.28-2.52 ms after (~32% less). The remaining two passes are the prefix check and the after-read check, which the same-tick guarantee needs. - TestUsagePartialLineThenCompletion (settled and unsettled): another writer's half line, a read, then the line completed; the fingerprint covers the parsed bytes only and the completed record is counted. On origin/main it fails on the fingerprint check (it covered the half line). Break checks: no after-read check -> RewriteBeforeFingerprint/after-read fails; no prefix verification on a continuation -> SameSizeAndMtimeRewrite append=true, SameTickRewrite append=true and LogGrowingRewriteThenAppend fail; lines not fed into the digest -> SameTickRewrite fails. Run: go test -tags nogui ./internal/usage/... ./internal/gateway/... (ok, temp HOME, pinned caches, removed after); go test -race ./internal/usage/... (ok); -race -count=10 on the rewrite/append tests (ok); gofmt -l (empty); go vet, GOOS=linux and GOOS=windows go vet -tags nogui ./internal/usage/ (ok). Not tried on Windows or Linux hardware; the change is platform-independent hashing over the same read. |
||
|
|
baecbb1be0 |
provider, gateway: Cursor moved onto its 0.2.x plugin gets Fast back, in the agent picker (#954) and for a Routing group's member, on the models Cursor has a fast variant of (#1360, loosheng: 插件 0.2.x 后 Cursor 的快速模式不见了,保存 cursor/<model>:fast 报 "has no fast mode magpie can ask for")
Plugin 0.2.x lists Cursor's picker once per context size (claude-opus-5-5@300k, @1m) and no -fast model, so CanFast, which looked for a -fast sibling, said no for every model: FastFor in route.go, canFast in the group editor, SetFastPick and cleanFast all go through it. The plugin itself still ran service_tier priority as the fast variant. opencode-cursor-auth 0.2.2 (magpie-community/plugins fb253f9) now says fast on a model listed at a size Cursor has a fast variant at; host.js passes it on as plugin.Model.Fast, CanFast takes it, and a plugin before 0.2.2 is still read by its -fast sibling. The Cursor mover asks for 0.2.2. On the way a fast request translated for a moved Cursor (Anthropic, Responses, chat not streamed) lost its service_tier: buildChat sets it only for host "cursor", and a moved provider's host is empty; buildHost names it. Tested: the fake plugin lists sized models with fast on one (FAKE_FAST=tier); TestCursorPluginGroupFast (CanFast per size, @1m:fast refused, a group's @300k:fast sends priority), TestCursorPluginGroupFastSibling (the old -fast list), TestCursorPluginFastPick (moved: picker fast on chat, chat streamed, Responses and Anthropic sends priority, codex's doesn't). Without groupfast.go, host.js's fast or buildHost they fail. go test -race -tags nogui on gateway, provider, plugin, agent, gui; go vet, GOOS=linux/windows vet and build. With the built binary under a temp HOME, the real npm 0.2.2 plugin and Cursor's API answered by a preload (a picker fixture, the agent API's Run caught): /api/state offers fastFor on @300k and composer-2.5, not @1m or grok; /api/groups canFast the same; saving @1m:fast is refused, @300k:fast saved; opencode's picked @300k reaches Cursor's Run with fast=true on chat, Anthropic and Responses, codex's with fast=false. origin/main's binary in the same HOME offers none and refuses @300k. Not tried with a real Cursor account. |
||
|
|
ea6f8f89f8 | routing: a group under the pointer stays where it is as requests come in (cwfox67 on X: picking a manual group's model meant fighting the page, which shook as it live-updated) — the Routing page keeps the groups in place on the screen only while they're in the upper half of the view or a field in them has the focus, else it keeps the requests list's place; at the page's foot the groups sit low in the view, so the list kept its place, grew with each request, and pushed a manual group's card down from under the pointer about to click it. Now the groups are also the part kept while the pointer is on them, and the pointer coming onto them or leaving them takes where the reader is again (readerLeaves), as their own scroll would. No new strings. Tested with a new internal/gui/tests/routing-pointer-pin.test.cjs in Chromium and WebKit at 420 and 1100 wide: wheeled down to the groups, pointer on the manual group's card, 8 requests streamed in (start, prompt read, done each): the card stays within 1.5 px on the screen. On origin/main it fails in both engines at 1100 (588 → 702 px). All routing-* GUI tests (199) and click-scroll pass in both engines. The wheel over the requests list still scrolls the list first (the browser's own scroll chaining); not changed. Not tried in the real window with a live gateway. | ||
|
|
461708907a |
docs: the 10-08 review moves real use and GUI-in-both-engines into the code standards, adds gofmt and a Linux/Windows go vet to the checks, and adds lessons on Windows CI, telemetry disclosure, timing tests, guarantee tests, real vendor pages, CI's Go version and large commits without a body
192 commits and 18 releases on 10-08 (through
|
||
|
|
3c629133c2 |
usage: refresh caches after same-tick log rewrites (#1328)
* usage: same-tick rewrites refresh cached totals and request pages Trust change stamps only when they were settled at the snapshot read's start. Fingerprint recent snapshots and append prefixes, and discard fingerprint trust when the content changes during a read. Fixes https://github.com/yetone/magpie/issues/1305 Verified: deterministic old-predicate FAIL/new PASS; usage tests with race; nogui vet; Linux nogui and Windows builds; usage test cross-compilation for Windows, Darwin and the unsupported-stamp fallback on FreeBSD. Full nogui suite exposed the independent sessions TestList empty-session limit failure. Native Linux GUI vet/build lack pkg-config and GTK/WebKit; native Windows/NTFS execution was not run. * usage: keep FILETIME stamp ages valid beyond UnixNano range Convert whole seconds and the 100ns fraction separately. Checked the epoch, current dates with fractional ticks and year 2500; Windows build and usage test cross-compilation pass. Native Windows execution was not run. * usage: keep history cached after an append settles An unchanged log now validates a recent snapshot's nonempty fingerprint before publishing a settled clone. Preserve its version and sealed blocks so the summary and page caches stay warm without mutating readers' snapshots. Follow-up to the review on https://github.com/yetone/magpie/pull/1328, for https://github.com/yetone/magpie/issues/1305. On e28e686, the real-clock append/read/settle/read tests fail with a version bump and 32,428 additional allocations over 4,000 versus 10 rows. With this change the targeted race run passes, with 165 extra allocations. The existing performance test now measures this path on the real clock. Verification uses Go 1.26.8, a temporary HOME and pinned Go caches. Native Linux GUI checks require missing pkg-config/GTK/WebKit libraries; native Windows/NTFS execution is not available. No GUI assets change. Implementation and verification used AI assistance. Final checks: usage race PASS (421 test results); nogui suite PASS (7,273 test results, 44 packages; 21 skipped tests, 3 packages without tests). Nogui vet, Linux nogui build, Windows build, and usage test cross-compilation for Windows amd64, Darwin arm64 and FreeBSD amd64 PASS. Native Linux GUI vet/build were attempted and failed at the missing pkg-config executable. |
||
|
|
c17cbfa161 |
routing, gui: the folded context window shows a thin bar of what fills it beside how full it is, one line high whatever a live request puts in it, and a request opened from the Usage page's Context tab shows its card unfolded, it alone (PILIPALA @NXirg2 on X, Jeremy.Zhou on Discord)
On af798bd1's fold: folded, the head carries a bar of the window's
parts (system, tools, memory, files, results, chat), morphed with the
head so a live request changes its widths only; the folded head is
pinned at the open head's 20px and doesn't wrap. "Latest request" and a
point of the session line on the Usage page's Context tab open the
request with { context: true }: its card shows unfolded for it, the
next request takes the reader's choice (magpie.ctxShut) again, which is
left as it was; folding that card by its title keeps it folded.
routing-context-fold (new): folded at 1920x1080, 1440x900 and 440x900
in en, zh and de, one line with the bar, the stage and (at 1440 and
wider) the request list in view, nothing running over; opening doesn't
move the page; the Usage page's request opens unfolded for itself only.
routing-live-patch's folded cases: the card, head and bar are the same
nodes across live requests and the card's height never changes. Both
fail on af798bd1's assets. No new strings.
|
||
|
|
03c8d21fde |
gui: opening the folded context window on Routing no longer scrolls the page 4px; the folded card keeps the open card's padding, so its title stays where it is and the browser's scroll anchoring has nothing to follow (af798bd1's fold)
The folded card had padding-block 10px against the open card's 14px, so
the title moved down 4px as the card opened and scroll anchoring moved
the page with it, at the top of the page too. A click never moves the
page. Folded, the card is 50px instead of 42px, still one line.
routing-ctx-fold now opens the card with the title in view and checks
that scrollTop is the same and the title moved at most 1px. On
af798bd1's assets it fails in Chromium and WebKit ("200 -> 204", "0 -> 4");
with this change it passes 3/3 in each.
|
||
|
|
e20e1e9eea |
provider, gui: Detect asks a Gemini API too, at the URL's …/v1beta, with the key in x-goog-api-key and a gemini model from the list, and the editor shows Detect on a Gemini pick (#1346, NagaseMinato: 「检测」不会探测 Gemini 端点,为什么呢?)
Detect asked OpenAI chat, Responses and Anthropic only, and the editor hid
Detect when Gemini was picked. Now detectProtocols adds Gemini:
- a typed …/v1 or a bare host is asked at …/v1beta; a …/v1beta base is
kept; a pasted …/models/x:generateContent URL is cut back to its base
(for every API, so the others aren't asked under ":generateContent");
- the provider's own Gemini URL is asked as it is;
- the probe is the same tinyBody a Gemini test sends, x-goog-api-key, no
Authorization; a model id with "gemini" in it is preferred.
Nothing is saved; "Use these" keeps the Gemini pick and its URL.
Run: go test -tags nogui ./... (all ok), go vet and GOOS=linux/windows
go vet on ./internal/provider ./internal/gui. GUI, Chromium and WebKit:
provider-gemini 8/8 (900px and 440px, en/zh/ja/de labels), provider-detect,
provider-detect-models, provider-base-api, provider-url-each-api,
provider-add-models, gui-ja, gui-de, gui-zh-tw, click-scroll.
Without the fix TestDetectGemini fails ("no model to try") and
provider-gemini fails 4 of 4 in Chromium.
Real: aihubmix (owner's key, sandbox HOME) at https://aihubmix.com/gemini/v1beta
with gemini-2.5-flash: Gemini ok 200; at https://aihubmix.com/v1 chat,
responses and anthropic ok and Gemini 400 (aihubmix serves Gemini under
/gemini). Not tried with a real Google AI Studio key.
|
||
|
|
b946e018cf |
provider: MiMo's mover asks for opencode-mimo-auth 0.1.9, so a moved account with a Token Plan and no app membership is answered by its Token Plan instead of 403 membership_required on every model (yetone: 2 个模型中有 2 个没有响应)
Checked on the owner's real account (Token Plan Lite, no app membership) in magpie-dev after upgrading its plugin to 0.1.9: mimo-app/mimo-pro and mimo-app/mimo-flash answered 200 through the gateway on 127.0.0.1:3426, streamed and not, and the editor's /api/provider/test answered both ok. go test ./internal/provider -run 'Mover|Moved|Migrat|Mimo'. |
||
|
|
f76a14dc52 |
gui: Settings → Observability heads its three groups as every other Settings tab does, a small label over a card of its own, instead of grey bands inside one card (yetone)
Run: otel (new: the headings are #otelList > .row-head labels styled as Network's, each over its own card, none inside one; fails on the old assets), settings-sections, settings-groups, settings-shortcut, mobile-web, readable-focus, search-first, plain-names, lightweight, segs-press-redraw: all pass in Chromium and WebKit; go test ./internal/gui. Screenshots of Observability beside Network, both engines, dark and light, en and zh, 1100 and 420px, looked at. |
||
|
|
b4aa62af96 |
docs: the READMEs' Privacy and the site's "What does magpie send about me?" (en, zh, ja) say a partner's daily counts are sent too
|
||
|
|
97f825d560 |
gui: spinners turn round their own centre: the drawing turns inside the svg, round the viewBox's centre, since WebKit turned a whole svg on a pixel-snapped layer and its ring circled a point ~1.3px off (Agents' "Updating…", Sync, Save, the quota and usage refreshes, the agent fix) (yetone)
Measured on the real screen (headed WebKit + screencapture, 30 frames, circle fit): the ring's centre wandered 2.7 device px before, 0.2 after. Playwright screenshots repaint and never showed it. The CSS ring spinners (sign-in, plugins) measured 0.2 already and are unchanged. #sync's stop-on-a-whole-turn reads the svg's subtree animations. Run: cli-update (fails on the old CSS), agent-update-all, sync-spin, provider-save-busy, agent-app-version, agent-unreachable, aside-native, usage-card-refresh, usage-remote-cards, usage-stale-answer: all pass, Chromium + WebKit. |
||
|
|
05b5965872 |
providers, gui: a partner listed since the add sheet last showed the partners puts a small dot on the "+ Add provider" button, its name in the title, until the sheet shows it (yetone: 有了新的合作伙伴的时候,这里能否有个小小的精致优雅的提醒吸引用户点击查看)
Ran: go test ./internal/provider ./internal/gui ./internal/tui .; partners.test.cjs (chromium + webkit, en/zh at 900 and 440), i18n-keys, gui-ja, gui-de; GOOS=linux -tags nogui and GOOS=windows builds. |
||
|
|
7d663a2769 |
providers, stats: count how often each partner is shown, opened, has its key page opened and is added, per day and capped, and send the days that have ended with the daily stats as "magpie partner" events; off with the stats or with what magpie is used with (yetone: 能否统计每个合作伙伴的展示数和点击数)
Ran: go test ./internal/provider ./internal/stats ./internal/gui ./internal/tui .; partners.test.cjs (chromium + webkit), privacy-usage-stats, i18n-keys, gui-ja, gui-de; GOOS=linux -tags nogui and GOOS=windows builds. |
||
|
|
af798bd1bf |
routing, gui: the context window's card folds to its head, with how full it is beside the title ("114K / 272K · 42%"), and stays folded for the next request and after the page comes back, so the request list under it has the room (0xBCD18E on X: 这个"上下文窗口"的板块非常占"上下"高度,还不能折叠,下面只能展示三行)
ctxCard takes opts.foldable (Routing passes it; the Usage page's cards don't): its title becomes a button with a chevron, aria-expanded, and a click toggles .shut on the card, which hides all but the head. The choice is kept in localStorage (magpie.ctxShut). The head is morphed in place, so the click reads the button from the event, and the card that is there keeps .shut across live patches; a new card takes it from the saved choice. Verified: routing-ctx-fold.test.cjs (new), at 420 and 1100 wide in Chromium and WebKit, 3/3 each: open, then folded to one line (<60px) with the figure, the list under it up by >200px, nothing runs over at 420px; another request picked is still folded; after a reload still folded; opened again, 400 cells and the same height as before. It fails on the old code (no .ctx-fold). routing-*, context-*, usage*, group-context, click-scroll, panel-routing, old-webkit, gui-ja/de/zh-tw: WebKit 371/371; Chromium 370/371 once at --test-concurrency=4, routing-blocked's "picking the request moved the page", which passes 3/3 alone and in two more routing-* runs at the same concurrency (196/196 each). Not filed as caused by this change; seen once under load. |
||
|
|
0d20aaba45 |
gateway: under In turn routing, new conversations sent at once spread over the accounts instead of all going to the one least held, which held one Antigravity account busy while the others sat idle (#1342, handong66)
leastHeld counted only conversations already answered, so every new
conversation in a burst saw the same counts and picked the same account.
A conversation that is opening now counts until it is answered or its
request ends.
Run: go test -tags nogui ./internal/gateway (ok); -race -count=5 on the
rotate/affinity tests (ok); go vet darwin, linux, windows (ok).
TestRotateSpreadsABurstOfNewConversations fails without the fix
("k1 got none of 6 new conversations sent at once ...: map[k2:4 k3:2]",
3 of 3 runs).
Not tried: a real burst on Antigravity accounts. The default (smart)
routing keeps using the first account while it has quota, by design.
|
||
|
|
b2c97659fb |
gateway: a model picked for Responses is asked on Responses again after the relay once answers it there with "use /v1/chat/completions", not sent as chat from then on (01huadalang on Discord: 为什么会转成 chat,我都设置的 response)
A wrong-endpoint answer marks the model unfit on that API until magpie restarts. With the user's per-model API (Names & levels, ModelAPI) the only API usable gives, one such answer left usable empty, and every later request fell back to p.Speaks(): chat first. Now the fallback is the picked API; without a pick it is Speaks() as before. TestModelAPIPickHoldsAfterWrongEndpoint (a relay with chat and Responses URLs, the model picked for Responses, its first Responses answer a 400 "not supported in /v1/responses, use /v1/chat/completions"; a Responses and a chat client each send twice) fails on the old code (the second request goes to /v1/chat/completions, upstream [/v1/responses /v1/chat/completions ...]) and passes with the change. go test -tags nogui -count=1 ./internal/gateway under a temp HOME passes; go vet (darwin, GOOS=linux, GOOS=windows) on ./internal/gateway passes. Not confirmed that this is the reporter's case: their provider and request weren't seen. |
||
|
|
cde2a351e2 |
library, gui: the Skills and MCP tabs' strip has a Discover button that goes to the market under the list, many screens down with dozens of skills (#1348, ttmouse)
The strip that stays at the top of the Library page gets a Discover button on the MCP and Skills tabs. It scrolls the market's heading to just under the strip (through scrollOnPurpose, so the click-scroll guard lets it) and puts the cursor in the market's search box. The strip's buttons now keep together at its end and wrap onto a line of their own when the window is too narrow, so the window's 560px minimum doesn't scroll sideways. New test library-discover-jump.test.cjs (Chromium and WebKit, en and zh, web and window, 1000px and 560px; 60 skills, 40 servers): the market starts more than 700px below the view, the button fits without sideways scroll, and after the click the market is in sight under the strip with its search box focused, and still is 500ms later. It fails without the library.js change (12 failures waiting for the button). Ran: library-discover-jump 64/64; library-*, market-*, click-scroll, gui-ja, gui-de, gui-zh-tw, i18n-keys, skill-*, mobile-web, phone-web, project-* and others in both engines, 520/524. The 4 failures: library-servers-bulk in Chromium fails the same without this change (#1322), and mobile-web's 900px desktop compare timed out / differed by 338px on Providers under --test-concurrency=6; run alone it passed 30/30 twice. |
||
|
|
b3330ea82d |
gateway: a Responses request relayed with a tool call no result answers sends the call a synthetic result, instead of Volcengine coding plan's 400 "An assistant message with 'tool_calls' must be followed by tool messages" (#1341, XcodeFish)
The reporter's own experiment through magpie to the same upstream: a function_call with no function_call_output was turned away with exactly the reported 400, and the same request with the output went through. Volcengine checks the exchange on the chat form it turns /responses into. The Chat path has mended this shape since #1275 (pairToolMessages); the Responses relay had only orphanedToolOutputs, which handles the opposite case (a result with no call). pairToolItems runs beside orphanedToolOutputs for every Responses request not going to a ChatGPT account. A function_call or custom_tool_call that no result in the input answers gets "[The result of this tool call is unavailable: the turn was interrupted.]". As in the Chat path, that result goes at the end of the run of calls and results the call is in, so no result lands between two parallel calls. A result with no call is still left to orphanedToolOutputs. A result naming a call from an earlier request (previous_response_id) stays as it is. Based on ggbdpq's patch on the issue, which put the result right after the call. TestResponsesRelayAnswersALoneCall (through the whole gateway) fails without the change ("the lone call went unanswered") and passes with it. TestPairToolItemsAnswersALoneCall covers parallel calls, custom calls, byte-for-byte pass-through of paired, out-of-order and previous_response_id requests, and that the caller's buffer is left alone. go test -tags nogui ./internal/gateway under a temp HOME passes, as do go vet (darwin, linux, windows) and gofmt. Not tried against the real Volcengine coding plan: the only Volcengine key here is a pay-as-you-go Ark key with no DeepSeek model enabled. The reporter's Codex rollout shows every call paired, so which call went unanswered in their request is not known. They are asked whether it still happens. |
||
|
|
23d9e5def8 |
gui tests: ledger-hscroll checks the Requests table's sideways scroll in a 600px window, where the table is wider by a margin no machine's fonts take away (#1340, wizzy-yang)
Since
|
||
|
|
844b9ca3ac |
provider, gateway, gui: a custom provider's Base URL can be a Gemini API (generateContent): …/v1beta, its models listed from it, the key sent in x-goog-api-key (#1346, NagaseMinato)
The custom provider editor's Base URL has a fifth API, "Gemini compatible"
(Gemini 兼容 / Gemini 互換 / Gemini-kompatibel), beside OpenAI, Anthropic and
Responses; More endpoints has a Gemini URL; the CLI takes gemini=. A
Gemini URL is kept as its base (…/v1beta, a relay's path prefix kept, a
pasted models/…:generateContent cut off). Every client's request, Chat,
Messages, Responses or Gemini, streamed or not, goes upstream to
models/{model}:streamGenerateContent?alt=sse under it, with no model in
the body and the key in x-goog-api-key; models are listed from its
/models, a page at a time, keeping those that serve generateContent.
Detect doesn't ask a Gemini API and is hidden while it is picked.
Factory's Gemini route and Bearer token are unchanged.
With five APIs the pick no longer fits a 900px editor in English and
German, so it wraps to a second row, and its thumb now follows the
picked option onto that row.
Verified against fake httptest upstreams only; no real Gemini key or
relay was tried:
- TestCustomGeminiUpstream (gateway): chat, chat stream, messages, a
Gemini client's stream and non-stream, behind /proxy/v1beta. Fails with
"chat: 404" without the gateway.go change.
- TestCustomGeminiProvider, TestGeminiBase, TestFactoryGeminiUnchanged
(provider).
- go vet, HOME=$(mktemp -d) go test -tags nogui ./... (44 packages ok),
GOOS=linux and GOOS=windows go vet on the touched packages.
- provider-gemini.test.cjs (new, en/zh/ja/de, Chromium + WebKit, 8 pass;
fails with "900px: the thumb is 22px off" without the slide() fix),
and in both engines: provider-base-api, provider-detect,
provider-detect-models, provider-add-models, provider-output-picks,
provider-search, provider-url-each-api, seg-contrast, settings-groups,
menu-scroll, click-scroll, ledger-hscroll, strip-scrollbar,
view-scrollbar, usage-ledger-chart, system-one, gui-ja, gui-de,
gui-zh-tw: all pass.
|
||
|
|
88f2eb8aa5 |
gui tests: signin-paste-codex, signin-again and signin-closes-sheet give the sign-in window they open an empty page instead of the real vendor's (#1307's siblings)
Found by logging every off-host request in a full Chromium GUI run: signin-paste-codex opened the real auth.openai.com (Cloudflare's challenge, the page that hung login-import's clicks), signin-again loaded www.codebuddy.cn with its downloads and trackers, and signin-closes-sheet example.com. Each routed only its page. Now the context answers anything off magpie.test with an empty page; a rerun logged only the three window URLs, no subresources. Chromium and WebKit, all three files: 9/9 pass each. |
||
|
|
f9a1ca0355 | docs: a Privacy section in the READMEs and a "What does magpie send about me?" FAQ on the site's Get started (en, zh, ja): the daily event's id, version and system, and the agents, providers and models by magpie's own ids (a provider of the user's own only "custom"), what is never sent, and the Settings → Privacy switches; node --test site/*.test.mjs passes | ||
|
|
9f0831b239 |
gui tests: copilot-ghe gives the device-sign-in window it opens an empty page instead of the real github.com and acme.ghe.com (#1307's sibling)
Like login-import's ChatGPT window, only the page was routed, so the window GitHub's device sign-in opens loaded the real site (304 requests to github.githubassets.com in one full Chromium run). It wasn't red here (10 at once in Chromium: 0/20 failed), but its outcome rested on pages the test doesn't control. Chromium and WebKit, en and zh: pass, and no request went past the two window URLs, which the route answers. |
||
|
|
9755201b44 |
gui tests: login-import gives the sign-in window it opens an empty page instead of ChatGPT's real one, whose Cloudflare challenge hung this page's clicks in Chromium (#1307)
The browser sign-in calls window.open on auth.openai.com, and only the page was routed, so the new window loaded the real site: Cloudflare's challenge. While it runs, Playwright's clicks on the opener in Chromium stall at "performing click action" past their 5s. Shown with a page that opens the same window and then clicks a button 30 times, 8 at once: real window, every run had 1-2 clicks time out (max 5.0-5.5s), twice over; window routed to an empty page, 0 timeouts (max 1.2-4.0s at load ~100-117). The test itself, 10 at once in Chromium (load 150-190): before 20/20 and 19/20 subtests failed (click or fill timeouts at lines 111-117, after the window opened); after 0/20 twice. WebKit, 8 at once: 0/16. en and zh both run. |
||
|
|
7ddea2c2e0 |
plugin tests: TestHostRunsAsMagpieBun puts the Bun on PATH at the old name as a hard link, not a fresh copy, so macOS's first-run check of a new program no longer comes out of the host's one-minute start under a full go test run (#1306)
A copy is a new program, which macOS checks before its first exec; the host's init ran it inside the test's minute. Timed here with load ~130 and syspolicyd not pegged: a fresh copy's first `--version` took 0.9-1.6s, a hard link of the same bun 0.04-0.3s; with syspolicyd busy the same check took 26-55s a program in #1284. Where no link can be made (another filesystem), the copy is run once before the minute starts. Run in Docker (golang:1.26.8, linux/arm64, bun 1.4.2): -count=3 of the test with bun on a bind mount (copy path) and beside HOME (link path), all PASS; go test -tags nogui ./internal/plugin ok; go vet for linux, windows and darwin clean. Not run: the test on macOS itself (Go tests are kept off this Mac while syspolicyd is overloaded), so the red under a slow first-run check is shown by the timings above, not by a failing run. |
||
|
|
1e405be3fc | gui tests: routing-sessions' suggestion groups answer the names ask with the titles their trace carries, as the gateway does; the Claude Code row has the page ask 300ms after it draws, and the empty answer took the titles away under load (#1307) | ||
|
|
8576e3cc33 | gui tests: routing-sessions leaves the held live trace for a reload to cancel; letting it go just before the reload had the page ask again while going away, which WebKit turned away "due to access control checks" as a page error (#1307) | ||
|
|
023f5aaad2 |
provider: a background reading of an account writes its quota history too (#1318)
* provider: a background reading of an account writes its quota history too
GET /v1/magpie/quotas/history was written only by the Usage page's
reads, so a magpie only ever serving other magpies - nobody opens its
Usage page - kept an empty history while its routing read each account
every few minutes.
When a login's fresh reading is kept, it is now noted in the history as
a page reading is, named for its account (a login's reading itself
carries no user); a read that failed is skipped, not told again. The
test is the reporter's story: a background LoginUsage, then the
history, no Usage page involved; it fails on main. Keys' background
readings (KeyAllowance) are not noted here - the page names a key's
history line by its key name, which routing's reading does not have.
Invited by yetone on #1317 after
|
||
|
|
84c3eb0362 |
provider: moved Cursor users get plugin 0.2.1: an id 0.1.x listed, at an effort only its other branch has, goes as that branch's nearest effort, not as the default's id with parameters that contradict it (magpie-community/plugins#54, geoqiao)
On 0.2.0, cursor/claude-opus-5 at xhigh went as claude-opus-5-thinking-high with thinking=false, effort=xhigh, because Opus 5 without thinking stops at high. 0.2.1 sends claude-opus-5-high with effort=high, as 0.1.11 did. The cursor mover's min goes from 0.2.0 to 0.2.1, so keepMovedCurrent updates every moved Cursor user. 0.2.1 on npm (registry.npmjs.org) has an index.mjs byte-identical to plugins main cf0de01e. The plugin PR was reviewed at ea00bd9 and merged: - bun test in packages/cursor: 49 pass, 0 fail - whole repo: 987 pass, 0 fail - bun scripts/check.mjs passes for all 15 packages - the new test fails on 0.2.0's index.mjs - not tried with a real Cursor account on the review side (the stored token answers 401) The case where the default's own branch stops short is still open: magpie-community/plugins#55. go test -tags nogui ./internal/provider passes (temp HOME, pinned caches). go vet -tags nogui ./internal/provider passes for darwin, linux and windows. gofmt -l is clean. |
||
|
|
b91569d311 | readme: feedback goes to Discord, and the repository takes no pull requests (only collaborators can open one) | ||
|
|
55209c5529 |
plugin tests: TestHostStreamCreditBoundsTheQueue waits for the given-up call to leave the host, which it does once its abort's write is known, after the child may already have read it (red on ubuntu CI at 2b5f19fc; a 50ms pause before the write is known fails the old check every time)
Co-Authored-By: Claude <noreply@anthropic.com> |
||
|
|
ed31293c0e |
usage, gui: a reply its vendor let go in a burst over a couple of hundred milliseconds tells no speed, not 2,237 tok/s (John on Discord)
John's Kimi Code rows after v0.1.1119 (OpenCode, group/k3-glm, api.kimi.ai,
Chat, k3-256k): 550 tokens, 22 of them reasoning, in 16 s with the first
content at 5.2 s read 2,237 tok/s, and 395 tokens in 19 s 2,012. 528 answer
tokens at 2,237 tok/s is 236 ms: the burst the vendor let go at the end.
|
||
|
|
3d34c2d747 |
routing: live, the next request is patched into the context window that is there, so its cells don't come in again and the card no longer goes and comes back, moving the request list under it (Zhenzhen on Discord)
Each new request in a session emptied the card while the gateway read its prompt (the box collapsed to 0, everything under it jumped up), then drew a new card whose 400 cells scaled in again, then a third card when it was done; a request under way had no Prompt cache stat, so the grid moved again when it came. Now ctxCard returns a card with ctxUpdate: the grid's cells are made once and only their classes change, the head, stats, legend, contents rows and foot are morphed in place, and an under-way request keeps the cache stat's place with "—". Routing patches the live card, keeps it in place while a live request has no prompt yet, and still draws a request the reader picks afresh. Verified: routing-live-patch.test.cjs (new) feeds two requests in three steps each, at 420 and 1100 wide, in Chromium and WebKit: the old rows, card, grid, cells, legend and contents rows stay the same nodes, the grid and card keep their height on every ResizeObserver frame, the view never scrolls. Without the change it fails: the card reads '' while the request is under way, the card/grid are new nodes, the box goes 884 -> 0 -> 789 -> 884. routing-*, context-*, click-scroll, panel-routing, old-webkit, gui-ja/de/zh-tw, usage* pass in both engines; go test ./internal/gui and GOOS=linux/windows go vet pass. |
||
|
|
ae24bcfcfb |
agent, gui: Codex's row on the Agents page shows its desktop app's version beside its CLI's ("CLI 0.130.0", "App 26.930.31730"), and the app's alone where no CLI is on PATH (#1334, laacmilan)
The app's version is read from /Applications or ~/Applications
ChatGPT.app (or Codex.app) Contents/Info.plist, taken only when its bundle
id is com.openai.codex, so the ChatGPT chat app (com.openai.chat) isn't
taken for it. macOS only: the Windows Store package isn't read yet. The
app chip's tooltip says it reads the same settings as the CLI, so
connecting Codex connects both. At narrow widths the chip ellipsizes, and
below 250px it gives way so the name keeps its room.
Verified: TestCodexAppVersion fails without the CLI() change ("the app
alone, no CLI"); agent-app-version.test.cjs fails 12/16 without the app.js
change and passes 16/16 with it, Chromium and WebKit, en/zh/ja/de, at 980,
560 and 440px. agent-cli-missing, agent-connect, agent-field-names,
agent-layout, agent-update-all, cli-update, click-scroll, gui-ja, gui-de,
i18n-keys pass in both engines. internal/agent passes in Linux Docker
(non-root); full ./... on macOS; vet darwin/linux/windows. Read
26.930.51102 from the real /Applications/ChatGPT.app on this Mac. Not
tried on the reporter's machine.
|