* refactor(keyword-detector): split constants into domain-specific modules * feat(shared): add requiresAnyModel and isAnyFallbackModelAvailable * feat(config): add hephaestus to agent schemas * feat(agents): add Hephaestus autonomous deep worker * feat(cli): update model-fallback for hephaestus support * feat(plugin): add hephaestus to config handler with ordering * test(delegate-task): update tests for hephaestus agent * docs: update AGENTS.md files for hephaestus * docs: add hephaestus to READMEs * chore: regenerate config schema * fix(delegate-task): bypass requiresModel check when user provides explicit config * docs(hephaestus): add 4-part context structure for explore/librarian prompts * docs: fix review comments from cubic (non-breaking changes) - Move Hephaestus from Primary Agents to Subagents (uses own fallback chain) - Fix Hephaestus fallback chain documentation (claude-opus-4-5 → gemini-3-pro) - Add settings.local.json to claude-code-hooks config sources - Fix delegate_task parameters in ultrawork prompt (agent→subagent_type, background→run_in_background, add load_skills) - Update line counts in AGENTS.md (index.ts: 788, manager.ts: 1440) * docs: fix additional documentation inconsistencies from oracle review - Fix delegate_task parameters in Background Agents example (docs/features.md) - Fix Hephaestus fallback chain in root AGENTS.md to match model-requirements.ts * docs: clarify Hephaestus has no fallback (requires gpt-5.2-codex only) Hephaestus uses requiresModel constraint - it only activates when gpt-5.2-codex is available. The fallback chain in code is unreachable, so documentation should not mention fallbacks. * fix(hephaestus): remove unreachable fallback chain entries Hephaestus has requiresModel: gpt-5.2-codex which means the agent only activates when that specific model is available. The fallback entries (claude-opus-4-5, gemini-3-pro) were unreachable and misleading. --------- Co-authored-by: justsisyphus <justsisyphus@users.noreply.github.com>
110 lines
4.0 KiB
TypeScript
110 lines
4.0 KiB
TypeScript
import type { PluginInput } from "@opencode-ai/plugin"
|
|
import { detectKeywordsWithType, extractPromptText, removeCodeBlocks } from "./detector"
|
|
import { isPlannerAgent } from "./constants"
|
|
import { log } from "../../shared"
|
|
import { hasSystemReminder, isSystemDirective, removeSystemReminders } from "../../shared/system-directive"
|
|
import { getMainSessionID, getSessionAgent, subagentSessions } from "../../features/claude-code-session-state"
|
|
import type { ContextCollector } from "../../features/context-injector"
|
|
|
|
export * from "./detector"
|
|
export * from "./constants"
|
|
export * from "./types"
|
|
|
|
export function createKeywordDetectorHook(ctx: PluginInput, collector?: ContextCollector) {
|
|
return {
|
|
"chat.message": async (
|
|
input: {
|
|
sessionID: string
|
|
agent?: string
|
|
model?: { providerID: string; modelID: string }
|
|
messageID?: string
|
|
},
|
|
output: {
|
|
message: Record<string, unknown>
|
|
parts: Array<{ type: string; text?: string; [key: string]: unknown }>
|
|
}
|
|
): Promise<void> => {
|
|
const promptText = extractPromptText(output.parts)
|
|
|
|
if (isSystemDirective(promptText)) {
|
|
log(`[keyword-detector] Skipping system directive message`, { sessionID: input.sessionID })
|
|
return
|
|
}
|
|
|
|
const currentAgent = getSessionAgent(input.sessionID) ?? input.agent
|
|
|
|
// 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(removeCodeBlocks(cleanText), currentAgent, modelID)
|
|
|
|
if (isPlannerAgent(currentAgent)) {
|
|
detectedKeywords = detectedKeywords.filter((k) => k.type !== "ultrawork")
|
|
}
|
|
|
|
if (detectedKeywords.length === 0) {
|
|
return
|
|
}
|
|
|
|
// Skip keyword detection for background task sessions to prevent mode injection
|
|
// (e.g., [analyze-mode]) which incorrectly triggers Prometheus restrictions
|
|
const isBackgroundTaskSession = subagentSessions.has(input.sessionID)
|
|
if (isBackgroundTaskSession) {
|
|
return
|
|
}
|
|
|
|
const mainSessionID = getMainSessionID()
|
|
const isNonMainSession = mainSessionID && input.sessionID !== mainSessionID
|
|
|
|
if (isNonMainSession) {
|
|
detectedKeywords = detectedKeywords.filter((k) => k.type === "ultrawork")
|
|
if (detectedKeywords.length === 0) {
|
|
log(`[keyword-detector] Skipping non-ultrawork keywords in non-main session`, {
|
|
sessionID: input.sessionID,
|
|
mainSessionID,
|
|
})
|
|
return
|
|
}
|
|
}
|
|
|
|
const hasUltrawork = detectedKeywords.some((k) => k.type === "ultrawork")
|
|
if (hasUltrawork) {
|
|
log(`[keyword-detector] Ultrawork mode activated`, { sessionID: input.sessionID })
|
|
|
|
if (output.message.variant === undefined) {
|
|
output.message.variant = "max"
|
|
}
|
|
|
|
ctx.client.tui
|
|
.showToast({
|
|
body: {
|
|
title: "Ultrawork Mode Activated",
|
|
message: "Maximum precision engaged. All agents at your disposal.",
|
|
variant: "success" as const,
|
|
duration: 3000,
|
|
},
|
|
})
|
|
.catch((err) =>
|
|
log(`[keyword-detector] Failed to show toast`, { error: err, sessionID: input.sessionID })
|
|
)
|
|
}
|
|
|
|
const textPartIndex = output.parts.findIndex((p) => p.type === "text" && p.text !== undefined)
|
|
if (textPartIndex === -1) {
|
|
log(`[keyword-detector] No text part found, skipping injection`, { sessionID: input.sessionID })
|
|
return
|
|
}
|
|
|
|
const allMessages = detectedKeywords.map((k) => k.message).join("\n\n")
|
|
const originalText = output.parts[textPartIndex].text ?? ""
|
|
|
|
output.parts[textPartIndex].text = `${allMessages}\n\n---\n\n${originalText}`
|
|
|
|
log(`[keyword-detector] Detected ${detectedKeywords.length} keywords`, {
|
|
sessionID: input.sessionID,
|
|
types: detectedKeywords.map((k) => k.type),
|
|
})
|
|
},
|
|
}
|
|
}
|