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.
This commit is contained in:
MoerAI
2026-05-18 19:49:02 +09:00
parent f6fba0b154
commit ed44466f33
3 changed files with 84 additions and 0 deletions
+63
View File
@@ -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> = {}): 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")
})
})
})
})
+16
View File
@@ -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<void> {
const { hooks } = args
return async (input, output) => {
const overrideHook = hooks.todoDescriptionOverride
if (overrideHook) {
await overrideHook["tool.definition"](input, output)
}
}
}