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.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import type { PluginInput } from "@opencode-ai/plugin"
|
||||
import type { KeywordDetectorConfig } from "../../config/schema/keyword-detector"
|
||||
import { detectKeywordsWithType, extractPromptText } from "./detector"
|
||||
import { isPlannerAgent, isNonOmoAgent } from "./constants"
|
||||
import { log } from "../../shared"
|
||||
@@ -17,8 +18,10 @@ import type { RalphLoopHook } from "../ralph-loop"
|
||||
export function createKeywordDetectorHook(
|
||||
ctx: PluginInput,
|
||||
_collector?: ContextCollector,
|
||||
_ralphLoop?: Pick<RalphLoopHook, "startLoop">
|
||||
_ralphLoop?: Pick<RalphLoopHook, "startLoop">,
|
||||
config?: KeywordDetectorConfig,
|
||||
) {
|
||||
const disabledKeywords = config?.disabled_keywords
|
||||
function getRuntimeVariant(input: { variant?: string }, message: Record<string, unknown>): string | undefined {
|
||||
if (typeof message["variant"] === "string") {
|
||||
return message["variant"]
|
||||
@@ -59,7 +62,7 @@ export function createKeywordDetectorHook(
|
||||
// Remove system-reminder content to prevent automated system messages from triggering mode keywords
|
||||
const cleanText = removeSystemReminders(promptText)
|
||||
const modelID = input.model?.modelID
|
||||
let detectedKeywords = detectKeywordsWithType(cleanText, currentAgent, modelID)
|
||||
let detectedKeywords = detectKeywordsWithType(cleanText, currentAgent, modelID, disabledKeywords)
|
||||
|
||||
if (isPlannerAgent(currentAgent)) {
|
||||
const preFilterCount = detectedKeywords.length
|
||||
|
||||
Reference in New Issue
Block a user