From 009e8e27eff0165f91b720e5e6c3e7fd9e7d3b80 Mon Sep 17 00:00:00 2001 From: eureka928 Date: Sat, 7 Feb 2026 05:41:58 +0100 Subject: [PATCH] fix: address Codex OAuth review feedback - Save CODEX_OAUTH_TOKEN marker config after OAuth to track install state - Check marker config instead of OPENAI_API_KEY for install detection - Avoid mutating input dict in CodexOAuthManager.save_token() - Add CODEX_OAUTH_TOKEN to Codex env_vars whitelist in server config - Prioritize named integration descriptions over env_vars fallback --- backend/app/agent/toolkit/codex_toolkit.py | 1 + server/app/model/config/config.py | 9 ++-- src/components/AddWorker/ToolSelect.tsx | 47 ++++++++++++++++++--- src/hooks/useIntegrationManagement.ts | 8 ++-- src/pages/Setting/MCP.tsx | 49 ++++++++++++++++++---- 5 files changed, 92 insertions(+), 22 deletions(-) diff --git a/backend/app/agent/toolkit/codex_toolkit.py b/backend/app/agent/toolkit/codex_toolkit.py index c8c1b405..11ba1bef 100644 --- a/backend/app/agent/toolkit/codex_toolkit.py +++ b/backend/app/agent/toolkit/codex_toolkit.py @@ -250,6 +250,7 @@ class CodexOAuthManager: True on success. """ path = cls._token_path() + token_data = token_data.copy() try: if "saved_at" not in token_data: token_data["saved_at"] = int(time.time()) diff --git a/server/app/model/config/config.py b/server/app/model/config/config.py index 7271a945..076afd1c 100644 --- a/server/app/model/config/config.py +++ b/server/app/model/config/config.py @@ -168,12 +168,11 @@ class ConfigInfo: "env_vars": ["OPENAI_API_KEY"], "toolkit": "rag_toolkit", }, - # Codex OAuth is a model-provider integration (obtains an - # OpenAI API key). After OAuth the key is stored via the - # Provider API, not here. The entry is kept only for - # install-state detection in the integration list UI. + # TODO: Codex is a model-provider integration, not a toolkit. + # Its OAuth entry point should move to the Model/Provider settings + # page. Keeping it here temporarily for install-state detection. ConfigGroup.CODEX.value: { - "env_vars": [], + "env_vars": ["CODEX_OAUTH_TOKEN"], "toolkit": None, "type": "provider", }, diff --git a/src/components/AddWorker/ToolSelect.tsx b/src/components/AddWorker/ToolSelect.tsx index 48aade74..0d3b48f1 100644 --- a/src/components/AddWorker/ToolSelect.tsx +++ b/src/components/AddWorker/ToolSelect.tsx @@ -268,11 +268,45 @@ const ToolSelect = forwardRef< } }; + const saveCodexMarkerConfig = async () => { + try { + const existingConfigs = await proxyFetchGet('/api/configs'); + const existing = Array.isArray(existingConfigs) + ? existingConfigs.find( + (c: any) => + c.config_group?.toLowerCase() === 'codex' && + c.config_name === 'CODEX_OAUTH_TOKEN' + ) + : null; + + const configPayload = { + config_group: 'Codex', + config_name: 'CODEX_OAUTH_TOKEN', + config_value: 'exists', + }; + + if (existing) { + await proxyFetchPut( + `/api/configs/${existing.id}`, + configPayload + ); + } else { + await proxyFetchPost('/api/configs', configPayload); + } + } catch (configError) { + console.warn( + 'Failed to persist Codex marker config', + configError + ); + } + }; + onInstall = async () => { try { const response = await fetchPost('/install/tool/codex'); if (response.success) { await saveCodexAsProvider(response); + await saveCodexMarkerConfig(); const codexItem = { id: 0, key: key, @@ -295,6 +329,7 @@ const ToolSelect = forwardRef< ); if (retryResponse.success) { await saveCodexAsProvider(retryResponse); + await saveCodexMarkerConfig(); fetchIntegrationsData(); const codexItem = { id: 0, @@ -342,16 +377,16 @@ const ToolSelect = forwardRef< env_vars: value.env_vars, toolkit: value.toolkit, desc: - value.env_vars && value.env_vars.length > 0 - ? `${t('layout.environmental-variables-required')} ${value.env_vars.join( - ', ' - )}` + key.toLowerCase() === 'codex' + ? t('layout.codex-integration') : key.toLowerCase() === 'notion' ? t('layout.notion-workspace-integration') : key.toLowerCase() === 'google calendar' ? t('layout.google-calendar-integration') - : key.toLowerCase() === 'codex' - ? t('layout.codex-integration') + : value.env_vars && value.env_vars.length > 0 + ? `${t('layout.environmental-variables-required')} ${value.env_vars.join( + ', ' + )}` : '', onInstall, }; diff --git a/src/hooks/useIntegrationManagement.ts b/src/hooks/useIntegrationManagement.ts index e60d697a..e89b7042 100644 --- a/src/hooks/useIntegrationManagement.ts +++ b/src/hooks/useIntegrationManagement.ts @@ -98,15 +98,15 @@ export function useIntegrationManagement(items: IntegrationItem[]) { ); map[item.key] = hasAccessToken; } else if (item.key === 'Codex') { - // Codex: check if OPENAI_API_KEY config is present - const hasApiKey = configs.some( + // Codex: check if CODEX_OAUTH_TOKEN marker config is present + const hasOAuthToken = configs.some( (c: any) => c.config_group?.toLowerCase() === 'codex' && - c.config_name === 'OPENAI_API_KEY' && + c.config_name === 'CODEX_OAUTH_TOKEN' && c.config_value && String(c.config_value).length > 0 ); - map[item.key] = hasApiKey; + map[item.key] = hasOAuthToken; } else { // For other integrations, use config_group presence const hasConfig = configs.some( diff --git a/src/pages/Setting/MCP.tsx b/src/pages/Setting/MCP.tsx index a449b8de..21cfb7b0 100644 --- a/src/pages/Setting/MCP.tsx +++ b/src/pages/Setting/MCP.tsx @@ -347,11 +347,11 @@ export default function SettingMCP() { ); } }; + } else if (key.toLowerCase() === 'codex') { // Codex OAuth obtains an OpenAI API key for model access. // After a successful flow we save it as an OpenAI *Provider* // (not a toolkit config) so it integrates with the model // configuration system. - } else if (key.toLowerCase() === 'codex') { const saveCodexAsProvider = async (installResponse: any) => { if (!installResponse?.access_token) return; try { @@ -391,12 +391,46 @@ export default function SettingMCP() { } }; + const saveCodexMarkerConfig = async () => { + try { + const existingConfigs = await proxyFetchGet('/api/configs'); + const existing = Array.isArray(existingConfigs) + ? existingConfigs.find( + (c: any) => + c.config_group?.toLowerCase() === 'codex' && + c.config_name === 'CODEX_OAUTH_TOKEN' + ) + : null; + + const configPayload = { + config_group: 'Codex', + config_name: 'CODEX_OAUTH_TOKEN', + config_value: 'exists', + }; + + if (existing) { + await proxyFetchPut( + `/api/configs/${existing.id}`, + configPayload + ); + } else { + await proxyFetchPost('/api/configs', configPayload); + } + } catch (configError) { + console.warn( + 'Failed to persist Codex marker config', + configError + ); + } + }; + onInstall = async () => { try { const response = await fetchPost('/install/tool/codex'); if (response.success) { toast.success(t('setting.codex-installed-successfully')); await saveCodexAsProvider(response); + await saveCodexMarkerConfig(); fetchList(); setRefreshKey((prev) => prev + 1); } else if (response.status === 'authorizing') { @@ -412,6 +446,7 @@ export default function SettingMCP() { const finalize = await fetchPost('/install/tool/codex'); if (finalize?.success) { await saveCodexAsProvider(finalize); + await saveCodexMarkerConfig(); toast.success( t('setting.codex-installed-successfully') ); @@ -462,16 +497,16 @@ export default function SettingMCP() { name: key, env_vars: value.env_vars, desc: - value.env_vars && value.env_vars.length > 0 - ? `${t( - 'setting.environmental-variables-required' - )}: ${value.env_vars.join(', ')}` + key.toLowerCase() === 'codex' + ? t('setting.codex-integration') : key.toLowerCase() === 'notion' ? t('setting.notion-workspace-integration') : key.toLowerCase() === 'google calendar' ? t('setting.google-calendar-integration') - : key.toLowerCase() === 'codex' - ? t('setting.codex-integration') + : value.env_vars && value.env_vars.length > 0 + ? `${t( + 'setting.environmental-variables-required' + )}: ${value.env_vars.join(', ')}` : '', onInstall, };