mirror of
https://github.com/stablyai/orca.git
synced 2026-10-18 08:03:22 +00:00
merge(codex-ma): integrate PR-B identity switcher
This commit is contained in:
@@ -1830,4 +1830,187 @@ describe('CodexAccountService config sync', () => {
|
||||
vi.doUnmock('../codex-cli/command')
|
||||
}
|
||||
})
|
||||
|
||||
describe('system-default identity', () => {
|
||||
const systemCodexHomeDir = () => join(testState.fakeHomeDir, '.codex')
|
||||
const systemAuthPath = () => join(systemCodexHomeDir(), 'auth.json')
|
||||
|
||||
async function newService() {
|
||||
const store = createStore(createSettings())
|
||||
const rateLimits = createRateLimits()
|
||||
const runtimeHome = createRuntimeHome()
|
||||
const { CodexAccountService } = await import('./service')
|
||||
return new CodexAccountService(store as never, rateLimits as never, runtimeHome as never)
|
||||
}
|
||||
|
||||
function withoutEnvApiKey<T>(run: () => T): T {
|
||||
const previous = process.env.OPENAI_API_KEY
|
||||
delete process.env.OPENAI_API_KEY
|
||||
try {
|
||||
return run()
|
||||
} finally {
|
||||
if (previous === undefined) {
|
||||
delete process.env.OPENAI_API_KEY
|
||||
} else {
|
||||
process.env.OPENAI_API_KEY = previous
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it('reports the real ~/.codex OAuth identity for the system default', async () => {
|
||||
writeFileSync(
|
||||
systemAuthPath(),
|
||||
createCodexAuthJson('real@home.dev', 'acct-real', 'refresh-real'),
|
||||
'utf-8'
|
||||
)
|
||||
const service = await newService()
|
||||
|
||||
const state = withoutEnvApiKey(() => service.listAccounts())
|
||||
|
||||
expect(state.activeAccountId).toBeNull()
|
||||
expect(state.systemDefault).toEqual({
|
||||
hasAuth: true,
|
||||
authKind: 'oauth',
|
||||
email: 'real@home.dev',
|
||||
providerAccountId: 'acct-real',
|
||||
workspaceLabel: null
|
||||
})
|
||||
})
|
||||
|
||||
it('reports an api-key auth.json as a custom provider with no identity', async () => {
|
||||
writeFileSync(systemAuthPath(), JSON.stringify({ OPENAI_API_KEY: 'sk-live-test' }), 'utf-8')
|
||||
const service = await newService()
|
||||
|
||||
const state = withoutEnvApiKey(() => service.listAccounts())
|
||||
|
||||
expect(state.systemDefault).toEqual({
|
||||
hasAuth: true,
|
||||
authKind: 'api-key',
|
||||
email: null,
|
||||
providerAccountId: null,
|
||||
workspaceLabel: null
|
||||
})
|
||||
})
|
||||
|
||||
it('reports signed-out when no auth.json and no env key is present', async () => {
|
||||
const service = await newService()
|
||||
|
||||
const state = withoutEnvApiKey(() => service.listAccounts())
|
||||
|
||||
expect(state.systemDefault).toEqual({
|
||||
hasAuth: false,
|
||||
authKind: 'none',
|
||||
email: null,
|
||||
providerAccountId: null,
|
||||
workspaceLabel: null
|
||||
})
|
||||
})
|
||||
|
||||
it('degrades safely when auth.json contains valid JSON with the wrong shape', async () => {
|
||||
writeFileSync(systemAuthPath(), 'null', 'utf-8')
|
||||
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
const service = await newService()
|
||||
|
||||
try {
|
||||
const state = withoutEnvApiKey(() => service.listAccounts())
|
||||
|
||||
expect(state.systemDefault).toEqual({
|
||||
hasAuth: true,
|
||||
authKind: 'none',
|
||||
email: null,
|
||||
providerAccountId: null,
|
||||
workspaceLabel: null
|
||||
})
|
||||
expect(warn).toHaveBeenCalledWith(
|
||||
'[codex-accounts] System-default Codex auth has an unexpected format'
|
||||
)
|
||||
} finally {
|
||||
warn.mockRestore()
|
||||
}
|
||||
})
|
||||
|
||||
it('does not log auth contents when auth.json is malformed', async () => {
|
||||
const secret = 'sk-secret-review-never-log'
|
||||
writeFileSync(systemAuthPath(), secret, 'utf-8')
|
||||
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
const service = await newService()
|
||||
|
||||
try {
|
||||
const state = withoutEnvApiKey(() => service.listAccounts())
|
||||
|
||||
expect(state.systemDefault?.authKind).toBe('none')
|
||||
expect(warn).toHaveBeenCalledWith(
|
||||
'[codex-accounts] System-default Codex auth is not valid JSON'
|
||||
)
|
||||
expect(JSON.stringify(warn.mock.calls)).not.toContain(secret)
|
||||
} finally {
|
||||
warn.mockRestore()
|
||||
}
|
||||
})
|
||||
|
||||
it('reports an env OPENAI_API_KEY (no auth.json) as a custom provider', async () => {
|
||||
const service = await newService()
|
||||
const previous = process.env.OPENAI_API_KEY
|
||||
process.env.OPENAI_API_KEY = 'sk-env-test'
|
||||
try {
|
||||
const state = service.listAccounts()
|
||||
expect(state.systemDefault).toEqual({
|
||||
hasAuth: false,
|
||||
authKind: 'api-key',
|
||||
email: null,
|
||||
providerAccountId: null,
|
||||
workspaceLabel: null
|
||||
})
|
||||
} finally {
|
||||
if (previous === undefined) {
|
||||
delete process.env.OPENAI_API_KEY
|
||||
} else {
|
||||
process.env.OPENAI_API_KEY = previous
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('never mutates ~/.codex when selecting or deselecting a managed account', async () => {
|
||||
const systemAuth = createCodexAuthJson('real@home.dev', 'acct-real', 'refresh-real')
|
||||
writeFileSync(systemAuthPath(), systemAuth, 'utf-8')
|
||||
const managedHomePath = createManagedHome(
|
||||
testState.userDataDir,
|
||||
'account-1',
|
||||
'approval_policy = "on-request"\n',
|
||||
createCodexAuthJson('managed@example.com', 'acct-managed', 'refresh-managed')
|
||||
)
|
||||
const store = createStore(
|
||||
createSettings({
|
||||
codexManagedAccounts: [
|
||||
{
|
||||
id: 'account-1',
|
||||
email: 'managed@example.com',
|
||||
managedHomePath,
|
||||
providerAccountId: 'acct-managed',
|
||||
workspaceLabel: null,
|
||||
workspaceAccountId: 'acct-managed',
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
lastAuthenticatedAt: 1
|
||||
}
|
||||
]
|
||||
})
|
||||
)
|
||||
const rateLimits = createRateLimits()
|
||||
const runtimeHome = createRuntimeHome()
|
||||
const { CodexAccountService } = await import('./service')
|
||||
const service = new CodexAccountService(
|
||||
store as never,
|
||||
rateLimits as never,
|
||||
runtimeHome as never
|
||||
)
|
||||
|
||||
await service.selectAccount('account-1')
|
||||
await service.selectAccount(null)
|
||||
|
||||
expect(readFileSync(systemAuthPath(), 'utf-8')).toBe(systemAuth)
|
||||
const state = withoutEnvApiKey(() => service.listAccounts())
|
||||
expect(state.systemDefault?.email).toBe('real@home.dev')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -11,7 +11,8 @@ import { getSpawnArgsForWindows } from '../win32-utils'
|
||||
import type {
|
||||
CodexManagedAccount,
|
||||
CodexManagedAccountSummary,
|
||||
CodexRateLimitAccountsState
|
||||
CodexRateLimitAccountsState,
|
||||
CodexSystemDefaultIdentity
|
||||
} from '../../shared/types'
|
||||
import type { CodexRuntimeHomeService } from './runtime-home-service'
|
||||
import { writeFileAtomically } from './fs-utils'
|
||||
@@ -309,10 +310,104 @@ export class CodexAccountService {
|
||||
.map((account) => this.toSummary(account))
|
||||
.sort((a, b) => b.updatedAt - a.updatedAt),
|
||||
activeAccountId: normalizeCodexRuntimeSelection(settings).host,
|
||||
activeAccountIdsByRuntime: normalizeCodexRuntimeSelection(settings)
|
||||
activeAccountIdsByRuntime: normalizeCodexRuntimeSelection(settings),
|
||||
systemDefault: this.resolveSystemDefaultIdentity()
|
||||
}
|
||||
}
|
||||
|
||||
// Why: the system-default (activeAccountId:null) account has no stored
|
||||
// identity — its effective login is whatever the real ~/.codex/auth.json is
|
||||
// right now. Read it live and read-only so the switcher can display who the
|
||||
// system default is and attribute usage, without ever mutating ~/.codex.
|
||||
private resolveSystemDefaultIdentity(): CodexSystemDefaultIdentity {
|
||||
const authFilePath = join(homedir(), '.codex', 'auth.json')
|
||||
let contents: string
|
||||
try {
|
||||
// Why: a single read avoids an exists/read race and halves filesystem
|
||||
// probes whenever an accounts snapshot resolves this live identity.
|
||||
contents = readFileSync(authFilePath, 'utf-8')
|
||||
} catch (error) {
|
||||
const code = (error as NodeJS.ErrnoException | null)?.code
|
||||
if (code === 'ENOENT' || code === 'ENOTDIR') {
|
||||
// Why: no auth.json means either a signed-out home or an env-key/custom
|
||||
// provider that authenticates via OPENAI_API_KEY instead of a token file.
|
||||
return {
|
||||
hasAuth: false,
|
||||
authKind: this.hasEnvApiKey() ? 'api-key' : 'none',
|
||||
email: null,
|
||||
providerAccountId: null,
|
||||
workspaceLabel: null
|
||||
}
|
||||
}
|
||||
console.warn(
|
||||
'[codex-accounts] Failed to read system-default Codex identity',
|
||||
code ?? 'unknown-error'
|
||||
)
|
||||
return {
|
||||
hasAuth: true,
|
||||
authKind: 'none',
|
||||
email: null,
|
||||
providerAccountId: null,
|
||||
workspaceLabel: null
|
||||
}
|
||||
}
|
||||
|
||||
let parsed: unknown
|
||||
try {
|
||||
parsed = JSON.parse(contents)
|
||||
} catch {
|
||||
// Why: SyntaxError messages can echo malformed input; never let auth
|
||||
// contents or token fragments reach logs while degrading safely.
|
||||
console.warn('[codex-accounts] System-default Codex auth is not valid JSON')
|
||||
return {
|
||||
hasAuth: true,
|
||||
authKind: 'none',
|
||||
email: null,
|
||||
providerAccountId: null,
|
||||
workspaceLabel: null
|
||||
}
|
||||
}
|
||||
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
||||
// Why: valid JSON can still have the wrong shape; account listing must
|
||||
// degrade to an unknown identity instead of crashing the settings pane.
|
||||
console.warn('[codex-accounts] System-default Codex auth has an unexpected format')
|
||||
return {
|
||||
hasAuth: true,
|
||||
authKind: 'none',
|
||||
email: null,
|
||||
providerAccountId: null,
|
||||
workspaceLabel: null
|
||||
}
|
||||
}
|
||||
const raw = parsed as Record<string, unknown>
|
||||
|
||||
if (typeof raw.OPENAI_API_KEY === 'string' && raw.OPENAI_API_KEY.trim() !== '') {
|
||||
// Why: API-key/custom-provider logins carry no OAuth identity or ChatGPT
|
||||
// usage. Surface them as a custom provider, not a blank/broken row.
|
||||
return {
|
||||
hasAuth: true,
|
||||
authKind: 'api-key',
|
||||
email: null,
|
||||
providerAccountId: null,
|
||||
workspaceLabel: null
|
||||
}
|
||||
}
|
||||
|
||||
const identity = this.resolveIdentityFromCredentials(this.extractOAuthCredentials(raw))
|
||||
return {
|
||||
hasAuth: true,
|
||||
authKind: 'oauth',
|
||||
email: identity.email,
|
||||
providerAccountId: identity.providerAccountId,
|
||||
workspaceLabel: identity.workspaceLabel
|
||||
}
|
||||
}
|
||||
|
||||
private hasEnvApiKey(): boolean {
|
||||
const key = process.env.OPENAI_API_KEY
|
||||
return typeof key === 'string' && key.trim() !== ''
|
||||
}
|
||||
|
||||
private toSummary(account: CodexManagedAccount): CodexManagedAccountSummary {
|
||||
return {
|
||||
id: account.id,
|
||||
@@ -945,7 +1040,12 @@ export class CodexAccountService {
|
||||
}
|
||||
|
||||
private readIdentityFromHome(managedHomePath: string): ResolvedCodexIdentity {
|
||||
const credentials = this.loadOAuthCredentials(managedHomePath)
|
||||
return this.resolveIdentityFromCredentials(this.loadOAuthCredentials(managedHomePath))
|
||||
}
|
||||
|
||||
private resolveIdentityFromCredentials(
|
||||
credentials: CodexOAuthCredentials
|
||||
): ResolvedCodexIdentity {
|
||||
const payload = credentials.idToken ? this.parseJwtPayload(credentials.idToken) : null
|
||||
const authClaims = this.readRecordClaim(payload, 'https://api.openai.com/auth')
|
||||
const profileClaims = this.readRecordClaim(payload, 'https://api.openai.com/profile')
|
||||
@@ -973,8 +1073,12 @@ export class CodexAccountService {
|
||||
|
||||
private loadOAuthCredentials(managedHomePath: string): CodexOAuthCredentials {
|
||||
const authFilePath = join(this.assertManagedHomePath(managedHomePath), 'auth.json')
|
||||
const raw = JSON.parse(readFileSync(authFilePath, 'utf-8')) as Record<string, unknown>
|
||||
return this.extractOAuthCredentials(
|
||||
JSON.parse(readFileSync(authFilePath, 'utf-8')) as Record<string, unknown>
|
||||
)
|
||||
}
|
||||
|
||||
private extractOAuthCredentials(raw: Record<string, unknown>): CodexOAuthCredentials {
|
||||
// Why: API-key-based auth files have no OAuth tokens or JWT identity
|
||||
// claims. Returning nulls causes the caller to fail with a clear
|
||||
// "could not resolve the account email" error rather than crashing
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useEffect, useRef, useState } from 'react'
|
||||
import type {
|
||||
ClaudeRateLimitAccountsState,
|
||||
CodexRateLimitAccountsState,
|
||||
CodexSystemDefaultIdentity,
|
||||
GlobalSettings
|
||||
} from '../../../../shared/types'
|
||||
import { Badge } from '../ui/badge'
|
||||
@@ -175,6 +176,29 @@ function getCodexAccountLabel(
|
||||
return state.accounts.find((account) => account.id === accountId)?.email ?? 'Codex account'
|
||||
}
|
||||
|
||||
// Why: the system-default row has no stored identity, so surface the real
|
||||
// ~/.codex login live — the OAuth email when signed in, a clear custom-provider
|
||||
// note for env-key logins, and the generic fallback when signed out.
|
||||
function getCodexSystemDefaultSubtitle(
|
||||
identity: CodexSystemDefaultIdentity | undefined,
|
||||
runtimeSentenceLabel: string
|
||||
): string {
|
||||
if (identity?.authKind === 'oauth' && identity.email) {
|
||||
return identity.email
|
||||
}
|
||||
if (identity?.authKind === 'api-key') {
|
||||
return translate(
|
||||
'auto.components.settings.AccountsPane.codexSystemDefaultCustomProvider',
|
||||
'Custom provider — no usage tracked.'
|
||||
)
|
||||
}
|
||||
return translate(
|
||||
'auto.components.settings.AccountsPane.fcc4093fc1',
|
||||
'Use your current {{value0}} Codex login.',
|
||||
{ value0: runtimeSentenceLabel }
|
||||
)
|
||||
}
|
||||
|
||||
function getClaudeAccountLabel(
|
||||
state: ClaudeRateLimitAccountsState,
|
||||
accountId: string | null | undefined
|
||||
@@ -385,6 +409,11 @@ export function AccountsPane({
|
||||
).some((account) =>
|
||||
providerAccountIsActiveInView(account, claudeAccounts, accountRuntime, accountVisibilityOptions)
|
||||
)
|
||||
// Why: the system default's real identity is host-scoped (it reflects the
|
||||
// runtime's own ~/.codex), so only surface it in the host view. Per-distro
|
||||
// WSL falls back to the generic label.
|
||||
const systemCodexIdentity =
|
||||
accountRuntime.runtime === 'host' ? codexAccounts.systemDefault : undefined
|
||||
// Why: the auth warning is derived from the desktop's own rate-limit poll;
|
||||
// with a remote owner it would misattribute local auth state to the server.
|
||||
const activeCodexAuthWarning =
|
||||
@@ -394,7 +423,8 @@ export function AccountsPane({
|
||||
target: codexRateLimitTarget,
|
||||
runtime: accountRuntime,
|
||||
activeAccountId: activeCodexAccountId,
|
||||
accountId: activeCodexAccountId
|
||||
accountId: activeCodexAccountId,
|
||||
authKind: activeCodexAccountId === null ? systemCodexIdentity?.authKind : undefined
|
||||
})
|
||||
: null
|
||||
const systemCodexNeedsReauthentication =
|
||||
@@ -1172,10 +1202,9 @@ export function AccountsPane({
|
||||
'Codex reported this {{value0}} login is out of date.',
|
||||
{ value0: accountRuntimeSentenceLabel }
|
||||
)
|
||||
: translate(
|
||||
'auto.components.settings.AccountsPane.fcc4093fc1',
|
||||
'Use your current {{value0}} Codex login.',
|
||||
{ value0: accountRuntimeSentenceLabel }
|
||||
: getCodexSystemDefaultSubtitle(
|
||||
systemCodexIdentity,
|
||||
accountRuntimeSentenceLabel
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -77,6 +77,19 @@ describe('codex account auth warning', () => {
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('does not mislabel an API-key system default as needing re-authentication', () => {
|
||||
expect(
|
||||
getCodexAccountAuthWarning({
|
||||
limits: codexLimits('chatgpt authentication required to read rate limits'),
|
||||
target: { runtime: 'host', wslDistro: null },
|
||||
runtime: { runtime: 'host' },
|
||||
activeAccountId: null,
|
||||
accountId: null,
|
||||
authKind: 'api-key'
|
||||
})
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('allows a WSL default account location to receive the active WSL target warning', () => {
|
||||
expect(
|
||||
codexRateLimitTargetMatchesAccountRuntime(
|
||||
|
||||
@@ -2,6 +2,7 @@ import type {
|
||||
ProviderRateLimits,
|
||||
RateLimitRuntimeTarget
|
||||
} from '../../../../shared/rate-limit-types'
|
||||
import type { CodexSystemDefaultIdentity } from '../../../../shared/types'
|
||||
import { isCodexAuthError } from '../../../../shared/codex-auth-errors'
|
||||
|
||||
type AccountRuntime = {
|
||||
@@ -28,7 +29,13 @@ export function getCodexAccountAuthWarning(args: {
|
||||
runtime: AccountRuntime
|
||||
activeAccountId: string | null
|
||||
accountId: string | null
|
||||
authKind?: CodexSystemDefaultIdentity['authKind']
|
||||
}): string | null {
|
||||
// Why: app-server reports API-key homes as a ChatGPT-auth error because
|
||||
// usage is unsupported; that is not a stale sign-in the user can re-auth.
|
||||
if (args.accountId === null && args.authKind === 'api-key') {
|
||||
return null
|
||||
}
|
||||
if (args.accountId !== args.activeAccountId) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -4957,7 +4957,8 @@
|
||||
"remoteScopeAccounts": "Showing accounts managed by {{value0}}. Add or re-authenticate accounts on that server.",
|
||||
"remoteScopeAuthContext": "Each account keeps its own sign-in context on {{value0}}.",
|
||||
"remoteEmptyClaudeAccounts": "No managed Claude accounts on {{value0}}. It uses its system default Claude login; add accounts on that server.",
|
||||
"remoteEmptyCodexAccounts": "No managed Codex accounts on {{value0}}. It uses its system default Codex login; add accounts on that server."
|
||||
"remoteEmptyCodexAccounts": "No managed Codex accounts on {{value0}}. It uses its system default Codex login; add accounts on that server.",
|
||||
"codexSystemDefaultCustomProvider": "Custom provider — no usage tracked."
|
||||
},
|
||||
"AdvancedPane": {
|
||||
"40b29e0bf3": "Restart",
|
||||
|
||||
@@ -4934,7 +4934,8 @@
|
||||
"remoteScopeAccounts": "Mostrando las cuentas administradas por {{value0}}. Agrega o vuelve a autenticar cuentas en ese servidor.",
|
||||
"remoteScopeAuthContext": "Cada cuenta mantiene su propio contexto de inicio de sesión en {{value0}}.",
|
||||
"remoteEmptyClaudeAccounts": "No hay cuentas de Claude administradas en {{value0}}. Usa su inicio de sesión de Claude predeterminado del sistema; agrega cuentas en ese servidor.",
|
||||
"remoteEmptyCodexAccounts": "No hay cuentas de Codex administradas en {{value0}}. Usa su inicio de sesión de Codex predeterminado del sistema; agrega cuentas en ese servidor."
|
||||
"remoteEmptyCodexAccounts": "No hay cuentas de Codex administradas en {{value0}}. Usa su inicio de sesión de Codex predeterminado del sistema; agrega cuentas en ese servidor.",
|
||||
"codexSystemDefaultCustomProvider": "Proveedor personalizado — no se registra el uso."
|
||||
},
|
||||
"AdvancedPane": {
|
||||
"40b29e0bf3": "Reiniciar",
|
||||
|
||||
@@ -4919,7 +4919,8 @@
|
||||
"remoteScopeAccounts": "{{value0}} が管理するアカウントを表示しています。アカウントの追加や再認証はそのサーバー上で行ってください。",
|
||||
"remoteScopeAuthContext": "各アカウントは {{value0}} 上に独自のサインインコンテキストを保持します。",
|
||||
"remoteEmptyClaudeAccounts": "{{value0}} に管理対象の Claude アカウントはありません。システム既定の Claude ログインを使用します。アカウントの追加はそのサーバー上で行ってください。",
|
||||
"remoteEmptyCodexAccounts": "{{value0}} に管理対象の Codex アカウントはありません。システム既定の Codex ログインを使用します。アカウントの追加はそのサーバー上で行ってください。"
|
||||
"remoteEmptyCodexAccounts": "{{value0}} に管理対象の Codex アカウントはありません。システム既定の Codex ログインを使用します。アカウントの追加はそのサーバー上で行ってください。",
|
||||
"codexSystemDefaultCustomProvider": "カスタムプロバイダー — 使用量は追跡されません。"
|
||||
},
|
||||
"AdvancedPane": {
|
||||
"40b29e0bf3": "再起動",
|
||||
|
||||
@@ -4919,7 +4919,8 @@
|
||||
"remoteScopeAccounts": "{{value0}}에서 관리하는 계정을 표시하고 있습니다. 계정 추가나 재인증은 해당 서버에서 진행하세요.",
|
||||
"remoteScopeAuthContext": "각 계정은 {{value0}}에 자체 로그인 컨텍스트를 유지합니다.",
|
||||
"remoteEmptyClaudeAccounts": "{{value0}}에 관리되는 Claude 계정이 없습니다. 해당 서버의 시스템 기본 Claude 로그인을 사용하며, 계정 추가는 그 서버에서 진행하세요.",
|
||||
"remoteEmptyCodexAccounts": "{{value0}}에 관리되는 Codex 계정이 없습니다. 해당 서버의 시스템 기본 Codex 로그인을 사용하며, 계정 추가는 그 서버에서 진행하세요."
|
||||
"remoteEmptyCodexAccounts": "{{value0}}에 관리되는 Codex 계정이 없습니다. 해당 서버의 시스템 기본 Codex 로그인을 사용하며, 계정 추가는 그 서버에서 진행하세요.",
|
||||
"codexSystemDefaultCustomProvider": "사용자 지정 제공업체 — 사용량을 추적하지 않습니다."
|
||||
},
|
||||
"AdvancedPane": {
|
||||
"40b29e0bf3": "다시 시작",
|
||||
|
||||
@@ -4919,7 +4919,8 @@
|
||||
"remoteScopeAccounts": "正在显示由 {{value0}} 管理的账户。请在该服务器上添加或重新验证账户。",
|
||||
"remoteScopeAuthContext": "每个账户在 {{value0}} 上保留自己的登录上下文。",
|
||||
"remoteEmptyClaudeAccounts": "{{value0}} 上没有受管理的 Claude 账户。它使用其系统默认的 Claude 登录;请在该服务器上添加账户。",
|
||||
"remoteEmptyCodexAccounts": "{{value0}} 上没有受管理的 Codex 账户。它使用其系统默认的 Codex 登录;请在该服务器上添加账户。"
|
||||
"remoteEmptyCodexAccounts": "{{value0}} 上没有受管理的 Codex 账户。它使用其系统默认的 Codex 登录;请在该服务器上添加账户。",
|
||||
"codexSystemDefaultCustomProvider": "自定义提供商 — 不跟踪使用情况。"
|
||||
},
|
||||
"AdvancedPane": {
|
||||
"40b29e0bf3": "重新启动",
|
||||
|
||||
@@ -2342,10 +2342,28 @@ export type CodexManagedAccountSummary = {
|
||||
lastAuthenticatedAt: number
|
||||
}
|
||||
|
||||
/** Live, read-only identity of the user's real ~/.codex used by the
|
||||
* system-default (activeAccountId:null) Codex account. Orca reads this to
|
||||
* display and attribute the system default; it never writes ~/.codex. */
|
||||
export type CodexSystemDefaultIdentity = {
|
||||
/** True when ~/.codex/auth.json exists (signed in via a token file). */
|
||||
hasAuth: boolean
|
||||
/** 'oauth' = ChatGPT sign-in with an id token (has ChatGPT usage);
|
||||
* 'api-key' = env-key/custom provider (no ChatGPT usage);
|
||||
* 'none' = signed out or identity could not be resolved. */
|
||||
authKind: 'oauth' | 'api-key' | 'none'
|
||||
email: string | null
|
||||
providerAccountId: string | null
|
||||
workspaceLabel: string | null
|
||||
}
|
||||
|
||||
export type CodexRateLimitAccountsState = {
|
||||
accounts: CodexManagedAccountSummary[]
|
||||
activeAccountId: string | null
|
||||
activeAccountIdsByRuntime?: CodexManagedAccountRuntimeSelection
|
||||
/** Resolved identity of the host system-default (real ~/.codex) account.
|
||||
* Omitted for runtimes where it is not resolved (e.g. per-distro WSL). */
|
||||
systemDefault?: CodexSystemDefaultIdentity
|
||||
}
|
||||
|
||||
export type CodexManagedAccountRuntimeSelection = {
|
||||
|
||||
Reference in New Issue
Block a user