diff --git a/src/hooks/keyword-detector/hook.ts b/src/hooks/keyword-detector/hook.ts index a6cb23d9..7da47ca1 100644 --- a/src/hooks/keyword-detector/hook.ts +++ b/src/hooks/keyword-detector/hook.ts @@ -11,6 +11,7 @@ import { getSessionAgent, subagentSessions, } from "../../features/claude-code-session-state" +import { getAgentConfigKey } from "../../shared/agent-display-names" import type { ContextCollector } from "../../features/context-injector" export function createKeywordDetectorHook(ctx: PluginInput, _collector?: ContextCollector) { @@ -48,7 +49,9 @@ export function createKeywordDetectorHook(ctx: PluginInput, _collector?: Context // Athena is a council orchestrator — skip all keyword injections. // search/analyze modes tell the agent to use explore agents and grep directly, // which conflicts with Athena's job of calling athena_council for council fan-out. - if (currentAgent?.toLowerCase() === "athena") { + // Use getAgentConfigKey to handle display name remapping ("Athena (Council)" → "athena"). + const agentConfigKey = currentAgent ? getAgentConfigKey(currentAgent) : undefined + if (agentConfigKey === "athena") { log(`[keyword-detector] Skipping all keywords for Athena (council orchestrator)`, { sessionID: input.sessionID, skippedTypes: detectedKeywords.map((k) => k.type), diff --git a/src/hooks/keyword-detector/index.test.ts b/src/hooks/keyword-detector/index.test.ts index 182b6afa..76b7d313 100644 --- a/src/hooks/keyword-detector/index.test.ts +++ b/src/hooks/keyword-detector/index.test.ts @@ -745,4 +745,54 @@ describe("keyword-detector agent-specific ultrawork messages", () => { expect(textPart!.text).toBe("ultrawork plan this") expect(textPart!.text).not.toContain("YOU ARE A PLANNER, NOT AN IMPLEMENTER") }) + + test("should skip ALL keyword injections for Athena with display name", async () => { + // given - session agent is stored as display name "Athena (Council)" after remapping + const collector = new ContextCollector() + const hook = createKeywordDetectorHook(createMockPluginInput(), collector) + const sessionID = "athena-display-name-session" + updateSessionAgent(sessionID, "Athena (Council)") + + const output = { + message: {} as Record, + parts: [{ type: "text", text: "ultrawork search for bugs in the code" }], + } + + // when - keyword detection runs with Athena display name in session state + await hook["chat.message"]({ sessionID }, output) + + // then - ALL keywords should be skipped (no injection) + const textPart = output.parts.find(p => p.type === "text") + expect(textPart!.text).toBe("ultrawork search for bugs in the code") + expect(textPart!.text).not.toContain("[search-mode]") + expect(textPart!.text).not.toContain("MAXIMIZE SEARCH EFFORT") + + const skipLog = logCalls.find(c => c.msg.includes("Skipping all keywords for Athena")) + expect(skipLog).toBeDefined() + + clearSessionAgent(sessionID) + }) + + test("should skip ALL keyword injections for Athena with lowercase config key", async () => { + // given - session agent is stored as lowercase "athena" + const collector = new ContextCollector() + const hook = createKeywordDetectorHook(createMockPluginInput(), collector) + const sessionID = "athena-lowercase-session" + + const output = { + message: {} as Record, + parts: [{ type: "text", text: "search for the implementation" }], + } + + // when - keyword detection runs with athena as input.agent + await hook["chat.message"]({ sessionID, agent: "athena" }, output) + + // then - ALL keywords should be skipped + const textPart = output.parts.find(p => p.type === "text") + expect(textPart!.text).toBe("search for the implementation") + expect(textPart!.text).not.toContain("[search-mode]") + + const skipLog = logCalls.find(c => c.msg.includes("Skipping all keywords for Athena")) + expect(skipLog).toBeDefined() + }) })