From a60a153d196b54e7302c5dc5f7951cb591fb03d3 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Wed, 18 Feb 2026 16:33:16 +0900 Subject: [PATCH] refactor(hooks): rename sisyphus-gpt-hephaestus-reminder to no-sisyphus-gpt Shorter hook name, disableable via disabled_hooks config, migration added for backward compatibility. Also forces agent switch to Hephaestus on Sisyphus + GPT detection. Docs updated with new hook name. --- assets/oh-my-opencode.schema.json | 2 +- docs/configurations.md | 2 +- src/config/schema/hooks.ts | 2 +- src/hooks/index.ts | 2 +- .../hook.ts | 16 +++++-- .../index.test.ts | 43 ++++++++++++++----- src/hooks/no-sisyphus-gpt/index.ts | 1 + .../sisyphus-gpt-hephaestus-reminder/index.ts | 1 - src/plugin/chat-message.ts | 2 +- src/plugin/hooks/create-session-hooks.ts | 10 ++--- src/shared/migration/hook-names.ts | 2 + 11 files changed, 57 insertions(+), 26 deletions(-) rename src/hooks/{sisyphus-gpt-hephaestus-reminder => no-sisyphus-gpt}/hook.ts (64%) rename src/hooks/{sisyphus-gpt-hephaestus-reminder => no-sisyphus-gpt}/index.test.ts (76%) create mode 100644 src/hooks/no-sisyphus-gpt/index.ts delete 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 459ef813..4e61ed1a 100644 --- a/assets/oh-my-opencode.schema.json +++ b/assets/oh-my-opencode.schema.json @@ -91,7 +91,7 @@ "delegate-task-retry", "prometheus-md-only", "sisyphus-junior-notepad", - "sisyphus-gpt-hephaestus-reminder", + "no-sisyphus-gpt", "start-work", "atlas", "unstable-agent-babysitter", diff --git a/docs/configurations.md b/docs/configurations.md index a446004f..e4f8c429 100644 --- a/docs/configurations.md +++ b/docs/configurations.md @@ -973,7 +973,7 @@ Disable specific built-in hooks via `disabled_hooks` in `~/.config/opencode/oh-m } ``` -Available hooks: `todo-continuation-enforcer`, `context-window-monitor`, `session-recovery`, `session-notification`, `comment-checker`, `grep-output-truncator`, `tool-output-truncator`, `directory-agents-injector`, `directory-readme-injector`, `empty-task-response-detector`, `think-mode`, `anthropic-context-window-limit-recovery`, `rules-injector`, `background-notification`, `auto-update-checker`, `startup-toast`, `keyword-detector`, `agent-usage-reminder`, `non-interactive-env`, `interactive-bash-session`, `compaction-context-injector`, `thinking-block-validator`, `claude-code-hooks`, `ralph-loop`, `preemptive-compaction`, `auto-slash-command`, `sisyphus-junior-notepad`, `start-work` +Available hooks: `todo-continuation-enforcer`, `context-window-monitor`, `session-recovery`, `session-notification`, `comment-checker`, `grep-output-truncator`, `tool-output-truncator`, `directory-agents-injector`, `directory-readme-injector`, `empty-task-response-detector`, `think-mode`, `anthropic-context-window-limit-recovery`, `rules-injector`, `background-notification`, `auto-update-checker`, `startup-toast`, `keyword-detector`, `agent-usage-reminder`, `non-interactive-env`, `interactive-bash-session`, `compaction-context-injector`, `thinking-block-validator`, `claude-code-hooks`, `ralph-loop`, `preemptive-compaction`, `auto-slash-command`, `sisyphus-junior-notepad`, `no-sisyphus-gpt`, `start-work` **Note on `directory-agents-injector`**: This hook is **automatically disabled** when running on OpenCode 1.1.37+ because OpenCode now has native support for dynamically resolving AGENTS.md files from subdirectories (PR #10678). This prevents duplicate AGENTS.md injection. For older OpenCode versions, the hook remains active to provide the same functionality. diff --git a/src/config/schema/hooks.ts b/src/config/schema/hooks.ts index bbd593fe..cf3d5009 100644 --- a/src/config/schema/hooks.ts +++ b/src/config/schema/hooks.ts @@ -37,7 +37,7 @@ export const HookNameSchema = z.enum([ "delegate-task-retry", "prometheus-md-only", "sisyphus-junior-notepad", - "sisyphus-gpt-hephaestus-reminder", + "no-sisyphus-gpt", "start-work", "atlas", "unstable-agent-babysitter", diff --git a/src/hooks/index.ts b/src/hooks/index.ts index fd8d61c8..db13d873 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -27,7 +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 { createNoSisyphusGptHook } from "./no-sisyphus-gpt"; 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/no-sisyphus-gpt/hook.ts similarity index 64% rename from src/hooks/sisyphus-gpt-hephaestus-reminder/hook.ts rename to src/hooks/no-sisyphus-gpt/hook.ts index 591d63a0..eec261b4 100644 --- a/src/hooks/sisyphus-gpt-hephaestus-reminder/hook.ts +++ b/src/hooks/no-sisyphus-gpt/hook.ts @@ -1,8 +1,8 @@ import type { PluginInput } from "@opencode-ai/plugin" import { isGptModel } from "../../agents/types" -import { getSessionAgent } from "../../features/claude-code-session-state" +import { getSessionAgent, updateSessionAgent } from "../../features/claude-code-session-state" import { log } from "../../shared" -import { getAgentConfigKey } from "../../shared/agent-display-names" +import { getAgentConfigKey, getAgentDisplayName } from "../../shared/agent-display-names" const TOAST_TITLE = "NEVER Use Sisyphus with GPT" const TOAST_MESSAGE = [ @@ -11,6 +11,7 @@ const TOAST_MESSAGE = [ "You are literally burning money.", "Use Hephaestus for GPT models instead.", ].join("\n") +const HEPHAESTUS_DISPLAY = getAgentDisplayName("hephaestus") function showToast(ctx: PluginInput, sessionID: string): void { ctx.client.tui.showToast({ @@ -21,19 +22,21 @@ function showToast(ctx: PluginInput, sessionID: string): void { duration: 10000, }, }).catch((error) => { - log("[sisyphus-gpt-hephaestus-reminder] Failed to show toast", { + log("[no-sisyphus-gpt] Failed to show toast", { sessionID, error, }) }) } -export function createSisyphusGptHephaestusReminderHook(ctx: PluginInput) { +export function createNoSisyphusGptHook(ctx: PluginInput) { return { "chat.message": async (input: { sessionID: string agent?: string model?: { providerID: string; modelID: string } + }, output?: { + message?: { agent?: string; [key: string]: unknown } }): Promise => { const rawAgent = input.agent ?? getSessionAgent(input.sessionID) ?? "" const agentKey = getAgentConfigKey(rawAgent) @@ -41,6 +44,11 @@ export function createSisyphusGptHephaestusReminderHook(ctx: PluginInput) { if (agentKey === "sisyphus" && modelID && isGptModel(modelID)) { showToast(ctx, input.sessionID) + input.agent = HEPHAESTUS_DISPLAY + if (output?.message) { + output.message.agent = HEPHAESTUS_DISPLAY + } + updateSessionAgent(input.sessionID, HEPHAESTUS_DISPLAY) } }, } diff --git a/src/hooks/sisyphus-gpt-hephaestus-reminder/index.test.ts b/src/hooks/no-sisyphus-gpt/index.test.ts similarity index 76% rename from src/hooks/sisyphus-gpt-hephaestus-reminder/index.test.ts rename to src/hooks/no-sisyphus-gpt/index.test.ts index 14b4615b..99f913af 100644 --- a/src/hooks/sisyphus-gpt-hephaestus-reminder/index.test.ts +++ b/src/hooks/no-sisyphus-gpt/index.test.ts @@ -1,33 +1,45 @@ import { describe, expect, spyOn, test } from "bun:test" import { _resetForTesting, updateSessionAgent } from "../../features/claude-code-session-state" import { getAgentDisplayName } from "../../shared/agent-display-names" -import { createSisyphusGptHephaestusReminderHook } from "./index" +import { createNoSisyphusGptHook } from "./index" const SISYPHUS_DISPLAY = getAgentDisplayName("sisyphus") const HEPHAESTUS_DISPLAY = getAgentDisplayName("hephaestus") -describe("sisyphus-gpt-hephaestus-reminder hook", () => { +function createOutput() { + return { + message: {}, + parts: [], + } +} + +describe("no-sisyphus-gpt hook", () => { test("shows toast on every chat.message when sisyphus uses gpt model", async () => { // given - sisyphus (display name) with gpt model const showToast = spyOn({ fn: async () => ({}) }, "fn") - const hook = createSisyphusGptHephaestusReminderHook({ + const hook = createNoSisyphusGptHook({ client: { tui: { showToast } }, } as any) + const output1 = createOutput() + const output2 = createOutput() + // when - chat.message is called repeatedly with display name await hook["chat.message"]?.({ sessionID: "ses_1", agent: SISYPHUS_DISPLAY, model: { providerID: "openai", modelID: "gpt-5.3-codex" }, - }) + }, output1) await hook["chat.message"]?.({ sessionID: "ses_1", agent: SISYPHUS_DISPLAY, model: { providerID: "openai", modelID: "gpt-5.3-codex" }, - }) + }, output2) // then - toast is shown for every message expect(showToast).toHaveBeenCalledTimes(2) + expect(output1.message.agent).toBe(HEPHAESTUS_DISPLAY) + expect(output2.message.agent).toBe(HEPHAESTUS_DISPLAY) expect(showToast.mock.calls[0]?.[0]).toMatchObject({ body: { title: "NEVER Use Sisyphus with GPT", @@ -40,37 +52,43 @@ describe("sisyphus-gpt-hephaestus-reminder hook", () => { test("does not show toast for non-gpt model", async () => { // given - sisyphus with claude model const showToast = spyOn({ fn: async () => ({}) }, "fn") - const hook = createSisyphusGptHephaestusReminderHook({ + const hook = createNoSisyphusGptHook({ client: { tui: { showToast } }, } as any) + const output = createOutput() + // when - chat.message runs await hook["chat.message"]?.({ sessionID: "ses_2", agent: SISYPHUS_DISPLAY, model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, - }) + }, output) // then - no toast expect(showToast).toHaveBeenCalledTimes(0) + expect(output.message.agent).toBeUndefined() }) test("does not show toast for non-sisyphus agent", async () => { // given - hephaestus with gpt model const showToast = spyOn({ fn: async () => ({}) }, "fn") - const hook = createSisyphusGptHephaestusReminderHook({ + const hook = createNoSisyphusGptHook({ client: { tui: { showToast } }, } as any) + const output = createOutput() + // when - chat.message runs await hook["chat.message"]?.({ sessionID: "ses_3", agent: HEPHAESTUS_DISPLAY, model: { providerID: "openai", modelID: "gpt-5.2" }, - }) + }, output) // then - no toast expect(showToast).toHaveBeenCalledTimes(0) + expect(output.message.agent).toBeUndefined() }) test("uses session agent fallback when input agent is missing", async () => { @@ -78,17 +96,20 @@ describe("sisyphus-gpt-hephaestus-reminder hook", () => { _resetForTesting() updateSessionAgent("ses_4", SISYPHUS_DISPLAY) const showToast = spyOn({ fn: async () => ({}) }, "fn") - const hook = createSisyphusGptHephaestusReminderHook({ + const hook = createNoSisyphusGptHook({ client: { tui: { showToast } }, } as any) + const output = createOutput() + // when - chat.message runs without input.agent await hook["chat.message"]?.({ sessionID: "ses_4", model: { providerID: "openai", modelID: "gpt-5.2" }, - }) + }, output) // then - toast shown via session-agent fallback expect(showToast).toHaveBeenCalledTimes(1) + expect(output.message.agent).toBe(HEPHAESTUS_DISPLAY) }) }) diff --git a/src/hooks/no-sisyphus-gpt/index.ts b/src/hooks/no-sisyphus-gpt/index.ts new file mode 100644 index 00000000..68434119 --- /dev/null +++ b/src/hooks/no-sisyphus-gpt/index.ts @@ -0,0 +1 @@ +export { createNoSisyphusGptHook } from "./hook" diff --git a/src/hooks/sisyphus-gpt-hephaestus-reminder/index.ts b/src/hooks/sisyphus-gpt-hephaestus-reminder/index.ts deleted file mode 100644 index cd576669..00000000 --- a/src/hooks/sisyphus-gpt-hephaestus-reminder/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { createSisyphusGptHephaestusReminderHook } from "./hook" diff --git a/src/plugin/chat-message.ts b/src/plugin/chat-message.ts index 6d648167..b5bd70c8 100644 --- a/src/plugin/chat-message.ts +++ b/src/plugin/chat-message.ts @@ -81,7 +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) + await hooks.noSisyphusGpt?.["chat.message"]?.(input, output) 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 96b9c7ab..f7047a90 100644 --- a/src/plugin/hooks/create-session-hooks.ts +++ b/src/plugin/hooks/create-session-hooks.ts @@ -20,7 +20,7 @@ import { createStartWorkHook, createPrometheusMdOnlyHook, createSisyphusJuniorNotepadHook, - createSisyphusGptHephaestusReminderHook, + createNoSisyphusGptHook, createQuestionLabelTruncatorHook, createPreemptiveCompactionHook, } from "../../hooks" @@ -51,7 +51,7 @@ export type SessionHooks = { startWork: ReturnType | null prometheusMdOnly: ReturnType | null sisyphusJuniorNotepad: ReturnType | null - sisyphusGptHephaestusReminder: ReturnType | null + noSisyphusGpt: ReturnType | null questionLabelTruncator: ReturnType taskResumeInfo: ReturnType anthropicEffort: ReturnType | null @@ -158,8 +158,8 @@ 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)) + const noSisyphusGpt = isHookEnabled("no-sisyphus-gpt") + ? safeHook("no-sisyphus-gpt", () => createNoSisyphusGptHook(ctx)) : null const questionLabelTruncator = createQuestionLabelTruncatorHook() @@ -187,7 +187,7 @@ export function createSessionHooks(args: { startWork, prometheusMdOnly, sisyphusJuniorNotepad, - sisyphusGptHephaestusReminder, + noSisyphusGpt, questionLabelTruncator, taskResumeInfo, anthropicEffort, diff --git a/src/shared/migration/hook-names.ts b/src/shared/migration/hook-names.ts index 35920272..3c0bba2e 100644 --- a/src/shared/migration/hook-names.ts +++ b/src/shared/migration/hook-names.ts @@ -5,6 +5,8 @@ export const HOOK_NAME_MAP: Record = { "anthropic-auto-compact": "anthropic-context-window-limit-recovery", "sisyphus-orchestrator": "atlas", + "sisyphus-gpt-hephaestus-reminder": "no-sisyphus-gpt", + // Removed hooks (v3.0.0) - will be filtered out and user warned "empty-message-sanitizer": null, }