From d9fdec12abe9dd5a68fb703d601537ce1149bc90 Mon Sep 17 00:00:00 2001 From: dunamismax Date: Mon, 2 Mar 2026 17:10:40 -0500 Subject: [PATCH] fix(signal): fall back to toolContext.currentMessageId for reactions Signal reactions required an explicit messageId parameter, unlike Telegram which already fell back to toolContext.currentMessageId. This made agent-initiated reactions fail on Signal because the inbound message ID was available in tool context but never used. - Destructure toolContext in Signal action handler - Fall back to toolContext.currentMessageId when messageId omitted - Update reaction schema descriptions (not Telegram-specific) - Add tests for fallback and missing-messageId rejection Closes #17651 --- src/agents/tools/message-tool.ts | 4 +-- src/channels/plugins/actions/actions.test.ts | 34 +++++++++++++++++++- src/channels/plugins/actions/signal.ts | 14 +++++--- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/agents/tools/message-tool.ts b/src/agents/tools/message-tool.ts index 4e8d4a2efe3..098368fe9e3 100644 --- a/src/agents/tools/message-tool.ts +++ b/src/agents/tools/message-tool.ts @@ -242,14 +242,14 @@ function buildReactionSchema() { messageId: Type.Optional( Type.String({ description: - "Target message id for reaction. For Telegram, if omitted, defaults to the current inbound message id when available.", + "Target message id for reaction. If omitted, defaults to the current inbound message id when available.", }), ), message_id: Type.Optional( Type.String({ // Intentional duplicate alias for tool-schema discoverability in LLMs. description: - "snake_case alias of messageId. For Telegram, if omitted, defaults to the current inbound message id when available.", + "snake_case alias of messageId. If omitted, defaults to the current inbound message id when available.", }), ), emoji: Type.Optional(Type.String()), diff --git a/src/channels/plugins/actions/actions.test.ts b/src/channels/plugins/actions/actions.test.ts index d88e2af49a9..e82dce0a76f 100644 --- a/src/channels/plugins/actions/actions.test.ts +++ b/src/channels/plugins/actions/actions.test.ts @@ -61,7 +61,11 @@ type SignalActionInput = Parameters { } }); + it("falls back to toolContext.currentMessageId for reactions when messageId is omitted", async () => { + sendReactionSignal.mockClear(); + await runSignalAction( + "react", + { to: "+15559999999", emoji: "🔥" }, + { toolContext: { currentMessageId: "1737630212345" } }, + ); + expect(sendReactionSignal).toHaveBeenCalledTimes(1); + expect(sendReactionSignal).toHaveBeenCalledWith( + "+15559999999", + 1737630212345, + "🔥", + expect.objectContaining({}), + ); + }); + + it("rejects reaction when neither messageId nor toolContext.currentMessageId is provided", async () => { + const cfg = { + channels: { signal: { account: "+15550001111" } }, + } as OpenClawConfig; + await expectSignalActionRejected( + { to: "+15559999999", emoji: "✅" }, + /messageId.*required/, + cfg, + ); + }); + it("requires targetAuthor for group reactions", async () => { const cfg = { channels: { signal: { account: "+15550001111" } }, diff --git a/src/channels/plugins/actions/signal.ts b/src/channels/plugins/actions/signal.ts index db1f06579a2..ff5433b3895 100644 --- a/src/channels/plugins/actions/signal.ts +++ b/src/channels/plugins/actions/signal.ts @@ -90,7 +90,7 @@ export const signalMessageActions: ChannelMessageActionAdapter = { }, supportsAction: ({ action }) => action !== "send", - handleAction: async ({ action, params, cfg, accountId }) => { + handleAction: async ({ action, params, cfg, accountId, toolContext }) => { if (action === "send") { throw new Error("Send should be handled by outbound, not actions handler."); } @@ -126,10 +126,14 @@ export const signalMessageActions: ChannelMessageActionAdapter = { throw new Error("recipient or group required"); } - const messageId = readStringParam(params, "messageId", { - required: true, - label: "messageId (timestamp)", - }); + const messageId = + readStringParam(params, "messageId") ?? + (toolContext?.currentMessageId != null ? String(toolContext.currentMessageId) : undefined); + if (!messageId) { + throw new Error( + "messageId (timestamp) required. Provide messageId explicitly or react to the current inbound message.", + ); + } const targetAuthor = readStringParam(params, "targetAuthor"); const targetAuthorUuid = readStringParam(params, "targetAuthorUuid"); if (target.groupId && !targetAuthor && !targetAuthorUuid) {