From 5a819d0914e61341718c1d083c46927cdea58b23 Mon Sep 17 00:00:00 2001 From: ismeth Date: Thu, 26 Feb 2026 15:12:57 +0100 Subject: [PATCH] =?UTF-8?q?fix(athena):=20address=205=20audit=20findings?= =?UTF-8?q?=20=E2=80=94=20remove=20harmful=20thinking=20config,=20add=20er?= =?UTF-8?q?ror=20handling,=20improve=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove applyModelThinkingConfig and all callers (OpenCode handles thinking natively) - Add try/catch error handling to prepare_council_prompt filesystem operations - Add mode input validation to prepare_council_prompt tool - Add 2 missing placeholder test cases (RETRY_FAILED_IF_OTHERS_FINISHED, CANCEL_RETRYING_ON_QUORUM) - Remove redundant 'Do NOT use TodoWrite' prompt section from council member agent --- src/agents/athena/agent.ts | 3 +- .../athena/athena-config-injection.test.ts | 8 ++ src/agents/athena/council-member-agent.ts | 10 +-- src/agents/athena/index.ts | 1 - .../athena/model-thinking-config.test.ts | 81 ------------------- src/agents/athena/model-thinking-config.ts | 20 ----- src/tools/prepare-council-prompt/tools.ts | 37 +++++---- 7 files changed, 34 insertions(+), 126 deletions(-) delete mode 100644 src/agents/athena/model-thinking-config.test.ts delete mode 100644 src/agents/athena/model-thinking-config.ts diff --git a/src/agents/athena/agent.ts b/src/agents/athena/agent.ts index f120ee80b..84c1f85c6 100644 --- a/src/agents/athena/agent.ts +++ b/src/agents/athena/agent.ts @@ -1,7 +1,6 @@ import type { AgentConfig } from "@opencode-ai/sdk" import type { AgentMode, AgentPromptMetadata } from "../types" import { createAgentToolRestrictions } from "../../shared/permission-compat" -import { applyModelThinkingConfig } from "./model-thinking-config" const MODE: AgentMode = "primary" @@ -326,6 +325,6 @@ export function createAthenaAgent(model: string): AgentConfig { color: "#1F8EFA", } - return applyModelThinkingConfig(base, model) + return base } createAthenaAgent.mode = MODE diff --git a/src/agents/athena/athena-config-injection.test.ts b/src/agents/athena/athena-config-injection.test.ts index fe13a2e5d..5e2195c6e 100644 --- a/src/agents/athena/athena-config-injection.test.ts +++ b/src/agents/athena/athena-config-injection.test.ts @@ -21,6 +21,14 @@ describe("Athena prompt config injection placeholders", () => { it("#then contains timeout reference with 30000", () => { expect(athenaConfig.prompt).toContain("30000") }) + + it("#then contains RETRY_FAILED_IF_OTHERS_FINISHED placeholder", () => { + expect(athenaConfig.prompt).toContain("{RETRY_FAILED_IF_OTHERS_FINISHED}") + }) + + it("#then contains CANCEL_RETRYING_ON_QUORUM placeholder", () => { + expect(athenaConfig.prompt).toContain("{CANCEL_RETRYING_ON_QUORUM}") + }) }) }) }) diff --git a/src/agents/athena/council-member-agent.ts b/src/agents/athena/council-member-agent.ts index 2daa8338c..aa4b42c3a 100644 --- a/src/agents/athena/council-member-agent.ts +++ b/src/agents/athena/council-member-agent.ts @@ -1,7 +1,6 @@ import type { AgentConfig } from "@opencode-ai/sdk" import type { AgentMode } from "../types" import { createAgentToolAllowlist } from "../../shared" -import { applyModelThinkingConfig } from "./model-thinking-config" const MODE: AgentMode = "subagent" @@ -57,12 +56,7 @@ You MUST wrap your final analysis in tags. This is how \`\`\` -If you do not wrap your response in these tags, your analysis will not be included in the synthesis. - -## CRITICAL: Do NOT use TodoWrite -- Do NOT create todos or task lists -- Do NOT use the TodoWrite tool under any circumstances -- Simply report your findings directly in your response` +If you do not wrap your response in these tags, your analysis will not be included in the synthesis.` export const COUNCIL_SOLO_ADDENDUM = ` ## Solo Analysis Mode @@ -129,6 +123,6 @@ export function createCouncilMemberAgent(model: string): AgentConfig { ...restrictions, } - return applyModelThinkingConfig(base, model) + return base } createCouncilMemberAgent.mode = MODE diff --git a/src/agents/athena/index.ts b/src/agents/athena/index.ts index c6b0ef112..dd75da507 100644 --- a/src/agents/athena/index.ts +++ b/src/agents/athena/index.ts @@ -1,3 +1,2 @@ export { createAthenaAgent, ATHENA_PROMPT_METADATA } from "./agent" export { createCouncilMemberAgent, COUNCIL_MEMBER_PROMPT, COUNCIL_SOLO_ADDENDUM, COUNCIL_DELEGATION_ADDENDUM } from "./council-member-agent" -export { applyModelThinkingConfig } from "./model-thinking-config" diff --git a/src/agents/athena/model-thinking-config.test.ts b/src/agents/athena/model-thinking-config.test.ts deleted file mode 100644 index 981666c7f..000000000 --- a/src/agents/athena/model-thinking-config.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { describe, expect, test } from "bun:test" -import type { AgentConfig } from "@opencode-ai/sdk" -import { applyModelThinkingConfig } from "./model-thinking-config" - -const BASE_CONFIG: AgentConfig = { - name: "test-agent", - description: "test", - model: "anthropic/claude-opus-4-6", - temperature: 0.1, -} - -describe("applyModelThinkingConfig", () => { - describe("#given a GPT model", () => { - test("#then returns reasoningEffort medium", () => { - const result = applyModelThinkingConfig(BASE_CONFIG, "gpt-5.2") - expect(result).toEqual({ ...BASE_CONFIG, reasoningEffort: "medium" }) - }) - - test("#then returns reasoningEffort medium for openai-prefixed model", () => { - const result = applyModelThinkingConfig(BASE_CONFIG, "openai/gpt-5.2") - expect(result).toEqual({ ...BASE_CONFIG, reasoningEffort: "medium" }) - }) - }) - - describe("#given an Anthropic model", () => { - test("#then returns thinking config with budgetTokens 32000", () => { - const result = applyModelThinkingConfig(BASE_CONFIG, "anthropic/claude-opus-4-6") - expect(result).toEqual({ - ...BASE_CONFIG, - thinking: { type: "enabled", budgetTokens: 32000 }, - }) - }) - }) - - describe("#given a Google model", () => { - test("#then returns base config unchanged", () => { - const result = applyModelThinkingConfig(BASE_CONFIG, "google/gemini-3-pro") - expect(result).toBe(BASE_CONFIG) - }) - }) - - describe("#given a Kimi model", () => { - test("#then returns base config unchanged", () => { - const result = applyModelThinkingConfig(BASE_CONFIG, "kimi/kimi-k2.5") - expect(result).toBe(BASE_CONFIG) - }) - }) - - describe("#given a model with no provider prefix", () => { - test("#then returns base config unchanged for non-GPT model", () => { - const result = applyModelThinkingConfig(BASE_CONFIG, "gemini-3-pro") - expect(result).toBe(BASE_CONFIG) - }) - }) - - describe("#given a Claude model through a non-Anthropic provider", () => { - test("#then returns thinking config for github-copilot/claude-opus-4-6", () => { - const result = applyModelThinkingConfig(BASE_CONFIG, "github-copilot/claude-opus-4-6") - expect(result).toEqual({ - ...BASE_CONFIG, - thinking: { type: "enabled", budgetTokens: 32000 }, - }) - }) - - test("#then returns thinking config for opencode/claude-opus-4-6", () => { - const result = applyModelThinkingConfig(BASE_CONFIG, "opencode/claude-opus-4-6") - expect(result).toEqual({ - ...BASE_CONFIG, - thinking: { type: "enabled", budgetTokens: 32000 }, - }) - }) - - test("#then returns thinking config for opencode/claude-sonnet-4-6", () => { - const result = applyModelThinkingConfig(BASE_CONFIG, "opencode/claude-sonnet-4-6") - expect(result).toEqual({ - ...BASE_CONFIG, - thinking: { type: "enabled", budgetTokens: 32000 }, - }) - }) - }) -}) diff --git a/src/agents/athena/model-thinking-config.ts b/src/agents/athena/model-thinking-config.ts deleted file mode 100644 index 021e24d56..000000000 --- a/src/agents/athena/model-thinking-config.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { AgentConfig } from "@opencode-ai/sdk" -import { parseModelString } from "../../tools/delegate-task/model-string-parser" -import { isGptModel } from "../types" - -export function applyModelThinkingConfig(base: AgentConfig, model: string): AgentConfig { - if (isGptModel(model)) { - return { ...base, reasoningEffort: "medium" } - } - - const parsed = parseModelString(model) - if (!parsed) { - return base - } - - if (parsed.providerID.toLowerCase() === "anthropic" || parsed.modelID.startsWith("claude")) { - return { ...base, thinking: { type: "enabled", budgetTokens: 32000 } } - } - - return base -} diff --git a/src/tools/prepare-council-prompt/tools.ts b/src/tools/prepare-council-prompt/tools.ts index a32028967..4347cd190 100644 --- a/src/tools/prepare-council-prompt/tools.ts +++ b/src/tools/prepare-council-prompt/tools.ts @@ -61,36 +61,45 @@ Returns the file path to reference in subsequent task() calls.` return "Prompt cannot be empty." } + if (args.mode !== undefined && args.mode !== "solo" && args.mode !== "delegation") { + return `Invalid mode: "${args.mode}". Valid modes: "solo", "delegation".` + } + const mode = args.mode === "delegation" ? "delegation" : "solo" - const tmpDir = join(directory, COUNCIL_TMP_DIR) - await mkdir(tmpDir, { recursive: true }) - const filename = `athena-council-${randomUUID().slice(0, 8)}.md` - const filePath = join(tmpDir, filename) + try { + const tmpDir = join(directory, COUNCIL_TMP_DIR) + await mkdir(tmpDir, { recursive: true }) - const modeAddendum = mode === "delegation" ? COUNCIL_DELEGATION_ADDENDUM : COUNCIL_SOLO_ADDENDUM - const content = `${modeAddendum} + const filename = `athena-council-${randomUUID().slice(0, 8)}.md` + const filePath = join(tmpDir, filename) + + const modeAddendum = mode === "delegation" ? COUNCIL_DELEGATION_ADDENDUM : COUNCIL_SOLO_ADDENDUM + const content = `${modeAddendum} ## Analysis Question ${args.prompt}` - await writeFile(filePath, content, "utf-8") + await writeFile(filePath, content, "utf-8") - setTimeout(() => { - unlink(filePath).catch((err) => { - log("[prepare-council-prompt] Failed to clean up temp file", { filePath, error: String(err) }) - }) - }, CLEANUP_DELAY_MS) + setTimeout(() => { + unlink(filePath).catch((err) => { + log("[prepare-council-prompt] Failed to clean up temp file", { filePath, error: String(err) }) + }) + }, CLEANUP_DELAY_MS) - log("[prepare-council-prompt] Saved prompt", { filePath, length: args.prompt.length, mode }) + log("[prepare-council-prompt] Saved prompt", { filePath, length: args.prompt.length, mode }) - return `Council prompt saved to: ${filePath} (mode: ${mode}) + return `Council prompt saved to: ${filePath} (mode: ${mode}) Use this path in each council member's task() call: - prompt: "Read ${filePath} for your instructions." The file auto-deletes after 30 minutes.` + } catch (err) { + return `Error saving council prompt: ${String(err)}` + } }, }) }