Files
oh-my-openagent/src/plugin/hooks/create-session-hooks.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

178 lines
6.8 KiB
TypeScript

import type { OhMyOpenCodeConfig, HookName } from "../../config"
import type { PluginContext } from "../types"
import {
createContextWindowMonitorHook,
createSessionRecoveryHook,
createSessionNotification,
createThinkModeHook,
createAnthropicContextWindowLimitRecoveryHook,
createAutoUpdateCheckerHook,
createAgentUsageReminderHook,
createNonInteractiveEnvHook,
createInteractiveBashSessionHook,
createRalphLoopHook,
createEditErrorRecoveryHook,
createDelegateTaskRetryHook,
createTaskResumeInfoHook,
createStartWorkHook,
createPrometheusMdOnlyHook,
createSisyphusJuniorNotepadHook,
createQuestionLabelTruncatorHook,
createPreemptiveCompactionHook,
} from "../../hooks"
import { createAnthropicEffortHook } from "../../hooks/anthropic-effort"
import {
detectExternalNotificationPlugin,
getNotificationConflictWarning,
log,
} from "../../shared"
import { safeCreateHook } from "../../shared/safe-create-hook"
import { sessionExists } from "../../tools"
export type SessionHooks = {
contextWindowMonitor: ReturnType<typeof createContextWindowMonitorHook> | null
preemptiveCompaction: ReturnType<typeof createPreemptiveCompactionHook> | null
sessionRecovery: ReturnType<typeof createSessionRecoveryHook> | null
sessionNotification: ReturnType<typeof createSessionNotification> | null
thinkMode: ReturnType<typeof createThinkModeHook> | null
anthropicContextWindowLimitRecovery: ReturnType<typeof createAnthropicContextWindowLimitRecoveryHook> | null
autoUpdateChecker: ReturnType<typeof createAutoUpdateCheckerHook> | null
agentUsageReminder: ReturnType<typeof createAgentUsageReminderHook> | null
nonInteractiveEnv: ReturnType<typeof createNonInteractiveEnvHook> | null
interactiveBashSession: ReturnType<typeof createInteractiveBashSessionHook> | null
ralphLoop: ReturnType<typeof createRalphLoopHook> | null
editErrorRecovery: ReturnType<typeof createEditErrorRecoveryHook> | null
delegateTaskRetry: ReturnType<typeof createDelegateTaskRetryHook> | null
startWork: ReturnType<typeof createStartWorkHook> | null
prometheusMdOnly: ReturnType<typeof createPrometheusMdOnlyHook> | null
sisyphusJuniorNotepad: ReturnType<typeof createSisyphusJuniorNotepadHook> | null
questionLabelTruncator: ReturnType<typeof createQuestionLabelTruncatorHook>
taskResumeInfo: ReturnType<typeof createTaskResumeInfoHook>
anthropicEffort: ReturnType<typeof createAnthropicEffortHook> | null
}
export function createSessionHooks(args: {
ctx: PluginContext
pluginConfig: OhMyOpenCodeConfig
isHookEnabled: (hookName: HookName) => boolean
safeHookEnabled: boolean
}): SessionHooks {
const { ctx, pluginConfig, isHookEnabled, safeHookEnabled } = args
const safeHook = <T>(hookName: HookName, factory: () => T): T | null =>
safeCreateHook(hookName, factory, { enabled: safeHookEnabled })
const contextWindowMonitor = isHookEnabled("context-window-monitor")
? safeHook("context-window-monitor", () => createContextWindowMonitorHook(ctx))
: null
const preemptiveCompaction =
isHookEnabled("preemptive-compaction") &&
pluginConfig.experimental?.preemptive_compaction
? safeHook("preemptive-compaction", () => createPreemptiveCompactionHook(ctx))
: null
const sessionRecovery = isHookEnabled("session-recovery")
? safeHook("session-recovery", () =>
createSessionRecoveryHook(ctx, { experimental: pluginConfig.experimental }))
: null
let sessionNotification: ReturnType<typeof createSessionNotification> | null = null
if (isHookEnabled("session-notification")) {
const forceEnable = pluginConfig.notification?.force_enable ?? false
const externalNotifier = detectExternalNotificationPlugin(ctx.directory)
if (externalNotifier.detected && !forceEnable) {
log(getNotificationConflictWarning(externalNotifier.pluginName!))
} else {
sessionNotification = safeHook("session-notification", () => createSessionNotification(ctx))
}
}
const thinkMode = isHookEnabled("think-mode")
? safeHook("think-mode", () => createThinkModeHook())
: null
const anthropicContextWindowLimitRecovery = isHookEnabled("anthropic-context-window-limit-recovery")
? safeHook("anthropic-context-window-limit-recovery", () =>
createAnthropicContextWindowLimitRecoveryHook(ctx, { experimental: pluginConfig.experimental }))
: null
const autoUpdateChecker = isHookEnabled("auto-update-checker")
? safeHook("auto-update-checker", () =>
createAutoUpdateCheckerHook(ctx, {
showStartupToast: isHookEnabled("startup-toast"),
isSisyphusEnabled: pluginConfig.sisyphus_agent?.disabled !== true,
autoUpdate: pluginConfig.auto_update ?? true,
}))
: null
const agentUsageReminder = isHookEnabled("agent-usage-reminder")
? safeHook("agent-usage-reminder", () => createAgentUsageReminderHook(ctx))
: null
const nonInteractiveEnv = isHookEnabled("non-interactive-env")
? safeHook("non-interactive-env", () => createNonInteractiveEnvHook(ctx))
: null
const interactiveBashSession = isHookEnabled("interactive-bash-session")
? safeHook("interactive-bash-session", () => createInteractiveBashSessionHook(ctx))
: null
const ralphLoop = isHookEnabled("ralph-loop")
? safeHook("ralph-loop", () =>
createRalphLoopHook(ctx, {
config: pluginConfig.ralph_loop,
checkSessionExists: async (sessionId) => sessionExists(sessionId),
}))
: null
const editErrorRecovery = isHookEnabled("edit-error-recovery")
? safeHook("edit-error-recovery", () => createEditErrorRecoveryHook(ctx))
: null
const delegateTaskRetry = isHookEnabled("delegate-task-retry")
? safeHook("delegate-task-retry", () => createDelegateTaskRetryHook(ctx))
: null
const startWork = isHookEnabled("start-work")
? safeHook("start-work", () => createStartWorkHook(ctx))
: null
const prometheusMdOnly = isHookEnabled("prometheus-md-only")
? safeHook("prometheus-md-only", () => createPrometheusMdOnlyHook(ctx))
: null
const sisyphusJuniorNotepad = isHookEnabled("sisyphus-junior-notepad")
? safeHook("sisyphus-junior-notepad", () => createSisyphusJuniorNotepadHook(ctx))
: null
const questionLabelTruncator = createQuestionLabelTruncatorHook()
const taskResumeInfo = createTaskResumeInfoHook()
const anthropicEffort = isHookEnabled("anthropic-effort")
? safeHook("anthropic-effort", () => createAnthropicEffortHook())
: null
return {
contextWindowMonitor,
preemptiveCompaction,
sessionRecovery,
sessionNotification,
thinkMode,
anthropicContextWindowLimitRecovery,
autoUpdateChecker,
agentUsageReminder,
nonInteractiveEnv,
interactiveBashSession,
ralphLoop,
editErrorRecovery,
delegateTaskRetry,
startWork,
prometheusMdOnly,
sisyphusJuniorNotepad,
questionLabelTruncator,
taskResumeInfo,
anthropicEffort,
}
}