333ad3aadd
The sort shim from the previous commit enforces canonical core ordering at runtime, so ZWSP prefixes are no longer needed. Removing them eliminates the Bun.stringWidth vs terminal-width drift that broke the TUI status bar (#3259). Drop AGENT_LIST_SORT_PREFIXES and getAgentRuntimeName from agent-display-names; switch all call sites to getAgentDisplayName. getAgentListDisplayName stays as a thin alias for external importers. Keep stripInvisibleAgentCharacters and the ZWSP regex paths so legacy session state and configs from v3.14.0-v3.16.0 still resolve. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
407 lines
15 KiB
TypeScript
407 lines
15 KiB
TypeScript
import { createBuiltinAgents } from "../agents";
|
|
import { createSisyphusJuniorAgentWithOverrides } from "../agents/sisyphus-junior";
|
|
import type { OhMyOpenCodeConfig } from "../config";
|
|
import { isTaskSystemEnabled, log, migrateAgentConfig } from "../shared";
|
|
import {
|
|
getAgentConfigKey,
|
|
getAgentDisplayName,
|
|
normalizeAgentForPromptKey,
|
|
} from "../shared/agent-display-names";
|
|
import { AGENT_NAME_MAP } from "../shared/migration";
|
|
import { registerAgentName } from "../features/claude-code-session-state";
|
|
import {
|
|
discoverConfigSourceSkills,
|
|
discoverGlobalAgentsSkills,
|
|
discoverOpencodeGlobalSkills,
|
|
discoverOpencodeProjectSkills,
|
|
discoverProjectAgentsSkills,
|
|
discoverProjectClaudeSkills,
|
|
discoverUserClaudeSkills,
|
|
} from "../features/opencode-skill-loader";
|
|
import {
|
|
loadProjectAgents,
|
|
loadUserAgents,
|
|
loadOpencodeGlobalAgents,
|
|
loadOpencodeProjectAgents,
|
|
loadAgentDefinitions,
|
|
readOpencodeConfigAgents,
|
|
} from "../features/claude-code-agent-loader";
|
|
import type { PluginComponents } from "./plugin-components-loader";
|
|
import { reorderAgentsByPriority } from "./agent-priority-order";
|
|
import { remapAgentKeysToDisplayNames } from "./agent-key-remapper";
|
|
import {
|
|
createProtectedAgentNameSet,
|
|
filterProtectedAgentOverrides,
|
|
} from "./agent-override-protection";
|
|
import { buildPrometheusAgentConfig } from "./prometheus-agent-config-builder";
|
|
import { buildPlanDemoteConfig } from "./plan-model-inheritance";
|
|
|
|
type AgentConfigRecord = Record<string, Record<string, unknown> | undefined> & {
|
|
build?: Record<string, unknown>;
|
|
plan?: Record<string, unknown>;
|
|
};
|
|
|
|
function getConfiguredDefaultAgent(config: Record<string, unknown>): string | undefined {
|
|
const defaultAgent = config.default_agent;
|
|
if (typeof defaultAgent !== "string") return undefined;
|
|
const trimmedDefaultAgent = defaultAgent.trim();
|
|
return trimmedDefaultAgent.length > 0 ? trimmedDefaultAgent : undefined;
|
|
}
|
|
|
|
export async function applyAgentConfig(params: {
|
|
config: Record<string, unknown>;
|
|
pluginConfig: OhMyOpenCodeConfig;
|
|
ctx: { directory: string; client?: any };
|
|
pluginComponents: PluginComponents;
|
|
}): Promise<Record<string, unknown>> {
|
|
const migratedDisabledAgents = (params.pluginConfig.disabled_agents ?? []).map(
|
|
(agent) => {
|
|
return AGENT_NAME_MAP[agent.toLowerCase()] ?? AGENT_NAME_MAP[agent] ?? agent;
|
|
},
|
|
) as typeof params.pluginConfig.disabled_agents;
|
|
|
|
const includeClaudeSkillsForAwareness = params.pluginConfig.claude_code?.skills ?? true;
|
|
const [
|
|
discoveredConfigSourceSkills,
|
|
discoveredUserSkills,
|
|
discoveredProjectSkills,
|
|
discoveredProjectAgentsSkills,
|
|
discoveredOpencodeGlobalSkills,
|
|
discoveredOpencodeProjectSkills,
|
|
discoveredGlobalAgentsSkills,
|
|
] = await Promise.all([
|
|
discoverConfigSourceSkills({
|
|
config: params.pluginConfig.skills,
|
|
configDir: params.ctx.directory,
|
|
}),
|
|
includeClaudeSkillsForAwareness ? discoverUserClaudeSkills() : Promise.resolve([]),
|
|
includeClaudeSkillsForAwareness
|
|
? discoverProjectClaudeSkills(params.ctx.directory)
|
|
: Promise.resolve([]),
|
|
includeClaudeSkillsForAwareness
|
|
? discoverProjectAgentsSkills(params.ctx.directory)
|
|
: Promise.resolve([]),
|
|
discoverOpencodeGlobalSkills(),
|
|
discoverOpencodeProjectSkills(params.ctx.directory),
|
|
includeClaudeSkillsForAwareness ? discoverGlobalAgentsSkills() : Promise.resolve([]),
|
|
]);
|
|
|
|
const allDiscoveredSkills = [
|
|
...discoveredConfigSourceSkills,
|
|
...discoveredOpencodeProjectSkills,
|
|
...discoveredProjectSkills,
|
|
...discoveredProjectAgentsSkills,
|
|
...discoveredOpencodeGlobalSkills,
|
|
...discoveredUserSkills,
|
|
...discoveredGlobalAgentsSkills,
|
|
];
|
|
|
|
const browserProvider =
|
|
params.pluginConfig.browser_automation_engine?.provider ?? "playwright";
|
|
const currentModel = params.config.model as string | undefined;
|
|
const disabledSkills = new Set<string>(params.pluginConfig.disabled_skills ?? []);
|
|
const useTaskSystem = isTaskSystemEnabled(params.pluginConfig);
|
|
const disableOmoEnv = params.pluginConfig.experimental?.disable_omo_env ?? false;
|
|
|
|
const includeClaudeAgents = params.pluginConfig.claude_code?.agents ?? true;
|
|
const userAgents = includeClaudeAgents ? loadUserAgents() : {};
|
|
const projectAgents = includeClaudeAgents ? loadProjectAgents(params.ctx.directory) : {};
|
|
const opencodeGlobalAgents = loadOpencodeGlobalAgents();
|
|
const opencodeProjectAgents = loadOpencodeProjectAgents(params.ctx.directory);
|
|
const rawPluginAgents = params.pluginComponents.agents;
|
|
|
|
const agentDefinitionAgents = params.pluginConfig.agent_definitions
|
|
? loadAgentDefinitions(params.pluginConfig.agent_definitions, "definition-file")
|
|
: {};
|
|
const opencodeConfigAgents = readOpencodeConfigAgents(params.ctx.directory);
|
|
|
|
const pluginAgents = Object.fromEntries(
|
|
Object.entries(rawPluginAgents).map(([key, value]) => {
|
|
if (!value) return [key, value];
|
|
const migrated = migrateAgentConfig(value as Record<string, unknown>);
|
|
if (!migrated.mode) migrated.mode = "subagent";
|
|
return [key, migrated];
|
|
}),
|
|
);
|
|
|
|
const configAgent = params.config.agent as AgentConfigRecord | undefined;
|
|
|
|
const customAgentSummaries = [
|
|
...Object.entries(configAgent ?? {}),
|
|
...Object.entries(userAgents),
|
|
...Object.entries(projectAgents),
|
|
...Object.entries(opencodeGlobalAgents),
|
|
...Object.entries(opencodeProjectAgents),
|
|
...Object.entries(pluginAgents).filter(([, config]) => config !== undefined),
|
|
...Object.entries(agentDefinitionAgents),
|
|
...Object.entries(opencodeConfigAgents),
|
|
]
|
|
.filter(([, config]) => config != null)
|
|
.map(([name, config]) => ({
|
|
name,
|
|
description: typeof (config as Record<string, unknown>)?.description === "string"
|
|
? ((config as Record<string, unknown>).description as string)
|
|
: "",
|
|
}));
|
|
|
|
log(
|
|
"[agent-config-handler] Agent sources loaded",
|
|
{
|
|
user: Object.keys(userAgents).length,
|
|
project: Object.keys(projectAgents).length,
|
|
opencodeGlobal: Object.keys(opencodeGlobalAgents).length,
|
|
opencodeProject: Object.keys(opencodeProjectAgents).length,
|
|
plugin: Object.keys(pluginAgents).length,
|
|
agentDefinitions: Object.keys(agentDefinitionAgents).length,
|
|
opencodeConfig: Object.keys(opencodeConfigAgents).length,
|
|
config: Object.keys(configAgent ?? {}).length,
|
|
}
|
|
);
|
|
|
|
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 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 builderEnabled =
|
|
params.pluginConfig.sisyphus_agent?.default_builder_enabled ?? false;
|
|
const plannerEnabled = params.pluginConfig.sisyphus_agent?.planner_enabled ?? true;
|
|
const replacePlan = params.pluginConfig.sisyphus_agent?.replace_plan ?? true;
|
|
const shouldDemotePlan = plannerEnabled && replacePlan;
|
|
const configuredDefaultAgent = getConfiguredDefaultAgent(params.config);
|
|
|
|
if (isSisyphusEnabled && builtinAgents.sisyphus) {
|
|
if (configuredDefaultAgent) {
|
|
const configKey = getAgentConfigKey(configuredDefaultAgent);
|
|
const runtimeConfigKey = normalizeAgentForPromptKey(configuredDefaultAgent) ?? configKey;
|
|
(params.config as { default_agent?: string }).default_agent =
|
|
getAgentDisplayName(runtimeConfigKey);
|
|
} else {
|
|
(params.config as { default_agent?: string }).default_agent =
|
|
getAgentDisplayName("sisyphus");
|
|
}
|
|
|
|
// Assembly order: Sisyphus -> Hephaestus -> Prometheus -> Atlas
|
|
const agentConfig: Record<string, unknown> = {
|
|
sisyphus: builtinAgents.sisyphus,
|
|
};
|
|
|
|
if (builtinAgents.hephaestus) {
|
|
agentConfig["hephaestus"] = builtinAgents.hephaestus;
|
|
}
|
|
|
|
if (plannerEnabled) {
|
|
const prometheusOverride = params.pluginConfig.agents?.["prometheus"] as
|
|
| (Record<string, unknown> & { prompt_append?: string })
|
|
| undefined;
|
|
|
|
agentConfig["prometheus"] = await buildPrometheusAgentConfig({
|
|
configAgentPlan: configAgent?.plan,
|
|
pluginPrometheusOverride: prometheusOverride,
|
|
userCategories: params.pluginConfig.categories,
|
|
currentModel,
|
|
disabledTools: params.pluginConfig.disabled_tools,
|
|
});
|
|
}
|
|
|
|
if (builtinAgents.atlas) {
|
|
agentConfig["atlas"] = builtinAgents.atlas;
|
|
}
|
|
|
|
agentConfig["sisyphus-junior"] = createSisyphusJuniorAgentWithOverrides(
|
|
params.pluginConfig.agents?.["sisyphus-junior"],
|
|
(builtinAgents.atlas as { model?: string } | undefined)?.model,
|
|
useTaskSystem,
|
|
);
|
|
|
|
if (builderEnabled) {
|
|
const { name: _buildName, ...buildConfigWithoutName } =
|
|
configAgent?.build ?? {};
|
|
const migratedBuildConfig = migrateAgentConfig(
|
|
buildConfigWithoutName as Record<string, unknown>,
|
|
);
|
|
const override = params.pluginConfig.agents?.["OpenCode-Builder"];
|
|
const base = {
|
|
...migratedBuildConfig,
|
|
description: `${(configAgent?.build?.description as string) ?? "Build agent"} (OpenCode default)`,
|
|
};
|
|
agentConfig["OpenCode-Builder"] = override ? { ...base, ...override } : base;
|
|
}
|
|
|
|
const filteredConfigAgents = configAgent
|
|
? Object.fromEntries(
|
|
Object.entries(configAgent)
|
|
.filter(([key]) => {
|
|
if (key === "build") return false;
|
|
if (key === "plan" && shouldDemotePlan) return false;
|
|
if (key in builtinAgents) return false;
|
|
return true;
|
|
})
|
|
.map(([key, value]) => {
|
|
if (!value) return [key, value];
|
|
const migrated = migrateAgentConfig(value as Record<string, unknown>);
|
|
if (!migrated.mode) migrated.mode = "subagent";
|
|
return [key, migrated];
|
|
}),
|
|
)
|
|
: {};
|
|
|
|
const migratedBuild = configAgent?.build
|
|
? migrateAgentConfig(configAgent.build as Record<string, unknown>)
|
|
: {};
|
|
|
|
const planDemoteConfig = shouldDemotePlan
|
|
? buildPlanDemoteConfig(
|
|
agentConfig["prometheus"] as Record<string, unknown> | undefined,
|
|
params.pluginConfig.agents?.plan as Record<string, unknown> | undefined,
|
|
)
|
|
: undefined;
|
|
|
|
const protectedBuiltinAgentNames = createProtectedAgentNameSet([
|
|
...Object.keys(agentConfig),
|
|
...Object.keys(builtinAgents),
|
|
]);
|
|
const filteredUserAgents = filterProtectedAgentOverrides(
|
|
userAgents,
|
|
protectedBuiltinAgentNames,
|
|
);
|
|
const filteredProjectAgents = filterProtectedAgentOverrides(
|
|
projectAgents,
|
|
protectedBuiltinAgentNames,
|
|
);
|
|
const filteredPluginAgents = filterProtectedAgentOverrides(
|
|
pluginAgents,
|
|
protectedBuiltinAgentNames,
|
|
);
|
|
const filteredOpencodeGlobalAgents = filterProtectedAgentOverrides(
|
|
opencodeGlobalAgents,
|
|
protectedBuiltinAgentNames,
|
|
);
|
|
const filteredOpencodeProjectAgents = filterProtectedAgentOverrides(
|
|
opencodeProjectAgents,
|
|
protectedBuiltinAgentNames,
|
|
);
|
|
const filteredAgentDefinitionAgents = filterProtectedAgentOverrides(
|
|
agentDefinitionAgents,
|
|
protectedBuiltinAgentNames,
|
|
);
|
|
const filteredOpencodeConfigAgents = filterProtectedAgentOverrides(
|
|
opencodeConfigAgents,
|
|
protectedBuiltinAgentNames,
|
|
);
|
|
|
|
params.config.agent = {
|
|
...agentConfig,
|
|
...Object.fromEntries(
|
|
Object.entries(builtinAgents).filter(
|
|
([key]) => key !== "sisyphus" && key !== "hephaestus" && key !== "atlas",
|
|
),
|
|
),
|
|
// Precedence: later entries override earlier (project > global > user > plugin)
|
|
...filterDisabledAgents(filteredPluginAgents),
|
|
...filterDisabledAgents(filteredUserAgents),
|
|
...filterDisabledAgents(filteredOpencodeGlobalAgents),
|
|
...filterDisabledAgents(filteredProjectAgents),
|
|
...filterDisabledAgents(filteredOpencodeProjectAgents),
|
|
...filterDisabledAgents(filteredAgentDefinitionAgents),
|
|
...filterDisabledAgents(filteredOpencodeConfigAgents),
|
|
...filteredConfigAgents,
|
|
build: { ...migratedBuild, mode: "subagent", hidden: true },
|
|
...(planDemoteConfig ? { plan: planDemoteConfig } : {}),
|
|
};
|
|
} else {
|
|
const protectedBuiltinAgentNames = createProtectedAgentNameSet(
|
|
Object.keys(builtinAgents),
|
|
);
|
|
const filteredUserAgents = filterProtectedAgentOverrides(
|
|
userAgents,
|
|
protectedBuiltinAgentNames,
|
|
);
|
|
const filteredProjectAgents = filterProtectedAgentOverrides(
|
|
projectAgents,
|
|
protectedBuiltinAgentNames,
|
|
);
|
|
const filteredPluginAgents = filterProtectedAgentOverrides(
|
|
pluginAgents,
|
|
protectedBuiltinAgentNames,
|
|
);
|
|
const filteredOpencodeGlobalAgents = filterProtectedAgentOverrides(
|
|
opencodeGlobalAgents,
|
|
protectedBuiltinAgentNames,
|
|
);
|
|
const filteredOpencodeProjectAgents = filterProtectedAgentOverrides(
|
|
opencodeProjectAgents,
|
|
protectedBuiltinAgentNames,
|
|
);
|
|
const filteredAgentDefinitionAgents = filterProtectedAgentOverrides(
|
|
agentDefinitionAgents,
|
|
protectedBuiltinAgentNames,
|
|
);
|
|
const filteredOpencodeConfigAgents = filterProtectedAgentOverrides(
|
|
opencodeConfigAgents,
|
|
protectedBuiltinAgentNames,
|
|
);
|
|
|
|
const defaultedConfigAgents = configAgent
|
|
? Object.fromEntries(
|
|
Object.entries(configAgent).map(([key, value]) => {
|
|
if (!value) return [key, value];
|
|
const migrated = migrateAgentConfig(value as Record<string, unknown>);
|
|
if (!migrated.mode) migrated.mode = "subagent";
|
|
return [key, migrated];
|
|
}),
|
|
)
|
|
: {};
|
|
|
|
params.config.agent = {
|
|
...builtinAgents,
|
|
// Precedence: later entries override earlier (project > global > user > plugin)
|
|
...filterDisabledAgents(filteredPluginAgents),
|
|
...filterDisabledAgents(filteredUserAgents),
|
|
...filterDisabledAgents(filteredOpencodeGlobalAgents),
|
|
...filterDisabledAgents(filteredProjectAgents),
|
|
...filterDisabledAgents(filteredOpencodeProjectAgents),
|
|
...filterDisabledAgents(filteredAgentDefinitionAgents),
|
|
...filterDisabledAgents(filteredOpencodeConfigAgents),
|
|
...defaultedConfigAgents,
|
|
};
|
|
}
|
|
|
|
if (params.config.agent) {
|
|
params.config.agent = remapAgentKeysToDisplayNames(
|
|
params.config.agent as Record<string, unknown>,
|
|
);
|
|
params.config.agent = reorderAgentsByPriority(
|
|
params.config.agent as Record<string, unknown>,
|
|
);
|
|
}
|
|
|
|
const agentResult = params.config.agent as Record<string, unknown>;
|
|
for (const name of Object.keys(agentResult)) {
|
|
registerAgentName(name);
|
|
}
|
|
log("[config-handler] agents loaded", { agentKeys: Object.keys(agentResult) });
|
|
return agentResult;
|
|
}
|