fix(athena): write council prompt to .sisyphus/tmp/, switch to allow-list permissions
Council members now use an allow-list (read, grep, glob, lsp_*, ast_grep_search) instead of a deny-list. Prompt file moved from /tmp/ to .sisyphus/tmp/ so no external_directory permission is needed. COUNCIL_MEMBER_PROMPT is included in the temp file for self-contained council member instructions.
This commit is contained in:
parent
1e0229226e
commit
92e9cbea5c
@ -35,7 +35,7 @@ Agent factories following `createXXXAgent(model) → AgentConfig` pattern. Each
|
|||||||
| Atlas | task, call_omo_agent |
|
| Atlas | task, call_omo_agent |
|
||||||
| Momus | write, edit, task |
|
| Momus | write, edit, task |
|
||||||
| Athena | write, edit, call_omo_agent |
|
| Athena | write, edit, call_omo_agent |
|
||||||
| Council-Member | write, edit, task, call_omo_agent, switch_agent, background_wait, prepare_council_prompt |
|
| Council-Member | ALL except read, grep, glob, lsp_*, ast_grep_search (allow-list) |
|
||||||
|
|
||||||
## STRUCTURE
|
## STRUCTURE
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import type { AgentConfig } from "@opencode-ai/sdk"
|
import type { AgentConfig } from "@opencode-ai/sdk"
|
||||||
import type { AgentMode } from "../types"
|
import type { AgentMode } from "../types"
|
||||||
import { createAgentToolRestrictions } from "../../shared/permission-compat"
|
import { createAgentToolAllowlist } from "../../shared"
|
||||||
import { applyModelThinkingConfig } from "./model-thinking-config"
|
import { applyModelThinkingConfig } from "./model-thinking-config"
|
||||||
|
|
||||||
const MODE: AgentMode = "subagent"
|
const MODE: AgentMode = "subagent"
|
||||||
|
|
||||||
const COUNCIL_MEMBER_PROMPT = `You are an independent code analyst in a multi-model analysis council. Your role is to provide thorough, evidence-based analysis.
|
export const COUNCIL_MEMBER_PROMPT = `You are an independent code analyst in a multi-model analysis council. Your role is to provide thorough, evidence-based analysis.
|
||||||
|
|
||||||
## Your Role
|
## Your Role
|
||||||
- You are one of several AI models analyzing the same question independently
|
- You are one of several AI models analyzing the same question independently
|
||||||
@ -25,14 +25,16 @@ const COUNCIL_MEMBER_PROMPT = `You are an independent code analyst in a multi-mo
|
|||||||
5. Be concise but thorough — quality over quantity`
|
5. Be concise but thorough — quality over quantity`
|
||||||
|
|
||||||
export function createCouncilMemberAgent(model: string): AgentConfig {
|
export function createCouncilMemberAgent(model: string): AgentConfig {
|
||||||
const restrictions = createAgentToolRestrictions([
|
// Allow-list: only read-only analysis tools. Everything else is denied via `*: deny`.
|
||||||
"write",
|
const restrictions = createAgentToolAllowlist([
|
||||||
"edit",
|
"read",
|
||||||
"task",
|
"grep",
|
||||||
"call_omo_agent",
|
"glob",
|
||||||
"switch_agent",
|
"lsp_goto_definition",
|
||||||
"background_wait",
|
"lsp_find_references",
|
||||||
"prepare_council_prompt",
|
"lsp_symbols",
|
||||||
|
"lsp_diagnostics",
|
||||||
|
"ast_grep_search",
|
||||||
])
|
])
|
||||||
|
|
||||||
const base = {
|
const base = {
|
||||||
|
|||||||
@ -139,7 +139,7 @@ export function createToolRegistry(args: {
|
|||||||
interactive_bash,
|
interactive_bash,
|
||||||
...taskToolsRecord,
|
...taskToolsRecord,
|
||||||
...hashlineToolsRecord,
|
...hashlineToolsRecord,
|
||||||
prepare_council_prompt: createPrepareCouncilPromptTool(),
|
prepare_council_prompt: createPrepareCouncilPromptTool(ctx.directory),
|
||||||
}
|
}
|
||||||
|
|
||||||
const filteredTools = filterDisabledTools(allTools, pluginConfig.disabled_tools)
|
const filteredTools = filterDisabledTools(allTools, pluginConfig.disabled_tools)
|
||||||
|
|||||||
@ -53,17 +53,21 @@ const AGENT_RESTRICTIONS: Record<string, Record<string, boolean>> = {
|
|||||||
|
|
||||||
// NOTE: Athena/council tool restrictions are also defined in:
|
// NOTE: Athena/council tool restrictions are also defined in:
|
||||||
// - src/agents/athena/agent.ts (AgentConfig permission format)
|
// - src/agents/athena/agent.ts (AgentConfig permission format)
|
||||||
// - src/agents/athena/council-member-agent.ts (AgentConfig permission format)
|
// - src/agents/athena/council-member-agent.ts (AgentConfig permission format — allow-list)
|
||||||
// - src/plugin-handlers/tool-config-handler.ts (allow/deny string format)
|
// - src/plugin-handlers/tool-config-handler.ts (allow/deny string format)
|
||||||
// Keep all three in sync when modifying.
|
// Keep all three in sync when modifying.
|
||||||
|
// Council members use an allow-list: only read-only analysis tools are permitted.
|
||||||
|
// Prompt file lives in .sisyphus/tmp/ (inside project) so no external_directory needed.
|
||||||
"council-member": {
|
"council-member": {
|
||||||
write: false,
|
"*": false,
|
||||||
edit: false,
|
read: true,
|
||||||
task: false,
|
grep: true,
|
||||||
call_omo_agent: false,
|
glob: true,
|
||||||
switch_agent: false,
|
lsp_goto_definition: true,
|
||||||
background_wait: false,
|
lsp_find_references: true,
|
||||||
prepare_council_prompt: false,
|
lsp_symbols: true,
|
||||||
|
lsp_diagnostics: true,
|
||||||
|
ast_grep_search: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
import { tool, type ToolDefinition } from "@opencode-ai/plugin"
|
import { tool, type ToolDefinition } from "@opencode-ai/plugin"
|
||||||
import { randomUUID } from "node:crypto"
|
import { randomUUID } from "node:crypto"
|
||||||
import { writeFile, unlink } from "node:fs/promises"
|
import { writeFile, unlink, mkdir } from "node:fs/promises"
|
||||||
import { join } from "node:path"
|
import { join } from "node:path"
|
||||||
import { tmpdir } from "node:os"
|
|
||||||
import { log } from "../../shared/logger"
|
import { log } from "../../shared/logger"
|
||||||
|
import { COUNCIL_MEMBER_PROMPT } from "../../agents/athena/council-member-agent"
|
||||||
|
|
||||||
const CLEANUP_DELAY_MS = 30 * 60 * 1000
|
const CLEANUP_DELAY_MS = 30 * 60 * 1000
|
||||||
|
const COUNCIL_TMP_DIR = ".sisyphus/tmp"
|
||||||
|
|
||||||
export function createPrepareCouncilPromptTool(): ToolDefinition {
|
export function createPrepareCouncilPromptTool(directory: string): ToolDefinition {
|
||||||
const description = `Save a council analysis prompt to a temp file so council members can read it.
|
const description = `Save a council analysis prompt to a temp file so council members can read it.
|
||||||
|
|
||||||
Athena-only tool. Saves the prompt once, then each council member task() call uses a short
|
Athena-only tool. Saves the prompt once, then each council member task() call uses a short
|
||||||
@ -26,10 +27,19 @@ Returns the file path to reference in subsequent task() calls.`
|
|||||||
return "Prompt cannot be empty."
|
return "Prompt cannot be empty."
|
||||||
}
|
}
|
||||||
|
|
||||||
const filename = `athena-council-${randomUUID().slice(0, 8)}.md`
|
const tmpDir = join(directory, COUNCIL_TMP_DIR)
|
||||||
const filePath = join(tmpdir(), filename)
|
await mkdir(tmpDir, { recursive: true })
|
||||||
|
|
||||||
await writeFile(filePath, args.prompt, "utf-8")
|
const filename = `athena-council-${randomUUID().slice(0, 8)}.md`
|
||||||
|
const filePath = join(tmpDir, filename)
|
||||||
|
|
||||||
|
const content = `${COUNCIL_MEMBER_PROMPT}
|
||||||
|
|
||||||
|
## Analysis Question
|
||||||
|
|
||||||
|
${args.prompt}`
|
||||||
|
|
||||||
|
await writeFile(filePath, content, "utf-8")
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
unlink(filePath).catch(() => {})
|
unlink(filePath).catch(() => {})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user