From 7ebd48c939f51f45ef17a2100a032aa9ad764a0e Mon Sep 17 00:00:00 2001 From: Ivan Marshall Widjaja Date: Sat, 10 Jan 2026 21:02:21 +1100 Subject: [PATCH 1/3] fix(config): resolve category to model for Prometheus (Planner) agent When Prometheus (Planner) was configured with only a category (e.g., "ultrabrain") and no explicit model, the category was ignored and the agent fell back to the hardcoded default "anthropic/claude-opus-4-5". Add resolveModelFromCategoryWithUserOverride() helper that checks user categories first, then DEFAULT_CATEGORIES, to resolve category names to their corresponding models. Apply this resolution when building the Prometheus agent configuration. Co-Authored-By: Sisyphus --- src/plugin-handlers/config-handler.test.ts | 79 ++++++++++++++++++++++ src/plugin-handlers/config-handler.ts | 29 +++++++- 2 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 src/plugin-handlers/config-handler.test.ts diff --git a/src/plugin-handlers/config-handler.test.ts b/src/plugin-handlers/config-handler.test.ts new file mode 100644 index 0000000000..d47f5e9275 --- /dev/null +++ b/src/plugin-handlers/config-handler.test.ts @@ -0,0 +1,79 @@ +import { describe, test, expect } from "bun:test" +import { DEFAULT_CATEGORIES } from "../tools/sisyphus-task/constants" +import type { CategoryConfig } from "../config/schema" + +function resolveModelFromCategoryWithUserOverride( + categoryName: string, + userCategories?: Record +): string | undefined { + const categoryConfig = userCategories?.[categoryName] ?? DEFAULT_CATEGORIES[categoryName] + return categoryConfig?.model +} + +describe("Prometheus category model resolution", () => { + test("resolves ultrabrain category to openai/gpt-5.2", () => { + // #given + const categoryName = "ultrabrain" + + // #when + const model = resolveModelFromCategoryWithUserOverride(categoryName) + + // #then + expect(model).toBe("openai/gpt-5.2") + }) + + test("resolves visual-engineering category to gemini model", () => { + // #given + const categoryName = "visual-engineering" + + // #when + const model = resolveModelFromCategoryWithUserOverride(categoryName) + + // #then + expect(model).toBe("google/gemini-3-pro-preview") + }) + + test("user categories override default categories", () => { + // #given + const categoryName = "ultrabrain" + const userCategories: Record = { + ultrabrain: { + model: "google/antigravity-claude-opus-4-5-thinking", + temperature: 0.1, + }, + } + + // #when + const model = resolveModelFromCategoryWithUserOverride(categoryName, userCategories) + + // #then + expect(model).toBe("google/antigravity-claude-opus-4-5-thinking") + }) + + test("returns undefined for unknown category", () => { + // #given + const categoryName = "nonexistent-category" + + // #when + const model = resolveModelFromCategoryWithUserOverride(categoryName) + + // #then + expect(model).toBeUndefined() + }) + + test("falls back to default when user category has no entry", () => { + // #given + const categoryName = "ultrabrain" + const userCategories: Record = { + "visual-engineering": { + model: "custom/visual-model", + }, + } + + // #when + const model = resolveModelFromCategoryWithUserOverride(categoryName, userCategories) + + // #then + expect(model).toBe("openai/gpt-5.2") + }) +}) diff --git a/src/plugin-handlers/config-handler.ts b/src/plugin-handlers/config-handler.ts index c29efa7276..77cc19aaf3 100644 --- a/src/plugin-handlers/config-handler.ts +++ b/src/plugin-handlers/config-handler.ts @@ -24,7 +24,9 @@ import type { OhMyOpenCodeConfig } from "../config"; import { log } from "../shared"; import { migrateAgentConfig } from "../shared/permission-compat"; import { PROMETHEUS_SYSTEM_PROMPT, PROMETHEUS_PERMISSION } from "../agents/prometheus-prompt"; +import { DEFAULT_CATEGORIES } from "../tools/sisyphus-task/constants"; import type { ModelCacheState } from "../plugin-state"; +import type { CategoryConfig } from "../config/schema"; export interface ConfigHandlerDeps { ctx: { directory: string }; @@ -32,6 +34,14 @@ export interface ConfigHandlerDeps { modelCacheState: ModelCacheState; } +function resolveModelFromCategoryWithUserOverride( + categoryName: string, + userCategories?: Record +): string | undefined { + const categoryConfig = userCategories?.[categoryName] ?? DEFAULT_CATEGORIES[categoryName]; + return categoryConfig?.model; +} + export function createConfigHandler(deps: ConfigHandlerDeps) { const { ctx, pluginConfig, modelCacheState } = deps; @@ -173,10 +183,25 @@ export function createConfigHandler(deps: ConfigHandlerDeps) { planConfigWithoutName as Record ); const prometheusOverride = - pluginConfig.agents?.["Prometheus (Planner)"]; + pluginConfig.agents?.["Prometheus (Planner)"] as + | (Record & { category?: string; model?: string }) + | undefined; const defaultModel = config.model as string | undefined; + + const resolvedModelFromCategory = + prometheusOverride?.category && !prometheusOverride?.model + ? resolveModelFromCategoryWithUserOverride( + prometheusOverride.category, + pluginConfig.categories + ) + : undefined; + const prometheusBase = { - model: defaultModel ?? "anthropic/claude-opus-4-5", + model: + prometheusOverride?.model ?? + resolvedModelFromCategory ?? + defaultModel ?? + "anthropic/claude-opus-4-5", mode: "primary" as const, prompt: PROMETHEUS_SYSTEM_PROMPT, permission: PROMETHEUS_PERMISSION, From d6c3a2e5afa90f46fd3b58793522fb5a964671d1 Mon Sep 17 00:00:00 2001 From: Ivan Marshall Widjaja Date: Sat, 10 Jan 2026 21:22:23 +1100 Subject: [PATCH 2/3] fix(test): use actual implementation instead of local duplicate Co-Authored-By: Sisyphus --- src/plugin-handlers/config-handler.test.ts | 10 +--------- src/plugin-handlers/config-handler.ts | 2 +- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/plugin-handlers/config-handler.test.ts b/src/plugin-handlers/config-handler.test.ts index d47f5e9275..21cab843bd 100644 --- a/src/plugin-handlers/config-handler.test.ts +++ b/src/plugin-handlers/config-handler.test.ts @@ -1,15 +1,7 @@ import { describe, test, expect } from "bun:test" -import { DEFAULT_CATEGORIES } from "../tools/sisyphus-task/constants" +import { resolveModelFromCategoryWithUserOverride } from "./config-handler" import type { CategoryConfig } from "../config/schema" -function resolveModelFromCategoryWithUserOverride( - categoryName: string, - userCategories?: Record -): string | undefined { - const categoryConfig = userCategories?.[categoryName] ?? DEFAULT_CATEGORIES[categoryName] - return categoryConfig?.model -} - describe("Prometheus category model resolution", () => { test("resolves ultrabrain category to openai/gpt-5.2", () => { // #given diff --git a/src/plugin-handlers/config-handler.ts b/src/plugin-handlers/config-handler.ts index 77cc19aaf3..60f6f5415d 100644 --- a/src/plugin-handlers/config-handler.ts +++ b/src/plugin-handlers/config-handler.ts @@ -34,7 +34,7 @@ export interface ConfigHandlerDeps { modelCacheState: ModelCacheState; } -function resolveModelFromCategoryWithUserOverride( +export function resolveModelFromCategoryWithUserOverride( categoryName: string, userCategories?: Record ): string | undefined { From 903c18955daf388a35d303cee9c4c92da216b325 Mon Sep 17 00:00:00 2001 From: Ivan Marshall Widjaja Date: Sat, 10 Jan 2026 22:06:38 +1100 Subject: [PATCH 3/3] fix(config): apply all category properties, not just model for Prometheus (Planner) The resolveModelFromCategoryWithUserOverride() helper only extracted the model field from CategoryConfig, ignoring critical properties like temperature, top_p, tools, maxTokens, thinking, reasoningEffort, and textVerbosity. This caused categories like "ultrabrain" (temperature: 0.1) to run with incorrect default temperatures. Refactor resolveModelFromCategoryWithUserOverride() to resolveCategoryConfig() that returns the full CategoryConfig. Update Prometheus (Planner) configuration to apply all category properties (temperature, top_p, tools, etc.) when a category is specified, matching the pattern established in Sisyphus-Junior. Explicit overrides still take precedence during merge. Co-Authored-By: Sisyphus --- src/plugin-handlers/config-handler.test.ts | 61 +++++++++++++++++----- src/plugin-handlers/config-handler.ts | 43 ++++++++++----- 2 files changed, 78 insertions(+), 26 deletions(-) diff --git a/src/plugin-handlers/config-handler.test.ts b/src/plugin-handlers/config-handler.test.ts index 21cab843bd..9724965fe3 100644 --- a/src/plugin-handlers/config-handler.test.ts +++ b/src/plugin-handlers/config-handler.test.ts @@ -1,28 +1,32 @@ import { describe, test, expect } from "bun:test" -import { resolveModelFromCategoryWithUserOverride } from "./config-handler" +import { resolveCategoryConfig } from "./config-handler" import type { CategoryConfig } from "../config/schema" -describe("Prometheus category model resolution", () => { - test("resolves ultrabrain category to openai/gpt-5.2", () => { +describe("Prometheus category config resolution", () => { + test("resolves ultrabrain category config", () => { // #given const categoryName = "ultrabrain" // #when - const model = resolveModelFromCategoryWithUserOverride(categoryName) + const config = resolveCategoryConfig(categoryName) // #then - expect(model).toBe("openai/gpt-5.2") + expect(config).toBeDefined() + expect(config?.model).toBe("openai/gpt-5.2") + expect(config?.temperature).toBe(0.1) }) - test("resolves visual-engineering category to gemini model", () => { + test("resolves visual-engineering category config", () => { // #given const categoryName = "visual-engineering" // #when - const model = resolveModelFromCategoryWithUserOverride(categoryName) + const config = resolveCategoryConfig(categoryName) // #then - expect(model).toBe("google/gemini-3-pro-preview") + expect(config).toBeDefined() + expect(config?.model).toBe("google/gemini-3-pro-preview") + expect(config?.temperature).toBe(0.7) }) test("user categories override default categories", () => { @@ -36,10 +40,12 @@ describe("Prometheus category model resolution", () => { } // #when - const model = resolveModelFromCategoryWithUserOverride(categoryName, userCategories) + const config = resolveCategoryConfig(categoryName, userCategories) // #then - expect(model).toBe("google/antigravity-claude-opus-4-5-thinking") + expect(config).toBeDefined() + expect(config?.model).toBe("google/antigravity-claude-opus-4-5-thinking") + expect(config?.temperature).toBe(0.1) }) test("returns undefined for unknown category", () => { @@ -47,10 +53,10 @@ describe("Prometheus category model resolution", () => { const categoryName = "nonexistent-category" // #when - const model = resolveModelFromCategoryWithUserOverride(categoryName) + const config = resolveCategoryConfig(categoryName) // #then - expect(model).toBeUndefined() + expect(config).toBeUndefined() }) test("falls back to default when user category has no entry", () => { @@ -63,9 +69,36 @@ describe("Prometheus category model resolution", () => { } // #when - const model = resolveModelFromCategoryWithUserOverride(categoryName, userCategories) + const config = resolveCategoryConfig(categoryName, userCategories) // #then - expect(model).toBe("openai/gpt-5.2") + expect(config).toBeDefined() + expect(config?.model).toBe("openai/gpt-5.2") + expect(config?.temperature).toBe(0.1) + }) + + test("preserves all category properties (temperature, top_p, tools, etc.)", () => { + // #given + const categoryName = "custom-category" + const userCategories: Record = { + "custom-category": { + model: "test/model", + temperature: 0.5, + top_p: 0.9, + maxTokens: 32000, + tools: { tool1: true, tool2: false }, + }, + } + + // #when + const config = resolveCategoryConfig(categoryName, userCategories) + + // #then + expect(config).toBeDefined() + expect(config?.model).toBe("test/model") + expect(config?.temperature).toBe(0.5) + expect(config?.top_p).toBe(0.9) + expect(config?.maxTokens).toBe(32000) + expect(config?.tools).toEqual({ tool1: true, tool2: false }) }) }) diff --git a/src/plugin-handlers/config-handler.ts b/src/plugin-handlers/config-handler.ts index 60f6f5415d..b16f8fb359 100644 --- a/src/plugin-handlers/config-handler.ts +++ b/src/plugin-handlers/config-handler.ts @@ -34,12 +34,11 @@ export interface ConfigHandlerDeps { modelCacheState: ModelCacheState; } -export function resolveModelFromCategoryWithUserOverride( +export function resolveCategoryConfig( categoryName: string, userCategories?: Record -): string | undefined { - const categoryConfig = userCategories?.[categoryName] ?? DEFAULT_CATEGORIES[categoryName]; - return categoryConfig?.model; +): CategoryConfig | undefined { + return userCategories?.[categoryName] ?? DEFAULT_CATEGORIES[categoryName]; } export function createConfigHandler(deps: ConfigHandlerDeps) { @@ -188,18 +187,20 @@ export function createConfigHandler(deps: ConfigHandlerDeps) { | undefined; const defaultModel = config.model as string | undefined; - const resolvedModelFromCategory = - prometheusOverride?.category && !prometheusOverride?.model - ? resolveModelFromCategoryWithUserOverride( - prometheusOverride.category, - pluginConfig.categories - ) - : undefined; + // Resolve full category config (model, temperature, top_p, tools, etc.) + // Apply all category properties when category is specified, but explicit + // overrides (model, temperature, etc.) will take precedence during merge + const categoryConfig = prometheusOverride?.category + ? resolveCategoryConfig( + prometheusOverride.category, + pluginConfig.categories + ) + : undefined; const prometheusBase = { model: prometheusOverride?.model ?? - resolvedModelFromCategory ?? + categoryConfig?.model ?? defaultModel ?? "anthropic/claude-opus-4-5", mode: "primary" as const, @@ -207,6 +208,24 @@ export function createConfigHandler(deps: ConfigHandlerDeps) { permission: PROMETHEUS_PERMISSION, description: `${configAgent?.plan?.description ?? "Plan agent"} (Prometheus - OhMyOpenCode)`, color: (configAgent?.plan?.color as string) ?? "#FF6347", + // Apply category properties (temperature, top_p, tools, etc.) + ...(categoryConfig?.temperature !== undefined + ? { temperature: categoryConfig.temperature } + : {}), + ...(categoryConfig?.top_p !== undefined + ? { top_p: categoryConfig.top_p } + : {}), + ...(categoryConfig?.maxTokens !== undefined + ? { maxTokens: categoryConfig.maxTokens } + : {}), + ...(categoryConfig?.tools ? { tools: categoryConfig.tools } : {}), + ...(categoryConfig?.thinking ? { thinking: categoryConfig.thinking } : {}), + ...(categoryConfig?.reasoningEffort !== undefined + ? { reasoningEffort: categoryConfig.reasoningEffort } + : {}), + ...(categoryConfig?.textVerbosity !== undefined + ? { textVerbosity: categoryConfig.textVerbosity } + : {}), }; agentConfig["Prometheus (Planner)"] = prometheusOverride