From b8f3e5434f957aa520ecdb788b9387722416e8c8 Mon Sep 17 00:00:00 2001 From: ismeth Date: Tue, 24 Feb 2026 15:12:05 +0100 Subject: [PATCH] fix(athena): remove duplicate council prompt, add temp file cleanup, fix code quality - Remove COUNCIL_MEMBER_PROMPT from temp file content since it's already provided as the council member's system prompt via agent config, saving ~350+ tokens per council member per invocation - Add startup cleanup for stale athena-council-*.md files older than 30 minutes in .sisyphus/tmp/ to handle cases where process exits before setTimeout cleanup fires - Remove redundant intermediate permission object spread in createAthenaAgent - Fix test style: it() -> test(), add #given/#then prefixes per conventions --- src/agents/athena/agent.ts | 5 +-- .../athena/model-thinking-config.test.ts | 32 ++++++++--------- src/tools/prepare-council-prompt/tools.ts | 36 ++++++++++++++++--- 3 files changed, 49 insertions(+), 24 deletions(-) diff --git a/src/agents/athena/agent.ts b/src/agents/athena/agent.ts index c234e47a7..d0bb1b9b2 100644 --- a/src/agents/athena/agent.ts +++ b/src/agents/athena/agent.ts @@ -236,9 +236,6 @@ export function createAthenaAgent(model: string): AgentConfig { const restrictions = createAgentToolRestrictions(["write", "edit", "call_omo_agent"]) // question permission is set by tool-config-handler.ts based on CLI mode (allow/deny) - const permission = { - ...restrictions.permission, - } const base = { description: @@ -246,7 +243,7 @@ export function createAthenaAgent(model: string): AgentConfig { mode: MODE, model, temperature: 0.1, - permission, + permission: restrictions.permission, prompt: ATHENA_SYSTEM_PROMPT, color: "#1F8EFA", } diff --git a/src/agents/athena/model-thinking-config.test.ts b/src/agents/athena/model-thinking-config.test.ts index 17f054e26..981666c7f 100644 --- a/src/agents/athena/model-thinking-config.test.ts +++ b/src/agents/athena/model-thinking-config.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "bun:test" +import { describe, expect, test } from "bun:test" import type { AgentConfig } from "@opencode-ai/sdk" import { applyModelThinkingConfig } from "./model-thinking-config" @@ -10,20 +10,20 @@ const BASE_CONFIG: AgentConfig = { } describe("applyModelThinkingConfig", () => { - describe("given a GPT model", () => { - it("returns reasoningEffort medium", () => { + 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" }) }) - it("returns reasoningEffort medium for openai-prefixed model", () => { + 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", () => { - it("returns thinking config with budgetTokens 32000", () => { + 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, @@ -32,29 +32,29 @@ describe("applyModelThinkingConfig", () => { }) }) - describe("given a Google model", () => { - it("returns base config unchanged", () => { + 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", () => { - it("returns base config unchanged", () => { + 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", () => { - it("returns base config unchanged for non-GPT model", () => { + 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", () => { - it("returns thinking config for github-copilot/claude-opus-4-6", () => { + 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, @@ -62,7 +62,7 @@ describe("applyModelThinkingConfig", () => { }) }) - it("returns thinking config for opencode/claude-opus-4-6", () => { + 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, @@ -70,7 +70,7 @@ describe("applyModelThinkingConfig", () => { }) }) - it("returns thinking config for opencode/claude-sonnet-4-6", () => { + 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, diff --git a/src/tools/prepare-council-prompt/tools.ts b/src/tools/prepare-council-prompt/tools.ts index d2b32c59a..a32028967 100644 --- a/src/tools/prepare-council-prompt/tools.ts +++ b/src/tools/prepare-council-prompt/tools.ts @@ -1,13 +1,38 @@ import { tool, type ToolDefinition } from "@opencode-ai/plugin" import { randomUUID } from "node:crypto" -import { writeFile, unlink, mkdir } from "node:fs/promises" +import { writeFile, unlink, mkdir, readdir, stat } from "node:fs/promises" import { join } from "node:path" import { log } from "../../shared/logger" -import { COUNCIL_MEMBER_PROMPT, COUNCIL_SOLO_ADDENDUM, COUNCIL_DELEGATION_ADDENDUM } from "../../agents/athena" +import { COUNCIL_SOLO_ADDENDUM, COUNCIL_DELEGATION_ADDENDUM } from "../../agents/athena" const CLEANUP_DELAY_MS = 30 * 60 * 1000 const COUNCIL_TMP_DIR = ".sisyphus/tmp" +const COUNCIL_FILE_PREFIX = "athena-council-" + +async function cleanupStaleTempFiles(directory: string): Promise { + const tmpDir = join(directory, COUNCIL_TMP_DIR) + try { + const files = await readdir(tmpDir) + const now = Date.now() + for (const file of files) { + if (!file.startsWith(COUNCIL_FILE_PREFIX) || !file.endsWith(".md")) continue + const filePath = join(tmpDir, file) + try { + const fileStat = await stat(filePath) + if (now - fileStat.mtimeMs > CLEANUP_DELAY_MS) { + await unlink(filePath) + log("[prepare-council-prompt] Cleaned up stale temp file", { filePath }) + } + } catch { + // File may have been deleted between readdir and stat + } + } + } catch { + // Directory may not exist yet — nothing to clean + } +} + export function createPrepareCouncilPromptTool(directory: string): ToolDefinition { const description = `Save a council analysis prompt to a temp file so council members can read it. @@ -21,6 +46,10 @@ The "mode" parameter controls whether council members can delegate exploration t Returns the file path to reference in subsequent task() calls.` + cleanupStaleTempFiles(directory).catch((err) => { + log("[prepare-council-prompt] Startup cleanup failed", { error: String(err) }) + }) + return tool({ description, args: { @@ -40,8 +69,7 @@ Returns the file path to reference in subsequent task() calls.` const filePath = join(tmpDir, filename) const modeAddendum = mode === "delegation" ? COUNCIL_DELEGATION_ADDENDUM : COUNCIL_SOLO_ADDENDUM - const content = `${COUNCIL_MEMBER_PROMPT} -${modeAddendum} + const content = `${modeAddendum} ## Analysis Question