diff --git a/src/features/claude-code-session-state/state.test.ts b/src/features/claude-code-session-state/state.test.ts index a0f1e0420..3f860b060 100644 --- a/src/features/claude-code-session-state/state.test.ts +++ b/src/features/claude-code-session-state/state.test.ts @@ -37,6 +37,18 @@ describe("claude-code-session-state", () => { expect(getSessionAgent(sessionID)).toBe(agent) }) + test("should strip zero-width ordering prefixes before storing agent for session", () => { + // given + const sessionID = "test-session-prefixed" + const agent = "\u200B\u200B\u200BPrometheus (Plan Builder)" + + // when + setSessionAgent(sessionID, agent) + + // then + expect(getSessionAgent(sessionID)).toBe("Prometheus (Plan Builder)") + }) + test("should NOT overwrite existing agent (first-write wins)", () => { // given const sessionID = "test-session-1" @@ -69,6 +81,18 @@ describe("claude-code-session-state", () => { // then expect(getSessionAgent(sessionID)).toBe("sisyphus") }) + + test("should strip zero-width ordering prefixes when overwriting existing agent", () => { + // given + const sessionID = "test-session-prefixed-update" + setSessionAgent(sessionID, "sisyphus") + + // when + updateSessionAgent(sessionID, "\u200B\u200BHephaestus (Deep Agent)") + + // then + expect(getSessionAgent(sessionID)).toBe("Hephaestus (Deep Agent)") + }) }) describe("clearSessionAgent", () => { diff --git a/src/features/claude-code-session-state/state.ts b/src/features/claude-code-session-state/state.ts index 929661d2c..f044b4ec6 100644 --- a/src/features/claude-code-session-state/state.ts +++ b/src/features/claude-code-session-state/state.ts @@ -21,6 +21,10 @@ function normalizeRegisteredAgentName(name: string): string { return name.replace(ZERO_WIDTH_CHARACTERS_REGEX, "").toLowerCase() } +function normalizeStoredAgentName(name: string): string { + return name.replace(ZERO_WIDTH_CHARACTERS_REGEX, "") +} + export function registerAgentName(name: string): void { const normalizedName = normalizeRegisteredAgentName(name) registeredAgentNames.add(normalizedName) @@ -48,12 +52,12 @@ const sessionAgentMap = new Map() export function setSessionAgent(sessionID: string, agent: string): void { if (!sessionAgentMap.has(sessionID)) { - sessionAgentMap.set(sessionID, agent) + sessionAgentMap.set(sessionID, normalizeStoredAgentName(agent)) } } export function updateSessionAgent(sessionID: string, agent: string): void { - sessionAgentMap.set(sessionID, agent) + sessionAgentMap.set(sessionID, normalizeStoredAgentName(agent)) } export function getSessionAgent(sessionID: string): string | undefined { diff --git a/src/plugin-handlers/agent-override-protection.ts b/src/plugin-handlers/agent-override-protection.ts index 1954b6529..476937b8c 100644 --- a/src/plugin-handlers/agent-override-protection.ts +++ b/src/plugin-handlers/agent-override-protection.ts @@ -1,7 +1,9 @@ const PARENTHETICAL_SUFFIX_PATTERN = /\s*(\([^)]*\)\s*)+$/u +const ZERO_WIDTH_CHARACTERS_PATTERN = /[\u200B\u200C\u200D\uFEFF]/g export function normalizeProtectedAgentName(agentName: string): string { return agentName + .replace(ZERO_WIDTH_CHARACTERS_PATTERN, "") .trim() .toLowerCase() .replace(PARENTHETICAL_SUFFIX_PATTERN, "") diff --git a/src/plugin/chat-message.test.ts b/src/plugin/chat-message.test.ts index 45ce7df99..11b4b6a48 100644 --- a/src/plugin/chat-message.test.ts +++ b/src/plugin/chat-message.test.ts @@ -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 } @@ -374,6 +375,31 @@ describe("createChatMessageHandler - TUI variant passthrough", () => { expect(getSessionModel("test-session")).toEqual({ providerID: "openai", modelID: "gpt-5.4" }) }) + 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" }) + const args = createMockHandlerArgs({ + shouldOverride: false, + pluginConfig: { + agents: { + prometheus: { model: "anthropic/claude-opus-4-6" }, + }, + }, + }) + const handler = createChatMessageHandler(args) + const input = createMockInput(getAgentListDisplayName("prometheus")) + const output = createMockOutput() + + //#when + await handler(input, output) + + //#then + expect(output.message["model"]).toBeUndefined() + expect(getSessionModel("test-session")).toEqual({ providerID: "openai", modelID: "gpt-5.4" }) + expect(getSessionAgent("test-session")).toBe("Prometheus (Plan Builder)") + }) + test("respects a mid-conversation model switch instead of reusing the previous stored model", async () => { //#given setMainSession("test-session") diff --git a/src/plugin/chat-message.ts b/src/plugin/chat-message.ts index 0d69ce3d4..ecce834b3 100644 --- a/src/plugin/chat-message.ts +++ b/src/plugin/chat-message.ts @@ -2,6 +2,7 @@ import type { OhMyOpenCodeConfig } from "../config" import type { PluginContext } from "./types" import { hasConnectedProvidersCache } from "../shared" +import { getAgentConfigKey } from "../shared/agent-display-names" import { getSessionModel, setSessionModel } from "../shared/session-model-state" import { getMainSessionID, setSessionAgent, subagentSessions } from "../features/claude-code-session-state" import { applyUltraworkModelOverrideOnMessage } from "./ultrawork-model-override" @@ -46,11 +47,12 @@ function hasExplicitAgentModelOverride( pluginConfig: OhMyOpenCodeConfig ): boolean { const configuredAgents = pluginConfig.agents - if (!agent || !configuredAgents || !(agent in configuredAgents)) { + const normalizedAgent = typeof agent === "string" ? getAgentConfigKey(agent) : undefined + if (!normalizedAgent || !configuredAgents || !(normalizedAgent in configuredAgents)) { return false } - const configuredAgent = configuredAgents[agent as keyof typeof configuredAgents] + const configuredAgent = configuredAgents[normalizedAgent as keyof typeof configuredAgents] const configuredModel = configuredAgent?.model return typeof configuredModel === "string" && configuredModel.trim().length > 0 }