fix(delegate-task): category default model takes precedence over parent model

Previously, parent model string would override category default model,
causing categories like 'ultrabrain' to use the parent's model (e.g., sonnet)
instead of the intended category default (e.g., gpt-5.2).

Model priority is now:
1. userConfig.model (oh-my-opencode.json override)
2. defaultConfig.model (category default)
3. parentModelString (fallback)
4. systemDefaultModel (last resort)
This commit is contained in:
justsisyphus 2026-01-16 18:45:19 +09:00
parent eeb7eb2be2
commit cb6f1c9f75
2 changed files with 24 additions and 11 deletions

View File

@ -19,7 +19,7 @@ function resolveCategoryConfig(
return null
}
const model = userConfig?.model ?? parentModelString ?? defaultConfig?.model ?? systemDefaultModel
const model = userConfig?.model ?? defaultConfig?.model ?? parentModelString ?? systemDefaultModel
const config: CategoryConfig = {
...defaultConfig,
...userConfig,
@ -212,15 +212,29 @@ describe("sisyphus-task", () => {
expect(result!.config.temperature).toBe(0.3)
})
test("parentModelString is used when no user model and takes precedence over default", () => {
// #given
test("category default model takes precedence over parentModelString", () => {
// #given - builtin category has default model, parent model should NOT override it
const categoryName = "visual-engineering"
const parentModelString = "cliproxy/claude-opus-4-5"
// #when
const result = resolveCategoryConfig(categoryName, { parentModelString })
// #then
// #then - category default model wins, parent model is ignored for builtin categories
expect(result).not.toBeNull()
expect(result!.config.model).toBe("google/gemini-3-pro-preview")
})
test("parentModelString is used as fallback when category has no default model", () => {
// #given - custom category with no model defined, only parentModelString as fallback
const categoryName = "my-custom-no-model"
const userCategories = { "my-custom-no-model": { temperature: 0.5 } } as unknown as Record<string, CategoryConfig>
const parentModelString = "cliproxy/claude-opus-4-5"
// #when
const result = resolveCategoryConfig(categoryName, { userCategories, parentModelString })
// #then - parent model is used as fallback since custom category has no default
expect(result).not.toBeNull()
expect(result!.config.model).toBe("cliproxy/claude-opus-4-5")
})
@ -888,18 +902,18 @@ describe("sisyphus-task", () => {
expect(actualModel).toBe("openai/gpt-5.2")
})
test("when parentModelString is used - modelInfo should report inherited", () => {
// #given
test("category default model takes precedence over parentModelString for builtin category", () => {
// #given - builtin ultrabrain category has default model gpt-5.2
const categoryName = "ultrabrain"
const parentModelString = "cliproxy/claude-opus-4-5"
// #when
const resolved = resolveCategoryConfig(categoryName, { parentModelString })
// #then - actualModel should be parentModelString, type should be "inherited"
// #then - category default model wins, not the parent model
expect(resolved).not.toBeNull()
const actualModel = resolved!.config.model
expect(actualModel).toBe(parentModelString)
expect(actualModel).toBe("openai/gpt-5.2")
})
test("when user defines model - modelInfo should report user-defined regardless of parentModelString", () => {

View File

@ -124,9 +124,8 @@ function resolveCategoryConfig(
return null
}
// Model priority: user override > parent model (inherit) > category default > system default
// Parent model takes precedence over category default so custom providers work out-of-box
const model = userConfig?.model ?? parentModelString ?? defaultConfig?.model ?? systemDefaultModel
// Model priority: user override > category default > parent model (fallback) > system default
const model = userConfig?.model ?? defaultConfig?.model ?? parentModelString ?? systemDefaultModel
const config: CategoryConfig = {
...defaultConfig,
...userConfig,