feat(settings): add Skills toggle for Codex CLI and improve config editor UX

Add "Enable Skills" toggle below "Enable WebSocket" in Codex CLI settings,
writing `skills = true` under [features] in config.toml. Also add max-height
to auth.json and config.toml textareas, and fix TOML section key insertion
to avoid stray blank lines between entries.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
xintaofei
2026-04-11 01:14:17 +08:00
parent 19979d50d0
commit 1eb9b4872e
11 changed files with 105 additions and 3 deletions

View File

@@ -118,6 +118,7 @@ interface AgentDraft {
codexProviderOptions: string[] codexProviderOptions: string[]
codexReasoningEffort: CodexReasoningEffort codexReasoningEffort: CodexReasoningEffort
codexSupportsWebsockets: boolean codexSupportsWebsockets: boolean
codexSkills: boolean
claudeMainModel: string claudeMainModel: string
claudeReasoningModel: string claudeReasoningModel: string
claudeDefaultHaikuModel: string claudeDefaultHaikuModel: string
@@ -1142,6 +1143,7 @@ interface CodexTomlImportantValues {
providerBaseUrls: Record<string, string> providerBaseUrls: Record<string, string>
providerSupportsWebsockets: Record<string, boolean> providerSupportsWebsockets: Record<string, boolean>
featureResponsesWebsocketsV2: boolean featureResponsesWebsocketsV2: boolean
featureSkills: boolean
} }
interface CodexImportantValues { interface CodexImportantValues {
@@ -1152,6 +1154,7 @@ interface CodexImportantValues {
reasoningEffort: CodexReasoningEffort reasoningEffort: CodexReasoningEffort
providerOptions: string[] providerOptions: string[]
supportsWebsockets: boolean supportsWebsockets: boolean
skills: boolean
} }
const CODEX_DEFAULT_MODEL_PROVIDER = "codeg" const CODEX_DEFAULT_MODEL_PROVIDER = "codeg"
@@ -1312,6 +1315,7 @@ function extractCodexTomlImportantValues(
let modelReasoningEffort: CodexReasoningEffort = let modelReasoningEffort: CodexReasoningEffort =
CODEX_DEFAULT_REASONING_EFFORT CODEX_DEFAULT_REASONING_EFFORT
let featureResponsesWebsocketsV2 = false let featureResponsesWebsocketsV2 = false
let featureSkills = false
let currentProviderSection: string | null = null let currentProviderSection: string | null = null
let inFeaturesSection = false let inFeaturesSection = false
@@ -1377,6 +1381,10 @@ function extractCodexTomlImportantValues(
featureResponsesWebsocketsV2 = boolAssignment.value featureResponsesWebsocketsV2 = boolAssignment.value
continue continue
} }
if (inFeaturesSection && boolAssignment.key === "skills") {
featureSkills = boolAssignment.value
continue
}
const dottedProviderWebsocketMatch = boolAssignment.key.match( const dottedProviderWebsocketMatch = boolAssignment.key.match(
/^model_providers\.([A-Za-z0-9_-]+)\.supports_websockets$/ /^model_providers\.([A-Za-z0-9_-]+)\.supports_websockets$/
) )
@@ -1390,6 +1398,10 @@ function extractCodexTomlImportantValues(
featureResponsesWebsocketsV2 = boolAssignment.value featureResponsesWebsocketsV2 = boolAssignment.value
continue continue
} }
if (boolAssignment.key === "features.skills") {
featureSkills = boolAssignment.value
continue
}
} }
if (!assignment) continue if (!assignment) continue
@@ -1436,6 +1448,7 @@ function extractCodexTomlImportantValues(
providerBaseUrls, providerBaseUrls,
providerSupportsWebsockets, providerSupportsWebsockets,
featureResponsesWebsocketsV2, featureResponsesWebsocketsV2,
featureSkills,
} }
} }
@@ -1530,6 +1543,7 @@ function extractCodexImportantValues(
toml.providerNames toml.providerNames
), ),
supportsWebsockets: providerSupportsWebsockets, supportsWebsockets: providerSupportsWebsockets,
skills: toml.featureSkills,
} }
} }
@@ -1701,7 +1715,14 @@ function upsertTomlSectionBooleanKey(
if (assignmentIndex >= 0) { if (assignmentIndex >= 0) {
lines[assignmentIndex] = lineText lines[assignmentIndex] = lineText
} else { } else {
lines.splice(section.end, 0, lineText) let insertAt = section.end
for (let i = section.end - 1; i > section.start; i -= 1) {
if (lines[i].trim() !== "") {
insertAt = i + 1
break
}
}
lines.splice(insertAt, 0, lineText)
} }
return lines.join("\n").trim() return lines.join("\n").trim()
} }
@@ -1927,6 +1948,7 @@ function patchCodexConfigTomlText(
modelProvider?: string modelProvider?: string
modelReasoningEffort?: string modelReasoningEffort?: string
supportsWebsockets?: boolean supportsWebsockets?: boolean
skills?: boolean
} }
): string { ): string {
let nextTomlText = configTomlText let nextTomlText = configTomlText
@@ -2019,6 +2041,14 @@ function patchCodexConfigTomlText(
"responses_websockets_v2", "responses_websockets_v2",
shouldEnableFeature ? true : null shouldEnableFeature ? true : null
) )
if (typeof patch.skills === "boolean") {
nextTomlText = upsertTomlSectionBooleanKey(
nextTomlText,
"features",
"skills",
patch.skills ? true : null
)
}
nextTomlText = updateTomlRootBooleanKey( nextTomlText = updateTomlRootBooleanKey(
nextTomlText, nextTomlText,
"disable_response_storage", "disable_response_storage",
@@ -2252,6 +2282,7 @@ function buildAgentDraft(agent: AcpAgentInfo): AgentDraft {
codexProviderOptions: codexImportant.providerOptions, codexProviderOptions: codexImportant.providerOptions,
codexReasoningEffort: codexImportant.reasoningEffort, codexReasoningEffort: codexImportant.reasoningEffort,
codexSupportsWebsockets: codexImportant.supportsWebsockets, codexSupportsWebsockets: codexImportant.supportsWebsockets,
codexSkills: codexImportant.skills,
claudeMainModel: important.claudeMainModel, claudeMainModel: important.claudeMainModel,
claudeReasoningModel: important.claudeReasoningModel, claudeReasoningModel: important.claudeReasoningModel,
claudeDefaultHaikuModel: important.claudeDefaultHaikuModel, claudeDefaultHaikuModel: important.claudeDefaultHaikuModel,
@@ -4432,6 +4463,7 @@ export function AcpAgentSettings() {
codexProviderOptions: important.providerOptions, codexProviderOptions: important.providerOptions,
codexReasoningEffort: important.reasoningEffort, codexReasoningEffort: important.reasoningEffort,
codexSupportsWebsockets: important.supportsWebsockets, codexSupportsWebsockets: important.supportsWebsockets,
codexSkills: important.skills,
})) }))
}, },
[selectedAgent, selectedDraft, updateSelectedDraft] [selectedAgent, selectedDraft, updateSelectedDraft]
@@ -4454,6 +4486,7 @@ export function AcpAgentSettings() {
codexProviderOptions: important.providerOptions, codexProviderOptions: important.providerOptions,
codexReasoningEffort: important.reasoningEffort, codexReasoningEffort: important.reasoningEffort,
codexSupportsWebsockets: important.supportsWebsockets, codexSupportsWebsockets: important.supportsWebsockets,
codexSkills: important.skills,
})) }))
}, },
[selectedAgent, selectedDraft, updateSelectedDraft] [selectedAgent, selectedDraft, updateSelectedDraft]
@@ -4505,6 +4538,7 @@ export function AcpAgentSettings() {
codexProviderOptions: synced.providerOptions, codexProviderOptions: synced.providerOptions,
codexReasoningEffort: synced.reasoningEffort, codexReasoningEffort: synced.reasoningEffort,
codexSupportsWebsockets: synced.supportsWebsockets, codexSupportsWebsockets: synced.supportsWebsockets,
codexSkills: synced.skills,
})) }))
return return
} }
@@ -4537,6 +4571,7 @@ export function AcpAgentSettings() {
codexProviderOptions: synced.providerOptions, codexProviderOptions: synced.providerOptions,
codexReasoningEffort: synced.reasoningEffort, codexReasoningEffort: synced.reasoningEffort,
codexSupportsWebsockets: synced.supportsWebsockets, codexSupportsWebsockets: synced.supportsWebsockets,
codexSkills: synced.skills,
})) }))
}, },
[selectedAgent, selectedDraft, updateSelectedDraft] [selectedAgent, selectedDraft, updateSelectedDraft]
@@ -4602,6 +4637,7 @@ export function AcpAgentSettings() {
codexProviderOptions: synced.providerOptions, codexProviderOptions: synced.providerOptions,
codexReasoningEffort: synced.reasoningEffort, codexReasoningEffort: synced.reasoningEffort,
codexSupportsWebsockets: synced.supportsWebsockets, codexSupportsWebsockets: synced.supportsWebsockets,
codexSkills: synced.skills,
codexAuthJsonText: nextAuth.authJsonText, codexAuthJsonText: nextAuth.authJsonText,
codexConfigTomlText: nextToml, codexConfigTomlText: nextToml,
})) }))
@@ -4637,6 +4673,39 @@ export function AcpAgentSettings() {
codexProviderOptions: synced.providerOptions, codexProviderOptions: synced.providerOptions,
codexReasoningEffort: synced.reasoningEffort, codexReasoningEffort: synced.reasoningEffort,
codexSupportsWebsockets: synced.supportsWebsockets, codexSupportsWebsockets: synced.supportsWebsockets,
codexSkills: synced.skills,
codexConfigTomlText: nextToml,
}))
},
[selectedAgent, selectedDraft, updateSelectedDraft]
)
const handleCodexSkillsChange = useCallback(
(enabled: boolean) => {
if (
!selectedAgent ||
!selectedDraft ||
selectedAgent.agent_type !== "codex"
)
return
const nextToml = patchCodexConfigTomlText(
selectedDraft.codexConfigTomlText,
{ skills: enabled }
)
const synced = extractCodexImportantValues(
selectedDraft.codexAuthJsonText,
nextToml
)
updateSelectedDraft((current) => ({
...current,
apiBaseUrl: synced.apiBaseUrl,
apiKey: synced.apiKey ?? current.apiKey,
model: synced.model,
codexModelProvider: synced.modelProvider,
codexProviderOptions: synced.providerOptions,
codexReasoningEffort: synced.reasoningEffort,
codexSupportsWebsockets: synced.supportsWebsockets,
codexSkills: synced.skills,
codexConfigTomlText: nextToml, codexConfigTomlText: nextToml,
})) }))
}, },
@@ -5196,6 +5265,19 @@ export function AcpAgentSettings() {
</div> </div>
</div> </div>
<div className="space-y-1.5">
<div className="flex items-center justify-between rounded-md border px-3 py-2">
<label className="text-[11px] text-muted-foreground">
{t("codex.enableSkills")}
</label>
<Switch
checked={selectedDraft.codexSkills}
onCheckedChange={handleCodexSkillsChange}
aria-label={t("codex.enableSkillsAria")}
/>
</div>
</div>
<div className="space-y-1.5"> <div className="space-y-1.5">
<label className="text-[11px] text-muted-foreground"> <label className="text-[11px] text-muted-foreground">
{t("codex.authJsonNative")} {t("codex.authJsonNative")}
@@ -5208,7 +5290,7 @@ export function AcpAgentSettings() {
placeholder={`{ placeholder={`{
"OPENAI_API_KEY": "sk-..." "OPENAI_API_KEY": "sk-..."
}`} }`}
className="min-h-28 font-mono text-xs" className="min-h-28 max-h-60 font-mono text-xs"
/> />
{selectedCodexAuthError && ( {selectedCodexAuthError && (
<div className="rounded-md border border-red-500/30 bg-red-500/5 px-2.5 py-1.5 text-[11px] text-red-400"> <div className="rounded-md border border-red-500/30 bg-red-500/5 px-2.5 py-1.5 text-[11px] text-red-400">
@@ -5237,7 +5319,7 @@ responses_websockets_v2 = true
[model_providers.codeg] [model_providers.codeg]
base_url = "https://api.openai.com/v1" base_url = "https://api.openai.com/v1"
supports_websockets = true`} supports_websockets = true`}
className="min-h-40 font-mono text-xs" className="min-h-40 max-h-80 font-mono text-xs"
/> />
</div> </div>

View File

@@ -515,6 +515,8 @@
"selectReasoningEffort": "اختر Reasoning Effort", "selectReasoningEffort": "اختر Reasoning Effort",
"enableWebsocket": "تفعيل WebSocket", "enableWebsocket": "تفعيل WebSocket",
"enableWebsocketAria": "تفعيل WebSocket لـ Codex Provider", "enableWebsocketAria": "تفعيل WebSocket لـ Codex Provider",
"enableSkills": "تفعيل Skills",
"enableSkillsAria": "تفعيل Skills لـ Codex",
"authJsonNative": "auth.json (أصلي)", "authJsonNative": "auth.json (أصلي)",
"configTomlNative": "config.toml (أصلي)" "configTomlNative": "config.toml (أصلي)"
}, },

View File

@@ -515,6 +515,8 @@
"selectReasoningEffort": "Reasoning Effort auswählen", "selectReasoningEffort": "Reasoning Effort auswählen",
"enableWebsocket": "WebSocket aktivieren", "enableWebsocket": "WebSocket aktivieren",
"enableWebsocketAria": "WebSocket für Codex Provider aktivieren", "enableWebsocketAria": "WebSocket für Codex Provider aktivieren",
"enableSkills": "Skills aktivieren",
"enableSkillsAria": "Skills für Codex aktivieren",
"authJsonNative": "auth.json (nativ)", "authJsonNative": "auth.json (nativ)",
"configTomlNative": "config.toml (nativ)" "configTomlNative": "config.toml (nativ)"
}, },

View File

@@ -515,6 +515,8 @@
"selectReasoningEffort": "Select Reasoning Effort", "selectReasoningEffort": "Select Reasoning Effort",
"enableWebsocket": "Enable WebSocket", "enableWebsocket": "Enable WebSocket",
"enableWebsocketAria": "Enable WebSocket for Codex Provider", "enableWebsocketAria": "Enable WebSocket for Codex Provider",
"enableSkills": "Enable Skills",
"enableSkillsAria": "Enable Skills for Codex",
"authJsonNative": "auth.json (native)", "authJsonNative": "auth.json (native)",
"configTomlNative": "config.toml (native)" "configTomlNative": "config.toml (native)"
}, },

