provider, gui: removing the Codex account in use signs Codex in to another of its accounts first, rather than leaving it signed out (vincentzhang1_55530 on Discord)
Removing a Team account beside a Plus one left Codex at its sign-in screen. ForgetLogin now switches Codex to the next saved account (nextOnForget: the first on, else one whose sign-in still holds, else any other) as Use would, then forgets the removed one; only the last account is still signed out as codex logout does. The accounts list gives the account in use a Remove when there are others. TestCodexSignOutLastAccount and TestCodexLogins fail without the change (SignedInError); codex-signout.test.cjs covers the new Remove in English and Chinese.
This commit is contained in:
1 parent
6d198f7f23
commit
4c35c1b8e6
7 files changed
+87
-28
No files matched your search
@@ -10078,6 +10078,13 @@ function renderAccounts(a, p) {
|
||||
out.title = t("Signs Codex out of this account, as codex logout does");
|
||||
out.onclick = () => askSignOutLogin(a, l);
|
||||
row.append(out);
|
||||
} else if (a.agent === "codex") {
|
||||
// Codex is signed in to another of its accounts first, then this
|
||||
// one goes (ForgetLogin), rather than Codex signed out
|
||||
const forget = el("button", "text quiet", t("Remove"));
|
||||
forget.title = t("Codex is signed in to another of its accounts, and magpie forgets this one; the account itself is untouched");
|
||||
forget.onclick = () => accountAction("login/forget", { agent: a.agent, user: l.user }, t("{user} removed", { user: l.user }));
|
||||
row.append(forget);
|
||||
}
|
||||
} else {
|
||||
const forget = el("button", "text quiet", t("Remove"));
|
||||
|
||||
@@ -620,6 +620,7 @@ const I18N = {
|
||||
"{name} signed out": "已退出 {name}",
|
||||
"{agent} is signed in to {user} now: sign it in to another of its accounts first (Use on that account), then remove this one": "{agent} 当前登录的是 {user}:请先在另一个账号上点「使用」把 {agent} 切过去,再移除这个账号",
|
||||
"Signs Codex out of this account, as codex logout does": "让 Codex 退出这个账号,与 codex logout 相同",
|
||||
"Codex is signed in to another of its accounts, and magpie forgets this one; the account itself is untouched": "Codex 会先登录到它的另一个账号,magpie 再忘掉这个账号;账号本身不受影响",
|
||||
"Codex itself is signed out too, as codex logout does.": "Codex 本身也会退出登录,与 codex logout 相同。",
|
||||
"Sign {agent} out of {user}?": "让 {agent} 退出 {user}?",
|
||||
"{agent} is signed out, as codex logout does, and magpie forgets the account. Sign in again to use it; an open {agent} may need quitting and opening again. The account itself is left as it is.": "{agent} 会退出登录(与 codex logout 相同),magpie 也不再保存这个账号。要再用需重新登录;已打开的 {agent} 可能要退出后重新打开。账号本身不受影响。",
|
||||
@@ -4007,6 +4008,7 @@ const I18N = {
|
||||
"{name} signed out": "{name} からサインアウトしました",
|
||||
"{agent} is signed in to {user} now: sign it in to another of its accounts first (Use on that account), then remove this one": "{agent} は現在 {user} でサインインしています。先に別のアカウントの「使用」で {agent} を切り替えてから、このアカウントを削除してください",
|
||||
"Signs Codex out of this account, as codex logout does": "codex logout と同じく、Codex をこのアカウントからサインアウトします",
|
||||
"Codex is signed in to another of its accounts, and magpie forgets this one; the account itself is untouched": "Codex を別のアカウントにサインインし直してから、magpie はこのアカウントを忘れます。アカウント自体はそのままです",
|
||||
"Codex itself is signed out too, as codex logout does.": "codex logout と同じく、Codex 自体もサインアウトします。",
|
||||
"Sign {agent} out of {user}?": "{agent} を {user} からサインアウトしますか?",
|
||||
"{agent} is signed out, as codex logout does, and magpie forgets the account. Sign in again to use it; an open {agent} may need quitting and opening again. The account itself is left as it is.": "codex logout と同じく {agent} はサインアウトし、magpie はこのアカウントを削除します。再び使うにはサインインし直してください。開いている {agent} は終了して開き直す必要があるかもしれません。アカウント自体はそのままです。",
|
||||
@@ -7380,6 +7382,7 @@ const I18N = {
|
||||
"{name} signed out": "{name} abgemeldet",
|
||||
"{agent} is signed in to {user} now: sign it in to another of its accounts first (Use on that account), then remove this one": "{agent} ist gerade bei {user} angemeldet: Melden Sie {agent} zuerst bei einem anderen seiner Konten an (dort „Verwenden“) und entfernen Sie dann dieses",
|
||||
"Signs Codex out of this account, as codex logout does": "Meldet Codex von diesem Konto ab, wie codex logout",
|
||||
"Codex is signed in to another of its accounts, and magpie forgets this one; the account itself is untouched": "Codex wird bei einem anderen seiner Konten angemeldet, und magpie vergisst dieses; das Konto selbst bleibt unberührt",
|
||||
"Codex itself is signed out too, as codex logout does.": "Codex selbst wird ebenfalls abgemeldet, wie mit codex logout.",
|
||||
"Sign {agent} out of {user}?": "{agent} von {user} abmelden?",
|
||||
"{agent} is signed out, as codex logout does, and magpie forgets the account. Sign in again to use it; an open {agent} may need quitting and opening again. The account itself is left as it is.": "{agent} wird abgemeldet, wie mit codex logout, und magpie vergisst das Konto. Zum erneuten Verwenden neu anmelden; ein geöffnetes {agent} muss eventuell beendet und neu geöffnet werden. Das Konto selbst bleibt unverändert.",
|
||||
|
||||
@@ -681,8 +681,9 @@ login/forget once per account and the line goes; in English and Chinese.
|
||||
couldn't be removed while Codex was signed in to it): its row has Sign out,
|
||||
asked in the app's own dialog; Cancel and Escape post nothing and go back to
|
||||
the editor, Sign out posts login/forget and says so. With another account
|
||||
saved the one in use has no Sign out, and a signed_in refusal is said in the
|
||||
reader's language. No select, no left border, the page doesn't move; in
|
||||
saved the one in use has no Sign out but Remove, which posts login/forget
|
||||
(vincentzhang1_55530 on Discord: magpie signs Codex in to another account
|
||||
first), and a signed_in refusal is said in the reader's language. No select, no left border, the page doesn't move; in
|
||||
English and Chinese.
|
||||
|
||||
`nokey-editor.test.cjs` opens two local Ollama providers saved with no key,
|
||||
|
||||
@@ -48,8 +48,8 @@ function serve(lang, logins, posts, refuse) {
|
||||
}
|
||||
|
||||
const words = {
|
||||
en: { out: "Sign out", cancel: "Cancel", ask: "Sign Codex out of me@example.com?", text: /Codex is signed out, as codex logout does/, done: "Codex signed out of me@example.com", refused: /Codex is signed in to me@example\.com now: sign it in to another of its accounts first/ },
|
||||
zh: { out: "退出登录", cancel: "取消", ask: "让 Codex 退出 me@example.com?", text: /codex logout/, done: "Codex 已退出 me@example.com", refused: /Codex 当前登录的是 me@example\.com:请先在另一个账号上点「使用」/ },
|
||||
en: { out: "Sign out", remove: "Remove", cancel: "Cancel", ask: "Sign Codex out of me@example.com?", text: /Codex is signed out, as codex logout does/, done: "Codex signed out of me@example.com", refused: /Codex is signed in to me@example\.com now: sign it in to another of its accounts first/ },
|
||||
zh: { out: "退出登录", remove: "移除", cancel: "取消", ask: "让 Codex 退出 me@example.com?", text: /codex logout/, done: "Codex 已退出 me@example.com", refused: /Codex 当前登录的是 me@example\.com:请先在另一个账号上点「使用」/ },
|
||||
};
|
||||
|
||||
for (const engine of (process.env.BROWSER ? [process.env.BROWSER] : ["chromium", "webkit"])) {
|
||||
@@ -112,12 +112,23 @@ for (const engine of (process.env.BROWSER ? [process.env.BROWSER] : ["chromium",
|
||||
assert.deepEqual(errors, []);
|
||||
});
|
||||
|
||||
test(`${engine} ${lang}: with another account saved, the one in use has no Sign out`, async (t) => {
|
||||
const { page, errors } = await open(t, two);
|
||||
// vincentzhang1_55530 on Discord: the one in use is removed as the
|
||||
// others are, and Codex is signed in to another first (ForgetLogin)
|
||||
test(`${engine} ${lang}: with another account saved, the one in use has Remove, not Sign out`, async (t) => {
|
||||
const { page, errors, posts } = await open(t, two);
|
||||
for (const user of ["me@example.com", "work@example.com"]) {
|
||||
const row = page.locator(".editor .acc", { hasText: user });
|
||||
assert.equal(await row.getByRole("button", { name: w.out, exact: true }).count(), 0, user);
|
||||
}
|
||||
const remove = page.locator(".editor .acc", { hasText: "me@example.com" }).getByRole("button", { name: w.remove, exact: true });
|
||||
assert.equal(await remove.count(), 1, "the one in use has Remove");
|
||||
assert.ok(await remove.getAttribute("title"));
|
||||
await remove.click();
|
||||
await page.waitForFunction(() => document.querySelector("#status")?.textContent.includes("me@example.com"));
|
||||
assert.deepEqual(posts, [{ agent: "codex", user: "me@example.com" }]);
|
||||
const missing = await page.evaluate(() => ["Codex is signed in to another of its accounts, and magpie forgets this one; the account itself is untouched"]
|
||||
.filter((k) => !I18N.zh[k] || !I18N.ja[k] || !I18N.de[k]));
|
||||
assert.deepEqual(missing, [], "every string has its zh, ja and de");
|
||||
assert.deepEqual(errors, []);
|
||||
});
|
||||
|
||||
|
||||
@@ -10,34 +10,37 @@ import (
|
||||
)
|
||||
|
||||
// Codex's only account is signed out when it is removed, as codex logout
|
||||
// does (mamba on Discord); one with another saved is still refused, with a
|
||||
// SignedInError the GUI says in the reader's language; and ForgetAccount,
|
||||
// which takes the one in use last, signs Codex out too.
|
||||
// does (mamba on Discord); one with another saved signs Codex in to that
|
||||
// one first (vincentzhang1_55530 on Discord: a Team account removed beside
|
||||
// a Plus one left Codex at its sign-in screen); and ForgetAccount, which
|
||||
// takes the one in use last, signs Codex out too.
|
||||
func TestCodexSignOutLastAccount(t *testing.T) {
|
||||
home := signIn(t)
|
||||
auth := filepath.Join(home, ".codex", "auth.json")
|
||||
rememberLogins(true)
|
||||
var signedIn *SignedInError
|
||||
|
||||
// another saved: the one signed in to is refused, as before
|
||||
// another saved: the one signed in to goes, and Codex is signed in to
|
||||
// the other, its own sign-in back in auth.json
|
||||
codexSignIn(t, home, "work@example.com", "r-work")
|
||||
rememberLogins(true)
|
||||
err := ForgetLogin("codex", "work@example.com")
|
||||
var signedIn *SignedInError
|
||||
if !errors.As(err, &signedIn) || signedIn.Agent != "codex" || signedIn.User != "work@example.com" {
|
||||
if err := ForgetLogin("codex", "work@example.com"); err != nil {
|
||||
t.Fatalf("the one in use with another saved: %v", err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "Codex is signed in to work@example.com now") {
|
||||
t.Fatalf("message: %v", err)
|
||||
live, ok := liveLogin("codex")
|
||||
if !ok || live.User != "me@example.com" {
|
||||
t.Fatalf("Codex signed in to %q (%v), want me@example.com", live.User, ok)
|
||||
}
|
||||
if _, err := os.Stat(auth); err != nil {
|
||||
t.Fatalf("auth.json went on a refusal: %v", err)
|
||||
if b, _ := os.ReadFile(auth); strings.Contains(string(b), "r-work") {
|
||||
t.Fatalf("auth.json still the removed one's: %s", b)
|
||||
}
|
||||
rememberLogins(true)
|
||||
if ls := Logins("codex"); len(ls) != 1 || ls[0].User != "me@example.com" || !ls[0].Active {
|
||||
t.Fatalf("listed: %+v", ls)
|
||||
}
|
||||
|
||||
// the other forgotten, the one left is signed out
|
||||
// the one left is signed out
|
||||
if err := ForgetLogin("codex", "me@example.com"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ForgetLogin("codex", "work@example.com"); err != nil {
|
||||
t.Fatalf("the only account: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(auth); !os.IsNotExist(err) {
|
||||
|
||||
@@ -946,7 +946,8 @@ func putClaudeLogin(l savedLogin) error {
|
||||
|
||||
// ForgetLogin drops a remembered account. The one an agent is signed in to
|
||||
// now can't be forgotten while it has another to be signed in to; it would
|
||||
// only be remembered again (SignedInError). Codex's last one is signed out
|
||||
// only be remembered again (SignedInError). Codex is signed in to another
|
||||
// of its accounts first (nextOnForget), and its last one is signed out
|
||||
// instead, as `codex logout` does (mamba on Discord: a single Codex account
|
||||
// couldn't be removed at all).
|
||||
func ForgetLogin(agent, user string) error {
|
||||
@@ -979,6 +980,18 @@ func ForgetLogin(agent, user string) error {
|
||||
case "gemini", "antigravity":
|
||||
return forgetGoogleLogin(agent, user)
|
||||
}
|
||||
if agent == "codex" {
|
||||
// the account Codex is signed in to, with another saved: Codex is
|
||||
// signed in to that one first, as its Use would, and this one is
|
||||
// forgotten then, rather than Codex left signed out or the removal
|
||||
// refused (vincentzhang1_55530 on Discord: a Team account removed
|
||||
// beside a Plus one, and Codex was at its sign-in screen)
|
||||
if next := nextOnForget(agent, user); next != "" {
|
||||
if err := SwitchLogin(agent, next); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
signedOut, err := forgetLogin(agent, user)
|
||||
if signedOut {
|
||||
// gone from the agent too: nothing of it is served any more
|
||||
@@ -987,6 +1000,29 @@ func ForgetLogin(agent, user string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// nextOnForget is the account an agent is signed in to in place of user,
|
||||
// which is being removed: "" when it isn't signed in to user, or has no
|
||||
// other saved. The first other in the order that is on, else one whose
|
||||
// sign-in still holds, else any other.
|
||||
func nextOnForget(agent, user string) string {
|
||||
ls := Logins(agent)
|
||||
if !slices.ContainsFunc(ls, func(l Login) bool { return l.Active && strings.EqualFold(l.User, user) }) {
|
||||
return ""
|
||||
}
|
||||
for _, fit := range []func(Login) bool{
|
||||
func(l Login) bool { return l.On && !l.Paused && l.Lapsed == "" },
|
||||
func(l Login) bool { return l.Lapsed == "" },
|
||||
func(Login) bool { return true },
|
||||
} {
|
||||
for _, l := range ls {
|
||||
if !strings.EqualFold(l.User, user) && fit(l) {
|
||||
return l.User
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func forgetLogin(agent, user string) (signedOut bool, err error) {
|
||||
loginsMu.Lock()
|
||||
defer loginsMu.Unlock()
|
||||
|
||||
@@ -66,14 +66,12 @@ func TestCodexLogins(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if err := ForgetLogin("codex", "me@example.com"); err == nil {
|
||||
t.Fatal("forgot the active login")
|
||||
}
|
||||
if err := ForgetLogin("codex", "work@example.com"); err != nil {
|
||||
// the active one forgotten: Codex is signed in to the other first
|
||||
if err := ForgetLogin("codex", "me@example.com"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if users, _ := loginUsers(Logins("codex")); strings.Join(users, ",") != "me@example.com" {
|
||||
t.Fatalf("after forget: %v", users)
|
||||
if users, active := loginUsers(Logins("codex")); strings.Join(users, ",") != "work@example.com" || active != "work@example.com" {
|
||||
t.Fatalf("after forget: %v, active %q", users, active)
|
||||
}
|
||||
if err := SwitchLogin("codex", "nobody@example.com"); err == nil {
|
||||
t.Fatal("switched to an unknown login")
|
||||
|
||||
Reference in new issue
Block a user