fix(athena): address 5 audit findings — remove harmful thinking config, add error handling, improve tests

- 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
This commit is contained in:
ismeth
2026-02-26 15:12:57 +01:00
committed by YeonGyu-Kim
parent 4cd27f620e
commit 5a819d0914
7 changed files with 34 additions and 126 deletions
+1 -2
View File
@@ -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
@@ -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}")
})
})
})
})
+2 -8
View File
@@ -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 <COUNCIL_MEMBER_RESPONSE> tags. This is how
</COUNCIL_MEMBER_RESPONSE>
\`\`\`
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
-1
View File
@@ -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"
@@ -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 },
})
})
})
})
@@ -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
}
+23 -14
View File
@@ -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)}`
}
},
})
}