fix(codex): validate OAuth credentials before enabling configuration

This commit is contained in:
lzt404 committed 2026-07-31 20:21:17 +08:00
1 parent 66f9ce71e6
commit e01aef44f5
5 files changed
+130 -34

No files matched your search

+7 -3
View File
@@ -402,13 +402,13 @@ export const jaOverrides = {
'agents.model.loading': 'モデルを読み込んでいます',
'agents.model.loadFailed': 'モデルの取得に失敗しました',
'agents.model.select': 'モデルを選択',
'agents.model.none': '選択可能なモデルなし',
'agents.model.none': 'モデル一覧は空です',
'agents.model.search': 'モデル名またはエイリアスを検索',
'agents.model.clearSearch': '検索を消去',
'agents.model.refresh': 'モデルを更新',
'agents.model.fetching': 'モデルを取得しています',
'agents.model.noMatch': '一致するモデルがありません',
'agents.model.unavailable': '現在利用可能なモデルはありません',
'agents.model.unavailable': 'モデル一覧は空です',
'agents.model.tryKeywords': '別のキーワードをお試しください',
'agents.model.connectFirst': '先に利用可能なモデルを接続してから更新してください',
'agents.model.available': '利用可能なモデル',
@@ -513,7 +513,11 @@ export const jaOverrides = {
'agents.modify.title': '設定変更',
'agents.modify.oauthConfiguration': 'OAuth 設定方式',
'agents.oauthLoginRequired.title': 'Codex へのログインが必要です',
'agents.oauthLoginRequired.description': 'OAuth 設定方式が有効ですが、有効な Codex のログイン認証情報が見つかりません。起動する前に Codex で ChatGPT アカウントにログインしてください。',
'agents.oauthLoginRequired.enableDescription': '有効な Codex のログイン認証情報が見つからないため、OAuth 設定方式を有効にできません。Codex アプリで ChatGPT アカウントにログインしてください。Codex アプリでログインを開始できない場合は、先に',
'agents.oauthLoginRequired.enableClearConfiguration': '設定を消去',
'agents.oauthLoginRequired.enableDescriptionSuffix': 'を使用してください。',
'agents.oauthLoginRequired.applyDescription': '有効な Codex のログイン認証情報が見つからないため、OAuth 設定を適用できません。Codex で ChatGPT アカウントにログインしてから、設定を適用してください。',
'agents.oauthLoginRequired.launchDescription': 'OAuth 設定方式が有効ですが、有効な Codex のログイン認証情報が見つかりません。起動する前に Codex で ChatGPT アカウントにログインしてください。',
'agents.oauthLoginRequired.confirm': '了解しました',
'agents.modify.apply': '設定変更を適用',
'agents.modify.default': 'デフォルト設定',
+14 -6
View File
@@ -405,13 +405,13 @@ export const zhCN = {
'agents.model.loading': '正在读取模型',
'agents.model.loadFailed': '获取模型失败',
'agents.model.select': '选择模型',
'agents.model.none': '无可选模型',
'agents.model.none': '模型列表为空',
'agents.model.search': '搜索模型名称或别名',
'agents.model.clearSearch': '清空搜索',
'agents.model.refresh': '刷新模型',
'agents.model.fetching': '正在获取模型',
'agents.model.noMatch': '没有匹配的模型',
'agents.model.unavailable': '暂时没有可用模型',
'agents.model.unavailable': '模型列表为空',
'agents.model.tryKeywords': '尝试其他关键词',
'agents.model.connectFirst': '请先接入可用模型后刷新',
'agents.model.available': '可用模型',
@@ -516,7 +516,11 @@ export const zhCN = {
'agents.modify.title': '配置修改',
'agents.modify.oauthConfiguration': 'Oauth配置方式',
'agents.oauthLoginRequired.title': '需要登录 Codex',
'agents.oauthLoginRequired.description': '已开启 OAuth 配置模式,但未检测到有效的 Codex 登录凭据。请先在 Codex 中登录 ChatGPT 账号,然后再启动。',
'agents.oauthLoginRequired.enableDescription': '未检测到有效的 Codex 登录凭据,无法开启 OAuth 配置模式。请先在 Codex 应用中登录 ChatGPT 账号,如果 Codex 应用无法触发登录,请先使用',
'agents.oauthLoginRequired.enableClearConfiguration': '清空配置',
'agents.oauthLoginRequired.enableDescriptionSuffix': '功能。',
'agents.oauthLoginRequired.applyDescription': '无法应用 OAuth 配置:未检测到有效的 Codex 登录凭据。请先在 Codex 中登录 ChatGPT 账号,然后再应用配置。',
'agents.oauthLoginRequired.launchDescription': '已开启 OAuth 配置模式,但未检测到有效的 Codex 登录凭据。请先在 Codex 中登录 ChatGPT 账号,然后再启动。',
'agents.oauthLoginRequired.confirm': '我知道了',
'agents.modify.apply': '应用配置修改',
'agents.modify.default': '默认配置',
@@ -1250,13 +1254,13 @@ export const en: Record<MessageKey, string> = {
'agents.model.loading': 'Loading models',
'agents.model.loadFailed': 'Failed to load models',
'agents.model.select': 'Select a model',
'agents.model.none': 'No models available',
'agents.model.none': 'Model list is empty',
'agents.model.search': 'Search model names or aliases',
'agents.model.clearSearch': 'Clear search',
'agents.model.refresh': 'Refresh models',
'agents.model.fetching': 'Fetching models',
'agents.model.noMatch': 'No matching models',
'agents.model.unavailable': 'No models are currently available',
'agents.model.unavailable': 'Model list is empty',
'agents.model.tryKeywords': 'Try different keywords',
'agents.model.connectFirst': 'Connect an available model, then refresh',
'agents.model.available': 'Available model',
@@ -1361,7 +1365,11 @@ export const en: Record<MessageKey, string> = {
'agents.modify.title': 'Configuration Changes',
'agents.modify.oauthConfiguration': 'OAuth configuration mode',
'agents.oauthLoginRequired.title': 'Codex sign-in required',
'agents.oauthLoginRequired.description': 'OAuth configuration mode is enabled, but no valid Codex sign-in credentials were found. Sign in to your ChatGPT account in Codex before launching it.',
'agents.oauthLoginRequired.enableDescription': 'No valid Codex sign-in credentials were found, so OAuth configuration mode cannot be enabled. Sign in to your ChatGPT account in the Codex app. If the Codex app cannot trigger sign-in, use',
'agents.oauthLoginRequired.enableClearConfiguration': 'Clear Configuration',
'agents.oauthLoginRequired.enableDescriptionSuffix': 'first.',
'agents.oauthLoginRequired.applyDescription': 'OAuth configuration cannot be applied because no valid Codex sign-in credentials were found. Sign in to your ChatGPT account in Codex, then apply the configuration.',
'agents.oauthLoginRequired.launchDescription': 'OAuth configuration mode is enabled, but no valid Codex sign-in credentials were found. Sign in to your ChatGPT account in Codex before launching it.',
'agents.oauthLoginRequired.confirm': 'Got it',
'agents.modify.apply': 'Apply Configuration Changes',
'agents.modify.default': 'Default Configuration',
+61 -20
View File
@@ -92,6 +92,8 @@ type ChatGptCloseResult = {
closedProcesses: number;
};
type OAuthLoginRequiredAction = 'enable' | 'apply' | 'launch';
const CODEX_OAUTH_LOGIN_REQUIRED_ERROR = 'CODEX_OAUTH_LOGIN_REQUIRED';
type AgentDefinition = {
@@ -488,7 +490,7 @@ export function AgentsPage() {
const [loading, setLoading] = useState(true);
const [modelLoading, setModelLoading] = useState(false);
const [busyAction, setBusyAction] = useState<
'apply' | 'default' | 'clear' | 'launch' | 'launch-cli' | 'launch-app' | 'close-app' | null
'apply' | 'default' | 'clear' | 'launch' | 'launch-cli' | 'launch-app' | 'close-app' | 'oauth-check' | null
>(null);
const busy = busyAction !== null;
const [detectionError, setDetectionError] = useState('');
@@ -504,7 +506,7 @@ export function AgentsPage() {
const [closeAppError, setCloseAppError] = useState('');
const [closeAppNotice, setCloseAppNotice] = useState('');
const [closeAppConfirmOpen, setCloseAppConfirmOpen] = useState(false);
const [oauthLoginRequiredOpen, setOauthLoginRequiredOpen] = useState(false);
const [oauthLoginRequiredAction, setOauthLoginRequiredAction] = useState<OAuthLoginRequiredAction | null>(null);
const [oauthConfigurationDraft, setOauthConfigurationDraft] = useState<boolean | null>(null);
const modelRequestRef = useRef(0);
@@ -602,7 +604,7 @@ export function AgentsPage() {
setCloseAppError('');
setCloseAppNotice('');
setCloseAppConfirmOpen(false);
setOauthLoginRequiredOpen(false);
setOauthLoginRequiredAction(null);
}, [selected]);
const activeDefinition = agentDefinitions.find((agent) => agent.id === selected)
@@ -696,6 +698,34 @@ export function AgentsPage() {
return model.name;
};
const handleOAuthLoginError = (requestError: unknown, action: OAuthLoginRequiredAction) => {
const message = String(requestError);
if (message.includes(CODEX_OAUTH_LOGIN_REQUIRED_ERROR)) {
setOauthLoginRequiredAction(action);
return true;
}
return false;
};
const changeOauthConfiguration = async (enabled: boolean) => {
if (!enabled) {
setOauthConfigurationDraft(false);
return;
}
setBusyAction('oauth-check');
try {
await invoke('check_codex_oauth_login');
setOauthConfigurationDraft(true);
} catch (requestError) {
if (!handleOAuthLoginError(requestError, 'enable')) {
setConfigurationError(String(requestError));
}
} finally {
setBusyAction(null);
}
};
const applyConfigurationChanges = async () => {
setConfigurationError('');
const model = requireSelectedModel();
@@ -710,7 +740,9 @@ export function AgentsPage() {
await reloadStatusesAfterAction();
setOauthConfigurationDraft(null);
} catch (requestError) {
setConfigurationError(String(requestError));
if (!handleOAuthLoginError(requestError, 'apply')) {
setConfigurationError(String(requestError));
}
} finally {
setBusyAction(null);
}
@@ -731,7 +763,9 @@ export function AgentsPage() {
await reloadStatusesAfterAction();
setOauthConfigurationDraft(null);
} catch (requestError) {
setDefaultError(String(requestError));
if (!handleOAuthLoginError(requestError, 'apply')) {
setDefaultError(String(requestError));
}
} finally {
setBusyAction(null);
}
@@ -762,16 +796,12 @@ export function AgentsPage() {
setBusyAction(launchAction);
setLaunchError('');
try {
if (selected !== 'codex' && draftChanged) {
if (draftChanged) {
throw new Error(t('agents.error.applyFirst'));
}
await invoke('launch_agent', { client: selected, target: target.id });
} catch (requestError) {
if (String(requestError) === CODEX_OAUTH_LOGIN_REQUIRED_ERROR) {
setOauthLoginRequiredOpen(true);
} else {
setLaunchError(String(requestError));
}
if (!handleOAuthLoginError(requestError, 'launch')) setLaunchError(String(requestError));
} finally {
setBusyAction(null);
}
@@ -827,6 +857,13 @@ export function AgentsPage() {
const availableSubpages = agentSubpages.filter(
(subpage) => !subpage.clients || subpage.clients.includes(selected),
);
const oauthLoginRequiredDescription = oauthLoginRequiredAction === 'enable' ? (
<>
{t('agents.oauthLoginRequired.enableDescription')}
<strong>{t('agents.oauthLoginRequired.enableClearConfiguration')}</strong>
{t('agents.oauthLoginRequired.enableDescriptionSuffix')}
</>
) : oauthLoginRequiredAction ? t(`agents.oauthLoginRequired.${oauthLoginRequiredAction}Description`) : '';
return (
<section className="page management-page agents-page">
@@ -973,7 +1010,7 @@ export function AgentsPage() {
type="checkbox"
role="switch"
checked={oauthConfiguration}
onChange={(event) => setOauthConfigurationDraft(event.currentTarget.checked)}
onChange={(event) => void changeOauthConfiguration(event.currentTarget.checked)}
disabled={busy}
aria-label={t('agents.modify.oauthConfiguration')}
/>
@@ -1038,8 +1075,10 @@ export function AgentsPage() {
type="button"
className="secondary-button agent-launch-button"
onClick={() => void launchAgent(cliLaunchTarget)}
disabled={busy || !canLaunchTarget(cliLaunchTarget)}
title={cliLaunchTarget?.detail ?? t('agents.launch.unavailable')}
disabled={busy || !canLaunchTarget(cliLaunchTarget) || draftChanged}
title={draftChanged
? t('agents.launch.applyFirst')
: cliLaunchTarget?.detail ?? t('agents.launch.unavailable')}
>
{busyAction === 'launch-cli' ? <LoaderCircle size={16} className="spin" /> : <Terminal size={16} />}
{busyAction === 'launch-cli' ? t('agents.launch.starting') : t('agents.launch.startCli')}
@@ -1048,8 +1087,10 @@ export function AgentsPage() {
type="button"
className="primary-button agent-launch-button"
onClick={() => void launchAgent(appLaunchTarget)}
disabled={busy || !canLaunchTarget(appLaunchTarget)}
title={appLaunchTarget?.detail ?? t('agents.launch.unavailable')}
disabled={busy || !canLaunchTarget(appLaunchTarget) || draftChanged}
title={draftChanged
? t('agents.launch.applyFirst')
: appLaunchTarget?.detail ?? t('agents.launch.unavailable')}
>
{busyAction === 'launch-app' ? <LoaderCircle size={16} className="spin" /> : <AppWindow size={16} />}
{busyAction === 'launch-app' ? t('agents.launch.starting') : t('agents.launch.startApp')}
@@ -1182,15 +1223,15 @@ export function AgentsPage() {
</div>
) : null}
{oauthLoginRequiredOpen ? (
{oauthLoginRequiredAction ? (
<div className="config-dialog-backdrop">
<section className="config-dialog agent-restore-dialog" role="alertdialog" aria-modal="true" aria-labelledby="agent-oauth-login-required-title">
<div className="config-dialog-heading">
<div><AlertTriangle size={19} /><h2 id="agent-oauth-login-required-title">{t('agents.oauthLoginRequired.title')}</h2></div>
</div>
<p>{t('agents.oauthLoginRequired.description')}</p>
<div className="config-dialog-actions">
<button type="button" className="primary-button" onClick={() => setOauthLoginRequiredOpen(false)}>{t('agents.oauthLoginRequired.confirm')}</button>
<p>{oauthLoginRequiredDescription}</p>
<div className="config-dialog-actions single-action">
<button type="button" className="primary-button" onClick={() => setOauthLoginRequiredAction(null)}>{t('agents.oauthLoginRequired.confirm')}</button>
</div>
</section>
</div>
+5
View File
@@ -2194,6 +2194,11 @@ textarea:focus-visible {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.config-dialog-actions.single-action {
grid-template-columns: 118px;
justify-content: end;
}
.config-dialog-actions button {
gap: 7px;
height: 40px;