Skip to content

feat(accounts): add native accounts and in-place Claude account switching - #919

Open
Adam-Dalloul wants to merge 44 commits into
spacering-net:mainfrom
Adam-Dalloul:native-built-in-agent-accounts
Open

Adam-Dalloul wants to merge 44 commits into
spacering-net:mainfrom
Adam-Dalloul:native-built-in-agent-accounts

Conversation

@Adam-Dalloul

@Adam-Dalloul Adam-Dalloul commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Extra subscriptions currently require custom agents, which lose behavior tied to the built-in agent type. This adds named accounts under the built-in agents, so separate sign-ins can run concurrently while keeping native controls, history and resume behavior.

Stacked on #471 and #470. Settings can create a profile or adopt an existing account folder, rename it, remove it without deleting files, and open the official sign-in flow. The browser keeps a copyable command when a terminal cannot be opened. Default continues to use the existing sign-in.

The account is saved on the conversation. Reconnect, fork, same-family delegation, transcript readers, imports, title refresh and selector preferences retain that binding. Removed or missing accounts fail explicitly. Identical native session IDs in different accounts stay distinct, and adopting a session from an older custom slot cannot create a second active writer.

Top-level Claude conversations can switch accounts in the same chat. The conversation, tab, native session ID, transcript, tool results and file history are retained. The target must acknowledge restoring the session before the saved account changes. Active turns and background work are refused; failed loads roll back and conflicting destination history is preserved. Other agents keep their original account. Account-specific mentions and remaining usage are in #472.

Named accounts cover Claude Code, Codex, Gemini, OpenCode, Grok, Hermes, CodeBuddy, Cline, Kimi Code, Pi, DeepSeek, Qoder and Antigravity. Cursor supports them on Windows and Linux. On macOS, use Default: Cursor's credential store requires replacing the home directory, so named-account creation and launches are disabled there. Existing records, folders and transcript readers remain intact. Kiro and ZCode do not yet support named accounts; OpenClaw uses its gateway's account.

OpenCode keeps HOME and USERPROFILE, isolates its XDG folders, and removes inherited OPENCODE_AUTH_CONTENT and OPENCODE_DB overrides. OpenCode and Cursor preserve GitHub CLI's original config directory, including an explicit setting. Other tools using the separate configuration folders may need their own setup; the Accounts card explains that. Shared MCP and skills updates preserve account-local entries and authentication files.

Backups retain account names, paths and bindings but exclude named-account folders and their transcripts/sign-ins. The backup screen explains this in all ten locales. The account guide covers separate backups and restoring the saved paths; a missing-folder error names the folder to restore.

The follow-up preserves the maintainer's migration-order fix, locale ordering and translated unsupported-agent notes through 21cd032a. The account migration remains last as m20261017_000001_agent_account.

Validation on the combined stack: 198 focused frontend tests, TypeScript, ESLint, production export, server/MCP build, and real local HTTP/MCP plus desktop and phone browser checks. These exercise account binding, explicit Default, inheritance, removal refusal, child/Assistant resume, full-history switching and retained model/mode/draft. Native all-target server/MCP Clippy passes with warnings denied.

Real authentication checked on Windows: Claude Code 2.1.280 reports two distinct existing Claude sign-ins through their respective CLAUDE_CONFIG_DIR settings, and both authenticate to the live usage endpoint. Credential bytes remain unchanged. This does not claim a fresh OAuth login or live sign-ins for every agent. OpenCode 1.14.50 reads separate synthetic auth/config stores while Git, npm and gh retain their fixture settings; the pinned OpenCode 1.19.1 and Cursor 2026.10.14 sources were also checked.

CI for af3ac231 passes all seven checks: frontend validation and desktop/server Rust checks on Linux, macOS and Windows. The Windows desktop job compiles tests and runs Clippy; it does not execute the desktop test suite.

Settings MCP still targets the built-in family row. Extra custom
agents whose spec.env sets CLAUDE_CONFIG_DIR, CODEX_HOME, GROK_HOME,
GEMINI_CONFIG_DIR, or OPENCODE_CONFIG_DIR now receive the same server
on upsert and lose it on remove.

