Repository navigation
Conversation
|
@MumuTW Thanks — this is a real bug and the approach (an ask that didn't answer leaves what was served, retried a minute later) is right. I reviewed 0aaae4c every line and ran it:
Load: 40 concurrent requests with a failing devin spawn it once, once more ~60s later — no pile-up, never blocks. Two things before I merge:
Not blocking, just so the description is accurate: cursor-agent's Push an update and I'll run it again. |
… kept without saying so (yetone#2230, JasonBuildAI) A vendor that keeps the prompt cached by itself and reports nothing of it — OpenAI's automatic prefix cache, Gemini — says neither a read nor a write of the turn that built the cache. affine judged that turn's stick by what the reply read and wrote (0 + 0), found it under cacheWorth and moved the conversation's second turn as "no-cache": the whole prompt went afresh to another key or account and was paid for in full, on every turn after the first, for anyone with more than one key or account and Affinity auto (the default). 7f968b95b named this as not covered: "a vendor that caches without reporting a write (OpenAI's automatic prefix cache, Gemini) still reports CacheWrite 0 on a first turn, so its second turn may still move". What such a vendor holds of the conversation after a turn is the prompt that turn was sent, which is what its cache now has. answered takes the reply's usage rather than its two cache counts, and where the reply reports neither a read nor a write, the prompt it was sent (Input) stands for the write: the second turn stays to read it, as it does for a turn that wrote its cache. A reply that reports no usage at all (Input 0) counts as no cache, as before, and a prompt under cacheWorth (1024) still moves. A read or a write the vendor does report is used as it was, and resting, spent, cold, strict, turn and rotate are judged before this, so nothing else changes. The observable change: with Affinity auto, a conversation whose last reply reported neither a cache read nor a cache write, and was sent at least 1024 tokens, is kept where it was (Why "cache", Kept true) rather than moved (Why "no-cache"). Its stick, affinity.json and the trace's Affinity.CacheWrite carry the prompt sent, and Affinity.CacheRead stays 0, which is what the Routing page shows as read and written; the page itself is unchanged. Tests: - affinity_silent_cache_test.go (new): TestAffinityKeepsACacheTheVendorKept Silently. Two keys, least-used routing, and a Chat vendor reporting prompt_tokens 3000 with cached_tokens 0 — what OpenAI and Gemini send of a first turn — keeps the second turn on k1 as "cache", with the trace telling CacheRead 0 and CacheWrite 3000; a vendor reporting no usage at all still moves to k2 as "no-cache". - TestAffinityKeepsACacheJustWritten, its "too little either way" (300/300, no-cache) case included, passes as it did. Run: - go test -tags nogui -count=1 -run TestAffinity ./internal/gateway: ok, 80.982s (TestAffinity 39.36s, TestAffinityKeepsACacheJustWritten 26.54s, TestAffinityKeepsACacheTheVendorKeptSilently 9.68s). - go test -tags nogui -count=1 -timeout 45m ./...: every package passes except 21 tests red on this Windows machine — agent 4 (TestInstallWithoutNodeParses, TestWSLClaudeFollowerOnTheMainModelIsNoDrift, TestCopilotFolderFromAnIDEPluginIsNotTheCLI, TestQoderSettingsFromAnIDEPluginIsNotTheCLI), gui 4 (TestGatewayHeldByAContainer, TestGatewayFixQuitsOnlyAMagpie, TestFXStateDoesNotWait, TestUpdateThatWontStart), library 3 (TestAgentsSkillsFolders, TestRemoveFoundSkill, TestHooksCodexTrustMovesWithTheGroups), gateway 2 (TestClaudeAsRootEnv, TestSweepBridgeProjectsTakesOnlyTheBridgesFolders) and one each in backup, davsync, pluginrt, provider, sessions, update and usage. A fresh worktree at the base e98440a, without this change, fails the same 20 of them. The 21st, gui's TestPurposeAssetMatchesUsage, fails only in this checkout, whose internal/gui/assets/purposes.js is CRLF (git says i/lf w/crlf) and so keeps the "\r" its TrimSuffix(";\n") doesn't cut; the file is not in this diff and the test passes in the base worktree. - gofmt -l on the changed files: nothing. go vet ./..., GOOS=linux go vet -tags nogui ./..., GOOS=windows go vet ./...: nothing. go build, GOOS=linux go build -tags nogui, GOOS=windows go build: ok. - Broken by hand: with the substitution removed the new test fails — "silent cache: affinity &{... CacheRead:0 CacheWrite:0 ... Why:no-cache}, want cache". At the base commit the case fails the same way, the second turn going to k2. Not tried: the Playwright suite (no GUI asset changed; the Routing page reads the same JSON) and a live OpenAI or Gemini key.
…a restart, and a prompt too small still moves (yetone#2230) JasonBuildAI's test covers OpenAI's Chat shape and a reply with no usage. TestAffinitySilentCacheGeminiRestartAndSmall adds the cases the same substitution has to hold for: Gemini's usageMetadata (promptTokenCount, cachedContentTokenCount 0 on a first turn), the stick read back from affinity.json after the in-memory one is dropped, and a 600-token prompt, under cacheWorth, which still moves as "no-cache". Each case also checks the trace tells CacheRead 0 and CacheWrite equal to the prompt sent. Run: - With the substitution in answered taken out, both this test and TestAffinityKeepsACacheTheVendorKeptSilently fail "Why:no-cache ... want cache" (the second turn went to k2). With it: PASS. - go test -tags nogui -run TestAffinity ./internal/gateway: ok. - go test -tags nogui ./... on macOS with this series (at 1b96dcc96 before the rebase): every package ok. gofmt -l, go vet ./... on darwin, linux (nogui) and windows, and the three builds: clean.
…effort of their own After yetone#2081 gave Desktop a subagent model, its effort still came from the request. Add subagent_effort beside that model, using the existing effort slider. The live setting DesktopSubagentEffort belongs to magpie; no Claude client file or binary is changed. Empty preserves the request's effort, including disabled thinking. A pick applies only to Desktop requests with an agent id and tools, independently of model replacement: unset, equal and unavailable model picks all work. Reuse the attempt-level fitting; fixed group-member efforts and reasonless models keep their existing precedence. count_tokens applies the pick after unmasked-body restoration. A Codex SubagentPick is not evidence of a Desktop tool turn. Route.Effort keeps the incoming level, SubagentEffort records the configured pick, and tries/usage keep the actual level. Routing explains the pick even without a model replacement. Options follow the target model; a saved level survives a model change. POST /api/settings preserves it. Disconnected preferences, Disconnect/reconnect and restored settings work; a Live pick gives restart advice only when it actually connects the agent. Codex's own settings and default-effort explanation are unchanged. Verification on b9c589e plus this change, with temp HOME and pinned Go caches: - go test -tags nogui ./...: 53 packages pass. - Gateway effort tests under -race -count=20: pass. - gofmt -l: empty; darwin, linux nogui and windows vet/build: exit 0. - 23 intentional Go behavior breaks produce real FAILs, then pass restored: application, scope, model independence, Default/none, disabled thinking, fitting/member priority, counts, next request, trace, saving/validation, Live notices and model-dependent options. - Three UI breaks fail in both engines at 1100/420: default explanation, missing effort story and missing translation. Restored: Desktop's new controls/story matrix passes 20/20 across five languages. Desktop, Codex, tier/effort and translation regressions pass 97/97. - Desktop-bundled Claude Code 2.1.295 runs real Agent calls through the built gateway, temporary HOME and loopback-only sandbox, all replies local fixtures. Low/high/Default sends its child low/high/xhigh on m2; its lead remains xhigh on m1. All three runs exit 0. The expanded Routing run is not green: Chromium 510/525, WebKit 485/525. These failures reproduce on untouched b9c589e, including targeted reruns. The day/row anchor assertions conflict with yetone#2134's at-top rule: undoing that one guard makes all 12 tests pass. A narrow request lies below the viewport; user wheel input makes the failed click pass. WorkBuddy's wheel pointer/hidden-button failures overlap yetone#2115; moving the wheel to the view gutter fixes 3/4 selected cases, one WebKit 420 case remains. The held-member clipping assertion also reproduces on base; its timing cause is not established. This baseline evidence is reported separately in the follow-up feature issue, not counted as passed or fixed here. Not run: the complete GUI suite, the Electron window/Cowork, native Linux/Windows or WebKitGTK. Cross-builds are not runtime checks. Reported-by: Koohoko
… back A pick on the Agents page redraws its square at once and writes behind it, queued (commit, and the effort slider's onchange). The test read the fixture's last write as soon as the square changed, so a slow write raced it: in a broad Routing run, six files at a time, Chromium's en 1100 case read "high" for the subagent effort just set back to Default. Each pick now waits for its /api/set answer, which the fixture sends after noting the write. The app is unchanged. Checked with the fixture's write held 300 ms (a probe, not committed): the test before this fails 4 of 4 (Chromium and WebKit, 1100 and 420, en) at "Desktop pick posts the catalog id", the model pick yetone#2081 brought; with this change it passes 4 of 4. Unheld, the race is rare: six runs of the whole file at once pass 120/120 before this and after it. The file alone: 20/20. The three UI breaks of the subagent effort still fail 4 of 4 (zh), and pass restored.
…it does Codex's Codex names a subagent's request (x-openai-subagent: collab_spawn), and the Routing page tags its row Subagent, with why it went where it did. Claude Code names no kind, so Claude Desktop's subagent turns, which magpie now moves to their own model and effort, read as the lead's: a Desktop session's rows on one routing group could not be told apart. Claude Code 2.1.295 sends x-claude-code-agent-id from every agent context but the main one. Its x-claude-code-request-class and x-claude-code-agent-type hints go only to a first-party base URL or with CLAUDE_CODE_GATEWAY_HINT_HEADERS, so magpie doesn't get them. - subagentTurnOf keeps the id in Route.SubagentID for a request that offers tools and names no kind: the turn desktopSubagentPick and desktopSubagentEffort take. Desktop's and terminal Claude Code's turns are kept, moved or not. The lead's turns, a subagent's tool-less helper calls (yetone#2081: Claude Code's housekeeping) and a blank id are not. A kind wins over the id. - A Desktop pick or effort that found the turn is trusted, so the body isn't decoded again; a request without the id isn't decoded at all. Only a subagent's turn with neither setting reads its tools once. - Kind stays empty (yetone#2081), so the ledger, the Usage page and the Purpose filter keep the turn unmarked. The ledger's Subagent already keeps the same id, on the helper calls too. - routing.js tags it through tagKind in the request list (by request and by session), the story, the replay and the tray panel, with Codex's words and reasons (kindWhy's collab_spawn: moved or kept, within a routing group, on the model asked for). No new strings. - The day's history keeps the id. A route kept before it has no tag, and its moved/kept story reads as before. Verified at this head, on b9c589e, under a temporary HOME with Go's caches pinned: - Each new test fails with its guarantee broken by hand (a real FAIL, not a build error), then passes restored. Go, 10: the id never set; tools, kind or the trusted pick ignored; tools decoded before the header (the lead's request then allocates 10 times, 1 for its headers); the route not given the id; Desktop only (terminal fails); every request trusted (the helpers fail); the JSON key renamed; the field left out of the history. GUI, 8, each 0 pass / 4 fail (Chromium and WebKit, 1100 and 420, zh) at its own assertion: tagKind ignoring the id; the list, the story, the replay or the panel testing Kind alone; kindWhy's collab_spawn reasons on Kind alone; the id over a kind; the Purpose filter following the tag. - go test -tags nogui ./...: 53 packages ok. - go test -race -count=20 -run '^(TestSubagentTurn|TestHistoryKeepsSubagentID|TestDesktopSubagent)' ./internal/gateway: ok. - gofmt -l: nothing. go vet ./..., GOOS=linux go vet -tags nogui ./..., GOOS=windows go vet ./..., go build, GOOS=linux go build -tags nogui, GOOS=windows go build: exit 0. - claude-subagent-tag.test.cjs: 20/20 (Chromium and WebKit; en, zh, zh-TW, ja, de; 1100 and 420), three runs in a row. - Desktop-bundled Claude Code 2.1.295 through the built gateway, a temporary HOME, a loopback-only sandbox and local fixture replies, its Agent tool spawning a real subagent: with nothing set, with a subagent model, and as terminal Claude Code, each run kept 4 routes; the subagent's one turn has the id the client sent upstream, the lead's 3 requests none, Kind empty; the model pick moved it to m2. No tool-less helper call came in these runs; the Go tests cover them. - The Routing GUI tests around it, 91 files (every routing-*, the tray panel's two, purpose-filter, click-scroll, the Codex and Desktop subagent files, subagent-effort, search-instead, search-mcp, vision-mcp, remote-group-served, reasoning-speed, usage-ledger, sessions-overview and the new one), six files at a time: Chromium 602/617, WebKit 572/617. Each failure fails without this commit too. On b9c589e untouched (checked for the effort commit): routing-days-fit, routing-failed-open and routing-side-calls in both engines (their position checks against yetone#2134's at-top rule), routing-wb-restricted (yetone#2115's wheel), and at 420 in WebKit routing-agent-gone, routing-agent-hue-not-red, routing-rest-off and routing-held-member. On 6926b7c: search-instead in WebKit, 5 of 5 alone, and routing-context-fold's picked request in WebKit, a row out of view, 2 of 5 runs alone there and 3 of 5 here. Not run: the whole GUI suite, a real Claude Desktop window or Cowork, Linux and Windows machines (GOOS vet and build only), WebKitGTK.
…ch is a plugin now (yetone#2232) Koohoko's patch was written before Routing moved into plugins/routing (eb7199e): its one new Routing string went into the core i18n.js. It is used only by plugins/routing/client.js, so its zh, zh-TW, ja and de go in plugins/routing/i18n.js, beside the page's other words, and the core scripts' size budget (91b9065) isn't spent on it. Run: claude-subagent-tag, desktop-subagent-model, subagent-effort, codex-subagent-model, routing-kind, purpose-filter, routing-purpose-state, i18n-keys and agent-notice-lang (Chromium and WebKit, 1100 and 420, the five languages where each test covers them): 111 pass, 0 fail.
…nside it, a long model's name ellipsized, not the chips cut off (碳碳双键 on Discord) 碳碳双键's screenshot (zh, ~770px): in a group's editor the 模型 rows ran past the editor's right edge, 跟随分组推理 / 标准速度 cut off, and the 模式 row didn't line up with them. .fallback and its .fbl are grids with an auto track, which grows to the widest row's min-content: one long model id (nvidia/nemotron-3-super-120b-a12b:free) widened the whole column. The track is minmax(0, 1fr) now, so the column is the editor's width. A member row's name and its provider shrink and ellipsize (the provider first), with the whole name as a title on hover; when the chips still don't fit they wrap to a second line, right-aligned. The rules group (.rt-rules) and the phone's own row layout (body.web, which wraps and starts at the left) are left as they were; the new rules sit in :where() so the phone's still win. New test group-editor-fits.test.cjs: Chromium and WebKit, the desktop window and the web page, 1100, 770 and 420 wide, en, zh, zh-TW, ja, de. It checks that nothing in the members or patterns box passes the editor's edges, no chip is cut, the page doesn't scroll sideways, the pattern row's edges are the members box's, and the long name is a title. It fails on origin/main 0050140 in both engines (desktop 1100 en already: past the editor's edge) and passes here. Screenshots at zh 770 and de 770 desktop and zh 420 web compared with origin's by eye. Run: the group-* and routing rules/weight/columns/shape files, 258 of 258; the full GUI suite (node --test, concurrency 6, 9679 tests): 34 red, in quota-count-wrap, quota-name-translated, refusal-retries-setting, routing-held-member, usage-agents-hide, usage-fold (red alone the same on the unchanged base 8e8316e; filed as yetone#2228, yetone#2163, yetone#2252, yetone#2211 and the new yetone#2258) and four that pass alone (deprecation-notice, login-import, name-suffix-no-names, remote-magpie).
…awn_agent task_name, its nickname shows apart, and Usage requests say which calls were compactions (yetone#1591, Hogeexxl) Hogeexxl's change (his fork branch codex/codex-session-details, f7c176c), cherry-picked with his authorship: - A Codex child thread whose title is still empty after the existing precedence takes the task_name of the parent's spawn_agent call, linked by the call id and the SubAgentActivity "started" item that names the child's agent_thread_id. Conflicting links leave the title empty. - session_meta.agent_nickname is exposed as `nickname` on the list, one, manage and overview responses, beside the title, not in place of it. - GET /api/usage/requests and /v1/magpie/usage/requests carry `compaction` (true/false) for Codex calls with native evidence, and leave it out otherwise. A gateway row matched by a unique response_id takes the marker and keeps its own model, usage, cost and kind. - Flags bits 1024 (rowCompactionKnown) and 2048 (rowCompaction) are new. TestRowFlagsApart covers them, and they don't overlap 1..512. The sessions cache goes to version 21 and the calls cache to calls-v15. Review: - Rollout format checked against Codex's source (openai/codex 669449b): - v2 spawn_agent args carry task_name. - A paginated rollout persists item_completed with a SubAgentActivity item: kind "started", id = the call id, agent_thread_id. - SessionMeta has a top-level agent_nickname, which Codex's own list.rs and metadata.rs read too. - compaction_response_id lives in token_usage_record. Gaps in legacy rollouts and in calls without a response_id are fixed in the next two commits. - Privacy: only a title string the user's own Codex wrote and a boolean are added to local API responses. Nothing new goes to a vendor or to telemetry. The ingest POST parses fields one by one, so it is unchanged. - No .github, dependency or network changes. What was run: - His tests pass: go test -tags nogui ./internal/sessions ./internal/usage, and the two new internal/gui API tests. - Broken by hand, each caught by his tests: - the nickname read removed; - the conflict checks removed; - mergeCodexCompactions broken; - the gateway pair's setCodexCompaction removed; - the flag read forced to true. - Not caught: - the parent check on the title link (covered by a test in the next commit); - `copy.codexCompactions = nil` in cloneCodexChunks removed. That line is defensive only: the shared chunks never hold the map. - The gofmt, vet and full-suite record for the three commits is in the last one.
… task_name too, and a forked child's copy of its parent's spawns no longer erases its siblings' titles (yetone#1591) Two gaps in the previous commit's title link: - Legacy rollouts. Codex's ThreadHistoryMode is Legacy by default. The local thread store has only defaulted to Paginated since openai/codex#47900 (2026-09-24). A legacy rollout doesn't persist item_completed for a started SubAgentActivity. It persists an event_msg instead: {"type":"sub_agent_activity", "event_id": <call id>, "agent_thread_id", "kind":"started", ...}. Its children got no title. That event now links the same way, through one helper (codexAgentStarted) shared with the item_completed path. - Forked children. A forked child's rollout repeats its parent's history, including the spawns of its siblings. The same call id then linked from two files, which counted as a conflict and erased the sibling's title. Titles are now keyed by {child, parent}, and only the file of the child's own parent_session names it or takes a name away from it. What was run: - New tests. On the previous commit each fails with: - TestCodexSubagentTaskTitleFromLegacyActivity: `legacy activity title for child-started = "", want "Legacy started task"`. - TestCodexSubagentTaskTitleComesFromItsOwnParent: `a fork's copy of the parent's spawn erased the child's title: ""`. - Broken by hand: with the parent dropped from the key, the second test fails with `a spawn its parent never made named the child: "Only in the fork's copy"`. - go test -tags nogui ./internal/sessions ./internal/usage: ok. - The event's field names come from Codex's source (protocol SubAgentActivityEvent and the rollout persistence policy, openai/codex 669449b). Not tried: a real legacy-history Codex subagent rollout.
…age requests instead of saying false (yetone#1591) Codex added token_usage_record and compaction_response_id together, in openai/codex#41912 (2026-08-31). A call that magpie reads from a token_count event alone, as in older rollouts, has no response_id. It can't carry the compaction marker either way. The previous commit reported such calls as `compaction: false`, which claims they were not compactions. codexCompactionOf now returns no state for them, so the ledger and the requests page leave the field out, as they do for any call with no native evidence. What was run: - New test TestCodexCallWithoutResponseIDHasNoCompactionState. On the previous commit it fails with `ledger: a call with no response_id claimed a compaction state: {..."compaction":false...}`, and with the same message for the requests page. It passes now. - go test -tags nogui ./internal/sessions ./internal/usage: ok. For all three commits on origin/main 1ad6044: - gofmt -l on the changed files: clean. - go vet ./..., GOOS=linux go vet -tags nogui ./..., GOOS=windows go vet ./...: ok. - go build for darwin, linux (nogui) and windows: ok. - Full suite: go test -tags nogui -timeout 40m ./... (pipefail, sandbox HOME): 53 packages ok, exit 0. - No GUI asset changed, so no Playwright run. - Not tried: a real Codex subagent rollout (legacy or paginated). The formats are from Codex's source, and Hogeexxl checked two real paginated subagents. - Rebased onto origin/main f255a3e (12 commits newer, including yetone#2233 and yetone#2234 in internal/sessions; no cache-version clash). Rerun there: go test -tags nogui -count=1 ./internal/sessions ./internal/usage ./internal/gui, gofmt and vet on both packages: ok.
… while it opens is closed upstream and dashboard usage is collected per account (plugins yetone#130, yetone#131) 0.2.13 (magpie-community/plugins 3351e47) carries two community fixes by nicegui. yetone#130: a cancel landing between the HTTP/2 head and runOnce's abort listener was never seen, so the Run kept streaming upstream with its heartbeat for nobody; it is now closed at each hand-off. yetone#131: the usage ledger's claimed/owed/flights were keyed by conversation alone, so a conversation moved to a second Cursor account had that account's own dashboard event taken as the first account's debt; they are now keyed by account and conversation. The built-in doesn't share this code, so un-moved users are unchanged. The cursor mover's min goes to 0.2.13 so keepMovedCurrent updates every moved user. Run: npm view @magpie-community/opencode-cursor-auth@0.2.13 version --prefer-online gives 0.2.13. Plugin: full bun test 1292 pass, 0 fail; own repros fail on 0.2.12 and pass on 0.2.13. Here: gofmt -l; go vet ./internal/provider for darwin, GOOS=linux -tags nogui, GOOS=windows; go test -timeout 40m ./... in a sandbox HOME: exit 0. Not run: a real Cursor account, nor GUI tests (no GUI change).
…idle wait over the body only, not over getting the headers (yetone#2248) The 400ms context deadline covered the whole request, so on a loaded machine the gateway's slow response headers ended the test at http.DefaultClient.Do ("context deadline exceeded") before it reached what it checks. The 400ms now starts once Do returns (time.AfterFunc cancel), standing for the client's idle timeout over the body. Run: - Reproduced by injecting a 600ms sleep before the fake vendor's first byte: old test FAIL keepalive_test.go:202 "context deadline exceeded", the issue's error; fixed test passes 3/3 with the delay in. - Broke the guarded behaviour by hand (a 5ms ticker calling alive() in readSSEAlive, so silence is covered), delay still in: FAIL "a silent provider was covered for: 30 in_progress events". - go test -tags nogui -race -count=20 -run of this test and the TestQueuedStreamKeptAlive* tests: ok (358s). gofmt -l clean, go vet -tags nogui ./internal/gateway (darwin) clean. Not run: the full go test ./..., GOOS=linux/windows vet. Stopped because the disk dropped below 10G free.
… the vendor's arrivals by a sequence, not a clock that ticks every ~0.5ms on Windows (yetone#2249) TestQueuedStreamKeptAliveForASlot checked heard.Before(asked) on wall-clock times. On Windows time.Now() advances in ~0.5ms ticks, and the keepalive, the slot being freed and the vendor being asked fit in one, so heard == asked and the test failed with the right order. Loosening it to !asked.Before(heard) would pass a broken keepalive: with queued keepalives off, the first write (the reply) lands in the same ms as the ask, seen below. A shared counter (step) now gives each gateway write a place in order, taken before the write so nothing the agent does on hearing it can come earlier, and each vendor arrival and answer one too. The sibling checks use it: TestQueuedStreamKeptAliveForTheMinute's same heard-before-asked and TestQueuedStreamKeptAliveOnceSentToItsVendor's writeLog.within (its "kept alive before the vendor was asked" is the same strict compare on the one write that frees the slot). Run: - Reproduced by truncating both clocks to 100ms (forced equal ticks): old ForASlot FAIL 5/5 at queue_keepalive_test.go:387 with equal times, the issue's message; OnceSentToItsVendor FAIL 5/5 "the agent wasn't kept alive before the vendor was asked". With the fix and the same truncation, all TestQueuedStream* pass 3/3. - Broke the guarded behaviour by hand (queuedAlive := func() {} in gateway.go): all three tests FAIL at the body check; with the body's ": keepalive" check also dropped, each FAILs at the new order check (ForASlot :433, ForTheMinute :304, OnceSentToItsVendor :368). - go test -tags nogui -race -count=20 -run of these and TestTranslatedSilenceStillTimesOut: ok (358s). gofmt -l clean, go vet -tags nogui ./internal/gateway (darwin) clean. Not run: the full go test ./..., GOOS=linux/windows vet, the Windows box. Stopped because the disk dropped below 10G free.
…ning none alone and asks no classifier, so its pick and what went out agree (yetone#2247, sryimnoob123) The classifier's pick was recorded but never applied when the agent asked for none, costing a classifier call per turn. The outer effort in the routing record is what the agent asked; the effort sent is tries[].effort on the try marked picked:true, which the groups help now says. Thanks to ggbdpq for the reproduction. Run at this head (on 6485056e1, with the yetone#2245/yetone#2246/yetone#2247 fixes together): gofmt -l clean; go vet ./... for darwin and windows, and linux -tags nogui; the full go test ./... in a sandbox HOME, all green. The new test fails on the old code and passes with the fix.
…er group's (yetone#2246, Gaudi-XIX) Each candidate now carries the firstToken of the innermost group that sets one; a candidate with none falls back to the requested group's value. TestNestedGroupFirstToken. Run at this head (on 6485056e1, with the yetone#2245/yetone#2246/yetone#2247 fixes together): gofmt -l clean; go vet ./... for darwin and windows, and linux -tags nogui; the full go test ./... in a sandbox HOME, all green. The new test fails on the old code and passes with the fix.
… Settings › Codex subagents on any model, or to start a new thread when it is on (yetone#2245, qikairo7) TestSealedTaskNamesSubagentsOnAnyModel. Run at this head (on 6485056e1, with the yetone#2245/yetone#2246/yetone#2247 fixes together): gofmt -l clean; go vet ./... for darwin and windows, and linux -tags nogui; the full go test ./... in a sandbox HOME, all green. The new test fails on the old code and passes with the fix.
…ot on 2026-10-15, so main is green in UTC (yetone#1591) 3c4b4dc's TestCodexSubagentDetailsReachSessionAPIs held usage's clock at 2026-10-15 12:00 local and wrote the rollouts then, but /api/sessions/overview reads sessions.StatsFor, whose 7-day window ends at the real today. On CI (UTC, still 10-14) the fixture day was tomorrow and the child was left out: "overview token top omitted the active Codex child", red on macOS and ubuntu at 84f14e5 and aae49d0. In UTC+8 it passed, and from 10-23 it would have failed everywhere. The test now holds the clock at time.Now(). What was run, at origin/main 8d2a4a7: - before: TZ=UTC go test -run TestCodexSubagentDetailsReachSessionAPIs ./internal/gui fails as on CI; Asia/Shanghai passes. - after: the test passes under TZ UTC, Asia/Shanghai, America/Los_Angeles and Pacific/Kiritimati; go test -tags nogui ./internal/gui (local TZ and UTC) and ./internal/sessions (UTC): ok. go vet ./internal/gui: clean. - Siblings: no other internal/gui test holds a fixed date and reads /api/sessions. Not run: the rest of ./... (test-only change in one file).
…e open issue, not only in magpie-releases' log (yetone#2122, KaiZe on Discord) magpie-releases writes the Chinese notes with a DeepSeek call. Its balance ran out, so v0.1.1233 to v0.1.1242 shipped English only, and Chinese apps showed English notes until a user reported it. The failure was only a warning in a log nobody reads. mirror-release now checks notes.md for the <!-- lang:zh --> marker. When it is missing, the job comments on the open "Release notes without Chinese" issue, or files that issue if none is open. If gh fails, the step only warns, so the release itself never fails here. What was run: the step's script against a fake gh in four cases (Chinese present, no issue open, issue already open, gh failing); the real `gh issue list` query, read-only; a YAML parse of the workflow. Not run: a real release (the next tag will run it). No Go code changed.
…ith a gateway key, as Origin null does (yetone#2256, ggbdpq) What changed - corsGuard refuses (403) an http(s) Origin at a subdomain of localhost unless the call carries an enabled gateway key (localhostSubdomain), the rule Origin "null" got in yetone#2242. foreignPage still passes it, so a page there that holds a key keeps working; neither gets CORS headers. - Why: Chrome and Firefox resolve *.localhost to loopback themselves, but Safari and other WebKit views hand the name to the system resolver, which before macOS 26 asks the network's DNS (WebKit bug 160504, closed MOVED: "implemented in OS frameworks below WebKit", 2025-09-19; fails on macOS 15.7). A captive portal's or hostile network's resolver serves such a page from anywhere, and its "simple" text/plain POST spent subscriptions with no key. - tauri.localhost and wails.localhost stay keyless on Windows only, where Tauri's and Wails' webviews serve themselves at those names. On macOS and Linux those apps use tauri:// and wails://, so the two names are any page's there, and a hostile resolver answers them as readily as any other (the residual the report left open). webviewOS (runtime.GOOS) lets the test be each system. - The same page also reached the gateway rebound: at evil.localhost:3425 once the name turns to 127.0.0.1, its GET carries no Origin, and lanGuard's ownHost counted every *.localhost Host as this computer, so GET /v1/models and /v1/magpie/quotas answered 200. ownHost no longer counts a subdomain of localhost (localhost itself and docker.for.mac.localhost still count); a browser at one needs a key. - TestForeignPagesRefused's tauri.localhost row now sends a key, and TestReboundPageRefused expects wails.localhost refused. - docs/subsystems/gateway-routing.md says both. Ran - TestLocalhostSubdomainNeedsAKey (darwin, linux and windows as webviewOS; six routes; cross-origin and rebound; the fake vendor never reached): fails on origin/main 0050140 (76 errors, e.g. "POST /v1/chat/completions from http://evil.localhost with no key: 200"), passes with the change. - Broken by hand, each red: ownHost counting *.localhost again (the rebound GETs 200); localhostSubdomain always false; the Windows-only exception dropped (tauri.localhost answered keyless on darwin). - The cors, rebind, LAN, usage-ingest and allow-list tests: ok. - gofmt -l clean; go vet ./..., GOOS=windows go vet ./..., GOOS=linux go vet -tags nogui ./... clean. - go test -p 4 ./... (macOS): ok in every package but internal/agent and internal/gateway, which hit the 10m package timeout under load ~30 with no test hung (the running test 1-2s old); rerun with -timeout 40m: gateway ok 551s, agent ok 426s. Not tried: a real Safari on macOS 15 against a resolver that answers *.localhost; Tauri or Wails apps on Windows. Co-authored-by: ggbdpq <ggbdpq@gmail.com>
…own Sink, not only the group the agent named (yetone#2255, ggbdpq) What changed - planLevel applies a nested group's own Sink (sinkOwn) to its own candidates and their trace rows at both places it plans one: the weighed units (Smart, usage, pace, weight) and the ordered walk. A key or account of that group rate limited with quota left goes behind its group's others, within that group's place, as it plans by its own routing (yetone#576) and rules. The group the agent asked for still sinks the whole after, as before. sinks() still leaves out rotate, weight and manual. - Why: only planGroup read Sink, for the group asked for; an inner group's switch did nothing, and its rate-limited account went back to the front as soon as its rest ended. - provider/group.go's comment and gateway-routing.md's Sink row say so. Ran - TestNestedGroupSinksItsOwnOrder (outer ordered, smart and by weight; three levels deep with the middle group's Sink and with the innermost's; inner Sink off; the outer's Sink alone): fails on origin/main 0050140 ("order [a b x], want b then a"; "mid's Sink: order [a b c y], want [b c a y]"), passes with the change. - Broken by hand, each red: sinkOwn never sinking (all subtests); only the ordered path left unsunk (outer ordered and deeper). - TestGroupRateLimitedSinks, TestRateLimitedSinksToTheBack (-count=3), and -run 'Sink|Nested|OwnRouting|Weighted|Group': ok. - gofmt -l clean; go vet ./..., GOOS=windows go vet ./..., GOOS=linux go vet -tags nogui ./... clean. - go test -p 4 ./... (macOS): ok in every package but internal/agent and internal/gateway, which hit the 10m package timeout under load ~30 with no test hung (the running test 1-2s old); rerun with -timeout 40m: gateway ok 551s, agent ok 426s. Not tried: a real nested pool with live accounts. The report's sibling, a nested group's Affinity read only from the outer group, is left for its own issue. Co-authored-by: ggbdpq <ggbdpq@gmail.com>
… repo's dist branch, so a reviewed plugin no longer waits unmerged for an owner's npm 2FA (magpie-community/plugins#84) npm's Trusted Publishing can't create a package, so a new plugin's first version needs an owner's 2FA, and reviewed PRs sat unmerged for it. A trust made after 2026-09-03 also allows stage publish only, which is why npm refused zed 0.1.7 and zen-free 0.1.7. The plugins repo now packs every merged version onto its `dist` branch (scripts/dist.mjs, magpie-community/plugins f5308cd): tgz/<file> and an index.json with each version's sha512, manifest and README, with no credentials. magpie still asks npm first. Only when `bun add` fails for a @magpie-community package at latest or an exact version, and not because of the user's minimumReleaseAge (that hold is kept), does installDist fetch the tarball. It refuses the tarball unless its sha512 is the index's, keeps it in plugins/dist and runs `bun add ./dist/<file>`. A later npm install replaces it, and pruneDist removes tarballs package.json no longer names. npmAsk (market version, Update, the hourly check) takes dist's latest when npm errors or is behind, and Readme falls back to dist's. The branch is read through jsDelivr when the China mirror switch is on, else raw.githubusercontent.com. installMu is now held for the whole install, fallback included. Run at this head (on origin/main 8d2a4a7): - go test ./... -count=1: all ok but internal/gateway, which hit the 600s default timeout inside a file rename. The disk was at 5.3 GB free and the load around 22. Rerun alone with -timeout 30m: ok in 559.7s. - gofmt -l clean. GOOS=windows go vet ./... ok. GOOS=linux go vet ok on every magpie package that doesn't import Wails (its Linux files need cgo, which can't be cross-compiled from macOS). - New tests, with real Bun 1.4.2 against a fake npm and a fake dist: TestDistInstallsAPackageNPMDoesntHave, TestDistServesAVersionNPMRefused, TestDistRefusesATarballNotItsIndexs and TestDistWanted. TestUpdateBunHoldsBackSaysWhy now has dist holding the held version too. - Broken by hand, each went red: no fallback, no sha512 check, no prune, no dist version in npmAsk, a loose file regexp, and no minAge guard. - The real dist branch serves index.json and zed's 0.1.7 tarball from raw (200). jsDelivr's @dist answered 200, cold in ~20s. - Real use, sandbox HOME, live npm and live dist: Info said zed 0.1.7 (npm has 0.1.6), and Add@0.1.7 installed it from dist, package.json naming ./dist/magpie-community-opencode-zed-auth-0.1.7.tgz. Not tried: the GUI's Add on the live branch, or the China mirror path.
…odex's compaction_trigger as sent and its sealed compaction goes back unchanged; a checkpoint stays with its writer (yetone#1099) A new per-provider option, off by default. With it on, Codex's compaction_trigger reaches the provider as Codex sent it, and the provider's encrypted compaction item goes back to Codex unchanged instead of magpie's magpie1: summary. Off, nothing changes. Where it can be on (provider.CarriesCompaction): a provider with a Responses API (Sub2API and other OpenAI relays, a remote magpie), a ChatGPT (codex) account, and the built-in GitHub Copilot on its GPT Responses path. Copilot's Claude models are asked on chat completions, so they still get magpie's summary (CompactsOn). A group compacts natively only when every member does. No silent downgrade: - A turn carrying a sealed compaction goes only to the key or account that wrote it (compactions.json beside affinity.json, 0600, 1024 entries / 30 days). With none left, Codex gets a 400 saying the conversation was compacted natively on <who>, not a summarised or dropped context. - A provider whose request would have to be translated (not relayed) is refused with a 400 instead of losing the item. - The upstream's own refusal goes back to Codex as it came, one call. - base_instructions and Codex's identity are untouched. Turning it on: - GUI: the provider editor's Compaction row, "Codex compacts on this provider" (shown only where the wire can carry it; zh, zh-TW, ja, de). - CLI: magpie provider set <id> compaction=native|magpie. A provider that can't carry it is refused. Run on origin/main 0050140, then rebased onto 84f14e5 (no file in common) and reran gofmt, vet and go test for gateway, gui, provider, cli, usage and sessions: all ok. Rebased again onto 8d2a4a7 (gateway.go and codex_backend.go changed on both sides, merged clean) and reran gofmt, vet (darwin, linux nogui, windows) and go test for gateway, cli, provider and gui: all ok. Last rebased onto 7a3fed6 (only gateway-routing.md shared, merged clean): gofmt, vet on all three OSes, and go test -run for the native compaction, sink, CORS, rebind and origin tests in gateway plus the provider tests: ok. - gofmt -l: clean. go vet darwin, GOOS=linux -tags nogui, GOOS=windows: clean. - set -o pipefail; go test -timeout 40m ./...: every package ok but internal/provider's TestCopilotSecretToolUsername (copilot_cli_test.go:93 token "", 10.05s), the open load flake yetone#1627; it passes alone -count=5 and this change doesn't touch it. - GUI (node --test, chromium + webkit): the provider-editor files, 946 pass, 0 fail; the new provider-native-compaction.test.cjs runs 5 languages at 1100 and 420 (60 cases). - New Go tests: TestNativeCompactionPassesTheTrigger, ...RefusalGoesBack, ...StaysWithItsWriter, ...Copilot (gateway), TestCarriesCompaction, TestSetNativeCompaction (provider), TestProviderSaveNativeCompaction (gui), TestProviderCompactionPair (cli). Broken by hand, each red, then restored: 1. codexInputAs dropping the trigger even when native: all 4 gateway tests fail. 2. checkpoint filter off: StaysWithItsWriter fails. 3. forced relay off: all 4 gateway tests fail (the web_search tool sends the turn to translation). 4. GUI save sending nativeCompaction false: the GUI test's first case fails. Not tried: any real upstream (Sub2API, ChatGPT, Copilot). Copilot may refuse a compacted history holding web_search items; that refusal would go back to Codex as-is.
…tile, and the tooltip says today's total (ylorn on Discord) ylorn (Hyprland, 0.1.1241): the StatusNotifierItem's IconPixmap was the coloured app icon on an opaque white tile, which clashed with a bar's monochrome icons and became a solid block on bars that tint icons by alpha; the tooltip said only "magpie". - Linux now gets the macOS menu bar's template bird (tray.png) in one colour on transparency (trayIconFor / linuxTrayIcon, trayglyph.go): light, or dark when xdg-desktop-portal's org.freedesktop.appearance color-scheme is 2 (prefer light); it is set again on the portal's SettingChanged (watchTrayScheme, own D-Bus connection). An unreadable portal counts as no preference (light). No IconName is set: a host that resolves a -symbolic name but draws it untinted would show a black bird on a dark bar with no pixmap fallback. macOS keeps its template icon, Windows the coloured app icon. - With no cards in the menu bar the tooltip is "magpie" over one line of today's total, as the panel's Usage tab sums it (trayIdleTip: all tokens, cost in Settings' currency, requests), refreshed with the cards' loop. Cards picked: unchanged. - The menu is unchanged (Open / version / Quit): no info lines. The panel one click away has the models and usage, and the menu is shared with Windows and macOS. Run (sandbox HOME, pinned GOMODCACHE, shared GOCACHE), on origin/main 21aa402: - TestTrayIconPerOS fails with Linux given appIcon back ("linux: (0,0)-(64,64), want the menu bar bird's (0,0)-(44,44)"); TestTrayIdleTip fails with the tooltip back to plain "magpie". Both pass restored, with TestPortalLight, TestTrayTotals, TestTrayIconSize, TestSNITipIsSafeMarkup. - gofmt -l clean; go vet ./... (darwin), GOOS=windows go vet ./..., GOOS=linux go vet -tags nogui ./... clean; GOOS=windows go build and GOOS=linux go build -tags nogui OK. - traytip_linux.go (linux && !nogui, cgo) can't be vetted from macOS: the two new functions were copied into a darwin build and vetted there. - go test -p 4 -timeout 40m ./...: exit 0, 53 ok, no FAIL. - GUI: tray-*.test.cjs (Playwright 1.62.0): 112/112 pass. Not tried on a real Linux bar (the Windows box with its WSL2/Omarchy VM was unreachable over ssh): the pixmap, its colour on a light/dark scheme and the tooltip on waybar/Quickshell/KDE are unverified on hardware.
…ay's observer point (P3)
P3 of docs/subsystems/plugin-runtime.md opens the gateway to plugins. The
observer point is first: it only reads, so it carries the least risk.
With plugins/otel off, the gateway exports nothing and does no observing
work. It keeps no reply whole and adds no request context.
- usage.Observer:
- Methods: Serve, Bodies, Begin(Request) Watch and Ledger(Record). Watch
has Attempt and End.
- Observe(o) registers an observer until its remove is called.
- ServeObservers is counted, and only Server.ListenAndServe calls it.
- ObservedBodies takes the most any observer wants.
- Record.SkipOTel becomes Watched, set by appendUsage for a request an
observer watched.
- The gateway's otel.go becomes observe.go:
- observeRequest asks each observer through Begin.
- attempt and end call each Watch. Bodies are added only when an
observer asks for them, and the whole reply only for WholeBodies.
- internal/otlp: the exporter (otel*.go) moves out of internal/usage, and
the gateway's per-request span state moves into request.go. otlp.New()
is an Exporter, a usage.Observer. What it sends is unchanged.
- plugins/otel:
- Apply registers otlp.New() and disposes of it when turned off.
- InUse is 1 while settings.OTelExport says to send. A user who exports
only through MAGPIE_OTEL_* keeps the plugin on under Essentials.
- pluginrt.KeepFresh: the process serving the gateway refreshes every 2s
when plugins.yml or settings.json changes. A switch made in the window or
the CLI reaches a running gateway, or a headless serve, without a restart.
- docs: plugin-runtime.md gets a P3 section, a gateway runtime path and an
updated failure section; reference.md says the export is the otel plugin.
Run at this tree on origin/main 82f48f0 (rebased onto 52ee847 after,
which touches only the Linux tray icon):
- gofmt -l is clean. go vet passes on darwin, GOOS=linux -tags nogui and
GOOS=windows.
- go test -count=1 -timeout 25m ./...: ok, 55 packages, none failed.
- New tests:
- usage: TestObserverServesWithTheGateway and TestObserverLedgerAndBodies.
- gateway: TestObserverPoint, covering unwatched, no bodies, captured,
whole and whole streamed.
- plugins/otel: TestOnExportsOffSendsNothing (on, off, on, with a real
collector) and TestInUseByEnvironment.
- pluginrt: TestKeepFreshFollowsPluginsYml.
- The OTel tests moved with their code to internal/otlp.
Broken by hand, each red, then restored:
- Apply without Observe, and without its dispose.
- KeepFresh refreshing only once.
- InUse reading settings.json only.
- Ledger keeping Watched rows: 3 gateway OTel tests red.
- ObservedBodies taking the last observer's answer.
- Observe not serving a late registration.
- appendUsage not setting Watched.
- Whole bodies never kept.
- Bodies always on with no observer asking.
…loaded Mac's suite, not "token """ after two 5s waits (yetone#1627) The cause: the test wrote its fake secret-tool with os.WriteFile, so its first exec paid macOS's check of a newly written program (0.27s here at load ~10, several seconds while a go test ./... starts its binaries, as testenv.Program's own doc records). That ran into copilotSecretWait (5s), which the happy path was held to: both lookups (account, then username) were killed and the token read as "". Not a product bug from this cause: copilotSecretTool runs only on Linux, against the user's installed secret-tool, which has no such check. The 5s bound is for a keyring prompt nobody answers (yetone#723) and stays. The fix, in the test only: - both fakes are testenv.Programs (a file that has already run once); - the lookups that answer get copilotSecretWait = 10 minutes, since which attribute is asked is no question of time; only the keyring that never answers keeps 200ms; - that case's bound moves from 10s to 30s: bounded it takes 2.4s at most unloaded, unbounded 60s, so a loaded runner has room on both sides. Run at the reviewed head (macOS, load ~9-15): - Injected delay (sleep 6 at the top of the fake): the old test fails with copilot_cli_test.go:94 token "", want the one under username (12.01s); the new one passes (27s). Removing only the 10-minute wait by hand, with the delay, goes red again. - Broken by hand in copilot_cli.go: asking "account" only fails the test (token ""), and dropping the timeout fails it on the hang case (63s). - go test -count=20 -cpu 1,4 -run 'TestCopilotSecretToolUsername$|TestCopilotCLI' ./internal/provider: ok. - gofmt -l: clean. go vet -tags nogui for darwin, windows, linux: clean. - go test -p 4 -timeout 40m ./... under a sandbox HOME: 53 packages ok, no FAIL.
… moves onto that model, not "Same as main model" (yetone#2257, luokinging) A Fable tier set to GPT-6.1 Sol read "Same as main model" once the main model was switched to Sol, and the next main-model move took it with it. On a move of the main model, a tier equal to the new main model was kept as its own only when it equalled what it would follow there. Fable follows nothing on a model that is no Fable, and haiku follows a Claude model on its Haiku, so neither was marked: Get then hid the tier as following, and the next move rewrote it. The new main model itself now counts as the tier's own model too, for every tier and both routes (magpie and Claude Code's /model). GUI, TUI and CLI all read the same Field. A user already caught by it sees "Same as main model" and sets the tier again once; that state can't be told from a tier that follows. TestClaudeFableTierOnClaude modelled 0.1.1229's wiring with fable=Sol written before the main model moved onto Sol, which is this issue's own case; its fixture now writes it after, as 0.1.1229 did. Run: - TestClaudeOwnTierOnTheNewMainModelStays (every tier, two own models, magpie and /model): on the old code fable fails for both models and routes, and haiku given Opus fails ("haiku reads \"\""); passes now. - CLI in a sandbox HOME/CLAUDE_CONFIG_DIR, old and new binary: main -> k2.8, fable -> Sol, main -> Sol, main -> GLM. Old: fable gone after the move onto Sol, no ANTHROPIC_DEFAULT_FABLE_MODEL after GLM. New: fable Sol after both, env ANTHROPIC_DEFAULT_FABLE_MODEL=oa/gpt-6.1-sol. - go test -tags nogui ./internal/agent/ ./internal/tui/ ./internal/cli/: ok. gofmt -l; go vet darwin, GOOS=linux -tags nogui, GOOS=windows: clean. Not tried: the GUI by hand (no asset changed; it reads the same Field).
…efore adding (yetone#2251, ggbdpq) An import link could set chat=, responses= and anthropic= but not gemini=, though the editor and the TUI take a Gemini base URL; a link with only gemini= failed with "it needs chat=, responses= or anthropic=". ParseImport now reads gemini= under the same base-URL rules, and the error names it (ggbdpq's patch, with docs/reference.md and the en/zh docs rows). Its siblings that listed the three base URLs: - the GUI's import dialog warned "Your prompts and this key will go to ;" with no host for a gemini-only link: it names the Gemini host now; - the site's handoff page (site/public/import.html) left it out of "Sends to"; - the docs' link builder (en, zh, ja) had no Gemini field and didn't count it as a base URL; the ja docs lacked the table row, and all three said one of chat/responses/anthropic is required. Run: - TestParseImportGemini fails on the old importurl.go ("the link gives no base URL: it needs chat=, responses= or anthropic="), passes now; go test -run Import ./internal/provider/ ./internal/gui/: ok. - import-link-gemini.test.cjs: 20/20 (chromium+webkit, en/zh/zh-TW/ja/de, 1100 and 420 px); against the old app.js the warning reads "will go to ;" and it fails. - docs builder (en/zh/ja) and handoff page in Chromium from file://: a gemini-only builder gives magpie://import?name=…&gemini=…, no errors; the handoff page lists gem.example under Sends to. - gofmt -l; go vet darwin, GOOS=linux -tags nogui, GOOS=windows: clean. Not tried: a real magpie://import click through the OS handler. Co-authored-by: ggbdpq <ggbdpq@gmail.com>
…no Library write target, and nothing it already has is removed (yetone#2254, ggbdpq) After an agent is uninstalled, the Agents page shows "CLI not found" (d0fcb8b), but Library still offered it for skills, MCP servers, shared instructions and rtk, and a sync wrote into a folder nothing reads. - library.Targets() leaves out an agent whose CLI is missing (agent.CLIMissing: false for WSL, plugin and detect agents), so sync, the read view, market, instructions, skills and found servers all drop it at their one upstream. - Takes() refuses a gone agent by name, so `library mcp add ... agents=hermes` says "Hermes Agent's CLI isn't installed here; nothing reads its MCP servers until it is installed again" instead of "every agent already has it". - The guards that must still see a gone agent's files (projects' notGlobal for skills and MCP, instructions' own-file check) read everyTarget(), so a set kept in the agent's own file is still refused. - rtk: a gone agent isn't offered rtk; one that still has the hook is listed so the hook can be taken out. - Nothing a user wrote is removed: sync acts per target, so a gone agent's skills, servers and AGENTS.md stay as they are, and the client keeps agent ids it doesn't show. Run: - Sandbox HOME, real binaries: old wrote mcp_servers into a gone ~/.hermes/config.yaml; new refuses with the message above, exit 1. - New tests TestTargetsLeaveGoneAgentsOut, TestGoneAgentKeepsWhatItHas, TestRTKLeavesGoneAgentsOut; each broken by hand (Targets check, Takes refusal, everyTarget in instructions, rtk gate) goes red. - go test -tags nogui ./internal/library ./internal/tui ./internal/cli ./internal/gui: ok. Fixtures that relied on a gone CLI now place one (holdCLI, TestRTKHermesHome, TestRTKOffPath), and so do profile's and davsync's sandboxes, whose library syncs to claude and codex (TestOldProfiles, TestProfileCarriesLibrary, TestSyncLibrary went red in the full run without them). - gofmt -l clean; go vet -tags nogui darwin/linux/windows clean. - go test -tags nogui -p 4 ./... at the pushed head: see the last commit of this push. Not tried: the real GUI with an uninstalled agent on a live machine. Co-authored-by: ggbdpq <ggbdpq@gmail.com>
…own line in the agent's file names (yetone#2253, cnlove7777777-art) Usage -> Sessions' conversation said only "Assistant", so which model wrote a reply couldn't be told, though the agents' files name it. - sessions.Part gains model (omitempty), set on assistant parts only, from the part's own line: Claude Code's message.model (the model that answered; none for its "<synthetic>" lines), Codex's turn_context.model (the model the turn was sent to; a /model between turns moves the next turn), Pi's and omp's message.model, OpenCode's modelID and OpenCode 2's model.id. - Kimi Code (its model is only on a usage.record written after the reply), Cursor (no model in the transcript) and gateway transcripts (GatewayTurn.Model is the model asked for, not the one that answered) name none. Nothing is guessed from nearby requests. - The reader (sessions client.js) says it muted after the role, once a reply at its first part and again only where it changes inside the reply; the tooltip is "Model: <id>" (an existing string, translated). ledSaid takes the model as an argument, so the Usage page's per-request content, whose row already names its model, is unchanged. No layout change: one muted span in the existing header line, ellipsized at narrow widths. Run: - TestTranscriptModelClaude/Codex/Pi/OpenCodeV2/OpenCodeFiles: all five fail on the old content.go/transcript*.go, pass on the new. - sessions-talk-model.test.cjs: 20/20 (chromium+webkit, en/zh/zh-TW/ ja/de, 1100 and 420). Fails on the old app.js/client.js, and with the once-a-reply rule removed by hand (label every assistant part). - sessions-talk, sessions-export, gateway-conversations, usage-ledger-content, usage-ledger-detail GUI tests: all pass. - go test -tags nogui ./internal/sessions ./internal/gui ./internal/gateway: ok. gofmt -l clean; go vet -tags nogui darwin/ linux/windows clean. Full suite for this push (yetone#2257, yetone#2251, yetone#2254, yetone#2253) at 53677ed8d: go test -tags nogui -p 4 -timeout 40m ./... with pipefail: every package ok. (An earlier full run caught davsync's TestSyncLibrary and profile's TestOldProfiles/TestProfileCarriesLibrary relying on a gone CLI after yetone#2254; their sandboxes now have the CLIs, in yetone#2254's commit.) Not tried: the real app against a live Codex rollout (fixtures follow the lines the readers already parse for usage).
…n is ended by usage, or cut
Linka (Discord #feedback): Gemini's OpenAI-compatible API sends a whole
function call (its thought signature, the usage) in one chunk and the
finish_reason in the next. When the upstream hung up between the two,
magpie relayed a stream that ended with neither, and Pi threw the
complete call away ("Stream ended without finish_reason") and asked the
whole 160k turn again. On a Chat upstream only a failed read was a cut,
so a clean EOF there went on as a stream with no end.
That API counts the usage only on the generation's last chunk(s): the
Vertex fixture (testdata/vertex_openai_thoughts.sse) has it only beside
finish_reason, Linka's frames have it on the call chunk and the finish
chunk, and Google's forum and third-party docs say the same; no Google
doc states it. So for a Chat upstream that is Gemini's (geminiCompat,
saysItsEnd) and ended without a finish_reason:
- its last chunk carried usage: the generation was done and only the
finish_reason was lost. It is filled in (chatTail), "tool_calls" after
a call and "stop" otherwise: relayed, as one chunk before [DONE]
(written only if [DONE] hadn't gone); translated, as the reply's normal
end (tool_use for Claude Code), streamed and unstreamed. Recorded ok.
- no usage at its end: the reply was cut, and the client gets "the reply
ended before it was complete" (errEndedShort), as a Gemini stream's
finishReason is told (yetone#1643, yetone#1644): the relay's error chunk, the
translated stream's error, an unstreamed 502.
A failed read keeps its own "connection lost mid-reply". Other vendors'
Chat streams are unchanged.
Tests (gemini_compat_cut_test.go, Linka's frames): relayed, the call
with usage ends with finish_reason tool_calls then [DONE], text with
usage with stop, the call without usage with the error and no
finish_reason; a whole reply with or without [DONE] goes as it came
with one finish_reason; kimi-k3 with and without usage unchanged.
Translated: with usage one message_stop with stop_reason tool_use and
no error, streamed, and a 200 tool_use unstreamed; without usage one
error and no message_stop, and a 502 unstreamed.
Broken by hand, each red: the relay fill off (relay test: no
finish_reason), the streamed fill off (0 stops, 1 error), the
unstreamed fill off (502), usage ignored in chatTail.finish (every
no-usage case ended as whole, unstreamed 200).
Run: gofmt -l clean; GOOS=darwin/linux/windows go vet -tags nogui ./...
ok; go test -run 'TestGeminiCompat|EndedShort|TestStreamCut|
TestWellFormed|Sniff' ./internal/gateway ok.
Docs: gateway-routing.md says when the finish_reason is filled in.
Full suite: go test -tags nogui -timeout 45m ./... at base a61a6f9 + this, EXIT 1: 65 packages ok, internal/gateway red only in TestVisionWaitIsntTheDescribersTime (300ms describer limit under load, unrelated; passes -count=5 alone; filed yetone#2444). Rebased onto 1a16fc0, internal/gateway whole: ok.
…rom no answer, and is kept while the item is unchanged (yetone#2367) Dony2050 (yetone#2367) runs Codex with cli_auth_credentials_store = "auto" and no auth.json, so magpie reads "Codex Auth" through /usr/bin/security. Four faults: - One 10s timeout covered both of macOS's prompts and the user typing their password. A read they approved after 11-16s still failed. - A timeout and a refusal gave the same error. - A read was kept for 10s only. With "Allow" (once), background polls prompted again about every 30s. - Only a restart cleared the 10-minute failure cache. What changed: - Reading the secret is one ask per item at a time (askCodexKeychain). It runs in its own goroutine for up to 2 minutes (keychainAsk), outside codexKeychainMu, so no `security` run happens under a lock a reader needs. A reader waits at most 5s from when the ask started (keychainHold), then gets errKeychainWaiting. A request or a page never waits out the prompt. The ask's answer is kept when it lands, unless magpie kept a newer one after it started. - Each failure says what happened: - exit 44: no item. - exit 51 (errSecAuthFailed) or 128 (errSecUserCanceled): denied or cancelled (errKeychainRefused). Checked against SecBase.h. Exit 44 was also checked on a throwaway keychain in the scratchpad. - the 2 minutes ran out: no answer (errKeychainNoAnswer). - anything else: errKeychainUnread. None of them counts as signed out. - A successful read is kept while the item is unchanged. After 10s a reader runs Stat (find-generic-password without -w), which shows no prompt and returns only the item's mdat. The secret is read again only when the mdat changed. - Every write of the credential re-derives the cache: - codexStore.write keeps the date of what it wrote (keepCodexKeychain). - Switch, sign-out (deleteCodexLogin) and refresh forget a read (forgetCodexKeychain), so the next read checks the date. - forget keeps a refusal for its 10 minutes (yetone#2408). - resetCodexKeychain clears everything. Only tests use it. - The saved accounts' line in the GUI has a "Try again" link beside Remove when a read was refused, got no answer or is waiting (Exclusion.Retry). It posts /api/provider/keychain-retry, and RetryCodexKeychain reads once past the refusal. - The reason now says "Always Allow" lets /usr/bin/security read the item, so macOS stops asking each time Codex changes it. - Two new strings in zh, zh-TW, ja and de. - docs/subsystems/providers-accounts.md: the "Codex's credential store" row describes the above. Tests: - TestCodexKeychainAskedSeldom (rewritten) covers: - an unchanged item over aged and forgotten rounds asks once; - a changed item asks again; - a gone item is not found without asking; - a refusal is kept through forget. - TestCodexKeychainPromptWaitedFor: a held prompt gives "waiting" to the first and to 8 concurrent readers without blocking. Once answered, the read succeeds with one ask. - TestCodexKeychainRetry: Retry and Why for refused, no answer and no item, and RetryCodexKeychain. - TestSecurityKeychainExits (codex_keychain_ask_unix_test.go): a fake `security` built with testenv.Program covers: - a 2s answer with keychainWait = 1s succeeds; - Stat parses the mdat; - exits 44, 51, 128 and 1 map as above; - a hang gives no answer. - signedout_test: Retry is set only for an unread item. - GUI keychain-retry.test.cjs runs in chromium and webkit, in en and zh: - order Try again, Remove; - the POST body; - the line leaves after a click; - no link without retry. Broken by hand (each restored after): - Find bounded by keychainWait again: TestSecurityKeychainExits red ("a read the user answered late ... no answer in time"). - exit 51 not classed as refused: TestSecurityKeychainExits red ("Find, denied"). - Readers wait on the ask (no hold): TestCodexKeychainPromptWaitedFor hangs; the package timed out. - forget clears every read: TestCodexKeychainAskedSeldom red (4 asks). - mdat match ignored: AskedSeldom red (7 asks). - Cache never re-checked: AskedSeldom red (the changed item wasn't seen). - RetryCodexKeychain also deleting the failure cache: still green, because a fresh read asks past a refusal. That loop was removed as redundant. Not tried: - A real macOS prompt, or an item Codex itself wrote. Every test uses a fake `security`; the real keychain and ~/.codex were not touched. - Put and Remove are still bounded at 10s. Run at this head, rebased on origin/main 1a16fc0: - gofmt -l: clean. - go vet -tags nogui ./...: ok for GOOS darwin, linux and windows. - go test -race -count=2 on the keychain, signed-out and Codex sign-in tests in internal/provider: ok. - GUI (node --test, NODE_PATH playwright 1.62.0): keychain-retry, i18n-keys, saved-signedout-remove, gui-de, gui-ja, gui-zh-tw: 24/24 pass. Then rebased on origin/main 167df70 (TestSecurityKeychainAnswers' fake is a testenv.Program; no overlap in hunks): go vet -tags nogui for darwin, linux and windows on provider and gui, and go test -race ./internal/provider: ok. Full suite: go test -timeout 45m ./... in a sandbox HOME, set -o pipefail, at this tree on origin/main 1a16fc0: exit 0, 66 packages ok, no FAIL (gateway 1588s, agent 1497s, provider 510s). Rebased onto origin/main a6f908b (yetone#2440, yetone#2437 Gemini finish_reason; no overlap): go test -tags nogui ./internal/provider ./internal/gui ok.
…eld at the top 8f55c27 (yetone#2396) holds Settings' tabs at the view's top. "100% picked at the foot of the page" scrolled until the 100% button was within 60px of the view's top, which is now under that head, and the click timed out on "<div class="row-head set-head stuck"> intercepts pointer events" in both engines (20/2 on origin/main 1a16fc0). It stops 60px under the head's foot now, as a reader could still click it. Run: text-size 22/0 (chromium, webkit) at 1a16fc0 + this change.
…ndows rule left two Mac tests red on Windows CI Added under 10-15's criteria still missed: 64b0f3a (menu-scroll, fixed a61a6f9), 8f55c27 (text-size, fixed d3ca90a) and 94942f9 (agent-connect-no-models, cf21b71, yetone#2435) each ran the GUI files they chose; 6e2aa8e's Windows rule and the dsh tests fixed in ce1251c. Docs only; no code or test changed, nothing run.
The owner: "Provider表单太复杂了…有一些可以折叠在高级里面". Headers,
proxy, concurrency and RPM limits, prices, context and output windows,
native compaction and fallback now sit in an "Advanced" fold beside the
key and models. It opens on its own when any of them is set, keeps the
state it was left in across the editor's redraws (keyed by the provider
being edited), and a Save refused on a field inside it opens it and
focuses that field. A signed-in account's editor folds the same.
Strings in zh, zh-TW, ja and de.
Motion ("整个弹窗高度的改变也应该是丝滑的动画"): the fold grows and
shrinks on one spring-like curve, its rows fading in, its arrow turning,
and the dialog's height follows on the same curve both ways; clicked
again mid-way it turns back from where it is. Reduce motion: at once.
Every dialog holds its place: the element last pressed or focused stays
where it was while the dialog grows or shrinks (a relative top plus a
max-height cap), so the button under the pointer doesn't run away. Where
there is no room above, only a pressed button below the fields shortens
them through the cap; nothing a click does scrolls the fields
(models-fold, names-fold). An element mid-scale (a Copy button's pop) is
measured by its parent (key-copy). The names list holds its height while
drawNames rebuilds it, so WebKit doesn't clamp the editor's scroll mid
rebuild (provider-detect, "Ask on one API moved" 64→190).
Tests: provider-advanced-fold (new, 5 languages × 2 engines, 420px);
about 21 editor tests open Advanced before reaching a folded field;
balance-method and plugin-move find the fold by its class; detect-draft
allows 0.5px. appScriptBudget 1,250,000 and coreScriptBudget 1,426,000
(1,232,466 and 1,405,937 with this).
Run: go test ./internal/gui/ ok. Every GUI file (771) in a full run,
4 at a time; its reds, rerun alone at this head, both engines:
key-copy 8/0, models-fold 4/0, names-fold 8/0, balance-method 42/0,
provider-detect 4/0, plus provider-remove 16/0, plugin-move 8/0,
provider-detect-draft 20/0, provider-advanced-fold 50/0,
provider-add-models 4/0. Red on origin/main as well, filed: yetone#2435
(agent-connect-no-models, fixed in cf21b71), yetone#2439 (fonts), yetone#2443
(key-balance-card), yetone#2228 (quota-count-wrap, quota-name-translated),
yetone#1905 (routing-wb-restricted), yetone#2448 (s3-sync); mobile-web and
library-workbuddy-trust red only under load, noted on yetone#1544;
readable-focus and text-size fixed on main in 1a16fc0 and d3ca90a.
…etone#2447) EdwinDigital: in the tray panel, WorkBuddy's row said its models in words ("gpt-6.1-sol · Microsoft Foundry, claude-opus-5-5 · Microsoft Foundry +1"), and the words took the row: the name was squeezed to 10px at 550px and to nothing at 440px and below, or at any width once the pointer came onto the row and the model's logo eased in. The row's columns were 20px, a name in minmax(0, 1fr) and the words in auto, so the words claimed their whole width and the name gave way. Now the name's column is fit-content(60%): it keeps its own width, up to 60% of the row, and the words take what is left and cut first (.vt already ellipsizes). The words sit at the row's end (justify-self: end), so their effort bars and chevrons stay in one column down the list. It holds for every panel row with a summary (a model list, one model's label, import, launch, CLI not found), not WorkBuddy alone. Name width, WorkBuddy / Claude Desktop, before -> after, chromium and webkit the same: 550px 10.05 / 8.09 -> 76.05 / 103.92; 440px and 360px 0 / 0 -> 76.05 / 103.92; hovered the same after. Run at 0cad76c: - agent-panel-name-width.test.cjs (new): chromium and webkit; en, zh, zh-TW, ja and de; 550, 440 and 360px; hovered and not. Each name is whole, the words are after it and inside the row, right edges line up, the model lists are cut, and nothing scrolls sideways. 32/32 pass. With HEAD's app.css it fails 32/32 (name 10.05px at 550, 0 below). - GUI, every file that renders the panel or an agent row (101 files, 4-wide): # tests 1390, pass 1387, fail 3. The 3 reds also fail at HEAD without this change, run alone: key-balance-card chromium de (view-usage 570>560, yetone#2443) and usage-fold chromium en/zh (the view didn't scroll, 184 !== 180, yetone#2163). - The whole GUI suite (780 files) was not run. - gofmt -l clean; GOOS=darwin/linux/windows go vet -tags nogui ./... ok; go test -tags nogui -timeout 45m ./internal/gui/... ok (385.6s).
… line (yetone#2446) A passthrough stream cut mid-line got its error event written onto the half-written line, so the client read "event: error" as more of that line's data and never saw the error as an event. ggbdpq's patch (yetone#2446) tracks whether the last write ended a line (passthrough's write, and holdWriter.atLine for cutLoop) and puts a blank line, two newlines when mid-line, before the appended event. The same join could happen to the other event passthrough appends: the finish_reason filled in for a Gemini OpenAI-compatible reply that ended done (a6f908b). It now goes through the same separator (apart). The truncated pending event is still dispatched; SSE can't take it back, but the error now arrives as an event of its own. holdWriter's other appends (release, failTo) only follow keepalive comment lines, so they already start on a line. Tests: TestPassthroughStreamCutMidLine, TestPassthroughStreamEndsOnLineEnd, TestLoopCutMidLine (ggbdpq), TestGeminiCompatFinishAfterPartialLine. Broken by hand: apart returning "\n" always -> CutMidLine and GeminiCompatFinishAfterPartialLine red; the Gemini fill-in without the separator -> GeminiCompatFinishAfterPartialLine red; cutLoop's "\n\n" as "\n" -> TestLoopCutMidLine red. Restored, all green. gofmt -l clean; GOOS=darwin/linux/windows go vet -tags nogui ./... clean. Full suite: go test -tags nogui -timeout 45m ./... at base 0cad76c, 66 packages ok, 0 FAIL (gateway 1869s, provider 445s; sandbox HOME) Co-authored-by: ggbdpq <67929842+ggbdpq@users.noreply.github.com>
…etone#2445) B.ai answered a 380K request, under the model's 1M window, with 400 "Input token exceed the limit (request id: …)". No pattern in tooLongRe matched it, and quotaWords wants "exceeded", so retryable() was false and group/top's other three members were never asked (worksyang). tooLongRe now also matches "input token(s) (count) exceed(s|ed) (the) (max|maximum) limit". The refusal is taken as an overflow, so the chat loop's existing yetone#700 path asks the next member that may hold the request (withRoom, failOverflow). Nobody rests, another account of the same model isn't asked, and with no member left the agent is told it overflowed, to compact. paceWords still keeps rate limits worded with input tokens out, and a plain bad request still isn't asked of anyone else. System One's decision groups (decide_group.go, which the report cites) are unchanged: their requests are short decision text. Test: TestRelayInputCapGoesToTheNextMember uses the reporter's message verbatim. The JSON around it is not the reporter's; the issue shows only the message. Docs: gateway-routing.md failure paragraph + verification. Run: - Broken by hand, the new test went red each time: - pattern removed: group answered 400, member 2 never asked; - paceWords check removed: two rate phrasings taken as overflow; - retryable true for every 400: the plain bad request reached member 2. - TestRelayInputCap, TestEveryVendorsOverflow, TestOverflow*, TestContextOverflow*, TestOutOfBudget*: ok. - gofmt -l: clean. go vet -tags nogui ./... on darwin, linux, windows: ok. Full suite: go test -tags nogui -timeout 45m ./... at 0cad76c + this change, sandbox HOME: 66 packages ok, 0 FAIL (internal/gateway 1768s).
@G_Z_Y looked for Cursor CLI on the Agents page and took the "Cursor" row for the editor. The row is cursor-agent, so it is named Cursor CLI. What it keeps in ~/.cursor is the Cursor editor's too (MCP servers, skills, chats), so the Library and Sessions name it Cursor there (SharedName). Run at this head before the rebase onto 0f391a7: go test -tags nogui -count=1 -timeout 45m ./... under a sandbox HOME, every package ok; gofmt -l clean; go vet clean for darwin, linux (nogui) and windows; GUI files agent*, i18n*, gui-*, getting-started, library*, sess* and import-row in both engines: 1638/1638.
The editor makes ~/.cursor on its own, so the folder alone says nothing of cursor-agent (dirShared): cli-config.json or the command finds Cursor CLI. The Library still writes the MCP servers and skills the editor reads there when only the editor is here (SharedHere). Run at this head before the rebase onto 0f391a7: go test -tags nogui -count=1 -timeout 45m ./... under a sandbox HOME, every package ok; gofmt -l clean; go vet clean for darwin, linux (nogui) and windows; GUI files agent*, i18n*, gui-*, getting-started, library*, sess* and import-row in both engines: 1638/1638.
Cursor CLI updates itself and isn't on npm or Homebrew, so magpie shows its version only (@G_Z_Y on X saw none). Run at this head before the rebase onto 0f391a7: go test -tags nogui -count=1 -timeout 45m ./... under a sandbox HOME, every package ok; gofmt -l clean; go vet clean for darwin, linux (nogui) and windows; GUI files agent*, i18n*, gui-*, getting-started, library*, sess* and import-row in both engines: 1638/1638.
"Only Cursor's own models" beside "Can't connect" read as two things. Each of the four native-only rows now says it uses only its vendor's models, so magpie can't connect it, in every language, and the line wraps instead of being cut short. Run at this head before the rebase onto 0f391a7: go test -tags nogui -count=1 -timeout 45m ./... under a sandbox HOME, every package ok; gofmt -l clean; go vet clean for darwin, linux (nogui) and windows; GUI files agent*, i18n*, gui-*, getting-started, library*, sess* and import-row in both engines: 1638/1638.
…ounted out, as the plugin does (plugins#138) hheei (magpie-community/plugins#138): an account at 560 / 560 credits was ranked spent for deepseek-v4.1-flash too, which WorkBuddy serves at x0.00 and which draws no credits. The plugin was fixed in 0.1.23 (notModels) and the mover min is 0.1.24 already; the built-in, which users who haven't moved still run, had the same fault. wbQuota's personal Credits window and the enterprise window now pass through wbSpareFree, which scopes them (QuotaWindow.matches) to the models not marked Free in the provider's last fetched list (catalog.Live). With no list read, every model counts, as before. The 14018 "Credits exhausted" rest already rests only the paid model (PaidBesideFree). Run: - TestWorkBuddyCreditsSpareFreeModels (new): with no live list the free model reads 100%; with one, deepseek-v4.1-flash (either case) reads 0 while -sg (x0.03) and gpt-5.5 read 100. Broken by hand (wbSpareFree returning early): red, "deepseek-v4.1-flash: 100% used, want 0". - gofmt -l clean; go vet -tags nogui ./... for darwin, linux, windows: 0. - Full suite: go test -timeout 45m ./... in a sandbox HOME, twice: every package ok except internal/gui's TestPanelJoinsFullScreenSpaces ("the panel shown doesn't take the keyboard"), which is red the same way alone on a fresh origin/main be1030e worktree; filed as yetone#2450.
…#2449) TestPluginLoginInstallsItsCLIFirst went red on macOS CI (run 38798393584) with "not signed in after the install: []". cli's run() sets catalog.Changed = agent.SyncCatalog, and a sync left going from an earlier step holds the catalog (provider.Hold), plugins and their sign-ins among it, for up to a second. The host's "auth" event dropped that only through catalog.Touched, told in the background, so on a busy runner provider.Logins right after the sign-in read the snapshot taken before it. plugin.OnChangeNow registers hooks run before changed() or listed() returns, i.e. before the host's reply after the event is read; provider registers dropHeld there. Changed() is filememo.Forget + dropHeld. Reproduced by injecting a pause where SyncCatalog holds (patch kept outside the tree): without the fix red 3/3 with the CI message; with it ok -count=5. TestHeldGoesWithAPluginChange: red without the fix ("held from before the change: built 1 times, want 2"), green with it, also -race -count=20. Run: -count=20 -run TestPluginLoginInstallsItsCLIFirst ./internal/cli ok; internal/cli, internal/plugin, internal/provider whole ok; gofmt -l clean; go vet -tags nogui for darwin, linux, windows on the three ok. Full suite: go test -tags nogui -timeout 45m ./... at 7fefbc6 with both yetone#2449 commits (this one and the Settle fix after it) (sandbox HOME, macOS): exit 0, 66 ok, 18 no test files, 0 FAIL.
Settle's own Restart tells the OnChange hooks, and one that calls
Cached() started refreshProvidersInBackground after Settle's last
refreshing.Wait. callWithTimeout starts the host under WithoutCancel, so
it went on once Settle returned and spawned a host in the test's removed
HOME ("Module not found host-....js", uv_cwd) or in the next test's,
while that one signed in. provSettling keeps Cached and the loop's
continue branch from starting one while Settle runs.
TestSettleStartsNoRefresh: red without the fix ("Settle started 3
refreshes"), green with it, also -race -count=20. In go test -v
./internal/cli the stray host lines went from 7 to 0.
Run: internal/cli, internal/plugin, internal/provider whole ok; gofmt -l
clean; go vet -tags nogui for darwin, linux, windows ok.
Full suite: go test -tags nogui -timeout 45m ./... at 7fefbc6 with both yetone#2449 commits (bd4e9da7b's and this one's changes) (sandbox HOME, macOS): exit 0, 66 ok, 18 no test files, 0 FAIL.
…pacts (@wilsonxuuuu on X)
@wilsonxuuuu's pi, on gpt-6.1 through magpie, stopped on
"magpie API error (413): {...,"type":"api_error"}" at 114k of a 1M
window and never auto-compacted. The vendor's 413 was a body byte cap.
pi's overflow table (pi-ai utils/overflow.ts) knows a byte cap only as
request_too_large, Anthropic's type, which magpie already gave on the
Anthropic route but not on chat/responses. writeErrorSaid now types
every 413 request_too_large (Gemini keeps its own status shape). The
status stays 413 and the byte-limit hint stays; it is not turned into
context_length_exceeded, so Codex and Claude Code read it as before
(Codex reads code, not type). pi compacts once and retries once
(_overflowRecoveryAttempted), so a body that is still too big after
compacting stops instead of looping.
Run:
- pi 1.1.0's own isContextOverflow on the reporter's body: api_error
false, request_too_large true.
- TestPayloadTooLargeSaysRequestTooLargeToPi (an nginx 413 page from a
chat upstream) and the new Chat/Responses rows of
TestBodyRejectionErrorShapes fail with the gateway.go change reverted
by hand, pass with it.
- go test -tags nogui -count=1 -timeout 45m ./... under a sandbox HOME:
every package ok. gofmt clean; GOOS=linux and windows go vet of
internal/gateway clean.
Not tried: a real pi session hitting a real vendor 413.
…arch (yetone#2439) The font menu, session combo and row menus focus whichever item the pointer moves onto. When the search narrowed the list under a pointer that hadn't moved, the redrawn item under it got a mouseenter too, took the focus out of the search, and the next letters went nowhere. fonts.test.cjs went red whenever the list landed while the pointer sat over the menu. hoverFocus now remembers where the last mousemove was. A mouseenter there is a redraw's (UI Events fires mouseenter before its mousemove), so it leaves the focus where it is. A real move still focuses the item. The new fonts test hovers an item, then types "HarmonyOS" into the search. The focus has to stay in the field, and Enter has to pick HarmonyOS Sans SC. - Broken by hand: with the old `b.onmouseenter = () => b.focus()`, the test is red in chromium and webkit (the focus is on the item, and the typed text is cut short). - With the fix: green in both engines, 3 runs. Full suite: node --test --test-concurrency=4 internal/gui/tests/*.test.cjs (chromium and webkit) on a61a6f9 with all three of these commits' changes: 10655 tests, 10616 pass, 39 fail, all in 16 top-level tests. Each of their files was then run alone: - green alone on origin/main 0085714 and on this tree (load): import-link-gemini, market-have, plugin-page, readable-focus, text-size. - red alone on main too, filed or commented: - quota-count-wrap and quota-name-translated: yetone#2228 - s3-sync: yetone#2448 - usage-agents-hide: yetone#2276 - usage-checkin: yetone#2142 - usage-fold: yetone#2163 - tray-usages: yetone#2453, new After rebasing onto 0085714, these were run again on this tree, alone: - green: fonts, key-balance-card, plugin-move, i18n-keys. - still red, the same as main: quota-name-translated, quota-count-wrap, tray-usages. The full GUI run was not repeated after the rebase. go test ./internal/gui on 0085714 plus these changes, sandbox HOME: FAIL. The only failure is TestPanelJoinsFullScreenSpaces, which is yetone#2450 and is red on main too. TestCoreScriptBudget passes.
…rd (yetone#2443) At 560px, German's "Zurückgesetzt 19. Okt. 02:37 ·" was wider than the room left beside the other parts of the reset line. The span is white-space: nowrap, so it ran 10px past the card. key-balance-card's view-usage test caught it only in weeks when the date fell on a long German month and weekday, so it was red on main some days and not others. Each part of .quota-reset (and .quota-holds) is now white-space: normal and min-width: 0. A part goes whole onto its own line when it doesn't fit, and only a part wider than the card breaks between its words. The test now pins its weekly reset to the next 28 Sept 02:37 that is more than eight days ahead. That is the widest German date, so the test no longer depends on today's date. - Broken by hand: with the old CSS, the pinned test is red in chromium de (10px past the card). - With the fix: green in both engines, every language. Full suite: node --test --test-concurrency=4 internal/gui/tests/*.test.cjs (chromium and webkit) on a61a6f9 with all three of these commits' changes: 10655 tests, 10616 pass, 39 fail, all in 16 top-level tests. Each of their files was then run alone: - green alone on origin/main 0085714 and on this tree (load): import-link-gemini, market-have, plugin-page, readable-focus, text-size. - red alone on main too, filed or commented: - quota-count-wrap and quota-name-translated: yetone#2228 - s3-sync: yetone#2448 - usage-agents-hide: yetone#2276 - usage-checkin: yetone#2142 - usage-fold: yetone#2163 - tray-usages: yetone#2453, new After rebasing onto 0085714, these were run again on this tree, alone: - green: fonts, key-balance-card, plugin-move, i18n-keys. - still red, the same as main: quota-name-translated, quota-count-wrap, tray-usages. The full GUI run was not repeated after the rebase. go test ./internal/gui on 0085714 plus these changes, sandbox HOME: FAIL. The only failure is TestPanelJoinsFullScreenSpaces, which is yetone#2450 and is red on main too. TestCoreScriptBudget passes.
The redraw comes from renderProviders, for example when an account's allowance read lands (loginUsageOf). It drew a subscription's "Move to the plugin" field again from the provider alone. Midway through a move, the button then said "Move to the plugin" again and took presses: a second move could start while the first ran. A failure that came after the redraw was said into the old field, which was gone, so the user saw nothing. renderMove now keeps the running move's label, and a failure's reason, per provider in moveSaid. A redraw draws both again. A failure that lands after a redraw redraws the providers, so the reason shows in the field on screen. Found while chasing yetone#2431 (plugin-move's editor scrolls 23px in WebKit). That shift was not reproduced: 16 loaded WebKit runs were green, and so were injected late usage, late icons, a busy main thread and a forced chain. The findings are on the issue. This doesn't close yetone#2431. plugin-move.test.cjs now redraws mid-move and after a failure. - Broken by hand: without moveSaid it is red 4 of 4, both engines. The errors are "a redraw mid-move forgot the move" and "a redraw forgot why the move failed". - With the fix: 8 of 8 green, 3 runs. Review: a failure said is kept only while the move's state is the one it failed from, so a move that lands another way (the hourly hand-over) doesn't keep showing it. plugin-move*, fonts: 56/56 in both engines after. Full suite: node --test --test-concurrency=4 internal/gui/tests/*.test.cjs (chromium and webkit) on a61a6f9 with all three of these commits' changes: 10655 tests, 10616 pass, 39 fail, all in 16 top-level tests. Each of their files was then run alone: - green alone on origin/main 0085714 and on this tree (load): import-link-gemini, market-have, plugin-page, readable-focus, text-size. - red alone on main too, filed or commented: - quota-count-wrap and quota-name-translated: yetone#2228 - s3-sync: yetone#2448 - usage-agents-hide: yetone#2276 - usage-checkin: yetone#2142 - usage-fold: yetone#2163 - tray-usages: yetone#2453, new After rebasing onto 0085714, these were run again on this tree, alone: - green: fonts, key-balance-card, plugin-move, i18n-keys. - still red, the same as main: quota-name-translated, quota-count-wrap, tray-usages. The full GUI run was not repeated after the rebase. go test ./internal/gui on 0085714 plus these changes, sandbox HOME: FAIL. The only failure is TestPanelJoinsFullScreenSpaces, which is yetone#2450 and is red on main too. TestCoreScriptBudget passes.
… a meter (yetone#2451) tianxia3111 runs Command Code on the keyed preset with a plan's key. Its card showed the 5-hour and weekly windows as meters, but the month's credits only as dollars left ($5.27), with no share of the month. The cause: billing/credits leaves out the planId, so the key's card couldn't know the plan's monthly pool. The signed-in subscription's card gets that pool from billing/subscriptions. The key's card (planKeyWindows -> cmdKeyWindows) now asks billing/subscriptions with the same key for the plan. It then makes the month a "Credits" window beside the other two, through the same cmdQuotaOf the subscription's card uses ("$4.78 / $10.00" on a Go plan). - The plan id is kept per key for ten minutes, since billing/subscriptions takes 15-20 s at times. - The id read before still counts while the plan can't be read. - A key whose plan was never read keeps its two windows alone. - A pay-as-you-go key, with no windows, still gets no card. planWindows' request and status handling move into planBody, so that cmdKeyWindows can decode the reply itself; other vendors are unchanged. Run: - New TestCommandCodeKeyCardHasCredits uses the reporter's own billing/credits reply. Its billing/subscriptions reply has the shape the other Command Code tests use (individual-go-monthly), because the reporter's plan id isn't known. - With the new branch disabled by hand, the test went red: two windows, no plan. With it, green. - TestKeyQuotasKeepLast now answers billing/subscriptions with 503 and still sees its two windows. - gofmt; go vet -tags nogui for darwin, linux and windows. - Not tried against Command Code's real API: there is no plan key here. - Full suite (go test -timeout 45m ./..., at f1c91a9 plus this change): every package ok except internal/gui, red only on TestPanelJoinsFullScreenSpaces, which is yetone#2450 (macOS 26.2, red on main before this change).
…ne#2452) What changed - viewGroup drew every member and every rule and left the rest to the terminal. A group with more of them than the terminal has rows (a whole provider's thirty models at 80x24) scrolled the header and the highlight off the top, and j/k moved a pick no one could see. The library page (yetone#2220) and the agents page (yetone#2428, fed24aa) had the same disease; this was the last page with a cursor and no window. - groupWindow draws the rows around the pick, as ggbdpq's patch does: a pick among the models keeps the first rules below it, a pick among the rules keeps the models' tail above it, a short group is drawn whole. - groupRows counts every other line the page draws, including the pattern lines, the classifier line and the "none" line, which the patch's flat h-10 left out. With h-10 the page still ran three lines past the terminal and its header scrolled away. Run - New TestGroupViewKeepsTheRowPicked: thirty models; one model with 24 rules; both, with three patterns and an intent rule's classifier. Every pick at 24 and 40 rows must draw no more lines than a short group's page, keep "◉ magpie" on screen, and mark the pick's own numbered row. A tall terminal draws the whole group. - Broken by hand: old routing.go goes red (pick 0 draws 41 lines, 22 fit), the patch's h-10 goes red (25 lines), and leaving the patterns uncounted goes red (23). With the fix it's green. - Other cursor pages were audited (agents, providers, groups, library, sessions, pickers). All are windowed; only the group page had none. - gofmt -l clean; go vet -tags nogui for darwin, linux and windows. - Full suite: go test -timeout 45m ./... at f1c91a9: 65 packages ok, internal/gui red only in TestPanelJoinsFullScreenSpaces ("the panel shown doesn't take the keyboard"), the macOS 26.2 red filed as yetone#2450 and red on main without this change. Co-authored-by: ggbdpq <ggbdpq@gmail.com>
Fate asked on Discord to see what a single request on the Routing page sent
and got back, such as a Codex prediction. The bodies were captured already,
in the Gateway page's 40 recent calls and the opt-in request archive, but
nothing led there from a Routing request.
- gateway: a Call carries the Routing request it was (Call.Route = the
trace's Route.ID), set where serve, the Codex backend and a refused title
record it. Server.RouteCall finds it in the ring. A route id is not a
call id. A route names its archive copy (Route.Archive =
call.archiveName()), set only when the archive was on, so it reaches the
saved history too.
- gui: GET /api/gateway/call takes ?route= besides ?id=. It is the same
handler, the same no-store, the same key guard and the same 404 once the
ring has let the call go.
- Routing page: the story's head has "Request and response". It fetches
only when pressed, and shows the masked bodies through callBodyPanel (an
SSE reply reads as its events). A request the ring no longer has shows
the archive's copy through archivePanel when the route names one.
Otherwise it says the last 40 are kept until magpie quits and how to turn
the archive on. Nothing more is captured or kept than before.
- The head now wraps. At narrow widths its buttons ran past the story
("Back to live" was cut off at 440px).
- The two button words live in i18n.js: i18n-keys' "a plugin's words are
its own" found them inside core strings.
- docs/subsystems/gui-shell.md says how a route reaches its bodies.
Tests:
- TestRoutingRequestOpensItsBodies (new): each of two routes gets its own
bodies; route 0 and an unknown route get 404. Red without
`call.Route = tr.ID` ("route …: 404").
- TestArchiveWholeBodies now checks that the route names the archive. Red
without the t.Archive line (`route names archive ""`).
- routing-bodies.test.cjs (new), chromium and webkit at 440px, en, zh,
zh-TW, ja and de, 10/10. It covers the ring's copy read only when asked,
hiding, another request closing it, the archive's copy, the
how-to-keep note, nothing past the story and no left border.
- Red with the client asking ?id= and with the head's wrap removed.
- routing-request-metrics.test.cjs: with the head wrapped, its 480px
picker clicks in WebKit landed on the footer. Playwright's WebKit places
elements in the scrolled view hundreds of px lower than
getBoundingClientRect, which elementFromPoint agrees with. The test now
clicks where the page shows them. 5 runs of both engines, 20/20.
Run in a sandbox HOME at f1c91a9 plus this change:
- gofmt -l: clean. go vet -tags nogui: darwin, linux and windows ok.
- GUI: routing-*, panel-routing*, recent-call*, i18n-keys and
header-relang in both engines: 1079 tests, 1079 pass (a first run had
2 reds, both fixed above).
- go test ./internal/gui after the i18n move (TestPanelJoinsFullScreenSpaces
skipped): ok.
Full suite: `go test -timeout 45m ./...` gave 65 ok, 1 FAIL. The FAIL is
internal/gui TestPanelJoinsFullScreenSpaces ("the panel shown doesn't take
the keyboard"), red 3/3 alone here and red the same on origin/main
1d227a7 just fetched. It is filed as yetone#2450.
…t at a time (@NetRozee on X)
@NetRozee on X: "when I add a thousand keys for a custom endpoint magpie
just froze or crash by itself".
Cause. The Providers page asked, for each key, what it rests by
(RestKey), and RestKey listed every key on again (KeysOn), which itself
looked along the list for each key: cubic in the keys. /api/providers
and every /api/keys/* answer, an import too, are drawn through it. The
gateway asked, for each key of every request, whether it serves the
model (Serves), and each asking read the provider's lists again (a
filememo stat, and a read while the file is new) and looked along each
model's key ids. fetchPerKey looked along the lists for each key and
model the same way.
Fix.
- KeysOn drops repeats by a set.
- RestKeys finds once whether several keys are on; providerInfo uses it.
- Serving(model) reads the lists once and answers for each key from sets
(keyLists, made again when the lists are read anew); perKeyBarred and
the test pick use it. Serves is Serving for one key.
- fetchPerKey keeps each model's keys as a set and indexes the old list
by key once; a model kept for a failed key is kept once, as before.
- KeysListed and ListsRead count listings and list reads for the tests.
Behaviour is otherwise the same.
Measured (go test in a sandbox HOME, a custom provider, 50 models):
1000 keys 5000 keys
providerInfo 1.54s -> 7.5ms 2m42.9s -> 18.3ms
providersState 1.65s -> 110ms 2m40.3s -> 146ms
POST /api/keys/import 1.64s -> 82ms 2m44.7s -> 288ms
of one more key
gateway perKey 100.9ms -> 8.2ms (old not run) -> 40ms
(each request)
Tests (counts, not time), each red on the old code by hand:
- TestProviderInfoListsKeysOnce: old 1001 listings for 1000 keys, 11 for
10; new 0 and 0.
- TestPerKeyReadsListsOnce: old 1001 list reads for 1000 keys, 11 for
10; new 1 and 1.
- TestServingReadsListsOnce, TestRestKeysIsRestKey,
TestKeysOnDropsRepeats: same answers as before.
And the vendor reads. KeyBalances, PlanQuotas and remoteKeyCards started a goroutine
for each key and asked the vendor with all of them at once: a thousand
keys were a thousand requests to one relay together, each holding a
connection and its body.
Fix. keySlots holds each provider's reads to keyReadsAtOnce (8); other
providers' keys are read beside them. A read whose context is done goes
on without a slot and fails at once, as before.
Tests:
- TestKeyReadsEightAtOnce: a Command Code provider of 31 keys and a
remote magpie of 31 keys behind a server that holds every request.
Old code (the slots taken out by hand): 31 at once for balances, plans
and remote limits; new: 8, every card still there.
- TestKeySlots: a done context takes no slot; another provider's keys
have their own.
Run: gofmt -l (clean); GOOS=linux and GOOS=windows go vet -tags nogui
./... (clean); go test -tags nogui -count=1 -timeout 45m ./... in a sandbox HOME (GOCACHE, GOMODCACHE pinned) on af60154 with this series: 66 packages ok, 18 without tests, no FAIL (gateway 468.5s, provider 215.9s, gui 122.8s).
… on X) @NetRozee on X: a custom endpoint given a thousand keys froze magpie. Cause. The editor's Advanced > Proxy drew a line for every key, each with a Provider's proxy | Direct | Custom control. Each control, once in the page, looked up its place among every .segs in the editor and slid its thumb (segsPlace, slide): quadratic, with a layout read each. The Accounts list above it already folded (keyFold); this list didn't. Fix. proxyLines folds a provider's keys as keyFold does: past KEYS_FOLD, the first KEYS_FIRST and those with a proxy of their own, then All N keys; opened, a filter (keyMatches, as the Accounts list's) over KEYS_PAGE lines and how many more, and Show fewer. Typing draws the lines again under the filter, which stays in the page (yetone#1055). A key not drawn keeps its proxy: Save posts every key's own, drawn or not. Subscription accounts' lines are unchanged. No new strings. Measured (Playwright, the editor opened on a provider of N keys): 1000 keys 5000 keys Chromium 1067ms -> 100ms 55.6s -> 93ms WebKit 4809ms -> 68ms 203s -> 81ms DOM 9388 -> 434 nodes 45388 -> 434 nodes Tests: provider-many-keys-proxy.test.cjs (new, en and zh, Chromium and WebKit): fails in all four on the old app.js (no All 300 keys). With account-proxy, provider-many-keys, filter-ime, usage-keys-fold, i18n-keys, click-scroll, provider-proxy, proxy-own-wording, routing-proxy, plugin-proxy-page, signin-proxy and proxy-test: 221 of 221 pass in both engines. Run: gofmt -l (clean); GOOS=linux and GOOS=windows go vet -tags nogui ./... (clean); go test -tags nogui -count=1 -timeout 45m ./... in a sandbox HOME (GOCACHE, GOMODCACHE pinned) on af60154 with this series: 66 packages ok, 18 without tests, no FAIL (gateway 468.5s, provider 215.9s, gui 122.8s). provider-many-keys-proxy.test.cjs rerun after the suite: 4 of 4.
…k_li21 on X)
@mark_li21 asked: 能支持通过curl例子直接导入provider不? A curl example
from a vendor's docs, pasted into a custom provider's Base URL or API key,
now fills in the editor instead of being pasted as text.
curlOf (app.js) is one pure function. It reads the paste the way a shell
would: lines joined by \, cmd's ^ or PowerShell's backtick; curl.exe;
quotes and escapes. From that it takes:
- the API that the URL's endpoint names: /chat/completions is Chat,
/messages is Anthropic and /responses is Responses;
- the base URL before that endpoint, spelled by respellURL the way the
editor already wants it;
- the key from Authorization: Bearer, x-api-key or api-key;
- the model in the JSON body.
A key that is only a placeholder ($X, ${X}, %X%, <token>, YOUR_API_KEY)
is not taken. The key box is left empty and focused instead. A new
provider adds the model to its models. A saved provider's picks are left
alone, because adding to an empty list would narrow what it serves. A
preset's editor only takes the key.
A note in the editor says what was filled in. It also points out a
placeholder left in the URL, such as {WorkspaceId}, and a URL with no
known endpoint. Its four strings are translated into zh, zh-TW, ja and
de. Text that isn't curl pastes as before, including several keys at
once.
Found along the way: url.onpaste = fromCurl cancelled every plain paste
into the Base URL, because an on-handler that returns false cancels the
event. The handler is now wrapped. The test fails on the unwrapped one.
Fixtures, in internal/gui/tests/fixtures/curl, are the docs' own bytes:
- OpenAI chat (API reference) and Responses (quickstart);
- Anthropic Messages;
- DeepSeek, Zhipu, Moonshot and OpenRouter quick starts;
- Alibaba Bailian's Windows cmd example (^ and \"-escaped JSON);
- SAP AI Core's PowerShell one-liner (curl.exe, <Deployment_url>);
- Unsloth Studio's PowerShell example, with backtick continuations, as
its usage panel renders the template. This one is not copied from a
static doc page.
The doc row is "A pasted curl command" in
docs/subsystems/providers-accounts.md.
Run at 674b6ca (origin/main) plus this change:
- provider-curl-paste.test.cjs (new): 10/10. It covers Chromium and
WebKit; curlOf on every fixture, with LF and CRLF line ends; and the
editor in en and zh at 900px and 440px. The editor checks are the
filled form, the focused key, the note's text, plain and many-key
pastes, the saved body, no sideways scroll, no <select> and no page
errors.
- Related GUI files, run together with the new one, 112/112:
gui-de, gui-ja, gui-zh-tw, i18n-keys, key-import, nokey-editor,
provider-add-gemini, provider-base-api, provider-many-keys,
provider-many-keys-proxy, provider-url-each-api and vertex-editor.
- Broken by hand, the new test goes red:
- fromCurl taking nothing: 8 of 10 fail (the editor runs);
- the endpoint not cut from the base URL: 10 of 10 fail;
- url.onpaste = fromCurl unwrapped: the plain-paste check fails.
- gofmt -l: none. GOOS=linux and GOOS=windows go vet -tags nogui ./...: ok.
- go test -tags nogui -count=1 -timeout 45m ./... in a sandbox HOME:
exit 0, 66 packages ok, no FAIL.
Not tried:
- the real app (Wails) on macOS, or WebKitGTK on Linux;
- Windows;
- a pasted command used to reach a real vendor with a real key.
OpenPencil (ZSeven-W/openpencil, the openpencil-desktop app) keeps the builtin agents its chat runs on in settings.json under Rust's dirs::config_dir()/openpencil (read from its settings_io.rs and settings_payload.rs). magpie adds one builtin_agents entry, id "magpie", kind openai-compat at the gateway's /v1 with the magpie-openpencil key, preset custom, the picked models as OpenPencil writes a list: the first as `model`, `models` only for two or more, at most 64. The user's other entries and settings stay, and so do the display_name and enabled they give magpie's entry; a sync with nothing to change doesn't write. A file magpie makes gets "version": 1 (OpenPencil sets a file without it aside as corrupt); a file with another version, or none, is left alone. Disconnect takes the entry out, and the file when nothing else is left. OpenPencil reads the file once at start and writes it all back when it quits, so a change is refused while openpencil-desktop runs (Windows can't be asked, so the notice says to quit it first). Notice translated in zh-CN, zh-TW, ja and de. README (en, zh-CN), docs/reference.md and docs/subsystems/agent-wiring.md list it. open-pencil/open-pencil, the other app of the name, keeps its providers in its webview's localStorage, so it gets no row. Run, under a scratchpad HOME with GOCACHE/GOMODCACHE pinned: - go test -p 2 -tags nogui -count=1 -timeout 45m ./...: ok, 66 packages ok, 18 with no test files - gofmt -l: clean; GOOS=linux and GOOS=windows go vet -tags nogui ./...: clean - go test ./internal/agent -run '^TestOpenPencil': ok - node --test agent-notice-lang, i18n-keys, header-relang (Chromium and WebKit, every language, wide and narrow): 37/37 pass Broken by hand, each went red: the running check, the 64 cap, the version check, keeping the user's display_name, the no-op sync, and writing the version into a new file. Real use: OpenPencil v0.8.4 (latest release, arm64, sha256 checked against its SHA256SUMS) under a sandbox HOME, started on a settings.json magpie wrote with the user's own Anthropic entry beside it. It started and ran 15s with the file intact and no .corrupt copy. v0.8.4 has no `models` field, so it lists only the first magpie model; main lists them all. Not tried: a chat request through OpenPencil's UI; its exit save (it didn't quit on an AppleEvent and was stopped with TERM, so the write-back wasn't seen); Linux and Windows; a build of OpenPencil's main.
TestPrimeAgent expects no detected agent before its config exists, but Agent.Detected also correctly finds prime-agent on PATH. A locally installed CLI made the full suite fail. Reproduced with unchanged production and test code at upstream a15e576; isolate PATH for this config-only fixture, as other config-detection tests do. No production behavior changes. Verification: TestPrimeAgent fails on the untouched base with prime-agent installed, then passes with -race after this change. go vet ./internal/agent passes for darwin, linux (-tags nogui), and windows. The complete integration suite is being rerun; its final result belongs to the integration commit.
…#136) Explicit CLI identity invalidation now clears user/plan/ok and removes only that CLI's persisted answer, so a failed first ask cannot serve the old account or restore it after restart. Ordinary failed refreshes still keep the previous answer, and the generation guard still discards an old ask. Update the providers/accounts reference to document this failure behavior. On upstream a15e576 with the new test and unchanged production code, TestCLIIdentityForgetThenUnanswered fails with old@example.com / Pro / true after forget plus a failed ask. It passes with this fix. Tests cover another CLI's persisted entry and in-flight invalidation too. Devin output-before-exit handling is already upstream; add nonzero signed-in/signed-out cases to TestAskDevinStatus rather than restoring the old implementation. Verified with Go 1.26.8 under a temporary HOME and pinned Go caches: - gofmt -l changed Go files: no output; git diff --check: clean. - CLI identity and Devin status tests: pass, including -race. - go vet ./... on macOS; GOOS=linux go vet -tags nogui ./...; GOOS=windows go vet ./...: all pass. - macOS, Linux nogui, and Windows go build: all pass. No real vendor-account check or native Linux/Windows runtime test was run. No GUI files changed; GUI browser suites were not run.
Preserve the original PR head 0aaae4c in ancestry while retaining the tested tree based on upstream a15e576. Upstream already implements the unanswered-status handling and parses Devin output before exit status; keep that newer implementation rather than reintroducing obsolete APIs. The preceding commits fix explicit identity invalidation and isolate the Prime Agent config-detection fixture from a CLI installed on PATH. Final integration validation (Go 1.26.8, temporary HOME, pinned caches): - go test -tags nogui ./... -timeout=45m: PASS, exit 0, 892.1 seconds. - CLI identity/Devin tests and isolated TestPrimeAgent with -race: PASS. - macOS, Linux nogui, Windows go vet ./... and builds: PASS. - Affected agent tests re-vetted for all three platforms after isolation: PASS. Changed Go files formatted, git diff --check clean. The new forgotten-account test fails with old@example.com / Pro / true on unchanged upstream production code, then passes with this fix. The first short-timeout suite was stopped and is not a pass. The next full run failed only TestPrimeAgent, independently reproduced at the unchanged upstream base and fixed in its own preceding test-only commit. The final complete suite passes. No GUI browser suite, real vendor-account check or native Linux/Windows runtime test was run.
An explicit account change invalidates the CLI identity, but if the next ask fails,
forget()still serves the previoususer/plan/ok. Its disk entry can also restore that account on restart. This update clears the served fields and removes only that CLI's persisted identity; the other CLI's identity stays intact. An ordinary failed background refresh continues to preserve the last answer.Refreshed against upstream
a15e576564cb90d4d027e8b8cb7821472de0a3fc. The earlier unanswered-ask handling, including interpreting Devin output before its exit status, is already on current main (askDevinStatusAt). The update keeps that implementation and adds explicit signed-in/signed-out nonzero-exit coverage instead of reintroducing the old parser. The remaining production change iscliIdentity.forget, shared by the built-in Cursor and Devin identity caches; moved plugin accounts use their own identity paths.Regression evidence: on unmodified current-main production code,
TestCLIIdentityForgetThenUnansweredfails withforget followed by failed ask served old account: "old@example.com" "Pro" true; it passes after the fix. It also checks restart behavior and preservation of another CLI's disk entry.TestCLIIdentityForgetDropsInflightAnswerchecks that a pre-invalidation ask cannot restore the old identity. The documented failure behavior is updated indocs/subsystems/providers-accounts.md.The first complete suite failed only in
internal/agent.TestPrimeAgent: it expects no detected agent before writing config, butAgent.Detectedcorrectly finds an installedprime-agenton PATH. This was independently reproduced on untouched upstreama15e5765. A separate test-only commit isolates PATH for that config-detection fixture; the test then passes with-race. No Prime Agent production behavior changes.Validation on the final tree, using Go 1.26.8:
go test -tags nogui ./... -timeout=45m: PASS (exit 0, 892.1s).-race: PASS.TestPrimeAgentwith-race: PASS.go vet ./...(macOS),GOOS=linux go vet -tags nogui ./...,GOOS=windows go vet ./...: PASS. After the test-only isolation change, the affected agent package was vetted again for all three platforms: PASS.git diff --check: clean.The initial short-timeout full run was intentionally stopped, not counted as a pass. The next full run exposed the Prime Agent fixture issue above; the final complete run after its fix passed. Tests use an isolated HOME with Go caches pinned. No real vendor account or GUI test was run; there is no UI change. Cross-platform builds/vet do not establish native Linux or Windows runtime behavior.
🎬 界面预览
CI 用这个 PR 的代码构建并真实运行 magpie(沙盒环境,配置了真实的 DeepSeek key),按改动自动操作、截图和录屏 ·
5c80760· 运行记录这次没录成(skipped)。详见运行记录。