(null);
- const slug = slugify(name);
+ const slug = slugifyProfileName(name);
+ const profileDir = `~/.${agent}-${slug}`;
- const loginsQuery = useHostUsageLogins(hostUrl, open && !found);
+ // Each opening polls its own logins query, so a result cached by an
+ // earlier opening never becomes this one's baseline.
+ const [opening, setOpening] = useState(0);
+ const [wasOpen, setWasOpen] = useState(open);
+ if (open !== wasOpen) {
+ setWasOpen(open);
+ if (open) setOpening((count) => count + 1);
+ }
+ const loginsQuery = useHostUsageLogins(hostUrl, open && !found, opening);
const setDefault = useSetDefaultUsageAccount(hostUrl);
+ const homeTaken =
+ !switchTarget &&
+ !!loginsQuery.data &&
+ isProfileHomeTaken(loginsQuery.data, agent, slug);
+ const nameUnusable = !switchTarget && name.trim() !== "" && slug === "";
+ // A new profile's command waits for this opening's logins, so it is never
+ // offered for a home that is already signed in.
+ const canSignIn =
+ !!switchTarget || (!!loginsQuery.data && slug !== "" && !homeTaken);
// The first poll result after opening is the baseline; anything beyond it
// is the sign-in we are waiting for.
useEffect(() => {
if (!open) {
baselineRef.current = null;
+ setName("");
setFound(null);
setCopied(false);
setTerminalAttempt(0);
@@ -135,6 +151,12 @@ export function AddAccountDialog({
return;
}
if (found || (terminalAttempt > 0 && !commandSucceeded)) return;
+ if (!switchTarget && !isProfileHomeTaken(logins, agent, slug)) {
+ // Nothing is signed in at the new profile's dir yet, so a login
+ // that lands there from now on is the one being added.
+ baselineRef.current = logins;
+ return;
+ }
const provisionProfile = (selection: string) => {
// Shares the default account's skills, plugins, MCP servers, and
@@ -149,7 +171,7 @@ export function AddAccountDialog({
const selection = switchTarget
? switchTarget.selection
- : `${logins.homeDir}/.${agent}-${slug}`;
+ : profileHome(logins.homeDir, agent, slug);
const fresh = findCompletedLogin({
agent,
credentialKind: switchTarget?.credentialKind ?? credentialKind,
@@ -157,6 +179,7 @@ export function AddAccountDialog({
baseline: baselineRef.current,
current: logins,
commandSucceeded,
+ newProfile: !switchTarget,
});
if (fresh) {
setFound(fresh);
@@ -179,7 +202,9 @@ export function AddAccountDialog({
const command = switchTarget
? switchSignInCommand(switchTarget)
- : addAccountCommand(agent, slug, credentialKind);
+ : slug
+ ? addAccountCommand(agent, slug, credentialKind)
+ : "";
// Codex's API login takes the key at a terminal prompt; every other flow
// finishes in the browser.
const promptsForKey =
@@ -357,8 +382,26 @@ export function AddAccountDialog({
onChange={(event) => setName(event.target.value)}
className="h-9 min-w-0 flex-1 text-sm"
placeholder="work"
+ aria-invalid={homeTaken || nameUnusable || undefined}
+ aria-describedby={
+ homeTaken || nameUnusable ? nameErrorId : undefined
+ }
/>
+ {(homeTaken || nameUnusable) && (
+
+ {homeTaken ? (
+
+ {profileDir} already exists. Choose another name.
+
+ ) : (
+ Use letters A–Z, digits, or hyphens.
+ )}
+
+ )}
Billing
@@ -397,7 +440,7 @@ export function AddAccountDialog({
)}
- {!canRunLocally && commandDetails}
+ {!canRunLocally && canSignIn && commandDetails}
{canRunLocally && open && terminalAttempt > 0 && (
@@ -432,7 +475,7 @@ export function AddAccountDialog({
)}
{terminalAttempt === 0 &&
- (!canRunLocally || loginsQuery.isError) &&
+ ((!canRunLocally && canSignIn) || loginsQuery.isError) &&
loginStatus}
@@ -471,7 +514,7 @@ export function AddAccountDialog({
{canRunLocally && !terminalRunning && (