Merge pull request #616 from neist/feat/signal-reactions
feat(signal): add reaction message support
This commit is contained in:
@@ -102,6 +102,7 @@
|
|||||||
- Pairing: replies now include sender ids for Discord/Slack/Signal/iMessage/WhatsApp; pairing list labels them explicitly.
|
- Pairing: replies now include sender ids for Discord/Slack/Signal/iMessage/WhatsApp; pairing list labels them explicitly.
|
||||||
- Messages: default inbound/outbound prefixes from the routed agent’s `identity.name` when set. (#578) — thanks @p6l-richard
|
- Messages: default inbound/outbound prefixes from the routed agent’s `identity.name` when set. (#578) — thanks @p6l-richard
|
||||||
- Signal: accept UUID-only senders for pairing/allowlists/routing when sourceNumber is missing. (#523) — thanks @neist
|
- Signal: accept UUID-only senders for pairing/allowlists/routing when sourceNumber is missing. (#523) — thanks @neist
|
||||||
|
- Signal: ignore reaction-only messages so they don't surface as unknown media. (#616) — thanks @neist
|
||||||
- Agent system prompt: avoid automatic self-updates unless explicitly requested.
|
- Agent system prompt: avoid automatic self-updates unless explicitly requested.
|
||||||
- Onboarding: tighten QuickStart hint copy for configuring later.
|
- Onboarding: tighten QuickStart hint copy for configuring later.
|
||||||
- Onboarding: set Gemini 3 Pro as the default model for Gemini API key auth. (#489) — thanks @jonasjancarik
|
- Onboarding: set Gemini 3 Pro as the default model for Gemini API key auth. (#489) — thanks @jonasjancarik
|
||||||
|
|||||||
@@ -153,6 +153,42 @@ describe("monitorSignalProvider tool results", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("ignores reaction-only messages", async () => {
|
||||||
|
const abortController = new AbortController();
|
||||||
|
|
||||||
|
streamMock.mockImplementation(async ({ onEvent }) => {
|
||||||
|
const payload = {
|
||||||
|
envelope: {
|
||||||
|
sourceNumber: "+15550001111",
|
||||||
|
sourceName: "Ada",
|
||||||
|
timestamp: 1,
|
||||||
|
reactionMessage: {
|
||||||
|
emoji: "👍",
|
||||||
|
targetAuthor: "+15550002222",
|
||||||
|
targetSentTimestamp: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
await onEvent({
|
||||||
|
event: "receive",
|
||||||
|
data: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
abortController.abort();
|
||||||
|
});
|
||||||
|
|
||||||
|
await monitorSignalProvider({
|
||||||
|
autoStart: false,
|
||||||
|
baseUrl: "http://127.0.0.1:8080",
|
||||||
|
abortSignal: abortController.signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
await flush();
|
||||||
|
|
||||||
|
expect(replyMock).not.toHaveBeenCalled();
|
||||||
|
expect(sendMock).not.toHaveBeenCalled();
|
||||||
|
expect(updateLastRouteMock).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it("does not resend pairing code when a request is already pending", async () => {
|
it("does not resend pairing code when a request is already pending", async () => {
|
||||||
config = {
|
config = {
|
||||||
...config,
|
...config,
|
||||||
|
|||||||
@@ -41,6 +41,15 @@ type SignalEnvelope = {
|
|||||||
dataMessage?: SignalDataMessage | null;
|
dataMessage?: SignalDataMessage | null;
|
||||||
editMessage?: { dataMessage?: SignalDataMessage | null } | null;
|
editMessage?: { dataMessage?: SignalDataMessage | null } | null;
|
||||||
syncMessage?: unknown;
|
syncMessage?: unknown;
|
||||||
|
reactionMessage?: SignalReactionMessage | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type SignalReactionMessage = {
|
||||||
|
emoji?: string | null;
|
||||||
|
targetAuthor?: string | null;
|
||||||
|
targetAuthorUuid?: string | null;
|
||||||
|
targetSentTimestamp?: number | null;
|
||||||
|
isRemove?: boolean | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
type SignalDataMessage = {
|
type SignalDataMessage = {
|
||||||
@@ -305,8 +314,22 @@ export async function monitorSignalProvider(
|
|||||||
const envelope = payload?.envelope;
|
const envelope = payload?.envelope;
|
||||||
if (!envelope) return;
|
if (!envelope) return;
|
||||||
if (envelope.syncMessage) return;
|
if (envelope.syncMessage) return;
|
||||||
|
|
||||||
const dataMessage =
|
const dataMessage =
|
||||||
envelope.dataMessage ?? envelope.editMessage?.dataMessage;
|
envelope.dataMessage ?? envelope.editMessage?.dataMessage;
|
||||||
|
if (envelope.reactionMessage && !dataMessage) {
|
||||||
|
const reaction = envelope.reactionMessage;
|
||||||
|
if (reaction.isRemove) return; // Ignore reaction removals
|
||||||
|
const emoji = reaction.emoji ?? "unknown";
|
||||||
|
const sender = resolveSignalSender(envelope);
|
||||||
|
if (!sender) return;
|
||||||
|
const senderDisplay = formatSignalSenderDisplay(sender);
|
||||||
|
const senderName = envelope.sourceName ?? senderDisplay;
|
||||||
|
logVerbose(`signal reaction: ${emoji} from ${senderName}`);
|
||||||
|
// Skip processing reactions as messages for now - just log them
|
||||||
|
// Future: could dispatch as a notification or store for context
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!dataMessage) return;
|
if (!dataMessage) return;
|
||||||
|
|
||||||
const sender = resolveSignalSender(envelope);
|
const sender = resolveSignalSender(envelope);
|
||||||
|
|||||||
Reference in New Issue
Block a user