fix: skip claude hook injection for internal prompts
This commit is contained in:
@@ -50,6 +50,7 @@ export function createChatMessageHandler(
|
||||
})
|
||||
|
||||
const messageParts: MessagePart[] = textParts.map((p) => ({
|
||||
...p,
|
||||
type: "text",
|
||||
text: p.text,
|
||||
}))
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import { describe, it, expect } from "bun:test"
|
||||
import { afterEach, describe, expect, it, mock, spyOn } from "bun:test"
|
||||
import { OMO_INTERNAL_INITIATOR_MARKER } from "../../shared/internal-initiator-marker"
|
||||
import * as dispatchHookModule from "./dispatch-hook"
|
||||
import {
|
||||
executeUserPromptSubmitHooks,
|
||||
type UserPromptSubmitContext,
|
||||
} from "./user-prompt-submit"
|
||||
|
||||
describe("executeUserPromptSubmitHooks", () => {
|
||||
afterEach(() => {
|
||||
mock.restore()
|
||||
})
|
||||
|
||||
it("returns early when no config provided", async () => {
|
||||
// given
|
||||
const ctx: UserPromptSubmitContext = {
|
||||
@@ -104,4 +110,65 @@ describe("executeUserPromptSubmitHooks", () => {
|
||||
expect(result1.block).toBe(false)
|
||||
expect(result2.block).toBe(false)
|
||||
})
|
||||
|
||||
it("#given synthetic hook context only #when prompt submit runs #then hook command is not dispatched", async () => {
|
||||
// given
|
||||
const dispatchSpy = spyOn(dispatchHookModule, "dispatchHook").mockResolvedValue({
|
||||
exitCode: 0,
|
||||
stdout: "hook output",
|
||||
stderr: "",
|
||||
})
|
||||
const ctx: UserPromptSubmitContext = {
|
||||
sessionId: "test-session-synthetic",
|
||||
prompt: "synthetic hook message",
|
||||
parts: [{ type: "text", text: "synthetic hook message", synthetic: true }],
|
||||
cwd: "/tmp",
|
||||
}
|
||||
const config = {
|
||||
UserPromptSubmit: [
|
||||
{ matcher: "*", hooks: [{ type: "command" as const, command: "echo hook" }] },
|
||||
],
|
||||
}
|
||||
|
||||
// when
|
||||
const result = await executeUserPromptSubmitHooks(ctx, config)
|
||||
|
||||
// then
|
||||
expect(result.block).toBe(false)
|
||||
expect(result.messages).toEqual([])
|
||||
expect(dispatchSpy).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
|
||||
it("#given internal prompt marker only #when prompt submit runs #then hook command is not dispatched", async () => {
|
||||
// given
|
||||
const dispatchSpy = spyOn(dispatchHookModule, "dispatchHook").mockResolvedValue({
|
||||
exitCode: 0,
|
||||
stdout: "hook output",
|
||||
stderr: "",
|
||||
})
|
||||
const ctx: UserPromptSubmitContext = {
|
||||
sessionId: "test-session-internal",
|
||||
prompt: `internal hook message\n${OMO_INTERNAL_INITIATOR_MARKER}`,
|
||||
parts: [
|
||||
{
|
||||
type: "text",
|
||||
text: `internal hook message\n${OMO_INTERNAL_INITIATOR_MARKER}`,
|
||||
},
|
||||
],
|
||||
cwd: "/tmp",
|
||||
}
|
||||
const config = {
|
||||
UserPromptSubmit: [
|
||||
{ matcher: "*", hooks: [{ type: "command" as const, command: "echo hook" }] },
|
||||
],
|
||||
}
|
||||
|
||||
// when
|
||||
const result = await executeUserPromptSubmitHooks(ctx, config)
|
||||
|
||||
// then
|
||||
expect(result.block).toBe(false)
|
||||
expect(result.messages).toEqual([])
|
||||
expect(dispatchSpy).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -4,6 +4,7 @@ import type {
|
||||
ClaudeHooksConfig,
|
||||
} from "./types"
|
||||
import { findMatchingHooks, log } from "../../shared"
|
||||
import { isRealUserTextPart } from "../../shared/internal-initiator-marker"
|
||||
import { dispatchHook, getHookIdentifier } from "./dispatch-hook"
|
||||
import { isHookCommandDisabled, type PluginExtendedConfig } from "./config-loader"
|
||||
|
||||
@@ -44,10 +45,14 @@ export async function executeUserPromptSubmitHooks(
|
||||
return { block: false, modifiedParts, messages }
|
||||
}
|
||||
|
||||
const realUserTextParts = ctx.parts.filter(isRealUserTextPart)
|
||||
if (realUserTextParts.length === 0) {
|
||||
return { block: false, modifiedParts, messages }
|
||||
}
|
||||
|
||||
// Check if hook tags are in the current user input only (not in injected context)
|
||||
// by checking only the text parts that were provided in this message
|
||||
const userInputText = ctx.parts
|
||||
.filter((p) => p.type === "text" && p.text)
|
||||
const userInputText = realUserTextParts
|
||||
.map((p) => p.text ?? "")
|
||||
.join("\n")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user