Commit Graph
1656 Commits
Author SHA1 Message Date
yetone 6ac01d2835 library: the skills picked can be put in a group of the user's own (#791, mintonight: 能不能在选择后自己创建分组呢) — with Select on (By source), the pick bar has Group…, which asks a name in the bar (Enter makes it, Escape goes back; a group there is is offered to add them to). The group is kept in library.json (skillGroups) and shown as a card of its own above the groups by source, open, its skills taken out of their sources' cards; a skill is in one group at most. The card has Rename (in place) and Ungroup, and the pick bar Ungroup for picked skills that are in one; grouping changes only how the page shows the skills, never what the agents get. A skill taken out of the library leaves its group, and an empty group goes. zh/ja/de strings added. TestSkillGroups and skill-groups.test.cjs (WebKit, en and zh: group, rename, ungroup; it fails on the old code, which has no Group…); pick-skills, skill-parts, group-skill-chips and the other library tests pass on WebKit. 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 under load and pass alone). 2026-10-05 01:21:05 +08:00
yetone d1c19131a6 provider: a decision model has its window and whether it takes images, and the Gateway list shows them with a tooltip saying what it is (ARNO on Discord: decision模型也是需要拉取正确的上下文长度以及是否支持视觉功能的,在网关的模型列表里也应该显示这些信息以及相关的tooltip) — a decision provider's list (OpenRouter's output_modalities=decisions, Vercel's) is now read for its context_length / context_window and its input modalities; a model the list says nothing of (TypeSafe's, OpenCode Zen's, Workers AI's and Bailian's presets) takes its vendor's documented values: Jev 32,000 and text, Clef and Clef Flash 65,536 and images, Bailian's decision-model-preview 65,536 and text. A gateway's Jev among its chat models has them too; its chat models are left as they were. The Gateway list's row of a decision model shows its context and image chips as other models' do, and its tooltip begins Decision model: routing groups ask it, agents never see it (zh, ja, de) in place of Reasoning levels: none known. /v1/models still leaves decision models out: agents can't use them. TestDecideModelsKnowWindowAndImages and gateway-decide-info.test.cjs (WebKit, en and zh) fail on the old code; gateway-model-info passes on WebKit. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass (TestClientGoneRecordedAsCanceled, TestProvidersAnswerWhileListsComeIn and TestClaudeSignedOut flaked under load, pass alone). 2026-10-05 01:20:06 +08:00
yetone 5daba301ca agent: WorkBuddy's magpie models pointed at another machine's magpie stay there through a sync (悠悠哥 on Discord: 这个问题workbuddy也会有) — as v0.1.891 did for ZCode: a sync rewrote every magpie entry in ~/.workbuddy/models.json to this machine's gateway and key, so models the user had moved to the magpie on a NAS went back to the local one. A sync now keeps the url and apiKey of a magpie entry on another machine (onAnotherMachine) for all of magpie's entries; one on this machine's loopback follows the gateway as before. TestWorkBuddyKeepsRemoteAddress fails on the old code (url http://127.0.0.1:3425/…, key magpie-workbuddy). go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. 2026-10-05 01:04:07 +08:00
yetone fe2603ff4f agent: Claude Code's model set back to its default puts back the endpoint and token the user had before magpie and leaves them (__jingling on X: 接入后跑 magpie claude model default,会把原来的 ANTHROPIC_BASE_URL 和 token 也删掉,之后再 default 也恢复不了,复现了两次) — the model field's default deleted every env key magpie writes, the user's own ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN among them, and forgot the stash that kept them from before magpie, so Disconnect after it had nothing to put back. It now takes magpie out as Disconnect does (its endpoint, token, tiers and window, the user's endpoint and token put back from the stash) and deletes only model; an endpoint the user set themselves, wired or not, stays, as the Agents page's default option (Claude Code · their host) says. magpie's env left at a gateway address since changed (its token) still goes. In a sandbox HOME with the release binary: settings.json with // mine, model opus, env ANTHROPIC_BASE_URL https://api.deepseek.com/anthropic and a token; magpie claude deepseek/deepseek-chat, then magpie claude model default: the comment, base URL and token back, model gone; magpie claude default after it leaves them. TestAgentModelDefaultKeepsTheEndpointBefore fails on the old code. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. 2026-10-05 00:56:46 +08:00
yetone b7fef3877f agent: Cursor Private Inference's program in a .app is named as on a Mac on any system, so TestCursorLocal passes on Linux CI — the .app fallback (no Info.plist) took product.json's applicationName as Linux names it (cursor), while a .app is a Mac's bundle and its program is Cursor. The test failed on the ubuntu CI since v0.1.877 and passes now in a linux/arm64 container (it failed there 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. 2026-10-05 00:53:09 +08:00
yetone 60b910b758 provider: the Agents page's data reads the settings and each provider's model list once per request (Junjie Zhou on Discord: the Agents page takes long to show) — a look at the agents asked settings.Load for every model of every agent (hundreds of reads of settings.json, about a fifth of /api/state's time in a profile) and catalog.Live for each model's APIs. The settings are now read through filememo (again only once the file changes) and held for the request like the catalog (heldSettings, used by VisibleTo, HiddenModels, ModelAPI, Labels), ListedAPIs holds each provider's live list, and settings.Save drops what is held. With a read-only copy of the owner's configs, a warm /api/state went from about 165 ms to about 100 ms. TestHoldReadsSettingsOnce. Check chain under a temp HOME passes. 2026-10-05 00:52:06 +08:00
yetone 63f83bf1e1 agent: a ZCode pointed at another machine's magpie in either of its files stays there in both (悠悠哥 on Discord: ZCode wired to the magpie on a NAS kept being switched back to the local one) — v0.1.812 kept a remote address per file only, but ZCode's own settings write provider_config.json alone, so a sync rewrote config.json to this machine's gateway and ZCode could take the provider back from it. A sync now reads magpie's provider in both files (zcodeKept): an address on another machine in either one is written to both, with its key; otherwise the gateway's. TestZCodeKeepsRemoteAddress edits each file alone to a NAS address and syncs; it fails on the old code (config http://127.0.0.1:3425, rules the NAS). Check chain under a temp HOME passes. 2026-10-05 00:52:06 +08:00
yetone 09a65410c3 i18n: German for the setting names #810 shows in an agent's opened row (standard, deep, visual, image), so gui-de's parity check passes again — gui-de.test.cjs and agent-field-names.test.cjs pass on WebKit 2026-10-05 00:43:41 +08:00
yetone 3f76105b33 gui: the Usage page's allowances head stays while the page loads again, and a failed check-in says why in its row (#809, #808, Hu9956: 用量界面红框这行会延迟几秒才会出现 — 点到其他界面比如Agent,再点回用量界面就能复现; TraeCN自动签到失效) — renderUsageLoading hid every .row-head on the Usage page, #quotaHead (Allowances, Trend, accounts masked, Used/Left) among them, while renderQuotas had just drawn it over the cards it heads; it came back only once /api/usage answered, seconds later, and a period with no calls hid it for good (renderUsage set every head to hidden=empty). Both loops now leave #quotaHead to renderQuotas. A failed WorkBuddy or Trae CN check-in kept its reason in the row's tooltip only, so the card read just 签到失败,magpie 稍后重试; the reason now follows it in the row (· code 9074: …). Trae CN's own fix is the plugin's: trae 0.1.9 (magpie-community/plugins 64c9e6c) sends the check-in as 0.1.6 did, which worked for the reporter before 0.1.7. usage-quota-head.test.cjs (WebKit, en and zh: the head stays visible through a slow reload and a period with no calls, a failed check-in shows its reason) fails on the old code; the usage-*.test.cjs and quota*.test.cjs 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 00:43:10 +08:00
Junjie Zhou d0fb1b6fee gui: name every setting in a connected agent's opened row (X: Aside's four roles and its picture model all read "default" under one logo) (#810)
A setting nobody has picked is the agent's own logo on the shut row, for want of
room for its name. In the window's opened row those are laid side by side, and
Aside's four task roles and its picture model all read "default" under the same
logo: six pills saying nothing about which is which. The tray panel already
names them as its rows open, so the window does the same, in both languages
(standard, deep, visual and image had no Chinese string at all).

Two things that made it ugly rather than only unreadable, and the names alone
would have left them: the panel's name column was 44px, so `standard` read
stand... (62px now, which fits the longest name an agent's own settings go by),
and the opened row laid its settings out in the shut row's two narrow columns,
where a name, a model and a logo do not fit in 124px. The opened row has the
whole width under the name, so they are a line of pills that wraps.

General, not Aside's: any agent with settings that follow its model. The
collapsed row is untouched, so a row that is not open still reads as it did.

agent-field-names.test.cjs covers both places, in English and Chinese, and fails
without each of the three: with the naming gone the window's four roles have no
name, with the column back at 44px the panel's `standard` is cut short. The ten
neighbouring agents-page tests pass, and gui-ja's "ja has every zh string" is
green with the four new strings.

(cherry picked from commit 39ea98581964416d9828faf3f8ebc5caa36ed172)
2026-10-05 00:42:45 +08:00
Zhizhen He 50686784bc appdir: reject tilde paths that readers do not expand (#803)
A literal XDG_CONFIG_HOME='~/.config' passed validation and let Goose
write its config under ./~ in magpie's working directory. The same
problem affected other folder variables consumed without expansion.

Only retain home-relative paths for the Pi, Hanako and T3 variables
whose readers expand them. Ignore other unrooted values and report them
through CheckEnv, preserving the environment passed to child processes.

Add regression coverage for variable validation and an actual Goose
config write, while keeping existing supported tilde paths working.
2026-10-05 00:42:29 +08:00
tobitege 38cf469fb2 i18n: German for the strings main added after #770 (#806)
Max output and its hint and error, the purpose filter and the copy-launch-command button get their German, so gui-de's parity test passes again; the site's download-size line follows the English (a 15 MB download on a Mac).
2026-10-05 00:42:22 +08:00
yetone 9761248de3 agent: dsh's home patch layer, ~/.dsh/cordis.patch.yml, gets magpie's route and start model when it overrides the profiles' (#804, liuweifeng) — dsh applies $DSH_HOME/cordis.patch.yml over every profile's own, so a llm-pi-ai or agent-default-model entry there replaced what magpie wrote in profiles/*/cordis.patch.yml, and dsh ran the home layer's old snapshot while magpie read as connected. When the home layer has a llm-pi-ai entry, magpie's route is written in it beside the user's routes (a sync, a model picked, the 30 s round, and taken out on Disconnect); when it has an agent-default-model, the model and effort magpie set go there (the user's one stashed and put back when magpie's goes). A home layer without those entries is left as it is. Reading goes by the home layer too (the model and effort shown, Joined, the checks), and Check says when the home layer's llm-pi-ai lacks magpie's route. TestDshHomePatchLayer and TestDshHomePatchLayerUnwire 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 TestUserPath flaked, pass alone). 2026-10-05 00:39:40 +08:00
yetone 3ffc99ef60 codex: a call id longer than 64 characters goes as one the backend takes on Codex's own ChatGPT sign-in too, and tool_search ids with it (#732, congee949) — #732 bounded them where magpie writes the request (codexBody: Chat and Anthropic clients on a Codex model, Codex App with spare accounts), but Codex on one ChatGPT account is relayed as it came (codexUpstream), so a conversation with a foreign provider's tool calls (two ids joined, 86 or 87 characters) still got 400 "input[7].call_id … maximum length 64" there: 30 in a week in the report, 29 of them thread_description on gpt-6-luna, on v0.1.869. The relay now gives every call_id over 64 characters as provider.BoundCallID has it (the sha256 of the trimmed id, so a call and its output, paired by the trimmed id, get the same one on every request), the rest of the request byte for byte; codexBody uses the same helper and covers tool_search_call / tool_search_output as well. TestCodexRelayBoundsLongCallIDs (single-account relay: function and tool_search ids bounded, paired and stable across requests, a 64-character id kept) fails on the old code. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. 2026-10-05 00:31:36 +08:00
congee949 6a67774a99 fix(codex): bound foreign tool call IDs and preserve result pairing (#732) 2026-10-05 00:31:19 +08:00
yetone 987ce3d1b0 codex: a memory call on a model of Codex's own goes where Codex's turns go, rather than a 404 (#742, congee949) — Codex writes and consolidates its memories on models of its own by their bare names (memories.extract_model and consolidation_model, gpt-5.6-luna and gpt-5.6-terra by default) whatever it is set to, so with magpie as its provider every memory_consolidation call was turned away as "magpie knows no model" before it was routed (28 in a week in the report, from Codex on two other computers with their own gateway keys, where the stand-in read from this computer's ~/.codex/config.toml can't reach). A Codex memory call (memory_consolidation, memgen, memory) on a model magpie can't resolve now goes to the model the same Codex (the agent and the gateway key it came with) last had a turn of its own answered on here, else the last such turn in the ledger a week back (so after a restart too), and is routed and recorded as the memory call it is. Any other call on an unknown model, and a memory call from a Codex with no turn answered, is turned away as before. Base instructions are untouched. TestCodexMemoryCallsGoWhereItsTurnsGo (terra and luna after a turn, after a restart from the ledger, unknown turn and review still 404) fails on the old code. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. 2026-10-05 00:25:10 +08:00
yetone f77ef6106e agent: Cursor Private Inference is connected by its row's switch, as other agents are, with no command to run (mamba on Discord: 这个单独搞个启动命令和别的供应商格格不入啊) — its Open configuration's base URL and key are kept in the state.vscdb regular Cursor shares, and are regular Cursor's own override settings too, so magpie still writes nothing of Cursor's. It sets CURSOR_LOCAL_AGENT_BASE_URL (<gateway>/v1) and CURSOR_LOCAL_AGENT_API_KEY (magpie-cursor-local), which only this build reads, for the user: launchctl setenv on the Mac (apps opened from the Dock or Finder from then on have them), HKCU\Environment plus a WM_SETTINGCHANGE on Windows, ~/.config/environment.d/magpie-cursor-local.conf (from the next login) and systemctl --user set-environment on Linux; switched off, it clears them. magpie notes it in cursor-local.env in its config folder, which the row reads as connected, and sets them again whenever it serves the gateway (launchd forgets them at a restart; the port may have changed). The row has the provider field (magpie) and the connect switch; connecting says to quit the app and open it again, and that a base URL in Open configuration still comes first. The launch command stays as the square beside it, for a start from a shell. TestCursorLocalConnects (connect sets them, a serve sets them again, disconnect clears them, off a serve sets nothing) fails on the old code; cursor-local-launch.test.cjs now checks the switch connects it with no command copied (WebKit, en and zh); agy-launch passes. Not tried with the real app (not installed here), and launchctl/the registry are not touched under tests. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. 2026-10-05 00:18:22 +08:00
yetone 5f5f745fff usage: a Codex account in brief has its Auto-use switch beside its name, not only words (#801, ezdemo: 更新最新版后,似乎自动使用重置卡的开关的入口找不到了) — with several Codex accounts the one shown in full is the one last served, so the account whose week ran out, the one holding resets, is in brief, and in brief it only said "↺ 2 · Auto-use: off": the switch was a fold away, and nothing said so. Its brief row now reads "↺ 2 · Auto-use" (zh 自动用卡, ja 自動使用, de Automatisch) with the same small switch as the full card's (role=switch, aria-label Auto-use resets, posts settings/codex-auto-reset); the full card's row is unchanged. codex-auto-reset.test.cjs (WebKit, en and zh) turns it on from the brief row, sees it on in full, turns it off there and sees it off back in brief; it fails on the old code. Looked at in light (zh) and dark (en). go vet, builds and go test -tags nogui -count=1 ./... under a temp HOME pass. 2026-10-05 00:16:44 +08:00
yetone 914abab3fc library: on this computer, skills whose names start alike are a part of their own wherever they sit, and a few of them among many are too (#791, mintonight: 没有看到自动分组 after v0.1.867) — the local group's parts were by folder first, so lark-* in ~/.agents/skills beside skills in ~/.claude/skills were only part of the ~/.agents/skills part, and a start shared by under a fifth of the skills (5 lark-* among 47) was dropped as one small part and the rest. The local group's parts now go by the start of the names first (a start three or more share, as before), and the rest by folder when they sit in more than one, else under Others; a repository's parts are as they were (its folders are its author's own grouping), less the under-a-fifth rule. skill-parts.test.cjs (WebKit, en and zh: lark / ~/.agents/skills / ~/.claude/skills, and lark 3 / Others 36) fails on the old code; pick-skills and list-sort pass. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass (TestSavedButSignedOutClaudeServed flaked once, passes alone). 2026-10-05 00:16:44 +08:00
yetone 6892a28685 quota: each allowance says when it was read, a cached one keeps its own time, and magpie quota history tells the windows' readings over time (#802, margbug01) — a subscription's card had no time unless its reading had failed, and a Claude account's allowance, which is what Claude Code last told (kept until it answers again or runs /usage), was shown on every refresh as if just read and written into the history again at each one's time. A reading now carries ReadAt: the time it was made (readNow, for every subscription and account read), and a Claude account's the time Claude Code told it (claudeReadAt). The usage card says "Updated 40 minutes ago" from it, a reading standing in for a failed one still says "As of …" in amber, a failed one says neither. The history (#651) keeps a point at its ReadAt, so a reading shown again is no new point. magpie quota and magpie accounts add "read 40m ago" to a line read more than 2 minutes before, and --json and GET /v1/magpie/quotas carry readAt. magpie quota history [<provider|account>…] [--days N] [--json] tells each account's windows over time (UTC, kept 45 days, a point only where it changed, the runs of a window apart by their reset), the last eight a window in text with ↻ where it started again, every one with --json, as GET /v1/magpie/quotas/history answers. On the card, hovering a window's key under the curve lists its latest readings with their times; no control is added to the card (the curves are kept uncluttered). Strings in zh, ja, de. Request-log correlation between readings isn't attempted. TestClaudeKeptReadingKeepsItsTime (a kept Claude reading shown three times ten minutes apart is one point at its own time; fails on the old code), TestQuotaHistoryCmd, and quota-read-at.test.cjs (WebKit, en and zh; fails on the old code). quota history run against a copy of the owner's real quota-history.json lists their Claude, Codex, Command Code, Copilot, Cursor and Devin windows. quota-curve, quota-pools, quota-families, quota-credits, quota-count-wrap, claude-usage-asked, panel-usage and balance-* pass on WebKit. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass (TestProvidersAnswerWhileListsComeIn, TestUserPath, TestClaudeSignedOut and TestCursorSignInJoinsAWrappedLink failed under load and pass alone). 2026-10-05 00:14:25 +08:00
Zhu Wang 6dd0079b86 fix(routing): group Codex image title requests with their chats (#797) 2026-10-05 00:14:23 +08:00
yetone 687fb675a4 icons: From the website takes a new-api or one-api relay's own logo from /api/status, and one with none gets the provider's first letters on a tile (wiixdede on X: 从网站获取 favicon,new-api 中转站拿到的都是 newapi 默认图标,js 运行后才有网站独特的 favicon,多个供应商在首页选模型时不好分辨,或许可以改成首字或前两个字母) — such a panel's page names no icon (or its own /logo.png), its /favicon.ico is the panel's, the same on every relay, and its script sets the tab's icon from /api/status's logo. A site whose /api/status answers as a panel (success, data.system_name) now has that logo looked at first, then an icon its page was given by hand, never the panel's /favicon.ico or /logo.png; a panel with neither gets a tile with the provider's name's letters (FR for Foo Relay, Ak for akash, 中 for 中转站; the panel's name when the editor's is empty and it isn't New API or One API, else the domain's), its colour picked by the name. Other sites are looked at as before. Against real sites: one.ocoolai.com and api.xty.app gave their own logos (from /api/status), api.gpt.ge (logo "svg", junk) its page's own favicon.svg, api.deepseek.com its favicon.ico as before; the tiles drawn by WebKit read FR, 中, Ak. TestFavicon's panel cases fail on the old code (the panel's own icon was kept). go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass (TestProvidersAnswerWhileListsComeIn and TestUserPath flaked under load, pass alone). 2026-10-05 00:13:54 +08:00
yetone 531dc0aa33 claude: a Claude model runs to its own window in Claude Code, not 272K, and magpie model compact on|off is the Long conversations switch from the terminal (Max on Discord: a [1m] model's context stopped at 272K, magpie wrote CLAUDE_CODE_AUTO_COMPACT_WINDOW back when it was taken out, and there seemed to be no way to change it) — on magpie, Claude Code was told CLAUDE_CODE_AUTO_COMPACT_WINDOW=272000 whatever its model, so a [1m] Claude model compacted at 272K, though Anthropic runs it to 1M (the cap was meant for other vendors' long windows: Chen's DeepSeek turns of ~550K). Now a main model that is one of Anthropic's Claude models, from whichever provider, has the env left out (magpie's own value taken away), and Claude Code compacts at the model's own window; another vendor's model of a longer window keeps the 272K cap unless Settings → Long conversations is on Full window. A value the user writes there is still kept. The switch was only in the app's Settings; magpie model compact [on|off] (alias full-context) shows and sets it, and the Settings row's description says how Claude models are treated (en, zh, ja, de). TestClaudeCompactWindow (a Claude [1m] model gets no 272000, set and synced; another vendor's after it is capped again) and TestModelCompactCmd fail on the old code. The Long conversations row read right in WebKit, en and zh. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass (TestClaudeFailureKeepsClaudeCodesStatus flaked once under load, passes alone). 2026-10-05 00:09:49 +08:00
Fanbuz b93cc1623a fix: 显示 Codex OAuth 账号的 credits 余额 (#800) 2026-10-05 00:05:55 +08:00
yetone e8eb8005f3 gui: one magpie at a time on the Mac, so one started at login twice (its own open at login and a Login Item the user added, or the Mac reopening it after a shutdown it wasn't quit for) ends instead of putting a second bird in the menu bar (Crispin on Discord) — Wails has no single instance on the Mac, and magpie's LaunchAgent runs the binary itself, past the Mac's one-copy-of-an-app, so both starts ran: the second found the gateway served and only watched it, with its own tray icon. A magpie now takes a flock in its config dir, keyed by the config dir and its resolved executable (a sandboxed HOME, and a copy elsewhere such as the owner's magpie-dev, each run on their own); a second one that finds it held ends: a start at login quietly, one for the window or the panel opens the .app, which has the running magpie show its window (the Dock's reopen; at most once in 10 s, so it can't go on), and a magpie:// link is opened, which the Mac hands to the running one. The system lets a flock go when its holder ends however it ends, so a shutdown leaves nothing held behind. Restart to update relaunches only once the old process has gone, so it is unaffected. TestOneMagpieAtATime (a second process holds the lock, this one is refused, the holder is killed, the lock is free) fails on the old code. Two GUI launches on the real Mac weren't tried, since a GUI magpie registers its magpie:// handler with the system. go vet, builds (darwin gui and nogui, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. 2026-10-04 23:43:34 +08:00
TryWorld 05596ef4e2 davsync: scope provider and library credentials during partial sync (#744)
* davsync: scope provider and library credentials during partial sync

* davsync: a sync writes which part carries its credentials, and no blank clears one

yetone's review of the first cut found two losses the per-part markers
caused, and both are fixed by making the markers say exactly which part
they belong to:

1. A keyed computer's library-only upload cleared a keyless computer's
   private MCP token. The blanks in the uploader's library are redactions
   it downloaded and never held a value for, not clears it made; take now
   keeps the server's value for an empty secret whatever the policy is, so
   only a server taken out here is gone. The test that had the loss as
   its expected now expects the token kept.

2. Mixed versions dropped the remote's provider keys. A keyed upload
   didn't write the whole-bundle Keys bit, and an older keyless magpie,
   which reads no per-part marker, uploaded its own providers over the
   server's with nothing to keep. take sets Keys for the providers' again.

The per-part markers are three-state now: a whole collect and a magpie
before them write none, and the whole-bundle bit says it then; a sync
writes one for every part it merges — true when the upload carried that
part's credentials, false when it didn't — and that bit, not Keys, is
what a reader goes by. That is what keeps 1 and 2 apart: the providers'
whole-bundle bit is set for older readers, while the settings' and the
library's own false is what tells a new reader they came without keys and
their local values stay. take marks its merged bundle as a sync one
(BundleVersion), so a sync's readers never fall back to Keys for a part
it wrote.

The two regression tests of the first cut that encoded the losses as
expected are updated: library/clear keeps the server's secret, and the
keyed providers upload writes the whole-bundle bit while leaving the
other parts' contents alone.

A remote that main already flipped to Keys=true over redacted settings or
library isn't repaired by this: the fix keeps new syncs from flipping it,
and a keyed computer syncing those parts repairs them by writing them
whole.

Validation on Windows: go test ./internal/... under a temp HOME passes
whole; the davsync, backup, access and settings packages pass with -race;
stashing the change fails TestTakeScopedPartCredentials (8 cases), the
scoped-sync tests that cover both losses, and the balance-token and
restore-scope tests that read the markers. go vet ./internal/... and the
darwin, GOOS=linux and GOOS=windows builds pass.

* davsync: preserve local secrets when applying partial uploads
2026-10-04 23:42:12 +08:00
yetone 258eae592d agent: Cursor Private Inference, which magpie finds by its product.json and starts on magpie with a command its row copies, and whose /models gives each model's api_types and capabilities (#299, mamba on Discord) — the build (Cursor 3.23.x, the same app name, bundle id and ~/.cursor as Cursor) runs its agent locally and asks the endpoint in CURSOR_LOCAL_AGENT_BASE_URL / CURSOR_LOCAL_AGENT_API_KEY when its Open configuration has none; mamba confirmed it works with any account pointed at magpie. magpie writes nothing of Cursor's (its settings share Cursor's state.vscdb): the Agents page lists it when an app's product.json says "Cursor Private Inference" (/Applications, ~/Applications; %LOCALAPPDATA%\Programs on Windows; /opt and the like on Linux), and its row's one control, Copy launch command (复制启动命令), copies CURSOR_LOCAL_AGENT_BASE_URL=<gateway>/v1 CURSOR_LOCAL_AGENT_API_KEY=magpie-cursor-local '<app>' (PowerShell on Windows); in the tray panel the row copies it. Its notice says to quit it first (a second start hands over to the running one) and that a base URL in Open configuration comes first. /v1/models asked with its key gives each model api_types (the APIs its provider serves it on, all three for a group or a translated one; it asks Anthropic Messages only when no OpenAI one is listed) and capabilities (context_length, max_output_tokens, supports_vision); any other client's list is unchanged. usage.AgentOf matches a name before a UA prefix, so cursor-local isn't counted as Cursor. TestCursorLocal, TestCursorLocalModels and cursor-local-launch.test.cjs (WebKit, en and zh) fail on the old code; agy-launch and agent-* GUI tests 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. 2026-10-04 23:38:19 +08:00
yetone 80441c2c29 gui: a provider's editor has a Max output beside its Context window, so a model whose reply limit magpie has wrong can be set right there (ARNO on Discord: 上下文长度有问题还可以手动覆盖,maxtoken错了如何解决呢) — the reply limits the user sets (ModelOutputs, "provider/model" or "provider/*") could only be set with magpie model output; the editor now shows them in a Max output box written as the Context window box is (32k for all its models, or gpt-6=128k, comma separated), and a Save sends them: the limits not in the box are dropped, a model the provider doesn't list is refused, and the catalog is touched so /v1/models, the agents' max tokens and the gateway's per-member clamp take the new limit. zh and ja strings added. TestModelOutputsSetFromTheEditor, TestProviderSaveOutputs and provider-max-output.test.cjs (WebKit, en and zh) 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 (TestClientGoneRecordedAsCanceled flaked once under load, passes alone). 2026-10-04 23:37:35 +08:00
yetone 3f5cd1f3cc tray: the Mac's menu bar icon can be Command-dragged again, so Hidden Bar and the like can move it (#792, BennettChina: after v0.1.865 a Command-click no longer opened the panel, but the icon still didn't move) — magpie's own monitor of mouse-downs on the icon (#629, which keeps it from flickering) took every left click on it and handed it to the action, so the button never saw a Command-held mouse-down, and that is what starts the system's drag. One with Command held is now left to the button. On this Mac (macOS 26.2) with a GUI build in a sandbox HOME: a synthetic Command-drag of its icon 60 px right did nothing on origin/main and swapped it with its neighbour with the change; the same drag moved a bare NSStatusItem, and one set up as Wails does (sendActionOn mouse-down, its local monitor) in both cases; a plain click still opened and closed the panel. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows), go test ./internal/gui/ and go test -tags nogui -count=1 ./... under a temp HOME pass. 2026-10-04 23:37:07 +08:00
tobitege 21702d01ff Add German translation; harden paths against unset env vars (#770)
* Add German translation and harden paths against unset env vars

Add a German locale to the GUI, tray menu, usage alerts and website home page, and refuse to start when the home folder is unset or relative so nothing is written under the working folder; the pi plugin no longer adopts the user's PI_CODING_AGENT_DIR, and an inherited APPIMAGE no longer selects portable mode. Scripts refuse to run from the wrong folder or empty a shared out folder, and a new internal/testenv sandboxes every test package from the user's real files, which also fixes the Claude picker and Termux installer tests on Windows.

* Makefile: know magpie's folder by its go.mod, so paths with spaces work

The check compared $(CURDIR) with $(dir $(abspath …)), which make splits on spaces, so a checkout under 'My Projects' or 'John Doe' refused every target. It now reads go.mod relative to the working folder and wants magpie's module there: make -C and a symlinked Makefile work, make -f from another folder still stops before any recipe runs.

* i18n: German for the strings main added since, with a parity test

75 strings main added (group order, patterns, slow-start wait, usage caps, RTK savings …) get their German, and two that main dropped go. gui-de.test.cjs checks, as gui-ja does, that de has every zh string with its placeholders and tags, and that the window reads in German when picked or on a German system.

* appdir: ignore relative folder variables when reading, keep them for children

CheckEnv no longer unsets anything. magpie reads the XDG folders, APPDATA/LOCALAPPDATA and every agent's folder variable through appdir.Getenv/LookupEnv, which pass over a relative value; the environment stays as it is, so the agents magpie starts get the variable as the user set it. That covers what proc.UserPath lends from the login shell too, which is set after main's check and reached the desktop app unchecked. On Windows a path rooted on the current drive (\Users\x, Git Bash's /c/x) counts as usable, and magpie version and help answer without a home, before run makes any folder.

* Sandbox the tests main added since

internal/wslrun gets testenv's TestMain, and four tests main added that set HOME alone set their home with testenv.SetHome, so on Windows they don't read the runner's profile through USERPROFILE.

* agentenv: PI_CONFIG_DIR and OPENCODE_DB take relative names on purpose

omp puts $PI_CONFIG_DIR under the home and OpenCode a relative $OPENCODE_DB in its data folder, so neither depends on the working folder: appdir no longer passes them over.

* i18n: German for the Library's multi-select and System One's decision API

The 19 strings main added since (#791's skill multi-select, the decision API snippets) get their German; the one main replaced (four APIs → five APIs) goes.

* appdir: tidy the import into magpie's group; say what Go makes of /c/x

The appdir import joins the group of magpie's own imports where a file has one. rooted's comment now says that Go takes a Git Bash path like /c/x as \c\x on the current drive, not C:\x.
2026-10-04 23:30:56 +08:00
Zhizhen He 689b819294 budget: keep reservations in their original window (#796)
A request still in flight across a calendar reset could reserve the next
window's entire budget even though that window had no usage. Scope held
reservations by the usage file, gateway key and window start, and use the
same key for admission, status and release.

Cover day, week and month boundaries with token and cost caps, including a
late check for the old window and settlement while the new window has an
active reservation. All six cases fail before the change. The key-limit
tests pass normally and under the race detector; access tests and vet of
the affected packages also pass.
2026-10-04 23:30:42 +08:00
Zhu Wang c1f919ca09 fix(routing): simplify the request purpose filter (#795) 2026-10-04 23:30:01 +08:00
yetone 0e7a1f6b6a cli: magpie <agent> default takes magpie out and puts back what the agent had before, as the Agents page's Disconnect does (__jingling on X: magpie claude default took the model they had set away with magpie's) — the agent-wide default set its first field to the agent's own default, which for Claude Code is Claude Code as installed: settings.json's model was deleted with magpie's endpoint and tiers, so a user on opus before magpie was left on no model at all. On an agent magpie is wired into it now runs Disconnect (Unwire, then each field magpie set back to its default), which puts back the model, endpoint and token stashed when magpie was wired in; Codex goes back to its own model the same way. magpie <agent> <field> default is as it was, the field to the agent's own default. The README and usemagpie.ai said under 15 MB for the desktop app and 7 MB for the terminal build; v0.1.869's are 27–30 MB (CLI) and 31–35 MB (desktop on Linux and Windows), a 13–16 MB download on a Mac, and say so now (en, zh, ja). In a sandbox HOME: settings.json with // mine, model opus and theme dark, magpie claude deepseek/deepseek-chat, then magpie claude default: model opus back, the env cleared, the comment and theme kept (on origin/main: no model). TestAgentDefaultPutsBackTheModelBefore fails on the old code. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. 2026-10-04 23:25:03 +08:00
yetone 0fd1c5c8f5 agent: an omp in WSL has its version asked in the distro, so its models offer max from 16.4.0 (whqtian on Discord: a WSL omp still had max made xhigh) — the omp there isn't the one on Windows' PATH, so its version wasn't known and was taken for an older omp's, whose models.yml refuses max. The distro's probe now asks the distro's own omp (not one on Windows' drives) its --version, with a 10 s timeout, and the WSL omp's entry is built for that version; one that says none is taken for an older omp as before. The version is kept with the distro's other probe results, and asked again when the distro is next started. TestWSLOmpVersionProbed; the probe script run under sh with a fake omp printed ver:omp omp/16.5.1. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. 2026-10-04 23:24:02 +08:00
yetone 7909de22ac usage: magpie usage and the TUI's Usage page show the agents' own calls, read from their session files, beside the gateway's, and a Codex-based client's calls aren't counted twice (Kumo31 on Discord: Codex used directly, not through magpie, was on the window's Usage tab but not in magpie usage) — magpie usage and the TUI only ever summed the gateway's log (usage.Summarize); the calls the agents' session files record that the gateway never saw (Codex on the user's own ChatGPT sign-in, Claude Code, Claude Desktop…) were merged only into the ledger the window's Requests tab and the tray panel read (#239). usage.Direct sums those rows of the ledger (Source "log", this computer's, rejected ones left out) by agent, model, account and session, priced as the ledger prices them, and magpie usage prints them after the gateway's tables under "not through magpie · the agents' own requests, read from their session files" (a model whose provider the file doesn't tell reads "local session/<model>"); the TUI's Usage page shows the same block under the gateway's, each with its totals and agents' and models' tables. The two parts are separate, so nothing is counted in both; a period with only the agents' own calls shows them under "no calls through magpie". All of magpie usage's output now goes to the writer it is given. Checking it against the owner's real data showed the ledger counted a Codex-based client's calls twice: miyi-helper's requests through the gateway are logged under its own name while its Codex session file names Codex, and the gateway/session-file matcher keyed on the agent, so they never matched; the matcher now keys on the session, tokens and time alone (a session id names one conversation), which also corrects the Requests tab. With read-only copies of the owner's ~/.config/magpie and four days of ~/.codex/sessions in a sandbox HOME: magpie usage week printed 177 calls not through magpie, in 1.3M, out 52.4K, cache read 3.9M, gpt-6-astra 154 and gpt-6-sol 23 — the same rows, to the token, as usage.LedgerOf (what /api/usage/requests serves) gave for the period; before the matcher fix, today's 7 miyi-helper calls showed in both parts. TestDirectCountsOnlySessionFileCalls (the miyi-helper case included), TestUsageShowsCallsNotThroughMagpie and TestUsagePageShowsCallsNotThroughMagpie 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. 2026-10-04 23:03:15 +08:00
yetone 2f5ff69d3d gui: the Gateway page's Connect speaks System One too, with a decision model (ARNO on Discord: gateway添加system one api 有想法吗) — magpie's gateway has served POST /v1/systemone, but Connect showed only OpenAI, Responses, Anthropic and Gemini, so nothing on the page said so or how to ask it. Connect has a System One tab: base …/v1, curl, Python (requests) and Node (fetch) snippets that POST …/v1/systemone with Authorization: Bearer, a state and a choice question, printing the answer's choice and confidence; the note says Clef also reads images ("images": [data URLs]). No SDK speaks it, so it has no Shell tab, and the base URL and key hints say where the request goes and how the key is sent instead of naming env variables. Its model is a decision model only (decidesModel: a decision-only provider's, or a Jev), and the other tabs no longer take one; a click on a decision model in the list switches Connect to System One, a click on any other model back to OpenAI, each tab keeping its own pick (magpie.decideModel). The head says five APIs; Connect's tab strips scroll in themselves on a narrow window, as the region strip does, rather than widening the card. zh and ja strings. gateway-systemone.test.cjs (WebKit, en and zh, light and dark) fails on the old code; gateway-connect-keys, -model-info, -fold, -older, -caller-keys, -key-limit and gui-ja pass on WebKit; Chromium doesn't launch in this sandbox. The snippet's request, sent to the gateway's handler, reached a Workers AI Jev and Clef in their own shapes, and with the owner's real TypeSafe key in a sandbox HOME, jev-latest answered 200 with choice billing, confidence 0.55. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. 2026-10-04 22:59:23 +08:00
yetone 26706c3371 library: skills are picked to turn on or off together, and a part of a list (lark-*) has its own chips (#791, mintonight: 资源库中local中的技能,能不能支持多选分组,或者支持多选来开启或关闭 … lark-* 我希望把他们归到一起统一来管理) — the skills on this computer are one list with no heading when they're all from there, so #787's group chips never showed for them, and Feishu's lark-* skills were turned on or off one row at a time. A part of a big list (its skills' shared name start, or their folder) now has chips on its heading, as a repository's heading does: lit when an agent has them all, half lit when it has some, a click gives it every one or takes them all, All does it for every agent; a click doesn't fold the part. Select, beside Turn all on, puts a box on every skill's row and on every heading (ticked when all under it are picked, half with some), a row's click picks it instead of opening its SKILL.md, and a bar that stays in view under the list says how many are picked, has a box for every skill the filter shows ("All 4 that match"), the same chips for the picked ones, Clear and Done; the picks stay through a change and Done takes the boxes away. A pick doesn't scroll the page. zh and ja strings added. pick-skills.test.cjs (WebKit, en and zh): the lark part's Codex chip posts the four lark skills, rows and headings pick, the bar's chips post the picked ones, the filter's All picks what it shows, Done ends it; it fails on the old code. Looked at in light and dark. group-skill-chips, every-skill, library-all, library-items, library-toggle-status, library-row-open-scroll, own-skill, remove-all-skills, shared-skills, import-all-skills, library-own-folder and library-loading pass on WebKit; Chromium doesn't launch in this sandbox. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./internal/gui/ under a temp HOME pass. 2026-10-04 22:52:01 +08:00
AaronYiandyizhuolin e8cd4bac8c fix: 透传 Claude 订阅审批上下文与服务端结果 (#794)
Co-authored-by: yizhuolin <yizhuolin@i-calorie.com>
2026-10-04 22:51:45 +08:00
yetone 7df9345903 routing: a group tells agents the longest reply a member of it gives, and each member is asked for no more than its own (ARNO on Discord: 发现group 模型的maxTokens是按照最小的来设定的,导致agent经常跑一半发生错误 "Response was truncated before completion.")
A group's catalog entry took the smallest member's output limit, so /v1/models and the agents' catalogs told agents that, and every reply through the group was capped at it, even from the members that write far longer ones. The group's Output is now its largest member's (as its context window already is, #700), and the gateway, for a group request, lowers the agent's max tokens to each member's own known limit (its catalog Output: the user's per-model setting, else the vendor's list or models.dev) only when the request asks for more: max_tokens / max_completion_tokens (Chat), max_output_tokens (Responses), generationConfig.maxOutputTokens (Gemini), and for Anthropic max_tokens with the thinking budget kept under it (budget = limit-1, or thinking off when no 1024+ budget fits). A request asking for less, or a member with no known limit, goes as before.

TestGroupOutputPerMember (members at 4096 and 32000: the group says 32000; asked for 20000, the 4096 one gets 4096 and the 32000 one 20000; asked for 1000, 1000) fails on the old code at either half; TestWithMaxOutputAnthropicThinking. 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 failed under load and pass alone).
2026-10-04 22:48:06 +08:00
yetone 3cdb7fb3a5 gui: a Command-click on the Mac's menu bar icon is left to the system, which drags the icon with it (#792, BennettChina) — Hidden Bar's users move icons with Command-drag, and magpie's icon opened its panel instead. The tray's click handler now reads the held modifiers ([NSEvent modifierFlags]) and does nothing when Command is down; other systems are unchanged. go vet, builds (darwin with the GUI, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./internal/gui/ under a temp HOME pass; the drag itself wasn't exercised here (it would move icons in the owner's live menu bar). 2026-10-04 22:45:28 +08:00
Junjie Zhou 28a830fc5c aside: magpie picks Aside's model, its thinking level, its four task models and its picture model (#789)
* aside: magpie picks Aside's model, its thinking level and its four task models (X: junjie — Aside Settings → Models has a Default model row magpie could not touch, and Fast/Standard/Deep/Visual rows it could not reach at all)

Aside reads the same provider list Pi does, so magpie hands it the whole
catalogue in a provider block of its own, carrying the magpie-aside token so
its requests are told apart in usage. What differs is the default: Aside keeps
it as one object in ~/.aside/u/0/settings.json naming the provider, the model,
the thinking level and fast mode together, so the model field reads and writes
two keys where Pi's are two strings, and both go through Aside's own settings
API — the running daemon holds its default and writing the file behind it
leaves the daemon on what it already had, read back the same way with the old
model still named after magpie had written the new one. The API is reached
through the repl, the settings command having no set of its own.

The four task roles are the same shape as the default and are wired the same
way, each a Quiet field following the model, so a role the user has not set is
not a line of its own and follows the default. A role is written whole:
thinkingLevel is required by Aside's schema and a role without one is refused,
so a role keeps the level and fast mode it had, a new one takes the default's,
and where neither is set it takes the level Aside gives its own roles. Taking
magpie out puts every role back, the user's own model where there was one and
nothing at all where magpie was the only thing that set it, and a role the user
moved onto a model of their own is left alone.

Each change checks the value that came back: Aside answers a set with the
settings as they now are and drops what it will not take without a word — a
role naming a model it has not resolved yet is the one that happens — so
without the check a change that went nowhere read as one that landed. Caught,
it is written to the file, which is what Aside reads at its next start, and
the Agents page says so.

The account magpie wires is 0: the folder is ~/.aside/u/0 while the CLI names
it u0, and TestMain stands in for the binary so a test can never change the
real Aside's default. A models.json carrying a magpie provider of someone
else's is refused rather than written over, and one that will not parse is the
user's to fix.

With the owner's real Aside under a sandbox HOME: a pick reached the gateway
and was attributed to a client named Aside, the default put the user's own
minimax model back and took the magpie provider out, and a role set through
magpie was read back by the running daemon the moment it had a magpie model to
resolve. Not tried: a change written to the file taking hold at Aside's next
start, which needs a restart. go vet, builds (darwin, GOOS=linux,
GOOS=windows) and go test -tags nogui -count=1 ./... pass (TestBunThroughBridge
fails, reproduced on a pristine worktree of the parent; internal/plugin passes
alone and times out only under the full run's load).

* aside: the row appears for an Aside that is installed and not yet used (the CI preview of #789 could not find .row.agent[data-id="aside"])

Detected() took ~/.aside/u/0, the account folder, and that only exists once
someone has signed in. The binary was the other look and the CLI installer
links it into ~/.local/bin, which a magpie launched from Finder has no PATH
entry for, so an Aside that was installed and never used showed no row at all —
the preview installed one, drove the UI and could not find it in either the
installed list or the row selector. The installer writes ~/.aside/cli and the
daemon writes the rest, so ~/.aside is the folder that is there from the
install, and it is the one Detected() should ask about. Path stays the
account's settings.json, which is the file magpie edits and the only one
worth reading a model out of.

TestAsideIsDetectedBeforeAnyAccount fails on the old Dir: it takes PATH away
so the binary cannot be found either, makes the installer's folder and no
account, and expects the row.

* aside: a wedged repl gives up, and the model comes off through Aside (yetone on #789)

Three things about the way out of an agent and into one, all found on a real
Aside.

The repl had no timeout. proc.Command(...).Output() waits as long as the
command does, so a daemon that has wedged or one waiting on input leaves a pick
in the Agents page or the CLI hanging on it, and switching off asks up to five
times over — the default and four roles. proc.CommandContext with ten seconds:
Aside answers in milliseconds, and a run that does not is written to the file
instead, which is what Aside reads at its next start, with the notice that says
so. TestAsideGivesUpOnAReplThatHangs runs the real command against an aside
that never returns: it passes in the timeout, and without CommandContext it
hangs until the test's own timeout kills it.

Switching off with no model of the user's behind magpie's wrote settings.json
directly. By then the provider block was gone, so the running daemon went on
using magpie/… with no way to reach it, and asideStale was never set, so
nothing said the change needed a restart. It goes through asideApply like
every other change, with the value that came back checked.

The provider block now goes last, after the default and the four roles are put
back. A step that fails on the way out otherwise leaves the settings naming a
provider that is no longer there. TestAsideDefaultTakesTheProviderOutLast
watches the block from inside the settings step and fails on the old order.

The icon is Aside's own brand-embossed-light.svg with the embossing's
transparency taken off, so the mask is solid and takes the row's text colour
like every other agent. As it was, the white fills at 8–20% opacity the dark
and light brand files are built with left the mark almost invisible on a light
row, and it vanished entirely in dark mode. Same paths, same shape, now solid;
checked in the app next to Pi's.

Tested against Aside 1.0.928.1 (daemon 1.26.928, CLI 1.26.916.1741) on
darwin/arm64. go vet, builds (darwin, GOOS=linux, GOOS=windows) and
go test -tags nogui -count=1 ./internal/agent pass (408).

* aside: the model Aside draws with is magpie's to set too (X: Aside Settings → Images had no row for it)

Aside keeps a fifth model setting beside the default and the four task roles:
imageGenerationModel, the provider and the model it draws pictures with and
nothing else — no thinking level, no fast mode, since neither means anything for
a picture — and no key at all on an account that has never picked one.

It is a magpie field like the roles: Quiet, so it is not a line of its own on an
account that has none, and taking the model after while it is unset. A model of
magpie's needs the provider block to reach it through, and what it named before
is remembered the same way a role's is, so `magpie aside model default` puts the
user's own picture model back and takes a picture model of magpie's off rather
than leaving it naming a provider that is no longer there.

Not wired: customModels, which is the user's own model definitions with their own
credentials, and asideModelCatalog, a list of model ids to pin or hide. Neither
is a model magpie picked, so neither is a setting of magpie's to own.

Aside 1.0.928.1, daemon 1.26.928. go vet, the agent/gui/profile tests, the three
CLI cross-builds and the CLI verification under a temp HOME all pass.
2026-10-04 22:45:16 +08:00
yetone 23eb6c5f32 gateway: a Clef asked through magpie's System One API gets the images the request embeds (ARNO on Discord: clef模型是支持图像输入的) — /v1/systemone already passed a request's fields to the Clef as they were, images among them, but refused any request over 1 MiB with a 413, so a single embedded screenshot didn't fit. The limit is now 16 MiB (Workers AI takes up to four images, 13 MiB of request in all). TestSystemOneClefImages (a 3 MiB data-URL image to cloudflare-jev/@cf/cloudflare/clef: answered, asked at the Clef's own path with model clef, the image reaching it unchanged) fails on the old code. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. 2026-10-04 22:36:56 +08:00
yetone 12acf07a30 gui: checkboxes are drawn as magpie's own, not the system's (ARNO on Discord: 会话面板下的checkbox的样式和软件整体的设计风格有点割裂) — the Sessions page's boxes, and every other checkbox (Library picks, import apps, caller-key limits), were the system's: white squares on the dark page. They are now a 14 px rounded box on the card's colour with a faint border, filled with the accent and a check in its foreground when ticked, a bar when partly ticked (a folder with some of its sessions picked), a ring on keyboard focus; pages keep their own margins. checkbox-look.test.cjs (WebKit, en and zh, light and dark: unticked on the card, ticked and partly ticked filled with the accent, a click still ticks and unticks) fails on the old code; Chromium doesn't launch in this sandbox. Looked at in light and dark on WebKit. sessions-manage, -folder-delete, -toolbar, -purge, -bar-names, gateway-caller-keys, gateway-key-limit, library-items, -all, -desktop-skills pass on WebKit; go test ./internal/gui/ passes. 2026-10-04 22:32:19 +08:00
yetone a94cc3c950 provider: Workers AI's decision models (Jev, Clef) are tested from their right-click as System One's are (ARNO on Discord: cloudflare-jev provider模型无法右击测试) — a model test of a cloudflare-jev provider said its models couldn't be tested, since the test sent System One's question as it is and Workers AI wants it wrapped. DecideAsk and DecideAnswer move from the gateway to the provider package, and AskSystemOne (which TestModels uses) now wraps the question for the provider's API and reads the answer out of Cloudflare's envelope, as routing's own decisions do: Jev is asked {model, input}, a Clef at /run/@cf/cloudflare/<clef> with its own model selector. TestCloudflareDecideModelsTested (an httptest Workers AI: both models pass, the bodies are each one's, a refused key fails) fails on the old code; TestModelTest's cf case now expects testable. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass (TestProvidersAnswerWhileListsComeIn flaked once, passes alone). 2026-10-04 22:22:23 +08:00
yetone 25826ea19f gui: the Usage chart's days are written in the page's language (the owner: 用量图表的日期也按语言格式化) — the backend labels a day "Sep 5" in every language, and the Overview chart put that label under its bars and in their titles in Chinese and Japanese too. The chart now writes each point's time as the page's language does (toLocaleDateString with intlLang, month short, day): "Sep 5" in English as before, "9月5日" in Chinese and Japanese, and a week's bar "9月12日 起的一周" / "9月12日 の週". An hour's bar keeps "15:00". usage-chart-dates.test.cjs (Chromium and WebKit, en, zh and ja: the days under the bars, a day's, a week's and an hour's title) fails for zh and ja on the old code. The usage-*, number-units, gui-ja and panel-usage GUI tests pass, but for usage-refresh-flight's two Chromium cases, which fail the same on origin/main. go build (darwin, GOOS=linux nogui) passes. 2026-10-04 22:13:12 +08:00
yetone 2dc8e249cb agent: dsh on a DeepSeek model picked in its own /model stays connected while magpie's route is in its list (Fate on Discord: DSH went back to Not connected) — magpie connects dsh by putting its route among dsh's custom providers and starting it on one of magpie's models; a model of DeepSeek's own picked in dsh is saved in its settings.yaml over the patch list, and the Agents page, which called an agent connected only when its model field was magpie's, showed it Not connected, though the route, its key and magpie's models in dsh's /model were all still there. dsh now has Joined (the first profile's patch list has magpie's route), as Codex signed in with ChatGPT does, so it reads Connected · pick magpie's models with /model, and a pick off magpie while joined isn't drift ("changed outside magpie"). Disconnect in that state has an Unwire that takes the route, the start magpie wrote and the key out of every profile and .env and leaves the model picked in dsh; on one of magpie's models it does nothing and the model set back does it all, as before. TestDshOwnPickStaysConnected (connect, a DeepSeek pick in settings.yaml: wired, no drift; Disconnect: the template as it was, no key, the pick kept) fails on the old code. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. 2026-10-04 22:03:41 +08:00
yetone ee3fca6375 decide: Cloudflare's Clef and Clef Flash are offered on Workers AI beside Jev (ARNO on Discord: cloudflare workers-ai 现在有了新的 decision 模型) — Workers AI now serves Cloudflare's own decision models, @cf/cloudflare/clef (27B) and @cf/cloudflare/clef-flash, which answer System One's questions as Jev does. The cloudflare-jev provider (now named Cloudflare Workers AI · Jev, Clef) lists them beside typesafe/jev, so a routing group can pick either as its classifier (cloudflare-jev/@cf/cloudflare/clef), and magpie's System One API routes to them by name. Each is asked at its own run (…/ai/run/@cf/cloudflare/clef), with the request as it is and its model named clef or clef-flash, as Cloudflare's docs give it; Jev is still a run of typesafe/jev with the questions as its input, and stays the default. TestCloudflareClef (offered, routed, each one's URL, a Clef id outside Workers AI no decider) and TestClefOnCloudflare (a group classified by Clef Flash through Workers AI) fail on the old code. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried against Cloudflare: no Workers AI key in the owner's config. 2026-10-04 21:52:11 +08:00
yetone 56d5341636 routing: an open group folds back into its row from its editor's heading (ARNO on Discord: 路由组详情现在可以单击展开,是否可以再加上单击收起) — a group's row opened its editor on a click, and only Cancel closed it. The editor's heading of an existing group is now a button (pointer, "Click to fold it", aria-expanded, Enter or Space from the keyboard) that folds it back while nothing in it has changed; with a change it stays open and says to Save or Cancel first, so a stray click never throws an edit away. Folding saves nothing. zh and ja strings. group-fold.test.cjs (WebKit, en and zh: click, Enter, a change kept with the warning, Cancel, nothing posted) fails on the old code. group-first-token, group-arrange, group-member-off, group-patterns and gui-ja pass on WebKit; group-from-model-add's 600x420 case fails the same on origin/main. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./internal/gui/ under a temp HOME pass. 2026-10-04 21:50:16 +08:00
yetone 0b8fca7380 gateway: a tool schema's oneOf goes to Cline, Kilo and OpenRouter as anyOf (H20 on Discord: Cline answered 400 "unsupported schema keyword: oneOf" for Codex's automation_update tool) — some of the models those routers serve sit behind a grammar compiler (xgrammar; OpenRouter's ModelRun) that refuses oneOf "with overlapping or non-provably-disjoint branches" and takes anyOf, which to a model choosing what to call says the same. Every oneOf key in a tool's parameters/input_schema (through properties, $defs, items, allOf/anyOf and the other schema keywords) is renamed in place, the same length, so nothing else of the body changes and the prompt cache keeps its prefix; a property called oneOf and a description that says it are left alone, and every other provider is sent oneOf as before. With the owner's real OpenRouter key in a sandbox gateway, qwen/qwen3.8-27b:free pinned to ModelRun with a oneOf tool: before, 400 from xgrammar; now a tool call {"id":"7","change":{"paused":true}}. TestToolOneOfAsAnyOf (Cline, OpenRouter renamed; nested, items; a property named oneOf and the description kept; another provider unchanged) fails on the old code. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. 2026-10-04 21:38:58 +08:00
TryWorld 64a4e1014b fix(gui): a reply that broke off is told as failed everywhere (#772)
* fix(gui): a reply that broke off is told as failed everywhere

733's tryOk rule — a try with a status under 400 and no fail didn't
answer — was put through the Routing page's log and its try tally, but
every place that judges a request as a whole still read the status alone:
a request whose reply had begun and then broke off (its 200 sent, the
vendor's error after it) kept an answerer it never had. Its row was a
green dot reading "provider · model" as if it answered, the replay and
the Agents panel told the same, and the "errors your agent saw" stat,
which counts the request, picked nothing when clicked since it only knew
statuses of 400 and up. The log of the very request said it began
answering and then broke off, so the page disagreed with itself.

outcome() asks tryOk for who answered, and a route with an error is bad
even under 400; the swap and routed marks, on the row, the seat and the
log, only show on a try tryOk answers; the stat's click knows an error;
and the two play animations send a try with a fail back as failed. The
row, the replay, the Agents panel, the stats and the animations now tell
the same story the log does.

routing-broke-off.test.cjs — Chromium and WebKit, English and Chinese —
fails on the old page with the row's class "rt-req ok" and passes: the
row is bad, the errors stat picks the request, and its story says it
began answering and broke off.

* fix(gui): a 200 with a note of its own is still an answer

yetone's review of the first cut: outcome() read a route's error field
for "bad", but a 200 whose error is magpie's own note answered — Codex's
titles off in Settings writes one, as a title reply with no title in it
does, and their tries carry no fail. Every titled thread's first request
turned red. Reads the tries now, as the rest of the rule does: a route
under 400 is bad only when it has tries and none answered.

The Agents panel's row said so too: its class came from the outcome, but
its "where it went" text still branched on the status alone, so a
broken-off request showed red with "→ provider" and no failure. It
branches on the outcome now.

routing-broke-off.test.cjs gains the note case (200, an error, a try with
no fail): its row is "rt-req bad" on the first cut and "ok" with this.
The broke-off case's rows, the errors stat and the story still hold, on
Chromium and WebKit, in English and Chinese.

Validation on Windows: node --test routing-broke-off.test.cjs — 8/8 on
Chromium and WebKit in both languages; both cases fail on the code
before this commit's routing.js (the broke-off row as "rt-req ok", the
noted one as "rt-req bad").

* fix(gui): the errors stat picks a broken-off reply, not a noted 200

yetone's second round: the stat's click looked for a status of 400 or an
error, which a 200 whose error is a note of magpie's own — Codex's titles
off in Settings, a title reply with no title — answers: every titled
thread's first request was a newer "error", and "show the latest request
that failed" jumped to a green row. It asks the same question the rows
and the story do now — outcome(r)[1] === "bad" — so the stat, the rows and
the story name the same request.

routing-broke-off.test.cjs puts the noted 200 above the broken-off one:
clicking the stat picks the broken-off request under it, and the noted row
is not picked. That fails on the previous commit (the noted 200, newer and
with an error, was picked) and passes. Two test fixes go with it: the
unpicked row's aria-pressed is "false", not absent, and the day list is
waited for before it is clicked, which webkit needed mid-run.
2026-10-04 21:27:46 +08:00