223 lines
7.5 KiB
TypeScript
223 lines
7.5 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()
|
|
})
|
|
|
|
test("triggers session notification hook for question tools", async () => {
|
|
let called = false
|
|
const ctx = {
|
|
client: {
|
|
session: {
|
|
messages: async () => ({ data: [] }),
|
|
},
|
|
},
|
|
}
|
|
|
|
const hooks = {
|
|
sessionNotification: async (input: { event: { type: string; properties?: Record<string, unknown> } }) => {
|
|
called = true
|
|
expect(input.event.type).toBe("tool.execute.before")
|
|
expect(input.event.properties?.sessionID).toBe("ses_q")
|
|
expect(input.event.properties?.tool).toBe("question")
|
|
},
|
|
}
|
|
|
|
const handler = createToolExecuteBeforeHandler({ ctx, hooks })
|
|
const input = { tool: "question", sessionID: "ses_q", callID: "call_q" }
|
|
const output = { args: { questions: [{ question: "Proceed?", options: [{ label: "Yes" }] }] } as Record<string, unknown> }
|
|
|
|
await handler(input, output)
|
|
|
|
expect(called).toBe(true)
|
|
})
|
|
|
|
test("does not trigger session notification hook for non-question tools", async () => {
|
|
let called = false
|
|
const ctx = {
|
|
client: {
|
|
session: {
|
|
messages: async () => ({ data: [] }),
|
|
},
|
|
},
|
|
}
|
|
|
|
const hooks = {
|
|
sessionNotification: async () => {
|
|
called = true
|
|
},
|
|
}
|
|
|
|
const handler = createToolExecuteBeforeHandler({ ctx, hooks })
|
|
|
|
await handler(
|
|
{ tool: "bash", sessionID: "ses_b", callID: "call_b" },
|
|
{ args: { command: "pwd" } as Record<string, unknown> },
|
|
)
|
|
|
|
expect(called).toBe(false)
|
|
})
|
|
|
|
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 {}
|