diff --git a/src/hooks/prometheus-md-only/constants.ts b/src/hooks/prometheus-md-only/constants.ts index 062a74149..b25db57f4 100644 --- a/src/hooks/prometheus-md-only/constants.ts +++ b/src/hooks/prometheus-md-only/constants.ts @@ -7,3 +7,24 @@ export const ALLOWED_EXTENSIONS = [".md"] export const ALLOWED_PATH_PREFIX = ".sisyphus/" export const BLOCKED_TOOLS = ["Write", "Edit", "write", "edit"] + +export const PLANNING_CONSULT_WARNING = ` + +--- + +[SYSTEM DIRECTIVE - READ-ONLY PLANNING CONSULTATION] + +You are being invoked by Prometheus (Planner), a READ-ONLY planning agent. + +**CRITICAL CONSTRAINTS:** +- DO NOT modify any files (no Write, Edit, or any file mutations) +- DO NOT execute commands that change system state +- DO NOT create, delete, or rename files +- ONLY provide analysis, recommendations, and information + +**YOUR ROLE**: Provide consultation, research, and analysis to assist with planning. +Return your findings and recommendations. The actual implementation will be handled separately after planning is complete. + +--- + +` diff --git a/src/hooks/prometheus-md-only/index.test.ts b/src/hooks/prometheus-md-only/index.test.ts index 526cc3f6c..2a61b477b 100644 --- a/src/hooks/prometheus-md-only/index.test.ts +++ b/src/hooks/prometheus-md-only/index.test.ts @@ -159,4 +159,109 @@ describe("prometheus-md-only", () => { hook["tool.execute.before"](input, output) ).resolves.toBeUndefined() }) + + test("should inject read-only warning when Prometheus calls sisyphus_task", async () => { + // #given + const hook = createPrometheusMdOnlyHook(createMockPluginInput()) + const input = { + tool: "sisyphus_task", + sessionID: "test-session", + callID: "call-1", + agent: "Prometheus (Planner)", + } + const output = { + args: { prompt: "Analyze this codebase" }, + } + + // #when + await hook["tool.execute.before"](input, output) + + // #then + expect(output.args.prompt).toContain("[SYSTEM DIRECTIVE - READ-ONLY PLANNING CONSULTATION]") + expect(output.args.prompt).toContain("DO NOT modify any files") + }) + + test("should inject read-only warning when Prometheus calls task", async () => { + // #given + const hook = createPrometheusMdOnlyHook(createMockPluginInput()) + const input = { + tool: "task", + sessionID: "test-session", + callID: "call-1", + agent: "Prometheus (Planner)", + } + const output = { + args: { prompt: "Research this library" }, + } + + // #when + await hook["tool.execute.before"](input, output) + + // #then + expect(output.args.prompt).toContain("[SYSTEM DIRECTIVE - READ-ONLY PLANNING CONSULTATION]") + }) + + test("should inject read-only warning when Prometheus calls call_omo_agent", async () => { + // #given + const hook = createPrometheusMdOnlyHook(createMockPluginInput()) + const input = { + tool: "call_omo_agent", + sessionID: "test-session", + callID: "call-1", + agent: "Prometheus (Planner)", + } + const output = { + args: { prompt: "Find implementation examples" }, + } + + // #when + await hook["tool.execute.before"](input, output) + + // #then + expect(output.args.prompt).toContain("[SYSTEM DIRECTIVE - READ-ONLY PLANNING CONSULTATION]") + }) + + test("should not inject warning for non-Prometheus agents calling sisyphus_task", async () => { + // #given + const hook = createPrometheusMdOnlyHook(createMockPluginInput()) + const input = { + tool: "sisyphus_task", + sessionID: "test-session", + callID: "call-1", + agent: "Sisyphus", + } + const originalPrompt = "Implement this feature" + const output = { + args: { prompt: originalPrompt }, + } + + // #when + await hook["tool.execute.before"](input, output) + + // #then + expect(output.args.prompt).toBe(originalPrompt) + expect(output.args.prompt).not.toContain("[SYSTEM DIRECTIVE - READ-ONLY PLANNING CONSULTATION]") + }) + + test("should not double-inject warning if already present", async () => { + // #given + const hook = createPrometheusMdOnlyHook(createMockPluginInput()) + const input = { + tool: "sisyphus_task", + sessionID: "test-session", + callID: "call-1", + agent: "Prometheus (Planner)", + } + const promptWithWarning = "Some prompt [SYSTEM DIRECTIVE - READ-ONLY PLANNING CONSULTATION] already here" + const output = { + args: { prompt: promptWithWarning }, + } + + // #when + await hook["tool.execute.before"](input, output) + + // #then + const occurrences = (output.args.prompt as string).split("[SYSTEM DIRECTIVE - READ-ONLY PLANNING CONSULTATION]").length - 1 + expect(occurrences).toBe(1) + }) }) diff --git a/src/hooks/prometheus-md-only/index.ts b/src/hooks/prometheus-md-only/index.ts index 52e7c83c4..2ff8c71bb 100644 --- a/src/hooks/prometheus-md-only/index.ts +++ b/src/hooks/prometheus-md-only/index.ts @@ -1,5 +1,5 @@ import type { PluginInput } from "@opencode-ai/plugin" -import { HOOK_NAME, PROMETHEUS_AGENTS, ALLOWED_EXTENSIONS, ALLOWED_PATH_PREFIX, BLOCKED_TOOLS } from "./constants" +import { HOOK_NAME, PROMETHEUS_AGENTS, ALLOWED_EXTENSIONS, ALLOWED_PATH_PREFIX, BLOCKED_TOOLS, PLANNING_CONSULT_WARNING } from "./constants" import { log } from "../../shared/logger" export * from "./constants" @@ -10,6 +10,8 @@ function isAllowedFile(filePath: string): boolean { return hasAllowedExtension && isInAllowedPath } +const TASK_TOOLS = ["sisyphus_task", "task", "call_omo_agent"] + export function createPrometheusMdOnlyHook(_ctx: PluginInput) { return { "tool.execute.before": async ( @@ -23,6 +25,21 @@ export function createPrometheusMdOnlyHook(_ctx: PluginInput) { } const toolName = input.tool + + // Inject read-only warning for task tools called by Prometheus + if (TASK_TOOLS.includes(toolName)) { + const prompt = output.args.prompt as string | undefined + if (prompt && !prompt.includes("[SYSTEM DIRECTIVE - READ-ONLY PLANNING CONSULTATION]")) { + output.args.prompt = prompt + PLANNING_CONSULT_WARNING + log(`[${HOOK_NAME}] Injected read-only planning warning to ${toolName}`, { + sessionID: input.sessionID, + tool: toolName, + agent: agentName, + }) + } + return + } + if (!BLOCKED_TOOLS.includes(toolName)) { return }