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
This commit is contained in:
YeonGyu-Kim
2026-04-08 22:51:59 +09:00
parent ed62f827e3
commit 47283f9238
10 changed files with 73 additions and 66 deletions
+12 -12
View File
@@ -1,6 +1,6 @@
import { describe, it, expect } from "bun:test"
import { remapAgentKeysToDisplayNames } from "./agent-key-remapper"
import { getAgentListDisplayName } from "../shared/agent-display-names"
import { getAgentDisplayName } from "../shared/agent-display-names"
describe("remapAgentKeysToDisplayNames", () => {
it("remaps known agent keys to display names", () => {
@@ -14,7 +14,7 @@ describe("remapAgentKeysToDisplayNames", () => {
const result = remapAgentKeysToDisplayNames(agents)
// then known agents get display name keys only
expect(result[getAgentListDisplayName("sisyphus")]).toBeDefined()
expect(result[getAgentDisplayName("sisyphus")]).toBeDefined()
expect(result["oracle"]).toBeDefined()
expect(result["sisyphus"]).toBeUndefined()
})
@@ -49,21 +49,21 @@ describe("remapAgentKeysToDisplayNames", () => {
const result = remapAgentKeysToDisplayNames(agents)
// then all get display name keys
expect(result[getAgentListDisplayName("sisyphus")]).toBeDefined()
expect(result[getAgentDisplayName("sisyphus")]).toBeDefined()
expect(result["sisyphus"]).toBeUndefined()
expect(result[getAgentListDisplayName("hephaestus")]).toBeDefined()
expect(result[getAgentDisplayName("hephaestus")]).toBeDefined()
expect(result["hephaestus"]).toBeUndefined()
expect(result[getAgentListDisplayName("prometheus")]).toBeDefined()
expect(result[getAgentDisplayName("prometheus")]).toBeDefined()
expect(result["prometheus"]).toBeUndefined()
expect(result[getAgentListDisplayName("atlas")]).toBeDefined()
expect(result[getAgentDisplayName("atlas")]).toBeDefined()
expect(result["atlas"]).toBeUndefined()
expect(result[getAgentListDisplayName("athena")]).toBeDefined()
expect(result[getAgentDisplayName("athena")]).toBeDefined()
expect(result["athena"]).toBeUndefined()
expect(result[getAgentListDisplayName("metis")]).toBeDefined()
expect(result[getAgentDisplayName("metis")]).toBeDefined()
expect(result["metis"]).toBeUndefined()
expect(result[getAgentListDisplayName("momus")]).toBeDefined()
expect(result[getAgentDisplayName("momus")]).toBeDefined()
expect(result["momus"]).toBeUndefined()
expect(result[getAgentListDisplayName("sisyphus-junior")]).toBeDefined()
expect(result[getAgentDisplayName("sisyphus-junior")]).toBeDefined()
expect(result["sisyphus-junior"]).toBeUndefined()
})
@@ -77,8 +77,8 @@ describe("remapAgentKeysToDisplayNames", () => {
const result = remapAgentKeysToDisplayNames(agents)
// then only display key is emitted
expect(Object.keys(result)).toEqual([getAgentListDisplayName("sisyphus")])
expect(result[getAgentListDisplayName("sisyphus")]).toBeDefined()
expect(Object.keys(result)).toEqual([getAgentDisplayName("sisyphus")])
expect(result[getAgentDisplayName("sisyphus")]).toBeDefined()
expect(result["sisyphus"]).toBeUndefined()
})
})