fix: wire 9 unwired plugin hooks to core code (openclaw#14882) thanks @shtse8

Verified:
- GitHub CI checks green (non-skipped)

Co-authored-by: shtse8 <8020099+shtse8@users.noreply.github.com>
This commit is contained in:
Kyle Tse
2026-02-13 00:14:14 +00:00
committed by GitHub
parent 957b883082
commit 2655041f69
11 changed files with 14750 additions and 2 deletions

View File

@@ -0,0 +1,200 @@
/**
* Test: after_tool_call hook wiring (pi-embedded-subscribe.handlers.tools.ts)
*/
import { beforeEach, describe, expect, it, vi } from "vitest";
const hookMocks = vi.hoisted(() => ({
runner: {
hasHooks: vi.fn(() => false),
runAfterToolCall: vi.fn(async () => {}),
},
}));
vi.mock("../plugins/hook-runner-global.js", () => ({
getGlobalHookRunner: () => hookMocks.runner,
}));
// Mock agent events (used by handlers)
vi.mock("../infra/agent-events.js", () => ({
emitAgentEvent: vi.fn(),
}));
describe("after_tool_call hook wiring", () => {
beforeEach(() => {
hookMocks.runner.hasHooks.mockReset();
hookMocks.runner.hasHooks.mockReturnValue(false);
hookMocks.runner.runAfterToolCall.mockReset();
hookMocks.runner.runAfterToolCall.mockResolvedValue(undefined);
});
it("calls runAfterToolCall in handleToolExecutionEnd when hook is registered", async () => {
hookMocks.runner.hasHooks.mockReturnValue(true);
const { handleToolExecutionEnd, handleToolExecutionStart } =
await import("../agents/pi-embedded-subscribe.handlers.tools.js");
const ctx = {
params: {
runId: "test-run-1",
session: { messages: [] },
agentId: "main",
sessionKey: "test-session",
onBlockReplyFlush: undefined,
},
state: {
toolMetaById: new Map<string, string | undefined>(),
toolMetas: [] as Array<{ toolName?: string; meta?: string }>,
toolSummaryById: new Set<string>(),
lastToolError: undefined,
pendingMessagingTexts: new Map<string, string>(),
pendingMessagingTargets: new Map<string, unknown>(),
messagingToolSentTexts: [] as string[],
messagingToolSentTextsNormalized: [] as string[],
messagingToolSentTargets: [] as unknown[],
blockBuffer: "",
},
log: { debug: vi.fn(), warn: vi.fn() },
flushBlockReplyBuffer: vi.fn(),
shouldEmitToolResult: () => false,
shouldEmitToolOutput: () => false,
emitToolSummary: vi.fn(),
emitToolOutput: vi.fn(),
trimMessagingToolSent: vi.fn(),
};
await handleToolExecutionStart(
ctx as never,
{
type: "tool_execution_start",
toolName: "read",
toolCallId: "call-1",
args: { path: "/tmp/file.txt" },
} as never,
);
handleToolExecutionEnd(
ctx as never,
{
type: "tool_execution_end",
toolName: "read",
toolCallId: "call-1",
isError: false,
result: { content: [{ type: "text", text: "file contents" }] },
} as never,
);
await vi.waitFor(() => {
expect(hookMocks.runner.runAfterToolCall).toHaveBeenCalledTimes(1);
});
const [event, context] = hookMocks.runner.runAfterToolCall.mock.calls[0];
expect(event.toolName).toBe("read");
expect(event.params).toEqual({ path: "/tmp/file.txt" });
expect(event.error).toBeUndefined();
expect(typeof event.durationMs).toBe("number");
expect(context.toolName).toBe("read");
});
it("includes error in after_tool_call event on tool failure", async () => {
hookMocks.runner.hasHooks.mockReturnValue(true);
const { handleToolExecutionEnd, handleToolExecutionStart } =
await import("../agents/pi-embedded-subscribe.handlers.tools.js");
const ctx = {
params: {
runId: "test-run-2",
session: { messages: [] },
onBlockReplyFlush: undefined,
},
state: {
toolMetaById: new Map<string, string | undefined>(),
toolMetas: [] as Array<{ toolName?: string; meta?: string }>,
toolSummaryById: new Set<string>(),
lastToolError: undefined,
pendingMessagingTexts: new Map<string, string>(),
pendingMessagingTargets: new Map<string, unknown>(),
messagingToolSentTexts: [] as string[],
messagingToolSentTextsNormalized: [] as string[],
messagingToolSentTargets: [] as unknown[],
blockBuffer: "",
},
log: { debug: vi.fn(), warn: vi.fn() },
flushBlockReplyBuffer: vi.fn(),
shouldEmitToolResult: () => false,
shouldEmitToolOutput: () => false,
emitToolSummary: vi.fn(),
emitToolOutput: vi.fn(),
trimMessagingToolSent: vi.fn(),
};
await handleToolExecutionStart(
ctx as never,
{
type: "tool_execution_start",
toolName: "exec",
toolCallId: "call-err",
args: { command: "fail" },
} as never,
);
handleToolExecutionEnd(
ctx as never,
{
type: "tool_execution_end",
toolName: "exec",
toolCallId: "call-err",
isError: true,
result: { status: "error", error: "command failed" },
} as never,
);
await vi.waitFor(() => {
expect(hookMocks.runner.runAfterToolCall).toHaveBeenCalledTimes(1);
});
const [event] = hookMocks.runner.runAfterToolCall.mock.calls[0];
expect(event.error).toBeDefined();
});
it("does not call runAfterToolCall when no hooks registered", async () => {
hookMocks.runner.hasHooks.mockReturnValue(false);
const { handleToolExecutionEnd } =
await import("../agents/pi-embedded-subscribe.handlers.tools.js");
const ctx = {
params: { runId: "r", session: { messages: [] } },
state: {
toolMetaById: new Map<string, string | undefined>(),
toolMetas: [] as Array<{ toolName?: string; meta?: string }>,
toolSummaryById: new Set<string>(),
lastToolError: undefined,
pendingMessagingTexts: new Map<string, string>(),
pendingMessagingTargets: new Map<string, unknown>(),
messagingToolSentTexts: [] as string[],
messagingToolSentTextsNormalized: [] as string[],
messagingToolSentTargets: [] as unknown[],
},
log: { debug: vi.fn(), warn: vi.fn() },
shouldEmitToolResult: () => false,
shouldEmitToolOutput: () => false,
emitToolSummary: vi.fn(),
emitToolOutput: vi.fn(),
trimMessagingToolSent: vi.fn(),
};
handleToolExecutionEnd(
ctx as never,
{
type: "tool_execution_end",
toolName: "exec",
toolCallId: "call-2",
isError: false,
result: {},
} as never,
);
expect(hookMocks.runner.runAfterToolCall).not.toHaveBeenCalled();
});
});