mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-19 18:39:20 -05:00
Agents: add before_message_write persistence regression tests
This commit is contained in:
@@ -82,6 +82,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Agents/Tools/exec: treat normal non-zero exit codes as completed and append the exit code to tool output to avoid false tool-failure warnings. (#18425)
|
||||
- Agents/Tools: make loop detection progress-aware and phased by hard-blocking known `process(action=poll|log)` no-progress loops, warning on generic identical-call repeats, warning + no-progress-blocking ping-pong alternation loops (10/20), coalescing repeated warning spam into threshold buckets (including canonical ping-pong pairs), adding a global circuit breaker at 30 no-progress repeats, and emitting structured diagnostic `tool.loop` warning/error events for loop actions. (#16808) Thanks @akramcodez and @beca-oc.
|
||||
- Agents/Hooks: preserve the `before_tool_call` wrapped-marker across abort-signal tool wrapping so the hook runs once per tool call in normal agent sessions. (#16852) Thanks @sreuter.
|
||||
- Agents/Tests: add `before_message_write` persistence regression coverage for block/mutate behavior (including synthetic tool-result flushes) and thrown-hook fallback persistence. (#18197) Thanks @shakkernerd
|
||||
- Agents/Tools: scope the `message` tool schema to the active channel so Telegram uses `buttons` and Discord uses `components`. (#18215) Thanks @obviyus.
|
||||
- Agents/Image tool: replace Anthropic-incompatible union schema with explicit `image` (single) and `images` (multi) parameters, keeping tool schemas `anyOf`/`oneOf`/`allOf`-free while preserving multi-image analysis support. (#18551, #18566) Thanks @aldoeliacim.
|
||||
- Agents/Models: probe the primary model when its auth-profile cooldown is near expiry (with per-provider throttling), so runs recover from temporary rate limits without staying on fallback models until restart. (#17478) Thanks @PlayerGhost.
|
||||
|
||||
@@ -261,6 +261,63 @@ describe("installSessionToolResultGuard", () => {
|
||||
expect(text).toBe(originalText);
|
||||
});
|
||||
|
||||
it("blocks persistence when before_message_write returns block=true", () => {
|
||||
const sm = SessionManager.inMemory();
|
||||
installSessionToolResultGuard(sm, {
|
||||
beforeMessageWriteHook: () => ({ block: true }),
|
||||
});
|
||||
|
||||
sm.appendMessage(
|
||||
asAppendMessage({
|
||||
role: "user",
|
||||
content: "hidden",
|
||||
timestamp: Date.now(),
|
||||
}),
|
||||
);
|
||||
|
||||
expect(getPersistedMessages(sm)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("applies before_message_write message mutations before persistence", () => {
|
||||
const sm = SessionManager.inMemory();
|
||||
installSessionToolResultGuard(sm, {
|
||||
beforeMessageWriteHook: ({ message }) => {
|
||||
if ((message as { role?: string }).role !== "toolResult") {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
message: {
|
||||
...(message as unknown as Record<string, unknown>),
|
||||
content: [{ type: "text", text: "rewritten by hook" }],
|
||||
} as unknown as AgentMessage,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
appendToolResultText(sm, "original");
|
||||
|
||||
const text = getToolResultText(getPersistedMessages(sm));
|
||||
expect(text).toBe("rewritten by hook");
|
||||
});
|
||||
|
||||
it("applies before_message_write to synthetic tool-result flushes", () => {
|
||||
const sm = SessionManager.inMemory();
|
||||
const guard = installSessionToolResultGuard(sm, {
|
||||
beforeMessageWriteHook: ({ message }) => {
|
||||
if ((message as { role?: string }).role !== "toolResult") {
|
||||
return undefined;
|
||||
}
|
||||
return { block: true };
|
||||
},
|
||||
});
|
||||
|
||||
sm.appendMessage(toolCallMessage);
|
||||
guard.flushPendingToolResults();
|
||||
|
||||
const messages = getPersistedMessages(sm);
|
||||
expect(messages.map((m) => m.role)).toEqual(["assistant"]);
|
||||
});
|
||||
|
||||
it("applies message persistence transform to user messages", () => {
|
||||
const sm = SessionManager.inMemory();
|
||||
installSessionToolResultGuard(sm, {
|
||||
|
||||
@@ -129,3 +129,51 @@ describe("tool_result_persist hook", () => {
|
||||
expect(toolResult.details).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("before_message_write hook", () => {
|
||||
it("continues persistence when a before_message_write hook throws", () => {
|
||||
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-before-write-"));
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = "/nonexistent/bundled/plugins";
|
||||
|
||||
const plugin = writeTempPlugin({
|
||||
dir: tmp,
|
||||
id: "before-write-throws",
|
||||
body: `export default { id: "before-write-throws", register(api) {
|
||||
api.on("before_message_write", () => {
|
||||
throw new Error("boom");
|
||||
}, { priority: 10 });
|
||||
} };`,
|
||||
});
|
||||
|
||||
const registry = loadOpenClawPlugins({
|
||||
cache: false,
|
||||
workspaceDir: tmp,
|
||||
config: {
|
||||
plugins: {
|
||||
load: { paths: [plugin] },
|
||||
allow: ["before-write-throws"],
|
||||
},
|
||||
},
|
||||
});
|
||||
initializeGlobalHookRunner(registry);
|
||||
|
||||
const sm = guardSessionManager(SessionManager.inMemory(), {
|
||||
agentId: "main",
|
||||
sessionKey: "main",
|
||||
});
|
||||
const appendMessage = sm.appendMessage.bind(sm) as unknown as (message: AgentMessage) => void;
|
||||
appendMessage({
|
||||
role: "user",
|
||||
content: "hello",
|
||||
timestamp: Date.now(),
|
||||
} as AgentMessage);
|
||||
|
||||
const messages = sm
|
||||
.getEntries()
|
||||
.filter((e) => e.type === "message")
|
||||
.map((e) => (e as { message: AgentMessage }).message);
|
||||
|
||||
expect(messages).toHaveLength(1);
|
||||
expect(messages[0]?.role).toBe("user");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user