From 6d6e762871a3bbc4bd4b8c035b49155add66e282 Mon Sep 17 00:00:00 2001 From: Epinephrine Date: Sat, 26 Sep 2026 09:46:25 +0000 Subject: [PATCH 1/4] fix(cli): disambiguate auto account selection --- src/cli/account-target.ts | 19 ++++++++++------ structure/providers/openai-accounts.md | 6 +++-- tests/cli/cli-account-alias-target.test.ts | 26 +++++++++++++++++----- 3 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/cli/account-target.ts b/src/cli/account-target.ts index 00d875f7cf5..e0b433da7e1 100644 --- a/src/cli/account-target.ts +++ b/src/cli/account-target.ts @@ -15,7 +15,7 @@ const MAIN_ALIAS = "main"; export type CodexAccountTarget = | { id: string } - | { error: string; kind: "not_found" | "ambiguous" | "reserved" } + | { error: string; kind: "not_found" | "ambiguous" | "reserved" | "unavailable" } | { networkDown: true; transportError?: string }; /** Built-in selectors cannot be reused as Codex account aliases. */ @@ -35,18 +35,21 @@ export async function resolveCodexAccountTarget( requested: string, ): Promise { if (requested === MAIN_ALIAS || requested === MAIN_CODEX_ACCOUNT_ID) return { id: MAIN_CODEX_ACCOUNT_ID }; - if (isReservedCodexAccountWord(requested)) { - return { error: `"${requested}" is reserved; it clears the selection with \`ocx account use\` and names no account`, kind: "reserved" }; - } const res = await apiJson(deps, baseUrl, "GET", "/api/codex-auth/accounts"); if (res.status === 0) return { networkDown: true, transportError: res.transportError }; // The list is only needed to turn an alias into an id. If the proxy cannot produce it, send // the argument as the id it may already be and let the route answer, as the CLI always did. - if (res.status !== 200) return { id: requested }; + if (res.status !== 200) { + if (!isReservedCodexAccountWord(requested)) return { id: requested }; + return { error: `Cannot safely resolve reserved account selector "${requested}" while the account list is unavailable`, kind: "unavailable" }; + } const accounts = (Array.isArray(res.json.accounts) ? res.json.accounts : []) .filter((entry): entry is { id: string; alias?: unknown } => typeof entry === "object" && entry !== null && typeof (entry as { id?: unknown }).id === "string"); if (accounts.some(account => account.id === requested)) return { id: requested }; + if (isReservedCodexAccountWord(requested)) { + return { error: `"${requested}" is reserved; it clears the selection with \`ocx account use\` and names no account`, kind: "reserved" }; + } const exact = accounts.filter(account => account.alias === requested); const matches = exact.length > 0 ? exact @@ -69,10 +72,12 @@ export async function resolveCodexUseTarget( baseUrl: string, requested: string, ): Promise { - if (requested === AUTO_ACCOUNT_ARGUMENT) return { accountId: null }; const target = await resolveCodexAccountTarget(deps, baseUrl, requested); if ("networkDown" in target) return target; - if ("error" in target) return target; + if ("error" in target) { + if (requested === AUTO_ACCOUNT_ARGUMENT && target.kind === "reserved") return { accountId: null }; + return target; + } return { accountId: target.id }; } diff --git a/structure/providers/openai-accounts.md b/structure/providers/openai-accounts.md index cb6f7fcadda..59671352fc6 100644 --- a/structure/providers/openai-accounts.md +++ b/structure/providers/openai-accounts.md @@ -156,8 +156,10 @@ Pool mode needs stable public names and a store that survives concurrent refresh - Public selectors are generated per account; the main login's selector is `main`, collision-suffixed if that name is taken, and it maps to the config-only sentinel `@main`, which sits outside the pool-account id grammar (`src/codex/account-namespaces.ts`, `src/codex/account-namespace-match.ts`). - Selectors must not collide with provider or combo ids. A user alias is display metadata; routing - consults credential identity, never the alias. + Selectors must not collide with provider or combo ids. The CLI's `auto` account control word is + resolved after exact stored account ids, so a legacy account with that id remains selectable + rather than invoking the control action. A user alias is display metadata; routing consults + credential identity, never the alias. - The credential store is generation-guarded and refresh-locked (`src/codex/account-store.ts`): a refresh persists only if the generation it started from still holds, and a lost race raises a generation-conflict error instead of overwriting the newer credential. diff --git a/tests/cli/cli-account-alias-target.test.ts b/tests/cli/cli-account-alias-target.test.ts index 23a897d81a3..96cddd30c1e 100644 --- a/tests/cli/cli-account-alias-target.test.ts +++ b/tests/cli/cli-account-alias-target.test.ts @@ -166,14 +166,24 @@ describe("ocx account: alias and auto as account arguments", () => { expect(result.stdout).toContain("chatgpt_2"); }); - test("use auto clears the pin and says the pool decides from here", async () => { + test("use auto selects an exact account id before treating auto as the pool selector", async () => { const h = harness(); - const result = await h.run(["use", "openai", "auto"]); + const automatic = await h.run(["use", "openai", "auto"]); - expect(result.code).toBe(0); + expect(automatic.code).toBe(0); expect(h.writes().at(-1)?.body).toEqual({ accountId: null }); - expect(result.stdout).toContain("automatic account selection"); - expect(result.stderr).not.toContain("may override this pin"); + expect(automatic.stdout).toContain("automatic account selection"); + expect(automatic.stderr).not.toContain("may override this pin"); + + h.accounts.push({ id: "auto", plan: "pro", quota: null }); + const selected = await h.run(["use", "openai", "auto"]); + expect(selected.code).toBe(0); + expect(h.writes().at(-1)?.body).toEqual({ accountId: "auto" }); + + const paused = await h.run(["pause", "openai", "auto"]); + expect(paused.code).toBe(0); + expect(h.requests.filter(r => r.path === "/api/codex-auth/accounts/pause").at(-1)?.body) + .toEqual({ id: "auto", paused: true }); }); test("missing and ambiguous aliases keep distinct errors before any write", async () => { @@ -220,8 +230,12 @@ describe("ocx account: alias and auto as account arguments", () => { const pause = await h.run(["pause", "openai", "auto"]); expect(pause.code).toBe(1); expect(pause.stderr).toContain("reserved"); - // The list only serves alias resolution: without it the argument is sent as an id, as before. h.listFailure = { status: 500, error: "list unavailable" }; + const automatic = await h.run(["use", "openai", "auto"]); + expect(automatic.code).toBe(1); + expect(automatic.stderr).toContain("Cannot safely resolve"); + expect(h.writes()).toHaveLength(0); + // The list only serves alias resolution: without it the argument is sent as an id, as before. const raw = await h.run(["use", "openai", "chatgpt_1"]); expect(raw.code).toBe(0); expect(h.writes().at(-1)?.body).toEqual({ accountId: "chatgpt_1" }); From d5d940212dc84ef444b3a18e80e281b001a14c76 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sat, 26 Sep 2026 13:03:39 +0000 Subject: [PATCH 2/4] fix(cli): add ocx account clear to restore automatic selection A Codex account with the id "auto" wins exact-id precedence, leaving "ocx account use openai auto" unable to clear the pin. The new verb sends {accountId: null} without resolving a selector, and the CLI reference gains the precedence note in all locales. Co-Authored-By: Epinephrine --- .../fr/reference/cli/providers-accounts.md | 11 +++-- .../ja/reference/cli/providers-accounts.md | 11 +++-- .../ko/reference/cli/providers-accounts.md | 11 +++-- .../docs/reference/cli/providers-accounts.md | 11 +++-- .../ru/reference/cli/providers-accounts.md | 11 +++-- .../tr/reference/cli/providers-accounts.md | 11 +++-- .../zh-cn/reference/cli/providers-accounts.md | 11 +++-- .../zh-tw/reference/cli/providers-accounts.md | 11 +++-- src/cli/account.ts | 47 ++++++++++++++++++- structure/providers/openai-accounts.md | 3 +- tests/cli/cli-account.test.ts | 23 +++++++++ 11 files changed, 134 insertions(+), 27 deletions(-) diff --git a/docs-site/src/content/docs/fr/reference/cli/providers-accounts.md b/docs-site/src/content/docs/fr/reference/cli/providers-accounts.md index a07ec7d6c71..41019dbe93f 100644 --- a/docs-site/src/content/docs/fr/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/fr/reference/cli/providers-accounts.md @@ -105,12 +105,13 @@ Répertoriez et changez de compte de fournisseur et de pools de clés API via le la surface est : ```text -Usage: ocx account ... +Usage: ocx account ... list [provider] Codex account pool, OAuth accounts and API keys (identifiers shown masked as the API returns them). history openai [--limit <1-200>] Recent routing decisions for one Codex pool account. current Show the active account or key. -use Switch the active credential; 'main' selects the Codex App login, 'auto' clears the selection. +use Switch the active credential; 'main' selects the Codex App login, 'auto' clears the selection unless an account carries that id. +clear Clear the manual Codex account selection unconditionally. refresh Force-refresh Codex or provider quota reports. auto-switch Control the Codex pool threshold. alias Set or clear an account's display name; '-' clears it. @@ -186,7 +187,7 @@ cet état et quitte toujours 0. `--json` renvoie : ### `ocx account use [--json]` -`auto` efface la sélection manuelle pour que le pool place à nouveau le travail selon sa propre stratégie. Un compte Codex peut être désigné par l'alias défini avec `ocx account alias` au lieu de son id ; cela vaut aussi pour `priority`, `pause`, `resume`, `clear-cooldown`, `remove` et `alias`. Pour les comptes Codex, `auto`, `main` et `__main__` sont réservés sans distinction de casse et ne peuvent pas être attribués comme alias. Les noms affichés des comptes OAuth et des clés API conservent leurs règles existantes. +`auto` efface la sélection manuelle pour que le pool place à nouveau le travail selon sa propre stratégie — sauf si un compte Codex porte littéralement l'id `auto`, qui l'emporte par correspondance exacte d'id ; `ocx account clear ` rétablit toujours la sélection automatique. Un compte Codex peut être désigné par l'alias défini avec `ocx account alias` au lieu de son id ; cela vaut aussi pour `priority`, `pause`, `resume`, `clear-cooldown`, `remove` et `alias`. Pour les comptes Codex, `auto`, `main` et `__main__` sont réservés sans distinction de casse et ne peuvent pas être attribués comme alias. Les noms affichés des comptes OAuth et des clés API conservent leurs règles existantes. Sélectionne un compte Codex, un compte OAuth ou une clé API existant. Pour `openai`, `main` sélectionne la connexion Codex App. Une sélection en mode Codex Pool efface l'affinité locale du processus et s'applique à la requête suivante, @@ -207,6 +208,10 @@ faire basculer la requête vers un autre compte de pool admissible. Ces transiti { ok: true, provider, type, activeId } ``` +### `ocx account clear [--json]` + +Efface la sélection manuelle du compte Codex sans résoudre d'id de compte, donc fonctionne même lorsqu'un compte s'appelle littéralement `auto`. Pools Codex uniquement ; les autres types de fournisseur n'ont pas de sélection automatique à rétablir. + ### `ocx account refresh [--json]` Pour le groupe de comptes Codex, utilisez `ocx account refresh openai [--json]`. Cette commande force l'actualisation des quotas de compte et diff --git a/docs-site/src/content/docs/ja/reference/cli/providers-accounts.md b/docs-site/src/content/docs/ja/reference/cli/providers-accounts.md index 2b73243cde6..1a73934278f 100644 --- a/docs-site/src/content/docs/ja/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/ja/reference/cli/providers-accounts.md @@ -79,12 +79,13 @@ ocx login anthropic 実行中のプロキシを介してプロバイダー アカウントと API キー プールを一覧表示し、切り替えます。出荷されたヘルプ画面は次のとおりです。 ```text -Usage: ocx account ... +Usage: ocx account ... list [provider] Codex account pool, OAuth accounts and API keys (identifiers shown masked as the API returns them). history openai [--limit <1-200>] Recent routing decisions for one Codex pool account. current Show the active account or key. -use Switch the active credential; 'main' selects the Codex App login, 'auto' clears the selection. +use Switch the active credential; 'main' selects the Codex App login, 'auto' clears the selection unless an account carries that id. +clear Clear the manual Codex account selection unconditionally. refresh Force-refresh Codex or provider quota reports. auto-switch Control the Codex pool threshold. alias Set or clear an account's display name; '-' clears it. @@ -144,7 +145,7 @@ Codex pool selection applies to the next request after clearing existing affinit ### `ocx account use [--json]` -`auto` は手動の選択を解除し、プールが自身の戦略で再び配置するようにします。Codex アカウントは id の代わりに `ocx account alias` で付けたエイリアスでも指定でき、`priority`、`pause`、`resume`、`clear-cooldown`、`remove`、`alias` でも同様です。Codex アカウントでは `auto`、`main`、`__main__` は大文字・小文字を区別せず予約語として扱われるため、エイリアスとして設定できません。OAuth アカウントと API キーの表示名には従来のルールが適用されます。 +`auto` は手動の選択を解除し、プールが自身の戦略で再び配置するようにします — ただし id が `auto` の Codex アカウントが存在する場合は完全一致の id が優先され、`ocx account clear ` が常に自動選択を復元します。Codex アカウントは id の代わりに `ocx account alias` で付けたエイリアスでも指定でき、`priority`、`pause`、`resume`、`clear-cooldown`、`remove`、`alias` でも同様です。Codex アカウントでは `auto`、`main`、`__main__` は大文字・小文字を区別せず予約語として扱われるため、エイリアスとして設定できません。OAuth アカウントと API キーの表示名には従来のルールが適用されます。 既存の Codex アカウント、OAuth アカウント、または API key を選びます。`openai` で `main` は Codex App ログインを 選択します。Codex Pool の選択は process-local affinity を消去し、既存の表示タスクを含む次のリクエストから適用されます。プロキシ再起動や affinity eviction 後もタスクは未紐付けになり得ますが、処理中のリクエストは取得済みアカウントを維持します。この選択は Pool routing のみを制御し、Direct mode は caller-owned/native main credential を使い続けます。使用量ベースのプロアクティブ切り替え、401/403 再認証、429/retry-after cooldown、除外、出力前 429/402 の障害回復により、後で別の適格 Pool アカウントが選ばれる場合があります。これらの回復経路は使用量ベース切り替えが off でも有効です。アカウント変更後も OpenCodex は会話コンテキストを再生しますが、provider prompt cache は再ウォームアップが必要な場合があります。 @@ -158,6 +159,10 @@ Codex pool selection applies to the next request after clearing existing affinit { ok: true, provider, type, activeId } ``` +### `ocx account clear [--json]` + +アカウント id を解決せずに Codex アカウントの手動選択を解除するため、`auto` という id のアカウントが存在しても機能します。Codex プール専用です。他のプロバイダー種別には復元する自動選択がありません。 + ### `ocx account refresh [--json]` Codex プールの場合は、`ocx account refresh openai [--json]` を使用します。アカウント クォータを強制的に更新し、利用可能な週次/月次のパーセンテージとリセット時間を出力します。不足しているクォータ データは、0% ではなく不明として報告されます。その JSON エンベロープは `{ accounts: AccountRow[] }` で、Codex の各行に `quota` があります。 diff --git a/docs-site/src/content/docs/ko/reference/cli/providers-accounts.md b/docs-site/src/content/docs/ko/reference/cli/providers-accounts.md index 42d13cae195..a822904fa5b 100644 --- a/docs-site/src/content/docs/ko/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/ko/reference/cli/providers-accounts.md @@ -164,12 +164,13 @@ Luna 메타데이터임을 표시해 사용합니다. 목록에 보인다는 사 실행 중인 프록시를 통해 제공자 계정과 API 키 풀을 나열하고 전환합니다. 제공되는 도움말 표면은 다음과 같습니다: ```text -Usage: ocx account ... +Usage: ocx account ... list [provider] Codex account pool, OAuth accounts and API keys (identifiers shown masked as the API returns them). history openai [--limit <1-200>] Recent routing decisions for one Codex pool account. current Show the active account or key. -use Switch the active credential; 'main' selects the Codex App login, 'auto' clears the selection. +use Switch the active credential; 'main' selects the Codex App login, 'auto' clears the selection unless an account carries that id. +clear Clear the manual Codex account selection unconditionally. refresh Force-refresh Codex or provider quota reports. auto-switch Control the Codex pool threshold. alias Set or clear an account's display name; '-' clears it. @@ -229,7 +230,7 @@ Codex pool selection applies to the next request after clearing existing affinit ### `ocx account use [--json]` -`auto`는 수동 선택을 지워 풀이 다시 자체 전략으로 작업을 배치하게 합니다. Codex 계정은 id 대신 `ocx account alias`로 지정한 별칭으로도 가리킬 수 있으며, `priority`, `pause`, `resume`, `clear-cooldown`, `remove`, `alias`에서도 마찬가지입니다. Codex 계정에서 `auto`, `main`, `__main__`은 대소문자 구분 없이 예약어이므로 별칭으로 지정할 수 없습니다. OAuth 계정과 API 키의 표시 이름에는 기존 규칙이 그대로 적용됩니다. +`auto`는 수동 선택을 지워 풀이 다시 자체 전략으로 작업을 배치하게 합니다 — 단 id가 `auto`인 Codex 계정이 있으면 정확한 id 일치가 우선되며 `ocx account clear `는 항상 자동 선택을 복원합니다. Codex 계정은 id 대신 `ocx account alias`로 지정한 별칭으로도 가리킬 수 있으며, `priority`, `pause`, `resume`, `clear-cooldown`, `remove`, `alias`에서도 마찬가지입니다. Codex 계정에서 `auto`, `main`, `__main__`은 대소문자 구분 없이 예약어이므로 별칭으로 지정할 수 없습니다. OAuth 계정과 API 키의 표시 이름에는 기존 규칙이 그대로 적용됩니다. 기존 Codex 계정, OAuth 계정 또는 API key를 선택합니다. `openai`에서 `main`은 Codex App 로그인을 선택합니다. Codex Pool 선택은 프로세스 로컬 affinity를 지우고 기존에 보이던 작업을 포함한 다음 요청부터 적용됩니다. 프록시 재시작이나 affinity eviction 뒤에도 작업이 바인딩 없는 상태가 될 수 있지만, 진행 중인 요청은 이미 확보한 계정을 유지합니다. 이 선택은 Pool 라우팅만 제어하며 Direct mode는 호출자 소유/native main credential을 계속 사용합니다. 사용량 기반 선제 전환, 401/403 재인증, 429/retry-after cooldown, 제외, 출력 전 429/402 실패 복구는 나중에 다른 적격 Pool 계정을 선택할 수 있습니다. 이러한 복구 경로는 사용량 기반 전환이 꺼져 있어도 동작합니다. 계정이 바뀌어도 OpenCodex는 대화 문맥을 재생하지만 프로바이더 측 prompt cache는 다시 예열해야 할 수 있습니다. @@ -243,6 +244,10 @@ Codex pool selection applies to the next request after clearing existing affinit { ok: true, provider, type, activeId } ``` +### `ocx account clear [--json]` + +계정 id를 해석하지 않고 Codex 계정의 수동 선택을 지우므로 `auto`라는 id의 계정이 있어도 동작합니다. Codex 풀 전용이며 다른 공급자 유형에는 복원할 자동 선택이 없습니다. + ### `ocx account refresh [--json]` Codex 풀에는 `ocx account refresh openai [--json]`를 사용합니다. 계정 할당량을 강제로 새로 고치고 사용 가능 주간/월간 비율과 재설정 시간을 출력합니다. 할당량 데이터가 없으면 0%가 아니라 알 수 없음으로 보고합니다. JSON 봉투는 `{ accounts: AccountRow[] }`이며, Codex 행마다 `quota`가 붙습니다. diff --git a/docs-site/src/content/docs/reference/cli/providers-accounts.md b/docs-site/src/content/docs/reference/cli/providers-accounts.md index 7acc0fe0955..8904afd5547 100644 --- a/docs-site/src/content/docs/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/reference/cli/providers-accounts.md @@ -226,12 +226,13 @@ List and switch provider accounts and API-key pools through the running proxy. T surface is: ```text -Usage: ocx account ... +Usage: ocx account ... list [provider] Codex account pool, OAuth accounts and API keys (identifiers shown masked as the API returns them). history openai [--limit <1-200>] Recent routing decisions for one Codex pool account. current Show the active account or key. -use Switch the active credential; 'main' selects the Codex App login, 'auto' clears the selection. +use Switch the active credential; 'main' selects the Codex App login, 'auto' clears the selection unless an account carries that id. +clear Clear the manual Codex account selection unconditionally. refresh Force-refresh Codex or provider quota reports. auto-switch Control the Codex pool threshold. alias Set or clear an account's display name; '-' clears it. @@ -395,7 +396,7 @@ that state and still exits 0. `--json` returns: ### `ocx account use [--json]` -`auto` clears the manual selection so the pool places work by its own strategy again. Any Codex account can be named by the alias set with `ocx account alias` instead of its id; that holds for `priority`, `pause`, `resume`, `clear-cooldown`, `remove` and `alias` too. For Codex accounts, `auto`, `main` and `__main__` are reserved regardless of case and cannot be assigned as aliases. OAuth and API-key display names keep their existing rules. +`auto` clears the manual selection so the pool places work by its own strategy again — unless a Codex account literally carries the id `auto`, which wins by exact-id precedence; `ocx account clear ` always restores automatic selection. Any Codex account can be named by the alias set with `ocx account alias` instead of its id; that holds for `priority`, `pause`, `resume`, `clear-cooldown`, `remove` and `alias` too. For Codex accounts, `auto`, `main` and `__main__` are reserved regardless of case and cannot be assigned as aliases. OAuth and API-key display names keep their existing rules. Selects an existing Codex account, OAuth account, or API key. For `openai`, `main` selects the Codex App login. A Codex Pool selection clears process-local affinity and applies to the next request, @@ -416,6 +417,10 @@ rotate the request to another eligible Pool account. These failure transitions r { ok: true, provider, type, activeId } ``` +### `ocx account clear [--json]` + +Clear the manual Codex account selection without resolving an account id, so it works even when an account is literally named `auto`. Codex pools only; other provider types have no automatic selection to restore. + ### `ocx account refresh [--json]` For the Codex pool, use `ocx account refresh openai [--json]`. It force-refreshes account quotas and diff --git a/docs-site/src/content/docs/ru/reference/cli/providers-accounts.md b/docs-site/src/content/docs/ru/reference/cli/providers-accounts.md index 82073a9df9b..7fbaaac7908 100644 --- a/docs-site/src/content/docs/ru/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/ru/reference/cli/providers-accounts.md @@ -94,12 +94,13 @@ ocx login anthropic Поставляемая help-surface выглядит так: ```text -Usage: ocx account ... +Usage: ocx account ... list [provider] Codex account pool, OAuth accounts and API keys (identifiers shown masked as the API returns them). history openai [--limit <1-200>] Recent routing decisions for one Codex pool account. current Show the active account or key. -use Switch the active credential; 'main' selects the Codex App login, 'auto' clears the selection. +use Switch the active credential; 'main' selects the Codex App login, 'auto' clears the selection unless an account carries that id. +clear Clear the manual Codex account selection unconditionally. refresh Force-refresh Codex or provider quota reports. auto-switch Control the Codex pool threshold. alias Set or clear an account's display name; '-' clears it. @@ -175,7 +176,7 @@ credential'а, это состояние тоже печатается, но к ### `ocx account use [--json]` -`auto` снимает ручной выбор, и пул снова распределяет работу по своей стратегии. Аккаунт Codex можно указать по псевдониму, заданному через `ocx account alias`, вместо id; это относится и к `priority`, `pause`, `resume`, `clear-cooldown`, `remove` и `alias`. Для аккаунтов Codex значения `auto`, `main` и `__main__` зарезервированы независимо от регистра и не могут назначаться как псевдонимы. Для отображаемых имён аккаунтов OAuth и API-ключей действуют прежние правила. +`auto` снимает ручной выбор, и пул снова распределяет работу по своей стратегии — если только аккаунт Codex буквально не имеет id `auto`: точное совпадение id выигрывает, а `ocx account clear ` всегда восстанавливает автоматический выбор. Аккаунт Codex можно указать по псевдониму, заданному через `ocx account alias`, вместо id; это относится и к `priority`, `pause`, `resume`, `clear-cooldown`, `remove` и `alias`. Для аккаунтов Codex значения `auto`, `main` и `__main__` зарезервированы независимо от регистра и не могут назначаться как псевдонимы. Для отображаемых имён аккаунтов OAuth и API-ключей действуют прежние правила. Выбирает существующий аккаунт Codex, OAuth-аккаунт или API-ключ. Для `openai` значение `main` выбирает вход Codex App. Выбор Codex Pool очищает process-local affinity и применяется к следующему запросу, включая запрос существующей видимой задачи; после перезапуска прокси или affinity eviction задача также может стать непривязанной, а выполняющиеся запросы сохраняют захваченный аккаунт. Это управляет только Pool routing; Direct mode продолжает использовать caller-owned/native main credential. Проактивное переключение по использованию, повторная аутентификация 401/403, cooldown 429/retry-after, исключение и восстановление после отказа 429/402 до вывода могут позже выбрать другой подходящий Pool-аккаунт. Эти пути восстановления остаются активными, когда переключение по использованию выключено. После смены аккаунта OpenCodex воспроизводит контекст разговора, но prompt cache провайдера может потребовать прогрева. Неизвестные провайдеры @@ -189,6 +190,10 @@ credential'а, это состояние тоже печатается, но к { ok: true, provider, type, activeId } ``` +### `ocx account clear [--json]` + +Снимает ручной выбор аккаунта Codex без разрешения id, поэтому работает, даже когда аккаунт буквально называется `auto`. Только для пулов Codex; у других типов провайдеров нет автоматического выбора для восстановления. + ### `ocx account refresh [--json]` Для пула Codex используйте `ocx account refresh openai [--json]`. Команда принудительно diff --git a/docs-site/src/content/docs/tr/reference/cli/providers-accounts.md b/docs-site/src/content/docs/tr/reference/cli/providers-accounts.md index 4a3c7fa11c4..9c9741e2969 100644 --- a/docs-site/src/content/docs/tr/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/tr/reference/cli/providers-accounts.md @@ -113,12 +113,13 @@ Bir sağlayıcı için saklanan OAuth kimlik bilgisini kaldırın. listeleyin ve değiştirin. Sağlanan yardım arayüzü şöyledir: ```text -Usage: ocx account ... +Usage: ocx account ... list [provider] Codex account pool, OAuth accounts and API keys (identifiers shown masked as the API returns them). history openai [--limit <1-200>] Recent routing decisions for one Codex pool account. current Show the active account or key. -use Switch the active credential; 'main' selects the Codex App login, 'auto' clears the selection. +use Switch the active credential; 'main' selects the Codex App login, 'auto' clears the selection unless an account carries that id. +clear Clear the manual Codex account selection unconditionally. refresh Force-refresh Codex or provider quota reports. auto-switch Control the Codex pool threshold. alias Set or clear an account's display name; '-' clears it. @@ -201,7 +202,7 @@ yine de 0 ile çıkar. `--json` şunu döndürür: ### `ocx account use [--json]` -`auto` elle yapılan seçimi temizler; havuz işi yeniden kendi stratejisiyle yerleştirir. Bir Codex hesabı, id yerine `ocx account alias` ile verilen takma adla da belirtilebilir; bu `priority`, `pause`, `resume`, `clear-cooldown`, `remove` ve `alias` için de geçerlidir. Codex hesaplarında `auto`, `main` ve `__main__` büyük/küçük harf fark etmeksizin ayrılmış sözcüklerdir ve takma ad olarak atanamaz. OAuth hesaplarının ve API anahtarlarının görünen adları için mevcut kurallar geçerlidir. +`auto` elle yapılan seçimi temizler; havuz işi yeniden kendi stratejisiyle yerleştirir — ancak id'si `auto` olan bir Codex hesabı varsa tam id eşleşmesi kazanır ve `ocx account clear ` her zaman otomatik seçimi geri yükler. Bir Codex hesabı, id yerine `ocx account alias` ile verilen takma adla da belirtilebilir; bu `priority`, `pause`, `resume`, `clear-cooldown`, `remove` ve `alias` için de geçerlidir. Codex hesaplarında `auto`, `main` ve `__main__` büyük/küçük harf fark etmeksizin ayrılmış sözcüklerdir ve takma ad olarak atanamaz. OAuth hesaplarının ve API anahtarlarının görünen adları için mevcut kurallar geçerlidir. Mevcut bir Codex hesabını, OAuth hesabını veya API anahtarını seçer. `openai` için `main` Codex App girişini seçer. Bir Codex Havuzu seçimi süreç içi yerel @@ -228,6 +229,10 @@ ayar yalnızca kullanıma dayalı proaktif geçişi devre dışı bırakır. { ok: true, provider, type, activeId } ``` +### `ocx account clear [--json]` + +Bir hesap id'si çözümlemeden Codex hesabının elle seçimini temizler; `auto` adında bir hesap olsa bile çalışır. Yalnızca Codex havuzları içindir; diğer sağlayıcı türlerinde geri yüklenecek otomatik seçim yoktur. + ### `ocx account refresh [--json]` Codex havuzu için `ocx account refresh openai [--json]` kullanın. Hesap diff --git a/docs-site/src/content/docs/zh-cn/reference/cli/providers-accounts.md b/docs-site/src/content/docs/zh-cn/reference/cli/providers-accounts.md index 50a5b1f6256..f0aa0d99697 100644 --- a/docs-site/src/content/docs/zh-cn/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/zh-cn/reference/cli/providers-accounts.md @@ -84,12 +84,13 @@ ocx login anthropic 通过正在运行的代理列出并切换提供方账号和 API 密钥池。随附的帮助输出如下: ```text -Usage: ocx account ... +Usage: ocx account ... list [provider] Codex account pool, OAuth accounts and API keys (identifiers shown masked as the API returns them). history openai [--limit <1-200>] Recent routing decisions for one Codex pool account. current Show the active account or key. -use Switch the active credential; 'main' selects the Codex App login, 'auto' clears the selection. +use Switch the active credential; 'main' selects the Codex App login, 'auto' clears the selection unless an account carries that id. +clear Clear the manual Codex account selection unconditionally. refresh Force-refresh Codex or provider quota reports. auto-switch Control the Codex pool threshold. alias Set or clear an account's display name; '-' clears it. @@ -159,7 +160,7 @@ OAuth 账号会显示为 `Account N`,而 plan/label 列会在 plan、屏蔽后 ### `ocx account use [--json]` -`auto` 会清除手动选择,让 Pool 重新按自身策略分配工作。Codex 账号可以用 `ocx account alias` 设置的别名代替 id 来指定;`priority`、`pause`、`resume`、`clear-cooldown`、`remove` 和 `alias` 同样如此。对于 Codex 账号,`auto`、`main` 和 `__main__` 为保留字(不区分大小写),不能设为别名。OAuth 账号和 API 密钥的显示名称仍遵循原有规则。 +`auto` 会清除手动选择,让 Pool 重新按自身策略分配工作 — 但如果某个 Codex 账号的 id 恰为 `auto`,则精确 id 匹配优先;`ocx account clear ` 始终恢复自动选择。Codex 账号可以用 `ocx account alias` 设置的别名代替 id 来指定;`priority`、`pause`、`resume`、`clear-cooldown`、`remove` 和 `alias` 同样如此。对于 Codex 账号,`auto`、`main` 和 `__main__` 为保留字(不区分大小写),不能设为别名。OAuth 账号和 API 密钥的显示名称仍遵循原有规则。 选择已有的 Codex 账号、OAuth 账号或 API key。对 `openai` 而言,`main` 选择 Codex App 登录。 Codex Pool 选择会清除进程本地 affinity,并从下一次请求开始生效,包括已有可见任务的请求;代理重启或 affinity eviction 后,任务也可能变为未绑定,但进行中的请求保留已捕获账号。此选择只控制 Pool routing;Direct mode 继续使用 caller-owned/native main credential。基于用量的主动切换、401/403 重新认证、429/retry-after cooldown、排除,以及输出前 429/402 故障恢复之后仍可能选择其他合格 Pool 账号。这些恢复路径在关闭基于用量的切换时仍然有效。账号变化后 OpenCodex 会重放对话上下文,但 provider prompt cache 可能需要重新预热。未知 provider 或 id 返回退出码 1。`--json` 返回: @@ -172,6 +173,10 @@ Codex Pool 选择会清除进程本地 affinity,并从下一次请求开始生 { ok: true, provider, type, activeId } ``` +### `ocx account clear [--json]` + +在不解析账号 id 的情况下清除 Codex 账号的手动选择,因此即使存在名为 `auto` 的账号也有效。仅适用于 Codex Pool;其他提供商类型没有可恢复的自动选择。 + ### `ocx account refresh [--json]` 对于 Codex 池,请使用 `ocx account refresh openai [--json]`。它会强制刷新账号配额, diff --git a/docs-site/src/content/docs/zh-tw/reference/cli/providers-accounts.md b/docs-site/src/content/docs/zh-tw/reference/cli/providers-accounts.md index 5fafb04e47b..bd3627bbfee 100644 --- a/docs-site/src/content/docs/zh-tw/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/zh-tw/reference/cli/providers-accounts.md @@ -62,12 +62,13 @@ ocx login anthropic 透過執行中的代理列出並切換供應商帳號與 API-key 池。隨附的說明介面如下: ```text -Usage: ocx account ... +Usage: ocx account ... list [provider] Codex 帳號池、OAuth 帳號與 API 金鑰(識別碼依 API 回傳遮罩顯示)。 history openai [--limit <1-200>] 單一 Codex 帳號池帳號的近期路由決策。 current 顯示現用帳號或金鑰。 -use 切換現用憑證;'main' 選擇 Codex App 登入,'auto' 清除選擇。 +use 切換現用憑證;'main' 選擇 Codex App 登入,'auto' 清除選擇,除非有帳號的 id 恰為此值。 +clear 無條件清除 Codex 帳號的手動選擇。 refresh 強制重新整理 Codex 或供應商配額報告。 auto-switch 控制 Codex 池閾值。 alias 設定或清除帳號顯示名稱;'-' 表示清除。 @@ -126,7 +127,7 @@ Codex 池選擇套用於清除既有親和性後的下一個請求;進行中 ### `ocx account use [--json]` -`auto` 會清除手動選擇,讓池重新依自身策略分配工作。Codex 帳號可以用 `ocx account alias` 設定的別名代替 id 來指定;`priority`、`pause`、`resume`、`clear-cooldown`、`remove` 與 `alias` 亦然。對於 Codex 帳號,`auto`、`main` 和 `__main__` 為保留字(不區分大小寫),不能設為別名。OAuth 帳號與 API 金鑰的顯示名稱仍遵循原有規則。 +`auto` 會清除手動選擇,讓池重新依自身策略分配工作 — 但若 Codex 帳號的 id 恰為 `auto`,則精確 id 比對優先;`ocx account clear ` 一律還原自動選擇。Codex 帳號可以用 `ocx account alias` 設定的別名代替 id 來指定;`priority`、`pause`、`resume`、`clear-cooldown`、`remove` 與 `alias` 亦然。對於 Codex 帳號,`auto`、`main` 和 `__main__` 為保留字(不區分大小寫),不能設為別名。OAuth 帳號與 API 金鑰的顯示名稱仍遵循原有規則。 選擇既有的 Codex 帳號、OAuth 帳號或 API 金鑰。對於 `openai`,`main` 選擇 Codex App 登入。Codex 池選擇清除行程本地親和性並套用於下一個請求,包含來自既有可見任務的請求;代理重啟或親和性驅逐也可能使任務未綁定,而進行中的請求保留其擷取的帳號。這僅控制池路由;Direct 模式繼續使用呼叫者擁有/原生的 main 憑證。基於用量的主動切換、401/403 重新認證、429/retry-after 冷卻、排除,以及 pre-output 429/402 失敗復原稍後可能選擇另一個合格的池帳號。當基於用量的切換關閉時,這些復原路徑仍然活躍。OpenCodex 在帳號變更後重播對話,但供應商端的 prompt cache 可能是冷的。未知的供應商或 id 離開 1。 在 **401/403** 時,App 登入清除該帳號的行程本地親和性並要求重新認證。 @@ -137,6 +138,10 @@ Codex 池選擇套用於清除既有親和性後的下一個請求;進行中 { ok: true, provider, type, activeId } ``` +### `ocx account clear [--json]` + +不解析帳號 id 即清除 Codex 帳號的手動選擇,即使存在名為 `auto` 的帳號仍有效。僅適用於 Codex 池;其他提供者類型沒有可還原的自動選擇。 + ### `ocx account refresh [--json]` 對於 Codex 池,請使用 `ocx account refresh openai [--json]`。它強制重新整理帳號配額並印出可用的週/月百分比與重置時間;缺失的配額資料被回報為未知,而非 0%。其 JSON 封裝為 `{ accounts: AccountRow[] }`,每個 Codex 列上有 `quota`。 diff --git a/src/cli/account.ts b/src/cli/account.ts index 78690c7894d..c7fbf2276bd 100644 --- a/src/cli/account.ts +++ b/src/cli/account.ts @@ -45,6 +45,7 @@ const ACCOUNT_USAGE = `Usage: ocx account history openai [--limit <1-200>] [--json] ocx account current [--json] ocx account use [--json] + ocx account clear [--json] ocx account refresh [--json] ocx account auto-switch > [--json] ocx account alias [--json] @@ -68,8 +69,10 @@ const ACCOUNT_USAGE = `Usage: List and switch provider accounts and API-key pools (masked output only). 'main' selects the Codex App login for the openai account pool; 'auto' clears the -selection so the pool places work by its own strategy. A Codex account can be named -by the alias set with 'ocx account alias' wherever an id is accepted.`; +selection so the pool places work by its own strategy — unless an account actually +carries that id, which wins, so 'ocx account clear' is the spelling that always +clears. A Codex account can be named by the alias set with 'ocx account alias' +wherever an id is accepted.`; function consumeFlag(args: string[], flag: string): boolean { const idx = args.indexOf(flag); @@ -350,6 +353,45 @@ async function cmdUse(rest: string[], deps: AccountDeps): Promise { return 0; } +/** `ocx account clear` never resolves its argument as an account id, so an account literally + * named `auto` cannot shadow the verb that returns the pool to automatic selection. */ +async function cmdClear(rest: string[], deps: AccountDeps): Promise { + const wantsJson = consumeFlag(rest, "--json"); + const name = rest.shift(); + const leftover = leftoverArgsError(rest); + if (!name || leftover) { + if (leftover) console.error(leftover); + console.error(ACCOUNT_USAGE); + return 1; + } + const config = deps.loadConfigImpl?.() ?? loadConfig(); + const c = classifyAccount(config, name); + if ("error" in c) { + console.error(`Error: ${c.error}. Known candidates: ${candidateNames(config)}`); + return 1; + } + if (c.type !== "codex") { + console.error(`Error: ${name} has no automatic-selection pin to clear; clear applies to Codex account pools`); + return 1; + } + const baseUrl = await resolveBaseUrl(deps); + if (!baseUrl) return proxyUnreachable(); + const res = await apiJson(deps, baseUrl, "PUT", "/api/codex-auth/active", { accountId: null }); + if (res.status === 0) return proxyUnreachable(res.transportError); + if (res.status !== 200) return apiError(res.json, `failed to clear ${name}`, res.status); + const pinDrainReason = typeof res.json.pinDrainReason === "string" ? res.json.pinDrainReason : undefined; + if (wantsJson) { + console.log(JSON.stringify({ + ok: true, provider: name, type: c.type, activeId: null, + ...(pinDrainReason !== undefined ? { pinDrained: true, pinDrainReason } : {}), + }, null, 2)); + } else { + console.log(`${name}: automatic account selection (pin cleared)`); + } + await explainCodexUseOutcome(deps, baseUrl, name, null, pinDrainReason); + return 0; +} + export async function cmdAccount(args: string[], deps: AccountDeps = {}): Promise { const [sub, ...rest] = args; try { @@ -360,6 +402,7 @@ export async function cmdAccount(args: string[], deps: AccountDeps = {}): Promis } if (sub === "current") return await cmdCurrent(rest, deps); if (sub === "use") return await cmdUse(rest, deps); + if (sub === "clear") return await cmdClear(rest, deps); if (sub === "refresh") return await cmdRefresh(rest, deps); if (sub === "auto-switch") return await cmdAutoSwitch(rest, deps); if (sub === "alias" || sub === "rename") return await cmdAlias(rest, deps); diff --git a/structure/providers/openai-accounts.md b/structure/providers/openai-accounts.md index 59671352fc6..b65bb95c013 100644 --- a/structure/providers/openai-accounts.md +++ b/structure/providers/openai-accounts.md @@ -158,7 +158,8 @@ Pool mode needs stable public names and a store that survives concurrent refresh pool-account id grammar (`src/codex/account-namespaces.ts`, `src/codex/account-namespace-match.ts`). Selectors must not collide with provider or combo ids. The CLI's `auto` account control word is resolved after exact stored account ids, so a legacy account with that id remains selectable - rather than invoking the control action. A user alias is display metadata; routing consults + rather than invoking the control action; `ocx account clear` skips selector resolution entirely + and always clears the selection. A user alias is display metadata; routing consults credential identity, never the alias. - The credential store is generation-guarded and refresh-locked (`src/codex/account-store.ts`): a refresh persists only if the generation it started from still holds, and a lost race raises a diff --git a/tests/cli/cli-account.test.ts b/tests/cli/cli-account.test.ts index 7772accc49d..7d73e51998c 100644 --- a/tests/cli/cli-account.test.ts +++ b/tests/cli/cli-account.test.ts @@ -809,6 +809,29 @@ describe("ocx account CLI (issue #180 matrix)", () => { expect(put?.body).toEqual({ accountId: "__main__" }); }); + test("an account named auto pins through use while clear still restores automatic selection", async () => { + codexAccounts = [...codexAccounts, { id: "auto", email: "a***@example.test", quota: null }]; + + const pinned = await run(["use", "openai", "auto"]); + const pinPut = requests.findLast(request => + request.method === "PUT" && request.path === "/api/codex-auth/active" + ); + + expect(pinned.code).toBe(0); + expect(pinPut?.body).toEqual({ accountId: "auto" }); + expect(activeCodexAccountId).toBe("auto"); + + const cleared = await run(["clear", "openai"]); + const clearPut = requests.findLast(request => + request.method === "PUT" && request.path === "/api/codex-auth/active" + ); + + expect(cleared.code).toBe(0); + expect(clearPut?.body).toEqual({ accountId: null }); + expect(activeCodexAccountId).toBeNull(); + expect(cleared.stdout).toContain("automatic account selection"); + }); + test("8: an unknown provider exits one and stderr names candidates", async () => { const result = await run(["use", "nosuch", "x"]); From 2f8e56eeb9353fb2df2449f7706caf59564b3043 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sat, 26 Sep 2026 13:30:03 +0000 Subject: [PATCH 3/4] test(cli): move the auto/clear case to the alias-target file cli-account.test.ts sits exactly at its file-size ratchet cap (2313); the new test pushed it to 2336 and failed 'file-size ratchet: repository'. cli-account-alias-target.test.ts is the sibling kept out of the #180 matrix file for this reason, so the case moves there byte-for-byte semantics, ported to that file's harness. Co-Authored-By: Devin AI --- tests/cli/cli-account-alias-target.test.ts | 14 +++++++++++++ tests/cli/cli-account.test.ts | 23 ---------------------- 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/tests/cli/cli-account-alias-target.test.ts b/tests/cli/cli-account-alias-target.test.ts index 96cddd30c1e..53c137fdffa 100644 --- a/tests/cli/cli-account-alias-target.test.ts +++ b/tests/cli/cli-account-alias-target.test.ts @@ -186,6 +186,20 @@ describe("ocx account: alias and auto as account arguments", () => { .toEqual({ id: "auto", paused: true }); }); + test("an account named auto pins through use while clear still restores automatic selection", async () => { + const h = harness(); + h.accounts.push({ id: "auto", plan: "pro", quota: null }); + + const pinned = await h.run(["use", "openai", "auto"]); + expect(pinned.code).toBe(0); + expect(h.writes().at(-1)?.body).toEqual({ accountId: "auto" }); + + const cleared = await h.run(["clear", "openai"]); + expect(cleared.code).toBe(0); + expect(h.writes().at(-1)?.body).toEqual({ accountId: null }); + expect(cleared.stdout).toContain("automatic account selection"); + }); + test("missing and ambiguous aliases keep distinct errors before any write", async () => { const h = harness(); h.accounts.push( diff --git a/tests/cli/cli-account.test.ts b/tests/cli/cli-account.test.ts index 7d73e51998c..7772accc49d 100644 --- a/tests/cli/cli-account.test.ts +++ b/tests/cli/cli-account.test.ts @@ -809,29 +809,6 @@ describe("ocx account CLI (issue #180 matrix)", () => { expect(put?.body).toEqual({ accountId: "__main__" }); }); - test("an account named auto pins through use while clear still restores automatic selection", async () => { - codexAccounts = [...codexAccounts, { id: "auto", email: "a***@example.test", quota: null }]; - - const pinned = await run(["use", "openai", "auto"]); - const pinPut = requests.findLast(request => - request.method === "PUT" && request.path === "/api/codex-auth/active" - ); - - expect(pinned.code).toBe(0); - expect(pinPut?.body).toEqual({ accountId: "auto" }); - expect(activeCodexAccountId).toBe("auto"); - - const cleared = await run(["clear", "openai"]); - const clearPut = requests.findLast(request => - request.method === "PUT" && request.path === "/api/codex-auth/active" - ); - - expect(cleared.code).toBe(0); - expect(clearPut?.body).toEqual({ accountId: null }); - expect(activeCodexAccountId).toBeNull(); - expect(cleared.stdout).toContain("automatic account selection"); - }); - test("8: an unknown provider exits one and stderr names candidates", async () => { const result = await run(["use", "nosuch", "x"]); From ba479b4030c5ce1aaff0157423598c9d7c8b8181 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sat, 26 Sep 2026 13:34:53 +0000 Subject: [PATCH 4/4] test(account): persist the mock active selection so pin/clear leave observable state Co-Authored-By: Epinephrine --- tests/cli/cli-account-alias-target.test.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/tests/cli/cli-account-alias-target.test.ts b/tests/cli/cli-account-alias-target.test.ts index 53c137fdffa..ba69f556691 100644 --- a/tests/cli/cli-account-alias-target.test.ts +++ b/tests/cli/cli-account-alias-target.test.ts @@ -23,16 +23,21 @@ interface Harness { listFailure: { status: number; error: string } | null; run: (args: string[]) => Promise<{ code: number; stdout: string; stderr: string }>; writes: () => Captured[]; + activeId: () => string | null; } function harness(providers: Record = {}): Harness { const requests: Captured[] = []; + // The mock active route persists what PUT writes so a verb that writes nothing (or the wrong + // thing) leaves observable state, not just a captured request. + let activeId: string | null = null; const state: Harness = { requests, accounts: [{ id: "chatgpt_1", plan: "pro", quota: null }], listFailure: null, run: async () => ({ code: 0, stdout: "", stderr: "" }), writes: () => requests.filter(r => r.method === "PUT" && r.path === "/api/codex-auth/active"), + activeId: () => activeId, }; const config = (): OcxConfig => ({ port: 10100, @@ -64,10 +69,10 @@ function harness(providers: Record = {}): Harness { return new Response(JSON.stringify({ accounts: state.accounts }), { status: 200 }); } if (captured.path === "/api/codex-auth/active") { - const pinned = captured.method === "PUT" - ? (captured.body as { accountId?: string | null } | undefined)?.accountId ?? null - : null; - return new Response(JSON.stringify({ ok: true, activeCodexAccountId: pinned, activeId: pinned }), { status: 200 }); + if (captured.method === "PUT") { + activeId = (captured.body as { accountId?: string | null } | undefined)?.accountId ?? null; + } + return new Response(JSON.stringify({ ok: true, activeCodexAccountId: activeId, activeId }), { status: 200 }); } return new Response(JSON.stringify({ ok: true }), { status: 200 }); }) as unknown as typeof fetch, @@ -179,6 +184,11 @@ describe("ocx account: alias and auto as account arguments", () => { const selected = await h.run(["use", "openai", "auto"]); expect(selected.code).toBe(0); expect(h.writes().at(-1)?.body).toEqual({ accountId: "auto" }); + expect(h.activeId()).toBe("auto"); + + const cleared = await h.run(["clear", "openai"]); + expect(cleared.code).toBe(0); + expect(h.activeId()).toBe(null); const paused = await h.run(["pause", "openai", "auto"]); expect(paused.code).toBe(0);