syncJSON and Pi's writeMagpie set providers.magpie to a freshly built
object, so a key magpie doesn't write there was dropped on every sync.
theirsKept now merges magpie's block into what the file holds: keys magpie
writes are its own, objects in both are merged key by key, and anything
else (compat, an options key, Crush's extra_headers) stays. The model list
is still magpie's whole, so a model gone from the catalog leaves it, and a
sync with nothing new writes nothing.
The same goes for every agent that writes the block whole: Pi and OmO,
Aside, Pencil, OpenCode (and the agents built on it), OpenChamber, Crush
and Command Code.
Verified: TestPiKeepsTheirKeysInMagpiesProvider (the reporter's
compat.sendSessionAffinityHeaders: true in Pi's models.json, through
SyncCatalog and a pick) and TestOpenCodeAndCrushKeepTheirKeysInMagpiesProvider
fail without the change ("compat.sendSessionAffinityHeaders dropped by the
sync", "opencode sync") and pass with it. go test -race ./internal/agent
and go vet ./... pass in a sandbox HOME.
Each was a one-line wrapper of its *At function for the local gateway.
The callers moved to closures of the same name over the agent's own
address (at.v1(), at.gw()), which shadow the package function, so the
wrappers were reached by nothing, tests included (deadcode -test). Their
doc comments now sit on the *At functions, and isCodexGatewayOn has its
own again: it had been left above codexKeptGateway.
go vet (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui
./internal/agent under a temp HOME pass.