oh-my-opencode/src/plugin/tool-execute-before.ts
ismeth 4bc4b36e75 fix(athena): update council member guards for new agent key format
The hasPendingCouncilMembers guard now matches the 'Council: ' prefix from COUNCIL_MEMBER_KEY_PREFIX instead of the old task.agent === 'council-member' check.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-02-24 22:25:26 +09:00

142 lines
6.0 KiB
TypeScript

import type { PluginContext } from "./types"
import type { BackgroundManager } from "../features/background-agent"
import { getMainSessionID } from "../features/claude-code-session-state"
import { clearBoulderState } from "../features/boulder-state"
import { log } from "../shared"
import { resolveSessionAgent } from "./session-agent-resolver"
import { parseRalphLoopArguments } from "../hooks/ralph-loop/command-arguments"
import { getAgentConfigKey } from "../shared/agent-display-names"
import type { CreatedHooks } from "../create-hooks"
import { COUNCIL_MEMBER_KEY_PREFIX } from "../agents/builtin-agents/council-member-agents"
export function createToolExecuteBeforeHandler(args: {
ctx: PluginContext
hooks: CreatedHooks
backgroundManager?: Pick<BackgroundManager, "getTasksByParentSession">
}): (
input: { tool: string; sessionID: string; callID: string },
output: { args: Record<string, unknown> },
) => Promise<void> {
const { ctx, hooks, backgroundManager } = args
function hasPendingCouncilMembers(sessionID: string): boolean {
if (!backgroundManager) {
return false
}
const tasks = backgroundManager.getTasksByParentSession(sessionID)
return tasks.some((task) =>
(task.agent === "council-member" || task.agent.startsWith(COUNCIL_MEMBER_KEY_PREFIX)) &&
(task.status === "pending" || task.status === "running")
)
}
return async (input, output): Promise<void> => {
const toolNameLower = input.tool?.toLowerCase()
if (toolNameLower === "question" || toolNameLower === "askuserquestion" || toolNameLower === "ask_user_question" || toolNameLower === "switch_agent") {
const sessionAgent = await resolveSessionAgent(ctx.client, input.sessionID)
const sessionAgentKey = sessionAgent ? getAgentConfigKey(sessionAgent) : undefined
if (sessionAgentKey === "athena" && hasPendingCouncilMembers(input.sessionID)) {
throw new Error(
"Council members are still running. Wait for all launched members to finish and collect their outputs before asking next-step questions or switching agents."
)
}
}
await hooks.writeExistingFileGuard?.["tool.execute.before"]?.(input, output)
await hooks.questionLabelTruncator?.["tool.execute.before"]?.(input, output)
await hooks.claudeCodeHooks?.["tool.execute.before"]?.(input, output)
await hooks.nonInteractiveEnv?.["tool.execute.before"]?.(input, output)
await hooks.commentChecker?.["tool.execute.before"]?.(input, output)
await hooks.directoryAgentsInjector?.["tool.execute.before"]?.(input, output)
await hooks.directoryReadmeInjector?.["tool.execute.before"]?.(input, output)
await hooks.rulesInjector?.["tool.execute.before"]?.(input, output)
await hooks.tasksTodowriteDisabler?.["tool.execute.before"]?.(input, output)
await hooks.prometheusMdOnly?.["tool.execute.before"]?.(input, output)
await hooks.sisyphusJuniorNotepad?.["tool.execute.before"]?.(input, output)
await hooks.atlasHook?.["tool.execute.before"]?.(input, output)
const normalizedToolName = input.tool.toLowerCase()
if (
normalizedToolName === "question"
|| normalizedToolName === "ask_user_question"
|| normalizedToolName === "askuserquestion"
) {
const sessionID = input.sessionID || getMainSessionID()
await hooks.sessionNotification?.({
event: {
type: "tool.execute.before",
properties: {
sessionID,
tool: input.tool,
args: output.args,
},
},
})
}
if (input.tool === "task") {
const argsObject = output.args
const category = typeof argsObject.category === "string" ? argsObject.category : undefined
const subagentType = typeof argsObject.subagent_type === "string" ? argsObject.subagent_type : undefined
const sessionId = typeof argsObject.session_id === "string" ? argsObject.session_id : undefined
if (category) {
argsObject.subagent_type = "sisyphus-junior"
} else if (!subagentType && sessionId) {
const resolvedAgent = await resolveSessionAgent(ctx.client, sessionId)
argsObject.subagent_type = resolvedAgent ?? "continue"
}
}
if (hooks.ralphLoop && input.tool === "skill") {
const rawName = typeof output.args.name === "string" ? output.args.name : undefined
const command = rawName?.replace(/^\//, "").toLowerCase()
const sessionID = input.sessionID || getMainSessionID()
if (command === "ralph-loop" && sessionID) {
const rawArgs = rawName?.replace(/^\/?(ralph-loop)\s*/i, "") || ""
const parsedArguments = parseRalphLoopArguments(rawArgs)
hooks.ralphLoop.startLoop(sessionID, parsedArguments.prompt, {
maxIterations: parsedArguments.maxIterations,
completionPromise: parsedArguments.completionPromise,
strategy: parsedArguments.strategy,
})
} else if (command === "cancel-ralph" && sessionID) {
hooks.ralphLoop.cancelLoop(sessionID)
} else if (command === "ulw-loop" && sessionID) {
const rawArgs = rawName?.replace(/^\/?(ulw-loop)\s*/i, "") || ""
const parsedArguments = parseRalphLoopArguments(rawArgs)
hooks.ralphLoop.startLoop(sessionID, parsedArguments.prompt, {
ultrawork: true,
maxIterations: parsedArguments.maxIterations,
completionPromise: parsedArguments.completionPromise,
strategy: parsedArguments.strategy,
})
}
}
if (input.tool === "skill") {
const rawName = typeof output.args.name === "string" ? output.args.name : undefined
const command = rawName?.replace(/^\//, "").toLowerCase()
const sessionID = input.sessionID || getMainSessionID()
if (command === "stop-continuation" && sessionID) {
hooks.stopContinuationGuard?.stop(sessionID)
hooks.todoContinuationEnforcer?.cancelAllCountdowns()
hooks.ralphLoop?.cancelLoop(sessionID)
clearBoulderState(ctx.directory)
log("[stop-continuation] All continuation mechanisms stopped", {
sessionID,
})
}
}
}
}