feat(keyword-detector): show toast notification when ultrawork mode is activated (#393)
* feat(keyword-detector): show toast notification when ultrawork mode is activated When users trigger ultrawork mode (via 'ultrawork' or 'ulw' keywords), a toast notification now appears to confirm the mode is active. The notification is shown once per session to avoid spamming. Changes: - Add detectKeywordsWithType() to identify which keyword type triggered - Show 'Ultrawork Mode Activated' toast with success variant - Track notified sessions to prevent duplicate toasts Closes #392 * fix(keyword-detector): fix index bug in detectKeywordsWithType and add error logging - Fix P1: detectKeywordsWithType now maps before filtering to preserve original KEYWORD_DETECTORS indices. Previously, the index used was from the filtered array, causing incorrect type assignment (e.g., 'search' match would incorrectly return 'ultrawork' type). - Fix P2: Replace silent .catch(() => {}) with proper error logging using the log function for easier debugging when toast notifications fail. --------- Co-authored-by: sisyphus-dev-ai <sisyphus-dev-ai@users.noreply.github.com>
This commit is contained in:
@@ -4,6 +4,11 @@ import {
|
|||||||
INLINE_CODE_PATTERN,
|
INLINE_CODE_PATTERN,
|
||||||
} from "./constants"
|
} from "./constants"
|
||||||
|
|
||||||
|
export interface DetectedKeyword {
|
||||||
|
type: "ultrawork" | "search" | "analyze"
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
|
||||||
export function removeCodeBlocks(text: string): string {
|
export function removeCodeBlocks(text: string): string {
|
||||||
return text.replace(CODE_BLOCK_PATTERN, "").replace(INLINE_CODE_PATTERN, "")
|
return text.replace(CODE_BLOCK_PATTERN, "").replace(INLINE_CODE_PATTERN, "")
|
||||||
}
|
}
|
||||||
@@ -15,6 +20,18 @@ export function detectKeywords(text: string): string[] {
|
|||||||
).map(({ message }) => message)
|
).map(({ message }) => message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function detectKeywordsWithType(text: string): DetectedKeyword[] {
|
||||||
|
const textWithoutCode = removeCodeBlocks(text)
|
||||||
|
const types: Array<"ultrawork" | "search" | "analyze"> = ["ultrawork", "search", "analyze"]
|
||||||
|
return KEYWORD_DETECTORS.map(({ pattern, message }, index) => ({
|
||||||
|
matches: pattern.test(textWithoutCode),
|
||||||
|
type: types[index],
|
||||||
|
message,
|
||||||
|
}))
|
||||||
|
.filter((result) => result.matches)
|
||||||
|
.map(({ type, message }) => ({ type, message }))
|
||||||
|
}
|
||||||
|
|
||||||
export function extractPromptText(
|
export function extractPromptText(
|
||||||
parts: Array<{ type: string; text?: string }>
|
parts: Array<{ type: string; text?: string }>
|
||||||
): string {
|
): string {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { detectKeywords, extractPromptText } from "./detector"
|
import type { PluginInput } from "@opencode-ai/plugin"
|
||||||
|
import { detectKeywords, detectKeywordsWithType, extractPromptText } from "./detector"
|
||||||
import { log } from "../../shared"
|
import { log } from "../../shared"
|
||||||
import { injectHookMessage } from "../../features/hook-message-injector"
|
import { injectHookMessage } from "../../features/hook-message-injector"
|
||||||
|
|
||||||
@@ -7,8 +8,9 @@ export * from "./constants"
|
|||||||
export * from "./types"
|
export * from "./types"
|
||||||
|
|
||||||
const sessionFirstMessageProcessed = new Set<string>()
|
const sessionFirstMessageProcessed = new Set<string>()
|
||||||
|
const sessionUltraworkNotified = new Set<string>()
|
||||||
|
|
||||||
export function createKeywordDetectorHook() {
|
export function createKeywordDetectorHook(ctx: PluginInput) {
|
||||||
return {
|
return {
|
||||||
"chat.message": async (
|
"chat.message": async (
|
||||||
input: {
|
input: {
|
||||||
@@ -26,12 +28,28 @@ export function createKeywordDetectorHook() {
|
|||||||
sessionFirstMessageProcessed.add(input.sessionID)
|
sessionFirstMessageProcessed.add(input.sessionID)
|
||||||
|
|
||||||
const promptText = extractPromptText(output.parts)
|
const promptText = extractPromptText(output.parts)
|
||||||
const messages = detectKeywords(promptText)
|
const detectedKeywords = detectKeywordsWithType(promptText)
|
||||||
|
const messages = detectedKeywords.map((k) => k.message)
|
||||||
|
|
||||||
if (messages.length === 0) {
|
if (messages.length === 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasUltrawork = detectedKeywords.some((k) => k.type === "ultrawork")
|
||||||
|
if (hasUltrawork && !sessionUltraworkNotified.has(input.sessionID)) {
|
||||||
|
sessionUltraworkNotified.add(input.sessionID)
|
||||||
|
log(`[keyword-detector] Ultrawork mode activated`, { sessionID: input.sessionID })
|
||||||
|
|
||||||
|
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 context = messages.join("\n")
|
const context = messages.join("\n")
|
||||||
|
|
||||||
// First message: transform parts directly (for title generation compatibility)
|
// First message: transform parts directly (for title generation compatibility)
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
|||||||
})
|
})
|
||||||
: null;
|
: null;
|
||||||
const keywordDetector = isHookEnabled("keyword-detector")
|
const keywordDetector = isHookEnabled("keyword-detector")
|
||||||
? createKeywordDetectorHook()
|
? createKeywordDetectorHook(ctx)
|
||||||
: null;
|
: null;
|
||||||
const agentUsageReminder = isHookEnabled("agent-usage-reminder")
|
const agentUsageReminder = isHookEnabled("agent-usage-reminder")
|
||||||
? createAgentUsageReminderHook(ctx)
|
? createAgentUsageReminderHook(ctx)
|
||||||
|
|||||||
Reference in New Issue
Block a user