mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-28 08:52:12 +00:00
Summary: - The PR adds an awaited `agent_end` helper, uses it for no-channel local CLI and Codex app-server terminal pa ... erves fire-and-forget behavior for channel-backed paths, and updates docs, changelog, and regression tests. - Reproducibility: yes. by source inspection. Current main calls `runAgentHarnessAgentEndHook` without awaiting in local CLI and Codex terminal paths, and the PR's pending-hook tests encode the failure and desired split. Automerge notes: - PR branch already contained follow-up commit before automerge: fix(agents): await local agent_end hooks - PR branch already contained follow-up commit before automerge: test: fix agent_end hook helper fixture - PR branch already contained follow-up commit before automerge: ci: retry security checkout - PR branch already contained follow-up commit before automerge: ci: allowlist qa-lab lifecycle fixtures - PR branch already contained follow-up commit before automerge: fix CLI channel agent_end delivery - PR branch already contained follow-up commit before automerge: ci: drop stale qa-lab deadcode entries Validation: - ClawSweeper review passed for head97b31379d7. - Required merge gates passed before the squash merge. Prepared head SHA:97b31379d7Review: https://github.com/openclaw/openclaw/pull/85007#issuecomment-4509911851 Co-authored-by: Kaspre <kaspre@gmail.com> Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com> Approved-by: takhoffman Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
177 lines
5.0 KiB
TypeScript
177 lines
5.0 KiB
TypeScript
import { spawnSync } from "node:child_process";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { createHookRunner } from "./hooks.js";
|
|
import { addTestHook, TEST_PLUGIN_AGENT_CTX } from "./hooks.test-helpers.js";
|
|
import { createEmptyPluginRegistry, type PluginRegistry } from "./registry.js";
|
|
import type { PluginHookRegistration } from "./types.js";
|
|
|
|
describe("hook correlation fields", () => {
|
|
let registry: PluginRegistry;
|
|
|
|
beforeEach(() => {
|
|
registry = createEmptyPluginRegistry();
|
|
});
|
|
|
|
it("adds runId to legacy before_agent_start events from hook context", async () => {
|
|
const handler = vi.fn(() => undefined);
|
|
addTestHook({
|
|
registry,
|
|
pluginId: "plugin-a",
|
|
hookName: "before_agent_start",
|
|
handler: handler as PluginHookRegistration["handler"],
|
|
});
|
|
|
|
const runner = createHookRunner(registry);
|
|
await runner.runBeforeAgentStart({ prompt: "hello" }, TEST_PLUGIN_AGENT_CTX);
|
|
|
|
expect(handler).toHaveBeenCalledWith(
|
|
{ prompt: "hello", runId: "test-run-id" },
|
|
TEST_PLUGIN_AGENT_CTX,
|
|
);
|
|
});
|
|
|
|
it("adds runId to agent_end events from hook context", async () => {
|
|
const handler = vi.fn(() => undefined);
|
|
addTestHook({
|
|
registry,
|
|
pluginId: "plugin-a",
|
|
hookName: "agent_end",
|
|
handler: handler as PluginHookRegistration["handler"],
|
|
});
|
|
|
|
const runner = createHookRunner(registry);
|
|
await runner.runAgentEnd(
|
|
{
|
|
messages: [],
|
|
success: true,
|
|
},
|
|
TEST_PLUGIN_AGENT_CTX,
|
|
);
|
|
|
|
expect(handler).toHaveBeenCalledWith(
|
|
{ messages: [], success: true, runId: "test-run-id" },
|
|
TEST_PLUGIN_AGENT_CTX,
|
|
);
|
|
});
|
|
|
|
it("times out never-settling agent_end handlers", async () => {
|
|
vi.useFakeTimers();
|
|
try {
|
|
const handler = vi.fn(() => new Promise<void>(() => {}));
|
|
addTestHook({
|
|
registry,
|
|
pluginId: "plugin-a",
|
|
hookName: "agent_end",
|
|
handler: handler as PluginHookRegistration["handler"],
|
|
});
|
|
const logger = {
|
|
error: vi.fn(),
|
|
warn: vi.fn(),
|
|
};
|
|
|
|
const runner = createHookRunner(registry, {
|
|
logger,
|
|
voidHookTimeoutMsByHook: { agent_end: 5 },
|
|
});
|
|
const run = runner.runAgentEnd({ messages: [], success: true }, TEST_PLUGIN_AGENT_CTX);
|
|
|
|
await vi.advanceTimersByTimeAsync(5);
|
|
|
|
await expect(run).resolves.toBeUndefined();
|
|
expect(logger.error).toHaveBeenCalledWith(
|
|
"[hooks] agent_end handler from plugin-a failed: timed out after 5ms",
|
|
);
|
|
} finally {
|
|
vi.useRealTimers();
|
|
}
|
|
});
|
|
|
|
it("keeps one-shot agent_end runs alive until a ref'd timeout fires", () => {
|
|
const script = `
|
|
import { createHookRunner } from "./src/plugins/hooks.ts";
|
|
const registry = {
|
|
typedHooks: [{
|
|
pluginId: "plugin-a",
|
|
hookName: "agent_end",
|
|
handler: () => new Promise(() => {}),
|
|
priority: 0,
|
|
source: "test",
|
|
}],
|
|
};
|
|
const logger = {
|
|
error: (message) => console.error(message),
|
|
warn: (message) => console.warn(message),
|
|
};
|
|
const runner = createHookRunner(registry, {
|
|
logger,
|
|
voidHookTimeoutMsByHook: { agent_end: 20 },
|
|
});
|
|
await runner.runAgentEnd(
|
|
{ messages: [], success: true },
|
|
{
|
|
runId: "test-run-id",
|
|
agentId: "test-agent",
|
|
sessionKey: "test-session",
|
|
sessionId: "test-session-id",
|
|
workspaceDir: "/tmp/openclaw-test",
|
|
messageProvider: "test",
|
|
},
|
|
{ unrefTimeout: false },
|
|
);
|
|
console.log("settled-after-timeout");
|
|
`;
|
|
|
|
const child = spawnSync(
|
|
process.execPath,
|
|
["--import", "tsx", "--input-type=module", "-e", script],
|
|
{
|
|
cwd: process.cwd(),
|
|
encoding: "utf8",
|
|
timeout: 3_000,
|
|
},
|
|
);
|
|
|
|
expect(child.status).toBe(0);
|
|
expect(child.stderr).toContain(
|
|
"[hooks] agent_end handler from plugin-a failed: timed out after 20ms",
|
|
);
|
|
expect(child.stdout).toContain("settled-after-timeout");
|
|
});
|
|
|
|
it("honors per-hook registration timeouts over the default void hook timeout", async () => {
|
|
vi.useFakeTimers();
|
|
try {
|
|
const handler = vi.fn(
|
|
async () =>
|
|
await new Promise<void>((resolve) => {
|
|
setTimeout(resolve, 20);
|
|
}),
|
|
);
|
|
addTestHook({
|
|
registry,
|
|
pluginId: "plugin-a",
|
|
hookName: "agent_end",
|
|
handler: handler as PluginHookRegistration["handler"],
|
|
timeoutMs: 30,
|
|
});
|
|
const logger = {
|
|
error: vi.fn(),
|
|
warn: vi.fn(),
|
|
};
|
|
|
|
const runner = createHookRunner(registry, {
|
|
logger,
|
|
voidHookTimeoutMsByHook: { agent_end: 5 },
|
|
});
|
|
const run = runner.runAgentEnd({ messages: [], success: true }, TEST_PLUGIN_AGENT_CTX);
|
|
|
|
await vi.advanceTimersByTimeAsync(20);
|
|
|
|
await expect(run).resolves.toBeUndefined();
|
|
expect(logger.error).not.toHaveBeenCalled();
|
|
} finally {
|
|
vi.useRealTimers();
|
|
}
|
|
});
|
|
});
|