From ed44466f331e465e21025dd6d97fcc47de98573e Mon Sep 17 00:00:00 2001 From: MoerAI Date: Mon, 18 May 2026 19:49:02 +0900 Subject: [PATCH] fix(plugin): wire tool.definition handler so todo-description-override actually fires (fixes #3705) The bundled createTodoDescriptionOverrideHook returns { 'tool.definition': fn }, but plugin-interface.ts never exposes 'tool.definition' as an OpenCode hook handler. Result: the hook is constructed by createToolGuardHooks (line 132-134 of src/plugin/hooks/create-tool-guard-hooks.ts) but the function is never invoked, so todowrite keeps using OpenCode's core 7 KB description instead of the 1.4 KB TODOWRITE_DESCRIPTION. User-defined plugins under ~/.config/opencode/plugin/*.js use the same hook contract and work fine, confirming the contract itself is functional in opencode 1.14.28+. Fix: add src/plugin/tool-definition.ts (createToolDefinitionHandler) that forwards the OpenCode 'tool.definition' input/output pair into hooks.todoDescriptionOverride. Wire it into plugin-interface.ts alongside tool.execute.before/after. Regression coverage: src/plugin/tool-definition.test.ts covers (a) todowrite override applied, (b) other tools left untouched, (c) null hook is a no-op. --- src/plugin-interface.ts | 5 +++ src/plugin/tool-definition.test.ts | 63 ++++++++++++++++++++++++++++++ src/plugin/tool-definition.ts | 16 ++++++++ 3 files changed, 84 insertions(+) create mode 100644 src/plugin/tool-definition.test.ts create mode 100644 src/plugin/tool-definition.ts diff --git a/src/plugin-interface.ts b/src/plugin-interface.ts index 5bcc0c364..9aa982808 100644 --- a/src/plugin-interface.ts +++ b/src/plugin-interface.ts @@ -8,6 +8,7 @@ import { createCommandExecuteBeforeHandler } from "./plugin/command-execute-befo import { createMessagesTransformHandler } from "./plugin/messages-transform" import { createSystemTransformHandler } from "./plugin/system-transform" import { createEventHandler } from "./plugin/event" +import { createToolDefinitionHandler } from "./plugin/tool-definition" import { createToolExecuteAfterHandler } from "./plugin/tool-execute-after" import { createToolExecuteBeforeHandler } from "./plugin/tool-execute-before" @@ -70,6 +71,10 @@ export function createPluginInterface(args: { hooks, }), + "tool.definition": createToolDefinitionHandler({ + hooks, + }), + "tool.execute.before": createToolExecuteBeforeHandler({ ctx, hooks, diff --git a/src/plugin/tool-definition.test.ts b/src/plugin/tool-definition.test.ts new file mode 100644 index 000000000..23c5167c6 --- /dev/null +++ b/src/plugin/tool-definition.test.ts @@ -0,0 +1,63 @@ +import { describe, it, expect } from "bun:test" +import { createToolDefinitionHandler } from "./tool-definition" +import { createTodoDescriptionOverrideHook } from "../hooks/todo-description-override/hook" +import { TODOWRITE_DESCRIPTION } from "../hooks/todo-description-override/description" +import type { CreatedHooks } from "../create-hooks" + +function buildHooks(overrides: Partial = {}): CreatedHooks { + return overrides as CreatedHooks +} + +describe("createToolDefinitionHandler (regression for #3705)", () => { + describe("#given todoDescriptionOverride hook is registered", () => { + describe("#when the tool.definition handler runs for the todowrite tool", () => { + it("#then forwards to the hook and rewrites the description", async () => { + //#given + const handler = createToolDefinitionHandler({ + hooks: buildHooks({ todoDescriptionOverride: createTodoDescriptionOverrideHook() }), + }) + const output = { description: "opencode core default", parameters: {} } + + //#when + await handler({ toolID: "todowrite" }, output) + + //#then + expect(output.description).toBe(TODOWRITE_DESCRIPTION) + }) + }) + + describe("#when the tool.definition handler runs for any other tool", () => { + it("#then leaves the description untouched", async () => { + //#given + const handler = createToolDefinitionHandler({ + hooks: buildHooks({ todoDescriptionOverride: createTodoDescriptionOverrideHook() }), + }) + const output = { description: "bash native description", parameters: {} } + + //#when + await handler({ toolID: "bash" }, output) + + //#then + expect(output.description).toBe("bash native description") + }) + }) + }) + + describe("#given todoDescriptionOverride hook is disabled (null)", () => { + describe("#when the tool.definition handler runs for todowrite", () => { + it("#then is a no-op", async () => { + //#given + const handler = createToolDefinitionHandler({ + hooks: buildHooks({ todoDescriptionOverride: null }), + }) + const output = { description: "opencode default kept", parameters: {} } + + //#when + await handler({ toolID: "todowrite" }, output) + + //#then + expect(output.description).toBe("opencode default kept") + }) + }) + }) +}) diff --git a/src/plugin/tool-definition.ts b/src/plugin/tool-definition.ts new file mode 100644 index 000000000..59f794eff --- /dev/null +++ b/src/plugin/tool-definition.ts @@ -0,0 +1,16 @@ +import type { CreatedHooks } from "../create-hooks" + +export function createToolDefinitionHandler(args: { + hooks: CreatedHooks +}): ( + input: { toolID: string }, + output: { description: string; parameters: unknown }, +) => Promise { + const { hooks } = args + return async (input, output) => { + const overrideHook = hooks.todoDescriptionOverride + if (overrideHook) { + await overrideHook["tool.definition"](input, output) + } + } +}