Claude extra homes only get enabledPlugins; server defs stay in the
shared ~/.claude.json. Auth files are never copied.
Custom agents whose spec.env sets a family isolator now have a Sign in
card on the Agents page. That opens the official CLI login
(claude/codex/grok/gemini/opencode) with the isolator env set.

No tokens are copied. Account 1 stays on the built-in home. Headless
and unit-test paths return the command instead of spawning a terminal.
@Adam-Dalloul
Adam-Dalloul marked this pull request as ready for review October 11, 2026 04:40
Settings MCP still targets the built-in family row. Extra custom
agents whose spec.env sets CLAUDE_CONFIG_DIR, CODEX_HOME, GROK_HOME,
GEMINI_CONFIG_DIR, or OPENCODE_CONFIG_DIR now receive the same server
on upsert and lose it on remove.

Claude extra homes only get enabledPlugins; server defs stay in the
shared ~/.claude.json. Auth files are never copied.
Custom agents whose spec.env sets a family isolator now have a Sign in
card on the Agents page. That opens the official CLI login
(claude/codex/grok/gemini/opencode) with the isolator env set.

No tokens are copied. Account 1 stays on the built-in home. Headless
and unit-test paths return the command instead of spawning a terminal.
@Adam-Dalloul
Adam-Dalloul force-pushed the native-built-in-agent-accounts branch from 4c9548b to 9ee89cc Compare October 11, 2026 05:23
@Adam-Dalloul Adam-Dalloul changed the title feat(accounts): keep extra accounts on built-in agent types feat(accounts): add native accounts and in-place Claude account switching Oct 11, 2026
Adam-Dalloul and others added 4 commits October 11, 2026 08:35
…tches

A connection whose task ends on its own (the agent failed to load or
exited, or the task panicked) now parks its child in the manager's
draining list, as every explicit teardown does, so a missing map entry is
never taken as proof that the process stopped. The deferred cleanup is
spawned on the connection's runtime handle, because the guard drops
outside any runtime context. Account switches wait for every writer of the
session before copying it and before rolling a target back, and a child
parked after its task finished leaves the list as soon as it is reaped.

A switch's staging copies are removed once the switch is committed, a
rollback completes, or staging itself fails. They are kept only when a
rollback fails.
Launching a named account re-applies the shared MCP servers to config
files that the account's own CLIs rewrite too. The config writers now
leave a file alone when its content would not change, and the config
staleness check no longer syncs account folders at all.

Pi accounts mirror pi's own server list instead of the empty ACP
forwarding view, which removed their servers again on every launch. A
missing account folder no longer fails the import scan or every MCP
change; that account is skipped until its folder is restored.
…ount

A conversation switched to another account keeps its session in that
account's folder. Work-task retries, returns and merges, their
fresh-session fallback, and chat-channel resumes now launch with the
conversation's saved account instead of the Default one, which the prompt
gate then refused.
A tab that starts as a draft keeps a virtual runtime key, so its detail is
never fetched. After the first send it read its account as Default and
held auto-connect and reconnects for good, for every user. The account now
comes from the binding the view set itself, then the newer of the
workspace list and detail copies, and auto-connect waits only while none
is known. The in-chat switcher stays disabled until then.

The conversation list gets the in-flight ordering folders already have: an
older snapshot is discarded, and a row an upsert changed while a refresh
was in flight keeps the upsert's copy.
Connecting or sending to a conversation of another agent was refused
with "This conversation belongs to a different agent account", which
sends the user looking for an account problem. The refusal now names
the other agent; a different account of the same agent keeps the
account message.
Removing an account keeps its folder and its conversations, but adding
that folder back created a new account. The conversations stayed bound
to the removed one, so none of them could continue.

Adding the folder of a removed account of the same agent now brings
that account back, under the name entered, with its conversations.
The removal dialog names the folder and says so, and a conversation
of a removed account tells which folder to add again.
… it left

