From e2e57bb2dda34c4bf62c7df2f9ba1056f5565d78 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Tue, 31 Mar 2026 15:11:00 -0700 Subject: [PATCH] fix(agents): use list display names for ordered agent config --- .../agent-key-remapper.test.ts | 2 +- src/plugin-handlers/agent-key-remapper.ts | 4 ++-- src/plugin-handlers/agent-priority-order.ts | 10 ++++---- src/plugin-handlers/config-handler.test.ts | 20 ++++++++-------- src/plugin-handlers/tool-config-handler.ts | 4 ++-- src/shared/agent-display-names.test.ts | 24 +++++++++++++++++-- src/shared/agent-display-names.ts | 19 +++++++++++++-- 7 files changed, 59 insertions(+), 24 deletions(-) diff --git a/src/plugin-handlers/agent-key-remapper.test.ts b/src/plugin-handlers/agent-key-remapper.test.ts index fea227ea3..d2e158605 100644 --- a/src/plugin-handlers/agent-key-remapper.test.ts +++ b/src/plugin-handlers/agent-key-remapper.test.ts @@ -54,7 +54,7 @@ describe("remapAgentKeysToDisplayNames", () => { expect(result["hephaestus"]).toBeUndefined() expect(result["Prometheus (Plan Builder)"]).toBeDefined() expect(result["prometheus"]).toBeUndefined() - expect(result["Atlas (Plan Executor)"]).toBeDefined() + expect(result["\u200BAtlas (Plan Executor)"]).toBeDefined() expect(result["atlas"]).toBeUndefined() expect(result["Athena (Council)"]).toBeDefined() expect(result["athena"]).toBeUndefined() diff --git a/src/plugin-handlers/agent-key-remapper.ts b/src/plugin-handlers/agent-key-remapper.ts index 57803df18..1becbcda9 100644 --- a/src/plugin-handlers/agent-key-remapper.ts +++ b/src/plugin-handlers/agent-key-remapper.ts @@ -1,4 +1,4 @@ -import { AGENT_DISPLAY_NAMES } from "../shared/agent-display-names" +import { getAgentListDisplayName } from "../shared/agent-display-names" export function remapAgentKeysToDisplayNames( agents: Record, @@ -6,7 +6,7 @@ export function remapAgentKeysToDisplayNames( const result: Record = {} for (const [key, value] of Object.entries(agents)) { - const displayName = AGENT_DISPLAY_NAMES[key] + const displayName = getAgentListDisplayName(key) if (displayName && displayName !== key) { result[displayName] = value // Regression guard: do not also assign result[key]. diff --git a/src/plugin-handlers/agent-priority-order.ts b/src/plugin-handlers/agent-priority-order.ts index c315ad76a..f69b9a13b 100644 --- a/src/plugin-handlers/agent-priority-order.ts +++ b/src/plugin-handlers/agent-priority-order.ts @@ -1,10 +1,10 @@ -import { getAgentDisplayName } from "../shared/agent-display-names"; +import { getAgentListDisplayName } from "../shared/agent-display-names"; const CORE_AGENT_ORDER: ReadonlyArray<{ displayName: string; order: number }> = [ - { displayName: getAgentDisplayName("sisyphus"), order: 1 }, - { displayName: getAgentDisplayName("hephaestus"), order: 2 }, - { displayName: getAgentDisplayName("prometheus"), order: 3 }, - { displayName: getAgentDisplayName("atlas"), order: 4 }, + { displayName: getAgentListDisplayName("sisyphus"), order: 1 }, + { displayName: getAgentListDisplayName("hephaestus"), order: 2 }, + { displayName: getAgentListDisplayName("prometheus"), order: 3 }, + { displayName: getAgentListDisplayName("atlas"), order: 4 }, ]; function injectOrderField( diff --git a/src/plugin-handlers/config-handler.test.ts b/src/plugin-handlers/config-handler.test.ts index 4d33d5d9f..e2c21eea4 100644 --- a/src/plugin-handlers/config-handler.test.ts +++ b/src/plugin-handlers/config-handler.test.ts @@ -4,7 +4,7 @@ import { describe, test, expect, spyOn, beforeEach, afterEach } from "bun:test" import { resolveCategoryConfig, createConfigHandler } from "./config-handler" import type { CategoryConfig } from "../config/schema" import type { OhMyOpenCodeConfig } from "../config" -import { getAgentDisplayName } from "../shared/agent-display-names" +import { getAgentDisplayName, getAgentListDisplayName } from "../shared/agent-display-names" import * as agents from "../agents" import * as sisyphusJunior from "../agents/sisyphus-junior" @@ -198,10 +198,10 @@ describe("Plan agent demote behavior", () => { // #then const keys = Object.keys(config.agent as Record) const coreAgents = [ - getAgentDisplayName("sisyphus"), - getAgentDisplayName("hephaestus"), - getAgentDisplayName("prometheus"), - getAgentDisplayName("atlas"), + getAgentListDisplayName("sisyphus"), + getAgentListDisplayName("hephaestus"), + getAgentListDisplayName("prometheus"), + getAgentListDisplayName("atlas"), ] const ordered = keys.filter((key) => coreAgents.includes(key)) expect(ordered).toEqual(coreAgents) @@ -1158,11 +1158,11 @@ describe("config-handler plugin loading error boundary (#1559)", () => { describe("per-agent todowrite/todoread deny when task_system enabled", () => { const AGENTS_WITH_TODO_DENY = new Set([ - getAgentDisplayName("sisyphus"), - getAgentDisplayName("hephaestus"), - getAgentDisplayName("atlas"), - getAgentDisplayName("prometheus"), - getAgentDisplayName("sisyphus-junior"), + getAgentListDisplayName("sisyphus"), + getAgentListDisplayName("hephaestus"), + getAgentListDisplayName("atlas"), + getAgentListDisplayName("prometheus"), + getAgentListDisplayName("sisyphus-junior"), ]) test("denies todowrite and todoread for primary agents when task_system is enabled", async () => { diff --git a/src/plugin-handlers/tool-config-handler.ts b/src/plugin-handlers/tool-config-handler.ts index 33f30b352..1e2b6867b 100644 --- a/src/plugin-handlers/tool-config-handler.ts +++ b/src/plugin-handlers/tool-config-handler.ts @@ -1,5 +1,5 @@ import type { OhMyOpenCodeConfig } from "../config"; -import { getAgentDisplayName } from "../shared/agent-display-names"; +import { getAgentDisplayName, getAgentListDisplayName } from "../shared/agent-display-names"; type AgentWithPermission = { permission?: Record }; @@ -15,7 +15,7 @@ function getConfigQuestionPermission(): string | null { } function agentByKey(agentResult: Record, key: string): AgentWithPermission | undefined { - return (agentResult[getAgentDisplayName(key)] ?? agentResult[key]) as + return (agentResult[getAgentListDisplayName(key)] ?? agentResult[getAgentDisplayName(key)] ?? agentResult[key]) as | AgentWithPermission | undefined; } diff --git a/src/shared/agent-display-names.test.ts b/src/shared/agent-display-names.test.ts index 5419e46ce..19de11a4b 100644 --- a/src/shared/agent-display-names.test.ts +++ b/src/shared/agent-display-names.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from "bun:test" -import { AGENT_DISPLAY_NAMES, getAgentDisplayName, getAgentConfigKey } from "./agent-display-names" +import { AGENT_DISPLAY_NAMES, getAgentConfigKey, getAgentDisplayName, getAgentListDisplayName, normalizeAgentForPrompt } from "./agent-display-names" describe("getAgentDisplayName", () => { it("returns display name for lowercase config key (new format)", () => { @@ -174,6 +174,26 @@ describe("getAgentConfigKey", () => { expect(getAgentConfigKey("Momus (Plan Critic)")).toBe("momus") expect(getAgentConfigKey("Sisyphus-Junior")).toBe("sisyphus-junior") }) + + it("resolves atlas even when the UI ordering prefix is present", () => { + expect(getAgentConfigKey(getAgentListDisplayName("atlas"))).toBe("atlas") + }) +}) + +describe("getAgentListDisplayName", () => { + it("keeps sisyphus unchanged for list display", () => { + expect(getAgentListDisplayName("sisyphus")).toBe("Sisyphus (Ultraworker)") + }) + + it("applies invisible atlas sort prefix for list display", () => { + expect(getAgentListDisplayName("atlas")).toBe("\u200BAtlas (Plan Executor)") + }) +}) + +describe("normalizeAgentForPrompt", () => { + it("strips atlas UI ordering prefix back to canonical display name", () => { + expect(normalizeAgentForPrompt(getAgentListDisplayName("atlas"))).toBe("Atlas (Plan Executor)") + }) }) describe("AGENT_DISPLAY_NAMES", () => { @@ -200,4 +220,4 @@ describe("AGENT_DISPLAY_NAMES", () => { // then contains all expected mappings expect(AGENT_DISPLAY_NAMES).toEqual(expectedMappings) }) -}) \ No newline at end of file +}) diff --git a/src/shared/agent-display-names.ts b/src/shared/agent-display-names.ts index 57b9be27d..b9eac59ef 100644 --- a/src/shared/agent-display-names.ts +++ b/src/shared/agent-display-names.ts @@ -20,6 +20,14 @@ export const AGENT_DISPLAY_NAMES: Record = { "council-member": "council-member", } +const AGENT_LIST_SORT_PREFIXES: Record = { + atlas: "\u200B", +} + +function stripAgentListSortPrefix(agentName: string): string { + return agentName.replace(/^\u200B+/, "") +} + /** * Get display name for an agent config key. * Uses case-insensitive lookup for backward compatibility. @@ -40,6 +48,13 @@ export function getAgentDisplayName(configKey: string): string { return configKey } +export function getAgentListDisplayName(configKey: string): string { + const displayName = getAgentDisplayName(configKey) + const prefix = AGENT_LIST_SORT_PREFIXES[configKey.toLowerCase()] + + return prefix ? `${prefix}${displayName}` : displayName +} + const REVERSE_DISPLAY_NAMES: Record = Object.fromEntries( Object.entries(AGENT_DISPLAY_NAMES).map(([key, displayName]) => [displayName.toLowerCase(), key]), ) @@ -49,7 +64,7 @@ const REVERSE_DISPLAY_NAMES: Record = Object.fromEntries( * "Atlas (Plan Executor)" → "atlas", "atlas" → "atlas", "unknown" → "unknown" */ export function getAgentConfigKey(agentName: string): string { - const lower = agentName.toLowerCase() + const lower = stripAgentListSortPrefix(agentName).toLowerCase() const reversed = REVERSE_DISPLAY_NAMES[lower] if (reversed !== undefined) return reversed if (AGENT_DISPLAY_NAMES[lower] !== undefined) return lower @@ -67,7 +82,7 @@ export function normalizeAgentForPrompt(agentName: string | undefined): string | return undefined } - const trimmed = agentName.trim() + const trimmed = stripAgentListSortPrefix(agentName.trim()) if (!trimmed) { return undefined }