Commit Graph
13 Commits
Author SHA1 Message Date
TryWorldandh4rK8s efd501ec74 agent: add Reasonix Studio with separate Executor and Plan models (#427)
* feat(agent): add Reasonix Studio 2.x integration

* agent: select Reasonix planner independently from executor

* agent: restore Reasonix when its credential file was initially absent

* agent: preserve Reasonix planner choices and model controls

Keep configured and saved native planner references selectable, and retain full model picker behavior and role labels. Exercise native planner restoration and both browser layouts. Separate temporary CLI first-launch work from version and intentional timeout assertions.

* test(gui): follow the current Reasonix connection flow

* test(gui): open connected Reasonix details before checking roles

* agentenv: isolate Reasonix configuration in test sandboxes

* gui: restore Reasonix roles without disconnecting the other

* gui: Reasonix's role labels read in every language, and its confirm names them

The review found four things on the Reasonix roles' strings.

"executor" and "planner" are field labels like any other, so t() translates
them; they had no i18n entry in any language, so the zh, ja and de windows
showed the English key. They now read 执行模型/规划模型, 実行モデル/計画モデル
and Executor-Modell/Planner-Modell, beside model/effort/thinking in each block.

The confirm said "neither Executor nor Plan" — names that appear nowhere in
the UI — and its zh text said "Executor 和 Plan", while the fields are labelled
executor and planner. It now names the labels, and each language names its own
translation of them (zh 执行模型和规划模型, ja 実行モデルも計画モデル, de vom
Executor- bzw. Planner-Modell).

Both new strings had a zh entry and no ja or de entry, which is what turned
gui-ja and gui-de red: every zh key needs its ja and de with the same
placeholders. "restore the previous {field} selection" also started at column
0 in i18n.js; it is indented with the lines around it now.

reasonix-roles.test.cjs asserts the rendered label, so its zh expectations name
执行模型/规划模型; the en ones stay as they were, the en label being the key.
Its en assertions on the confirm's first sentence are unchanged, and the
reworded second sentence is not asserted.

Verified on this head: gui-ja passes 3/3 and gui-de 3/3 (every zh string has
its ja and de, same placeholders and tags); reasonix-roles.test.cjs passes 8/8
in Chromium and WebKit, en and zh, window (1000px) and panel (440px); go vet
./... and the four builds pass. The rest of internal/gui/tests was run and
compared against a clean main worktree at e0bf3e61: the same assertions fail on
both, and none of them is about i18n or Reasonix (see the PR comment).

* edit: a TOML value is written the way TOML reads it, preserving or not

setTOMLTop spelled every value with strconv.Quote. Go's escapes are not TOML's:
a control byte came out as \x01, and TOML defines no \x escape, so a preserving
edit wrote a config.toml that no parser reads — an agent's own value could not
be written at all. Worse, it only failed at writeTOML, after the edit was
already decided.

The preserving path and the path that adds a key now both use tomlLiteral, as
SetTOMLKey already did, so both write the same encoding. tomlString spells a
basic string from the TOML spec (\b \t \n \f \r \" \\, \uXXXX for the other
control bytes and DEL, everything else as it is, non-ASCII included). A byte
that is not valid UTF-8 has no TOML escape, so it is left as it is and writeTOML
refuses the file, rather than the value being quietly replaced by U+FFFD.

TestTOMLPreservingKeepsAValueTOMLCanParse parses the file back with the TOML
parser and compares the value, and TestTOMLPreservingAndPlainAgreeOnEncoding
puts the same value through both paths and compares the bytes. TestSetTOMLKeepsCRLF's
"top = \"9\"" is "top = 9" now: an int is a TOML integer, the same value
SetTOMLKey writes for it, and no caller but that test passes anything but a
string here.

Verified on this head: go test ./internal/edit/ passes whole; the two new tests
and TestSetTOMLKeepsCRLF pass under -race -count=10; go vet ./... and the four
builds pass. TestTOMLBigFileParsesInLinearTime fails under -race -count=10 on
this head and on a clean main at e0bf3e61 alike (a 1s budget against ~1.2s under
the detector); it passes without -race.

---------

Co-authored-by: h4rK8s <63350517+h4rk8s@users.noreply.github.com>
2026-10-06 16:14:35 +08:00
yetone a711f428ad agent: CodeBuddy Code (Tencent's CLI, codebuddy/cbc, the China edition too) is an agent that uses magpie's models (#980, yujianbinm) — the CLI (npm @tencent-ai/codebuddy-code 2.161.4, one package for codebuddy.ai and codebuddy.cn, CODEBUDDY_INTERNET_ENVIRONMENT=internal) takes models of one's own from ~/.codebuddy/models.json ($CODEBUDDY_CONFIG_DIR), the format WorkBuddy reads, hot-reloaded, and starts a session on settings.json's "model". magpie now writes its models there as WorkBuddy's are written (vendor magpie, key magpie-codebuddy, url the gateway's /v1/chat/completions), keeping the user's own entries, their order, a model turned off and availableModels (magpie's ids added when the list is there), and sets "model" to the pick, the user's other settings kept; one of CodeBuddy's own models, or default, takes magpie's models out again. A new models.json is {"models":[…]} as CodeBuddy's docs give it. workbuddyWrite becomes buddyWrite(agent), the same writer for both. CODEBUDDY_CONFIG_DIR joins agentenv.Vars; cliupdate knows the npm package. TestCodeBuddyCode (user's model, availableModels and settings kept; magpie pick; sync; back to own; default) and TestCodeBuddyCodeNewFile fail without the agent ("no codebuddy agent") and pass; -race -count=5 on the CodeBuddy/WorkBuddy tests. End to end, the real CLI 2.161.4 under a temp HOME with CODEBUDDY_INTERNET_ENVIRONMENT=internal and no CodeBuddy sign-in: magpie codebuddy magpie/mock/pro against a sandbox gateway on 3462, then codebuddy -p with no --model, answered through the gateway from a mock upstream, and magpie's usage counted it under CodeBuddy Code (the CLI sends "CLI/2.161.4 CodeBuddy/2.161.4", so it is told by its key); magpie codebuddy default took it out. A bare-list models.json is read by the CLI too. Not tried with a real CodeBuddy account or a real model vendor. go test ./internal/agent/ ./internal/agentenv/ ./internal/usage/ ., go vet, darwin, linux (nogui) and windows builds pass. 2026-10-06 15:32:23 +08:00
Yibo Xiandyetone 5115f0eea6 feat: configure Zed-compatible Agent paths (#922)
* feat: configure Zed-compatible agent paths

* fix: honor custom Zed bundle paths

* fix: classify Zed path overrides correctly

---------

Co-authored-by: yetone <yetoneful@gmail.com>
2026-10-06 01:04:26 +08:00
yetone 055f17883f gateway: a Claude subscription conversation the client sends back rewritten goes on in the run that had its last turn, and Claude Code's temp images go with their session (#912, margbug01) — Alma takes the [Context: …] block out of its last user message once the turn is over, the <alma_notification> block out of the end of the reply, and picks the tools for each turn, so turnKey, which hashes all of it, never matched and every turn started a new claude -p told the whole conversation, written to the prompt cache again with its images. ended keeps a looseTurn beside the key: the settings without the tools, the conversation before the last turn hashed, and the last turn's messages one by one. When the exact key misses, rewritten takes the one idle run or saved session whose looseTurn has the same owner, settings and earlier turns, the same roles, tool calls and results in the last turn, where a user message only had one part taken out (cutFrom) and the reply is a non-empty start of what the run said. Two that could be it start a new run, as does an idle run not told a tool offered now (offers) or a saved session one of whose tools is gone (toolsCover). Claude Code's <CLAUDE_CODE_TMPDIR or /tmp>/claude-<uid>/<work project>/<session> folder, where it keeps the images it was given, is removed with the session (removeSession), and the sweep removes those a stopped gateway left once their session file is gone and nothing in them changed for a day; CLAUDE_CODE_TMPDIR joins agentenv.Vars. Old images are still sent to a new run: replacing them would change what the model sees. docs/subsystems/claude-subscription-bridge.md is new. claude_rewritten_test.go has a case for each relaxation (failing before the change) and one for each rule that keeps another conversation out (each failing with that rule taken out). Tested for real with the owner's Claude Max account in a sandbox HOME: turn one asked with a [Context] block and two tools, turn two sent it back with the block taken out, the reply cut short and one tool, and went on in the same claude process ("goes on in its run", cache_read 1932). go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. 2026-10-05 22:56:30 +08:00
yetone 585f7f7dfa sessions, gui: Codex sessions made with another provider are moved to Codex's own from the Sessions page, and back (#887, cjyrainbow) — Codex's history lists only the sessions of the provider it uses now, so sessions made through CC Switch (model_provider "custom") vanished from it once Codex used magpie, though their files were still there and the Sessions page listed them. The Codex tab now reads each session's provider from its session_meta (cached with the rest) and the one Codex in that session's home uses (config.toml's model_provider, or its profile's; "openai" when none), says how many were made with another and which, and tags each. "Move to <provider>…" opens magpie's own dialog listing them, each ticked; Move rewrites only the model_provider string in each session's files (.jsonl and .jsonl.zst, in its own Codex home, so WSL's too, mtime kept) and in Codex's state_*.sqlite threads row: messages, id, title and archive are left as they are. A copy of the files goes to magpie's trash folder (trash/codex-provider, with a move.json) first, a failed rewrite is put back from it, and a session written to in the last minute is left alone and said so. Undo moves each back to the provider it had, byte for byte. Nothing is moved by itself. POST /api/sessions/codex-provider; en/zh/ja/de. TestMoveCodexProvider (listing, bytes, mtime, the DB row and nothing else, backup, byte-identical undo, an active session, a bad id), TestMoveCodexProviderPacked and TestCodexProviderIn fail without the change, as does sessions-codex-provider.test.cjs (WebKit, en and zh: magpie's dialog, Cancel posts nothing, an unticked one stays, Undo, no click moves the page, no left border, translations); sessions-manage, -toolbar, -purge, -folder-delete, -wsl-delete and click-scroll pass. Not tried against a real CC Switch history. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. 2026-10-05 17:26:19 +08:00
tobitege 21702d01ff Add German translation; harden paths against unset env vars (#770)
* Add German translation and harden paths against unset env vars

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

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

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

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

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

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

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

* Sandbox the tests main added since

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

* agentenv: PI_CONFIG_DIR and OPENCODE_DB take relative names on purpose

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

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

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

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

The appdir import joins the group of magpie's own imports where a file has one. rooted's comment now says that Go takes a Git Bash path like /c/x as \c\x on the current drive, not C:\x.
2026-10-04 23:30:56 +08:00
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
Lyric WaiandClaude Opus 5.5 5fb7799b11 agents: Mister Morph (morph) — picking a magpie model points ~/.morph/config.yaml's llm at the gateway ($MISTER_MORPH_CONFIG names another file) (#728)
Morph has no list of providers to add magpie to, so magpie takes llm over:
inference_provider openai_response_compatible (the Responses API, morph's
own default), endpoint the gateway's /v1 (morph adds no second /v1), api_key
magpie's token, model the catalog id, context_window_tokens the window magpie
knows (morph knows one only from its own list), and headers.User-Agent
mistermorph so the gateway credits its requests to morph. llm.provider is
left alone: morph derives it from inference_provider. What the user had under
those keys is stashed and put back when they pick a model of their own or
reset; comments, other headers and other keys stay. An effort field sets
llm.reasoning_effort. Found by ~/.morph, not by the morph command (a common
name). Also on WSL. In NO_PICKER, as morph's /models switches profiles, not
models.

TestMorph and TestMorphContextWindow. go vet, builds (GOOS=linux/darwin
-tags nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp
HOME pass, but for TestPluginProxy, TestPluginSOCKSProxy and
TestCodexArchivedSameSizeRewriteInvalidatesDiscovery, which fail on main too.
With the installed morph against a sandbox gateway and a mock vendor, morph
run's requests went to /v1/responses, were relayed as chat completions and
were credited to morph; magpie morph default restored the user's file. Not
run against a real model.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 07:57:44 +08:00
mofelee 38b4a9d984 feat: export gateway and agent conversation waterfalls to Langfuse (#581) 2026-10-03 01:43:32 +08:00
yetone ff36a2994f agents: T3 Code gets magpie as a provider of its own, every magpie model in it (KevinXC on Discord: T3 Code's Settings → Providers lists Codex, Claude, Cursor… each with "Add custom model"; magpie's models should be there without adding them one by one) — T3 Code (pingdotgg/t3code) keeps its settings in ~/.t3/userdata/settings.json ($T3CODE_HOME/userdata), sparse JSON its server watches and reloads on an outside edit, and besides its built-in providers takes provider instances of one's own: {"providerInstances":{"<id>":{"driver","displayName","enabled","environment":[{name,value,sensitive}],"config":{"customModels":[{slug,name}]}}}}. A claudeAgent instance runs Claude Code (the Agent SDK, ~/.claude settings read as Claude Code reads them) with its environment added, and sends a custom model's slug as Claude Code's model. The new t3code agent (t3code.go) adds the instance "magpie": driver claudeAgent, displayName magpie, ANTHROPIC_BASE_URL the gateway and ANTHROPIC_AUTH_TOKEN the token Claude Code is routed with (so its requests are Claude Code's to the gateway, haiku tier and all), not sensitive so T3 keeps them in the file rather than its secret store, and every magpie model shown to T3 Code as a custom model, a 1M one marked [1m] as for Claude Code (claude1MFor). The binary path and Claude home the user gave T3's own Claude (its instance, else the legacy providers.claudeAgent) are carried over. It is magpie's alone: the user's instances, their custom models and T3's other keys stay, off takes only it out, and the catalog sync rewrites it only where it is. T3's Codex lists Codex's model/list, which is magpie's catalog once Codex is routed through magpie, so it needs nothing; T3's own Claude asks for Claude's models by their full ids, which the gateway already sends to Claude Code's tiers (claudeTierStandIn). Found by ~/.t3 or, on a Mac, T3 Code (Alpha).app / T3 Code.app; T3CODE_HOME is in agentenv.Vars. Its icon is T3's mark (assets/prod/logo.svg, MIT), its letters alone so it draws as a mask; the option's note has its Chinese. Checked against T3 Code's own schema (effect 4.0.0-rc.115, packages/contracts at 54084ae1): the file magpie writes decodes as ServerSettings, magpie's config as ClaudeSettings, and encodes back to the same instance. Not run against a live T3 Code. TestT3CodeInstance (the user's instance, legacy Claude binary path and T3's key kept through on, sync and off; magpie's instance decoded strictly; found only with ~/.t3; among All()), TestT3CodeHomeAndInstance ($T3CODE_HOME, a file made, the Claude home carried from the user's claudeAgent instance) and TestStateListsT3Code are new. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui -count=1 ./... under a temp HOME pass. 2026-10-03 00:15:53 +08:00
yetone 1e4174cada sessions: Cursor CLI's chats are on the Sessions page, each listed, opened, resumed and deleted to magpie's trash as another agent's sessions are (@ownsh1p on X: why doesn't the Sessions page show cursor-agent's sessions?) — magpie read the session files of Claude Code, Codex, OpenCode, Pi and the rest, and none of Cursor's. cursor-agent (2026.10.01) keeps a chat in its config folder ($CURSOR_CONFIG_DIR, else $XDG_CONFIG_HOME/cursor, else ~/.cursor) at chats/<md5 of the cwd>/<chat id>/store.db, a SQLite file of two tables as its run store (cursor-sdk-local-runtime's sqlite-blob-store) makes them: meta, whose row "0" is the chat's metadata as JSON in hex (agentId, name — "New Agent" until it has a title — createdAt, lastUsedModel, latestRootBlobId and, for a subagent's chat, subagentInfo with its parent's id), and blobs, content-addressed by the hex of their SHA-256. The root blob is a ConversationStateStructure (protobuf): field 1 the ids of the conversation's messages in order, field 13 the archives a compaction summed up (each one's field 1 the messages it summed up), field 26 when the conversation started. Each message is the AI SDK's JSON ({role, content: a string or parts — text, reasoning, tool-call, tool-result, image, file}). Newer builds also write meta.json beside store.db: title, createdAtMs, updatedAtMs, isSubagent and cwd, the one place the chat's folder is said in words (the folder's name is only its hash). This was read from the installed bundle and matches what other projects that read these stores found; no real chat was opened.
internal/sessions/cursor.go reads it: a chat is a session with the title meta.json or the store gives it (not "New Agent"), else its first typed prompt (the text in <user_query>, with Cursor's context tags — user_info, rules, system_reminder and the rest — left out, as Cursor's own transcript writer leaves them out); its cwd from meta.json; its start and last from meta.json and the store; prompts, replies and tool calls counted on the day it was last at work, since a message carries no time of its own. A subagent's chat, a sibling folder whose subagentInfo names its parent, is counted in its parent's session. Its resume line is cd '<cwd>' && cursor-agent --resume <id> — cursor-agent looks a chat up by the cwd's hash, so a chat whose cwd isn't known (no meta.json) gets none. There are no tokens or cost: Cursor counts and bills them on its servers and keeps none in the chat. A chat's WAL log and meta.json count in its size and time, so a chat Cursor is writing is read again and is never deleted mid-run. Reading writes nothing in Cursor's folder: SQLite opening a WAL store even read-only makes -wal and -shm files beside it and writes its read marks in -shm, which would also make every chat look written a moment ago and refuse its delete; a store with no log is opened immutable, and one with a log is read from a copy of store.db and its -wal in a temp folder that is removed after. Delete moves the chat's folder, its subagents' and its transcripts (~/.cursor/projects/<cwd slug>/agent-transcripts/<id>, .jsonl, .txt) to magpie's trash, and Restore puts them back. CURSOR_CONFIG_DIR and CURSOR_DATA_DIR join internal/agentenv's list, and the sessions tests' setup clears XDG_CONFIG_HOME, so no test reads a developer's own chats. Cursor's ACP sessions (acp-sessions/) and the editor's chats are not read.

cursor_test.go builds stores as Cursor writes them in a temp folder — hex meta, SHA-256 blobs, a hand-encoded root with a compaction archive, a subagent's chat, one kept open by its writer so all of it is in -wal, one with its meta as plain JSON and no meta.json, one never spoken in — and checks the listing (two sessions, the subagent's in its parent's, the title from the typed prompt, cwd, start and last, no tokens, the resume line, none without a cwd), the counts and the tools, that reading leaves every file in Cursor's folder as it was (it made store.db-wal and store.db-shm before the immutable open and the copy), that a chat just written is refused, and Delete and Restore of the chat, its subagent's and its transcripts. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui -count=1 ./... under a temp HOME pass.
2026-10-02 20:47:22 +08:00
Lin Huazhong 7586df4400 test: usage's and provider's TestMains clear the agents' variables too, and a new agent's variable left out of the list now fails (yetone, reviewing #543: internal/usage/main_test.go's TestMain still clears no agent variables, so a CLAUDE_CONFIG_DIR left in the shell reaches the real ~/.claude there too; nothing fails yet when a new agent's variable is left out of Vars) — both TestMains gave their package a home of its own and cleared no agent variable, the provider one as much as the usage one that was named. A CLAUDE_CONFIG_DIR left in the shell reached the real ~/.claude through the ledger's sessions.Calls (usage/ledger.go:122): with a canary Claude folder holding three of the sessions package's fixtures, six usage tests fail — TestLedger, TestSubscriptionListPrice, TestLedgerUsesTheStatedPrice, TestLedgerConfiguredPricesAndRefresh and the two Antigravity ledger ones — and pass with the clearing; provider passes with the same canary either way, but its Accounts read what an agent keeps on the machine too. #543 missed these by how it looked for its sites: by the clearing loops a sandbox already had, which one with no loop at all has no way to answer. TestFolderVarsAreListed reads the sources of the four packages an agent's folder variable is read in — this one, sessions, provider and library, the rest of the repository reading only magpie's own variables, the process' and the desktop's — and fails on a name that is neither in agentenv.Vars nor excused here with a reason, so a new agent's variable no longer waits for a developer's shell to meet one. It reads them as files, since a test in sessions or provider cannot import this package, which imports them; it asks for one name each way its pattern matches, so a change in how a variable is read empties the scan loudly instead of passing it, and for every name excused, so no excuse outlives its variable. What it cannot see is a name the sources build at runtime, as omoDir reads its two out of a slice (omo.go:33); both are listed, and a third taken the same way would not be caught. The package doc's VarsTest is TestVarsAreRead. TestFolderVarsAreListed fails without the change when KIMI_CODE_HOME is taken out of Vars, or when a pretend NEWAGENT_HOME is read in agents.go, and names the file that reads it; the six usage tests fail without theirs. On Windows 11 the whole suite answers as it did before the change: 31 packages pass, and the same 15 tests in internal/library, internal/gateway and internal/agent fail for what they failed for before. go vet ./..., go build -tags nogui (windows, GOOS=linux, GOOS=darwin), go vet -tags nogui for linux and darwin over every package touched, and gofmt pass. (#549) 2026-10-02 18:41:27 +08:00
Lin Huazhong d8ebf2b893 test: one list of the agent folders a sandbox clears, so no sandbox misses an agent (yetone, on #522: A shared list of env vars for the other sandboxes would be a welcome follow-up) — every sandbox kept its own list of the variables that point at an agent's folder, and each had drifted: the gateway's cleared 5 of the 32 magpie reads, the provider package's 2, the tui's 10, the library's 20. A sandbox clearing 2 meets whatever Grok, Kimi, Hermes, MiniMax, Qoder or ZCode the developer has installed, which is how #522 came about — Qoder's two were in the library's list and in none of the four that failed. internal/agentenv is now the one list, and the 21 sandboxes that fake an environment with no agent in it clear it, each keeping beside it the variables that are not an agent's: APPDATA and LOCALAPPDATA, which a sandbox sets to a folder of its own rather than clearing, VISUAL and EDITOR. A name in the list is one magpie reads, and TestVarsAreRead walks the sources to say so, which is how two names a sandbox had been clearing turned out to be read by nothing at all — GEMINI_CLI_HOME and OPENCODE_CONFIG in internal/backup's, while OPENCODE_CONFIG_DIR, the one internal/agent reads (agents.go:545), was not in it. Where a sandbox set an agent's folder to a fixture of its own, the clearing now comes first, since a list of 32 wipes what a list of 5 left standing. Not converted: the omp tests' four variables, which are the ones omp's own lookup consults (a Pi fork) rather than every agent's, and the warm-up's Claude Code traffic flags, which are not folders — CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC stays out of the list for that reason too, its one test setting it on purpose. On Windows 11 the whole suite answers as it did before the change: 31 packages pass, and the same 15 tests in internal/library, internal/gateway and internal/agent fail for what they failed for before (symlinks this machine has no privilege for, and TestSuffixModesReachAgentFiles). go vet ./..., go build -tags nogui (windows, GOOS=linux, GOOS=darwin), and go vet -tags nogui for linux and darwin over every package touched pass. (#543) 2026-10-02 17:22:16 +08:00