mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-28 11:23:33 +00:00
Summary: - The PR changes shared poll-intent detection so `pollDurationHours` and `pollMulti` alone no longer make `send` actions fail, with focused unit and outbound validation coverage. - PR surface: Source -2, Tests +40. Total +38 across 3 files. - Reproducibility: yes. Source inspection shows current main and `v2026.5.28` expose `pollDurationHours` throu ... d message schema, classify non-zero shared duration as poll intent, and throw before a `send` can dispatch. Automerge notes: - No ClawSweeper repair was needed after automerge opt-in. Validation: - ClawSweeper review passed for head0fd95756cd. - Required merge gates passed before the squash merge. Prepared head SHA:0fd95756cdReview: https://github.com/openclaw/openclaw/pull/89601#issuecomment-4606487310 Co-authored-by: Gabriel Fratica <gabriel@codez.ro> 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>
88 lines
4.2 KiB
TypeScript
88 lines
4.2 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { hasPollCreationParams } from "./poll-params.js";
|
|
|
|
describe("poll params", () => {
|
|
it("does not treat explicit false booleans as poll creation params", () => {
|
|
expect(
|
|
hasPollCreationParams({
|
|
pollMulti: false,
|
|
pollAnonymous: false,
|
|
pollPublic: false,
|
|
}),
|
|
).toBe(false);
|
|
});
|
|
|
|
it.each([{ key: "pollAnonymous" }, { key: "pollPublic" }])(
|
|
"treats channel-extra $key=true as poll creation intent",
|
|
({ key }) => {
|
|
expect(
|
|
hasPollCreationParams({
|
|
[key]: true,
|
|
}),
|
|
).toBe(true);
|
|
},
|
|
);
|
|
|
|
it("treats non-zero finite numeric channel-extra poll params as poll creation intent", () => {
|
|
expect(hasPollCreationParams({ pollDurationSeconds: 60 })).toBe(true);
|
|
expect(hasPollCreationParams({ pollDurationSeconds: "60" })).toBe(true);
|
|
expect(hasPollCreationParams({ pollDurationSeconds: "+60" })).toBe(true);
|
|
expect(hasPollCreationParams({ pollDurationSeconds: "1e3" })).toBe(true);
|
|
expect(hasPollCreationParams({ pollDurationSeconds: "-5" })).toBe(true);
|
|
expect(hasPollCreationParams({ pollDurationSeconds: Infinity })).toBe(false);
|
|
expect(hasPollCreationParams({ pollDurationSeconds: "60abc" })).toBe(false);
|
|
expect(hasPollCreationParams({ pollDurationSeconds: "0x10" })).toBe(false);
|
|
});
|
|
|
|
it("does not treat zero-valued numeric channel-extra poll params as poll creation intent", () => {
|
|
// Zero values are typically defaults/unset values from tool schemas,
|
|
// not intentional poll creation. Fixes #52118.
|
|
expect(hasPollCreationParams({ pollDurationSeconds: 0 })).toBe(false);
|
|
expect(hasPollCreationParams({ poll_duration_seconds: 0 })).toBe(false);
|
|
});
|
|
|
|
it("does not treat shared modifier params (pollDurationHours, pollMulti) as poll creation intent without a question or options", () => {
|
|
// These two are exposed by the shared `message` tool schema for both
|
|
// `send` and `poll` actions, so LLMs routinely schema-pad them on every
|
|
// plain `send` call with their schema-implied defaults (1 for an integer
|
|
// with `minimum: 1`, `false` for a boolean). Treating those defaults as
|
|
// poll intent blocks routine sends — see the regression that motivated
|
|
// this carve-out.
|
|
expect(hasPollCreationParams({ pollDurationHours: 1 })).toBe(false);
|
|
expect(hasPollCreationParams({ pollDurationHours: 1, pollMulti: false })).toBe(false);
|
|
expect(hasPollCreationParams({ pollDurationHours: 0 })).toBe(false);
|
|
expect(hasPollCreationParams({ pollDurationHours: -1 })).toBe(false);
|
|
expect(hasPollCreationParams({ pollDurationHours: "0" })).toBe(false);
|
|
expect(hasPollCreationParams({ pollDurationHours: Number.NaN })).toBe(false);
|
|
expect(hasPollCreationParams({ poll_duration_hours: "0" })).toBe(false);
|
|
expect(hasPollCreationParams({ pollMulti: true })).toBe(false);
|
|
});
|
|
|
|
it("still flags shared modifier params when accompanied by a question or options", () => {
|
|
expect(hasPollCreationParams({ pollQuestion: "Ready?", pollDurationHours: 1 })).toBe(true);
|
|
expect(hasPollCreationParams({ pollOption: ["Yes", "No"], pollMulti: true })).toBe(true);
|
|
});
|
|
|
|
it("treats string-encoded boolean poll params as poll creation intent when true", () => {
|
|
expect(hasPollCreationParams({ pollPublic: "true" })).toBe(true);
|
|
expect(hasPollCreationParams({ pollAnonymous: "false" })).toBe(false);
|
|
});
|
|
|
|
it("treats string poll options as poll creation intent", () => {
|
|
expect(hasPollCreationParams({ pollOption: "Yes" })).toBe(true);
|
|
});
|
|
|
|
it("detects snake_case poll fields as poll creation intent", () => {
|
|
expect(hasPollCreationParams({ poll_question: "Lunch?" })).toBe(true);
|
|
expect(hasPollCreationParams({ poll_option: ["Pizza", "Sushi"] })).toBe(true);
|
|
expect(hasPollCreationParams({ poll_duration_seconds: "60" })).toBe(true);
|
|
expect(hasPollCreationParams({ poll_public: "true" })).toBe(true);
|
|
});
|
|
|
|
it("ignores poll vote params when deciding whether send should become poll", () => {
|
|
expect(hasPollCreationParams({ pollId: "poll-1" })).toBe(false);
|
|
expect(hasPollCreationParams({ pollOptionId: "answer-1" })).toBe(false);
|
|
expect(hasPollCreationParams({ pollOptionIndexes: [1] })).toBe(false);
|
|
});
|
|
});
|