feat(pool): an admin's switch moves a user's Claude login at once

PUT /api/accounts/pins (and agenthub accounts pin) now writes a pinned
Claude login into the user's home right away (claude-pool.switchHome):
every home's newer pair is copied back first, the member's own login is
never written over, and no pane is restarted — running sessions take the
new pair at their next credentials read. A live home stays on the login it
holds, so unpinning does not flip it back. The Users page calls it
"Switch a user's account" and says what happened.
This commit is contained in:
2026-10-19 10:23:44 +07:00
parent 9c98638ec0
commit 776ad6466f
16 changed files with 208 additions and 89 deletions
+21 -14
View File
@@ -355,7 +355,7 @@ claude setup-token
agenthub accounts add team-1
agenthub accounts list # live panes, $ and tokens in the last 5 h / 7 d, quota, state
agenthub accounts remove team-1
agenthub accounts pin team-1 mia # mia's new sessions always run on team-1 (unpin mia to undo)
agenthub accounts pin team-1 mia # switch mia to team-1 (unpin mia = automatic again)
```
In server mode an admin can do the same on the **Users** page, in the
@@ -424,19 +424,26 @@ How a new session picks its account (`server/lib/claude-pool.js`):
pane still needs that member's own `/login`.
- A token more than a year old is skipped.
- A resumed session picks an account again, like a new one.
- **Pinning a user to one account.** An admin can skip the ranking for one
user: **Pin a user** in the Account pool card (or
`agenthub accounts pin <account> <user> [--tool codex]`, `unpin <user>` to
undo; their own password again, audited). Every new session in a workspace
that user OWNS then runs on that account, even when it is full — other users
are not affected, and the account is not reserved for them. The pin is a
preference the pool still bounds: an expired account, or a login a host pane
may not take, falls back to the ranking; and since a home holds one
`.credentials.json`, a pinned login waits until that user's panes on another
pool login have ended (a setup-token or API key pin goes in by env and
applies at once). Running sessions keep their account. Removing the account
drops its pins; deleting the user drops theirs. Pins apply in server mode only
(`server/lib/account-pins.js`).
- **Switching a user to one account.** An admin can take one user off the
ranking: **Switch a user's account** in the Account pool card (or
`agenthub accounts pin <account> <user> [--tool codex]`, `unpin <user>` for
automatic again; their own password again, audited). Every session in a
workspace that user OWNS then runs on that account, even when it is full;
other users are not affected, and the account is not reserved for them.
- **A Claude login switches at once, without a restart.** The server first
copies every home's newer pair back into the pool (so the login leaving
the home keeps its only live refresh token), then writes the new login
into the user's `~/.claude/.credentials.json` as them. New sessions start
on it; sessions already running keep their process and move to it the
next time Claude Code re-reads its credentials (it does so before it
refreshes its access token, so at the latest when that token expires).
A login the user made themselves (`/login`) is never written over — the
card says so. Their sessions are booked to the account they started on.
- A setup-token, a Codex account, or a host pane gets the account at its
next spawn; running ones keep theirs.
- An expired account falls back to the ranking. Removing the account drops
the switch; deleting the user drops theirs. Server mode only
(`server/lib/account-pins.js`).
- Quota is read with each account's own token, in the background, at most every
5 minutes. If the usage endpoint refuses a setup-token, the state column shows
`no quota (…)` and ranking uses spend alone.