View File

@@ -515,6 +515,8 @@
"selectReasoningEffort": "Seleccionar Reasoning Effort", "selectReasoningEffort": "Seleccionar Reasoning Effort",
"enableWebsocket": "Habilitar WebSocket", "enableWebsocket": "Habilitar WebSocket",
"enableWebsocketAria": "Habilitar WebSocket para Codex Provider", "enableWebsocketAria": "Habilitar WebSocket para Codex Provider",
"enableSkills": "Habilitar Skills",
"enableSkillsAria": "Habilitar Skills para Codex",
"authJsonNative": "auth.json (nativo)", "authJsonNative": "auth.json (nativo)",
"configTomlNative": "config.toml (nativo)" "configTomlNative": "config.toml (nativo)"
}, },

View File

@@ -515,6 +515,8 @@
"selectReasoningEffort": "Sélectionner Reasoning Effort", "selectReasoningEffort": "Sélectionner Reasoning Effort",
"enableWebsocket": "Activer WebSocket", "enableWebsocket": "Activer WebSocket",
"enableWebsocketAria": "Activer WebSocket pour Codex Provider", "enableWebsocketAria": "Activer WebSocket pour Codex Provider",
"enableSkills": "Activer Skills",
"enableSkillsAria": "Activer Skills pour Codex",
"authJsonNative": "auth.json (natif)", "authJsonNative": "auth.json (natif)",
"configTomlNative": "config.toml (natif)" "configTomlNative": "config.toml (natif)"
}, },

