From 0bffdc441e2d84da09599dad23c8ce6b1a75b0fb Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Tue, 17 Feb 2026 23:33:24 +0900 Subject: [PATCH] feat(hooks): add sisyphus-gpt-hephaestus-reminder hook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shows error toast when Sisyphus runs with a GPT model, nudging user to use Hephaestus instead. 🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode) --- assets/oh-my-opencode.schema.json | 1 + src/config/schema/hooks.ts | 1 + src/hooks/index.ts | 1 + .../sisyphus-gpt-hephaestus-reminder/hook.ts | 37 +++++++++ .../index.test.ts | 78 +++++++++++++++++++ .../sisyphus-gpt-hephaestus-reminder/index.ts | 1 + src/plugin/chat-message.ts | 1 + src/plugin/hooks/create-session-hooks.ts | 7 ++ 8 files changed, 127 insertions(+) create mode 100644 src/hooks/sisyphus-gpt-hephaestus-reminder/hook.ts create mode 100644 src/hooks/sisyphus-gpt-hephaestus-reminder/index.test.ts create mode 100644 src/hooks/sisyphus-gpt-hephaestus-reminder/index.ts diff --git a/assets/oh-my-opencode.schema.json b/assets/oh-my-opencode.schema.json index 44bbf593..459ef813 100644 --- a/assets/oh-my-opencode.schema.json +++ b/assets/oh-my-opencode.schema.json @@ -91,6 +91,7 @@ "delegate-task-retry", "prometheus-md-only", "sisyphus-junior-notepad", + "sisyphus-gpt-hephaestus-reminder", "start-work", "atlas", "unstable-agent-babysitter", diff --git a/src/config/schema/hooks.ts b/src/config/schema/hooks.ts index b985c3c5..bbd593fe 100644 --- a/src/config/schema/hooks.ts +++ b/src/config/schema/hooks.ts @@ -37,6 +37,7 @@ export const HookNameSchema = z.enum([ "delegate-task-retry", "prometheus-md-only", "sisyphus-junior-notepad", + "sisyphus-gpt-hephaestus-reminder", "start-work", "atlas", "unstable-agent-babysitter", diff --git a/src/hooks/index.ts b/src/hooks/index.ts index f199ef80..fd8d61c8 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -27,6 +27,7 @@ export { createInteractiveBashSessionHook } from "./interactive-bash-session"; export { createThinkingBlockValidatorHook } from "./thinking-block-validator"; export { createCategorySkillReminderHook } from "./category-skill-reminder"; export { createRalphLoopHook, type RalphLoopHook } from "./ralph-loop"; +export { createSisyphusGptHephaestusReminderHook } from "./sisyphus-gpt-hephaestus-reminder"; export { createAutoSlashCommandHook } from "./auto-slash-command"; export { createEditErrorRecoveryHook } from "./edit-error-recovery"; export { createJsonErrorRecoveryHook } from "./json-error-recovery"; diff --git a/src/hooks/sisyphus-gpt-hephaestus-reminder/hook.ts b/src/hooks/sisyphus-gpt-hephaestus-reminder/hook.ts new file mode 100644 index 00000000..a2c1f815 --- /dev/null +++ b/src/hooks/sisyphus-gpt-hephaestus-reminder/hook.ts @@ -0,0 +1,37 @@ +import type { PluginInput } from "@opencode-ai/plugin" +import { getSessionAgent } from "../../features/claude-code-session-state" +import { log } from "../../shared" + +const TOAST_TITLE = "Use Hephaestus for GPT Models" +const TOAST_MESSAGE = "Sisyphus is using a GPT model. Use Hephaestus and include 'ulw' in your prompt." + +export function createSisyphusGptHephaestusReminderHook(ctx: PluginInput) { + return { + "chat.message": async (input: { + sessionID: string + agent?: string + model?: { providerID: string; modelID: string } + }): Promise => { + const agentName = (input.agent ?? getSessionAgent(input.sessionID) ?? "").toLowerCase() + const modelID = input.model?.modelID?.toLowerCase() ?? "" + + if (agentName !== "sisyphus" || !modelID.includes("gpt")) { + return + } + + await ctx.client.tui.showToast({ + body: { + title: TOAST_TITLE, + message: TOAST_MESSAGE, + variant: "error", + duration: 5000, + }, + }).catch((error) => { + log("[sisyphus-gpt-hephaestus-reminder] Failed to show toast", { + sessionID: input.sessionID, + error, + }) + }) + }, + } +} diff --git a/src/hooks/sisyphus-gpt-hephaestus-reminder/index.test.ts b/src/hooks/sisyphus-gpt-hephaestus-reminder/index.test.ts new file mode 100644 index 00000000..09505274 --- /dev/null +++ b/src/hooks/sisyphus-gpt-hephaestus-reminder/index.test.ts @@ -0,0 +1,78 @@ +import { describe, expect, test, spyOn } from "bun:test" +import { createSisyphusGptHephaestusReminderHook } from "./index" +import { _resetForTesting, updateSessionAgent } from "../../features/claude-code-session-state" + +describe("sisyphus-gpt-hephaestus-reminder hook", () => { + test("shows error toast when sisyphus uses gpt model", async () => { + // given - sisyphus agent with gpt model + const showToast = spyOn({ + fn: async () => ({}), + }, "fn") + const hook = createSisyphusGptHephaestusReminderHook({ + client: { + tui: { showToast }, + }, + } as any) + + // when - chat.message runs + await hook["chat.message"]?.({ + sessionID: "ses_1", + agent: "sisyphus", + model: { providerID: "openai", modelID: "gpt-5.3-codex" }, + }) + + // then - error toast is shown + expect(showToast).toHaveBeenCalledTimes(1) + expect(showToast.mock.calls[0]?.[0]).toMatchObject({ + body: { + title: "Use Hephaestus for GPT Models", + variant: "error", + }, + }) + }) + + test("does not show toast for non-gpt model", async () => { + // given - sisyphus agent with non-gpt model + const showToast = spyOn({ + fn: async () => ({}), + }, "fn") + const hook = createSisyphusGptHephaestusReminderHook({ + client: { + tui: { showToast }, + }, + } as any) + + // when - chat.message runs with claude model + await hook["chat.message"]?.({ + sessionID: "ses_2", + agent: "sisyphus", + model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, + }) + + // then - no toast + expect(showToast).toHaveBeenCalledTimes(0) + }) + + test("uses session agent fallback when input agent is missing", async () => { + // given - session agent saved as sisyphus + _resetForTesting() + updateSessionAgent("ses_3", "sisyphus") + const showToast = spyOn({ + fn: async () => ({}), + }, "fn") + const hook = createSisyphusGptHephaestusReminderHook({ + client: { + tui: { showToast }, + }, + } as any) + + // when - chat.message runs without input.agent + await hook["chat.message"]?.({ + sessionID: "ses_3", + model: { providerID: "openai", modelID: "gpt-5.2" }, + }) + + // then - toast shown via fallback agent lookup + expect(showToast).toHaveBeenCalledTimes(1) + }) +}) diff --git a/src/hooks/sisyphus-gpt-hephaestus-reminder/index.ts b/src/hooks/sisyphus-gpt-hephaestus-reminder/index.ts new file mode 100644 index 00000000..cd576669 --- /dev/null +++ b/src/hooks/sisyphus-gpt-hephaestus-reminder/index.ts @@ -0,0 +1 @@ +export { createSisyphusGptHephaestusReminderHook } from "./hook" diff --git a/src/plugin/chat-message.ts b/src/plugin/chat-message.ts index e6720320..6d648167 100644 --- a/src/plugin/chat-message.ts +++ b/src/plugin/chat-message.ts @@ -81,6 +81,7 @@ export function createChatMessageHandler(args: { await hooks.keywordDetector?.["chat.message"]?.(input, output) await hooks.claudeCodeHooks?.["chat.message"]?.(input, output) await hooks.autoSlashCommand?.["chat.message"]?.(input, output) + await hooks.sisyphusGptHephaestusReminder?.["chat.message"]?.(input) if (hooks.startWork && isStartWorkHookOutput(output)) { await hooks.startWork["chat.message"]?.(input, output) } diff --git a/src/plugin/hooks/create-session-hooks.ts b/src/plugin/hooks/create-session-hooks.ts index 354ccdb6..96b9c7ab 100644 --- a/src/plugin/hooks/create-session-hooks.ts +++ b/src/plugin/hooks/create-session-hooks.ts @@ -20,6 +20,7 @@ import { createStartWorkHook, createPrometheusMdOnlyHook, createSisyphusJuniorNotepadHook, + createSisyphusGptHephaestusReminderHook, createQuestionLabelTruncatorHook, createPreemptiveCompactionHook, } from "../../hooks" @@ -50,6 +51,7 @@ export type SessionHooks = { startWork: ReturnType | null prometheusMdOnly: ReturnType | null sisyphusJuniorNotepad: ReturnType | null + sisyphusGptHephaestusReminder: ReturnType | null questionLabelTruncator: ReturnType taskResumeInfo: ReturnType anthropicEffort: ReturnType | null @@ -156,6 +158,10 @@ export function createSessionHooks(args: { ? safeHook("sisyphus-junior-notepad", () => createSisyphusJuniorNotepadHook(ctx)) : null + const sisyphusGptHephaestusReminder = isHookEnabled("sisyphus-gpt-hephaestus-reminder") + ? safeHook("sisyphus-gpt-hephaestus-reminder", () => createSisyphusGptHephaestusReminderHook(ctx)) + : null + const questionLabelTruncator = createQuestionLabelTruncatorHook() const taskResumeInfo = createTaskResumeInfoHook() @@ -181,6 +187,7 @@ export function createSessionHooks(args: { startWork, prometheusMdOnly, sisyphusJuniorNotepad, + sisyphusGptHephaestusReminder, questionLabelTruncator, taskResumeInfo, anthropicEffort,