diff --git a/docs/providers/provider-runtimes.md b/docs/providers/provider-runtimes.md
index 1f740d7..a21c036 100644
--- a/docs/providers/provider-runtimes.md
+++ b/docs/providers/provider-runtimes.md
@@ -274,7 +274,7 @@ If you want the user-facing setup workflow instead of the runtime internals, use
- `Guided`: `workspace-write` + `untrusted` + network off + web search cached
- `Auto`: `danger-full-access` + `never` + network on + web search live
-Current Codex defaults follow the App Server-aligned baseline in Stave: `workspace-write` file access, `untrusted` approvals, `network access = off`, `web search = cached`, `reasoning effort = medium`, raw reasoning off, and reasoning summary auto-detection enabled.
+Current Codex defaults follow the App Server-aligned baseline in Stave: `workspace-write` file access, `untrusted` approvals, `network access = off`, `web search = cached`, `reasoning effort = xhigh` (the codex-cli 0.144.1 server-catalog default for the GPT-5.6 family), raw reasoning off, and reasoning summary auto-detection enabled.
Stave now forwards an explicit `show_raw_agent_reasoning: false` override when the Codex UI toggle is off, so local CLI defaults or config files do not leave raw reasoning enabled unexpectedly.
diff --git a/src/components/layout/PresetBar.tsx b/src/components/layout/PresetBar.tsx
index 00a8d81..9018980 100644
--- a/src/components/layout/PresetBar.tsx
+++ b/src/components/layout/PresetBar.tsx
@@ -219,7 +219,14 @@ function PresetChip(props: PresetChipProps) {
-
+ event.preventDefault()}
+ >
onRequestEdit()}>
Edit…
diff --git a/src/components/layout/settings-dialog-providers-section.tsx b/src/components/layout/settings-dialog-providers-section.tsx
index 5f96dda..1a22c8a 100644
--- a/src/components/layout/settings-dialog-providers-section.tsx
+++ b/src/components/layout/settings-dialog-providers-section.tsx
@@ -48,6 +48,7 @@ import {
DEFAULT_CLAUDE_OPUS_MODEL,
DEFAULT_CLAUDE_SONNET_MODEL,
getDefaultModelForProvider,
+ listCodexReasoningEffortsForModel,
} from "@/lib/providers/model-catalog";
import { useCodexModelCatalog } from "@/lib/providers/use-codex-model-catalog";
import { UI_LAYER_CLASS } from "@/lib/ui-layers";
@@ -366,6 +367,22 @@ const CODEX_REASONING_EFFORT_HELP = [
example:
"Reserve this for genuinely complex work where you want Codex to think much longer.",
},
+ {
+ value: "max",
+ label: "Max",
+ description:
+ "Highest deliberation on the GPT-5.6 effort scale, above `xhigh`.",
+ example:
+ "Reserve this for hard multi-step implementation or debugging work where accuracy matters more than speed.",
+ },
+ {
+ value: "ultra",
+ label: "Ultra",
+ description:
+ "The deepest reasoning budget Codex offers and the highest latency cost. Not every model accepts this tier (e.g. GPT-5.6 Luna caps at `max`).",
+ example:
+ "Reserve this for the hardest frontier-model tasks where you want Codex to think as long as possible.",
+ },
] as const satisfies readonly ExplainedSelectOption<
NonNullable
>[];
@@ -604,6 +621,7 @@ export function ProvidersSection() {
codexNetworkAccess,
codexApprovalPolicy,
codexReasoningEffort,
+ modelCodex,
codexWebSearch,
codexShowRawReasoning,
codexReasoningSummary,
@@ -640,6 +658,7 @@ export function ProvidersSection() {
state.settings.codexNetworkAccess,
state.settings.codexApprovalPolicy,
state.settings.codexReasoningEffort,
+ state.settings.modelCodex,
state.settings.codexWebSearch,
state.settings.codexShowRawReasoning,
state.settings.codexReasoningSummary,
@@ -683,6 +702,18 @@ export function ProvidersSection() {
model: defaultClaudeAdvisorModel,
fallback: DEFAULT_CLAUDE_SONNET_MODEL,
});
+ // Scoped to the default Codex model so, e.g., GPT-5.6 Luna never offers
+ // "Ultra" here — a value only Sol/Terra accept. "Minimal" is always kept
+ // available since it's a legacy value Stave still maps to "low" at
+ // runtime, not part of the current model-reported effort scale.
+ const codexReasoningEffortOptions = useMemo(() => {
+ const supported = listCodexReasoningEffortsForModel({ model: modelCodex });
+ return CODEX_REASONING_EFFORT_HELP.filter(
+ (option) =>
+ option.value === "minimal" ||
+ (supported as readonly string[]).includes(option.value),
+ );
+ }, [modelCodex]);
const claudeAdvisorModelEnabled = claudeAdvisorModel.trim().length > 0;
const activeClaudeAdvisorSourceModel = resolveClaudeAdvisorSourceModel({
model: claudeAdvisorModel.trim() || modelClaude,
@@ -1237,15 +1268,15 @@ export function ProvidersSection() {
}
>
updateSettings({
patch: {
diff --git a/src/components/layout/settings-dialog-sections.tsx b/src/components/layout/settings-dialog-sections.tsx
index b86b87f..bb07f12 100644
--- a/src/components/layout/settings-dialog-sections.tsx
+++ b/src/components/layout/settings-dialog-sections.tsx
@@ -104,7 +104,7 @@ import { useCodexModelCatalog } from "@/lib/providers/use-codex-model-catalog";
import {
BOOLEAN_TOGGLE_OPTIONS,
CLAUDE_EFFORT_OPTIONS,
- CODEX_EFFORT_OPTIONS,
+ listCodexEffortOptionsForModel,
} from "@/lib/providers/runtime-option-contract";
import { cn } from "@/lib/utils";
import {
@@ -1789,6 +1789,12 @@ function ModelsSection() {
() => modelOptions.filter((option) => option.providerId === "codex"),
[modelOptions],
);
+ // Scoped to the default Codex model so, e.g., GPT-5.6 Luna never offers
+ // "Ultra" here — a value only Sol/Terra accept.
+ const codexEffortOptions = useMemo(
+ () => listCodexEffortOptionsForModel({ model: modelCodex }),
+ [modelCodex],
+ );
const updateEligibleModels = useCallback(
(args: { providerId: "claude-code" | "codex"; models: string[] }) => {
updateSettings({
@@ -1950,7 +1956,7 @@ function ModelsSection() {
- {CODEX_EFFORT_OPTIONS.map((option) => (
+ {codexEffortOptions.map((option) => (
{option.label}
diff --git a/src/components/layout/task-preset-editor.tsx b/src/components/layout/task-preset-editor.tsx
index ff85138..116b3db 100644
--- a/src/components/layout/task-preset-editor.tsx
+++ b/src/components/layout/task-preset-editor.tsx
@@ -59,9 +59,11 @@ export function TaskPresetEditor(props: TaskPresetEditorProps) {
() => listModelsForPresetProvider(provider),
[provider],
);
+ // Model-scoped so, e.g., GPT-5.6 Luna never offers "Ultra" — a value only
+ // Sol/Terra accept.
const effortOptions = useMemo(
- () => listEffortsForPresetProvider(provider),
- [provider],
+ () => listEffortsForPresetProvider(provider, model),
+ [provider, model],
);
const providerOptions = useMemo<
{ value: ProviderId; label: string }[]
@@ -91,10 +93,30 @@ export function TaskPresetEditor(props: TaskPresetEditorProps) {
: "claude-code";
setProvider(providerId);
const nextModels = listModelsForPresetProvider(providerId);
- if (!nextModels.includes(model)) {
- setModel(getDefaultModelForProvider({ providerId }));
+ const nextModel = nextModels.includes(model)
+ ? model
+ : getDefaultModelForProvider({ providerId });
+ if (nextModel !== model) {
+ setModel(nextModel);
}
- const nextEfforts = listEffortsForPresetProvider(providerId).map(
+ const nextEfforts = listEffortsForPresetProvider(
+ providerId,
+ nextModel,
+ ).map((option) => option.value);
+ if (
+ effort !== DEFAULT_EFFORT_VALUE &&
+ !nextEfforts.includes(effort as TaskPresetEffort)
+ ) {
+ setEffort(DEFAULT_EFFORT_VALUE);
+ }
+ }
+
+ function handleModelChange(nextModel: string) {
+ setModel(nextModel);
+ // Some Codex models accept a narrower effort scale than others (e.g.
+ // GPT-5.6 Luna has no "Ultra"); drop back to the default when the
+ // currently selected effort isn't valid for the new model.
+ const nextEfforts = listEffortsForPresetProvider(provider, nextModel).map(
(option) => option.value,
);
if (
@@ -170,7 +192,7 @@ export function TaskPresetEditor(props: TaskPresetEditorProps) {
Model
-