7 Commits
Author SHA1 Message Date
ggbdpqandfactory-droid[bot] d81706f974 gateway: a Chat or Responses client's xhigh on Claude Opus 4.7 and later, Sonnet 5 and later or Haiku 5.5 goes as xhigh, not max
buildAnthropic asked every Claude that thinks only adaptively for max
when the client asked xhigh, on the grounds that 4.6 has no xhigh. Only
Opus and Sonnet 4.6 lack it: Anthropic's effort docs list xhigh on
Fable 5 and 5.1, Mythos 5 and 5.1, Opus 5.5, 5, 4.8 and 4.7, Sonnet 5.5
and 5, and Haiku 5.5, and describe max as no constraint on token
spending. So Codex, Cursor or any Chat client picking xhigh on
claude-opus-5-5 was billed for max. Claude Code's own levels in magpie
(claudeEffortsFor) already say only Opus and Sonnet 4.6 skip xhigh.

Now xhigh becomes max only on 4.6 (noXhigh, which reads the version
through claudeVersionOf, shared with adaptiveOnly). A relayed thinking
budget is unchanged: budgetOf gives xhigh and max the same budget, and
31999 is Claude Code's old ultrathink, so a budget above high still
goes as max. docs/subsystems/gateway-routing.md now says which models
are asked adaptively (#1470) and where xhigh stays xhigh.

What was run: TestXhighStaysXhighWhereClaudeTakesIt (Chat and Responses
through the server stack to a fake Anthropic upstream) fails on the old
main with output_config = map[effort:max], want "xhigh" for Opus 5.5
(Chat and Responses), Opus 4.7, Sonnet 5 and Haiku 5.5, and passes with
the change; Opus and Sonnet 4.6 get max before and after, and a Fable
5.1 row asserts xhigh. TestAdaptiveThinking's Opus 5.5 xhigh case now
asserts xhigh, beside a 4.6 case asserting max. Broken by hand (noXhigh
always false) the 4.6 cases fail. gofmt -l prints nothing; go vet -tags
nogui ./... passes for darwin, linux and windows; go test -tags nogui
-count=1 ./... under a temp HOME passes. Through the aihubmix relay,
claude-opus-5-5 with adaptive thinking and effort xhigh answers 200.
Not tried against Anthropic directly (no credential outside the
keychain); a relay that checks effort against an older list could still
refuse xhigh.

Reported and fixed by ggbdpq (#1471).

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2026-10-10 15:17:13 +08:00
ggbdpqandfactory-droid[bot] 55ac9ec5d2 gateway: Claude Fable and Mythos are never sent a thinking budget, so a Chat client's reasoning_effort or a relayed budget no longer gets their 400
adaptiveOnly read a Claude's version with claudeVersion, which knew
opus, sonnet and haiku only, while claudeLine (thinkingOffOf,
samplingRefused) already knew fable and mythos. So claude-fable-5,
claude-fable-5-1, claude-mythos-5 and claude-mythos-5-1 were taken for
models that take thinking.type=enabled with budget_tokens:
buildAnthropic turned a Chat or Responses client's reasoning_effort into
a budget, forwardOnce's adaptiveThinking relayed an agent's budget as it
came, and AdaptiveThinking didn't tell Pi (forceAdaptiveThinking), dsh
or omp (anthropic-adaptive) to ask adaptively. Anthropic documents that
Fable 5 and 5.1 and Mythos 5 and 5.1 answer thinking.type=enabled with
budget_tokens with a 400 and take only {"type": "adaptive"} with the
effort in output_config.

claudeVersion now reads fable and mythos too, so all three paths treat
these models as Opus 5.5 is treated. claude-mythos-preview has no
version in its id and is left as it was.

What was run: TestFableAndMythosNeverGetABudget (a fake Anthropic
upstream answering 400 to enabled) fails on the old main: adaptiveOnly
false for all five ids, and status 400 for a relayed Fable budget, a
Chat client's reasoning_effort on Fable and a relayed Mythos budget. It
passes with the change, as do TestAdaptive*, TestThinkingOff*,
TestSampling*, TestClaudeFiveFive* and TestHaikuFiveFive*. Broken by
hand (mythos dropped from the regex) the Mythos cases fail again.
gofmt -l prints nothing; go vet -tags nogui ./... passes for darwin,
linux and windows; go test -tags nogui -count=1 ./... under a temp HOME
passes.

Not tried against Anthropic itself: the owner's Claude sign-in keeps
its token in the keychain, which a sandbox doesn't read (and must not
refresh), and the one relay with Fable listed (aihubmix) has no balance
for it; that relay also takes a budget on Opus 5.5, so it doesn't show
Anthropic's refusal either.

Reported and fixed by ggbdpq (#1470).

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2026-10-10 15:17:11 +08:00
ggbdpqandfactory-droid[bot] 8714abd45d agent: TestUpdateWithBunBridgesSOCKS reads only the proxy variables it sets, so a machine with NO_PROXY or NODE_USE_ENV_PROXY set no longer fails it
The test collected every variable whose name ends in _PROXY from
updateEnv's environment and required each to be a bridged
http://127.0.0.1 URL for bun. updateEnv passes the rest of the
environment through, so a NO_PROXY of the machine running the test
("bun is given localhost,127.0.0.1") or Node's NODE_USE_ENV_PROXY=1
("bun is given 1") made it red, though neither names a proxy. It now
reads the six proxy variables it sets (the ones netproxy.EnvForBun
bridges, in either case). Test-only; updateEnv is unchanged.

What was run: on the old main the test is red with
NO_PROXY=localhost,127.0.0.1 and, separately, with NODE_USE_ENV_PROXY=1
in the environment, green with neither. With this change it is green
with both set. Broken by hand (updateEnv's netproxy.EnvForBun call
removed) it fails with "bun is given socks5://127.0.0.1:4401". gofmt -l
prints nothing; go vet -tags nogui ./... passes for darwin, linux and
windows; go test -tags nogui -count=1 ./... under a temp HOME passes.
Not run on the Windows box.

Reported and fixed by ggbdpq (#1472).

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2026-10-10 15:17:08 +08:00
ggbdpq 74c48ac060 agent: the managed daemon's subcommand helpers are no stale Codex copy (#1461)
Codex keeps a long-lived helper next to the managed app-server (codex
app-server daemon pid-update-loop). It matches the codex startsWith
pattern, carries no --managed-daemon, sits under no app bundle or
Application Support folder, so codexCopyKind called it "cli": a helper
that started before magpie's last change to what Codex reads counted as
"A Codex in a terminal" forever, a daemon restart didn't end it, and the
only way to clear the row was killing a process the user can't name.

codexCopyKind now answers "helper" for an app-server daemon <sub>
command line, and StaleCopies skips it: it reads no list and there is no
Codex to quit. The daemon itself (--managed-daemon) is told apart
before, and a terminal Codex still counts.

Red/green: TestCodexDaemonHelperIsNoStaleCopy runs the helper alone from
before magpie's change — FAIL on the old code (one stale "cli" copy),
passes now; a terminal codex from before still counts 1.

Generated-by: GLM-5.3-Flash (ZCode)

Reviewed and landed by the maintainer (cherry-pick of b40a1dd0 from
ggbdpq:fix/codex-daemon-helper-1461, lychjck's report). Run on main at
c98160c0 with this commit on top, macOS arm64:
- On this Mac (ps, read only): the installed codex-cli 0.162.0 binary has
  the subcommand `app-server daemon pid-update-loop` ("[internal] Run the
  detached pid-backed standalone updater loop"): an updater, it reads no
  model list. The managed daemons running here are
  `codex app-server --listen unix:// --managed-daemon` and stay "daemon".
  No pid-update-loop helper was running on this Mac at review time, so the
  reporter's live row was not reproduced here; the fixture is the command
  line from the report. provider's isManagedDaemon already names the same
  helper as no daemon.
- TestCodexDaemonHelperIsNoStaleCopy with main's connectinfo.go: FAIL,
  "the daemon's helper counted a stale copy: [{Kind:cli ...}], want none";
  passes with the change.
- gofmt -l: clean. go vet -tags nogui ./internal/agent for darwin, linux
  and windows; GOOS=linux go vet -tags nogui ./... and GOOS=windows go vet
  ./...: clean. go build, GOOS=linux go build -tags nogui, GOOS=windows
  go build: ok.
- go test -tags nogui -count=1 ./... under a scratch HOME with GOPATH,
  GOMODCACHE and GOCACHE pinned: 44 packages ok, none failed.
- Not run: GUI tests (no GUI change), Windows or Linux hardware (the
  stale-copy check doesn't run on Windows; Linux uses the same ps path).
2026-10-10 14:07:55 +08:00
ggbdpq 65dcc57369 tests: the root package's plugin tests run the bun on PATH once before their one-minute context, so a bun that has never run here doesn't time them out (ggbdpq, #1306)
TestAccountsOfAPlugin and the plugin login tests (fakeCoHome) point
MAGPIE_BUN at the bun found on PATH and do all their plugin work inside
a one-minute context. 7ddea2c2 kept macOS's first-exec check of the
copied magpie-bun out of that minute, but not the PATH bun's own: when
bun was just installed or upgraded, its check landed inside the minute
and the test failed with "starting plugins: context deadline exceeded".

Both now take their bun from pathBun, which looks it up as before and
runs `bun --version` once before the context opens. This is patch 1 from
ggbdpq's comment on #1306, with the warm-up in one helper beside
fakeCoHome instead of copied into both tests. Patch 2 (quota_served's
stand-ins) is left out: it only saves wall-clock time, and that test has
no deadline.

What was run (macOS arm64, bun 1.4.2):
- Red, old code: a wrapper bun first on PATH whose first exec sleeps
  61s (marker-gated, then execs the real bun). TestAccountsOfAPlugin:
  FAIL at 62.0s, "accounts_plugin_cli_test.go:38: starting plugins:
  context deadline exceeded". TestPluginLoginTakesTheLapseOff: FAIL at
  62.0s, "plugin_login_cli_test.go:46: starting plugins: context
  deadline exceeded".
- Green, this change, the same cold wrapper with a fresh marker:
  TestAccountsOfAPlugin PASS (65.2s), TestPluginLoginTakesTheLapseOff
  PASS (63.4s); the 61s is spent before the context opens.
- go test -tags nogui -count=1 . under a temp HOME with GOMODCACHE,
  GOCACHE and GOPATH pinned: ok (22.8s); with a warm bun the warm-up
  adds nothing noticeable.
- gofmt -l on both files: clean. go vet -tags nogui . for darwin,
  GOOS=linux and GOOS=windows: clean.

Not covered: the same shape (LookPath("bun") as MAGPIE_BUN, then a
one-minute context) is in internal/provider (pluginsignin_paste_test,
plugin_fetch_lapse_test, signin_removed_test) and internal/plugin
(plugin_test). Not run on a genuinely cold bun on a loaded machine; the
sleep stands in for the check.
2026-10-10 10:12:59 +08:00
ggbdpq e9ed294383 provider, tests: a history write holds no reading waiter, and a batch of cached readings notes nothing new (#1358)
TestHistoryWriteHoldsNoWaiter is named for the guarantee that a history
write never holds a caller waiting on a reading, but its second caller
arrives after the reading has landed and answers from the cache: with
the note moved back into the reading's goroutine before close(r.done)
(the regression it is for), the test still passes. The new
TestHistoryWriteHoldsNoReadingWaiter starts caller B while the reading
is still in flight, so B waits on the reading's done channel: with the
regression injected, both callers are held ("both callers held by one
history write", 5/5); on main, the reading's done channel closes before
the note, and caller B answers.

Also from the #1318 review: every LoginUsage noted its batch's history
by reading and parsing quota-history.json under quotaHistMu even when
every reading in the batch was answered from the minute's cache, so a
cached batch re-read the file for points it already had
(TestCachedBatchNoteSkipsTheRead failed with "a cached batch read the
history file 1 times"). noteQuotaHistory now skips a batch whose
readings are all no newer than what was last noted for their account;
the noted times are kept per history file and in memory, so a new file
(or a test's fresh home) starts clean, and a fresh reading is always
newer than what was noted and goes through as before.

Verified: go test -tags nogui ./internal/provider -count=1 (full
package); the five history tests under -race -count=5; gofmt -l clean
on the three touched files; go vet -tags nogui ./internal/provider;
go build and GOOS=linux/GOOS=windows go build -tags nogui.

Generated-by: GLM-5.3-Flash (ZCode)
Review-run: with the note moved into the reading's goroutine before close(r.done), TestHistoryWriteHoldsNoReadingWaiter fails 5/5 ("both callers held by one history write") while TestHistoryWriteHoldsNoWaiter still passes; on origin/main's code it passes 10/10 under -race; the skip taken out fails TestCachedBatchNoteSkipsTheRead ("read the history file 1 times"); skip always taken fails 5 history tests; no per-path reset fails TestMergeQuotaHistory; no goroutine left past the tests; go test -tags nogui ./internal/provider/... ok; history tests -race -count=10 ok; gofmt -l clean; go vet -tags nogui ./internal/provider/ on darwin, GOOS=linux and GOOS=windows ok.
2026-10-09 07:26:00 +08:00
小七 172f45a5e6 agent: dsh's check stays quiet when its key store holds a caller key of this gateway's (#1332) (#1339)
The drift check asked only whether refs.MAGPIE_GATEWAY_KEY equals the key
magpie writes into .env, so a caller key the gateway itself issued — which
dsh's store prefers and the gateway authenticates — was named as "another
MAGPIE_GATEWAY_KEY" and the user was told to remove it there, while
delete → disconnect → re-apply wrote the very same state back. The check
now asks the gateway's own Authenticate first: a key it takes means dsh is
going through magpie, and only a key it would refuse (a foreign one, a
switched-off one) is drift. The .env side needs nothing: the serving round
rewrites .env to magpie's key, so a stray value there heals itself.

TestDshStoreHoldingAGatewayCallerKey (a key from access.Update add-key in
the store's refs) failed without the change with the "holds another
MAGPIE_GATEWAY_KEY" drift; with it the check is quiet, and a key the
gateway doesn't take still reports.

Verified: go test -tags nogui ./internal/agent -count=1; the three dsh
store tests under -race -count=10; gofmt -l clean; go vet -tags nogui
./internal/agent; go build and GOOS=linux/GOOS=windows go build -tags
nogui.

Generated-by: GLM-5.3-Flash (ZCode)
2026-10-08 23:51:34 +08:00