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 -5
View File
@@ -2,12 +2,14 @@
* @file account-pins.js
* @description Account pins: an admin's choice that one server-mode user's
* new sessions of a tool run on one named pool account instead of the one the
* balancer would pick (claude-pool.js / codex-pool.js `pick`). One pin per
* user and tool (`account_pins`); the user is the workspace's OWNER
* balancer would pick (claude-pool.js / codex-pool.js `pick`) — the Users
* page's "switch a user's account". One pin per user and tool
* (`account_pins`); the user is the workspace's OWNER
* (`lanes.owner_id`), never whoever clicked Start. A pin is a preference the
* pool still bounds: an account a pane may not use (expired, or a login the
* pane's home cannot take) falls back to the balancer, and a login never
* replaces another one a live pane in the same home runs on.
* pane's home cannot take) falls back to the balancer. A Claude login is
* written into the user's home right away (`applyPin`), over the pool login
* their running panes use, so those move to it without a restart.
*
* Server mode only: in local mode there are no users and nothing is pinned.
* Removing the account drops its pins (`dropAccount`); deleting the user drops
@@ -79,6 +81,20 @@ function setPin(userId, tool, account) {
return { userId: user.id, username: user.username, account: name };
}
/**
* Make a pin take effect now where it can: a Claude login is written into
* the user's home at once (claude-pool `switchHome`), so their running panes
* move to it without a restart. Anything else goes in at the next spawn.
* Answers what happened (see `switchHome`).
*/
async function applyPin(userId, tool) {
const pin = pinFor(userId, tool);
if (!pin || tool !== "claude") return "next-session";
const user = db().prepare("SELECT username FROM users WHERE id = ?").get(userId);
const member = user ? require("./run-as").accountForUser(user.username) : null;
return poolOf(tool).switchHome(member, pin);
}
/** A removed account takes its pins with it. */
function dropAccount(tool, account) {
try {
@@ -88,4 +104,4 @@ function dropAccount(tool, account) {
}
}
module.exports = { TOOLS, pinFor, listPins, setPin, dropAccount };
module.exports = { TOOLS, pinFor, listPins, setPin, applyPin, dropAccount };