diff --git a/src/main/codex-usage/scanner-paths.test.ts b/src/main/codex-usage/scanner-paths.test.ts index ecdc03e139f..43f5b0475dd 100644 --- a/src/main/codex-usage/scanner-paths.test.ts +++ b/src/main/codex-usage/scanner-paths.test.ts @@ -156,6 +156,26 @@ describe('listCodexSessionFiles', () => { expect(await listCodexSessionFiles()).toEqual([runtimeSessionPath, systemSessionPath].sort()) }) + it('scans per-account self-contained Codex homes that hold sessions', async () => { + const runtimeSessionsDir = join(userDataDir, 'codex-runtime-home', 'home', 'sessions') + const systemSessionsDir = join(fakeHomeDir, '.codex', 'sessions') + const accountSessionsDir = join(userDataDir, 'codex-accounts', 'acct-1', 'home', 'sessions') + mkdirSync(runtimeSessionsDir, { recursive: true }) + mkdirSync(systemSessionsDir, { recursive: true }) + mkdirSync(accountSessionsDir, { recursive: true }) + // Why: an account home without a sessions tree (auth-only) must not add a scan root. + mkdirSync(join(userDataDir, 'codex-accounts', 'acct-2', 'home'), { recursive: true }) + const accountSessionPath = join(accountSessionsDir, 'account.jsonl') + writeFileSync(accountSessionPath, '{}\n', 'utf-8') + + expect(getCodexSessionDirectories()).toEqual([ + runtimeSessionsDir, + systemSessionsDir, + accountSessionsDir + ]) + expect(await listCodexSessionFiles()).toEqual([accountSessionPath]) + }) + it('dedupes managed session aliases that point at system sessions', async () => { const runtimeSessionsDir = join(userDataDir, 'codex-runtime-home', 'home', 'sessions') const systemSessionsDir = join(fakeHomeDir, '.codex', 'sessions') diff --git a/src/main/codex-usage/scanner.ts b/src/main/codex-usage/scanner.ts index 97e8f6f7b09..eb5fbcaab14 100644 --- a/src/main/codex-usage/scanner.ts +++ b/src/main/codex-usage/scanner.ts @@ -6,6 +6,7 @@ import { createInterface } from 'node:readline' import type { Repo } from '../../shared/types' import { areWorktreePathsEqual } from '../ipc/worktree-logic' import { getOrcaManagedCodexHomePath, getSystemCodexHomePath } from '../codex/codex-home-paths' +import { getCodexAccountHomeSessionDirectories } from '../codex/codex-account-home-discovery' import { getLegacyCopiedCodexSessionBridgeScanPreference } from '../codex/codex-session-bridge' import { canonicalizeUsageWorktreePaths } from '../usage-worktree-canonicalizer' import type { @@ -133,11 +134,14 @@ export function getCodexSessionsDirectory(): string { } export function getCodexSessionDirectories(): string[] { - // Why: upgraded users still have ordinary Codex history under ~/.codex, while - // new Orca-launched sessions are written under Orca's managed runtime home. - return [getCodexSessionsDirectory(), join(getSystemCodexHomePath(), 'sessions')].filter( - (dirPath, index, allDirPaths) => allDirPaths.indexOf(dirPath) === index - ) + // Why: sessions now live in three lanes — the shared runtime mirror, the real + // ~/.codex, and per-account self-contained homes; missing any lane silently + // undercounts usage for multi-account users. + return [ + getCodexSessionsDirectory(), + join(getSystemCodexHomePath(), 'sessions'), + ...getCodexAccountHomeSessionDirectories() + ].filter((dirPath, index, allDirPaths) => allDirPaths.indexOf(dirPath) === index) } function hasLegacyCopiedSessionBridgeMarkers(): boolean { diff --git a/src/main/codex/codex-account-home-discovery.ts b/src/main/codex/codex-account-home-discovery.ts new file mode 100644 index 00000000000..18ddee1eb57 --- /dev/null +++ b/src/main/codex/codex-account-home-discovery.ts @@ -0,0 +1,19 @@ +import { existsSync, readdirSync } from 'node:fs' +import { join } from 'node:path' +import { getOrcaUserDataPath } from './codex-home-paths' + +/** Session roots of per-account self-contained host Codex homes present on disk. + * Why disk-enumerated, not settings-driven: rollouts retained after an account + * change must still be counted, and CLI callers have no settings store. WSL + * account homes live inside their distro and are scanned by their own lane. */ +export function getCodexAccountHomeSessionDirectories(): string[] { + const accountsRoot = join(getOrcaUserDataPath(), 'codex-accounts') + try { + return readdirSync(accountsRoot, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => join(accountsRoot, entry.name, 'home', 'sessions')) + .filter((sessionsPath) => existsSync(sessionsPath)) + } catch { + return [] + } +} diff --git a/src/main/codex/codex-home-paths.ts b/src/main/codex/codex-home-paths.ts index 1a6a8ba0b8b..61e2eba1eca 100644 --- a/src/main/codex/codex-home-paths.ts +++ b/src/main/codex/codex-home-paths.ts @@ -42,7 +42,7 @@ export function getCodexSessionBackfillStateDirPath(): string { return join(getOrcaUserDataPath(), 'codex-session-backfill') } -function getOrcaUserDataPath(): string { +export function getOrcaUserDataPath(): string { if (process.env.ORCA_USER_DATA_PATH) { return process.env.ORCA_USER_DATA_PATH } diff --git a/src/renderer/src/components/settings/AccountsPane.tsx b/src/renderer/src/components/settings/AccountsPane.tsx index 5e065c483c7..396d0f92351 100644 --- a/src/renderer/src/components/settings/AccountsPane.tsx +++ b/src/renderer/src/components/settings/AccountsPane.tsx @@ -1873,8 +1873,8 @@ export function AccountsPane({ {translate( - 'auto.components.settings.AccountsPane.99c8f9e498', - 'Orca will delete the managed Codex home for this saved account. If it is currently active, Orca falls back to the system default Codex login.' + 'auto.components.settings.AccountsPane.380a7736cc', + 'Removing this account permanently deletes its managed Codex home, including all Codex session history and MCP logins stored inside. This cannot be undone. If the account is currently active, Orca falls back to the system default Codex login.' )} diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 78dce5f59e5..5ce0d051ff5 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -4885,7 +4885,7 @@ "dbb9626ed1": "Cancel", "854ebbcc45": "Orca will delete the managed Claude auth for this saved account. If it is currently active, Orca falls back to the system default Claude login.", "63843e37e2": "Remove Claude Account?", - "99c8f9e498": "Orca will delete the managed Codex home for this saved account. If it is currently active, Orca falls back to the system default Codex login.", + "380a7736cc": "Removing this account permanently deletes its managed Codex home, including all Codex session history and MCP logins stored inside. This cannot be undone. If the account is currently active, Orca falls back to the system default Codex login.", "0d47394635": "Remove Codex Account?", "ae3b21eb6c": "opencode.ai/workspace/wrk_…/go", "51c9104e13": "Find this in the URL after logging into opencode.ai (e.g.", diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index 57451380c2b..0b9a24de01b 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -4862,7 +4862,7 @@ "dbb9626ed1": "Cancelar", "854ebbcc45": "Orca eliminará la autenticación de Claude gestionada para esta cuenta guardada. Si está activa actualmente, Orca volverá al login predeterminado de Claude del sistema.", "63843e37e2": "¿Eliminar la cuenta de Claude?", - "99c8f9e498": "Orca eliminará el home de Codex gestionado para esta cuenta guardada. Si está activo actualmente, Orca volverá al login predeterminado de Codex del sistema.", + "380a7736cc": "Removing this account permanently deletes its managed Codex home, including all Codex session history and MCP logins stored inside. This cannot be undone. If the account is currently active, Orca falls back to the system default Codex login.", "0d47394635": "¿Eliminar cuenta de Codex?", "ae3b21eb6c": "opencode.ai/workspace/wrk__…/go", "51c9104e13": "Encuéntralo en la URL después de iniciar sesión en opencode.ai (p. ej.", diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index a7ee7072c59..4f3a7f53c56 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -4847,7 +4847,7 @@ "dbb9626ed1": "キャンセル", "854ebbcc45": "Orca は、この保存されたアカウントの管理対象 Claude 認証を削除します。現在アクティブな場合、Orca はシステムのデフォルトの Claude ログインに戻ります。", "63843e37e2": "Claude アカウントを削除しますか?", - "99c8f9e498": "Orca は、この保存されたアカウントの管理対象 Codex ホームを削除します。現在アクティブな場合、Orca はシステムのデフォルトの Codex ログインに戻ります。", + "380a7736cc": "Removing this account permanently deletes its managed Codex home, including all Codex session history and MCP logins stored inside. This cannot be undone. If the account is currently active, Orca falls back to the system default Codex login.", "0d47394635": "Codex アカウントを削除しますか?", "ae3b21eb6c": "opencode.ai/workspace/wrk_…/go", "51c9104e13": "opencode.ai にログインした後、URL でこれを見つけます (例:", diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index 92bbb8ec6a7..8d3b034b4b2 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -4847,7 +4847,7 @@ "dbb9626ed1": "취소", "854ebbcc45": "Orca는 저장된 계정에 대해 관리되는 Claude 인증을 삭제합니다. 현재 활성화된 경우 Orca는 시스템 기본 Claude 로그인으로 대체됩니다.", "63843e37e2": "Claude 계정을 삭제하시겠습니까?", - "99c8f9e498": "Orca는 저장된 계정에 대해 관리되는 Codex 홈을 삭제합니다. 현재 활성화된 경우 Orca는 시스템 기본 Codex 로그인으로 대체됩니다.", + "380a7736cc": "Removing this account permanently deletes its managed Codex home, including all Codex session history and MCP logins stored inside. This cannot be undone. If the account is currently active, Orca falls back to the system default Codex login.", "0d47394635": "Codex 계정을 제거하시겠습니까?", "ae3b21eb6c": "opencode.ai/workspace/wrk_…/go", "51c9104e13": "opencode.ai에 로그인한 후 URL에서 이를 찾으세요(예:", diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index 8064647193c..ea2c25f8cbf 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -4847,7 +4847,7 @@ "dbb9626ed1": "取消", "854ebbcc45": "Orca 将删除此已保存账户的托管 Claude 身份验证。如果当前处于活动状态,Orca 将回退到系统默认的 Claude 登录名。", "63843e37e2": "删除 Claude 账户?", - "99c8f9e498": "Orca 将删除此已保存账户的托管 Codex 主目录。如果当前处于活动状态,Orca 将回退到系统默认的 Codex 登录。", + "380a7736cc": "Removing this account permanently deletes its managed Codex home, including all Codex session history and MCP logins stored inside. This cannot be undone. If the account is currently active, Orca falls back to the system default Codex login.", "0d47394635": "删除 Codex 账户?", "ae3b21eb6c": "opencode.ai/workspace/wrk_…/go", "51c9104e13": "登录 opencode.ai 后在 URL 中找到此内容(例如",