fix(custom-agents): align planner catalog and schema validation
This commit is contained in:
parent
922ff7f2bc
commit
a5749a1392
@ -3152,7 +3152,7 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"propertyNames": {
|
"propertyNames": {
|
||||||
"type": "string",
|
"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": {
|
"additionalProperties": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|||||||
@ -23,6 +23,8 @@ describe("schema document generation", () => {
|
|||||||
expect(agentsSchema?.additionalProperties).toBeFalse()
|
expect(agentsSchema?.additionalProperties).toBeFalse()
|
||||||
expect(customAgentsSchema).toBeDefined()
|
expect(customAgentsSchema).toBeDefined()
|
||||||
expect(customPropertyNames?.pattern).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(customAdditionalProperties).toBeDefined()
|
||||||
expect(customAgentProperties?.model).toEqual({ type: "string" })
|
expect(customAgentProperties?.model).toEqual({ type: "string" })
|
||||||
expect(customAgentProperties?.temperature).toEqual(
|
expect(customAgentProperties?.temperature).toEqual(
|
||||||
|
|||||||
@ -81,8 +81,27 @@ const RESERVED_CUSTOM_AGENT_NAMES = OverridableAgentNameSchema.options
|
|||||||
const RESERVED_CUSTOM_AGENT_NAME_SET = new Set(
|
const RESERVED_CUSTOM_AGENT_NAME_SET = new Set(
|
||||||
RESERVED_CUSTOM_AGENT_NAMES.map((name) => name.toLowerCase()),
|
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(
|
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
|
export const CustomAgentOverridesSchema = z
|
||||||
|
|||||||
@ -82,6 +82,15 @@ export async function applyAgentConfig(params: {
|
|||||||
const browserProvider =
|
const browserProvider =
|
||||||
params.pluginConfig.browser_automation_engine?.provider ?? "playwright";
|
params.pluginConfig.browser_automation_engine?.provider ?? "playwright";
|
||||||
const currentModel = params.config.model as string | undefined;
|
const currentModel = params.config.model as string | undefined;
|
||||||
|
const disabledAgentNames = new Set(
|
||||||
|
(migratedDisabledAgents ?? []).map((agent) => agent.toLowerCase()),
|
||||||
|
);
|
||||||
|
const filterDisabledAgents = (agents: Record<string, unknown>) =>
|
||||||
|
Object.fromEntries(
|
||||||
|
Object.entries(agents).filter(
|
||||||
|
([name]) => !disabledAgentNames.has(name.toLowerCase()),
|
||||||
|
),
|
||||||
|
);
|
||||||
const disabledSkills = new Set<string>(params.pluginConfig.disabled_skills ?? []);
|
const disabledSkills = new Set<string>(params.pluginConfig.disabled_skills ?? []);
|
||||||
const useTaskSystem = params.pluginConfig.experimental?.task_system ?? false;
|
const useTaskSystem = params.pluginConfig.experimental?.task_system ?? false;
|
||||||
const disableOmoEnv = params.pluginConfig.experimental?.disable_omo_env ?? 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 configAgent = params.config.agent as AgentConfigRecord | undefined;
|
||||||
|
const filteredUserAgents = filterDisabledAgents(userAgents as Record<string, unknown>);
|
||||||
|
const filteredProjectAgents = filterDisabledAgents(projectAgents as Record<string, unknown>);
|
||||||
|
const filteredPluginAgents = filterDisabledAgents(pluginAgents as Record<string, unknown>);
|
||||||
|
const filteredConfigAgentsForSummary = filterDisabledAgents(
|
||||||
|
(configAgent as Record<string, unknown> | undefined) ?? {},
|
||||||
|
);
|
||||||
const mergedCategories = mergeCategories(params.pluginConfig.categories)
|
const mergedCategories = mergeCategories(params.pluginConfig.categories)
|
||||||
const knownCustomAgentNames = collectKnownCustomAgentNames(
|
const knownCustomAgentNames = collectKnownCustomAgentNames(
|
||||||
userAgents as Record<string, unknown>,
|
filteredUserAgents,
|
||||||
projectAgents as Record<string, unknown>,
|
filteredProjectAgents,
|
||||||
pluginAgents as Record<string, unknown>,
|
filteredPluginAgents,
|
||||||
configAgent as Record<string, unknown> | undefined,
|
filteredConfigAgentsForSummary,
|
||||||
)
|
)
|
||||||
|
|
||||||
const customAgentSummaries = mergeCustomAgentSummaries(
|
const customAgentSummaries = mergeCustomAgentSummaries(
|
||||||
collectCustomAgentSummariesFromRecord(userAgents as Record<string, unknown>),
|
collectCustomAgentSummariesFromRecord(filteredUserAgents),
|
||||||
collectCustomAgentSummariesFromRecord(projectAgents as Record<string, unknown>),
|
collectCustomAgentSummariesFromRecord(filteredProjectAgents),
|
||||||
collectCustomAgentSummariesFromRecord(pluginAgents as Record<string, unknown>),
|
collectCustomAgentSummariesFromRecord(filteredPluginAgents),
|
||||||
collectCustomAgentSummariesFromRecord(configAgent as Record<string, unknown> | undefined),
|
collectCustomAgentSummariesFromRecord(filteredConfigAgentsForSummary),
|
||||||
filterSummariesByKnownNames(
|
filterSummariesByKnownNames(
|
||||||
collectCustomAgentSummariesFromRecord(
|
collectCustomAgentSummariesFromRecord(
|
||||||
params.pluginConfig.custom_agents as Record<string, unknown> | undefined,
|
params.pluginConfig.custom_agents as Record<string, unknown> | undefined,
|
||||||
@ -135,14 +150,6 @@ export async function applyAgentConfig(params: {
|
|||||||
useTaskSystem,
|
useTaskSystem,
|
||||||
disableOmoEnv,
|
disableOmoEnv,
|
||||||
);
|
);
|
||||||
const disabledAgentNames = new Set(
|
|
||||||
(migratedDisabledAgents ?? []).map(a => a.toLowerCase())
|
|
||||||
);
|
|
||||||
|
|
||||||
const filterDisabledAgents = (agents: Record<string, unknown>) =>
|
|
||||||
Object.fromEntries(
|
|
||||||
Object.entries(agents).filter(([name]) => !disabledAgentNames.has(name.toLowerCase()))
|
|
||||||
);
|
|
||||||
const isSisyphusEnabled = params.pluginConfig.sisyphus_agent?.disabled !== true;
|
const isSisyphusEnabled = params.pluginConfig.sisyphus_agent?.disabled !== true;
|
||||||
const builderEnabled =
|
const builderEnabled =
|
||||||
params.pluginConfig.sisyphus_agent?.default_builder_enabled ?? false;
|
params.pluginConfig.sisyphus_agent?.default_builder_enabled ?? false;
|
||||||
@ -230,9 +237,9 @@ export async function applyAgentConfig(params: {
|
|||||||
...Object.fromEntries(
|
...Object.fromEntries(
|
||||||
Object.entries(builtinAgents).filter(([key]) => key !== "sisyphus"),
|
Object.entries(builtinAgents).filter(([key]) => key !== "sisyphus"),
|
||||||
),
|
),
|
||||||
...filterDisabledAgents(userAgents),
|
...filteredUserAgents,
|
||||||
...filterDisabledAgents(projectAgents),
|
...filteredProjectAgents,
|
||||||
...filterDisabledAgents(pluginAgents),
|
...filteredPluginAgents,
|
||||||
...filteredConfigAgents,
|
...filteredConfigAgents,
|
||||||
build: { ...migratedBuild, mode: "subagent", hidden: true },
|
build: { ...migratedBuild, mode: "subagent", hidden: true },
|
||||||
...(planDemoteConfig ? { plan: planDemoteConfig } : {}),
|
...(planDemoteConfig ? { plan: planDemoteConfig } : {}),
|
||||||
@ -240,9 +247,9 @@ export async function applyAgentConfig(params: {
|
|||||||
} else {
|
} else {
|
||||||
params.config.agent = {
|
params.config.agent = {
|
||||||
...builtinAgents,
|
...builtinAgents,
|
||||||
...filterDisabledAgents(userAgents),
|
...filteredUserAgents,
|
||||||
...filterDisabledAgents(projectAgents),
|
...filteredProjectAgents,
|
||||||
...filterDisabledAgents(pluginAgents),
|
...filteredPluginAgents,
|
||||||
...configAgent,
|
...configAgent,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -352,6 +352,94 @@ describe("custom agent overrides", () => {
|
|||||||
expect(agentsConfig[pKey].prompt).not.toContain("ghostwriter")
|
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<string, unknown> = {
|
||||||
|
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<string, { prompt?: string }>
|
||||||
|
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<string, unknown> = {
|
||||||
|
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<string, { prompt?: string }>
|
||||||
|
const pKey = getAgentDisplayName("prometheus")
|
||||||
|
expect(agentsConfig[pKey]).toBeDefined()
|
||||||
|
expect(agentsConfig[pKey].prompt).toContain("Custom planner prompt")
|
||||||
|
expect(agentsConfig[pKey].prompt).toContain("<custom_agent_catalog>")
|
||||||
|
expect(agentsConfig[pKey].prompt).toContain("translator")
|
||||||
|
})
|
||||||
|
|
||||||
test("custom agent summary merge preserves flags when custom_agents adds description", async () => {
|
test("custom agent summary merge preserves flags when custom_agents adds description", async () => {
|
||||||
// #given
|
// #given
|
||||||
;(agentLoader.loadUserAgents as any).mockReturnValue({
|
;(agentLoader.loadUserAgents as any).mockReturnValue({
|
||||||
|
|||||||
@ -103,5 +103,12 @@ export async function buildPrometheusAgentConfig(params: {
|
|||||||
if (prompt_append && typeof merged.prompt === "string") {
|
if (prompt_append && typeof merged.prompt === "string") {
|
||||||
merged.prompt = merged.prompt + "\n" + resolvePromptAppend(prompt_append);
|
merged.prompt = merged.prompt + "\n" + resolvePromptAppend(prompt_append);
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
customAgentBlock
|
||||||
|
&& typeof merged.prompt === "string"
|
||||||
|
&& !merged.prompt.includes("<custom_agent_catalog>")
|
||||||
|
) {
|
||||||
|
merged.prompt = merged.prompt + customAgentBlock;
|
||||||
|
}
|
||||||
return merged;
|
return merged;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user