Revert "Merge pull request #1951 from edxeth/feat/custom-agents"

This reverts commit 47e300b17e, reversing
changes made to 243ce1b7e8.
This commit is contained in:
YeonGyu-Kim
2026-03-02 23:55:48 +09:00
parent 314532de6a
commit 813973041e
19 changed files with 47 additions and 1511 deletions
+30 -82
View File
@@ -4,7 +4,6 @@ import type { OhMyOpenCodeConfig } from "../config";
import { log, migrateAgentConfig } from "../shared";
import { AGENT_NAME_MAP } from "../shared/migration";
import { getAgentDisplayName } from "../shared/agent-display-names";
import { mergeCategories } from "../shared/merge-categories";
import {
discoverConfigSourceSkills,
discoverOpencodeGlobalSkills,
@@ -18,13 +17,6 @@ import { reorderAgentsByPriority } from "./agent-priority-order";
import { remapAgentKeysToDisplayNames } from "./agent-key-remapper";
import { buildPrometheusAgentConfig } from "./prometheus-agent-config-builder";
import { buildPlanDemoteConfig } from "./plan-model-inheritance";
import {
applyCustomAgentOverrides,
collectCustomAgentSummariesFromRecord,
mergeCustomAgentSummaries,
collectKnownCustomAgentNames,
filterSummariesByKnownNames,
} from "./custom-agent-utils";
type AgentConfigRecord = Record<string, Record<string, unknown> | undefined> & {
build?: Record<string, unknown>;
@@ -82,19 +74,26 @@ 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<string, unknown>) =>
Object.fromEntries(
Object.entries(agents).filter(
([name]) => !disabledAgentNames.has(name.toLowerCase()),
),
);
const disabledSkills = new Set<string>(params.pluginConfig.disabled_skills ?? []);
const useTaskSystem = params.pluginConfig.experimental?.task_system ?? false;
const disableOmoEnv = params.pluginConfig.experimental?.disable_omo_env ?? false;
const builtinAgents = await createBuiltinAgents(
migratedDisabledAgents,
params.pluginConfig.agents,
params.ctx.directory,
currentModel,
params.pluginConfig.categories,
params.pluginConfig.git_master,
allDiscoveredSkills,
params.ctx.client,
browserProvider,
currentModel,
disabledSkills,
useTaskSystem,
disableOmoEnv,
);
const includeClaudeAgents = params.pluginConfig.claude_code?.agents ?? true;
const userAgents = includeClaudeAgents ? loadUserAgents() : {};
const projectAgents = includeClaudeAgents ? loadProjectAgents(params.ctx.directory) : {};
@@ -107,49 +106,15 @@ export async function applyAgentConfig(params: {
]),
);
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 disabledAgentNames = new Set(
(migratedDisabledAgents ?? []).map(a => a.toLowerCase())
);
const mergedCategories = mergeCategories(params.pluginConfig.categories)
const knownCustomAgentNames = collectKnownCustomAgentNames(
filteredUserAgents,
filteredProjectAgents,
filteredPluginAgents,
filteredConfigAgentsForSummary,
)
const customAgentSummaries = mergeCustomAgentSummaries(
collectCustomAgentSummariesFromRecord(filteredUserAgents),
collectCustomAgentSummariesFromRecord(filteredProjectAgents),
collectCustomAgentSummariesFromRecord(filteredPluginAgents),
collectCustomAgentSummariesFromRecord(filteredConfigAgentsForSummary),
filterSummariesByKnownNames(
collectCustomAgentSummariesFromRecord(
params.pluginConfig.custom_agents as Record<string, unknown> | undefined,
),
knownCustomAgentNames,
),
)
const filterDisabledAgents = (agents: Record<string, unknown>) =>
Object.fromEntries(
Object.entries(agents).filter(([name]) => !disabledAgentNames.has(name.toLowerCase()))
);
const builtinAgents = await createBuiltinAgents(
migratedDisabledAgents,
params.pluginConfig.agents,
params.ctx.directory,
currentModel,
params.pluginConfig.categories,
params.pluginConfig.git_master,
allDiscoveredSkills,
customAgentSummaries,
browserProvider,
currentModel,
disabledSkills,
useTaskSystem,
disableOmoEnv,
);
const isSisyphusEnabled = params.pluginConfig.sisyphus_agent?.disabled !== true;
const builderEnabled =
params.pluginConfig.sisyphus_agent?.default_builder_enabled ?? false;
@@ -158,6 +123,8 @@ export async function applyAgentConfig(params: {
const shouldDemotePlan = plannerEnabled && replacePlan;
const configuredDefaultAgent = getConfiguredDefaultAgent(params.config);
const configAgent = params.config.agent as AgentConfigRecord | undefined;
if (isSisyphusEnabled && builtinAgents.sisyphus) {
if (configuredDefaultAgent) {
(params.config as { default_agent?: string }).default_agent =
@@ -201,7 +168,6 @@ export async function applyAgentConfig(params: {
pluginPrometheusOverride: prometheusOverride,
userCategories: params.pluginConfig.categories,
currentModel,
customAgentSummaries,
});
}
@@ -237,9 +203,9 @@ export async function applyAgentConfig(params: {
...Object.fromEntries(
Object.entries(builtinAgents).filter(([key]) => key !== "sisyphus"),
),
...filteredUserAgents,
...filteredProjectAgents,
...filteredPluginAgents,
...filterDisabledAgents(userAgents),
...filterDisabledAgents(projectAgents),
...filterDisabledAgents(pluginAgents),
...filteredConfigAgents,
build: { ...migratedBuild, mode: "subagent", hidden: true },
...(planDemoteConfig ? { plan: planDemoteConfig } : {}),
@@ -247,31 +213,13 @@ export async function applyAgentConfig(params: {
} else {
params.config.agent = {
...builtinAgents,
...filteredUserAgents,
...filteredProjectAgents,
...filteredPluginAgents,
...filterDisabledAgents(userAgents),
...filterDisabledAgents(projectAgents),
...filterDisabledAgents(pluginAgents),
...configAgent,
};
}
if (params.config.agent) {
const builtinOverrideKeys = new Set([
...Object.keys(builtinAgents).map((key) => key.toLowerCase()),
"build",
"plan",
"sisyphus-junior",
"opencode-builder",
])
applyCustomAgentOverrides({
mergedAgents: params.config.agent as Record<string, unknown>,
userOverrides: params.pluginConfig.custom_agents,
builtinOverrideKeys,
mergedCategories,
directory: params.ctx.directory,
})
}
if (params.config.agent) {
params.config.agent = remapAgentKeysToDisplayNames(
params.config.agent as Record<string, unknown>,
-341
View File
@@ -162,347 +162,6 @@ describe("Sisyphus-Junior model inheritance", () => {
})
})
describe("custom agent overrides", () => {
test("passes custom agent summaries into builtin agent prompt builder", async () => {
// #given
;(agentLoader.loadUserAgents as any).mockReturnValue({
translator: {
name: "translator",
mode: "subagent",
description: "Translate and localize text",
prompt: "Translate content",
},
})
const createBuiltinAgentsMock = agents.createBuiltinAgents as unknown as {
mock: { calls: unknown[][] }
}
const pluginConfig: OhMyOpenCodeConfig = {
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 firstCallArgs = createBuiltinAgentsMock.mock.calls[0]
expect(firstCallArgs).toBeDefined()
expect(Array.isArray(firstCallArgs[7])).toBe(true)
expect(firstCallArgs[7]).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: "translator",
description: "Translate and localize text",
}),
]),
)
})
test("applies oh-my-opencode agent overrides to custom Claude agents", async () => {
// #given
;(agentLoader.loadUserAgents as any).mockReturnValue({
translator: {
name: "translator",
mode: "subagent",
description: "(user) translator",
prompt: "Base translator prompt",
},
})
const pluginConfig: OhMyOpenCodeConfig = {
custom_agents: {
translator: {
model: "google/gemini-3-flash-preview",
temperature: 0,
prompt_append: "Always preserve placeholders exactly.",
},
},
}
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 agentConfig = config.agent as Record<string, { model?: string; temperature?: number; prompt?: string }>
expect(agentConfig.translator).toBeDefined()
expect(agentConfig.translator.model).toBe("google/gemini-3-flash-preview")
expect(agentConfig.translator.temperature).toBe(0)
expect(agentConfig.translator.prompt).toContain("Base translator prompt")
expect(agentConfig.translator.prompt).toContain("Always preserve placeholders exactly.")
})
test("prometheus prompt includes custom agent catalog for planning", async () => {
// #given
;(agentLoader.loadUserAgents as any).mockReturnValue({
translator: {
name: "translator",
mode: "subagent",
description: "Translate and localize locale files",
prompt: "Translate content",
},
})
const pluginConfig: OhMyOpenCodeConfig = {
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_agent_catalog>")
expect(agentsConfig[pKey].prompt).toContain("translator")
expect(agentsConfig[pKey].prompt).toContain("Translate and localize locale files")
})
test("prometheus prompt excludes unknown custom_agents entries", async () => {
// #given
;(agentLoader.loadUserAgents as any).mockReturnValue({
translator: {
name: "translator",
mode: "subagent",
description: "Translate and localize locale files",
prompt: "Translate content",
},
})
const pluginConfig: OhMyOpenCodeConfig = {
custom_agents: {
translator: {
description: "Translate and localize locale files",
},
ghostwriter: {
description: "This agent does not exist in runtime",
},
},
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("translator")
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 () => {
// #given
;(agentLoader.loadUserAgents as any).mockReturnValue({
translator: {
name: "translator",
mode: "subagent",
description: "",
hidden: true,
disabled: true,
enabled: false,
prompt: "Translate content",
},
})
const createBuiltinAgentsMock = agents.createBuiltinAgents as unknown as {
mock: { calls: unknown[][] }
}
const pluginConfig: OhMyOpenCodeConfig = {
custom_agents: {
translator: {
description: "Translate and localize locale files",
},
},
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 firstCallArgs = createBuiltinAgentsMock.mock.calls[0]
const summaries = firstCallArgs[7] as Array<{
name: string
description: string
hidden?: boolean
disabled?: boolean
enabled?: boolean
}>
const translatorSummary = summaries.find((summary) => summary.name === "translator")
expect(translatorSummary).toBeDefined()
expect(translatorSummary?.description).toBe("Translate and localize locale files")
expect(translatorSummary?.hidden).toBe(true)
expect(translatorSummary?.disabled).toBe(true)
expect(translatorSummary?.enabled).toBe(false)
})
})
describe("Plan agent demote behavior", () => {
test("orders core agents as sisyphus -> hephaestus -> prometheus -> atlas", async () => {
// #given
-142
View File
@@ -1,142 +0,0 @@
import type { AgentConfig } from "@opencode-ai/sdk";
import { applyOverrides } from "../agents/builtin-agents/agent-overrides";
import type { AgentOverrideConfig } from "../agents/types";
import type { OhMyOpenCodeConfig } from "../config";
import { getAgentConfigKey } from "../shared/agent-display-names";
import { AGENT_NAME_MAP } from "../shared/migration";
import { mergeCategories } from "../shared/merge-categories";
const RESERVED_AGENT_KEYS = new Set(
[
"build",
"plan",
"sisyphus-junior",
"opencode-builder",
...Object.keys(AGENT_NAME_MAP),
...Object.values(AGENT_NAME_MAP),
].map((key) => getAgentConfigKey(key).toLowerCase()),
);
export type AgentSummary = {
name: string;
description: string;
hidden?: boolean;
disabled?: boolean;
enabled?: boolean;
};
export function applyCustomAgentOverrides(params: {
mergedAgents: Record<string, unknown>;
userOverrides: OhMyOpenCodeConfig["custom_agents"] | undefined;
builtinOverrideKeys: Set<string>;
mergedCategories: ReturnType<typeof mergeCategories>;
directory: string;
}): void {
if (!params.userOverrides) return;
for (const [overrideKey, override] of Object.entries(params.userOverrides)) {
if (!override) continue;
const normalizedOverrideKey = getAgentConfigKey(overrideKey).toLowerCase();
if (params.builtinOverrideKeys.has(normalizedOverrideKey)) continue;
const existingKey = Object.keys(params.mergedAgents).find(
(key) => key.toLowerCase() === overrideKey.toLowerCase() || key.toLowerCase() === normalizedOverrideKey,
);
if (!existingKey) continue;
const existingAgent = params.mergedAgents[existingKey];
if (!existingAgent || typeof existingAgent !== "object") continue;
params.mergedAgents[existingKey] = applyOverrides(
existingAgent as AgentConfig,
override as AgentOverrideConfig,
params.mergedCategories,
params.directory,
);
}
}
export function collectCustomAgentSummariesFromRecord(
agents: Record<string, unknown> | undefined,
): AgentSummary[] {
if (!agents) return [];
const summaries: AgentSummary[] = [];
for (const [name, value] of Object.entries(agents)) {
const normalizedName = getAgentConfigKey(name).toLowerCase();
if (RESERVED_AGENT_KEYS.has(normalizedName)) continue;
if (!value || typeof value !== "object") continue;
const agentValue = value as Record<string, unknown>;
const description = typeof agentValue.description === "string" ? agentValue.description : "";
summaries.push({
name,
description,
hidden: typeof agentValue.hidden === "boolean" ? agentValue.hidden : undefined,
disabled: typeof agentValue.disabled === "boolean" ? agentValue.disabled : undefined,
enabled: typeof agentValue.enabled === "boolean" ? agentValue.enabled : undefined,
});
}
return summaries;
}
export function mergeCustomAgentSummaries(...summaryGroups: AgentSummary[][]): AgentSummary[] {
const merged = new Map<string, AgentSummary>();
for (const group of summaryGroups) {
for (const summary of group) {
const key = summary.name.toLowerCase();
if (!merged.has(key)) {
merged.set(key, summary);
continue;
}
const existing = merged.get(key);
if (!existing) continue;
const existingDescription = existing.description.trim();
const incomingDescription = summary.description.trim();
merged.set(key, {
...existing,
...summary,
hidden: summary.hidden ?? existing.hidden,
disabled: summary.disabled ?? existing.disabled,
enabled: summary.enabled ?? existing.enabled,
description: incomingDescription || existingDescription,
});
}
}
return Array.from(merged.values());
}
export function collectKnownCustomAgentNames(
...agentGroups: Array<Record<string, unknown> | undefined>
): Set<string> {
const knownNames = new Set<string>();
for (const group of agentGroups) {
if (!group) continue;
for (const [name, value] of Object.entries(group)) {
const normalizedName = getAgentConfigKey(name).toLowerCase();
if (RESERVED_AGENT_KEYS.has(normalizedName)) continue;
if (!value || typeof value !== "object") continue;
knownNames.add(normalizedName);
}
}
return knownNames;
}
export function filterSummariesByKnownNames(
summaries: AgentSummary[],
knownNames: Set<string>,
): AgentSummary[] {
return summaries.filter((summary) => knownNames.has(summary.name.toLowerCase()));
}
@@ -1,7 +1,6 @@
import type { CategoryConfig } from "../config/schema";
import { PROMETHEUS_PERMISSION, getPrometheusPrompt } from "../agents/prometheus";
import { resolvePromptAppend } from "../agents/builtin-agents/resolve-file-uri";
import { parseRegisteredAgentSummaries } from "../agents/custom-agent-summaries";
import { AGENT_MODEL_REQUIREMENTS } from "../shared/model-requirements";
import {
fetchAvailableModels,
@@ -28,7 +27,6 @@ export async function buildPrometheusAgentConfig(params: {
pluginPrometheusOverride: PrometheusOverride | undefined;
userCategories: Record<string, CategoryConfig> | undefined;
currentModel: string | undefined;
customAgentSummaries?: unknown;
}): Promise<Record<string, unknown>> {
const categoryConfig = params.pluginPrometheusOverride?.category
? resolveCategoryConfig(params.pluginPrometheusOverride.category, params.userCategories)
@@ -67,18 +65,11 @@ export async function buildPrometheusAgentConfig(params: {
const maxTokensToUse =
params.pluginPrometheusOverride?.maxTokens ?? categoryConfig?.maxTokens;
const customAgentCatalog = parseRegisteredAgentSummaries(params.customAgentSummaries)
const customAgentBlock = customAgentCatalog.length > 0
? `\n\n<custom_agent_catalog>\nAvailable custom agents for planning/delegation:\n${customAgentCatalog
.map((agent) => `- ${agent.name}: ${agent.description || "No description provided"}`)
.join("\n")}\n</custom_agent_catalog>`
: ""
const base: Record<string, unknown> = {
...(resolvedModel ? { model: resolvedModel } : {}),
...(variantToUse ? { variant: variantToUse } : {}),
mode: "all",
prompt: getPrometheusPrompt(resolvedModel) + customAgentBlock,
prompt: getPrometheusPrompt(resolvedModel),
permission: PROMETHEUS_PERMISSION,
description: `${(params.configAgentPlan?.description as string) ?? "Plan agent"} (Prometheus - OhMyOpenCode)`,
color: (params.configAgentPlan?.color as string) ?? "#FF5722",
@@ -103,12 +94,5 @@ 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("<custom_agent_catalog>")
) {
merged.prompt = merged.prompt + customAgentBlock;
}
return merged;
}