diff --git a/assets/oh-my-opencode.schema.json b/assets/oh-my-opencode.schema.json index b3ab6ce2..8106f6e2 100644 --- a/assets/oh-my-opencode.schema.json +++ b/assets/oh-my-opencode.schema.json @@ -99,7 +99,6 @@ "tasks-todowrite-disabler", "write-existing-file-guard", "anthropic-effort", - "hashline-edit-disabler", "hashline-read-enhancer" ] } diff --git a/src/config/schema/hooks.ts b/src/config/schema/hooks.ts index a4461e2c..d0e1e191 100644 --- a/src/config/schema/hooks.ts +++ b/src/config/schema/hooks.ts @@ -45,7 +45,6 @@ export const HookNameSchema = z.enum([ "tasks-todowrite-disabler", "write-existing-file-guard", "anthropic-effort", - "hashline-edit-disabler", "hashline-read-enhancer", ]) diff --git a/src/features/hashline-provider-state.test.ts b/src/features/hashline-provider-state.test.ts deleted file mode 100644 index d8eb272f..00000000 --- a/src/features/hashline-provider-state.test.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { describe, expect, test, beforeEach } from "bun:test" -import { setProvider, getProvider, clearProvider } from "./hashline-provider-state" - -describe("hashline-provider-state", () => { - beforeEach(() => { - // Clear state before each test - clearProvider("test-session-1") - clearProvider("test-session-2") - }) - - describe("setProvider", () => { - test("should store provider ID for a session", () => { - // given - const sessionID = "test-session-1" - const providerID = "openai" - - // when - setProvider(sessionID, providerID) - - // then - expect(getProvider(sessionID)).toBe("openai") - }) - - test("should overwrite existing provider for same session", () => { - // given - const sessionID = "test-session-1" - setProvider(sessionID, "openai") - - // when - setProvider(sessionID, "anthropic") - - // then - expect(getProvider(sessionID)).toBe("anthropic") - }) - }) - - describe("getProvider", () => { - test("should return undefined for non-existent session", () => { - // given - const sessionID = "non-existent-session" - - // when - const result = getProvider(sessionID) - - // then - expect(result).toBeUndefined() - }) - - test("should return stored provider ID", () => { - // given - const sessionID = "test-session-1" - setProvider(sessionID, "anthropic") - - // when - const result = getProvider(sessionID) - - // then - expect(result).toBe("anthropic") - }) - - test("should handle multiple sessions independently", () => { - // given - setProvider("session-1", "openai") - setProvider("session-2", "anthropic") - - // when - const result1 = getProvider("session-1") - const result2 = getProvider("session-2") - - // then - expect(result1).toBe("openai") - expect(result2).toBe("anthropic") - }) - }) - - describe("clearProvider", () => { - test("should remove provider for a session", () => { - // given - const sessionID = "test-session-1" - setProvider(sessionID, "openai") - - // when - clearProvider(sessionID) - - // then - expect(getProvider(sessionID)).toBeUndefined() - }) - - test("should not affect other sessions", () => { - // given - setProvider("session-1", "openai") - setProvider("session-2", "anthropic") - - // when - clearProvider("session-1") - - // then - expect(getProvider("session-1")).toBeUndefined() - expect(getProvider("session-2")).toBe("anthropic") - }) - - test("should handle clearing non-existent session gracefully", () => { - // given - const sessionID = "non-existent" - - // when - clearProvider(sessionID) - - // then - expect(getProvider(sessionID)).toBeUndefined() - }) - }) -}) diff --git a/src/features/hashline-provider-state.ts b/src/features/hashline-provider-state.ts deleted file mode 100644 index 5c04a730..00000000 --- a/src/features/hashline-provider-state.ts +++ /dev/null @@ -1,13 +0,0 @@ -const providerStateMap = new Map() - -export function setProvider(sessionID: string, providerID: string): void { - providerStateMap.set(sessionID, providerID) -} - -export function getProvider(sessionID: string): string | undefined { - return providerStateMap.get(sessionID) -} - -export function clearProvider(sessionID: string): void { - providerStateMap.delete(sessionID) -} diff --git a/src/hooks/hashline-edit-disabler/constants.ts b/src/hooks/hashline-edit-disabler/constants.ts deleted file mode 100644 index a6001965..00000000 --- a/src/hooks/hashline-edit-disabler/constants.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const HOOK_NAME = "hashline-edit-disabler" - -export const EDIT_DISABLED_MESSAGE = `The 'edit' tool is disabled. Use 'hashline_edit' tool instead. Read the file first to get LINE:HASH anchors, then use hashline_edit with set_line, replace_lines, or insert_after operations.` diff --git a/src/hooks/hashline-edit-disabler/hook.ts b/src/hooks/hashline-edit-disabler/hook.ts deleted file mode 100644 index aa49e26a..00000000 --- a/src/hooks/hashline-edit-disabler/hook.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { Hooks, PluginInput } from "@opencode-ai/plugin" -import { getProvider } from "../../features/hashline-provider-state" -import { EDIT_DISABLED_MESSAGE } from "./constants" - -export interface HashlineEditDisablerConfig { - experimental?: { - hashline_edit?: boolean - } -} - -export function createHashlineEditDisablerHook( - config: HashlineEditDisablerConfig, -): Hooks { - const isHashlineEnabled = config.experimental?.hashline_edit ?? false - - return { - "tool.execute.before": async ( - input: { tool: string; sessionID: string }, - ) => { - if (!isHashlineEnabled) { - return - } - - const toolName = input.tool.toLowerCase() - if (toolName !== "edit") { - return - } - - const providerID = getProvider(input.sessionID) - if (providerID === "openai") { - return - } - - throw new Error(EDIT_DISABLED_MESSAGE) - }, - } -} diff --git a/src/hooks/hashline-edit-disabler/index.test.ts b/src/hooks/hashline-edit-disabler/index.test.ts deleted file mode 100644 index 2112497d..00000000 --- a/src/hooks/hashline-edit-disabler/index.test.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from "bun:test" -import { createHashlineEditDisablerHook } from "./index" -import { setProvider, clearProvider } from "../../features/hashline-provider-state" - -describe("hashline-edit-disabler hook", () => { - const sessionID = "test-session-123" - - beforeEach(() => { - clearProvider(sessionID) - }) - - afterEach(() => { - clearProvider(sessionID) - }) - - it("blocks edit tool when hashline enabled + non-OpenAI provider", async () => { - //#given - setProvider(sessionID, "anthropic") - const hook = createHashlineEditDisablerHook({ - experimental: { hashline_edit: true }, - }) - const input = { tool: "edit", sessionID } - const output = { args: {} } - - //#when - const executeBeforeHandler = hook["tool.execute.before"] - if (!executeBeforeHandler) { - throw new Error("tool.execute.before handler not found") - } - - //#then - await expect(executeBeforeHandler(input, output)).rejects.toThrow( - /hashline_edit/, - ) - }) - - it("passes through edit tool when hashline disabled", async () => { - //#given - setProvider(sessionID, "anthropic") - const hook = createHashlineEditDisablerHook({ - experimental: { hashline_edit: false }, - }) - const input = { tool: "edit", sessionID } - const output = { args: {} } - - //#when - const executeBeforeHandler = hook["tool.execute.before"] - if (!executeBeforeHandler) { - throw new Error("tool.execute.before handler not found") - } - - //#then - const result = await executeBeforeHandler(input, output) - expect(result).toBeUndefined() - }) - - it("passes through edit tool when OpenAI provider (even if hashline enabled)", async () => { - //#given - setProvider(sessionID, "openai") - const hook = createHashlineEditDisablerHook({ - experimental: { hashline_edit: true }, - }) - const input = { tool: "edit", sessionID } - const output = { args: {} } - - //#when - const executeBeforeHandler = hook["tool.execute.before"] - if (!executeBeforeHandler) { - throw new Error("tool.execute.before handler not found") - } - - //#then - const result = await executeBeforeHandler(input, output) - expect(result).toBeUndefined() - }) - - it("passes through non-edit tools", async () => { - //#given - setProvider(sessionID, "anthropic") - const hook = createHashlineEditDisablerHook({ - experimental: { hashline_edit: true }, - }) - const input = { tool: "write", sessionID } - const output = { args: {} } - - //#when - const executeBeforeHandler = hook["tool.execute.before"] - if (!executeBeforeHandler) { - throw new Error("tool.execute.before handler not found") - } - - //#then - const result = await executeBeforeHandler(input, output) - expect(result).toBeUndefined() - }) - - it("blocks case-insensitive edit tool names", async () => { - //#given - setProvider(sessionID, "anthropic") - const hook = createHashlineEditDisablerHook({ - experimental: { hashline_edit: true }, - }) - - //#when - const executeBeforeHandler = hook["tool.execute.before"] - if (!executeBeforeHandler) { - throw new Error("tool.execute.before handler not found") - } - - //#then - for (const toolName of ["Edit", "EDIT", "edit", "EdIt"]) { - const input = { tool: toolName, sessionID } - const output = { args: {} } - await expect(executeBeforeHandler(input, output)).rejects.toThrow( - /hashline_edit/, - ) - } - }) - - it("passes through when hashline config is undefined", async () => { - //#given - setProvider(sessionID, "anthropic") - const hook = createHashlineEditDisablerHook({ - experimental: {}, - }) - const input = { tool: "edit", sessionID } - const output = { args: {} } - - //#when - const executeBeforeHandler = hook["tool.execute.before"] - if (!executeBeforeHandler) { - throw new Error("tool.execute.before handler not found") - } - - //#then - const result = await executeBeforeHandler(input, output) - expect(result).toBeUndefined() - }) - - it("error message includes hashline_edit tool guidance", async () => { - //#given - setProvider(sessionID, "anthropic") - const hook = createHashlineEditDisablerHook({ - experimental: { hashline_edit: true }, - }) - const input = { tool: "edit", sessionID } - const output = { args: {} } - - //#when - const executeBeforeHandler = hook["tool.execute.before"] - if (!executeBeforeHandler) { - throw new Error("tool.execute.before handler not found") - } - - //#then - try { - await executeBeforeHandler(input, output) - throw new Error("Expected error to be thrown") - } catch (error) { - if (error instanceof Error) { - expect(error.message).toContain("hashline_edit") - expect(error.message).toContain("set_line") - expect(error.message).toContain("replace_lines") - expect(error.message).toContain("insert_after") - } - } - }) -}) diff --git a/src/hooks/hashline-edit-disabler/index.ts b/src/hooks/hashline-edit-disabler/index.ts deleted file mode 100644 index 7bc6f96e..00000000 --- a/src/hooks/hashline-edit-disabler/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { createHashlineEditDisablerHook } from "./hook" -export { HOOK_NAME, EDIT_DISABLED_MESSAGE } from "./constants" diff --git a/src/hooks/hashline-read-enhancer/hook.ts b/src/hooks/hashline-read-enhancer/hook.ts index 5aefbf31..18dfabe2 100644 --- a/src/hooks/hashline-read-enhancer/hook.ts +++ b/src/hooks/hashline-read-enhancer/hook.ts @@ -1,5 +1,4 @@ import type { PluginInput } from "@opencode-ai/plugin" -import { getProvider } from "../../features/hashline-provider-state" import { computeLineHash } from "../../tools/hashline-edit/hash-computation" interface HashlineReadEnhancerConfig { @@ -12,15 +11,8 @@ function isReadTool(toolName: string): boolean { return toolName.toLowerCase() === "read" } -function shouldProcess(sessionID: string, config: HashlineReadEnhancerConfig): boolean { - if (!config.hashline_edit?.enabled) { - return false - } - const providerID = getProvider(sessionID) - if (providerID === "openai") { - return false - } - return true +function shouldProcess(config: HashlineReadEnhancerConfig): boolean { + return config.hashline_edit?.enabled ?? false } function isTextFile(output: string): boolean { @@ -65,7 +57,7 @@ export function createHashlineReadEnhancerHook( if (typeof output.output !== "string") { return } - if (!shouldProcess(input.sessionID, config)) { + if (!shouldProcess(config)) { return } output.output = transformOutput(output.output) diff --git a/src/hooks/hashline-read-enhancer/index.test.ts b/src/hooks/hashline-read-enhancer/index.test.ts index 243e0097..0c640f09 100644 --- a/src/hooks/hashline-read-enhancer/index.test.ts +++ b/src/hooks/hashline-read-enhancer/index.test.ts @@ -1,7 +1,6 @@ -import { describe, it, expect, beforeEach, afterEach } from "bun:test" +import { describe, it, expect, beforeEach } from "bun:test" import { createHashlineReadEnhancerHook } from "./hook" import type { PluginInput } from "@opencode-ai/plugin" -import { setProvider, clearProvider } from "../../features/hashline-provider-state" //#given - Test setup helpers function createMockContext(): PluginInput { @@ -27,11 +26,6 @@ describe("createHashlineReadEnhancerHook", () => { beforeEach(() => { mockCtx = createMockContext() - clearProvider(sessionID) - }) - - afterEach(() => { - clearProvider(sessionID) }) describe("tool name matching", () => { @@ -120,51 +114,6 @@ describe("createHashlineReadEnhancerHook", () => { }) }) - describe("provider check", () => { - it("should skip when provider is OpenAI", async () => { - //#given - setProvider(sessionID, "openai") - const hook = createHashlineReadEnhancerHook(mockCtx, createMockConfig(true)) - const input = { tool: "read", sessionID, callID: "call-1" } - const originalOutput = "1: hello\n2: world" - const output = { title: "Read", output: originalOutput, metadata: {} } - - //#when - await hook["tool.execute.after"](input, output) - - //#then - expect(output.output).toBe(originalOutput) - }) - - it("should process when provider is Claude", async () => { - //#given - setProvider(sessionID, "anthropic") - const hook = createHashlineReadEnhancerHook(mockCtx, createMockConfig(true)) - const input = { tool: "read", sessionID, callID: "call-1" } - const output = { title: "Read", output: "1: hello\n2: world", metadata: {} } - - //#when - await hook["tool.execute.after"](input, output) - - //#then - expect(output.output).toContain("|") - }) - - it("should process when provider is unknown (undefined)", async () => { - //#given - // Provider not set, getProvider returns undefined - const hook = createHashlineReadEnhancerHook(mockCtx, createMockConfig(true)) - const input = { tool: "read", sessionID, callID: "call-1" } - const output = { title: "Read", output: "1: hello\n2: world", metadata: {} } - - //#when - await hook["tool.execute.after"](input, output) - - //#then - expect(output.output).toContain("|") - }) - }) - describe("output transformation", () => { it("should transform 'N: content' format to 'N:HASH|content'", async () => { //#given diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 16ebdc23..bdc27211 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -43,5 +43,4 @@ export { createUnstableAgentBabysitterHook } from "./unstable-agent-babysitter"; export { createPreemptiveCompactionHook } from "./preemptive-compaction"; export { createTasksTodowriteDisablerHook } from "./tasks-todowrite-disabler"; export { createWriteExistingFileGuardHook } from "./write-existing-file-guard"; -export { createHashlineEditDisablerHook } from "./hashline-edit-disabler"; export { createHashlineReadEnhancerHook } from "./hashline-read-enhancer"; diff --git a/src/plugin/chat-params.ts b/src/plugin/chat-params.ts index f9110811..8f996a88 100644 --- a/src/plugin/chat-params.ts +++ b/src/plugin/chat-params.ts @@ -1,5 +1,3 @@ -import { setProvider } from "../features/hashline-provider-state" - type ChatParamsInput = { sessionID: string agent: { name?: string } @@ -68,8 +66,6 @@ export function createChatParamsHandler(args: { if (!normalizedInput) return if (!isChatParamsOutput(output)) return - setProvider(normalizedInput.sessionID, normalizedInput.model.providerID) - await args.anthropicEffort?.["chat.params"]?.(normalizedInput, output) } } diff --git a/src/plugin/hooks/create-tool-guard-hooks.ts b/src/plugin/hooks/create-tool-guard-hooks.ts index 062cf6d6..46a36140 100644 --- a/src/plugin/hooks/create-tool-guard-hooks.ts +++ b/src/plugin/hooks/create-tool-guard-hooks.ts @@ -10,7 +10,6 @@ import { createRulesInjectorHook, createTasksTodowriteDisablerHook, createWriteExistingFileGuardHook, - createHashlineEditDisablerHook, createHashlineReadEnhancerHook, } from "../../hooks" import { @@ -30,7 +29,6 @@ export type ToolGuardHooks = { rulesInjector: ReturnType | null tasksTodowriteDisabler: ReturnType | null writeExistingFileGuard: ReturnType | null - hashlineEditDisabler: ReturnType | null hashlineReadEnhancer: ReturnType | null } @@ -89,10 +87,6 @@ export function createToolGuardHooks(args: { ? safeHook("write-existing-file-guard", () => createWriteExistingFileGuardHook(ctx)) : null - const hashlineEditDisabler = isHookEnabled("hashline-edit-disabler") - ? safeHook("hashline-edit-disabler", () => createHashlineEditDisablerHook(pluginConfig)) - : null - const hashlineReadEnhancer = isHookEnabled("hashline-read-enhancer") ? safeHook("hashline-read-enhancer", () => createHashlineReadEnhancerHook(ctx, { hashline_edit: { enabled: pluginConfig.experimental?.hashline_edit ?? false } })) : null @@ -106,7 +100,6 @@ export function createToolGuardHooks(args: { rulesInjector, tasksTodowriteDisabler, writeExistingFileGuard, - hashlineEditDisabler, hashlineReadEnhancer, } } diff --git a/src/plugin/tool-execute-before.ts b/src/plugin/tool-execute-before.ts index 42a876dd..70d023b4 100644 --- a/src/plugin/tool-execute-before.ts +++ b/src/plugin/tool-execute-before.ts @@ -29,8 +29,6 @@ export function createToolExecuteBeforeHandler(args: { await hooks.prometheusMdOnly?.["tool.execute.before"]?.(input, output) await hooks.sisyphusJuniorNotepad?.["tool.execute.before"]?.(input, output) await hooks.atlasHook?.["tool.execute.before"]?.(input, output) - await hooks.hashlineEditDisabler?.["tool.execute.before"]?.(input, output) - if (input.tool === "task") { const argsObject = output.args const category = typeof argsObject.category === "string" ? argsObject.category : undefined diff --git a/src/plugin/tool-registry.ts b/src/plugin/tool-registry.ts index 99993c4d..b4de8b5c 100644 --- a/src/plugin/tool-registry.ts +++ b/src/plugin/tool-registry.ts @@ -120,7 +120,7 @@ export function createToolRegistry(args: { const hashlineEnabled = pluginConfig.experimental?.hashline_edit ?? false const hashlineToolsRecord: Record = hashlineEnabled - ? { hashline_edit: createHashlineEditTool() } + ? { edit: createHashlineEditTool() } : {} const allTools: Record = {