oh-my-opencode/src/plugin/tool-execute-before.test.ts
YeonGyu-Kim b58f3edf6d refactor: remove redundant subagent-question-blocker hook
Replace PreToolUse hook-based question tool blocking with the existing
tools parameter approach (tools: { question: false }) which physically
removes the tool from the LLM's toolset before inference.

The hook was redundant because every session.prompt() call already passes
question: false via the tools parameter. OpenCode converts this to a
PermissionNext deny rule and deletes the tool from the toolset, preventing
the LLM from even seeing it. The hook only fired after the LLM already
called the tool, wasting tokens.

Changes:
- Remove subagent-question-blocker hook invocation from PreToolUse chain
- Remove hook registration from create-session-hooks.ts
- Delete src/hooks/subagent-question-blocker/ directory (dead code)
- Remove hook from HookNameSchema and barrel export
- Fix sync-executor.ts missing question: false in tools parameter
- Add regression tests for both the removal and the tools parameter
2026-02-13 14:55:46 +09:00

169 lines
6.0 KiB
TypeScript

const { describe, expect, test } = require("bun:test")
const { createToolExecuteBeforeHandler } = require("./tool-execute-before")
describe("createToolExecuteBeforeHandler", () => {
test("does not execute subagent question blocker hook for question tool", async () => {
//#given
const ctx = {
client: {
session: {
messages: async () => ({ data: [] }),
},
},
}
const hooks = {
subagentQuestionBlocker: {
"tool.execute.before": async () => {
throw new Error("subagentQuestionBlocker should not run")
},
},
}
const handler = createToolExecuteBeforeHandler({ ctx, hooks })
const input = { tool: "question", sessionID: "ses_sub", callID: "call_1" }
const output = { args: { questions: [] } as Record<string, unknown> }
//#when
const run = handler(input, output)
//#then
await expect(run).resolves.toBeUndefined()
})
describe("task tool subagent_type normalization", () => {
const emptyHooks = {}
function createCtxWithSessionMessages(messages: Array<{ info?: { agent?: string; role?: string } }> = []) {
return {
client: {
session: {
messages: async () => ({ data: messages }),
},
},
}
}
test("sets subagent_type to sisyphus-junior when category is provided without subagent_type", async () => {
//#given
const ctx = createCtxWithSessionMessages()
const handler = createToolExecuteBeforeHandler({ ctx, hooks: emptyHooks })
const input = { tool: "task", sessionID: "ses_123", callID: "call_1" }
const output = { args: { category: "quick", description: "Test" } as Record<string, unknown> }
//#when
await handler(input, output)
//#then
expect(output.args.subagent_type).toBe("sisyphus-junior")
})
test("preserves existing subagent_type when explicitly provided", async () => {
//#given
const ctx = createCtxWithSessionMessages()
const handler = createToolExecuteBeforeHandler({ ctx, hooks: emptyHooks })
const input = { tool: "task", sessionID: "ses_123", callID: "call_1" }
const output = { args: { subagent_type: "plan", description: "Plan test" } as Record<string, unknown> }
//#when
await handler(input, output)
//#then
expect(output.args.subagent_type).toBe("plan")
})
test("sets subagent_type to sisyphus-junior when category provided with different subagent_type", async () => {
//#given
const ctx = createCtxWithSessionMessages()
const handler = createToolExecuteBeforeHandler({ ctx, hooks: emptyHooks })
const input = { tool: "task", sessionID: "ses_123", callID: "call_1" }
const output = { args: { category: "quick", subagent_type: "oracle", description: "Test" } as Record<string, unknown> }
//#when
await handler(input, output)
//#then
expect(output.args.subagent_type).toBe("sisyphus-junior")
})
test("resolves subagent_type from session first message when session_id provided without subagent_type", async () => {
//#given
const ctx = createCtxWithSessionMessages([
{ info: { role: "user" } },
{ info: { role: "assistant", agent: "explore" } },
{ info: { role: "assistant", agent: "oracle" } },
])
const handler = createToolExecuteBeforeHandler({ ctx, hooks: emptyHooks })
const input = { tool: "task", sessionID: "ses_123", callID: "call_1" }
const output = { args: { session_id: "ses_abc123", description: "Continue task", prompt: "fix it" } as Record<string, unknown> }
//#when
await handler(input, output)
//#then
expect(output.args.subagent_type).toBe("explore")
})
test("falls back to 'continue' when session has no agent info", async () => {
//#given
const ctx = createCtxWithSessionMessages([
{ info: { role: "user" } },
{ info: { role: "assistant" } },
])
const handler = createToolExecuteBeforeHandler({ ctx, hooks: emptyHooks })
const input = { tool: "task", sessionID: "ses_123", callID: "call_1" }
const output = { args: { session_id: "ses_abc123", description: "Continue task", prompt: "fix it" } as Record<string, unknown> }
//#when
await handler(input, output)
//#then
expect(output.args.subagent_type).toBe("continue")
})
test("preserves subagent_type when session_id is provided with explicit subagent_type", async () => {
//#given
const ctx = createCtxWithSessionMessages()
const handler = createToolExecuteBeforeHandler({ ctx, hooks: emptyHooks })
const input = { tool: "task", sessionID: "ses_123", callID: "call_1" }
const output = { args: { session_id: "ses_abc123", subagent_type: "explore", description: "Continue explore" } as Record<string, unknown> }
//#when
await handler(input, output)
//#then
expect(output.args.subagent_type).toBe("explore")
})
test("does not modify args for non-task tools", async () => {
//#given
const ctx = createCtxWithSessionMessages()
const handler = createToolExecuteBeforeHandler({ ctx, hooks: emptyHooks })
const input = { tool: "bash", sessionID: "ses_123", callID: "call_1" }
const output = { args: { command: "ls" } as Record<string, unknown> }
//#when
await handler(input, output)
//#then
expect(output.args.subagent_type).toBeUndefined()
})
test("does not set subagent_type when neither category nor session_id is provided and subagent_type is present", async () => {
//#given
const ctx = createCtxWithSessionMessages()
const handler = createToolExecuteBeforeHandler({ ctx, hooks: emptyHooks })
const input = { tool: "task", sessionID: "ses_123", callID: "call_1" }
const output = { args: { subagent_type: "oracle", description: "Oracle task" } as Record<string, unknown> }
//#when
await handler(input, output)
//#then
expect(output.args.subagent_type).toBe("oracle")
})
})
})
export {}