diff --git a/assets/oh-my-opencode.schema.json b/assets/oh-my-opencode.schema.json index 30757523b..df796fb78 100644 --- a/assets/oh-my-opencode.schema.json +++ b/assets/oh-my-opencode.schema.json @@ -3152,7 +3152,7 @@ "type": "object", "propertyNames": { "type": "string", - "pattern": "^(?!(?:build|plan|sisyphus|hephaestus|sisyphus-junior|OpenCode-Builder|prometheus|metis|momus|oracle|librarian|explore|multimodal-looker|atlas)$).+" + "pattern": "^(?!(?:[bB][uU][iI][lL][dD]|[pP][lL][aA][nN]|[sS][iI][sS][yY][pP][hH][uU][sS]|[hH][eE][pP][hH][aA][eE][sS][tT][uU][sS]|[sS][iI][sS][yY][pP][hH][uU][sS]-[jJ][uU][nN][iI][oO][rR]|[oO][pP][eE][nN][cC][oO][dD][eE]-[bB][uU][iI][lL][dD][eE][rR]|[pP][rR][oO][mM][eE][tT][hH][eE][uU][sS]|[mM][eE][tT][iI][sS]|[mM][oO][mM][uU][sS]|[oO][rR][aA][cC][lL][eE]|[lL][iI][bB][rR][aA][rR][iI][aA][nN]|[eE][xX][pP][lL][oO][rR][eE]|[mM][uU][lL][tT][iI][mM][oO][dD][aA][lL]-[lL][oO][oO][kK][eE][rR]|[aA][tT][lL][aA][sS])$).+" }, "additionalProperties": { "type": "object", diff --git a/src/config/schema-document.test.ts b/src/config/schema-document.test.ts index 12cc09b87..bc6863f91 100644 --- a/src/config/schema-document.test.ts +++ b/src/config/schema-document.test.ts @@ -23,6 +23,8 @@ describe("schema document generation", () => { expect(agentsSchema?.additionalProperties).toBeFalse() expect(customAgentsSchema).toBeDefined() expect(customPropertyNames?.pattern).toBeDefined() + expect(customPropertyNames?.pattern).toContain("[bB][uU][iI][lL][dD]") + expect(customPropertyNames?.pattern).toContain("[pP][lL][aA][nN]") expect(customAdditionalProperties).toBeDefined() expect(customAgentProperties?.model).toEqual({ type: "string" }) expect(customAgentProperties?.temperature).toEqual( diff --git a/src/config/schema/agent-overrides.ts b/src/config/schema/agent-overrides.ts index eb5429fba..bc40a7313 100644 --- a/src/config/schema/agent-overrides.ts +++ b/src/config/schema/agent-overrides.ts @@ -81,8 +81,27 @@ const RESERVED_CUSTOM_AGENT_NAMES = OverridableAgentNameSchema.options const RESERVED_CUSTOM_AGENT_NAME_SET = new Set( RESERVED_CUSTOM_AGENT_NAMES.map((name) => name.toLowerCase()), ) +function escapeRegexLiteral(value: string): string { + return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") +} + +function toCaseInsensitiveLiteralPattern(value: string): string { + return value + .split("") + .map((char) => { + if (/^[A-Za-z]$/.test(char)) { + const lower = char.toLowerCase() + const upper = char.toUpperCase() + return `[${lower}${upper}]` + } + + return escapeRegexLiteral(char) + }) + .join("") +} + const RESERVED_CUSTOM_AGENT_NAME_PATTERN = new RegExp( - `^(?!(?:${RESERVED_CUSTOM_AGENT_NAMES.map((name) => name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})$).+`, + `^(?!(?:${RESERVED_CUSTOM_AGENT_NAMES.map(toCaseInsensitiveLiteralPattern).join("|")})$).+`, ) export const CustomAgentOverridesSchema = z diff --git a/src/plugin-handlers/agent-config-handler.ts b/src/plugin-handlers/agent-config-handler.ts index 7d8893be8..4e61b5c15 100644 --- a/src/plugin-handlers/agent-config-handler.ts +++ b/src/plugin-handlers/agent-config-handler.ts @@ -82,6 +82,15 @@ export async function applyAgentConfig(params: { const browserProvider = params.pluginConfig.browser_automation_engine?.provider ?? "playwright"; const currentModel = params.config.model as string | undefined; + const disabledAgentNames = new Set( + (migratedDisabledAgents ?? []).map((agent) => agent.toLowerCase()), + ); + const filterDisabledAgents = (agents: Record) => + Object.fromEntries( + Object.entries(agents).filter( + ([name]) => !disabledAgentNames.has(name.toLowerCase()), + ), + ); const disabledSkills = new Set(params.pluginConfig.disabled_skills ?? []); const useTaskSystem = params.pluginConfig.experimental?.task_system ?? false; const disableOmoEnv = params.pluginConfig.experimental?.disable_omo_env ?? false; @@ -99,19 +108,25 @@ export async function applyAgentConfig(params: { ); const configAgent = params.config.agent as AgentConfigRecord | undefined; + const filteredUserAgents = filterDisabledAgents(userAgents as Record); + const filteredProjectAgents = filterDisabledAgents(projectAgents as Record); + const filteredPluginAgents = filterDisabledAgents(pluginAgents as Record); + const filteredConfigAgentsForSummary = filterDisabledAgents( + (configAgent as Record | undefined) ?? {}, + ); const mergedCategories = mergeCategories(params.pluginConfig.categories) const knownCustomAgentNames = collectKnownCustomAgentNames( - userAgents as Record, - projectAgents as Record, - pluginAgents as Record, - configAgent as Record | undefined, + filteredUserAgents, + filteredProjectAgents, + filteredPluginAgents, + filteredConfigAgentsForSummary, ) const customAgentSummaries = mergeCustomAgentSummaries( - collectCustomAgentSummariesFromRecord(userAgents as Record), - collectCustomAgentSummariesFromRecord(projectAgents as Record), - collectCustomAgentSummariesFromRecord(pluginAgents as Record), - collectCustomAgentSummariesFromRecord(configAgent as Record | undefined), + collectCustomAgentSummariesFromRecord(filteredUserAgents), + collectCustomAgentSummariesFromRecord(filteredProjectAgents), + collectCustomAgentSummariesFromRecord(filteredPluginAgents), + collectCustomAgentSummariesFromRecord(filteredConfigAgentsForSummary), filterSummariesByKnownNames( collectCustomAgentSummariesFromRecord( params.pluginConfig.custom_agents as Record | undefined, @@ -135,14 +150,6 @@ export async function applyAgentConfig(params: { useTaskSystem, disableOmoEnv, ); - const disabledAgentNames = new Set( - (migratedDisabledAgents ?? []).map(a => a.toLowerCase()) - ); - - const filterDisabledAgents = (agents: Record) => - Object.fromEntries( - Object.entries(agents).filter(([name]) => !disabledAgentNames.has(name.toLowerCase())) - ); const isSisyphusEnabled = params.pluginConfig.sisyphus_agent?.disabled !== true; const builderEnabled = params.pluginConfig.sisyphus_agent?.default_builder_enabled ?? false; @@ -230,9 +237,9 @@ export async function applyAgentConfig(params: { ...Object.fromEntries( Object.entries(builtinAgents).filter(([key]) => key !== "sisyphus"), ), - ...filterDisabledAgents(userAgents), - ...filterDisabledAgents(projectAgents), - ...filterDisabledAgents(pluginAgents), + ...filteredUserAgents, + ...filteredProjectAgents, + ...filteredPluginAgents, ...filteredConfigAgents, build: { ...migratedBuild, mode: "subagent", hidden: true }, ...(planDemoteConfig ? { plan: planDemoteConfig } : {}), @@ -240,9 +247,9 @@ export async function applyAgentConfig(params: { } else { params.config.agent = { ...builtinAgents, - ...filterDisabledAgents(userAgents), - ...filterDisabledAgents(projectAgents), - ...filterDisabledAgents(pluginAgents), + ...filteredUserAgents, + ...filteredProjectAgents, + ...filteredPluginAgents, ...configAgent, }; } diff --git a/src/plugin-handlers/config-handler.test.ts b/src/plugin-handlers/config-handler.test.ts index 6896898c2..3dbe54f4b 100644 --- a/src/plugin-handlers/config-handler.test.ts +++ b/src/plugin-handlers/config-handler.test.ts @@ -352,6 +352,94 @@ describe("custom agent overrides", () => { expect(agentsConfig[pKey].prompt).not.toContain("ghostwriter") }) + test("prometheus prompt excludes disabled custom agents from catalog", async () => { + // #given + ;(agentLoader.loadUserAgents as any).mockReturnValue({ + translator: { + name: "translator", + mode: "subagent", + description: "Translate and localize locale files", + prompt: "Translate content", + }, + }) + + const pluginConfig: OhMyOpenCodeConfig = { + disabled_agents: ["translator"], + sisyphus_agent: { + planner_enabled: true, + }, + } + const config: Record = { + model: "anthropic/claude-opus-4-6", + agent: {}, + } + + const handler = createConfigHandler({ + ctx: { directory: "/tmp" }, + pluginConfig, + modelCacheState: { + anthropicContext1MEnabled: false, + modelContextLimitsCache: new Map(), + }, + }) + + // #when + await handler(config) + + // #then + const agentsConfig = config.agent as Record + const pKey = getAgentDisplayName("prometheus") + expect(agentsConfig[pKey]).toBeDefined() + expect(agentsConfig[pKey].prompt).not.toContain("translator") + }) + + test("prometheus custom prompt override still includes custom agent catalog", async () => { + // #given + ;(agentLoader.loadUserAgents as any).mockReturnValue({ + translator: { + name: "translator", + mode: "subagent", + description: "Translate and localize locale files", + prompt: "Translate content", + }, + }) + + const pluginConfig: OhMyOpenCodeConfig = { + agents: { + prometheus: { + prompt: "Custom planner prompt", + }, + }, + sisyphus_agent: { + planner_enabled: true, + }, + } + const config: Record = { + model: "anthropic/claude-opus-4-6", + agent: {}, + } + + const handler = createConfigHandler({ + ctx: { directory: "/tmp" }, + pluginConfig, + modelCacheState: { + anthropicContext1MEnabled: false, + modelContextLimitsCache: new Map(), + }, + }) + + // #when + await handler(config) + + // #then + const agentsConfig = config.agent as Record + const pKey = getAgentDisplayName("prometheus") + expect(agentsConfig[pKey]).toBeDefined() + expect(agentsConfig[pKey].prompt).toContain("Custom planner prompt") + expect(agentsConfig[pKey].prompt).toContain("") + expect(agentsConfig[pKey].prompt).toContain("translator") + }) + test("custom agent summary merge preserves flags when custom_agents adds description", async () => { // #given ;(agentLoader.loadUserAgents as any).mockReturnValue({ diff --git a/src/plugin-handlers/prometheus-agent-config-builder.ts b/src/plugin-handlers/prometheus-agent-config-builder.ts index 8bd674b7e..a2d63c84a 100644 --- a/src/plugin-handlers/prometheus-agent-config-builder.ts +++ b/src/plugin-handlers/prometheus-agent-config-builder.ts @@ -103,5 +103,12 @@ export async function buildPrometheusAgentConfig(params: { if (prompt_append && typeof merged.prompt === "string") { merged.prompt = merged.prompt + "\n" + resolvePromptAppend(prompt_append); } + if ( + customAgentBlock + && typeof merged.prompt === "string" + && !merged.prompt.includes("") + ) { + merged.prompt = merged.prompt + customAgentBlock; + } return merged; }