fix(agents): remove ZWSP sort prefixes from display name helper (#3259)
AGENT_LIST_SORT_PREFIXES prepended U+200B Zero Width Space characters
to the four core agent display names so they would sort ahead of user
agents in the Tab cycle. Two problems with that approach surfaced:
1. Some terminal emulators (Ghostty, certain Windows Terminal
builds) render ZWSP as a visible box or extra space, producing a
visible black gap in the status bar before "Sisyphus" and
misaligning the layout (#3259).
2. The prefixes leaked into the plugin API surface via config.agent
keys, breaking prompt_async consumers that received
ZWSP-contaminated agent names (#3238).
#3242 already removed every call site of getAgentListDisplayName() in
production code. That made the sort prefixes dead code: the constant
table was still defined but nothing read it. This PR finishes the
cleanup by:
- Deleting the AGENT_LIST_SORT_PREFIXES constant entirely
- Turning getAgentListDisplayName() into a thin alias over
getAgentDisplayName() for BC with external importers
- Keeping stripAgentListSortPrefix() as a legacy data migration for
users upgrading from v3.14.0-v3.16.0 whose config.agent keys may
still have ZWSP baked in from the old code path
- Documenting the history on stripAgentListSortPrefix() so future
maintainers understand why the stripper has to stay even after
the injector is gone
Sort ordering is preserved via JS object insertion order in
reorderAgentsByPriority() plus the `order` field it injects on the
four core agents. Both mechanisms are already in place and both
pre-date this PR; the ZWSP prefix was an older third layer that was
only meant to work around alphabetical sorting in legacy OpenCode
before the `order` field landed upstream.
Tests: 4445 pass, 0 fail. Added 3 new assertions to
agent-display-names.test.ts verifying that getAgentListDisplayName
returns plain names containing no zero-width characters. Updated
chat-message.test.ts to use a literal ZWSP string instead of the
helper so the defensive-strip path still has coverage.
Closes #3259
This commit is contained in:
@@ -183,30 +183,46 @@ describe("getAgentConfigKey", () => {
|
||||
expect(getAgentConfigKey("Sisyphus-Junior")).toBe("sisyphus-junior")
|
||||
})
|
||||
|
||||
it("resolves atlas even when the UI ordering prefix is present", () => {
|
||||
expect(getAgentConfigKey(getAgentListDisplayName("atlas"))).toBe("atlas")
|
||||
it("resolves atlas even when a legacy ZWSP sort prefix is present on the stored key", () => {
|
||||
// Users who installed v3.14.0 through v3.16.0 may have ZWSP-prefixed agent
|
||||
// names baked into their config.agent keys. The resolver must still find
|
||||
// the canonical config key after strip.
|
||||
expect(getAgentConfigKey("\u200B\u200B\u200B\u200BAtlas - Plan Executor")).toBe("atlas")
|
||||
})
|
||||
})
|
||||
|
||||
describe("getAgentListDisplayName", () => {
|
||||
it("applies invisible stable-sort prefixes to the core agent list", () => {
|
||||
expect(getAgentListDisplayName("sisyphus")).toBe("\u200BSisyphus - Ultraworker")
|
||||
expect(getAgentListDisplayName("hephaestus")).toBe("\u200B\u200BHephaestus - Deep Agent")
|
||||
expect(getAgentListDisplayName("prometheus")).toBe("\u200B\u200B\u200BPrometheus - Plan Builder")
|
||||
expect(getAgentListDisplayName("atlas")).toBe("\u200B\u200B\u200B\u200BAtlas - Plan Executor")
|
||||
describe("getAgentListDisplayName (deprecated alias, GH-3259)", () => {
|
||||
it("returns plain display names without the legacy ZWSP sort prefix", () => {
|
||||
// ZWSP prefixes were removed in #3242/#3259. This alias is retained for
|
||||
// external callers that may still import it, but it now behaves
|
||||
// identically to getAgentDisplayName.
|
||||
expect(getAgentListDisplayName("sisyphus")).toBe("Sisyphus - Ultraworker")
|
||||
expect(getAgentListDisplayName("hephaestus")).toBe("Hephaestus - Deep Agent")
|
||||
expect(getAgentListDisplayName("prometheus")).toBe("Prometheus - Plan Builder")
|
||||
expect(getAgentListDisplayName("atlas")).toBe("Atlas - Plan Executor")
|
||||
})
|
||||
|
||||
it("keeps non-core agents unprefixed for list display", () => {
|
||||
it("matches getAgentDisplayName for unknown agents", () => {
|
||||
expect(getAgentListDisplayName("oracle")).toBe("oracle")
|
||||
})
|
||||
|
||||
it("contains no zero-width characters in any core agent output (GH-3259)", () => {
|
||||
const coreAgents = ["sisyphus", "hephaestus", "prometheus", "atlas"]
|
||||
for (const agent of coreAgents) {
|
||||
const result = getAgentListDisplayName(agent)
|
||||
expect(result).not.toMatch(/[\u200B\u200C\u200D\uFEFF]/)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("normalizeAgentForPrompt", () => {
|
||||
it("strips core UI ordering prefixes back to canonical display names", () => {
|
||||
expect(normalizeAgentForPrompt(getAgentListDisplayName("sisyphus"))).toBe("Sisyphus - Ultraworker")
|
||||
expect(normalizeAgentForPrompt(getAgentListDisplayName("hephaestus"))).toBe("Hephaestus - Deep Agent")
|
||||
expect(normalizeAgentForPrompt(getAgentListDisplayName("prometheus"))).toBe("Prometheus - Plan Builder")
|
||||
expect(normalizeAgentForPrompt(getAgentListDisplayName("atlas"))).toBe("Atlas - Plan Executor")
|
||||
it("strips legacy ZWSP sort prefixes from stored agent keys back to canonical display names", () => {
|
||||
// Configs from v3.14.0-v3.16.0 may persist ZWSP-prefixed keys. The
|
||||
// normalizer must restore the canonical name on read.
|
||||
expect(normalizeAgentForPrompt("\u200BSisyphus - Ultraworker")).toBe("Sisyphus - Ultraworker")
|
||||
expect(normalizeAgentForPrompt("\u200B\u200BHephaestus - Deep Agent")).toBe("Hephaestus - Deep Agent")
|
||||
expect(normalizeAgentForPrompt("\u200B\u200B\u200BPrometheus - Plan Builder")).toBe("Prometheus - Plan Builder")
|
||||
expect(normalizeAgentForPrompt("\u200B\u200B\u200B\u200BAtlas - Plan Executor")).toBe("Atlas - Plan Executor")
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user