Commit Graph
144 Commits
Author SHA1 Message Date
tsuixl bd33e575f1 gui: let users choose installed interface and code fonts (#1007)
* gui: choose installed interface and code fonts and styles

Discover installed styles through DirectWrite, CoreText and Fontconfig. Keep choices per computer, apply them before paint and notify both desktop webviews after saving; defaults retain platform and Omarchy fallbacks.

Verified seven focused Go tests, including real Windows discovery (90 families, 276 styles), 14 font browser cases in Chromium/WebKit, 68 settings/zoom/Omarchy/web cases, go vet, Windows amd64/arm64 and Linux nogui builds. Against the original assets, the new UI test fails with Settings must offer an interface font picker (0 != 1). Full Windows suites have separately reproduced upstream failures; macOS/Linux native discovery is covered by the added CI step.

* gui: retain bundled Mac fonts and variable style precision

Only filter an undownloaded CoreText face when it is explicitly downloadable; bundled fonts are installed without being downloaded. Use resolved font names when descriptors omit them. Preserve fractional weights and nonnegative CSS widths for named variable styles.

* gui: restore the authored font cascade when returning to defaults

Apply family-only trait overrides only while a custom face is active. The browser regression catches code under a bold parent becoming 700 instead of its original 400. Keep the compatibility shim first, and apply boot fonts before visible content.

* gui: keep font picker review separate from CI integration

Leave the native test sources and manual verification command with the feature. Move the added workflow step to a follow-up so the font picker can take the normal review path requested on PR #1007.
2026-10-06 19:32:56 +08:00
yetone 490d2ea362 gui: the tray panel's Allowances tab hides subscriptions and moves them, from an Arrange at its foot (H20 on Discord: "菜单栏的详情页能不能选择某些不显示,有点额度并不想看到或者可以调整顺序,目前的还不行") — the panel's tab already followed the Usage page's order (usageOrder, byUsageOrder), but it could be set only by dragging a card's logo on the window's Usage page, which nothing in the panel said, and there was no way to leave a subscription out of the panel at all. The tab's foot now has Arrange, and "{n} hidden" beside it when some are: Arrange lists a row a subscription (balances too) in the shared order, its logo the handle (a drag, or Alt+arrows; the grip by it on hover only, as on the Usage page) and a Hide/Show pill, under a note that hiding is the panel's only and the order is the Usage page's too; Done draws the cards again. What's hidden is kept in magpie's settings (settings.PanelUsageHidden, panelUsageHidden), not the browser: routing, caps, the Usage page and the menu bar's cells still have it, and a menu bar cell for a hidden subscription still opens its card (panelPeek) until the panel is put away or the tab left. POST /api/usage/arrange takes order and panelHidden and changes only the one it is sent (both were whole-order before; an order alone still works), and the Settings save keeps panelUsageHidden as it does usageOrder. Saves draw at once and are put back with the error if they fail; no click moves the page (the view's scroll is kept across the redraw); no native select, no left-border stripe. Which allowances the menu bar shows beside its icon stays Settings' pick (trayUsages). New t() strings have zh, ja and de. TestUsageArrangePanelHidden (each field set and cleared on its own, a Settings save between) passes -race -count=20, TestSettingsSaveKeepsWhatItDoesNotSend's fixture names the field. panel-arrange.test.cjs (440px: the hidden card left out and counted, Arrange keeping the reader's scroll, the rows and note, the hover-only grip, no stripe, no sideways overflow, Show/Hide posting panelHidden without a scroll and a failed save put back, Alt+arrow and a drag posting the order, Done's cards in it, a menu bar cell opening a hidden card until the panel hides, the Usage page keeping the hidden card) passes on Chromium and WebKit in en, zh, ja and de; click-scroll, gui-ja, gui-de, i18n-keys, panel-fold, panel-usage, quota-*, routing-quota-left, tray-cell-click, tray-inuse, tray-logos, tray-usages, usage-accounts(-fold), usage-arrange, usage-fold, usage-quota-head, usage-card-refresh, usage-stale-answer, usage-remote-cards, api-key-usage, claude-usage-asked and usage-checkin pass on both. go test -tags nogui ./internal/... under a temp HOME, go vet, gofmt -l, linux (nogui) and windows builds pass. Not tried in the real tray panel on a Mac. docs/reference.md and docs/subsystems/gui-shell.md say it. 2026-10-06 19:18:14 +08:00
yetone 87487c5ef0 plugin, provider: a plugin can press its vendor's daily check-in itself (auth.checkin), and magpie runs it as its own check-ins (Lemon on Discord #feedback: provider 每日签到的方法能放到插件里面去做吗; ARNO earlier asked for Qoder's) — the check-ins magpie had (WorkBuddy, Trae CN, MiniMax Code, Qoder) are each written in magpie against the vendor's endpoint, even for a subscription that moved to its plugin, so a plugin author had no way to give their provider one. A plugin's auth hook may now have checkin(getAuth, provider) beside usage: the host lists the provider with checkin: true (plugin.Provider.Checkin) and answers checkin({provider, account}) (plugin.AccountCheckin), refreshing the account as usage does, with {outcome, credit, streak, message}; outcome is claimed, done, ineligible, inactive, captcha or failed, and a throw or an outcome outside these is failed with the reason. internal/provider/plugin_checkin.go runs these through checkin.go's checkiner, as the built-ins are: each signed-in account in use once a Beijing day (first look 2 minutes after start, then every 30), a failure asked again after wbCheckinRetry, every other outcome settled for the day, results kept in plugin-checkin.json by provider|account; a failure is recorded as failed and resets nothing. captcha is new (CheckinCaptcha): the vendor wants a captcha, which magpie never solves, so it is said as "check in in its own app", not as a failure, and not asked again that day. The results mark the account's Usage card (checkinBy "plugin:<provider>", WithCheckins), so the card's check-in row, its Auto check-in and Check in now work for it (POST /api/settings/plugin-checkin {provider, on}, POST /api/usage/plugin-checkin {provider}); Settings → Usage gains a Plugins tab, shown while such a plugin is signed in, a Daily check-in row per provider with Off/On and how each account went; magpie accounts checkin and c on the TUI's Usage page include them (the TUI's "no account" line names check-in plugins). The switch (settings.PluginCheckins, by provider) is off by default; for WorkBuddy, Trae CN, MiniMax Code and Qoder it follows that vendor's existing switch until set on its own, and magpie's own check-in through the plugin's fetch leaves a provider whose plugin checks in to it (pluginChecksIn), so no account is checked in twice. The Settings page's save carries PluginCheckins over (TestSettingsSaveKeepsWhatItDoesNotSend's fixture names it). No plugin declares the hook yet; Qoder's check-in stays magpie's own here. TestPluginCheckinRunsAsTheBuiltinsDo (the fake plugin with FAKE_CHECKIN, five accounts answering claimed, done, captcha, a throw and an unknown outcome: off presses nothing; on, each outcome kept with its vendor; a minute on nothing pressed again; past the retry only the two failures; the next day all five; PluginCheckins and the cards marked; another provider's press asks none) and TestPluginCheckinTakesOverTheVendors (a qoder plugin without the hook leaves magpie's Qoder check-in its account, with it none; PluginCheckinOn's fallbacks) pass -race -count=10, and the latter fails with pluginChecksIn stubbed false; TestTUIChecksPluginsIn passes -race -count=20. plugin-checkin.test.cjs (the card's row with the captcha said, Auto check-in posting {provider, on}, no page move; the Settings Plugins tab at 440px with the row fitting, no sideways scroll, On posting) passes on Chromium and WebKit in en, zh, ja and de; usage-checkin, i18n-keys, gui-ja, gui-de and settings-* pass. go test -tags nogui ./internal/..., go vet, gofmt -l, linux (nogui) and windows builds pass. Not tried with a real plugin or account: no published plugin has the hook yet. docs/subsystems/provider-plugins.md (the hook's contract) and docs/reference.md say it. 2026-10-06 18:40:45 +08:00
yetone 5361a2178e agent, gateway: Cursor Private Inference's row has an effort, which magpie's gateway asks its requests for (#1003, gjf7) — other agents' rows pick a reasoning effort that magpie writes into their config, but Cursor Private Inference reads only its base URL and key from the environment, and its app offers no Reasoning control for most of magpie's models, so there was no way to set one short of a routing group with a member fixed at a level, which the reporter used as a workaround. Its row now has an effort field like the others': the default (as Cursor asks) first, then every level the models it lists have (cursorLocalEfforts: their levels in MemberEfforts order, low/medium/high where a model reasons with levels unknown, none at all when nothing it lists reasons or it isn't connected). The pick is kept in magpie, settings' agentEfforts by agent (provider.AgentEffort / SetAgentEffort, a level of MemberEfforts or "" for none), and the gateway's candidate loop asks a cursor-local request for it with withFixedEffort, fitted to the model's levels and bounded by a group's, on a model that reasons (the new Provider.Thinks: levels, its list's Reasoning, or models.dev's). Precedence: a group member's fixed effort, then a :effort suffix, then a routing rule's turn pick, then the agent's pick, then the request's own; the pick wins over a level Cursor sent, as Cursor sends its own default as often as a choice, and Default hands the choice back to Cursor. A model that doesn't reason and every other agent's requests go as they came. The Settings page's save carries agentEfforts over as it does fastPicks (TestSettingsSaveKeepsWhatItDoesNotSend's fixture names it). In the window the field sits under "Settings" rather than "New sessions" for this agent alone, as it applies to every request from then on, not to what a session starts on; no new t() strings (Settings, effort and default are translated in zh, ja and de already). TestAgentEffortOnCursorLocal (Chat and Responses with and without a level of their own, Claude on Messages made to think, OpenCode's request untouched, a non-reasoning model untouched, a member fixed at low kept, the pick taken off) fails without the gateway change and passes; TestCursorLocalEffort covers the options (none with no reasoning model, the union of levels, the low/medium/high fallback, none while disconnected), Apply and a bad level refused; both pass -race (-count=20 gateway, -count=10 agent). cursor-local-effort.test.cjs (the window at 560px and the tray panel at 440px: the field under Settings, the slider's five stops with Default first, a pick posting the field, no sideways scroll) passes on Chromium and WebKit in en, zh, ja and de; claude-effort, subagent-effort, agent-field-names, agent-connect, agent-layout, agent-tweak-fold, agent-models, cursor-local-launch and i18n pass on both. go test -tags nogui ./internal/..., go vet, gofmt -l, linux (nogui) and windows builds pass. Not tried with the real Cursor Private Inference: whether it sends a reasoning level of its own on models it shows a Reasoning control for is unverified, which the precedence makes moot. docs/subsystems/gateway-routing.md and agent-wiring.md say it. 2026-10-06 17:29:28 +08:00
forever f109b270c0 gui: the main window opens as it was last left — maximised again if it was, at its last size otherwise (#995)
On a MacBook the window had last been zoomed. magpie skipped a maximised
window when it saved the size, so it reopened at an older, nearly full
size: 1712×1083 at (8,34) on a 1728×1084 work area, neither full nor the
size it was given nor the default.

settings.WindowMaximised (this computer's own, KeepOwn) now records
whether the window was maximised when it settled; Window stays the size
it restores to (settle). A window not shown yet, let go, full screen or
minimised, or on Linux hidden, records nothing.

Reopened maximised: on the Mac and Windows placeMain maximises the made
window just before its first Show (on Windows once its page has come);
Wails' StartState is undone by the Mac's centring and shows a Windows
window before its page. The least size Maximise drops is put back. On
Linux makeMain sets StartState, since GTK can't be asked about a window
not mapped yet.

A size kept on a larger screen: Windows fits the window to the work area
and centres it, title bar in reach, and keeps the larger size for the
larger screen (fitted); the Mac fits and zooms it itself, and magpie
doesn't zoom it again, so it can still be restored.

TestSettle fails with upstream's rule (5 of 9 cases); TestOpenSize,
TestFitRoom, TestWindowMaximisedKeptAndOwn; the Mac's real build under a
sandbox HOME, 11 cases.
2026-10-06 16:09:58 +08:00
yetone 2cd25e2e88 codex: each ChatGPT account can have its own daily warm-up time (#957) — two Plus accounts both warmed at the one codexWarmAt (07:00) started and ran out their 5-hour windows together. settings.codexWarmAtOf holds an account's own time ("HH:MM" or "off"), by its name in lower case; an account not in it follows codexWarmAt as before. warmNow picks each account's time, and the loop wakes for an account's own time even with the global one off. Settings › Usage › Warm-up, Codex: with two accounts or more, each has a row under Daily warm-up — Same (the time above), Own (a time field) or Off — saved through POST /api/settings/codex-warm-at, and the full Settings save keeps the map. zh/ja/de added.
TestWarmAtEachAccountsOwn, TestWarmLookAtAnAccountsOwnTime and TestSetCodexWarmAt pass with -race -count=20, and the first two fail when an account's own time is ignored. TestSettingsSaveKeepsWhatItDoesNotSend covers the new field and fails without the keep in api.go. warm-at-own (two accounts: rows, Own posts 06:00 then 09:00, Off, back to Same, the page doesn't move, nothing cut, no <select>; one account: no rows) passes in Chromium and WebKit, en/zh/ja/de, 900px and 440px, and fails 8 of 9 with main's app.js. i18n-keys, gui-ja, gui-de, settings-groups, settings-sections, click-scroll, reset-reminder, usage-alert and codex-auto-review pass in both engines; go test ./internal/provider ./internal/settings ./internal/gui and vet pass.
2026-10-06 14:32:58 +08:00
forever b37ac53bef codex: a switch for whether an account spends its credits (#974)
* tui: show a ChatGPT account's credits beside its windows

The Usage page printed only the balance for an account that had one, so a
ChatGPT account holding credits lost its 5-hour and weekly windows there.
The balance now follows the windows, as on the app's cards and in
magpie quota.

* accounts: tell a ChatGPT account's credits

magpie accounts left out the credits an account holds, which magpie quota
and the Usage page show. Its rows now carry them, in the line and as
balance in --json.

* codex: a switch for whether an account spends its credits

A ChatGPT account holding credits keeps answering once a usage window is
used up: the vendor spends the credits, and a task goes on. That stays the
default. Each Codex account now has a say in it (Usage card and menu bar
panel: Use credits; magpie quota credits <account> on|off; settings'
codexNoCredits). Turned off, the gateway holds the account at 100% of a
window, as a usage cap holds it at its share, so the other accounts,
groups and fallbacks take the request and no credits are spent. With none
left the request gets a 429 naming what holds each account and how to go
on, unless the account spends its resets by itself and its week is used
up: then a reset is spent first, whether every candidate was held or the
last one tried was refused. A cap set on the account takes precedence.

A reset made now forgets the account's cached allowance, so neither a cap
nor this hold outlives the windows it started again. The switch is
routing's only: which account Codex is signed in to is unchanged.

The Routing page tells why such an account was left out, and magpie quota,
magpie accounts and the TUI mark credits not spent.

* usage: an auto-reset switch keeps by its first line

The auto-reset row centred its switch on its words, so the switch moved
when they rewrapped on a click, and it sat lower than the Use credits
switch beneath it. Both now keep the switch by the first line.

* gui tests: quota-curve expects the Use credits row to close a Codex account's section

The credits switch adds a row after the Auto-use one, so the account after
a Codex account follows the curve, the Auto-use row and then the Use
credits row.
2026-10-06 14:20:09 +08:00
yetone 8b52c521fa qoder: daily check-in for Qoder and Qoder CN (ARNO on Discord) — while Settings › Daily check-in › Qoder (or the card's Auto check-in) is on, the gateway claims each plugin account's claimable daily credits (campaigns with actionType CLAIM_BENEFIT on openapi.qoder.sh / openapi.qoder.com.cn, as wallechfox/qoder-checkin claims them) once a Beijing day, through the plugin's fetch on its device token (@magpie-community/opencode-qoder-auth 0.2.7, now the mover's min). One claimed already is done, none listed is no event, an expired sign-in or an old plugin says so. Kept in qoder-checkin.json by site and uid; shown on the usage card, in the TUI's and CLI's check-in. Built-in (unmoved) accounts aren't checked in. Not tried with a real Qoder account. qoder_checkin_test (fake campaigns, both sites; -race -count=3) and usage-checkin (Chromium and WebKit, en and zh, fails without the GUI change) pass; go test provider/gui/tui/settings/gateway, vet, linux/windows/nogui builds pass. 2026-10-06 11:41:11 +08:00
yetone 2533ccdaf9 gui: a model with a fast mode is sent fast, or not, from its row in the agent's picker (#954, Gyarados4157) — Fast was only a routing group member's (Group.Fast), so a GPT on a ChatGPT account picked straight in Codex or OpenCode could only go fast through a group of one. A model provider.CanFast says has a fast mode (a ChatGPT account's GPT, an OpenAI key's Responses GPT/o-series, Cursor's -fast, Claude Opus on an Anthropic key) now has a bolt beside its star in the agent's picker and in the same picker opened from a provider's row; a click posts /api/agent-fast and switches it for that agent (settings FastPicks, by agent, "<provider>/<model>", moved by a provider rename and kept by the Settings page's save) without changing the pick or closing the picker; a refusal puts it back. The field shows a bolt after a model sent fast. The gateway asks for the vendor's fast mode (service_tier "priority" / speed "fast", as for a group member) on that agent's requests for that entry, every account and key of it, not its fallbacks, another agent's requests or the same model at another provider; the Routing trace marks the try Fast. A routing group is never switched: its members say it. zh/ja/de added. fast-pick (window and 440px panel, en/zh/ja/de, Chromium and WebKit; the bolt only on models with a fast mode, the post, the refusal, the provider row's picker) passes and fails without the fix; TestFastPick (gateway, Chat/Responses/Anthropic in, fails with the gateway hunk off), TestSetFastPick, TestFastPickOptions and the settings-keep test (fails without the carry) pass with -race; i18n-keys, gui-ja, gui-de, favorite-star, agent-models-picker, claude-picker-fold and click-scroll pass; go test ./internal/gui/ ./internal/provider/ ./internal/agent/ ./internal/gateway/ ./internal/settings/ pass. 2026-10-06 11:15:52 +08:00
tsuixl db55bc8b7a gui: reject unknown Codex auto-review models from known providers (#938) (#943)
Resolve deliberately accepts arbitrary model names under an existing provider.
The auto-review setting consequently accepted fake/missing and fake/, replacing
a valid reviewer with one that fails when Codex requests approval.

Validate against the served catalog at the settings endpoint. Keep model and
group choices, unlisted providers, whitespace trimming and clearing. Rejected
values leave the saved reviewer and model-list tag unchanged.

TestCodexAutoReviewSetting fails against the upstream API with fake/missing
and fake/ returning 200, and passes with the fix. Focused auto-review tests,
go vet ./..., the Windows build and Linux nogui build pass. The full Windows
suite has failures also reproduced with upstream production code; details
are recorded in the PR.
2026-10-06 09:26:04 +08:00
yetone 9abfbff607 cleanup: gofmt the 15 Go files main had unformatted — whitespace, comment alignment and three one-line funcs gofmt opens up; nothing else. go build and vet -tags nogui pass. 2026-10-06 07:43:47 +08:00
yetone ede0efb054 codexcat, gateway, gui: Settings › Codex auto-review model picks the model Codex's auto-review decides approvals on (#938, guitaoliu)
Codex's auto-review (approvals_reviewer = "auto_review") runs on the
auto_review_model_override of the conversation model's entry in its list,
else on codex-auto-review, else on the conversation's model at low effort,
so a conversation on an expensive model reviewed every approval on it.
settings.CodexAutoReview, picked from the app's menu (magpie's models and
groups, or Codex's own pick), is now put as auto_review_model_override in
every entry magpie hands Codex: magpie-models.json and a signed-in Codex's
/models, the ChatGPT backend's entries included. Unset, the list is as it
was (an override magpie left in Codex's cache is taken out, one OpenAI gives
kept). POST /api/settings/codex-auto-review refuses an id magpie doesn't
serve; settings refuse a value that is no model's id; the list's ETag
carries it so Codex asks again; another Settings save keeps it.

Tested end to end in a sandbox HOME with a real DeepSeek account and Codex
0.160.0 (codex exec, approval_policy on-request, approvals_reviewer
auto_review, read-only sandbox, a command asking for escalation): with the
setting deepseek/deepseek-flash the guardian request (kind guardian) went
to deepseek-flash while the conversation stayed on deepseek-v4-pro; unset,
magpie-models.json had no override and the review ran on deepseek-v4-pro.
Picking DeepSeek V4.1 Flash in the real web UI's Settings row wrote the
setting and rewrote magpie-models.json (4 entries with the override), and
the next codex exec reviewed on deepseek-flash. TestCodexAutoReviewEntries,
TestCodexAutoReviewChecked, TestCodexModelsAutoReview,
TestCodexAutoReviewSetting and codex-auto-review.test.cjs (Chromium and
WebKit, en and zh) are new and fail without the change; gateway-mode and
settings_keep cover the new row and field. go vet, go build (GOOS=linux
nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass.
2026-10-06 01:58:42 +08:00
wizzy-yangandwizzy-yang 7cba95b13d gui: a save shares one build of the catalog, not one per look-up (#908)
Saving a routing group took about 2.4s on a machine with 20 providers and
95 models. The save is not slow: the state it answers with is. groupsState
asks every group for its members and every model for its facts, and the
held middleware let only GET share one build of the catalog
(provider.Hold), so each of those look-ups built it again — 20 builds in
one save here, each reading every provider's files and every agent's
sign-in.

The Routing page's own 25s trace long-poll is a GET, so it usually holds
the catalog and the save costs ~140ms. Whenever it is not in flight — the
first save after the page loads, or between two polls — the save falls
back to the full 2.4s.

A write shares the build now. It is safe because every write drops what is
held as it happens: provider.store ends in catalog.Touched, and
catalog.Forget is Changed (provider/hold.go), while settings.Save calls
catalog.Forget itself. So a save still answers with the state after the
write, never the one before it, and the request leaves nothing held for
the next. Reads are unchanged.

TestSaveSharesOneCatalogBuild counts the builds one save makes
(provider.AllBuilt: 20 before, 2 after) and checks the answer is the state
just written — the group saved, renamed, deleted. TestAgentWriteAnsweredAs
Written covers the one path a write does not drop the catalog on,
agent.Apply (it writes an agent's config and never calls
catalog.Touched): the field it wrote is in the answer that POST returns.

POST /api/groups/save, the same group of 6 members (5 of them groups),
same config, same machine: 2452, 2659, 2388, 2333, 2308ms before
(median 2388ms), 1650, 138, 127, 124, 137ms after (median 137ms; the first
is the cold start). go vet, go build and go build -tags nogui pass.
TestGatewayFixQuitsOnlyAMagpie and TestPurposeAssetMatchesUsage fail on
unmodified main here too (a running magpie on the port, a stale generated
catalog), so they are not this change's.

Co-authored-by: wizzy-yang <wizzy-yang@users.noreply.github.com>
2026-10-05 21:42:15 +08:00
yetone 43a6595035 provider, gui: Codex's only account can be signed out from its row, as codex logout does, and removing the account Codex is on while another is saved says what to do, in the reader's language (mamba on Discord) — removing Codex's only account failed with "codex is signed in to <user> now; switch to another account first", with no other account to switch to, and Providers › Codex › Sign out hit the same error, as it forgets the account in use last. ForgetLogin on the account Codex is signed in to, with no other Codex account saved, now signs Codex out as codex logout does: ~/.codex/auth.json is deleted, magpie forgets the account and drops what it served (magpie only knows file-stored Codex sign-ins, liveLogin reads auth.json, and the keychain is never touched); one signed in but not saved yet is signed out the same way. With another saved it is still refused, now as a SignedInError ("Codex is signed in to <user> now: sign it in to another of its accounts first (Use on that account), then remove this one"), which /api answers with code signed_in, the agent and the user, and app.js says in en/zh/ja/de. The only Codex account's row in its editor has Sign out (退出登录), asked in the app's own dialog, whose Cancel, Escape and backdrop go back to the editor and send nothing (closeConfirmAsk follows a dialog's back), then posts login/forget; the removed-provider Sign out dialog for Codex says Codex itself is signed out too. Claude Code keeps the refusal: its sign-in may be in the keychain, which magpie never touches, and magpie serves its saved accounts while it is signed out (claudeStandIn) and treats its /logout apart (claudeLoggedOut). TestCodexSignOutLastAccount (refused with another saved, auth.json kept; signed out when the last; an unsaved sign-in signed out; an unknown account still an error), TestForgetAccountSignsCodexOut (fails without the change with mamba's error) and TestClaudeInUseStillRefused are new; codex-signout.test.cjs (WebKit, en and zh: the button and its title, the dialog, Cancel and Escape post nothing and go back to the editor, the post and its status, no Sign out with two accounts, the translated refusal, no select, no left border, no scroll) fails without the change. The account-*, own-signin-remove, saved-signedout-remove, reset-reminder and click-scroll tests pass in WebKit; Chromium wouldn't launch on this machine. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. 2026-10-05 21:23:18 +08:00
yetone 528d6c12dc agent, gui: Codex left on model_provider = "magpie" with no [model_providers.magpie] loads its config again, and connecting with nothing in magpie says what to do, in the reader's language (Tystem on Discord) — the Codex app said "无法加载 config.toml … Model provider magpie not found", magpie showed Codex as not connected on "deepseek-flash", and its switch answered only "Codex can't be connected to magpie", in English. Codex looks model_provider up in [model_providers.*] as it loads config.toml (codex-rs: "Model provider {id} not found"), so the key without the table keeps it from loading any config, a new thread too, not just an old one. magpie never writes one without the other: it takes model_provider out whenever it steps out as the provider (disconnect, Unwire, a model of Codex's own, Join) and keeps the table so the threads started on magpie still open. That is unchanged, and TestCodexSteppingOutLeavesNoHalfState now covers it. Another tool or a hand edit had taken the table away. Sync, at magpie's start and whenever the catalog changes, now writes the table back whenever the config names magpie as its provider, at the top or in a profile, and has no table, whether Codex is connected or not. Check reports that state too. A config magpie can't parse is still only reported, never written to. A Connect that finds none of magpie's models because no provider or subscription has been added answers agent.NoModelsError, "Add a provider or subscription in magpie first, then connect Codex". /api answers it with code no_models and the agent's name, which app.js says through t() (en/zh/ja/de). docs/reference.md no longer says the table goes with a native model. TestCodexProviderWithoutTable (top and profile: Check says so, Sync writes the table) and TestConnectWithNoModels in internal/agent, TestAgentConnectNoModels in internal/gui, and agent-connect-no-models.test.cjs (WebKit, en/zh/ja/de: the toast in the reader's language, the switch back off, no scroll, no native dialog) are new and fail without the change; the latter's en case passes either way, as the server's message is English. Chromium wouldn't launch on this machine for this test or the existing agent-connect.test.cjs. TestCodexSyncAddsBaseURLToProvider now expects the table added to a hand-made provider that had none. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. In the first full run, internal/gui's TestProvidersAnswerWhileListsComeIn and internal/proc's TestUserPath failed under load; both passed alone and in the second full run. 2026-10-05 21:09:13 +08:00
yetone dab887db57 update, gui: Settings › About › Download source sets the update mirror, and a download that failed through it offers GitHub (#893, M3chD09) — the mirror updates could come through was only magpie update mirror in a terminal, and the GUI never said where updates came from or that a failed download was the mirror's. About now has a Download source row: GitHub, or the mirror set (the same UpdateMirror setting the CLI writes); Use a mirror… takes a full https:// address (http, a bare host or a malformed one refused, with the reason in the row and the draft kept), Change edits it, Use GitHub takes it away in one click; Enter saves, Esc cancels, no click scrolls. POST /api/settings/update-mirror sets it; the Settings page's own save keeps it. A download through the mirror that doesn't answer, answers other than 200, or sends a file whose SHA-256 isn't usemagpie.ai's feed's is an update.MirrorError (never installed, the file removed, as before); /api/update's error carries the mirror, and the Version row says the download failed through that mirror and offers Download from GitHub, which takes the mirror away and downloads again. magpie names no mirror or CDN of its own. en/zh/ja/de. TestMirrorFailuresAreTheMirrors (tampered, 404 and closed mirrors each the mirror's, no file kept; a direct failure no mirror's; CheckMirrorURL) and TestUpdateMirrorSetting (set trimmed, http/bare/ftp/empty-host refused, Settings save keeps it, off, an updater failing through a 404 mirror reports it with retry, a new check clears it) are new; update-mirror.test.cjs (WebKit, en and zh: GitHub shown, http refused with the reason, https saved with Enter, Use GitHub posts "", a failed download through the mirror offers GitHub, which posts "" then update/install; no select, no left border, no scroll) fails without the change; update-auto, cli-path, cli-behind, settings-sections and click-scroll pass in WebKit. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. 2026-10-05 18:49:04 +08:00
yetone 1534382ff9 gui, web: gateway mode — magpie web on a server that is only a gateway shows no Agents, Sessions or Library (Player on Discord) — a magpie web run on a server or NAS for other machines' agents showed the Agents, Sessions and Library pages and the settings written into this machine's agents, none of which do anything there. In gateway mode the page has Providers, Gateway, Routing, Usage, Plugins and Settings; an address kept for a page left out opens Providers. Settings leaves out provider in model names, Codex subagents, long conversations, Codex thread titles, the usage/balance alerts and reset reminders, on top of the tray, open at login, Dock, text size and lightweight rows magpie web already hid; Language, theme, currency, proxy, redaction, vision/image models, sync, updates and the rest stay. magpie web is in it by itself when it finds no agents on its machine (the Docker image, a NAS) and with the new magpie web --gateway; Settings › General › Gateway mode (Automatic / On / Off, saying why it is on) wins over both and Off brings every page back at once. The choice is this machine's own (KeepOwn: never synced or restored from another computer), set only by POST /api/settings/gateway-mode, never by the Settings page's save. The app's own windows are never in gateway mode. boot.js tells the page before it paints. en/zh/ja/de; docs/reference.md (Docker › NAS, CLI list). TestGatewayMode (temp HOME: no agents → on, agents → off, --gateway → on, Off wins over --gateway, the Settings save leaves it, On with agents, Automatic, an unknown mode refused, the app window never told, KeepOwn) is new; TestSettingsSaveKeepsWhatItDoesNotSend sets it. gateway-mode.test.cjs (WebKit, en and zh: the tabs, ?view=agents/sessions/library open Providers, the switch and why, the seven rows hidden, Off posts "off" and brings tabs and rows back with Settings kept open and Agents opening, Automatic hides them again, the Settings save never sends it, no scroll, no select, no left border; web with agents and the app window keep every tab, the switch is magpie web's alone) fails without the change. mobile-web's desktop comparison differs from origin/main on Settings only, by the new row. settings-sections, settings-groups, click-scroll, cli-path, plain-names, codex-titles and agent-grip pass in WebKit. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. 2026-10-05 18:20:28 +08:00
yetone d6daa40ce3 update, gui: Settings › About › Command line says what magpie runs in a new terminal and adds it (PAMI on Discord) — the app downloaded from the site, and every Windows one, left terminals without the magpie command, and nothing in the GUI said whether it was there. A Command line row now asks each shell installed (zsh, bash, fish and the login shell; PowerShell and cmd on Windows) for its PATH and says, with a dot each, whether its magpie runs this app, another copy (with that copy's version, read from its .app's Info.plist or Homebrew Cellar folder, never by running it), or none. Add to PATH links this app's program (Homebrew's opt link, the AppImage) into the folder on the default shell's PATH whose magpie runs first, else ~/.local/bin or the like when on PATH, replacing another copy there as install.sh's ln -sf does; on Windows it puts the app's folder first in HKCU\Environment Path and broadcasts WM_SETTINGCHANGE. A shell whose PATH lacks that folder gets its own button naming its profile (~/.zshrc, ~/.bash_profile on the Mac or ~/.bashrc, fish's config.fish), which links into ~/.local/bin and adds that folder with one marked line, once; no profile is written otherwise. A translocated or disk-image app is told to move first and gets no button. It says new terminals take the change (on Windows: new PowerShell/cmd windows, restart Windows Terminal). GET/POST /api/cli; en/zh/ja/de. TestCLIOnPath (temp HOME, stubbed shells: link, fish profile once, a shadowing copy replaced, no folder → zsh's profile kept and appended, an unknown shell refused, plist version, translocated refused) and TestPathFirst are new; cli-path.test.cjs (WebKit, en and zh: per-shell dots, Add posts {} and turns green, fish's button posts fish alone, Windows lists PowerShell and cmd, translocated gets no button, no scroll, no select, no left border, translations) fails without the change; cli-behind and update-auto pass. Not tried on a real Windows machine. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass (TestOTelWholeWaterfallFallback and TestDevinSignedInOnlyInMagpie failed once each under load and pass alone). 2026-10-05 17:49:20 +08:00
yetone 1aaac93b52 provider, gui: Compact at — where Codex and Claude Code compact a long conversation, set per provider or per model in its editor and to any size in Settings → Long conversations (#876, hisiling)
Until now the only choice was 272K for every model or Full window for every model. The 272K default stays as it is (e950eb08: OpenAI runs its own 1M models at 272K in Codex, and Chen saw 70–90s to a first token at 550K).

What changes:

- A provider's editor has Compact at beside Context window and Max output, in the same form: "500k" for all of its models, "deepseek-v4-flash=272k" for one, and empty to follow Settings. A size at or above a model's window means the whole window. The values are kept in settings.ModelCompacts.
- Settings → Long conversations takes a size of its own beside Compact at / Full window (settings.CompactAt).
- `magpie model compact` takes a size such as 500k.

Order: the model's own value, then its provider's "*", then Settings. Full window turns off only the Settings value. A group uses the smallest value among its members.

Where it applies:

- Codex's model_catalog is told the threshold as context_window, with the whole window as max_context_window.
- Claude Code's CLAUDE_CODE_AUTO_COMPACT_WINDOW follows the main model. A Claude model now takes its provider's value when one is set.
- The Codex list tag changes when the Settings size or Full window changes.

Tests:

- New: TestModelCompactsSetFromTheEditor, TestSetCompactAt, TestCodexCatalogCompactAt and TestClaudeCompactAt.
- TestSettingsSaveKeepsWhatItDoesNotSend now covers the two new fields.
- compact-at.test.cjs (WebKit, en/zh) checks the editor field (shown, refused, chips, saved) and the Settings size (posted, hidden under Full window, no scroll).
- go vet ./..., go test -tags nogui of the root package, provider, codexcat, agent, gui, gateway, settings and catalog under a temp HOME, and GOOS=linux/windows builds all pass.
2026-10-05 15:20:05 +08:00
yetone 2c801b6f9a gui, proc: the Gateway card quits an older magpie serving the gateway and takes it over, or restarts the gateway (leslie_luo on Discord) — the card said an older magpie served the gateway and to quit it, with no way to tell which process that was. With an older magpie on the port it now has "Quit magpie {v} and take over": the process listening on the gateway's port is found (lsof on a Mac, /proc on Linux, Get-NetTCPConnection on Windows), its program path checked to be magpie's (magpie, magpie-*, dial, .exe/.old), asked to quit (SIGTERM, then SIGKILL after 5 s; TerminateProcess on Windows) and this magpie serves the gateway at once instead of within 15 s. Otherwise the card has "Restart gateway" ("Start gateway" when none runs); a magpie of this version or newer gets no button. Anything else on the port is never ended: a program that isn't magpie, one of another user or an administrator, one that won't quit, or a magpie a LaunchAgent/systemd unit/scheduled task starts again is said under the button with its path and pid, in en/zh/ja/de. TestGatewayFixQuitsOnlyAMagpie (copies of the test binary on a port of its own: one named other-server answering as magpie is left running, one named magpie is quit and the gateway is served here, then restarted) and gateway-takeover.test.cjs (WebKit and Chromium, en and zh, no scroll, no left border) fail without the change; TestListeningOn/TestIsMagpie pass on macOS and in a Linux container. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. 2026-10-05 15:05:31 +08:00
Junjie Zhou 4a97acf12a refactor(agent): separate Aside provider connection from model settings (#827)
* refactor(agent): connect Aside as a native provider with verified settings

* fix(agent): offer explicit offline Aside model and disconnect actions

Offer Save for next start after an initial runtime read failure, using a
structured API error instead of saved runtime state. Keep refusals and
failed readbacks on the live error path.

Add a separate offline disconnect confirmation that restores the checked
file plan, clears restore records and removes the provider last. Bind the
confirmation to its preview revision and roll back ordinary write failures.

Cover offline API and browser flows, cancellation, stale plans, missing
CLI, restore metadata and native provider spelling. Keep state file-only.
2026-10-05 14:37:44 +08:00
yetone 6e500cb520 provider, gui: a reset reminder, a notification before a long window renews with much of it unused or before resets expire (thedavidweng, #720)
thedavidweng asked to be told before an allowance resets, or a banked reset runs out, while it is still unused. magpie's notifications only said a window was nearly used up or a balance was low.

Settings › Usage now has a Reset reminder row: Off, 12 h, 24 h or 48 h before (settings.ResetReminder, 0 to 168 hours).

What it tells of, through the same watcher and notifications as the usage alerts (WatchQuotas, dueReminders):
- a window of a day or more (weekly, monthly) renewing within those hours with under 85% of it used. A 5-hour window renews too often to be news.
- an account's resets (Codex's, a GLM team plan's) expiring within those hours unused.

Each is told once per run, kept in usage-alerts.json as the alerts are. A failed or stale reading says nothing.

The notification reads, for example, 7 days: 55% left, renews tomorrow 14:30 — use it before then, and 2 resets unused, expiring tomorrow 14:30. zh, ja, de strings, there and in the row.

Turning it on asks the system's leave to notify and looks at once, as the alerts do.

Tests:
- TestDueReminders: once per run; a 5-hour window, one 90% used and one renewing later are left out; a longer lead takes the later one; resets told; a failed reading says nothing.
- TestReminderText (en, zh, ja, de).
- reset-reminder.test.cjs (WebKit, en and zh): the row, a pick saves it, another save keeps it, no click moves the page. It fails 2/2 on the old assets. usage-alert.test.cjs passes.
- go vet passes. The darwin, GOOS=linux nogui and GOOS=windows builds pass. go test -tags nogui -count=1 ./... passes under a temp HOME.
2026-10-05 14:36:41 +08:00
yetone 273be22cac agents: Codex's /model lists its models in the order dragged on the Agents page (M3chD09, #855) — Codex lists the models by the priority magpie gives them: a ChatGPT account's own first, as the backend has them, then magpie's in the catalog's order, with no way to change it. Codex's model list (Pick on its row) now has a third view, Order: the models shown as Codex lists them, numbered, each dragged elsewhere (its grip shows on hover) or moved with Alt+↑/↓; it is saved at once (settings orderedModels, by agent and entry id), and the account's own models and magpie's go in one order. A model not in the order (one added later) goes after those that are, as it stood; the hidden stay hidden and out of the order view, hiding keeps the order, and a provider renamed renames its ids in the order too. Default order puts magpie's back. Codex gets it at once: signed in to ChatGPT, the gateway's /models gives the backend's and magpie's models priorities 1..n in that order and the list's ETag carries the order so Codex asks again; not signed in, the catalog file magpie writes for Codex (ListedFor → InOrder) is rewritten in it. Only Codex's list can be ordered; base_instructions are untouched. zh, ja, de strings. TestCodexModelListInUsersOrder (gateway: default order, an order across the account's and magpie's, the ETag changing, CatalogFor, opencode's untouched, none restoring it) and TestAgentModelOrderAPI (gui: orderable, an order read back, a new model last, hidden kept, hiding keeping it, reset, opencode refused) fail on the old code (no SetModelOrder/ModelOrder); codex-model-order.test.cjs (WebKit, en and zh: Order lists the own first and leaves the hidden out, Alt+↓ and a drag post the order, Default order posts none, nothing scrolls; a list that can't be ordered has no Order) fails on the old code (no Order). agent-models, agent-models-all-hidden, agent-models-loading, agent-model-groups, click-scroll, gui-ja and gui-de pass on WebKit. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. 2026-10-05 11:52:34 +08:00
yetone 2880b95b60 settings, gateway: the gateway's port is set in Settings › Network › Gateway, and every agent magpie connected moves with it (Magic_zero on Discord: 能否添加自定义端口号 — 3425 is taken on their machine, and they want a port of their own that is easy to remember and tell apart)
Before this, only MAGPIE_ADDR could move the gateway, and the app on a Mac or one started from the Start menu has no easy way to set an env var.

The setting:
- Settings has a Gateway port field. Any port from 1024 to 65535 can be typed, then applied with Apply or Enter.
- The port is saved in settings.json and kept on this computer (KeepOwn), so a backup or WebDAV doesn't carry it across.
- MAGPIE_ADDR, where it is set, still comes first. The field is then off and says so.
- A port out of range is refused in the page.
- A port another program listens on is refused: "port N is in use by another program: pick another one". The gateway stays where it was.
- With another magpie serving the gateway, the field says to set the port there.

On a change:
- The listener moves live (Relisten listens on the new port before closing the old one).
- When the old port had been taken by another program and nothing served, the gateway starts on the new one.
- Every agent connected to magpie has its config rewritten with the new URL (agent.OnGateway before the move, agent.Rewire after):
  - each one whose wiring is now "unwired" is set again;
  - Codex signed in with ChatGPT is joined again and keeps its own model;
  - Cursor Private Inference's variables and dsh's routes follow.
- What each agent had before magpie (the stash) is kept through this. Before, setting an agent again on the old URL would have written magpie's old URL and token over it.
- The page says how many agents moved.

What follows the saved port (through gateway.Addr()/URL() and settings.GatewayAddr):
- the CLI and healthcheck;
- Running();
- LAN sharing (Port);
- the drift check for the installed magpie, from beside a MAGPIE_ADDR one;
- importapps' "is this entry magpie's own" check.

zh, ja and de strings.

Tests, all failing on the old code:
- TestPortMovesEveryAgent: every agent with a config file is connected at 3591 and moved to 3592. No file keeps 3591, and none is off afterwards.
- TestPortMovesJoinedCodex.
- TestPortLeavesAnUnwiredAgent.
- TestPortKeepsWhatAnAgentHadBefore: the relay URL and own token come back on disconnect.
- TestDriftInstalledMagpieOnItsPort.
- TestSetPortMovesTheGateway, through the API: taken, out of range, moved, the old port silent, POST /api/settings keeps it, MAGPIE_ADDR refused.
- TestSetPortServesWhenTheOldOneWasTaken.
- TestGatewayURLOnSettingsPort.
- gateway-port.test.cjs on WebKit, en and zh:
  - out of range, taken (translated), applied by Enter, the agents counted, the new URL shown;
  - the MAGPIE_ADDR state;
  - no select, no left border, and the page never scrolls (the row is swapped whole, or WebKit laid the list out empty and the view jumped).

Sandbox run (own HOME, settings port 3591, magpie web on 3593):
- Claude Code (over a relay with its own token) and Codex were connected at 3591.
- POST /api/settings/port:
  - 3594, held by another program: refused, in use;
  - 80: refused;
  - 3592: moved [Claude Code, Codex].
- Both configs then say 3592. The gateway answers on 3592 and not on 3591, and `magpie healthcheck` exits 0.
- `magpie claude default` gave back https://relay.example and own-token.

lan-docker, settings-sections, settings-groups, click-scroll, gateway-caller-keys, gui-ja, gui-de and webdav-settings-refresh pass on WebKit.

go vet, the builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
2026-10-05 11:28:51 +08:00
yetone 597d16358b gui: the header's refresh reads again what the page shown draws, and no longer looks for a newer magpie (evilgaoshu, #844: 顶部刷新按钮按直觉应该是「刷新当前页面」,检查更新是低频操作,不必占用顶部一个常驻按钮) — the round ↻ refreshed the model lists and checked for an update whatever page was shown. Now on Agents it looks for the agents on this computer again: POST /api/agents/rescan (agent.Rescan forgets the answers kept a while because asking was slow: Cursor Private Inference's apps folder, WSL's distro list and failed probes, Alma's server) and draws the list, Install another agent and the CLI versions afresh, so one just installed or removed, or changed outside magpie, shows at once. On Usage it reads the page now, the allowances too (as its Refresh now); in the panel, Agents rescans, Allowances reads the allowances and Usage the requests; on Providers and every other page it refreshes the model lists as before. Its tooltip says what it reads on the page shown (zh, ja, de strings). A newer magpie is looked for by magpie's own timer and Settings › About's Check, as before. TestRescanLooksAgain (an app installed after magpie looked is found only after the half minute without it) and refresh-page.test.cjs (WebKit, en and zh, window and panel: tooltip per page, Agents rescans and a new agent shows and leaves Install another agent, Usage reads the allowances afresh, Providers syncs, never an update check, no click moves the page) fail on the old code; sync-update.test.cjs, which asserted the old update check, is replaced by it. sync-spin, update-hide, update-check, click-scroll, gui-ja, gui-de, usage-card-refresh and agent-update-all pass on WebKit. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. 2026-10-05 09:26:30 +08:00
yetone 5268a3308e agent, gui: an agent whose CLI is gone while its settings are left says so, and Install another agent offers it again (evilgaoshu, #843: 卸载了 DeepSeek Harness,但 ~/.dsh 还在,Agents 页面依然把它当作已安装列出来,也不会出现在 Install another agent 里) — an agent was here when its config or folder was, so dsh, uninstalled with ~/.dsh kept, was listed as installed, and Install another agent left out every agent listed. Agent.CLIMissing: its folder or config is here, its CLI isn't, neither on PATH nor where users' tools go (proc.FindTool, as #839's Claude Code is found on Windows); only for an agent that is its CLI and has an install command magpie knows, not one whose folder an app or editor extension keeps too (Codex, Claude Code, Gemini CLI, OpenCode, Cline, Goose), nor a WSL one. Its row stays, settings kept and nothing deleted, with "CLI not found" where its version goes (its tooltip names the folder and points to the install command), and Install another agent lists it, marked "CLI not found" under its name. dsh now has its npm package, @deepseek-ai/dsh, so it is offered with npm install -g @deepseek-ai/dsh, and its version and updates show like the other CLIs'. zh, ja, de strings. TestCLIMissing (dsh with ~/.dsh and no dsh is missing and offered; codex's folder, an agent not here, a WSL one aren't; dsh in ~/.local/bin off PATH isn't) fails on the old code; agent-cli-missing.test.cjs (WebKit, en and zh: the row's note, its height kept, Install another agent lists it with its command) fails on the old code. agent-update-all, agent-layout, agent-start-pick, click-scroll, gui-ja, gui-de and import-row pass on WebKit. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. 2026-10-05 09:25:01 +08:00
yetone c377754c8f provider, gui: MiniMax Code's daily check-in (Hu9956, #811) — magpie claims each MiniMax Code (China) account's daily check-in credits once a Beijing day, as MiniMax Code's page does, the same way it does WorkBuddy's and Trae CN's. It asks GET agent.minimax.cn/minimax-cloud/api/v1/signin/status?timezone_id=Asia/Shanghai and, only while today's day is claimable (status 2), POST /claim with {}. Both go through the minimax-code plugin's fetch, which signs them in as the account. The page's x-timestamp and x-signature (md5 of ts + salt + body) go with them, though MiniMax doesn't check these now. Day status 3 (claimed) and claim_result 2 count as done; status 4 is not eligible; base_resp's code and message are the failure's reason, and an expired sign-in says sign in again. The streak is the week's day_no. Results are kept in minimax-checkin.json. The international site's check-in isn't known, so minimax-code-global accounts are left alone. The switch is in Settings › Usage › warm-up and check-in › MiniMax Code (minimaxCheckin, off by default) and on the usage card (Auto check-in, Check in now: POST /api/settings/minimax-checkin, /api/usage/minimax-checkin); the card row's vendor strings are now a table (CHECKINS). magpie checkin and the TUI also check it in. zh, ja and de strings. No real MiniMax account was at hand: TestMiniMaxCheckin and TestMiniMaxClaimAlready run against an httptest fake built from Hu9956's captured answers. The fake checks timezone_id, the signature, the GET without a body and {} on the claim. It covers claim, no second ask the same day, in already, disabled, a refusal with its reason, and expiry. They pass with -race. usage-checkin.test.cjs adds the MiniMax card (its own switch and press, Trae's untouched) and the Settings tab (WebKit, en and zh, 8/8). settings-sections, gui-ja, gui-de, click-scroll and usage-quota-head pass on WebKit. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. 2026-10-05 03:05:14 +08:00
yetone 2a4042a94d perf: magpie opens and shows the Agents page in a fraction of the time it took (Junjie Zhou on Discord: 8 个 agent 30 个 provider … 每次我打开的时候都会很慢,要好几秒。setting 页面秒进后 我切换到 agent 就有问题) — at start, writing the agents' model lists (agent.SyncCatalog) built every provider's catalog thousands of times with nothing held, each build reading migrations.json, settings and groups again: 6000+ file reads and ~8.5s of CPU in the first 8s with the owner's 30-odd providers, all competing with the first /api/state and the Agents page. Now SyncCatalog, magpieModels and main's start-up moves run under provider.Hold, migrations, groups, settings and a provider's fetched models are held for a second like the rest, and filememo.Hold lets a GET /api/* not stat a file it confirmed unchanged within the last second (any magpie write, Changed() or a recent mtime still reads it again). A big ~/.codex/config.toml parsed in quadratic time (the TOML parser's Shape counts newlines from the start for every key): a 3 MB one took 5.4s, now 0.04s (tomlShaper). theInstalled reads through edit.Read's memo. Measured with a copy of the owner's magpie config and agent configs under a sandbox HOME, the real server in WebKit: CPU in the first 8s 8.59s → 0.39s, page up 475 → 75ms, Agents rows drawn 775 → 325ms after start; the agents' written model files are unchanged (pi byte for byte, opencode the same models). TestTOMLShaperMatchesParser, TestTOMLBigFileParsesInLinearTime (fails on the old code), TestHold (filememo), TestHoldReadsMigrationsOnce. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. 2026-10-05 03:02:40 +08:00
yetone 6159129f26 settings, library: the searcher picker says which providers it leaves out and why (#825), and Alma takes skills, in ~/.config/alma/skills (#824)
#825 (cjyrainbow): Settings → Web search's 代搜供应商 offered only DeepSeek though they have many providers. Only a provider that searches the web by itself can search for another model — Claude, Codex and Grok accounts; the APIs of Anthropic, OpenAI, DeepSeek, xAI, Zhipu and OpenRouter; Gemini on a Google sign-in; a Kimi Code plan — so the list is right, but the row never said so. It now names the providers on that are left out (searchLeftOut; past six, the first five and how many more) and says that for their models the search APIs below search. zh, ja, de strings. searcher-pick.test.cjs (WebKit, en and zh) checks the line and the cut-short list; it fails on the old code.

#824 (babyjiang7): Alma reads personal skills from ~/.config/alma/skills (its skills service, in Alma.app's bundle: personalSkillsPath, beside ~/.claude/skills, ~/.codex/skills and ~/.agents/skills), so the library now gives it skills there, Claude Code's, Codex's and the shared folder's counted as its too. It still has no user-wide instructions or MCP file of the kind magpie writes, and says so. TestAgentsSkillsFolders (a skill given to Alma lands in ~/.config/alma/skills) and TestTakesRefusesAlma fail on the old code.

go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass (TestProvidersAnswerWhileListsComeIn and TestClaudeSignedOut flaked, pass alone).
2026-10-05 02:56:46 +08:00
yetone 2f183c4cad agent: Cursor Private Inference's row picks which of magpie's models its own model picker lists, as Claude Desktop's does (mamba on Discord: 如果是以这种环境变量的方式接入的话,选不了模型在仪表盘) — its picker is the gateway's /v1/models as its key (magpie-cursor-local) is shown them, which already left out the models taken out for it (settings.HiddenModels), but its row had no way to take any out: no field of its picks among the catalog, so it had no model line or list. Agent.ListsModels marks an agent whose menu is that list; the row's line counts its models for it (agentModelCount takes the agent), and in the window and the tray panel the connected row has the Models button beside the launch square (MENU_FROM_LIST), which opens its model list to tick several at once; it says to quit and reopen it after a change. cursor-local-models.test.cjs (WebKit, en and zh, window and panel: the row's words, the button beside the launch square, a model taken out and put back posted, no scroll, no <select>) and TestCursorLocalModelLine fail on the old code; TestCursorLocalModels checks a model taken out isn't listed with its key and still is with another agent's. desktop-model-menu, cursor-local-launch and agy-launch pass on WebKit. Not tried with the real app (not installed here). go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass (TestProvidersAnswerWhileListsComeIn, TestUserPath and TestClaudeSignedOut flaked in the full run, pass alone). 2026-10-05 01:21:35 +08:00
yetone 9aa7a3dca6 tray: the Mac menu bar can show the allowances without magpie's icon (KevinXC on Discord: 能否支持隐藏状态栏里的图标,只显示各个供应商的剩余额度?) — Settings → Usage has a Menu bar magpie icon row (On by default) under Menu bar logos, on a Mac with a card in the menu bar. Off (settings.TrayNoBird) draws the cards alone: the image starts at the first card, with no room left for the bird; a click on a card still opens the panel at it, a modified click opens it as before, and the flap leaves the image alone. With no cards up the bird is shown as before, so the item never goes blank. It is the computer's own, like the other menu-bar settings (KeepOwn), and a change redraws at once. Menu bar logos is 菜单栏订阅图标 in Chinese now, so the two rows read apart. TestTrayImageNoBird (the image 48 px narrower at 2x, the rest the same cells, ink at its left edge, logos and plain, light and dark) fails on the old code; tray-bird.test.cjs (WebKit, en and zh) and tray-logos pass; the birdless image was looked at in light and dark. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass (TestClientGoneRecordedAsCanceled failed once under the full run's load and passes alone, five times, and in the gateway package's own run). Not tried in the real menu bar. 2026-10-04 20:01:57 +08:00
yetone e950eb080a codex, claude: a model with a window above 272K is compacted at 272K, as OpenAI's and Anthropic's own are (X: Chen — through magpie on DeepSeek flash, Codex and Claude Code both "wait" before every answer, and more the longer the task) — magpie told both agents a 1M model's whole window, so neither compacted until near 1M and every turn sent the whole conversation again: the live ledger had turns of ~550K tokens waiting 70–90s for a first token, while magpie's own time before the vendor stays under 0.2s. OpenAI runs its own 1M-capable models at 272K in Codex (context_window 272000, max_context_window 872000) and Anthropic its at 200K unless a [1m] one is picked; magpie now does the same by default. Codex's catalog gives such a model context_window 272000 and its whole window as max_context_window; Claude Code gets CLAUDE_CODE_AUTO_COMPACT_WINDOW=272000 while it runs on magpie (it compacts at the smaller of that and the model's window, a [1m] model's too), kept apart from a value the user set and taken out with magpie. Settings → Long conversations ("Compact at 272K" / "Full window", en and zh) turns it off (settings.FullContext), and both agents' lists are written again at once. In a sandbox with the owner's real accounts: Codex's /models listed group/test and group/auto-glm-5-3-flash at 272000 with max 1000000 (stepfun's 32K and 16K as they were, gpt-6-* still 272000/872000), Claude Code on deepseek/deepseek-flash[1m] answered and its /context said 16.3k / 272k, and Full window turned both back to 1M (/context 16.3k / 1m). TestClaudeCompactWindow and the extended TestCodexCatalogToolSearch fail on the old code. go vet (darwin, GOOS=windows), builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. 2026-10-04 18:27:09 +08:00
yetone 47eb27296f settings: a search API's saved key can be shown, hidden and copied in its row, as a provider's can (OnurBen on Discord: the Providers page shows a key, Settings → Models → Search APIs only listed Firecrawl/Tavily/Exa keys masked, so copying one meant going back to the vendor's site)
Each saved search API with a key gets Show and Copy next to Remove. Show asks the new POST /api/settings/search-key for the key (served on loopback, as provider/key is) and puts it in the row's line in place of the masked one, selectable; Hide puts the masked one back. Copy copies the key without showing it. The settings still list keys masked. search-api.test.cjs checks Show/Hide/Copy (fails on the old app.js) and TestSearchKeyShown the endpoint. GUI: search-api, searcher-pick, click-scroll pass on WebKit; go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
2026-10-04 17:29:11 +08:00
yetone 4e0bf88370 gateway: Gemini on a Google sign-in searches with Google Search and draws with its image models (#757, on Antigravity: gemini-3.8-flash's searches went to the Codex account and spent its allowance, and neither the searcher picker nor the image model picker offered Antigravity's models) — a Google sign-in (Gemini CLI's or Antigravity's, not a plugin's) was never said to search by itself, and buildCodeAssistSent sent no googleSearch, so a Claude Code WebSearch on an Antigravity model was handed to magpie's search, which the searcher magpie picked (Codex first) answered.
Search: Code Assist runs googleSearch for Gemini, but Antigravity turns it down beside function tools (400 "Please enable tool_config.include_server_side_tool_invocations to use Built-in tools with Function calling", tried with that flag in toolConfig too), so searchesFor (searchesItself for one request) says a Google sign-in's Gemini searches by itself for a request without tools; buildCodeAssistSent then sends tools [{"googleSearch":{}}], and codeAssistDecoder reads the candidate's groundingMetadata (webSearchQueries, groundingChunks[].web) as a search the client is told of (Anthropic's server_tool_use + web_search_tool_result), once. A request with tools — every agent's — is given magpie's search tool as before, and its searches go to the conversation's own account first (ownSearcher, as a Kimi Code plan's do): searchWith on its Gemini without tools, so googleSearch runs there and no other subscription is spent. A Google sign-in is also a searcher for other models, last of those magpie picks by itself (rank 5; relays named by hand move to 6), and is offered in Settings → 代搜供应商, with its Gemini models only; the row says such sign-ins search for their own models first. smallModel took every Gemini for a small model ("gemini" has "mini" in it), so the first listed, gemini-pro-agent, searched; it now picks a Flash.

Images: Drawers lists the image models a Google sign-in has (gemini-3.1-flash-image), and draw asks them on Code Assist through the account (drawCodeAssist, askTranslated with the conversation's envelope); buildCodeAssistSent asks an image model for responseModalities ["TEXT","IMAGE"], which a chat with one now gets too. AutoDrawer doesn't pick them by itself (no known price), so nobody's automatic drawer changes.

With the owner's real Antigravity account (a copy of the config, a sandbox gateway): a web_search with no other tool on gemini-3.8-flash-low came back with Google's grounding as web_search_tool_result; with a bash tool beside it the model called magpie's search, which went to antigravity/gemini-3-flash (not Codex) and gave pages; /v1/images/generations with antigravity/gemini-3.1-flash-image drew a 1024x1024 JPEG. TestCodeAssistSearchesWithoutTools, TestCodeAssistDecodesGrounding, TestCodeAssistImageModelAsksForImages and TestSmallModelOfGemini are new; searcher-pick.test.cjs checks the note. go vet, builds (darwin, GOOS=linux, GOOS=windows), go test -tags nogui -count=1 ./... under a temp HOME and the searcher GUI tests (webkit) pass. Not tried with a Gemini CLI sign-in, which takes the same path.
2026-10-04 15:31:23 +08:00
yetone b56e1ff931 agent: a model picker says which way each model goes, a pick that takes a connected agent off magpie is asked first, and the switch says what it started the agent on (#726, EZN7L2C3 on v0.1.804: with Claude Code connected, Opus 5.5 moved it under 未设置 with 接入 off, Sonnet 5.5 turned it back on, and neither said "via magpie") — the connected Claude Code picker lists Claude Code's own Anthropic models (claudeOwn, direct "Anthropic") under the "Claude Code" header with nothing saying they are not through magpie, and the ones on its own account through magpie fold into "N models via magpie"; the reporter's Opus 5.5 was the own one, and Set with a model not magpie's unroutes Claude Code (unroute puts back the pre-magpie endpoint and token), so Wired() went false and the row folded under Not set up as v0.1.798 does with an agent not on magpie — what was written and the grouping agreed, nothing said why. Not connected, the row's picker lists magpie's models only, and picking one connects first (Agent.Pick), which is why Sonnet 5.5 turned 接入 on; that pop is the narrow one, where "<email> · via magpie" could be cut off at the end (the reporter at 00:56: the 未设置 dropdown did say via magpie).
Now each row in a field that mixes the two carries a tag: "via magpie" (经 magpie) on magpie's, "direct, not via magpie" (直连,不经 magpie) on the agent's own, and "off magpie" (不经 magpie) on Default where picking it takes the agent off; the note no longer ends in "via magpie", so it is said once and never cut. A pick in the field an agent is connected through that takes it from one of magpie's models off magpie — one of its own models, or Default — (leavesMagpie) opens a confirm as the switch's off does (askLeave, the disconnect-ask dialog, Cancel focused): it says what the pick does, that 「接入」 goes off (and the row goes under Not set up when other agents are on magpie), and that running copies take it once reopened. Default's offers Disconnect and restore beside Use default, the two not being the same: Default leaves the agent as installed (magpie's endpoint and models out, its own default model) and neither puts back what it had before magpie nor keeps the models for the next switch-on, which Disconnect does. Confirmed, it is written as before (setPick, the old commit body), Default saying "no longer goes through magpie · on its own default".

Switched on, the status says what model the agent was started on and why: agent.ConnectHow returns a Connection (how: kept, again, joined, magpie, same, alike, default, first; field; value) from the rule Connect follows (reconnect's memory; Codex on ChatGPT joined, its own last pick kept and magpie's models in its /model; else the model it was on through magpie — its own account, a subscription, a key — or its alias family, or with none set its catalog's first; else the first on its own account, the first subscription, the first), POST /api/agents/connect/{id} returns it as state.connected, and app.js says e.g. "Claude Code is connected to magpie · on Claude Sonnet 5.5 through magpie: it had no model set, so the first of its own". Connect is ConnectHow without the answer. New strings have their Chinese.

TestConnectSaysHow (alike for {"model":"opus"}, kept when connected, again after a disconnect, first for a model magpie doesn't serve) and agent-leave-magpie.test.cjs (the tags in a long-noted pop, Opus asked with nothing posted until Use, Cancel leaving it connected, Default's ask offering Disconnect and restore, a pick within magpie's and one connecting an unconnected agent not asked, the switch's how in the status, no click moving the page, en and zh) fail on the old code (ConnectHow undefined; no .badge.path) and pass; claude-picker-fold's note expectations drop "· via magpie" and it checks the tags. go vet, builds (nogui, GOOS=windows, GOOS=linux nogui), go test -tags nogui ./internal/agent ./internal/gui under a temp HOME, and the agent-*, claude-*, click-scroll and show-unset-agent GUI tests in WebKit pass; the rest of the suite fails as on HEAD (cli-update's tight row and others), and Chromium doesn't start in this sandbox. Not tried with the reporter's accounts or in the desktop window.
2026-10-04 09:12:05 +08:00
yetone e85886c760 gui: the Agents page updates every agent's CLI at once, and lists the agents not on this machine with their vendors' install commands (#727, Sun1090: on a new machine with no agent installed magpie gave no install commands or how-to, and agents updated only one at a time, where cc-switch has both) — Update all: with two or more CLIs behind (#202's cliInfo update), a line above the list says which and offers Update all, which runs the existing per-agent update (POST /api/agents/cli/{id}, updateCLI, now returning why it failed and quiet for the run) for each, one after another (two global npm installs at once can trip over each other's folders), each row's pill busy in its turn, then says how it went ("2 of 3 agents updated · 1 didn't: Gemini CLI", the errors in its tooltip and the status pill). Once shown the line stays until the agents are read again (load), saying what is left ("Crush has an update", "Every agent is up to date"), so neither its click nor a row's own pill moves the list under the reader. Install: agent.Installs (internal/agent/install.go) lists the agents magpie knows that aren't detected (not WSL twins) with their commands: the vendor's installer first where its docs give one (Claude Code's claude.ai/install.sh, install.ps1 on Windows; Codex's chatgpt.com/codex/install.sh, install.ps1, and brew --cask codex on a Mac; OpenCode's opencode.ai/install; Gemini CLI's brew formula), then npm install -g of the package cliSpecs already updates from (pi, omp, copilot, crush, cline, omo too); GET /api/agents/install serves it. Under the list, "Install another agent (n)" unrolls them (open when no agent is here at all), each command with a copy button and a note that npm needs Node.js and magpie lists the agent once installed. magpie doesn't run the installs: a new machine may have no Node.js or Homebrew yet and the update machinery derives its command from where an installed binary is, so the commands are to copy. zh strings for every new one. TestInstallCommands and TestInstallsOnlyMissing pass; agent-update-all.test.cjs (webkit: Update all runs three in order never two at once, a failure named, the page and list not moved, a row's own pill leaves the line in place, one behind gets no Update all, the install list folded under the list without the installed Codex, copy sends the command, open with no agents, Chinese) passes and fails on the old code. Webkit agent-connect, agent-connected-unfolded, agent-disconnect, agent-grip, agent-layout, agent-tweak-fold, click-scroll, copy-command, show-unset-agent, panel-fold, cli-behind pass; cli-update's tight-row subtest fails the same on the old code. go vet ./internal/..., go build -tags nogui ./..., GOOS=windows and GOOS=linux builds pass; the full go test under a temp HOME was not run here (the sandbox refused the HOME override) — internal/agent's install/version tests and internal/gui's asset tests pass. Not tried in the reporter's app. 2026-10-04 07:58:09 +08:00
858281521c fix(gui): use provider model names for web searchers (#715)
* gui: searchers show provider model names

Settings web search reads the names given in Providers from provider.Served, the same source as image recognition, so Automatic and a named searcher show those names instead of bare model IDs. Tests cover both.

* gui: searcher-pick's zh case names the searcher by its model name too, as the en one does — the turned-off note reads 没有用 OpenAI · GPT-5 Mini Named now that the small model goes by its name

---------

Co-authored-by: hazukisendai <251835060+hazukisendai@users.noreply.github.com>
Co-authored-by: yetone <yetoneful@gmail.com>
2026-10-04 04:04:06 +08:00
yetone 0c7e6d834d provider: Trae CN's daily check-in (每日签到) is pressed once a Beijing day for each Trae CN account, as WorkBuddy's is (#694, Hu9956: TRAE cn也有每天签到送100积分活动,这个会考虑支持吗) — Trae CN is served only by its community plugin (trae-cn, no built-in, no mover), so magpie asks Trae through the plugin's fetch, which from trae 0.1.4 (magpie-community/plugins, branch trae-checkin) sends api.trae.cn's /trae/api/ pages as the account (Cloud-IDE-JWT renewed when near its end, its device id): POST /trae/api/v2/ug/checkin_credits/status with {}, and /claim with {} only while enable is set and checked_in isn't. These are the endpoints autumnsentiment/Trae2api-cn (the relay the plugin's protocol came from) and qilimixingkong/trae-checkin both call; an unauthenticated POST to /status answers 200 {code:1001, enable:false, checked_in:false} and a made-up sibling path 404s. Answers: not enabled → inactive, checked_in → done with its credits, a claim → claimed +credits(+extra_credits), 9095 (this device is in today) → not eligible for the day (no other device id is sent to get round it, as qilimixingkong's does), 1001 or the plugin's X-Magpie-Sign-In: expired → failed, sign in again; 9074 and anything else → failed and tried again; a plugin before 0.1.4 (400, not JSON) → failed, update it. The day loop and its file are WorkBuddy's, made generic: checkin.go's checkiner (accounts as checkinAcct{key, do}) holds checkinNow, which wbCheckiner now wraps; keepCheckedIn and wbCheckinLoop.tick take a checkinRunner and a log label; withCheckins goes through markCheckins over checkinCards. Trae's is kept in trae-checkin.json by trae-cn|uid; settings.TraeCheckin (traeCheckin) is its own switch; the gateway runs KeepTraeCheckedIn beside KeepWorkBuddyCheckedIn; WithCheckins marks Trae CN cards too, with SubscriptionQuota.CheckinBy and WorkBuddyCheckin.By "trae" (never kept), so the Usage card's row uses POST /api/settings/trae-checkin and /api/usage/trae-checkin and its first Trae card holds Trae's own Auto check-in and Check in now; Settings' Usage part gains a Trae CN tab (traeList) shown while an account is signed in or the switch is on; magpie accounts checkin and the TUI's c check Trae CN accounts in too (the footer says daily check-in). Tests: trae_checkin_test.go (once a day: status then claim, nothing sent again that day, done the next; inactive, 9095 held for the day, 9074 retried, expired, an old plugin; Trae cards marked, another plugin's not), TestTUIChecksTraeIn, usage-checkin.test.cjs's Trae CN card (its own switch and press, WorkBuddy's untouched) and settings-groups.test.cjs's Trae CN tab; the WorkBuddy check-in tests pass unchanged. go vet ./internal/..., builds (darwin, GOOS=linux -tags nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass; the GUI tests pass on WebKit (Playwright's Chromium here fails to start, icudtl.dat missing, for the old tests too). Not tried with a real Trae CN account: none is signed in to the owner's magpie (no trae-cn in plugin-auth.json), so the authenticated status and claim answers' fields are as both open-source clients read them, not seen here. 2026-10-04 03:54:23 +08:00
yetone df27f0882a agent: the 接入 switch and a model picked in the row, both (owner: 对于那种想在 magpie 中一键设置 Agent 模型的用户能否也兼容一下,鱼和熊掌都要兼得); a row opens in place and slides as on iOS (owner: 展开的时候,上/下 padding 变了,导致抖动;展开和折叠都没有 iOS 那种丝滑优雅精致的动画)
Every agent row keeps its 接入 switch and now has its model picker beside it. Picking a magpie model on an agent that isn't connected connects it first (agent.Pick, used by /api/set), so one pick sets the agent up; disconnecting still puts back what the agent had. The intro line says both ways.

An opened row shook: the connected row centred its lines in its 56px min-height, and the 3px row-gap couldn't be folded away, so opening moved the name and the switch and changed the space under them. The first line now has its own height and isn't centred, the row-gap is 0 with the spacing in margins, and the opened part starts where the shut row ended (56/56 at 980px, 90/90 at 560px; the name at 8px and the switch at 19px open and shut). Opening and closing animate the part's height, padding, margin and border on iOS's sheet curve cubic-bezier(.32,.72,0,1), 460 ms open and 340 ms shut, and what's in it fades in from 6px above; a row already open slides shut before another opens. With reduced motion it opens at once.

Gemini CLI's .env made by magpie for its key was left holding a newline after a disconnect (main too): DelEnvFile now removes a dotenv file once the deleted keys were all it held.

TestPickConnectsFirst, the only.env case in TestEnvFile, agent-start-pick and agent-expand-steady (name and switch unmoved, the opened part's top at the shut row's end, at least 3 heights between on the way open and shut, growing/shrinking only, chromium and webkit at 980 and 560px) fail on the old code and pass. Tests that open rows wait for the slide. In a sandbox HOME with real accounts: Claude Code and Gemini CLI picked while not connected answer PONG and disconnect back to what they had; Goose's pick → disconnect round trip is byte-identical (goose itself stops at a Linear MCP OAuth, so no request). go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui for edit, agent and gui under a temp HOME pass; GUI tests that fail here fail the same on 82340f41 (account-arrange, cli-update, mobile-web, provider-levels-scroll, provider-proxy, redact-rule-row, routing-compact, provider-detect-models, plugin-move).
2026-10-04 02:05:10 +08:00
yetone 661d27b560 agent: the disconnect preview right on Windows, and the Codex panel says what connecting does (owner: 去 Windows 测试机上测一下 / 是不是需要界面上解释一下现在的逻辑) — on the Windows test box Claude Code's and OpenCode's disconnect previews were empty though both were connected. Claude: the dry run's temporary home has no ~/.codex/auth.json, so a codex/… model wasn't magpie's there; the magpie asking now writes the providers it has on into that home and the dry run takes their models as magpie's (TestDryRunKnowsTheAskersProviders). OpenCode: magpie/codex/gpt-6.1-sol, a model its copy of magpie's list didn't have yet, read as not connected and Disconnect left it on magpie; a magpie/… value whose model magpie routes is magpie's (TestOpenCodeOnAModelNotInItsList). The preview pairs a line going back only with one as deep (Claude's model, not a modelPicker row) and doesn't mask …_TOKENS counts. The Codex panel, joined: 「重开 Codex 后,/model 里自己的模型照旧,magpie 的模型也在里面。」, and 新会话默认 says 「和 Codex 的 /model 是同一个选择,在哪边改都行」 rather than 「不设就用 Codex 自己上次的选择」 beside a value (/api/state: joined). On Windows (sandbox USERPROFILE/APPDATA, real accounts): previews for Claude, OpenCode, Codex and Gemini; disconnecting all four restored them (Claude back to opus, Gemini to Google sign-in, Codex keeps its [model_providers.magpie] by design), real ~/.claude untouched; real Claude Code requests via yylx/claude-opus-5-5 and codex/gpt-5.5 answered. The new tests fail on the old code. go vet, builds (darwin, GOOS=linux, GOOS=windows), go test -tags nogui -count=1 ./... under a temp HOME and the 34 agent GUI tests pass but cli-update, menu-scroll and old-webkit, which fail on main too. 2026-10-04 02:05:10 +08:00
yetone d0d098c69f agents: the 「接入」 boards filled in — the opened row as designed, real accounts tried
- The opened row follows the boards: what to do in the agent, running copies with the old list, Claude Code's tiers (each with its own effort beside its model again, #536), the model list by whose models they are with what's hidden, the model new sessions start on, what connecting costs (Gemini's Google sign-in), and the files put back on disconnect, shown line by line.
- Disconnecting asks first and shows the lines it puts back. Secrets are masked, and a config of tens of thousands of lines diffs in a fraction of a second.
- Connecting prefers the agent's own subscription when magpie serves it (Claude, Codex).
- The dry run goes through proc.
- Every model hidden still keeps the row connected, with the way back to the list (#356).
- Routing groups are named in Chinese.
- Phone widths: the pickers and squares wrap inside the opened row, and the name stays beside its logo.

GUI tests moved to the new rows: connected fixtures carry refs and open the row from its link. tier-effort and mobile-web each found a real gap and now guard the fix.

Tried in a sandbox HOME with the owner's real accounts:
- Claude Code: connected, a request answered, and disconnect restored the settings exactly.
- OpenCode: a request answered.
- Gemini CLI: connected.
- Codex: connected.
- Model list picks were saved.

go vet, builds (darwin, GOOS=windows, GOOS=linux -tags nogui) and go test -tags nogui ./... under a temp HOME pass. The agent GUI tests pass except cli-update's tight-row case, menu-scroll and old-webkit, which fail on main too.
2026-10-04 02:05:10 +08:00
yetone 4c7ab667e5 agents: Claude Code's /model pick holds for new sessions (owner: 去掉 ANTHROPIC_MODEL,让 /model 的选择对新会话也生效) — magpie wrote the main model as env.ANTHROPIC_MODEL too, which outranks settings.json's model, so a pick in /model lasted one session. The main model is now settings.json's model alone (an alias opus/sonnet/haiku/fable reads as its tier's model); magpie remembers the one it wrote (claude.main), and the tiers and subagents equal to it follow it. Agent.Follow, run when the GUI builds its state, moves the followers onto an in-agent pick and takes an older magpie's ANTHROPIC_MODEL out; it writes nothing with nothing to follow. Subagents that follow leave CLAUDE_CODE_SUBAGENT_MODEL unset and run on the session's model. TestClaudeModelPickedInClaudeCode. Real Claude Code 2.1.288 in a sandbox: a legacy settings.json lost ANTHROPIC_MODEL on the GUI's refresh, /model → m-beta answered from m-beta, a new session started on m-beta, and the tiers followed it on the next refresh. go vet, GOOS=linux/windows builds, go test -tags nogui ./... under a temp HOME pass. 2026-10-04 02:05:09 +08:00
yetone 90d884ee60 agents: a switch connects an agent to magpie, and its models are picked in the agent (prototype, the owner: 在 agent 页面不应该是为 agent 选择某个模型,而是应该有个「接入」的开关) — agent.Connect sets the agent's main field to one of magpie's models (the one serving the model it is on now, else the first) or to magpie itself for an app whose one setting is magpie as its provider; that writes the provider and the whole catalog, so the agent's own list has every model. POST /api/agents/connect/{id}. Each row with magpie models to offer gets a switch and a line under its name (已接入 · 在 Codex 里用 /model 选 magpie 的模型 / 未接入 · Codex 用它自己的设置); off goes through the existing Disconnect dialog. Claude Code's /model lists only its tiers, so while routed magpie also writes modelPicker (2.1.287) with every magpie model, taken out on disconnect; one the user wrote is left alone. Checked in a sandbox HOME against a fake upstream: Codex 0.160 /model lists and uses magpie's models, OpenCode models, Pi --list-models, Crush's providers, Claude Code /model after connect; disconnect puts Codex back on gpt-5.4/high. 2026-10-04 02:05:09 +08:00
yetone 7f2e380f55 search: a Kimi Code plan searches the web for its own models with its own search service, before the Codex account or whatever magpie picks, and Settings may name it to search for every model (Discord: on Claude Code with the Kimi Code plan, why does web search use Codex's model?) — kimi-cli's SearchWeb (kimi_cli/tools/web/search.py, 1.52.0) POSTs {text_query, limit, enable_page_crawling, timeout_seconds:30} to the plan's search_url, which auth/platforms.py makes <base_url>/search (https://api.kimi.com/coding/v1/search) with the same key as the models (setup.py and oauth.py write services.moonshot_search with the plan's key or sign-in), User-Agent KimiCLI/<ver> and X-Msh-Tool-Call-Id, and reads search_results [{site_name,title,url,snippet,content,date}]. provider.KimiCodeSearch finds a plan by api.kimi.com/api.kimi.ai /coding or its kimi-code preset; magpie asks it the same way (no model asked, pages as the search APIs' are, date as page_age). A model served by a plan is given magpie's web_search even with nothing else set up, and its searches go to the plan first; failing, magpie's pick, then the search APIs, as before. For other providers' models a plan is never picked automatically (it would spend the plan's allowance and change today's searcher): Settings › 联网搜索 offers it once, by its web search with no model, and says it searches for its own models first. TestKimiCodePlanSearchesForItsModels fails on the old code (no search tool with nothing else set up; with a searching API and a search API, the old code asked them and never Kimi) and passes; TestKimiCodePlanNamedToSearch and TestKimiCodeSearchAddress cover naming it and the address; searcher-pick.test.cjs (webkit, en/zh) the picker. Against the real account's key from a temp copy of the config: api.kimi.com/coding/v1/search exists (404 on a wrong path, 400 on a bad body) but answers 403 access_terminated_error for search and chat alike, as that Kimi Code subscription has lapsed, so a real search result wasn't seen. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. 2026-10-04 02:00:32 +08:00
yetone 65e78e459e codex: Settings decides where Codex's thread-title requests go — Codex's own ChatGPT as before, off, or a magpie model (#705, DuGuaiYA: Codex signed in with ChatGPT sends thread_title / thread_title_reconsideration requests that magpie relays through ChatGPT even when the conversation is on a magpie provider) — a new setting codexTitles ("" = as Codex sends them, the default; "off"; or a model's id such as deepseek/deepseek-v4-flash or a group) is read on /backend-api/codex/responses for a request whose x-openai-subagent or turn metadata thread_source is a title kind. Off answers at once with an empty completed response (Codex then keeps no title, quietly, as its source shows), contacts nothing, and is still recorded: a Routing trace "Titles off" and a usage row of Kind thread_title marked rejected (left out of stats). A model gets the request through serve (so it is recorded as a title request on that provider), and its answer is given back as one Responses message holding {"title":…}, since Codex's text.format schema does not reach Chat or Anthropic upstreams. Settings has a row "Codex thread titles" (zh: Codex 会话标题) with the app's model menu (Codex's own, Off, magpie's models and groups), saved on its own through POST /api/settings/codex-titles, which refuses a model magpie doesn't serve; the page's other saves keep it. TestCodexTitlesSetting (gateway) fails on the old code (off still went to ChatGPT) and checks the ledger kinds, rejected rows and trace; TestTitleJSON; TestCodexTitlesSetting (gui) for the endpoint; codex-titles.test.cjs (en/zh, webkit) fails on the old assets: no select, the picks post, the subtitle follows, no click scrolls. go vet, builds (darwin nogui, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried with a real Codex TUI: no end-to-end run of Codex's title generation against magpie. 2026-10-04 00:23:05 +08:00
yetone bc141ad834 update: install.sh and magpie update take --proxy and --mirror, and magpie update mirror keeps one for the app's updates too (akic404 on Discord: 安装和update如果没有梯子也是麻烦事,能否在install.sh和magpie update后加子选项?--proxy等可自定义或默认ghproxy) — install.sh had no options and fetched with curl alone, and magpie update took none; without a way out to GitHub both failed. Now both take --proxy <url> (http, https, socks5, socks5h; install.sh also MAGPIE_PROXY=) for the feed and the download, and --mirror <prefix> (or MAGPIE_MIRROR=), a GitHub download mirror the release file is fetched from as <prefix>https://github.com/…; only github.com / githubusercontent.com URLs are put through it. No mirror is used unless one is given: magpie names none and has none by default. The feed with each file's SHA-256 is always fetched from usemagpie.ai, never through the mirror, so a file the mirror changed is refused and deleted ("… from the mirror X does not match its checksum from https://usemagpie.ai; not installed"); macOS still checks the app's signing team. magpie update mirror <prefix>|off saves Settings' updateMirror (http(s) only), which the app's own background update follows too; magpie update --mirror off skips it once; the Settings page's save keeps it. The app's update already followed Settings → Proxy (#294); magpie update --proxy overrides it for one run. The environment's ALL_PROXY / all_proxy is now honoured when HTTPS_PROXY / HTTP_PROXY are unset (Go's ProxyFromEnvironment ignores it), with NO_PROXY as curl reads it. magpie update takes --flag value or --flag=value, and an unknown word is an error instead of an update. The help, README and the docs' FAQ (en/zh/ja) say how. install.test.mjs's proxy, mirror and tampered-mirror tests, TestAllProxyFromTheEnvironment, TestUpdateThroughAMirror, TestUpdateThroughAProxyGiven and TestUpdateFlags fail on the old code and pass; TestSettingsSaveKeepsWhatItDoesNotSend covers updateMirror. A dry run of install.sh as Linux amd64 into a temp HOME installed the real 0.1.773 with its SHA-256 checked, refused a local mirror serving a wrong file, and with --proxy to a closed port failed reaching the site through it. go vet, builds (darwin, GOOS=linux, GOOS=windows), sh -n, node --test site/install.test.mjs and go test -tags nogui -count=1 ./... under a temp HOME pass but TestClaudeIdentityServedWhileAsked, a timing test that passes alone. Not tried through a real proxy server or a real public mirror; no install.ps1 exists for Windows, and the app has no field for the mirror. 2026-10-04 00:19:47 +08:00
yetone 8b8f91ba8f usage: a WorkBuddy account's card says how its daily check-in went, and turns it on (#694, Dazzle-sys: 签到设置放到用量卡片上,卡片显示今日是否已经签到;要去设置里看才知道签没签是不对的) — the check-in ran and was kept (workbuddy-checkin.json) but only Settings' Daily check-in said anything of it. It runs in magpie for a moved account too (through the plugin's fetch, wbCheckinAccounts), so the card can say it for both. GET /api/usage/quotas now adds, uncached, to each WorkBuddy (China) account checked in each day (provider.WithCheckins, matched by user name, an unnamed card only when there is one account) checkins and its last record; each such account's meters end in a line with a dot: Checked in today +credit · N-day streak (green), Not eligible for the daily check-in, No check-in event now, Check-in failed; magpie tries again later (red, the vendor's message in its tooltip), or Not checked in yet today (amber) / Auto check-in is off, with · last checked in {day}. The card's first such line has Auto check-in, the same setting as Settings' Daily check-in, which stays (POST /api/settings/workbuddy-checkin, written as Codex's auto reset is), and Check in now while an account isn't in today (POST /api/usage/workbuddy-checkin, CheckInWorkBuddy, then the cards are read again). Another vendor's card has none of it; no native select, no left stripe, the page doesn't move. zh strings added. usage-checkin.test.cjs (webkit, en/zh: three accounts' lines and states, none on Codex, the switch once and off, turned on with no scroll and the line turning amber, Check in now posted and the cards re-read) fails on the old code and passes; TestWorkBuddyCheckinOnTheCard covers the matching and that the cache isn't changed. credit-days, codex-auto-reset, quota-*, usage-arrange/fold/alert, settings-sections, tray-usages, balance-parts, click-scroll, add-sheet, removed-forget/hide and plugin-two-subs still pass. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried with a real WorkBuddy account. 2026-10-03 23:55:18 +08:00
Kagari Mimiandhazukisendai 763eb60478 gateway: let a searching relay be named (#686)
* gateway: let a searching relay be named

A provider marked as searching by itself is still not picked automatically, so a relay quota is not spent for other models, but Settings can now name it as the searcher. The row and tests say both halves of that rule.

* gateway: keep named search relays out of fallback

Search ranking is automatic and fallback ranking again, while Settings additionally accepts a relay said to search. Manual-only choices come last and modelSearch skips them, so a failed automatic search never spends a relay quota; a provider on a searching host keeps its automatic rank even when its box is ticked.

---------

Co-authored-by: hazukisendai <251835060+hazukisendai@users.noreply.github.com>
2026-10-03 22:34:05 +08:00
yetone e144db09c2 gui: the Usage page's cards are arranged as the Agents page's rows are, by dragging their logos (owner: 用量页面能否支持供应商排序?像 agents 页面一样)
A card's logo is its handle. A grip shows by it only while the pointer is on the card, as on Agents and Providers. The card is dragged across the grid: dragCards is dragRows in two directions, the slot nearest its middle is where it goes, and the cards between move a slot over. Alt+arrows move it from the keyboard and keep the focus on it. The order is settings usageOrder, by provider id, saved by POST /api/usage/arrange. It is apart from the Providers order, which is also the order providers are tried in. POST /api/settings keeps it. A card it doesn't name follows in magpie's own order, and an order naming one not on the page now keeps it after them. The tray panel's Usage tab follows the same order.

usage-arrange.test.cjs covers the hover-only grip, a drag across the grid, Alt+arrows, a click that moves nothing, the order after a reload, and the panel. It passes in Chromium and WebKit, en and zh. TestSettingsSaveKeepsWhatItDoesNotSend covers usageOrder. go test ./internal/gui ./internal/settings under a temp HOME, go vet, and builds (darwin, GOOS=linux, GOOS=windows) pass. Also tried on a sandbox magpie web with faked quotas: the drag, the keyboard and a reload kept the order in WebKit and Chromium. The other Usage/panel UI tests pass, except credit-days.test.cjs, which fails about 1 run in 4 on main too.
2026-10-03 15:42:25 +08:00
yetone 19e239c7bf vision: while a model that sees describes images, agents are told every model takes them (Discord, Fate: with Settings › Vision set, a text-only model in Codex still said it "does not support image input") — the gateway already described an image for a text-only model on every wire format, but the agents were told the model was text-only (Codex input_modalities ["text"], opencode modalities, dsh/omp input, droid NoImageSupport, cline/zed/qoder/workbuddy flags…), so they turned the image away before magpie was asked. provider.Described, set by the gateway to whether seer() finds a describer (Vision names one, or Automatic finds a model that sees; not with Vision off), makes magpieModels (every agent's model file) and codexListed (the ChatGPT backend's /models list and its ETag) say images for every model; Vision changed in Settings rewrites the agents' lists (catalog.Touched). TestDescribedModelsTakeImagesInAgentsLists (Codex list, input_modalities, tag) and TestDescribedModelTakesImages (agent files) fail on the old code and pass; TestDshModelLimits and TestMiniMaxCode turn Vision off to keep testing a text-only model as such. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried in the Codex App with a real account. 2026-10-03 15:25:43 +08:00