A Claude account switch copies the session into the target account and
leaves the source account's copy in place. The import scan listed that
copy as new in the source account, and importing it created a stale
duplicate whose row then blocked switching the conversation back.

A copy is now treated as already imported when a live conversation
under another account holds everything in it: its transcript as a
prefix, and its sub-agent and tool result folder and file history as
older trees, the switch's own test for a copy it may replace. The scan
shows it as imported, an import of it is skipped, and the folder sweep
leaves it out. A copy with anything of its own, or one that appears
in two project folders, stays importable.
Every open conversation tab, hidden ones included, fetched the account
list for itself and refetched it on every agent registry change, so
one change sent a request per tab.

The list is now kept once per agent and shared, the way the agent
registry is: one request per agent on each change, and a refetch after
a web client reconnects, since changes made while it was disconnected
are lost. A failed refresh keeps the last list instead of emptying
every tab at once, which made a draft's chosen account look gone.
…them

A custom agent set up as a second account of a built-in agent (an
extra slot) and a native account of that agent now do the same job,
but their sign-ins had drifted apart. The slot ran the bare login
command, which fails when the CLI is only installed by codeg (the
OpenCode binary, say), and kept credentials the terminal exports,
which can stand in for the login it is meant to create.

Both now build the command the same way: the vendor CLI is resolved
like codeg resolves it, and inherited credentials are cleared unless
the slot sets them itself. The slot's card also points to the built-in
agent's accounts, which keep that agent's features, and names the
folder to add there. OpenCode is the exception, since its slot names
only a config folder.
@xintaofei

Copy link
Copy Markdown
Collaborator

codeg work task 333 is done — #919 (37 files, +2063/-204).

@Adam-Dalloul

Copy link
Copy Markdown
Contributor Author