View File

@@ -515,6 +515,8 @@
"selectReasoningEffort": "Reasoning Effort を選択", "selectReasoningEffort": "Reasoning Effort を選択",
"enableWebsocket": "WebSocket を有効化", "enableWebsocket": "WebSocket を有効化",
"enableWebsocketAria": "Codex Provider の WebSocket を有効化", "enableWebsocketAria": "Codex Provider の WebSocket を有効化",
"enableSkills": "Skills を有効化",
"enableSkillsAria": "Codex の Skills を有効化",
"authJsonNative": "auth.jsonネイティブ", "authJsonNative": "auth.jsonネイティブ",
"configTomlNative": "config.tomlネイティブ" "configTomlNative": "config.tomlネイティブ"
}, },

View File

@@ -515,6 +515,8 @@
"selectReasoningEffort": "Reasoning Effort 선택", "selectReasoningEffort": "Reasoning Effort 선택",
"enableWebsocket": "WebSocket 활성화", "enableWebsocket": "WebSocket 활성화",
"enableWebsocketAria": "Codex Provider용 WebSocket 활성화", "enableWebsocketAria": "Codex Provider용 WebSocket 활성화",
"enableSkills": "Skills 활성화",
"enableSkillsAria": "Codex용 Skills 활성화",
"authJsonNative": "auth.json (네이티브)", "authJsonNative": "auth.json (네이티브)",
"configTomlNative": "config.toml (네이티브)" "configTomlNative": "config.toml (네이티브)"
}, },

