Commit Graph
1062 Commits
Author SHA1 Message Date
yetone e2e5f4bb64 agents: dsh's patch list written in flow style is edited like a block one, and a file magpie can't edit is named as itself (#445) — a profile's cordis.patch.yml whose top-level array was written as [ { id: some-plugin, disabled: false } ] (valid YAML, and what dsh's own writers keep the template's [] as once they add a row) failed every model switch with "config.yaml is not a list of entries magpie can edit": the patch-list reader took only '- ' lines, and the error named config.yaml whatever the file; since web's list goes first, one such profile stopped all of them. New edit.BlockList reads a file whose one document is a flow sequence as the same list in block style (nested mappings and lists in block style, scalars quoted and tagged as they were, the lines before it and the comments after it kept); dsh's model/effort wiring and its MCP servers (internal/library) read and write such a file through it, and its errors name the real path. dshProfiles puts web first by partition instead of a sort whose less wasn't a strict weak order. TestDshFlowPatchList (agent, library) fails without the change; TestBlockList covers one-line, folded/nested/!!js, empty, CRLF and what isn't a flow list. Sandbox HOME: magpie dsh deepseek-v4-pro on main fails with the config.yaml error, with the change both profiles get the model and web keeps some-plugin. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-01 23:25:05 +08:00
yetone bd529a555e netproxy: a host whose IPv6 takes the connection and drops it is reached over IPv4 (tony on Discord: models.dev sync and the update check failed with a bare EOF, direct or auto, while curl got through) — behind a TUN (xray) without IPv6 out, a connection to a site's IPv6 address is accepted, so the dialer's own fallback to IPv4 never starts, and closed once TLS begins. A connection to an IPv6 address that ends before a byte comes back now marks its host:port to be dialed over IPv4 for 30 minutes, and the request that met it is sent once more over IPv4 when nothing of it can have reached the site (HTTPS, or an idempotent method) and its body can be had again; proxied requests and IPv4 connections are left as they are, and TLS, ALPN/HTTP/2, headers and bodies are unchanged. Every transport made with Dispatch (http.DefaultClient, the gateway's) and the update client get it. TestIPv4Fallback fails without the change (Get "https://example.com/": EOF). go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestFXInState flaked on TempDir cleanup under load, passes alone). 2026-10-01 23:20:58 +08:00
yetone eeb906e1b7 plugins: the built-in list's ZCode summary says what the registry does (ZCode's free Start Plan isn't offered by the plugin) — the fallback market.json, used until the registry is fetched, still promised the Start Plan; it now reads as registry.json does. go test ./internal/plugin passes. 2026-10-01 23:20:57 +08:00
yetone 5200c0a61b gateway: a Cursor tool call whose arguments aren't whole JSON keeps its place in the history (follow-up to #434) — a call's arguments are sent to Cursor as they are, to keep large integers exact; arguments cut short, as the search loop's streamed ones can be, made the whole assistant message fail to marshal and drop out of the history. Arguments that aren't valid JSON are now sent as {} as before #434. TestCursorTruncatedArgsKeepCall fails without the change. 2026-10-01 23:20:57 +08:00
yetone 728cb83c47 routing: a request's changed reasoning keeps to its own box, cut with an ellipsis, never drawn over its numbers (#435, azir12345: 文字重叠) — the Requests list's rows share their columns, and the numbers' one is as wide as the longest numbers in the list (three tries · minutes · TTFT · a million tokens), so where a request went could be left next to no room; its reasoning would not shrink and ran on over "15 秒 · 首字 14 秒 · 6.9k token", while a short "· low" still fit. Where it went now gives way first, then the level asked for ("m… → xhigh"), each with an ellipsis, and the level sent stays whole; the reasoning cell clips as a last resort. Safari 15's styles (no subgrid, no container queries) lay each row out by itself and stay as they were. routing-effort-change.test.cjs (Chromium and WebKit, English and Chinese, dark, 1440 to 400px, and with Safari 15's styles) fails without the change; routing-effort-row.test.cjs counts the reasoning's new inner spans as the reasoning. routing-effort, routing-effort-row, routing-kind, routing-served, routing-sessions, routing-columns, remote-group-served, routing-reroute-title, routing-steady, routing-time, routing-flood, request-archive, panel-routing, click-scroll, old-webkit, text-size pass; go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build pass. 2026-10-01 23:20:57 +08:00
yetone 4547dafe95 models: Provider in model names has a third way, a name the user gave used just as they wrote it (#92 marsxxl: v0.1.591 的 Codex 下拉里自定义名称又带上了 · WorkBuddy / · Claude Code) — 559ce021 (#203) had a custom name keep " · <provider>" after it like any other, so "Opus 5.5" read "Opus 5.5 · Claude Code" in every list Labels feeds (Codex's model_catalog_json and the gateway's Codex /models, Pi's, OpenCode's, ZCode's, OMP's, Muse's), while #92 had been promised the name as written. Both are kept: the setting is now Off / Not on names I set (自定义名称不带供应商) / On. On stays the default and keeps #203's behaviour, so nobody's list changes unasked; own leaves a name the user gave as it is and a vendor's name with its provider's; off is #335's plain names. Stored as plainOwnNames beside plainNames, so a settings.json of before reads as it did (plainNames: true is off). POST /api/settings/plain-names takes mode on|own|off (a body of on alone still works), magpie model suffix on|own|off, a Settings save keeps it, and /v1/models' magpie_label now follows the setting through Labels as the agents' lists do. The row's hint is one line that fits (en, zh), and its zh name says 供应商 as the rest of the app. Prompts and headers are unchanged. TestSuffixMode, TestSuffixModesReachAgentFiles (Codex model_catalog_json and Pi models.json), TestModelsListSuffixModes (/v1/models magpie_label) and model-suffix-own.test.cjs (Chromium and WebKit, en and zh) fail without the change; TestModelName and TestModelNameAndLevelsReachAgentFiles keep #203's assertions. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestConfiguredModelsAdvertiseImageInput load-flaky, passes alone); plain-names, click-scroll, old-webkit pass. 2026-10-01 23:20:57 +08:00
yetone 41dde238dd usage: a sessions bar's name takes the width it needs, its bar what is left (John on Discord: widened, the names stayed cut short with … while the bars grew) — the Sessions overview's projects, models, tools and skills rows held their names to 9em, 10em and 11em whatever the window's width, every extra pixel going to the bar. Each card is now one grid whose rows are subgrids: the names' column is as wide as the longest (minmax(0, max-content)), the bars take the rest and are never under 5em, and every row's bar starts at the same place; narrow, the names are shortened rather than a bar. Without subgrid (before Safari 16) each row sizes its own name the same way. sessions-bar-names.test.cjs fails without the change (196px name shown in 108px at 1600 wide); it, sessions-overview, sessions-calendar-fill, sessions-today, click-scroll and old-webkit pass in Chromium and WebKit, en and zh. 2026-10-01 23:20:56 +08:00
yetone cbf51ec7a4 settings: Number units lets Chinese say a large count in K/M/B instead of 万/亿 (John on Discord asked for K/M/B with the UI in Chinese) — the Requests tab's totals and chart axis and the tray panel's Usage tab shortened every count with 万 and 亿 whenever the language was Chinese (ledShort, the one place either is used). A Number units row under Currency, shown in Chinese only, picks 中文(万 / 亿)(the default, unchanged) or 英文(K / M / B), kept as settings.westernUnits and sent with the rest of Settings; picked, ledShort says it as English does (1.54B, 80M), the Usage page and an open panel redraw at once, and the axis labels are still measured before the plot. number-units.test.cjs fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; number-units, click-scroll, old-webkit, usage-chart-axis, text-size, currency, panel-usage pass. 2026-10-01 23:20:56 +08:00
yetone 0361189f9e test: every package's tests pass on Windows, and none writes outside its sandbox (the user: 把 Windows 上那几个失败的测试修掉) — on a Windows box the suite failed in most packages, and the root package hung for 25 minutes. Tests set HOME, which os.UserHomeDir ignores on Windows (it reads USERPROFILE), so they read and wrote the runner's own home; each now sets USERPROFILE beside HOME, and Devin's, Goose's, Crush's and Claude Desktop's tests set APPDATA/LOCALAPPDATA, where those keep their files there. said() read the command's output only once it returned, and a pipe holds a few KB on Windows, so magpie model --help blocked forever; it reads while the command writes. Unix permission bits (the gateway key store's 0600 too), exec bits and symlinks Windows lacks are checked only off Windows; TOML strings hold Windows paths as literal strings, JSON as marshalled ones; a URL's path is compared as one. The provider and usage packages run in a home of their own (TestMain, as the gateway's already does), since a CLI's identity kept by a refresh still going when its test ended landed in the runner's ~/.config/magpie — and on a Mac in the user's own. Two product fixes came out of it: imagemcp took a file:///C:/… root or image for the path /C:/…, which doesn't exist on Windows, and now makes a Windows path of it (a host for a UNC share); and session discovery could keep a new Cowork session hidden on Linux, where a folder's mtime is a kernel tick and its size doesn't change, so an entry added within the tick of the last listing left both as they were — a listing made within 2s of its folder's last change is read again, as git does for racy files. TestFilePath and TestRacyDirectoryListedAgain fail without the change. On Windows 11 every package passes (32 of 32) and a run of each package in a fresh home leaves nothing in home, APPDATA or LOCALAPPDATA; on Linux (WSL) and macOS go test -tags nogui ./... passes. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build pass. 2026-10-01 23:15:07 +08:00
TryWorld 6f99e5d0ff fix(gateway): preserve optional-field validation errors (#403)
* fix(gateway): preserve optional-field validation errors

* fix(gateway): recognize proxy-wrapped field refusals
2026-10-01 23:08:36 +08:00
TechnoHouse e76f119048 gateway: report cache writes in Chat usage (#429)
Claude Code reports cache writes, and the gateway retains them in Usage. Usage.chat() leaves them out of the response, so OpenAI-compatible clients count those tokens as ordinary input. Emit prompt_tokens_details.cache_write_tokens and keep prompt_tokens as the total prompt.

TestClaudeChatCacheWrites drives Claude stream usage through both Chat response modes. It fails without the serializer change. go vet, the gateway/redaction race tests, the provider account race tests, the no-GUI build, and go test ./... pass, except TestPluginModelListThroughAccountProxy, TestPluginProxy and TestPluginSOCKSProxy: they fail on main too on the test machine, whose DNS resolves .invalid names. A local live client run records cache writes on a cold turn and cache reads on a later turn.
2026-10-01 23:08:30 +08:00
Charan Rathore d5645e57ce gateway: preserve large integers in Cursor tool history (#434) 2026-10-01 23:08:24 +08:00
yetone a97643bcc8 gateway: a translated or relayed stream keeps its client alive while its provider is (#436, congee949: Chat → Responses dropped the upstream's ": keepalive" and Codex hit "idle timeout waiting for SSE" on a live connection) — a reply magpie translates was read event by event and written only when an event had something for the client, so a provider's SSE keepalives, and events with nothing to translate, reached no one, and a reply that thought quietly for five minutes ended in Codex's idle timeout and a retry. Now, whenever the provider is heard from and the client has heard nothing for a second, magpie sends the client a keepalive in its own protocol: response.in_progress again on Responses (Codex's timeout counts events, never a comment — a sandboxed codex 0.159.2 on a fake provider sending only comments timed out, one sending response.in_progress didn't), ping on Anthropic (as Anthropic does; Claude Code's watchdog counts it), an SSE comment on Chat Completions, nothing on Gemini (Google's SDK can't read past one). A provider that sends nothing at all is not covered for, so a dead connection still ends at the client's timeout. A reply relayed from the Claude subscription's bridge, Kiro, Cursor, Devin, search, Qoder, Zed or Command Code is kept alive every 15s while it goes quiet, for up to 5 minutes since its last event. Native passthrough is unchanged, and prompts and headers are unchanged. The routing trace no longer drops a request still going for the 60 finished after it: the oldest finished ones go first, unless more than 120 are going at once. TestTranslatedStreamKeepsClientAlive (the issue's repro: 20ms keepalives, 150ms idle timeout; Chat → Responses, Chat → Anthropic, Responses → Chat), TestRelayKeepsClientAlive and TestTraceKeepsInflight fail without the change; TestTranslatedSilenceStillTimesOut keeps a silent provider uncovered. In a sandbox CODEX_HOME, codex exec through the gateway on a provider sending 6s of keepalives with stream_idle_timeout_ms 2500 answers, and without the change fails with idle timeout waiting for SSE. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-01 22:36:36 +08:00
yetone 38f184ae0e signin: Grok and Command Code sign in from magpie in Docker (xugui on Discord: docker版登不上grok和command code) — Grok's sign-in first installs Grok Build with its installer, curl … | bash, and the image has neither bash nor curl, so it stopped at "installing Grok Build failed (exec: "bash": executable file not found)"; the community Grok plugin, beside the built-in rather than moved onto, wasn't given the CLI at all and said "install Grok Build first". Where no shell and curl/wget can run, Grok Build is now installed as its installer does, from the same x.ai/cli (then its storage bucket) address: the stable version, its binary for the platform, checked with --version, linked as ~/.grok/bin/grok and agent, installer = "internal" noted in its config.toml; the plugin's sign-in installs it first too. The device sign-in itself needs nothing back from the browser and was fine. Command Code's Studio posts the key to magpie's 127.0.0.1 port in the background, which a browser outside the container can't reach and no address carries; while it waits, its sign-in now takes an API key made on commandcode.ai/settings/keys and pasted (as Command Code's own CLI offers), checked with whoami and its plan as Studio's was, and a plugin's browser sign-in offers "Use an API key instead", its own api way (the Command Code plugin has one). No identity, header or prompt changes. TestGrokInstallsWithoutShell, TestCommandCodeSignInPastedKey and TestGrokPluginSignInInstallsCLI fail without the change, signin-paste-key.test.cjs too. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; signin-paste-key, signin-paste-codex, plugin-signin, signin-callback, commandcode-risk, plugin-key-hint, signin-link, click-scroll, old-webkit pass. 2026-10-01 22:25:39 +08:00
yetone 37ba4deb3d goose: a key magpie adds to config.yaml goes after the whole last entry, never between extensions: and its children (found reviewing #431) — SetYAMLTop put a new top-level key right after the last top-level key line, so on a config.yaml ending in an extensions: block, magpie goose effort off wrote GOOSE_THINKING_EFFORT between extensions: and its indented children and Goose's config no longer parsed. A top-level entry is now its key line plus its indented lines, a sequence written at column 0 under it, and blank or column-0 comment lines between them (and the trailing blank lines of a |+ block scalar); a new key goes after the last such entry, before any trailing blank lines and comments. Setting or removing a key whose value ran over several lines now replaces or removes those lines too. Every other line stays byte for byte. TestYAMLTopAfterBlockEntry (block mapping, sequences, block scalars, no trailing newline, empty file, a block value replaced) fails without the change and checks each result parses with yaml.v3 and keeps the other keys' values. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; the reviewer's goose431.sh repro in a sandbox HOME now yields a config.yaml that parses. 2026-10-01 22:23:41 +08:00
yetone cd201d888c sessions: a Sessions page lists an agent's sessions by folder, to resume or delete, and deleted ones wait in magpie's trash to be restored (TJHHHH on Discord: session management like CC Switch's, deleting above all, in the GUI) — sessions were only seen on Usage, with no way to remove one. The new Sessions tab shows, per agent, every session under the folder it ran in, with its title or first prompt, when, how many messages and how big, a filter, its resume command to copy and, in the Mac app, Open in terminal. Picked sessions (or one, by its own button) are deleted after magpie's own dialog: their files (Claude Code's .jsonl, its subagents folder and what it keeps by the id under file-history, session-env and todos; every segment of a Codex rollout; Qoder's, WorkBuddy's, Pi's, oh-my-pi's) are moved into magpie's folder, trash/sessions/<agent>/<when>-<id>, with a manifest, never erased; Trash lists them and Restore puts them back where they were. A session written to in the last minute is left, and said so, as the agent may still be running it. Agents' own indexes (Codex's session_index.jsonl and state database, Claude Code's history.jsonl) are not edited: they are written while the agent runs, and a stale entry there is harmless. OpenCode, ZCode, dsh, Cline and Grok keep sessions in databases magpie doesn't write, and show no delete. With nine tabs the narrowest header gets a further step that draws the tabs in, and past that they scroll sideways rather than push the page wider (560px in English, or at 150% text). The dialog uses the shared confirmAsk/closeConfirmAsk, as the gateway keys' do. TestListAgent, TestDeleteRestoreClaude, TestDeleteRestoreCodex, TestDeleteActiveRefused (fails without the running check) and TestDeleteOnlyKnownAgents run in a temp HOME; sessions-manage.test.cjs (Chromium, WebKit, en, zh) fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; sessions-manage, click-scroll, old-webkit, text-size, session-terminal, sessions-today, brand, rail-tip, web-page, omarchy, model-test-one, gateway-caller-keys, gateway-connect-keys, api-key-usage, api-key-theme pass. 2026-10-01 22:11:46 +08:00
Charan Rathore 563dc83eef edit: keep YAML top-level strings as strings and unescape single quotes (#431) 2026-10-01 22:10:26 +08:00
yetone 99b1b15d98 claude: Claude Code is told what each magpie model can do, so ultracode works on one with xhigh (#430) — Claude Code run on a gateway (CLAUDE_CODE_USE_GATEWAY, as Claude Desktop's Code tab runs it) takes no model it doesn't know for one with effort, xhigh or max, and "ultracode": true needs xhigh, so on group/…, provider/model and mythos-magpie-<number> it was silently off. While Claude Code is routed through magpie, settings.json's env now has CLAUDE_CODE_MODEL_CAPABILITIES: <model>=effort[,xhigh_effort][,max_effort] for each magpie model with reasoning levels, only the levels magpie knows it has (a Claude model's no more than Claude Code gives it, a model of no known levels not named), lowercased exact ids, the models Claude Code is set to first and the whole within 8000 characters; while Claude Desktop runs on magpie, its mythos-magpie-<number> ids too. It is rewritten as the catalog changes and when Desktop is put on or off magpie, and goes when Claude Code is taken off magpie; a value the user set is left as it is. Prompts and headers are unchanged. TestClaudeModelCapabilities and TestClaudeCapabilitiesSync fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; in a sandbox HOME, claude 2.1.286 -p '/effort ultracode' on the settings.json magpie wrote, in gateway mode: group/auto-deep (xhigh) and its Desktop alias are on, group/auto-mid (no xhigh) and the same settings without the env are not. 2026-10-01 22:04:28 +08:00
yetone 92760896e3 usage: the chart's side labels keep the card's padding (John on Discord: 这是bug吗?贴边了) — the plot of the Requests tab and the tray panel's Usage tab started a fixed 44px (34px in the panel) from the left, and the labels were drawn to end 6px short of it, so any label wider than that ran out leftward: "8000 万" in the panel reached past the card's 10px padding to its edge, and "200,000" or "$0.50" in the window crossed its padding too. The labels are now measured as drawn and the plot starts where the widest one ends, so every label stands as far in as the tabs above it, in English and Chinese, for tokens, cost in $ and ¥, and requests, small and large, wide and narrow. usage-chart-axis.test.cjs (Chromium and WebKit, en and zh) fails without the change. click-scroll, old-webkit, usage-ledger-chart, panel-usage, usage-ledger*, usage-refresh, usage-alert, api-key-usage, tray-usages, claude-usage-asked, currency pass; go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-01 21:59:55 +08:00
yetone ad1c8fd1ec test(provider): TestFetchNewLeavesListedPlugins waits for the refresh its sign-in started, and TestFetchNewRetriedSoon puts back what it set — signing in with a key (PluginAPIKey) shows the account, which lists the providers while the host's auth event has them stale, so Cached refreshes them in the background; that refresh could end after the test had set the list's time back, rewriting plugin-providers.json, which read as FetchNew asking the plugins again (failed 3 in 5 run after TestFetchNewRetriedSoon, and now and then after TestFetchNewAccount). plugin.Refreshed waits for those refreshes with the host left running, and the test waits before it sets the time. TestFetchNewRetriedSoon now resets newFetches, newSoonAt, wbTokens and the account caches, after waiting for a FetchNewSoon under way. The pair passes 10 in 10 (and with TestFetchNewAccount 10 in 10); ./internal/provider 3 in 3; go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-01 21:49:16 +08:00
yetone 171d8dc646 web: behind a reverse proxy, magpie web prints MAGPIE_PUBLIC_URL as the page's link (#372) — with MAGPIE_PUBLIC_URL=https://magpie.example.com the link for other machines was http://magpie.example.com:3430/?k=…, its host on the page's port over plain http, which nothing answers. A public URL with no port of its own is now the link, scheme and path kept; one with a port (a container's published ports, http://<host>:3425) still gives that host on the page's port. TestNetworkLinksPublicURL fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-01 21:49:15 +08:00
yetone 58aca0eeb8 providers: an account whose list couldn't be had is asked again a minute on, and opening the panel asks (#422, Q1CHENL: after the in-app update Kiro still offered Auto alone until magpie was restarted by hand) — the update restarts magpie as a new process, whose one try for an account still without its vendor's list was made at start-up, or by the Providers page with 8 seconds for it; one that failed left the account on its fallback (Kiro's Auto) for ten minutes, and only the Providers page asked again, never the panel and its model picker. The retry is now a minute, and GET /api/state (the panel, the window's Agents page) starts FetchNew in the background, no more than once in 15s and never two at once, so the page doesn't wait on vendors. TestFetchNewRetriedSoon fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-01 21:49:14 +08:00
Ns2Kracy bb1aa3acbd feat(gateway): named gateway keys with per-client usage (#344)
* feat(gui): show and filter usage for each API key

* feat(gateway): issue per-user caller keys and attribute usage

* feat(gui): manage gateway users and filter caller-key usage

* test: verify caller-key failover and document user management

* refactor(gateway): use named caller keys without user accounts

* feat(gui): manage named caller keys with existing account styles

* fix: remove user breakdown from CLI and leftover gateway styling

* fix(usage): drop stale user field from request response

* feat(gateway): authenticate LAN calls with named caller keys

* fix(gateway): preserve LAN key rotation in the named caller store

* fix(gui): retain native LAN key copy and rotation controls

* fix(gui): inherit the global theme for API key inputs

* fix(usage): show client API keys using the existing terminology

* fix(gateway): rotate named keys without changing usage identity

* fix(gui): unify gateway keys and selectable connection examples

Keep sharing controls in Settings and manage credentials only in Gateway. Refresh the named-key list on navigation, ignore stale reads after key actions, and generate all API examples from the selected address and enabled key.

* fix(gui): keep caller key names literal in connection menus

* fix(access): use Magpie as default key and drop user migration

* refactor(gui): name caller key source and tests consistently

* refactor(usage): split provider-key accounting out of caller-key PR

* fix(gateway): preserve LAN compatibility and add headless key management

* fix(gui): clarify gateway keys and confirm destructive actions

* test(gateway): guard caller attribution under 120 concurrent requests

* fix(gateway): keep open access, tolerate migration errors and revoke legacy keys

* fix(backup): preserve named gateway keys and honor no-keys restores

* fix(gui): keep local API-key semantics and disambiguate shared credentials

* docs(gateway): keep review scope focused and append browser test documentation

* fix(backup): retain existing default keys when old backups lack their store

* fix(gateway): attribute video creation usage to the caller key

* fix(access): explain default key revocation blocked by read-only settings

* test(usage): preserve provider and caller identities after rebase

* test(gui): reconcile caller key fixtures with merged main

* docs(usage): clarify caller coverage and independent provider accounting

* fix(gui): initialize fast members for model-shortcut groups

Latest main introduced fast-member arrays but omitted the model shortcut's draft. The existing unlisted-models browser regression fails unchanged on main. Initialize fast to an empty list and assert a standard-speed group is saved, without relaxing existing assertions.

* fix(access): retain legacy LAN access when the key store cannot be created

Allow only the configured legacy credential before either migration marker exists. Refuse revoked mirrors, disabled matches and unreadable stores; keep the fallback's caller identity stable when migration later succeeds. Cover read-only configuration directories, remote credential transports and revocation.

* fix(backup): save settings before replacing gateway credentials

Leave the key store untouched when settings validation or writes fail. Restore the previous settings association when the key store refuses its write, and report a rollback failure separately. Cover invalid settings and read-only settings/store failures.

* fix(gui): reset missing caller filters and focus key controls without scrolling

* test(provider): synchronize Zhipu quota fixture observations
2026-10-01 21:28:54 +08:00
TryWorld 820413b87f test(gateway): handle intermediate session route poll updates (#424) 2026-10-01 20:50:22 +08:00
Zhu Wang 1adb2b571e feat(routing): group requests by session with names and estimated costs (#396)
* feat(routing): group requests by session with names and estimated costs

* fix(routing): address session view review feedback

Preserve Luna Reserve and other header fallbacks for ordinary Codex turn metadata; reuse metadata and the kind across trace and native accounting. Default to By request and persist the grouping choice. Store only token accounting fields in route history while retaining legacy JSON compatibility.

Add regressions for helper precedence, native kind accounting, history compatibility and grouping preferences.
2026-10-01 20:50:15 +08:00
yetone 40102f74c0 kiro: a Builder ID sign-in is asked with Builder ID's service profile, so its models are listed (#422, Q1CHENL: only Auto offered while kiro-cli lists Opus 5.5, Sonnet 5 …) — a Builder ID sign-in (kiro-cli's, the Kiro IDE's or magpie's own) names no profile, and Kiro won't list one for it: List-Available-Profiles answers 403 "AWS Builder ID is not supported for this operation" in both regions, so every call needing the account's auth failed — List-Available-Models (magpie fell back to Auto alone), Get-Usage-Limits (the account stayed "Kiro account") and chat. A sign-in kiro-cli keeps with Builder ID's start URL (or none, as kiro-cli takes it), or one saved with provider BuilderId, now takes the service profile the Kiro IDE and kiro-cli use for Builder ID (arn:aws:codewhisperer:us-east-1:638616132270:profile/AAAACCCCXXXX) without asking, and an AWS sign-in whose profile listing is that 403 takes it too. Identity Center sign-ins still have their profile listed. TestKiroBuilderIDListsItsModels and TestKiroBuilderIDFoundByKirosAnswer fail without the change (fake management API). go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestFXInState's temp-dir cleanup flaked once under load, passes alone and with its package). 2026-10-01 20:45:11 +08:00
yetone 986a4474fe zcode: the Start Plan's 405 block is said for what it is, not only as a firewall (#425: ZCode 405 Method Not Allowed on GLM-5.3-Flash from DeepSeek Harness, while zcode2api works with the same account) — zcode.z.ai answers a Start Plan request it turns away with 405 "request has been blocked due to unusual activity" (code 3012 "method not allowed"), which magpie read as Alibaba Cloud's firewall blocking the address and told the user to wait or switch network. zcode2api's own write-ups (a137460387/zcode2api "3012 的根因与修复", gakiyukr/zcode2api-plus request.go) find the endpoint checks what a request carries and takes only one with ZCode's own system prompt, which zcode2api injects; magpie's endpoint, method and protocol are the same as theirs, and magpie doesn't pretend to be ZCode. On a Start Plan account the 405 / 3012 / block page now reads "ZCode's Start Plan turns away requests that don't come from the ZCode app itself, and magpie doesn't pretend to be it; it can also be a network block of this IP. Use an account with a GLM Coding Plan, or add another provider to this group", in place of the generic hint, to the agent and in the trace, where the Routing page gives it on its own line in English or Chinese; the group still goes on to its next member. A Coding Plan account and every other provider keep the generic hint. Requests, headers and prompts are unchanged. TestZCodeStartBlockedSaid and routing-zcode-blocked.test.cjs fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestUserPath, a load flake, passes alone); routing-zcode-blocked, routing-blocked, routing-wb-refused, click-scroll, old-webkit pass. 2026-10-01 20:45:11 +08:00
yetone a999770531 plugins: a plugin's subscription does what a built-in's does, in seven places it didn't (the user: 要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样) — an installed plugin's provider id is kept for it signed in or not, as a built-in's is: a provider of the user's own isn't saved on it, renamed onto it or imported as it (freeID gives id-2). A plugin's account whose plan lacks the model is tried after the ones that list it, as a Codex Free account behind a Plus, by the list the plugin told for it. Cursor's plugin has fast mode where the built-in Cursor has it: a model with a -fast one is saved :fast in a group and sent service_tier "priority", one without is refused. Removing a plugin's provider hides it as a built-in's is, its accounts kept and shown again from Hidden or by signing in, rather than signing every account out ("stays signed in" said so already). magpie accounts lists, adds, switches and forgets a plugin's accounts by its provider's id or name. Grok's plugin beside the built-in Grok (grok-plugin) searches by itself as Grok does. A plugin's plan is named as its built-in's ("Cursor Pro", "SuperGrok"), and a plugin's resets are shown but have no Use button or Auto-use, which spent Codex's reset of the same user. TestPluginIDReservedSignedOut, TestPluginAccountLackingTheModelGoesLast, TestCursorPluginGroupFast, TestAccountsOfAPlugin, TestGrokPluginSearchesItself, plugin-plan and codex-auto-reset fail without the change; TestPluginSignIn and TestPluginAccounts now expect a removed plugin hidden. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestClaudeSignedOut failed once under load, passes alone); the gateway, provider and plugin suites and TestAccountsOfAPlugin pass on Windows, the plugin tests on Linux. 2026-10-01 20:26:55 +08:00
yetone b9895c2ded tests: the gateway's and Devin's tests run on Windows in a home of their own — on Windows os.UserHomeDir reads USERPROFILE, not HOME, so a test that set HOME to a temp dir still found the one USERPROFILE every gateway test shares (and the box's real agents); Devin's CLI keeps its sign-in under APPDATA. Each such test now sets USERPROFILE with HOME (setHome), TestDevinOwnMovesWithPlan sets APPDATA, and the tests whose Claude Code is a shell script skip on Windows, where it can't run and the box's real claude ran instead. Tests only. The gateway suite and TestDevinOwnMovesWithPlan pass on Windows; go test -tags nogui ./... passes on macOS. 2026-10-01 20:26:53 +08:00
yetone 7caba10d9c omp: a model's or group's max is offered as max to omp 16.4.0 and later (whqtian on Discord: a routing group with max ticked showed only xhigh in oh-my-pi, while Pi had max) — magpie wrote every model's thinking efforts into omp's models.yml with max taken out and xhigh in its place, since omp 16.3.5's schema stops at xhigh and turns the whole file away over a max; but a group whose levels name both xhigh and max then had no way to max at all, as the gateway keeps an xhigh asked for. omp has taken max since 16.4.0 (pi-ai's changelog; 18.4.8's EffortSchema has it), so an omp on PATH at 16.4.0 or later is now given max as it is; an older one, or one whose version isn't known, keeps xhigh standing in. omp 18.4.8 in a sandbox HOME lists the group's thinking as high,xhigh,max and sends reasoning_effort max with --thinking max. TestOmpGroupOffersMax fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-01 20:18:02 +08:00
yetone 41d50e15c8 claude: the Claude accounts saved in magpie are served while Claude Code is logged out, and none is dropped (StringKe on Discord: CC(API) -> Magpie -> CC Subscription, and as soon as I /logout in Claude Code all six accounts I manage in magpie disappear) — magpie served its saved Claude accounts only beside the one Claude Code was signed in to: claudeAccount gave no provider without Claude Code's own credentials (or with claude auth status saying no one is signed in), so the gateway had no Claude account at all and the page said the saved ones "are only offered beside the account it is signed in to". Claude Code itself tells the user to log out: with magpie's ANTHROPIC_AUTH_TOKEN beside a claude.ai sign-in it warns "ANTHROPIC_AUTH_TOKEN overriding Claude subscription login · unset it or /logout to sign it out". magpie keeps every account's sign-in in logins.json, so a logout now signs none of them out of magpie: the account seen last (the one Claude Code was signed in to until then, which magpie saw every 30s) stands in its place, run in a config directory of its own as the others on are, listed on, and it can be paused behind another as Claude Code's own could; the others on are served behind it, the Usage page reads it as a saved account, and the saved-but-signed-out notice is no longer given for Claude. An account whose saved sign-in is unreadable is listed as signed out ("its sign-in is gone; sign in again"), never dropped, and isn't picked to stand in while another has one. An account signed in in magpie while Claude Code is logged out and magpie has Claude accounts no longer signs Claude Code back in (which only brought back its warning); it is magpie's alone, on, and the one served first till then stays on behind it. Prompts, identity lines and headers are unchanged; magpie still never refreshes a Claude sign-in nor asks Anthropic anything itself. TestClaudeAccountsOutliveLogout, TestClaudeSignInWhileLoggedOut and TestSavedButSignedOutClaudeServed fail without the change; TestSavedButSignedOutCanBeRemoved now removes the account from the list. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestFetchNewLeavesListedPlugins, a bun plugin test, failed once under load and passes alone and in a rerun of the package). 2026-10-01 20:13:58 +08:00
yetone f45e9737b3 gateway: a conversation stays with the account that answered it across a restart of magpie, and routing goes by each account's last known allowance until the first reading since the start is back (vincentzhang on Discord: in smart routing a restart moved a session to the first account, an update and restart back to the second, and the prompt cache was read by neither) — who answered a conversation last was kept in memory only, so after a restart every conversation was "first" and went to whoever routing put first; and until the accounts' allowances were read again (a first wait of 3s, the read itself up to 30s) none was known, every account counted as unused and the first in order went first, until the reading came back and put the one renewing soonest first again. Who answered is now also kept in affinity.json next to the providers (0600, written atomically, the latest 512 conversations, none older than the 24h a conversation is remembered), read again when a conversation isn't known here and the file changed — after a restart, or written by the magpie handing over. The rules for when a conversation moves are unchanged: within a turn it stays, across turns while the vendor read enough from its cache within the last five minutes, and never to an account resting, all but used up or gone. While the first reading is out, Allowances returns each account's last reading kept in quotas.json, its windows past their reset counted empty. Prompts and headers are unchanged. TestAffinitySurvivesRestart and TestAllowancesBeforeFirstReading fail without the change; TestAffinityFileBounded keeps the file bounded. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestFetchNewLeavesListedPlugins and TestFXInState flake under load on main too, pass alone). 2026-10-01 20:09:25 +08:00
yetone a02076dcb8 claude: a Claude account's usage and model test read Claude Code's answer with its verbose output on (KevinXC on Discord: 新版本 claude 额度查询失败了; the card said usage couldn't be read, its tooltip began [{"type":"system","subtype":"init") — with Claude Code's own "Verbose output" turned on (its /config, kept in .claude.json, which --setting-sources "" leaves in force), claude -p --output-format json prints every message as a JSON array, the system init first and the result last, and magpie read only the one-object form: /usage's output was taken for an error and shown as one, and a model test's is_error was missed. The result is now the array's last "result" message when it is an array; the one-object form reads as before. Arguments, prompts and headers are unchanged. TestClaudeVerboseOutput fails without the change; the real Claude Code 2.1.286 with verbose on, in a sandbox config directory, gives an array that now reads to its /usage text. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./internal/gateway ./internal/provider pass. 2026-10-01 20:05:33 +08:00
yetone 9c54a560cc provider: a providers.json that can't be read is said where the providers are listed, never backed up or synced as none, and is replaced through a rename (PR #415's review) — #415 made edits refuse a file they couldn't read or decode, but every read for the user still took it as empty: magpie providers said "no providers yet", magpie models "no models yet", the Providers page opened the Add sheet as for a first use, and backup.Collect, so an export and a WebDAV/S3 sync, carried it as no providers, which another computer's Mirror would then take as every provider removed. magpie providers and magpie models now list what is left (the signed-in accounts) and end in the error, the TUI says it over its list, and the Providers page shows a "Your providers file can't be read" box (无法读取供应商配置文件) over the list with magpie's reason and no Add sheet; start-up, the gateway and the catalog still go on with it as empty. Stored and StoredGroups return the read error, so a backup and a sync stop on it with nothing pushed. The error, for reads and refused writes alike, names the file, says magpie left it unchanged and to fix it or move it aside (errors.Is ErrUnreadable). store writes through writePrivate, a 0600 temp file renamed over it (steady.Rename waits out a Windows reader holding it, a symlink keeps its target), instead of os.WriteFile's truncate-then-write, and the read goes through steady.ReadFile. TestFileErrorSaysUnreadable, TestWriteErrorsSayLeftUnchanged, TestStoredRefusesUnreadable, TestStoreReplacesAtomically, TestSyncStopsOnUnreadableProviders, TestProvidersCmdSaysUnreadable and provider-file-error.test.cjs fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; provider-file-error, click-scroll, old-webkit, providers-off-fold, add-sheet pass. 2026-10-01 19:45:24 +08:00
yetone f654bec468 gateway: a model's web search goes to a search API of the user's own when no provider can search (#419 by HwFee: let me set up Tavily, Brave or SearXNG with my key rather than lose the search tool) — with no provider that searches by itself and no provider magpie could search with, the gateway took the client's web search tool away. Tavily, Brave Search, Exa, Firecrawl and SearXNG (an address of one's own, a key only if it asks for one) can now be set up, in an order: the injected web_search tool is then offered, and a search goes to the searcher's model as before, or, with none or when it fails, to each search API in turn, called directly with no model, its results handed back as the same hits and text the searcher path gives, so clients see the same web_search_tool_result blocks. The order is provider searches itself, searcher provider, search APIs, strip. They are kept in providers.json (0600) like provider keys; a backup carries them, their keys only when it carries keys, and one restored without keys keeps the key here; sync does the same, and a backup or sync from a magpie before them leaves the ones here alone. magpie search lists them, magpie search add <api> [key] [url=…] adds or updates one, magpie search rm <api> removes one; Settings has a Web search section (联网搜索) to add and remove them. Prompts, identity lines and headers to model providers are unchanged. TestWebSearchBySearchAPI, TestNoSearchAPINoTool, TestBackupSearchAPIs, TestSyncSearchAPIs and search-api.test.cjs fail without the change; TestSearchAPIsEachAsked checks each vendor against a fake, TestSearchCLI the command. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; search-api, provider-search, click-scroll, old-webkit pass. 2026-10-01 19:44:44 +08:00
yetone 044f61f50f usage: a custom balance's card says when it was read, shows each of its amounts on a line of its own and a percent as a meter (#420 by emo172: 余额卡片显示读取时间、百分比进度条) — a balance read from a provider's own Balance URL showed one line, "Balance $2.00 · Today $2.50 · Used 90%", and nothing said how old it was. magpie now hands the card the amounts the user's expression names, each with its label, and a "%" one's share; the Usage page shows each on a line of its own, the user's label quiet and the first amount the larger, "Balance" only where no label was given, and a percent as the quota meter, amber from 90%. Every card that was read says so, "Updated 3 minutes ago" (3分钟前更新), and one standing in for a reading that failed just now says "As of … — couldn't be read just now" (截至 …,暂时无法获取最新用量), allowance cards too. The menu bar panel's Balances show the same amounts and meter and an amber "As of" (截至), and the tray's tooltip adds "as of …, couldn't be read just now". One amount alone reads as it did, and the balance line, the CLI and the expression syntax are unchanged. No balance URL, path or site is added: /api/usage/token and unlimited_quota are one kind of relay's and stay the user's to write. TestBalanceParts, TestTrayUsageText and balance-parts.test.cjs fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; balance-parts, click-scroll, old-webkit, quota-families, panel-usage, tray-usages, balance-fix, usage-refresh, currency, claude-usage-asked pass. 2026-10-01 19:44:44 +08:00
yetone 6532e018b1 web: the page asks for no Wails runtime in a browser, and its files are asked for again by their content (Jorben on Discord: in the remote web UI the Gateway page had no 请求存档 switch and the usage charts no icons, and /wails/runtime.js was a 404) — nothing in the page or the server holds the archive switch or the charts back from magpie web: the same Handler serves both, /api/settings/archive, /api/archive and /api/usage/requests are there behind the web key, and a sandbox magpie web of v0.1.578 and of main, opened in headless Chromium at 127.0.0.1 and at a non-loopback host, drew the switch with no page error. The 404 was the page's import of /wails/runtime.js, which the app's window has and a browser doesn't; it was caught, and only Close and Maximise use it, so it broke nothing, but a browser now never asks for it. What can hide a version's new parts is an older app.js: the page's files went out with no Cache-Control, no ETag and no date (an embedded file has none), so a cache in front of magpie web (a proxy, a CDN, a tunnel's) could keep an older version's app.js under the new index.html, untouched by a cleared browser cache or a private window. They now go out with Cache-Control: no-cache and an ETag of their content's hash, an unchanged one a 304; boot.js stays no-store, the web key's guard still answers first. TestPageFilesRevalidate and web-page.test.cjs fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; web-page, request-archive, click-scroll, old-webkit, usage-ledger-chart, gateway-fold, model-test-one pass. 2026-10-01 19:44:44 +08:00
yetone 2e94643c7b sync: moving between WebDAV and S3 keeps the other's server, and moving back finds it (ARNO on Discord: 我体验了一下s3同步,结果它把我之前配置好的webdav同步设置给删了) — sync.json held one setup, and saving S3 in Settings › Sync and backup (or magpie s3 on) wrote the bucket over it, so the WebDAV address, user and password were gone. sync.json now keeps the other kind's server beside the one synced to (other: its address, user, password or secret, and S3's endpoint, region and path-style), in the same file, readable by the user alone, and never in a backup, as before. Only one is synced to. Moving back with the password left empty uses the one kept, for the same server and user alone; magpie webdav on / magpie s3 on with nothing else given moves back to the kept one, and magpie webdav / s3 say which is kept. In Settings the row says "WebDAV settings kept" (or S3's), the form marks the kind synced to "· on", says whether saving keeps it or moves sync, its button reads "Move sync to WebDAV" when it does, and the other kind's fields show the kept server. A sync.json from before reads as it did; turning sync off still removes it all. TestConfigureKeepsTheOtherKind, TestS3Cmd and sync-other-kind.test.cjs fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; sync-other-kind, s3-sync, webdav-settings-refresh, click-scroll, old-webkit pass. 2026-10-01 19:44:29 +08:00
yetone 2199eaf76f claude: a tool Claude Code's ToolSearch loads mid-turn is handed to the running subscription session, not a restart with the conversation as text (StringKe on Discord: with ENABLE_TOOL_SEARCH, tool calls came out as raw XML and the turn stalled) — when the client offered a tool the hidden claude -p was never told of, magpie ended the run and started one told the whole conversation in one message, earlier tool calls and results written as text, and its model went on to write its own calls as text. The new tools now go back with the tool results: magpie's MCP helper declares listChanged, swaps its tools, sends notifications/tools/list_changed and answers the call once Claude Code has listed them again (at most 5s). Prompts and headers are unchanged. TestToolSearchLoadKeepsTheRun and TestNewToolsListedBeforeTheAnswer fail without the change; real Claude Code 2.1.286 against a fake API offers the new tool on its next request. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-01 19:44:28 +08:00
TryWorld a32186a6ba fix(profile): preserve default values in snapshots (#421) 2026-10-01 19:43:32 +08:00
TryWorld e4bf875b85 fix(backup): exclude balance tokens from keyless exports (#418) 2026-10-01 19:43:27 +08:00
yetone e1af812dbf gui: a short window's provider editor scrolls past its models, and a routing group made from a model is added (悠悠哥 on Discord: in a small window the WorkBuddy editor's model list was cut off and the wheel took it no further until the window was maximized; the new group "Hy4 preview" did nothing on 添加, its 推理档位 label at the right and 成员共有/自定 under the labels) — the model chips (.mchips) scroll on their own, 132px high, with overscroll-behavior: contain; in a short window the editor's body shows little more than the list, so the pointer was nearly always over it, and at its ends the held wheel never took the body on to the fields below (the body stopped at 539 of 660px at 800x500). Now the chips are at most 28vh high, and in a window up to 640px tall their ends pass the wheel on to the body; Names & levels is held to 40vh there. The dialog, head and Save already fit any window. A group opened from a model (newGroupWith: the picker's and the provider editor's "Make a routing group of it", the tray panel's ?newgroup=) had no fast in its draft, so Add threw on d.fast.filter and posted nothing, since 10b94023 (unlisted-models.test.cjs has failed since); the editor now fills a draft's members, fast and rules whoever opened it, a throw in Add (or Enter in the name) is said in the status line as "Couldn't save the group: …" (分组没能保存:…), and Add leaves busy (pointer-events: none) once the save is answered, so a refused save can be pressed again. The classifier row's grid cell was left in the grid when it was hidden, so every row after it slipped one cell; it is hidden with its label now. editor-short-window.test.cjs (12) and group-from-model-add.test.cjs (8) fail without the change. Chromium and WebKit, en/zh: editor-short-window, group-from-model-add, unlisted-models, group-member-fast, routing-effort, routing-effort-row, routing-levels, routing-manual, routing-time, routing-compact, settings-groups, agent-model-groups, add-button, add-sheet, library-all, model-test-one, plugin-market, plugin-move, plugin-signin, provider-levels-scroll, saved-signedout-remove, nokey-editor, model-pick, old-webkit pass; click-scroll and plugin-moved-signin timed out under load (avg 239) and pass alone. 2026-10-01 19:38:36 +08:00
yetone 4ceef99d4b usage: the Sessions activity calendar fills its card (John on Discord: 这个活跃度怎么没有铺满全部宽度呢?) — a calendar's week was held to 20px, so 118 days (17 weeks) took about a third of a wide card and left the rest empty. The weeks before the range now come in as empty, fainter cells, GitHub's way, as many as the card has room for up to a year, their months named over them, with no tooltip and nothing said about them; the cells grow up to a few pixels to take what is left and stay square. The calendar is laid out again as the window is made wider or narrower: a narrower card gets fewer weeks before, one narrower than the range gets none and its cells shrink as before, and nothing runs past the card. Ranges up to 100 days are bars and already took the card's width. sessions-calendar-fill.test.cjs fails without the change (38% of the card at 1400 wide); sessions-overview's fixture answers /api/usage/quotas with an array, as the Go side writes it. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./internal/gui pass; sessions-calendar-fill, sessions-overview, sessions-today, usage-ledger-chart, old-webkit and click-scroll (Chromium) pass; click-scroll in WebKit fails the same way on the unchanged code under this machine's load. 2026-10-01 19:38:36 +08:00
yetone 2a6ba1a20f workbuddy: an added account is told from the others by its uid, and the one WorkBuddy is signed in to stays once WorkBuddy moves on (#413: 添加已提前登录的 WorkBuddy 账号会顶掉默认第一个账号) — magpie matched a new sign-in to a listed account by its name, the nickname: another account of the same nickname replaced the one listed, or was taken for WorkBuddy's own and let go with "signed in". And WorkBuddy's own account is only read from WorkBuddy's file, so signing WorkBuddy in to the next account (to pick it on WorkBuddy's page from magpie) renamed that row, the last account gone, and the one picked, being WorkBuddy's own, was let go: one account at a time. A WorkBuddy or WorkBuddy AI sign-in is now matched by its uid (one of the same name whose uid isn't known is still taken to be it, #155); another account of the same name is added as "name (end of uid)"; the same account renews its sign-in where it is listed, and the one WorkBuddy is signed in to becomes magpie's own sign-in of it, first as it was, so it stays when WorkBuddy signs in to another. The sign-in says so: "该账号已在列表中,已更新登录" in the app, "is already listed — its sign-in was renewed" from magpie accounts add. On the plugin, the host no longer takes two sign-ins of one account id for one account when their uids differ. No request header or user agent changes. TestWorkBuddyAddSameNicknameAsOwn, TestWorkBuddyAddSameNicknameTwice, TestWorkBuddyAddWhatWorkBuddyIsSignedInTo, TestPluginSameNameOtherUID and signin-again.test.cjs fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; signin-again, click-scroll, old-webkit pass. 2026-10-01 19:38:36 +08:00
yetone 52082f916f devin: whether a Devin model takes images is what Devin tells its CLI, for models models.dev doesn't know (#417, HwFee: swe-2 and gpt-6-1-sol were said to take no images) — devin models list says nothing of images and models.dev has no entry for Devin's own ids, so swe-2, gpt-6-1-sol and the like were served as text-only and agents dropped the images. When it reads the list, magpie now also asks Devin's GetCliModelConfigs, the read-only call the CLI's own model picker makes, with the same metadata magpie's chats already send. Each ClientModelConfig's supports_images is put on its variant, and on the family and its tiers when all their variants agree: swe-2, gpt-6-1-sol, claude-sonnet-5-5 and kimi-k3 take images, GLM and DeepSeek don't. A model Devin doesn't name, or a failed call, is left to models.dev as before; a list saved earlier takes what Devin said, and a user's own image setting still wins. No prompt, header, user agent or existing request changes. TestDevinImagesFromModelConfigs fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestUserPath and the Claude account tests flaked and pass alone). 2026-10-01 19:38:36 +08:00
yetone 47fd3bcb97 agents: Claude Code's alias reads as the model it stands for, and a model it asks Anthropic for itself says it isn't through magpie (StringKe on Discord: "model": "sonnet" showed as a bare sonnet; no ANTHROPIC_BASE_URL in settings.json read as magpie having failed to set it up) — with "model": "sonnet" (or opus[1m], best, haiku, opusplan, default) in Claude Code's settings.json the row showed the word with no logo, matching none of the models offered. The alias is now offered first in the Claude Code group as the model Claude Code takes it for: "sonnet · claude-sonnet-5-5", the newest of its family in the catalog's Anthropic list (no compiled-in table), or the model the user's own env gives that tier (ANTHROPIC_DEFAULT_SONNET_MODEL); [1m] keeps its mark, best is the newest Opus, opusplan names both, default is left to Claude Code's plan. Claude's logo with it; the file is only read, never rewritten, and a model picked in magpie is written as before. Each of Claude Code's own models now carries who it goes to (Anthropic, or the host of the user's own ANTHROPIC_BASE_URL): the row's tooltip says it isn't through magpie, so settings.json names no magpie endpoint and that is expected, and picking one says "straight to Anthropic, not through magpie"; a magpie model says neither. TestClaudeAliasShown and claude-direct.test.cjs fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestUserPath and TestClaudeAccountNamedAsItsLogin, load flakes, pass alone); claude-direct, model-pick, claude-effort, agent-model-groups, plain-names, old-webkit pass; click-scroll's WebKit run fails as it does on main without the change. 2026-10-01 19:38:36 +08:00
yetone ee16c6576d autostart: a magpie opened at login comes back from "update and restart" on the Mac (悠悠哥 on Discord: 更新后开机自启动没了) — open at login was never lost: the Mac's launch agent runs magpie as a launchd job, and launchd ends whatever a job started once the job exits, so the shell update.Relaunch leaves to open the new version was killed as magpie quit and magpie never came back. The launch agent now carries AbandonProcessGroup, and the desktop app rewrites one an older magpie wrote in the current form at start, keeping the program path it names; with none it stays off, and a launch agent magpie didn't write is left alone. Two throwaway launchd jobs showed children, detached ones too, killed on the job's exit without the key and living with it. TestLaunchAgentLetsTheRelaunchLive and TestRefreshOlderLaunchAgent fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-01 19:38:36 +08:00
yetone f2c7536d7d gateway: a relayed WorkBuddy reply is one block in Qoder again, not a word to a line (ARNO on Discord: workbuddy provider断句出错) — copilot.tencent.com sends "tool_calls": [] on every delta, text too, and Qoder's stream adapter (qodercli 1.1.65) tests delta.tool_calls for being there rather than for holding a call, so it closed its text block on each chunk and opened a new one for the next piece of text. chatTidy, which already mends that stream's empty reasoning_content, finish_reason and tool name, now leaves an empty tool_calls list out; a real call's list goes on as it came. Translated to Messages or Responses the stream was already one text block (the decoder reads an empty list as no call). TestChatRelayDropsEmptyToolCalls fails without the change; TestChatTidyKeepsToolCalls, TestWorkBuddyTextIsOneBlock guard the rest. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-01 19:38:35 +08:00
yetone c3197ac217 plugins: magpie keeps the Bun plugins run on up to date by itself (the user: 我们能否自动去管理一个bun的版本?去自动的下载并维护这个bun的版本到最新版) — plugins ran on the one Bun magpie pinned, or on whatever $MAGPIE_BUN pointed at. Every six hours (first two minutes after start), while there are plugins, magpie now asks GitHub for Bun's newest release and takes it once it has been out two days: downloaded, its SHA-256 checked against Bun's SHASUMS, tried (it says its version, and runs a script with AsyncLocalStorage, fetch and fs), then switched to, the host restarted gracefully so a streaming reply finishes on the old one. BunVersion is the floor; the Bun before stays on disk, and a new one whose try fails, or that the host won't start on, is set aside for the one before and never taken again (bun/state.json). $MAGPIE_BUN is left alone. Plugins' page and magpie plugins show the version in use. TestCheckBunTakesASettledRelease, TestCheckBunWaitsAndKeepsTheFloor, TestCheckBunSetsAsideOneThatFailsItsTry, TestTryBunOnRealBun and TestHostFallsBackWhenTheNewBunDies pass on macOS, Linux and Windows; a real run from 1.3.14 to GitHub's 1.4.2 started the host on all three. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestFetchNewLeavesListedPlugins failed once under load, passes alone). 2026-10-01 19:33:04 +08:00
yetone 289de06b1f providers: the way to more subscriptions in Plugins is in the add sheet alone (the user: 这个不应该在这个页面,应该在第二个页面的这里) — the providers list's "More subscriptions in Plugins" button beside Add provider is gone; the add sheet's Subscriptions ends with its "More in Plugins" row, as before. plugin-market passes (Chromium, WebKit, en/zh); click-scroll, old-webkit, providers-off-fold pass. 2026-10-01 19:07:28 +08:00