Skip to content

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

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

Adam-Dalloul wants to merge 25 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 steering, controls, history and resume behavior.

Stacked on #471 and #470. Default continues to use the existing sign-in. 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.

The account is saved on the conversation. Reconnect, config restart, 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 remain distinct; adopting the same physical session through an old custom slot cannot create a second active writer.

Existing Claude conversations now have an account picker in the chat. Switching keeps the conversation, tab and native session id, with the full transcript, tool results and file-history. The target must acknowledge restoring the session before the saved account changes. Active turns and background work are refused; failed loads roll back, conflicting destination history is preserved, and a disconnected caller cannot interrupt the commit or rollback. Switching back carries the newer history forward. Other agents retain their saved account until equivalent native transfer is verified.

Supported: Claude Code, Codex, Gemini, OpenCode, Grok, Hermes, CodeBuddy, Cline, Kimi Code, Pi, DeepSeek, Qoder, Antigravity and Cursor. OpenClaw continues to use the account on its configured gateway. Cursor also isolates its credential store, since moving its chat folder alone does not move authentication. Shared MCP changes and skills reach account homes while preserving account-local entries and authentication files. Existing per-agent MCP limitations still apply.

Pi project-trust decisions and their acknowledgements also follow the selected account, including the restart after approval. Windows skill links are removed without touching the shared directory. Connection discovery retains the existing null response for a missing conversation and cannot use that absence to attach to Default.

Validation:

  • Frontend lint, TypeScript, focused account-picker tests and production export.
  • Rust account isolation, migration, history import, fork, transfer and binding tests, plus Clippy with warnings denied.
  • Authenticated HTTP checks cover complete transcript bytes, round trips through named accounts and Default, failed-load rollback, active-turn refusal, divergent-history preservation, stale reconnect rejection and caller cancellation.
  • Headless Chromium exercises the actual account picker at desktop and mobile widths, retaining the same conversation and full history. These tests use synthetic accounts and an offline ACP fixture, without live provider OAuth or generation.

Named profiles also clear OpenCode API-key overrides from the default launch environment. The account-isolation regression test covers those keys. All seven CI jobs pass on cef21c9b: frontend and desktop/server checks on Linux, macOS and Windows.

@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 10 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).

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