View File

@@ -515,6 +515,8 @@
"selectReasoningEffort": "Selecionar Reasoning Effort", "selectReasoningEffort": "Selecionar Reasoning Effort",
"enableWebsocket": "Habilitar WebSocket", "enableWebsocket": "Habilitar WebSocket",
"enableWebsocketAria": "Habilitar WebSocket para Codex Provider", "enableWebsocketAria": "Habilitar WebSocket para Codex Provider",
"enableSkills": "Habilitar Skills",
"enableSkillsAria": "Habilitar Skills para Codex",
"authJsonNative": "auth.json (nativo)", "authJsonNative": "auth.json (nativo)",
"configTomlNative": "config.toml (nativo)" "configTomlNative": "config.toml (nativo)"
}, },

View File

@@ -515,6 +515,8 @@
"selectReasoningEffort": "选择 Reasoning Effort", "selectReasoningEffort": "选择 Reasoning Effort",
"enableWebsocket": "启用 WebSocket", "enableWebsocket": "启用 WebSocket",
"enableWebsocketAria": "Codex Provider 启用 WebSocket", "enableWebsocketAria": "Codex Provider 启用 WebSocket",
"enableSkills": "启用 Skills",
"enableSkillsAria": "Codex 启用 Skills",
"authJsonNative": "auth.json原生", "authJsonNative": "auth.json原生",
"configTomlNative": "config.toml原生" "configTomlNative": "config.toml原生"
}, },

View File

@@ -515,6 +515,8 @@
"selectReasoningEffort": "選擇 Reasoning Effort", "selectReasoningEffort": "選擇 Reasoning Effort",
"enableWebsocket": "啟用 WebSocket", "enableWebsocket": "啟用 WebSocket",
"enableWebsocketAria": "Codex Provider 啟用 WebSocket", "enableWebsocketAria": "Codex Provider 啟用 WebSocket",
"enableSkills": "啟用 Skills",
"enableSkillsAria": "Codex 啟用 Skills",
"authJsonNative": "auth.json原生", "authJsonNative": "auth.json原生",
"configTomlNative": "config.toml原生" "configTomlNative": "config.toml原生"
}, },