refactor(athena): strip audit bias from council member prompt + add intent addendums + write boundary hook + relax restrictions
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
import { ATHENA_AGENT } from "./constants"
|
||||
|
||||
export function isAthenaAgent(agentName: string | undefined): boolean {
|
||||
return agentName?.toLowerCase().includes(ATHENA_AGENT) ?? false
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export const HOOK_NAME = "athena-sisyphus-only"
|
||||
|
||||
export const ATHENA_AGENT = "athena"
|
||||
|
||||
export const BLOCKED_TOOLS = ["Write", "Edit", "write", "edit"]
|
||||
@@ -0,0 +1,49 @@
|
||||
import type { PluginInput } from "@opencode-ai/plugin"
|
||||
import { HOOK_NAME, BLOCKED_TOOLS } from "./constants"
|
||||
import { log } from "../../shared/logger"
|
||||
import { getAgentFromSession } from "../prometheus-md-only/agent-resolution"
|
||||
import { isAthenaAgent } from "./agent-matcher"
|
||||
import { isAllowedPath } from "./path-policy"
|
||||
|
||||
export function createAthenaSisyphusOnlyHook(ctx: PluginInput) {
|
||||
return {
|
||||
"tool.execute.before": async (
|
||||
input: { tool: string; sessionID: string; callID: string },
|
||||
output: { args: Record<string, unknown>; message?: string }
|
||||
): Promise<void> => {
|
||||
if (!BLOCKED_TOOLS.includes(input.tool)) {
|
||||
return
|
||||
}
|
||||
|
||||
const agentName = await getAgentFromSession(input.sessionID, ctx.directory, ctx.client)
|
||||
|
||||
if (!isAthenaAgent(agentName)) {
|
||||
return
|
||||
}
|
||||
|
||||
const filePath = (output.args.filePath ?? output.args.path ?? output.args.file) as string | undefined
|
||||
if (!filePath) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!isAllowedPath(filePath, ctx.directory)) {
|
||||
log(`[${HOOK_NAME}] Blocked: Athena attempted write outside .sisyphus/`, {
|
||||
sessionID: input.sessionID,
|
||||
tool: input.tool,
|
||||
filePath,
|
||||
agent: agentName,
|
||||
})
|
||||
throw new Error(
|
||||
`[${HOOK_NAME}] Athena can only write/edit files inside .sisyphus/ directory. Attempted to modify: ${filePath}`
|
||||
)
|
||||
}
|
||||
|
||||
log(`[${HOOK_NAME}] Allowed: .sisyphus/ write permitted`, {
|
||||
sessionID: input.sessionID,
|
||||
tool: input.tool,
|
||||
filePath,
|
||||
agent: agentName,
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./constants"
|
||||
export { createAthenaSisyphusOnlyHook } from "./hook"
|
||||
@@ -0,0 +1,30 @@
|
||||
import { relative, resolve, isAbsolute } from "node:path"
|
||||
|
||||
/**
|
||||
* Cross-platform path validator for Athena file writes.
|
||||
* Uses path.resolve/relative instead of string matching to handle:
|
||||
* - Windows backslashes (e.g., .sisyphus\\notepads\\x.yaml)
|
||||
* - Mixed separators (e.g., .sisyphus\\plans/x.md)
|
||||
* - Case-insensitive directory matching
|
||||
* - Workspace confinement (blocks paths outside root or via traversal)
|
||||
* - No extension restriction: any file type is allowed inside .sisyphus/
|
||||
*/
|
||||
export function isAllowedPath(filePath: string, workspaceRoot: string): boolean {
|
||||
// 1. Resolve to absolute path
|
||||
const resolved = resolve(workspaceRoot, filePath)
|
||||
|
||||
// 2. Get relative path from workspace root
|
||||
const rel = relative(workspaceRoot, resolved)
|
||||
|
||||
// 3. Reject if escapes root (starts with ".." or is absolute)
|
||||
if (rel.startsWith("..") || isAbsolute(rel)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 4. Check if .sisyphus/ or .sisyphus\ exists anywhere in the path (case-insensitive)
|
||||
if (!/\.sisyphus[/\\]/i.test(rel)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user