Files
oh-my-opencode/src/plugin/hooks/create-transform-hooks.ts
T
YeonGyu-Kim 91c0d75588 feat(keyword-detector): add per-keyword disable config
Adds keyword_detector.disabled_keywords config so users can opt out of
specific keyword detectors individually without disabling the entire
keyword-detector hook. Allowed values: 'ultrawork', 'search', 'analyze',
'team'. Default empty/missing -> all four detectors active (no behavior
change for existing configs).

Motivation: an audit revealed search and analyze patterns trigger on
~30-60% of normal conversational user messages (e.g. 'how to', 'why is',
'show me', '왜', '어떻게'). The disable list is the immediate kill switch
while the patterns themselves are tightened in a separate PR.

Schema follows the existing per-feature config block convention shared
by team_mode, ralph_loop, runtime_fallback, and comment_checker. The
KeywordType enum (z.enum) lives next to the config schema and is
re-imported by the detector to keep the union type in lockstep with the
schema.

Threading:
  pluginConfig.keyword_detector
    -> create-transform-hooks.ts (factory wiring)
    -> createKeywordDetectorHook(config)
    -> detectKeywordsWithType(text, agent, model, disabledKeywords)
    -> Set-based filter at the source-of-truth detector

Adds 8 regression tests covering per-keyword disable, multi-keyword
disable, partial disable (one keyword off, another still firing),
ultrawork toast suppression, undefined config, and empty array.
2026-05-06 14:19:40 +09:00

116 lines
3.5 KiB
TypeScript

import type { OhMyOpenCodeConfig } from "../../config"
import type { PluginContext } from "../types"
import type { RalphLoopHook } from "../../hooks/ralph-loop"
import {
createClaudeCodeHooksHook,
createKeywordDetectorHook,
createTeamMailboxInjector,
createTeamModeStatusInjector,
createThinkingBlockValidatorHook,
createToolPairValidatorHook,
} from "../../hooks"
import {
contextCollector,
createContextInjectorMessagesTransformHook,
} from "../../features/context-injector"
import { safeCreateHook } from "../../shared/safe-create-hook"
export type TransformHooks = {
claudeCodeHooks: ReturnType<typeof createClaudeCodeHooksHook> | null
keywordDetector: ReturnType<typeof createKeywordDetectorHook> | null
contextInjectorMessagesTransform: ReturnType<typeof createContextInjectorMessagesTransformHook>
teamModeStatusInjector: ReturnType<typeof createTeamModeStatusInjector> | null
teamMailboxInjector: ReturnType<typeof createTeamMailboxInjector> | null
thinkingBlockValidator: ReturnType<typeof createThinkingBlockValidatorHook> | null
toolPairValidator: ReturnType<typeof createToolPairValidatorHook> | null
}
export function createTransformHooks(args: {
ctx: PluginContext
pluginConfig: OhMyOpenCodeConfig
isHookEnabled: (hookName: string) => boolean
safeHookEnabled?: boolean
ralphLoop?: RalphLoopHook | null
}): TransformHooks {
const { ctx, pluginConfig, isHookEnabled, ralphLoop } = args
const safeHookEnabled = args.safeHookEnabled ?? true
const claudeCodeHooks = isHookEnabled("claude-code-hooks")
? safeCreateHook(
"claude-code-hooks",
() =>
createClaudeCodeHooksHook(
ctx,
{
disabledHooks: (pluginConfig.claude_code?.hooks ?? true) ? undefined : true,
keywordDetectorDisabled: !isHookEnabled("keyword-detector"),
},
contextCollector,
),
{ enabled: safeHookEnabled },
)
: null
const keywordDetector = isHookEnabled("keyword-detector")
? safeCreateHook(
"keyword-detector",
() =>
createKeywordDetectorHook(
ctx,
contextCollector,
ralphLoop ?? undefined,
pluginConfig.keyword_detector,
),
{ enabled: safeHookEnabled },
)
: null
const contextInjectorMessagesTransform =
createContextInjectorMessagesTransformHook(contextCollector)
const teamModeConfig = pluginConfig.team_mode
const teamModeStatusInjector = teamModeConfig?.enabled
? safeCreateHook(
"team-mode-status-injector",
() => createTeamModeStatusInjector(teamModeConfig),
{ enabled: safeHookEnabled },
)
: null
const teamMailboxInjector = teamModeConfig?.enabled
? safeCreateHook(
"team-mailbox-injector",
() => createTeamMailboxInjector(ctx, teamModeConfig),
{ enabled: safeHookEnabled },
)
: null
const thinkingBlockValidator = isHookEnabled("thinking-block-validator")
? safeCreateHook(
"thinking-block-validator",
() => createThinkingBlockValidatorHook(),
{ enabled: safeHookEnabled },
)
: null
const toolPairValidator = isHookEnabled("tool-pair-validator")
? safeCreateHook(
"tool-pair-validator",
() => createToolPairValidatorHook(),
{ enabled: safeHookEnabled },
)
: null
return {
claudeCodeHooks,
keywordDetector,
contextInjectorMessagesTransform,
teamModeStatusInjector,
teamMailboxInjector,
thinkingBlockValidator,
toolPairValidator,
}
}