@xintaofei, the account and usage PRs (#470, #471, #919, and #472) are refreshed on v0.36.0, with conflicts resolved and desktop/mobile checks passing locally. Could you take another look when the remaining CI checks finish?

The account migration is now m20261017_000001_agent_account and is
registered last, after the four migrations v0.36.0 shipped.

A backup records only the last registered migration, and a release
restores any backup whose last migration it knows. With the account
migration in the middle of the list, a backup from this build named
m20261016_000002_conversation_assistant, so v0.36.0 restored it and
then failed to start, since it has no file for the account migration.
Now v0.36.0 refuses such a backup as coming from a newer version.

A database that already ran a build with the old name must have its
seaql_migrations row renamed to the new one.
The locale files now differ from main only by this branch's own
strings. Merging v0.36.0 had moved four of main's blocks to the end of
their sections in all ten locales: MentionPanelSettings,
WindowAttentionSettings, and AcpAgentSettings' kiro and distribution.
No string was lost, but every locale conflicted with later changes next
to those blocks, and keeping main's side of a conflict listed kiro
twice.
Kiro's and ZCode's settings showed the server's English reason, "Named
accounts are not yet supported for this agent.", in every locale. The
accounts card now shows a translated note for any built-in agent
without accounts, and keeps the OpenClaw gateway note.
@xintaofei

Copy link
Copy Markdown
Collaborator

@Adam-Dalloul thanks for refreshing the stack on v0.36.0. The conflict resolutions hold up: I went through git show --remerge-diff for every hand-resolved file in a385417e6 and the earlier merges, and both sides' changes survived, including the review fixes from the last round. Dropping the duplicate /acp_login_extra_agent route in 86c087cb7 was a good catch too, since axum panics on an overlapping method route when the router is built.

I pushed three commits on top of 412f570b1. The first fixes a problem that would otherwise ship in the next release, and the other two are small. Below that are three open items I'd like to settle with you before merging.

1. The account migration now runs after v0.36.0's (2d79f6a10)

After the merge, m20261011_000001_agent_account was registered before the four migrations v0.36.0 already shipped. A backup's manifest names only the last registered migration (latest_migration_name), and that name is what a release checks to decide whether a backup comes from a newer version (evaluate_compat). With that order, a backup from a build of this branch named m20261016_000002_conversation_assistant. v0.36.0 knows that name, so it would accept the backup. Once the restored database was swapped in at startup, v0.36.0's migrator would stop on an applied migration it has no file for, and Codeg would not start.

The migration is now m20261017_000001_agent_account, registered last, so backups made with this build name it, and v0.36.0 refuses them as coming from a newer version. account_migration_runs_after_every_released_migration checks its position against v0.36.0's newest migration and fails with the old order. The migration's content is unchanged, and your account_migration_can_follow_newer_release_migrations test still covers upgrading a v0.36.0 database.

One step for a database that ran an earlier build of this branch: it will no longer start, and the error is

Migration file of version 'm20261011_000001_agent_account' is missing, this migration has been applied but its file is missing

The failed start migrates nothing. Quit every Codeg process that uses that database, then rename the recorded migration. The file is codeg-dev.db for a debug desktop build and codeg.db otherwise, in Codeg's data directory. Keep the quotes, since that path can contain spaces:

sqlite3 "/path/to/codeg-dev.db" "UPDATE seaql_migrations SET version = 'm20261017_000001_agent_account' WHERE version = 'm20261011_000001_agent_account'; SELECT changes();"

It prints 1 when it renamed the row, and 0 when no row currently has the old name, including when it was already renamed. The account tables are already there, so the account migration doesn't run again, and any v0.36.0 migration the database hadn't applied yet runs on the next start.

Backups taken with earlier builds of this branch keep the old name as well. Don't restore one taken after the v0.36.0 merge into v0.36.0. If you restore one into this build, Codeg stops with the same error after the restore, and the same rename fixes it. Backups taken before the merge name the old migration in their manifest, so this build refuses them as coming from a newer version.

2. The locale files add strings without moving main's (e20e93723)

The merge had moved four blocks that v0.36.0 added, MentionPanelSettings, WindowAttentionSettings, AcpAgentSettings.kiro and AcpAgentSettings.distribution, to the end of their sections in all ten locales. No string was lost, but the moves make later edits near those blocks likely to conflict in every locale. The files now differ from main only by this PR's strings.

3. Kiro and ZCode show a translated note (21cd032ac)

Their accounts card showed the server's English reason, "Named accounts are not yet supported for this agent.", in every locale. It now shows AgentAccounts.notSupported, translated in all ten locales. OpenClaw keeps its gateway note.

Checks

On 21cd032ac, all of these pass on macOS: tsc --noEmit, eslint ., vitest run with 9,725 tests, next build, the server lib tests with 5,388 passing, the desktop tests with test-utils with 5,727 passing, and clippy -D warnings for the desktop, server and codeg-mcp targets.

Open before merging

The refresh didn't change these, and I'd like your view on them:

  1. OpenCode, and Cursor on macOS, get the account folder as HOME (OpenCode, Cursor). OpenCode's account also gets its own XDG folders. The tools the agent runs in a named-account session inherit those values, so unless something points them elsewhere, git looks for the user's ~/.gitconfig, npm for ~/.npmrc and gh for its login inside the account folder instead. Is there a way to keep these two agents' credentials per account without replacing HOME? If not, one option is to leave them without named accounts for now, as Kiro and ZCode are.
  2. Real sign-ins. The tests check the isolation against fixture folders. Have you signed in with two real accounts on any of these agents? A short list of which agents you checked, and on which OS, would help us decide what to enable first.
  3. Backups. With "include conversation content" on, a backup packs the transcript folders Codeg resolves from its own environment (external_transcript_sources). It doesn't look at saved account folders, and Codeg's own agent-accounts folder isn't one of the backup sections either. A restore onto another machine therefore brings back the named-account conversations and their account bindings, but not the account folders or their transcripts. That seems fine as a documented limitation for now; I'm noting it so it's tracked.

If any of this misreads the code or your intent, please push back.

@Adam-Dalloul

Copy link
Copy Markdown
Contributor Author

Thanks @xintaofei, I've addressed the isolation and backup points and carried your fixes into #472. The updated description includes the exact validation, including two existing Claude sign-ins on Windows.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants