mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-23 14:45:46 +00:00
fix(poll-params): treat zero-valued numeric poll params as unset (#52150)
Merged via squash.
Prepared head SHA: 189e695b7c
Co-authored-by: Bartok9 <259807879+Bartok9@users.noreply.github.com>
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Reviewed-by: @frankekn
This commit is contained in:
@@ -203,6 +203,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Telegram/security: add regression coverage proving pinned fallback host overrides stay bound to Telegram and delegate non-matching hostnames back to the original lookup path. Thanks @vincentkoc.
|
||||
- Secrets/exec refs: require explicit `--allow-exec` for `secrets apply` write plans that contain exec SecretRefs/providers, and align audit/configure/apply dry-run behavior to skip exec checks unless opted in to prevent unexpected command side effects. (#49417) Thanks @restriction and @joshavant.
|
||||
- Tools/image generation: add bundled fal image generation support so `image_generate` can target `fal/*` models with `FAL_KEY`, including single-image edit flows via FLUX image-to-image. Thanks @vincentkoc.
|
||||
- Messages/polls: treat zero-valued poll params on `message.send` as unset defaults while keeping non-zero poll params on the poll validation path. (#52150) Fixes #52118. Thanks @Bartok9.
|
||||
- xAI/web search: add missing Grok credential metadata so the bundled provider registration type-checks again. (#49472) thanks @scoootscooob.
|
||||
- Signal/runtime API: re-export `SignalAccountConfig` so Signal account resolution type-checks again. (#49470) Thanks @scoootscooob.
|
||||
- Google Chat/runtime API: thin the private runtime barrel onto the curated public SDK surface while keeping public Google Chat exports intact. (#49504) Thanks @scoootscooob.
|
||||
|
||||
@@ -347,6 +347,15 @@ describe("runMessageAction context isolation", () => {
|
||||
poll_public: "true",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "negative poll duration params",
|
||||
actionParams: {
|
||||
channel: "slack",
|
||||
target: "#C12345678",
|
||||
message: "hi",
|
||||
pollDurationSeconds: -5,
|
||||
},
|
||||
},
|
||||
])("rejects send actions that include $name", async ({ actionParams }) => {
|
||||
await expect(
|
||||
runDrySend({
|
||||
|
||||
@@ -217,6 +217,66 @@ describe("runMessageAction plugin dispatch", () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("does not misclassify send as poll when zero-valued poll params are present", async () => {
|
||||
const sendMedia = vi.fn().mockResolvedValue({
|
||||
channel: "testchat",
|
||||
messageId: "m2",
|
||||
chatId: "c1",
|
||||
});
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([
|
||||
{
|
||||
pluginId: "testchat",
|
||||
source: "test",
|
||||
plugin: createOutboundTestPlugin({
|
||||
id: "testchat",
|
||||
outbound: {
|
||||
deliveryMode: "direct",
|
||||
sendText: vi.fn().mockResolvedValue({
|
||||
channel: "testchat",
|
||||
messageId: "t2",
|
||||
chatId: "c1",
|
||||
}),
|
||||
sendMedia,
|
||||
},
|
||||
}),
|
||||
},
|
||||
]),
|
||||
);
|
||||
const cfg = {
|
||||
channels: {
|
||||
testchat: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
const result = await runMessageAction({
|
||||
cfg,
|
||||
action: "send",
|
||||
params: {
|
||||
channel: "testchat",
|
||||
target: "channel:abc",
|
||||
media: "https://example.com/file.txt",
|
||||
message: "hello",
|
||||
pollDurationHours: 0,
|
||||
pollDurationSeconds: 0,
|
||||
pollMulti: false,
|
||||
pollQuestion: "",
|
||||
pollOption: [],
|
||||
},
|
||||
dryRun: false,
|
||||
});
|
||||
|
||||
expect(result.kind).toBe("send");
|
||||
expect(sendMedia).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
text: "hello",
|
||||
mediaUrl: "https://example.com/file.txt",
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("card-only send behavior", () => {
|
||||
|
||||
@@ -23,16 +23,27 @@ describe("poll params", () => {
|
||||
},
|
||||
);
|
||||
|
||||
it("treats finite numeric poll params as poll creation intent", () => {
|
||||
expect(hasPollCreationParams({ pollDurationHours: 0 })).toBe(true);
|
||||
it("treats non-zero finite numeric poll params as poll creation intent", () => {
|
||||
expect(hasPollCreationParams({ pollDurationSeconds: 60 })).toBe(true);
|
||||
expect(hasPollCreationParams({ pollDurationSeconds: "60" })).toBe(true);
|
||||
expect(hasPollCreationParams({ pollDurationSeconds: "1e3" })).toBe(true);
|
||||
expect(hasPollCreationParams({ pollDurationHours: -1 })).toBe(true);
|
||||
expect(hasPollCreationParams({ pollDurationSeconds: "-5" })).toBe(true);
|
||||
expect(hasPollCreationParams({ pollDurationHours: Number.NaN })).toBe(false);
|
||||
expect(hasPollCreationParams({ pollDurationSeconds: Infinity })).toBe(false);
|
||||
expect(hasPollCreationParams({ pollDurationSeconds: "60abc" })).toBe(false);
|
||||
});
|
||||
|
||||
it("does not treat zero-valued numeric poll params as poll creation intent", () => {
|
||||
// Zero values are typically defaults/unset values from tool schemas,
|
||||
// not intentional poll creation. Fixes #52118.
|
||||
expect(hasPollCreationParams({ pollDurationHours: 0 })).toBe(false);
|
||||
expect(hasPollCreationParams({ pollDurationSeconds: 0 })).toBe(false);
|
||||
expect(hasPollCreationParams({ pollDurationHours: "0" })).toBe(false);
|
||||
expect(hasPollCreationParams({ poll_duration_seconds: 0 })).toBe(false);
|
||||
expect(hasPollCreationParams({ poll_duration_hours: "0" })).toBe(false);
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
@@ -69,12 +69,16 @@ export function hasPollCreationParams(params: Record<string, unknown>): boolean
|
||||
}
|
||||
}
|
||||
if (def.kind === "number") {
|
||||
if (typeof value === "number" && Number.isFinite(value)) {
|
||||
// Treat zero-valued numeric defaults as unset, but preserve any non-zero
|
||||
// numeric value as explicit poll intent so invalid durations still hit
|
||||
// the poll-only validation path.
|
||||
if (typeof value === "number" && Number.isFinite(value) && value !== 0) {
|
||||
return true;
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
const trimmed = value.trim();
|
||||
if (trimmed.length > 0 && Number.isFinite(Number(trimmed))) {
|
||||
const parsed = Number(trimmed);
|
||||
if (trimmed.length > 0 && Number.isFinite(parsed) && parsed !== 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user