Revert "Merge pull request #3260 from code-yeongyu/fix/remove-zwsp-sort-prefixes"

This reverts commit c3be4c2793, reversing
changes made to d2bb5d57d1.
This commit is contained in:
YeonGyu-Kim
2026-04-09 12:21:02 +09:00
parent ef95a99420
commit 0d5b087440
4 changed files with 34 additions and 64 deletions
+1
View File
@@ -6,6 +6,7 @@ import { randomUUID } from "node:crypto"
import { createPluginInterface } from "./plugin-interface"
import { createAutoSlashCommandHook } from "./hooks/auto-slash-command"
import { createStartWorkHook } from "./hooks/start-work"
import { getAgentListDisplayName } from "./shared/agent-display-names"
import { readBoulderState } from "./features/boulder-state"
import {
_resetForTesting,
+3 -5
View File
@@ -9,6 +9,7 @@ import { createAutoSlashCommandHook } from "../hooks/auto-slash-command"
import { createStartWorkHook } from "../hooks/start-work"
import { readBoulderState } from "../features/boulder-state"
import { _resetForTesting, setMainSession, subagentSessions, registerAgentName, updateSessionAgent, getSessionAgent } from "../features/claude-code-session-state"
import { getAgentListDisplayName } from "../shared/agent-display-names"
import { clearSessionModel, getSessionModel, setSessionModel } from "../shared/session-model-state"
type ChatMessagePart = { type: string; text?: string; [key: string]: unknown }
@@ -402,10 +403,7 @@ describe("createChatMessageHandler - TUI variant passthrough", () => {
expect(getSessionModel("test-session")).toEqual({ providerID: "openai", modelID: "gpt-5.4" })
})
test("treats legacy ZWSP-prefixed agent names as explicit model overrides (GH-3259)", async () => {
// Users upgrading from v3.14.0-v3.16.0 may still have ZWSP-prefixed agent
// keys persisted in their session state. The handler must strip the
// prefix and resolve to the canonical display name.
test("treats prefixed list-display agent names as explicit model overrides", async () => {
//#given
setMainSession("test-session")
setSessionModel("test-session", { providerID: "openai", modelID: "gpt-5.4" })
@@ -418,7 +416,7 @@ describe("createChatMessageHandler - TUI variant passthrough", () => {
},
})
const handler = createChatMessageHandler(args)
const input = createMockInput("\u200B\u200B\u200BPrometheus - Plan Builder")
const input = createMockInput(getAgentListDisplayName("prometheus"))
const output = createMockOutput()
//#when
+14 -30
View File
@@ -183,46 +183,30 @@ describe("getAgentConfigKey", () => {
expect(getAgentConfigKey("Sisyphus-Junior")).toBe("sisyphus-junior")
})
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")
it("resolves atlas even when the UI ordering prefix is present", () => {
expect(getAgentConfigKey(getAgentListDisplayName("atlas"))).toBe("atlas")
})
})
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")
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")
})
it("matches getAgentDisplayName for unknown agents", () => {
it("keeps non-core agents unprefixed for list display", () => {
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 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")
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")
})
})
+16 -29
View File
@@ -26,23 +26,13 @@ export const AGENT_DISPLAY_NAMES: Record<string, string> = {
"council-member": "council-member",
}
/**
* Strip the legacy zero-width-space sort prefix from an agent name.
*
* v3.14.0 through v3.16.0 prefixed the four core agents (Sisyphus,
* Hephaestus, Prometheus, Atlas) with U+200B Zero Width Space characters
* so they would sort ahead of user agents in the Tab cycle. Some terminal
* emulators (Ghostty, certain Windows Terminal builds) render ZWSP as a
* visible box or extra space, breaking the status bar layout (#3259), and
* the prefixes also leaked through the plugin API and broke prompt_async
* consumers (#3238).
*
* The prefixes are no longer injected anywhere (#3242 removed all call
* sites and #3259 removed the constant table). This helper remains so
* existing user configs that still have the ZWSP baked into their
* `config.agent` keys from an older install continue to resolve
* correctly after upgrading.
*/
const AGENT_LIST_SORT_PREFIXES: Record<string, string> = {
sisyphus: "\u200B",
hephaestus: "\u200B\u200B",
prometheus: "\u200B\u200B\u200B",
atlas: "\u200B\u200B\u200B\u200B",
}
export function stripAgentListSortPrefix(agentName: string): string {
return agentName.replace(/^\u200B+/, "")
}
@@ -68,20 +58,17 @@ export function getAgentDisplayName(configKey: string): string {
}
/**
* @deprecated Use {@link getAgentDisplayName} directly.
*
* Historically this returned the display name with a ZWSP sort prefix
* prepended so core agents would sort ahead of user agents in the Tab
* cycle. The ZWSP prefixes caused visible rendering artifacts in some
* terminals (#3259) and leaked into the plugin API surface (#3238), so
* they were removed in #3242/#3259. This function is now a thin alias
* over {@link getAgentDisplayName} that exists only for external
* callers that may still import it. Sort ordering is now handled by
* the `order` field injection in `reorderAgentsByPriority()` plus the
* core-first insertion order in the same helper.
* @deprecated Do NOT use for config.agent keys or API-facing names.
* ZWSP prefixes leak into the /agent API response and break prompt_async consumers.
* Use getAgentDisplayName() instead. The `order` field injected by
* reorderAgentsByPriority() handles sort ordering without invisible characters.
* See: https://github.com/code-yeongyu/oh-my-openagent/issues/3238
*/
export function getAgentListDisplayName(configKey: string): string {
return getAgentDisplayName(configKey)
const displayName = getAgentDisplayName(configKey)
const prefix = AGENT_LIST_SORT_PREFIXES[configKey.toLowerCase()]
return prefix ? `${prefix}${displayName}` : displayName
}
const REVERSE_DISPLAY_NAMES: Record<string, string> = Object.fromEntries(