Files
oh-my-opencode/src/plugin-handlers/command-config-handler.ts
T
YeonGyu-Kim 47283f9238 fix(agents): remove ZWSP prefixes from config.agent keys (#3238)
Agent names in the config.agent object (which becomes the /agent API
response) contained invisible Zero-Width Space (U+200B) characters
baked in by getAgentListDisplayName(). These ZWSP prefixes were used
for TUI sort ordering, but they leaked into the public API surface.

Impact: any prompt_async consumer that discovered agent names via the
/agent endpoint and passed them back to prompt_async without manual
ZWSP stripping got silent message drops — the agent name didn't match.
hy-pony's feishu-bridge integration went dark after upgrading to 3.16.0
with no error, no warning, and no indication that invisible Unicode
characters in agent names were the cause.

Fix: switch all four callsites from getAgentListDisplayName() (which
prepends \u200B×N) to getAgentDisplayName() (clean names):

- agent-key-remapper.ts: config keys → display names (was the primary
  injection point)
- agent-priority-order.ts: CORE_AGENT_ORDER lookup (must agree with
  the keys emitted by the remapper)
- command-config-handler.ts: command agent field normalization
- tool-config-handler.ts: agent config lookup (simplified fallback
  chain since the primary lookup is now clean)

Sort ordering is preserved by:
1. JS object insertion order from reorderAgentsByPriority()
2. The injected `order` field (1-4) added by injectOrderField()

getAgentListDisplayName() is marked @deprecated with a link to #3238.
AGENT_LIST_SORT_PREFIXES and stripAgentListSortPrefix() are kept for
any internal callers that strip prefixes from legacy data.

Closes #3238
2026-04-08 22:51:59 +09:00

106 lines
3.5 KiB
TypeScript

import type { OhMyOpenCodeConfig } from "../config";
import {
getAgentConfigKey,
getAgentDisplayName,
} from "../shared/agent-display-names";
import {
loadUserCommands,
loadProjectCommands,
loadOpencodeGlobalCommands,
loadOpencodeProjectCommands,
} from "../features/claude-code-command-loader";
import { loadBuiltinCommands } from "../features/builtin-commands";
import {
discoverConfigSourceSkills,
loadGlobalAgentsSkills,
loadProjectAgentsSkills,
loadUserSkills,
loadProjectSkills,
loadOpencodeGlobalSkills,
loadOpencodeProjectSkills,
skillsToCommandDefinitionRecord,
} from "../features/opencode-skill-loader";
import {
detectExternalSkillPlugin,
getSkillPluginConflictWarning,
log,
} from "../shared";
import type { PluginComponents } from "./plugin-components-loader";
export async function applyCommandConfig(params: {
config: Record<string, unknown>;
pluginConfig: OhMyOpenCodeConfig;
ctx: { directory: string };
pluginComponents: PluginComponents;
}): Promise<void> {
const builtinCommands = loadBuiltinCommands(params.pluginConfig.disabled_commands, {
useRegisteredAgents: true,
});
const systemCommands = (params.config.command as Record<string, unknown>) ?? {};
const includeClaudeCommands = params.pluginConfig.claude_code?.commands ?? true;
const includeClaudeSkills = params.pluginConfig.claude_code?.skills ?? true;
const externalSkillPlugin = detectExternalSkillPlugin(params.ctx.directory);
if (includeClaudeSkills && externalSkillPlugin.detected) {
log(getSkillPluginConflictWarning(externalSkillPlugin.pluginName!));
}
const [
configSourceSkills,
userCommands,
projectCommands,
opencodeGlobalCommands,
opencodeProjectCommands,
userSkills,
globalAgentsSkills,
projectSkills,
projectAgentsSkills,
opencodeGlobalSkills,
opencodeProjectSkills,
] = await Promise.all([
discoverConfigSourceSkills({
config: params.pluginConfig.skills,
configDir: params.ctx.directory,
}),
includeClaudeCommands ? loadUserCommands() : Promise.resolve({}),
includeClaudeCommands ? loadProjectCommands(params.ctx.directory) : Promise.resolve({}),
loadOpencodeGlobalCommands(),
loadOpencodeProjectCommands(params.ctx.directory),
includeClaudeSkills ? loadUserSkills() : Promise.resolve({}),
includeClaudeSkills ? loadGlobalAgentsSkills() : Promise.resolve({}),
includeClaudeSkills ? loadProjectSkills(params.ctx.directory) : Promise.resolve({}),
includeClaudeSkills ? loadProjectAgentsSkills(params.ctx.directory) : Promise.resolve({}),
loadOpencodeGlobalSkills(),
loadOpencodeProjectSkills(params.ctx.directory),
]);
params.config.command = {
...builtinCommands,
...skillsToCommandDefinitionRecord(configSourceSkills),
...userCommands,
...userSkills,
...globalAgentsSkills,
...opencodeGlobalCommands,
...opencodeGlobalSkills,
...systemCommands,
...projectCommands,
...projectSkills,
...projectAgentsSkills,
...opencodeProjectCommands,
...opencodeProjectSkills,
...params.pluginComponents.commands,
...params.pluginComponents.skills,
};
remapCommandAgentFields(params.config.command as Record<string, Record<string, unknown>>);
}
function remapCommandAgentFields(commands: Record<string, Record<string, unknown>>): void {
for (const cmd of Object.values(commands)) {
if (cmd?.agent && typeof cmd.agent === "string") {
cmd.agent = getAgentDisplayName(getAgentConfigKey(cmd.agent));
}
}
}