Imported from willhama/md-file-study (
corpus/code-yeongyu__oh-my-openagent/files/packages/omo-opencode/src/hooks/keyword-detector/AGENTS.md). Install upstream withnpx skills add willhama/md-file-study --skill keyword-detector. Copyright stays with the author.
src/hooks/keyword-detector/ -- Mode Keyword Injection
Generated: 2026-07-17
OVERVIEW
Transform Tier hook on messages.transform. Scans the first user message for mode keywords and injects mode-specific system prompts. The detector and routing logic stay in src/hooks/keyword-detector/; prompt bodies now live in packages/prompts-core/prompts/ so they can be shared by future harness adapters.
This matches the package layering direction in ROADMAP.md: packages/prompts-core owns static prompt content, while this OpenCode hook owns keyword detection, model routing, and message injection.
KEYWORDS
| Keyword | Pattern | Effect |
|---|---|---|
ultrawork / ulw |
`/\b(ultrawork | ulw)\b/i` |
| Team mode | TEAM_PATTERN (from team/) |
Forces orchestration via team_* tools when user invokes team mode / team-mode / team_mode / teammode; instructs user to enable team_mode.enabled if tools are absent and reminds lead to run the closure sequence once every task is terminal |
| Hyperplan mode | HYPERPLAN_PATTERN (from hyperplan/) |
Loads the hyperplan skill and injects adversarial planning mode guidance |
| Hyperplan-ultrawork combo | HYPERPLAN_ULTRAWORK_PATTERN (from constants.ts) |
Prepends the combo banner, requires the hyperplan skill, then appends the routed ultrawork message |
STRUCTURE
keyword-detector/
├── index.ts # Barrel export
├── hook.ts # createKeywordDetectorHook() chat.message handler
├── detector.ts # detectKeywordsWithType() + extractPromptText()
├── constants.ts # KEYWORD_DETECTORS array, re-exports from submodules
├── types.ts # KeywordDetector, DetectedKeyword types
├── ultrawork/
│ ├── index.ts # getUltraworkMessage() router
│ ├── source-detector.ts # agent/model routing helpers
│ ├── default.ts # thin loader for prompts-core/prompts/ultrawork/default.md
│ ├── gpt.ts # thin loader for prompts-core/prompts/ultrawork/gpt.md
│ ├── gemini.ts # thin loader for prompts-core/prompts/ultrawork/gemini.md
│ ├── glm.ts # thin loader for prompts-core/prompts/ultrawork/glm.md
│ └── planner.ts # thin loader for prompts-core/prompts/ultrawork/planner.md
├── team/
│ ├── index.ts
│ └── default.ts # TEAM_PATTERN + TEAM_MESSAGE from prompts-core mode prompt
└── hyperplan/
├── index.ts
└── default.ts # HYPERPLAN_PATTERN + HYPERPLAN_MESSAGE from prompts-core mode prompt
PROMPT CONTENT LOCATIONS
| Prompt family | Markdown source |
|---|---|
| Ultrawork default | packages/prompts-core/prompts/ultrawork/default.md |
| Ultrawork GPT | packages/prompts-core/prompts/ultrawork/gpt.md |
| Ultrawork Gemini | packages/prompts-core/prompts/ultrawork/gemini.md |
| Ultrawork GLM | packages/prompts-core/prompts/ultrawork/glm.md |
| Ultrawork planner | packages/prompts-core/prompts/ultrawork/planner.md |
| Team mode | packages/prompts-core/prompts/mode/team.md |
| Hyperplan mode | packages/prompts-core/prompts/mode/hyperplan.md |
The src/hooks/keyword-detector/{team,hyperplan}/default.ts files keep the regex triggers in the hook layer and import the markdown-backed constants from @oh-my-opencode/prompts-core. The ultrawork files import markdown with Bun's .md text loader so the exact prompt bytes are bundled into dist/index.js.
ULTRAWORK VARIANT ROUTING
ultrawork/source-detector.ts decides the ultrawork source in priority order:
- Planner agents (
prometheus,planner, or normalizedplan) route toplanner.md. - GPT family models, as detected by
isGptModel(modelID), route togpt.md. - Gemini family models, as detected by
isGeminiModel(modelID), route togemini.md. - GLM family models, as detected by
isGlmModel(modelID), route toglm.md. - Everything else routes to
default.md.
ultrawork/index.ts exposes getUltraworkMessage(agentName, modelID), switches on that source, and returns the loaded markdown body.
DETECTION LOGIC
chat.message (user input)
-> extractPromptText(parts)
-> isSystemDirective? skip
-> removeSystemReminders(text) # strip <SYSTEM_REMINDER> blocks
-> detectKeywordsWithType(cleanText, agentName, modelID, disabledKeywords)
-> isNonOmoAgent(agentName)? filter keyword injection
-> isPlannerAgent(agentName)? filter standalone ultrawork
-> for each detected keyword: inject mode message into output
CONFIG
{
"keyword_detector": {
// Skip injection for any keyword in this list.
"disabled_keywords": ["team", "hyperplan"]
}
}
Default: empty/missing means every detector is active. Schema lives at src/config/schema/keyword-detector.ts.
GUARDS
- System directive skip: Messages tagged as system directives are not scanned (prevents infinite loops)
- Planner agent filter: Prometheus/plan agents do not receive
ultraworkinjection - Non-OMO agent filter: OpenCode built-in Builder/Plan agents do not receive keyword injection
- Session agent tracking: Uses
getSessionAgent()to get actual agent (not just input hint) - Model-aware messages:
getUltraworkMessage(agentName, modelID)adapts message to active model - Ultrawork source routing:
ultrawork/ultrawork-source-routing.test.tspins agent/model-to-prompt-source routing forgetUltraworkSource
