oh-my-opencode/src/plugin/tool-execute-before.ts
YeonGyu-Kim b58f3edf6d refactor: remove redundant subagent-question-blocker hook
Replace PreToolUse hook-based question tool blocking with the existing
tools parameter approach (tools: { question: false }) which physically
removes the tool from the LLM's toolset before inference.

The hook was redundant because every session.prompt() call already passes
question: false via the tools parameter. OpenCode converts this to a
PermissionNext deny rule and deletes the tool from the toolset, preventing
the LLM from even seeing it. The hook only fired after the LLM already
called the tool, wasting tokens.

Changes:
- Remove subagent-question-blocker hook invocation from PreToolUse chain
- Remove hook registration from create-session-hooks.ts
- Delete src/hooks/subagent-question-blocker/ directory (dead code)
- Remove hook from HookNameSchema and barrel export
- Fix sync-executor.ts missing question: false in tools parameter
- Add regression tests for both the removal and the tools parameter
2026-02-13 14:55:46 +09:00

105 lines
4.6 KiB
TypeScript

import type { PluginContext } from "./types"
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 type { CreatedHooks } from "../create-hooks"
export function createToolExecuteBeforeHandler(args: {
ctx: PluginContext
hooks: CreatedHooks
}): (
input: { tool: string; sessionID: string; callID: string },
output: { args: Record<string, unknown> },
) => Promise<void> {
const { ctx, hooks } = args
return async (input, output): Promise<void> => {
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)
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 === "slashcommand") {
const rawCommand = typeof output.args.command === "string" ? output.args.command : undefined
const command = rawCommand?.replace(/^\//, "").toLowerCase()
const sessionID = input.sessionID || getMainSessionID()
if (command === "ralph-loop" && sessionID) {
const rawArgs = rawCommand?.replace(/^\/?(ralph-loop)\s*/i, "") || ""
const taskMatch = rawArgs.match(/^["'](.+?)["']/)
const prompt =
taskMatch?.[1] ||
rawArgs.split(/\s+--/)[0]?.trim() ||
"Complete the task as instructed"
const maxIterMatch = rawArgs.match(/--max-iterations=(\d+)/i)
const promiseMatch = rawArgs.match(/--completion-promise=["']?([^"'\s]+)["']?/i)
hooks.ralphLoop.startLoop(sessionID, prompt, {
maxIterations: maxIterMatch ? parseInt(maxIterMatch[1], 10) : undefined,
completionPromise: promiseMatch?.[1],
})
} else if (command === "cancel-ralph" && sessionID) {
hooks.ralphLoop.cancelLoop(sessionID)
} else if (command === "ulw-loop" && sessionID) {
const rawArgs = rawCommand?.replace(/^\/?(ulw-loop)\s*/i, "") || ""
const taskMatch = rawArgs.match(/^["'](.+?)["']/)
const prompt =
taskMatch?.[1] ||
rawArgs.split(/\s+--/)[0]?.trim() ||
"Complete the task as instructed"
const maxIterMatch = rawArgs.match(/--max-iterations=(\d+)/i)
const promiseMatch = rawArgs.match(/--completion-promise=["']?([^"'\s]+)["']?/i)
hooks.ralphLoop.startLoop(sessionID, prompt, {
ultrawork: true,
maxIterations: maxIterMatch ? parseInt(maxIterMatch[1], 10) : undefined,
completionPromise: promiseMatch?.[1],
})
}
}
if (input.tool === "slashcommand") {
const rawCommand = typeof output.args.command === "string" ? output.args.command : undefined
const command = rawCommand?.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,
})
}
}
}
}