91c0d75588
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.
116 lines
3.5 KiB
TypeScript
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,
|
|
}
|
|
}
|