fix(native-chat): don't force a default model/effort on agent spawn (#9134)
#9085 made resolveNativeChatSessionOptionDefaults fall back to the catalog default model (sonnet) and effort (high) whenever the user had not explicitly picked one, injecting `--model sonnet --effort high` (and codex `-m`/`-c` equivalents) onto every agent spawn: composer, worktree activation, source-control agents, onboarding, work-item launch, floating terminal, and new-tab launch. That silently overrode each user's own CLI-configured default model. Return undefined (spawn the agent exactly as its own CLI would) unless the user has explicitly selected a model, and carry only explicitly stored option values. An explicit model selection still applies that model and its catalog option defaults via resolveAgentSessionOptionLaunch.
This commit is contained in:
parent
1b331f282c
commit
1284a00e93
|
|
@ -14,8 +14,7 @@ describe('buildOnboardingFolderAgentStartup', () => {
|
|||
})
|
||||
|
||||
expect(startup).toEqual({
|
||||
command:
|
||||
"codex '-m' 'gpt-5.6-sol' '-c' 'model_reasoning_effort=medium' '--dangerously-bypass-approvals-and-sandbox'",
|
||||
command: "codex '--dangerously-bypass-approvals-and-sandbox'",
|
||||
env: {},
|
||||
launchAgent: 'codex',
|
||||
launchConfig: {
|
||||
|
|
@ -23,7 +22,7 @@ describe('buildOnboardingFolderAgentStartup', () => {
|
|||
agentArgs: '--dangerously-bypass-approvals-and-sandbox',
|
||||
agentEnv: {}
|
||||
},
|
||||
sessionOptions: { model: 'gpt-5.6-sol', effort: 'medium' },
|
||||
sessionOptions: undefined,
|
||||
telemetry: {
|
||||
agent_kind: 'codex',
|
||||
launch_source: 'onboarding',
|
||||
|
|
@ -99,8 +98,7 @@ describe('buildOnboardingFolderAgentStartup', () => {
|
|||
false
|
||||
)
|
||||
).toEqual({
|
||||
command:
|
||||
"echo onboarding-folder-agent '-m' 'gpt-5.6-sol' '-c' 'model_reasoning_effort=medium' '--dangerously-bypass-approvals-and-sandbox'",
|
||||
command: "echo onboarding-folder-agent '--dangerously-bypass-approvals-and-sandbox'",
|
||||
env: {},
|
||||
launchAgent: 'codex',
|
||||
launchConfig: {
|
||||
|
|
@ -108,7 +106,7 @@ describe('buildOnboardingFolderAgentStartup', () => {
|
|||
agentArgs: '--dangerously-bypass-approvals-and-sandbox',
|
||||
agentEnv: {}
|
||||
},
|
||||
sessionOptions: { model: 'gpt-5.6-sol', effort: 'medium' },
|
||||
sessionOptions: undefined,
|
||||
telemetry: {
|
||||
agent_kind: 'codex',
|
||||
launch_source: 'onboarding',
|
||||
|
|
|
|||
|
|
@ -340,8 +340,7 @@ describe('launchAgentInNewTab', () => {
|
|||
environmentId: 'web-runtime',
|
||||
targetGroupId: 'group-1',
|
||||
activate: true,
|
||||
command:
|
||||
"codex '-m' 'gpt-5.6-sol' '-c' 'model_reasoning_effort=medium' '--model' 'gpt-5' '--reasoning-effort' 'high' 'fix the spinner'",
|
||||
command: "codex '--model' 'gpt-5' '--reasoning-effort' 'high' 'fix the spinner'",
|
||||
env: { CODEX_PROFILE: 'captured' },
|
||||
startupCommandDelivery: 'shell-ready',
|
||||
launchConfig: {
|
||||
|
|
@ -486,8 +485,7 @@ describe('launchAgentInNewTab', () => {
|
|||
expect(mockQueueTabStartupCommand).toHaveBeenCalledWith(
|
||||
'tab-1',
|
||||
expect.objectContaining({
|
||||
command:
|
||||
"claude '--model' 'sonnet' '--effort' 'high' '--dangerously-skip-permissions' --prefill 'review Bob''s change'"
|
||||
command: "claude '--dangerously-skip-permissions' --prefill 'review Bob''s change'"
|
||||
})
|
||||
)
|
||||
})
|
||||
|
|
@ -505,7 +503,7 @@ describe('launchAgentInNewTab', () => {
|
|||
expect(mockQueueTabStartupCommand).toHaveBeenCalledWith(
|
||||
'tab-1',
|
||||
expect.objectContaining({
|
||||
command: 'claude "--model" "sonnet" "--effort" "high" "--dangerously-skip-permissions"'
|
||||
command: 'claude "--dangerously-skip-permissions"'
|
||||
})
|
||||
)
|
||||
})
|
||||
|
|
@ -523,7 +521,7 @@ describe('launchAgentInNewTab', () => {
|
|||
expect(mockQueueTabStartupCommand).toHaveBeenCalledWith(
|
||||
'tab-1',
|
||||
expect.objectContaining({
|
||||
command: "claude '--model' 'sonnet' '--effort' 'high' '--dangerously-skip-permissions'"
|
||||
command: "claude '--dangerously-skip-permissions'"
|
||||
})
|
||||
)
|
||||
})
|
||||
|
|
@ -543,8 +541,7 @@ describe('launchAgentInNewTab', () => {
|
|||
expect(mockQueueTabStartupCommand).toHaveBeenCalledWith(
|
||||
'tab-1',
|
||||
expect.objectContaining({
|
||||
command:
|
||||
'codex "-m" "gpt-5.6-sol" "-c" "model_reasoning_effort=medium" "--model" "gpt-5" "fix the spinner"'
|
||||
command: 'codex "--model" "gpt-5" "fix the spinner"'
|
||||
})
|
||||
)
|
||||
})
|
||||
|
|
@ -564,8 +561,7 @@ describe('launchAgentInNewTab', () => {
|
|||
expect(mockQueueTabStartupCommand).toHaveBeenCalledWith(
|
||||
'tab-1',
|
||||
expect.objectContaining({
|
||||
command:
|
||||
"claude '--model' 'sonnet' '--effort' 'high' '--dangerously-skip-permissions' --prefill 'review Bob'\\''s change'"
|
||||
command: "claude '--dangerously-skip-permissions' --prefill 'review Bob'\\''s change'"
|
||||
})
|
||||
)
|
||||
})
|
||||
|
|
@ -583,7 +579,7 @@ describe('launchAgentInNewTab', () => {
|
|||
expect(mockQueueTabStartupCommand).toHaveBeenCalledWith(
|
||||
'tab-1',
|
||||
expect.objectContaining({
|
||||
command: "claude '--model' 'sonnet' '--effort' 'high' '--dangerously-skip-permissions'"
|
||||
command: "claude '--dangerously-skip-permissions'"
|
||||
})
|
||||
)
|
||||
})
|
||||
|
|
@ -620,8 +616,7 @@ describe('launchAgentInNewTab', () => {
|
|||
expect(mockQueueTabStartupCommand).toHaveBeenCalledWith(
|
||||
'tab-1',
|
||||
expect.objectContaining({
|
||||
command:
|
||||
"claude '--model' 'sonnet' '--effort' 'high' '--dangerously-skip-permissions' --prefill 'review Bob'\\''s change'"
|
||||
command: "claude '--dangerously-skip-permissions' --prefill 'review Bob'\\''s change'"
|
||||
})
|
||||
)
|
||||
})
|
||||
|
|
@ -642,7 +637,7 @@ describe('launchAgentInNewTab', () => {
|
|||
expect(mockQueueTabStartupCommand).toHaveBeenCalledWith(
|
||||
'tab-1',
|
||||
expect.objectContaining({
|
||||
command: "claude '--model' 'sonnet' '--effort' 'high' '--dangerously-skip-permissions'"
|
||||
command: "claude '--dangerously-skip-permissions'"
|
||||
})
|
||||
)
|
||||
expect(mockPasteDraftWhenAgentReady).toHaveBeenCalledWith(
|
||||
|
|
@ -848,7 +843,7 @@ describe('launchAgentInNewTab', () => {
|
|||
expect(mockQueueTabStartupCommand).toHaveBeenCalledWith(
|
||||
'tab-1',
|
||||
expect.objectContaining({
|
||||
command: "codex '-m' 'gpt-5.6-sol' '-c' 'model_reasoning_effort=medium' '--model' 'gpt-5.5'"
|
||||
command: "codex '--model' 'gpt-5.5'"
|
||||
})
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -402,7 +402,7 @@ describe('launchWorkItemDirect', () => {
|
|||
cmdOverrides: {},
|
||||
agentArgs: '--dangerously-skip-permissions',
|
||||
agentEnv: {},
|
||||
sessionOptions: { model: 'sonnet', effort: 'high' },
|
||||
sessionOptions: undefined,
|
||||
platform: 'win32',
|
||||
isRemote: false
|
||||
})
|
||||
|
|
@ -526,7 +526,7 @@ describe('launchWorkItemDirect', () => {
|
|||
cmdOverrides: {},
|
||||
agentArgs: '--yolo',
|
||||
agentEnv: {},
|
||||
sessionOptions: { model: 'auto' },
|
||||
sessionOptions: undefined,
|
||||
platform: 'linux',
|
||||
isRemote: true
|
||||
})
|
||||
|
|
@ -536,7 +536,7 @@ describe('launchWorkItemDirect', () => {
|
|||
cmdOverrides: {},
|
||||
agentArgs: '--yolo',
|
||||
agentEnv: {},
|
||||
sessionOptions: { model: 'auto' },
|
||||
sessionOptions: undefined,
|
||||
platform: 'linux',
|
||||
isRemote: true,
|
||||
allowEmptyPromptLaunch: true
|
||||
|
|
|
|||
|
|
@ -96,8 +96,7 @@ describe('activateAndRevealWorktree created agent reopen', () => {
|
|||
expect(result).toEqual({ primaryTabId: reopenedTab?.id })
|
||||
expect(reopenedTab).toBeDefined()
|
||||
expect(state.pendingStartupByTabId[reopenedTab!.id]).toEqual({
|
||||
command:
|
||||
"codex '-m' 'gpt-5.6-sol' '-c' 'model_reasoning_effort=medium' '--dangerously-bypass-approvals-and-sandbox'",
|
||||
command: "codex '--dangerously-bypass-approvals-and-sandbox'",
|
||||
env: {},
|
||||
launchAgent: 'codex',
|
||||
launchConfig: {
|
||||
|
|
@ -106,7 +105,7 @@ describe('activateAndRevealWorktree created agent reopen', () => {
|
|||
agentEnv: {}
|
||||
},
|
||||
launchToken: expect.any(String),
|
||||
sessionOptions: { model: 'gpt-5.6-sol', effort: 'medium' },
|
||||
sessionOptions: undefined,
|
||||
telemetry: {
|
||||
agent_kind: 'codex',
|
||||
launch_source: 'sidebar',
|
||||
|
|
|
|||
|
|
@ -58,8 +58,7 @@ describe('repo slice skipped-onboarding folder startup', () => {
|
|||
{
|
||||
sidebarRevealBehavior: 'auto',
|
||||
startup: {
|
||||
command:
|
||||
"codex '-m' 'gpt-5.6-sol' '-c' 'model_reasoning_effort=medium' '--dangerously-bypass-approvals-and-sandbox'",
|
||||
command: "codex '--dangerously-bypass-approvals-and-sandbox'",
|
||||
env: {},
|
||||
launchAgent: 'codex',
|
||||
launchConfig: {
|
||||
|
|
@ -67,7 +66,7 @@ describe('repo slice skipped-onboarding folder startup', () => {
|
|||
agentArgs: '--dangerously-bypass-approvals-and-sandbox',
|
||||
agentEnv: {}
|
||||
},
|
||||
sessionOptions: { model: 'gpt-5.6-sol', effort: 'medium' },
|
||||
sessionOptions: undefined,
|
||||
telemetry: {
|
||||
agent_kind: 'codex',
|
||||
launch_source: 'onboarding',
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ describe('agent session option catalog', () => {
|
|||
})
|
||||
})
|
||||
|
||||
it('restores defaults per model without leaking values across models', () => {
|
||||
it('resolves only stored values without leaking values across models', () => {
|
||||
let persisted = updateNativeChatSessionOptionDefaults({
|
||||
persisted: undefined,
|
||||
agent: 'claude',
|
||||
|
|
@ -96,17 +96,56 @@ describe('agent session option catalog', () => {
|
|||
})
|
||||
|
||||
expect(resolveNativeChatSessionOptionDefaults(persisted, 'claude')).toEqual({
|
||||
model: 'sonnet',
|
||||
effort: 'high'
|
||||
model: 'sonnet'
|
||||
})
|
||||
expect(persisted.claude?.valuesByModel?.opus).toEqual({ effort: 'xhigh' })
|
||||
})
|
||||
|
||||
it('seeds a fresh launch from the catalog defaults', () => {
|
||||
expect(resolveNativeChatSessionOptionDefaults(undefined, 'claude')).toEqual({
|
||||
model: 'sonnet',
|
||||
effort: 'high'
|
||||
})
|
||||
it('spawns vanilla when the user has not explicitly selected a model', () => {
|
||||
// Regression (#9085): a fresh launch must not force the catalog default
|
||||
// model/effort — the agent must spawn exactly as its own CLI would.
|
||||
expect(resolveNativeChatSessionOptionDefaults(undefined, 'claude')).toBeUndefined()
|
||||
expect(resolveNativeChatSessionOptionDefaults({}, 'claude')).toBeUndefined()
|
||||
expect(resolveNativeChatSessionOptionDefaults({}, 'future-agent')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('resolves an explicitly selected model and only its stored options', () => {
|
||||
let persisted = updateNativeChatSessionOptionDefaults({
|
||||
persisted: undefined,
|
||||
agent: 'claude',
|
||||
modelId: 'opus',
|
||||
optionId: 'model',
|
||||
value: 'opus'
|
||||
})
|
||||
expect(resolveNativeChatSessionOptionDefaults(persisted, 'claude')).toEqual({
|
||||
model: 'opus'
|
||||
})
|
||||
persisted = updateNativeChatSessionOptionDefaults({
|
||||
persisted,
|
||||
agent: 'claude',
|
||||
modelId: 'opus',
|
||||
optionId: 'effort',
|
||||
value: 'xhigh'
|
||||
})
|
||||
expect(resolveNativeChatSessionOptionDefaults(persisted, 'claude')).toEqual({
|
||||
model: 'opus',
|
||||
effort: 'xhigh'
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps catalog option defaults after the user explicitly selects a model', () => {
|
||||
const persisted = updateNativeChatSessionOptionDefaults({
|
||||
persisted: undefined,
|
||||
agent: 'claude',
|
||||
modelId: 'sonnet',
|
||||
optionId: 'model',
|
||||
value: 'sonnet'
|
||||
})
|
||||
const defaults = resolveNativeChatSessionOptionDefaults(persisted, 'claude')
|
||||
|
||||
expect(resolveAgentSessionOptionLaunch('claude', defaults)).toEqual({
|
||||
args: ['--model', 'sonnet', '--effort', 'high'],
|
||||
appliedValues: { model: 'sonnet', effort: 'high' }
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,10 +1,5 @@
|
|||
import type { AgentType } from './agent-status-types'
|
||||
import {
|
||||
catalogDefaultModel,
|
||||
findCatalogModel,
|
||||
getAgentSessionOptionCatalog,
|
||||
sessionOptionValueIsValid
|
||||
} from './agent-session-option-catalog'
|
||||
import { sessionOptionValueIsValid } from './agent-session-option-catalog'
|
||||
import type {
|
||||
PersistedNativeChatSessionOptions,
|
||||
SessionOptionValue
|
||||
|
|
@ -14,16 +9,10 @@ export function resolveNativeChatSessionOptionDefaults(
|
|||
persisted: PersistedNativeChatSessionOptions | null | undefined,
|
||||
agent: AgentType
|
||||
): Record<string, SessionOptionValue> | undefined {
|
||||
const catalog = getAgentSessionOptionCatalog(agent)
|
||||
const entry = persisted?.[agent]
|
||||
// Why: fresh launches must be authoritative too, so resolve the catalog's
|
||||
// declared fallback before the launch command is assembled.
|
||||
const modelId =
|
||||
typeof entry?.model === 'string' && entry.model.trim()
|
||||
? entry.model
|
||||
: catalog
|
||||
? catalogDefaultModel(catalog)?.id
|
||||
: undefined
|
||||
// Why: untouched settings must preserve the agent CLI's configured defaults;
|
||||
// only a model explicitly selected by the user authorizes launch flags.
|
||||
const modelId = typeof entry?.model === 'string' && entry.model.trim() ? entry.model : undefined
|
||||
if (!modelId) {
|
||||
return undefined
|
||||
}
|
||||
|
|
@ -36,11 +25,6 @@ export function resolveNativeChatSessionOptionDefaults(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
const model = catalog ? findCatalogModel(catalog, modelId) : undefined
|
||||
for (const option of model?.options ?? []) {
|
||||
values[option.id] ??= option.kind.defaultValue
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue