Files
magpie/internal/edit
Leslieandgoogya 8031edc78b agent: AtomCode support — one provider account, one model table per catalog model, as its own sign-in writes (#736)
* agent: AtomCode is wired like its own CodingPlan sign-in — one provider account, one model table per catalog model

AtomCode (atomgit.com/atomgit_atomcode/atomcode, AtomGit's open Rust terminal agent) keeps its settings in ~/.atomcode/config.toml ($ATOMCODE_HOME). Its account schema, what its own CodingPlan sign-in writes, is one [provider_accounts."magpie"] table at the gateway's /v1 (protocol openai, gateway.TokenFor("atomcode") as the key, so a request is its agent's even when the UA is overridden) and one [models."magpie/<provider>/<model>"] table per catalog model: the model string goes to the gateway as it is to resolve, with the model's context window (AtomCode compacts as it nears it; a 128000 one is told when the model's isn't known, and an output above the told window is cut to it), its output limit, vision and reasoning_effort_levels; reasoning_effort rides on the table once the user picks one. Picking a model points default_provider and default_model at it — default_model wins when both are set, as atomcode reads it — and stashes the defaults the user had; stepping out puts them back. The picker offers the user's own model tables and the catalog via magpie; the Library gives AtomCode its ATOMCODE.md, its mcp.json (omp's shape) and skills/; its UA is atomcode/<version>.

Found against a real atomcode 5.2.1: the documented legacy [providers.*] tables work, but its own sign-in writes the account schema, so magpie writes that; reasoning_effort, max_tokens and supports_vision were verified to reach the request from the model table, and default_model to win over default_provider. Verified end to end on a real install: atomcode --provider "magpie/…" answered through the gateway, and the Agents page shows the tile, its picker and the Usage attribution. TestAtomcodeWiring, TestAtomcodeRestoresOwnDefaults and TestAtomcodeSync fail on the old code; TestDriftUnwiredEveryAgent and TestUnsetAgentsReadEmpty cover it from All(). go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./internal/agent ./internal/library ./internal/agentenv pass; the plugin-host and timing failures of the full ./... run fail on main without this change too.

* tweak

* agent: AtomCode's config is matched by its own spellings, and Sync keeps the effort — AtomCode rewrites its config through serde on every /model and /cd, and that writes the account header bare ([provider_accounts.magpie]) where magpie wrote it quoted, so after the first rewrite Check read the wiring as gone, Sync and every pick appended a second account table (edited TOML is invalid: table magpie already exists) and clearing left the unquoted table and its api_key behind. The account is now matched by exact name in its three spellings (quoted, bare, literal — magpie-2 stays out) and the model tables by their decoded keys (kimiModelKey, replacing atomcodeModelName), through a new SetTOMLTablesMatching that takes exact names beside prefixes; SetTOMLTables delegates with none, unchanged. Sync carries each magpie model table's reasoning_effort over when rewriting — it ran at every gateway start and catalog change and dropped both an effort picked in magpie and one picked with AtomCode's /effort, and picking another model cleared every table's. The effort Set validates against the levels AtomCode knows (low, medium, high, xhigh, max — its clamp_effort_to_levels drops none, minimal and ultra), a model without levels offers none now, and the low/medium/high fallback is gone with it. supports_vision is written explicitly false, where AtomCode otherwise guesses from the model name. The user's legacy [providers.*] entries join the picker, a stashed default_model naming one comes back on Disconnect, and picking one sets default_provider and takes default_model out, as the legacy shape reads. site docs (en, zh, ja) list AtomCode. TestAtomcodeSync rewrites the config into the bare and single-quoted spellings before Sync and again before clearing and fails on the old code (drift reported, effort dropped, unquoted table left behind); TestAtomcodeLegacyProvidersArePickableAndRestored and TestAtomcodeEffortLevelsMatchAgentSupport are new. Reported by yetone on #736. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./internal/agent ./internal/edit pass.

* agent: TestAtomcodeWiring picks an effort through the field too — the write side of the validation the last commit added (a level AtomCode knows lands as reasoning_effort on the model table, one it does not know is refused) had no coverage left: the test only read back what AtomCode's own /effort had written. provider.SetModelEfforts gives fake/m1 levels to offer.

* agent: Disconnect puts AtomCode's default_provider back only where AtomCode still has it — the restore checked the stash's value was not magpie's but not that the table it names was still there, so a table the user had deleted in between came back as a dangling default (the account schema's default_provider names a model table, the legacy shape a [providers.*] one; own answers for both). The default_model branch checked already. TestAtomcodeRestoresOwnDefaults deletes the stashed provider's model table before clearing and fails on the old code.

* agent: AtomCode joins the CLI updates — npm @atomgit.com/atomcode, the brew cask atomcode, and the curl script's ~/.local/bin/atomcode updating itself with atomcode upgrade, so the Agents page shows its version and a newer one as the other agents' do (#736). TestHowInstalled covers the npm prefix, the cask and the self-updating installer.

* agent: AtomCode's self-updater waits for its install paths to be confirmed — the branch recognized a binary under ~/.local/bin as `atomcode upgrade`, on a docs page, while the README shows another path and the command has not been driven outside the help text; the reviewer asked for it removed until the standalone command and the script's real default are settled. npm and the brew cask stay, with their tests; a comment marks what is pending.

---------

Co-authored-by: googya <118465+googya@users.noreply.github.com>
2026-10-04 14:11:14 +08:00
..
accounts: on Linux an account put first is read first at once, and on Windows a file being replaced isn't read as none (goal: 插件订阅 provider 跟内置的所有逻辑都要一模一样, run on Windows and Linux) — the provider tests run on the Windows test box and in WSL found two bugs of magpie's own files. Linux stamps a file's time from a clock a tick (a few milliseconds) coarse, and filememo took same time and size for the same file: two accounts swapped (Devin, Grok, WorkBuddy, ZCode, Claude…) read back in the old order, so magpie kept using the account the user had just moved down, and a moved-back plugin's accounts read as before. A file written within the last two seconds is now also compared by its bytes. On Windows a file being renamed over refuses to open for a moment ("Access is denied"), and one held open refuses to be renamed over: plugin-auth.json, plugins.json and the plugin providers read as nothing (a usage fetch right after a move showed no cards), logins.json failed to save, and host.js, reading plugin-auth.json as {}, would have written the other accounts away on the next sign-in. New internal/steady waits such a read or rename out (half a second at most, Windows only); filememo, edit.WriteAtomic, writePrivate, Kiro's writes and the plugin store go through it, host.js retries its read and rename, and the plugin store writes its files by a rename. Tests: USERPROFILE beside HOME in the test homes, the fake devin (a shell script) skipped on Windows, plugin-auth.json's mode bits unchecked there. TestReadSameSizeSameTick fails on Linux and TestRenameOverAFileHeldOpen/TestReadAFileHeldOpen on Windows without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; on Linux (WSL) the provider and plugin packages pass whole (13 failed before), on Windows the plugin package and the move/plugin/Grok/Devin/usage tests pass 10 times over (TestMoveToPlugin failed one run in a few before); 15 built-in tests that assume Unix (mode bits, Keychain, shell-script CLIs) still fail there, as before.
2026-10-01 13:27:12 +08:00
test: every package's tests pass on Windows, and none writes outside its sandbox (the user: 把 Windows 上那几个失败的测试修掉) — on a Windows box the suite failed in most packages, and the root package hung for 25 minutes. Tests set HOME, which os.UserHomeDir ignores on Windows (it reads USERPROFILE), so they read and wrote the runner's own home; each now sets USERPROFILE beside HOME, and Devin's, Goose's, Crush's and Claude Desktop's tests set APPDATA/LOCALAPPDATA, where those keep their files there. said() read the command's output only once it returned, and a pipe holds a few KB on Windows, so magpie model --help blocked forever; it reads while the command writes. Unix permission bits (the gateway key store's 0600 too), exec bits and symlinks Windows lacks are checked only off Windows; TOML strings hold Windows paths as literal strings, JSON as marshalled ones; a URL's path is compared as one. The provider and usage packages run in a home of their own (TestMain, as the gateway's already does), since a CLI's identity kept by a refresh still going when its test ended landed in the runner's ~/.config/magpie — and on a Mac in the user's own. Two product fixes came out of it: imagemcp took a file:///C:/… root or image for the path /C:/…, which doesn't exist on Windows, and now makes a Windows path of it (a host for a UNC share); and session discovery could keep a new Cowork session hidden on Linux, where a folder's mtime is a kernel tick and its size doesn't change, so an entry added within the tick of the last listing left both as they were — a listing made within 2s of its folder's last change is read again, as git does for racy files. TestFilePath and TestRacyDirectoryListedAgain fail without the change. On Windows 11 every package passes (32 of 32) and a run of each package in a fresh home leaves nothing in home, APPDATA or LOCALAPPDATA; on Linux (WSL) and macOS go test -tags nogui ./... passes. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build pass.
2026-10-01 23:15:07 +08:00
toml: Codex's config.toml with a multi-line array (notify = [ … ]) is edited right, and an edit that fails leaves every file as it was (#253 kerwinliu00001: "edited TOML is invalid: line 2, column 1: incomplete number", config.toml left with model_provider gone and [model_providers.magpie] added but no model) — the top-level key edits (GetTOMLTop, SetTOMLTop, DelTOMLTop) went line by line with regexes: a new key went after the last line that looked like key = …, which for notify = [ was inside the array; key = value lines inside a multi-line string were read as keys and a line starting with [ as a table; replacing or deleting a multi-line value touched only its first line. They now locate the top-level keys with the TOML parser, as the table edits already did, each with its value's whole range however many lines it takes (parseTOMLFile, the unnamed root table beside the tables); SetTOMLTop finds all its keys in the file as read and writes once. A file the parser refuses is still read line by line, so a broken config keeps showing its model, and writes to it are refused untouched. And a pick is several writes (Codex: drop model_provider, add magpie's provider table, set openai_base_url and model), each checked, so one failing left the earlier ones on disk: edit.Atomically(fn, paths…) puts each file back to its bytes and mode, or removes one it created, when fn fails, and every field set and Sync of Codex (config.toml and magpie-models.json, so Codex in WSL too), Kimi and Grok goes through it, as do Codex's MCP server writes (putCodex, delCodex). Tested in temp dirs and a temp HOME: the reporter's file read, set and deleted directly and through Codex's model field (signed in, with model_provider and model above the array, with magpie as the provider), the array's text kept and the TOML valid, and back to a Codex model; nested arrays, trailing commas, comments in arrays, [x] and a = b in strings, an inline table in an array; """ and ''' strings holding model = … and [table] lines not read as keys, new keys after their closing quotes, one deleted whole; a forced failure of Codex's last write leaving config.toml byte-identical; TestAtomically. They fail on main (the Codex ones with the reporter's error). go vet ./internal/..., go build -tags nogui (and GOOS=linux, GOOS=windows) and go test -tags nogui ./... pass. Not tried against a real ~/.codex, in WSL or in the GUI; multi-line inline tables (TOML 1.1) are refused by the parser and so left untouched with an error.
2026-09-30 09:31:59 +08:00
toml: Codex's config.toml with a multi-line array (notify = [ … ]) is edited right, and an edit that fails leaves every file as it was (#253 kerwinliu00001: "edited TOML is invalid: line 2, column 1: incomplete number", config.toml left with model_provider gone and [model_providers.magpie] added but no model) — the top-level key edits (GetTOMLTop, SetTOMLTop, DelTOMLTop) went line by line with regexes: a new key went after the last line that looked like key = …, which for notify = [ was inside the array; key = value lines inside a multi-line string were read as keys and a line starting with [ as a table; replacing or deleting a multi-line value touched only its first line. They now locate the top-level keys with the TOML parser, as the table edits already did, each with its value's whole range however many lines it takes (parseTOMLFile, the unnamed root table beside the tables); SetTOMLTop finds all its keys in the file as read and writes once. A file the parser refuses is still read line by line, so a broken config keeps showing its model, and writes to it are refused untouched. And a pick is several writes (Codex: drop model_provider, add magpie's provider table, set openai_base_url and model), each checked, so one failing left the earlier ones on disk: edit.Atomically(fn, paths…) puts each file back to its bytes and mode, or removes one it created, when fn fails, and every field set and Sync of Codex (config.toml and magpie-models.json, so Codex in WSL too), Kimi and Grok goes through it, as do Codex's MCP server writes (putCodex, delCodex). Tested in temp dirs and a temp HOME: the reporter's file read, set and deleted directly and through Codex's model field (signed in, with model_provider and model above the array, with magpie as the provider), the array's text kept and the TOML valid, and back to a Codex model; nested arrays, trailing commas, comments in arrays, [x] and a = b in strings, an inline table in an array; """ and ''' strings holding model = … and [table] lines not read as keys, new keys after their closing quotes, one deleted whole; a forced failure of Codex's last write leaving config.toml byte-identical; TestAtomically. They fail on main (the Codex ones with the reporter's error). go vet ./internal/..., go build -tags nogui (and GOOS=linux, GOOS=windows) and go test -tags nogui ./... pass. Not tried against a real ~/.codex, in WSL or in the GUI; multi-line inline tables (TOML 1.1) are refused by the parser and so left untouched with an error.
2026-09-30 09:31:59 +08:00
2026-10-04 02:05:10 +08:00
2026-10-04 02:05:10 +08:00