fix: signal own reactions match uuid + phone (#632) (thanks @neist)
Co-authored-by: neist <1029724+neist@users.noreply.github.com>
This commit is contained in:
@@ -27,6 +27,7 @@
|
|||||||
- Providers: add Microsoft Teams provider with polling, attachments, and CLI send support. (#404) — thanks @onutc
|
- Providers: add Microsoft Teams provider with polling, attachments, and CLI send support. (#404) — thanks @onutc
|
||||||
- Slack: honor reply tags + replyToMode while keeping threaded replies in-thread. (#574) — thanks @bolismauro
|
- Slack: honor reply tags + replyToMode while keeping threaded replies in-thread. (#574) — thanks @bolismauro
|
||||||
- Slack: configurable reply threading (`slack.replyToMode`) + proper mrkdwn formatting for outbound messages. (#464) — thanks @austinm911
|
- Slack: configurable reply threading (`slack.replyToMode`) + proper mrkdwn formatting for outbound messages. (#464) — thanks @austinm911
|
||||||
|
- Signal: match own-mode reactions when target includes uuid + phone. (#632) — thanks @neist
|
||||||
- Providers: remove ack reactions after reply on Discord/Slack/Telegram. (#633) — thanks @levifig
|
- Providers: remove ack reactions after reply on Discord/Slack/Telegram. (#633) — thanks @levifig
|
||||||
- Discord: avoid category parent overrides for channel allowlists and refactor thread context helpers. (#588) — thanks @steipete
|
- Discord: avoid category parent overrides for channel allowlists and refactor thread context helpers. (#588) — thanks @steipete
|
||||||
- Discord: fix forum thread starters and cache channel lookups for thread context. (#585) — thanks @thewilloftheshadow
|
- Discord: fix forum thread starters and cache channel lookups for thread context. (#585) — thanks @thewilloftheshadow
|
||||||
|
|||||||
@@ -249,6 +249,60 @@ describe("monitorSignalProvider tool results", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("notifies on own reactions when target includes uuid + phone", async () => {
|
||||||
|
config = {
|
||||||
|
...config,
|
||||||
|
signal: {
|
||||||
|
autoStart: false,
|
||||||
|
dmPolicy: "open",
|
||||||
|
allowFrom: ["*"],
|
||||||
|
account: "+15550002222",
|
||||||
|
reactionNotifications: "own",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const abortController = new AbortController();
|
||||||
|
|
||||||
|
streamMock.mockImplementation(async ({ onEvent }) => {
|
||||||
|
const payload = {
|
||||||
|
envelope: {
|
||||||
|
sourceNumber: "+15550001111",
|
||||||
|
sourceName: "Ada",
|
||||||
|
timestamp: 1,
|
||||||
|
reactionMessage: {
|
||||||
|
emoji: "✅",
|
||||||
|
targetAuthor: "+15550002222",
|
||||||
|
targetAuthorUuid: "123e4567-e89b-12d3-a456-426614174000",
|
||||||
|
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();
|
||||||
|
|
||||||
|
const route = resolveAgentRoute({
|
||||||
|
cfg: config as ClawdbotConfig,
|
||||||
|
provider: "signal",
|
||||||
|
accountId: "default",
|
||||||
|
peer: { kind: "dm", id: normalizeE164("+15550001111") },
|
||||||
|
});
|
||||||
|
const events = peekSystemEvents(route.sessionKey);
|
||||||
|
expect(events.some((text) => text.includes("Signal reaction added"))).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("processes messages when reaction metadata is present", async () => {
|
it("processes messages when reaction metadata is present", async () => {
|
||||||
const abortController = new AbortController();
|
const abortController = new AbortController();
|
||||||
replyMock.mockResolvedValue({ text: "pong" });
|
replyMock.mockResolvedValue({ text: "pong" });
|
||||||
|
|||||||
@@ -124,36 +124,42 @@ type SignalReactionTarget = {
|
|||||||
display: string;
|
display: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function resolveSignalReactionTarget(
|
function resolveSignalReactionTargets(
|
||||||
reaction: SignalReactionMessage,
|
reaction: SignalReactionMessage,
|
||||||
): SignalReactionTarget | null {
|
): SignalReactionTarget[] {
|
||||||
|
const targets: SignalReactionTarget[] = [];
|
||||||
const uuid = reaction.targetAuthorUuid?.trim();
|
const uuid = reaction.targetAuthorUuid?.trim();
|
||||||
if (uuid) {
|
if (uuid) {
|
||||||
return { kind: "uuid", id: uuid, display: `uuid:${uuid}` };
|
targets.push({ kind: "uuid", id: uuid, display: `uuid:${uuid}` });
|
||||||
}
|
}
|
||||||
const author = reaction.targetAuthor?.trim();
|
const author = reaction.targetAuthor?.trim();
|
||||||
if (!author) return null;
|
if (author) {
|
||||||
const normalized = normalizeE164(author);
|
const normalized = normalizeE164(author);
|
||||||
return { kind: "phone", id: normalized, display: normalized };
|
targets.push({ kind: "phone", id: normalized, display: normalized });
|
||||||
|
}
|
||||||
|
return targets;
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldEmitSignalReactionNotification(params: {
|
function shouldEmitSignalReactionNotification(params: {
|
||||||
mode?: SignalReactionNotificationMode;
|
mode?: SignalReactionNotificationMode;
|
||||||
account?: string | null;
|
account?: string | null;
|
||||||
target?: SignalReactionTarget | null;
|
targets?: SignalReactionTarget[];
|
||||||
sender?: ReturnType<typeof resolveSignalSender> | null;
|
sender?: ReturnType<typeof resolveSignalSender> | null;
|
||||||
allowlist?: string[];
|
allowlist?: string[];
|
||||||
}) {
|
}) {
|
||||||
const { mode, account, target, sender, allowlist } = params;
|
const { mode, account, targets, sender, allowlist } = params;
|
||||||
const effectiveMode = mode ?? "own";
|
const effectiveMode = mode ?? "own";
|
||||||
if (effectiveMode === "off") return false;
|
if (effectiveMode === "off") return false;
|
||||||
if (effectiveMode === "own") {
|
if (effectiveMode === "own") {
|
||||||
const accountId = account?.trim();
|
const accountId = account?.trim();
|
||||||
if (!accountId || !target) return false;
|
if (!accountId || !targets || targets.length === 0) return false;
|
||||||
if (target.kind === "uuid") {
|
const normalizedAccount = normalizeE164(accountId);
|
||||||
return accountId === target.id || accountId === `uuid:${target.id}`;
|
return targets.some((target) => {
|
||||||
}
|
if (target.kind === "uuid") {
|
||||||
return normalizeE164(accountId) === target.id;
|
return accountId === target.id || accountId === `uuid:${target.id}`;
|
||||||
|
}
|
||||||
|
return normalizedAccount === target.id;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (effectiveMode === "allowlist") {
|
if (effectiveMode === "allowlist") {
|
||||||
if (!sender || !allowlist || allowlist.length === 0) return false;
|
if (!sender || !allowlist || allowlist.length === 0) return false;
|
||||||
@@ -401,11 +407,11 @@ export async function monitorSignalProvider(
|
|||||||
const senderDisplay = formatSignalSenderDisplay(sender);
|
const senderDisplay = formatSignalSenderDisplay(sender);
|
||||||
const senderName = envelope.sourceName ?? senderDisplay;
|
const senderName = envelope.sourceName ?? senderDisplay;
|
||||||
logVerbose(`signal reaction: ${emojiLabel} from ${senderName}`);
|
logVerbose(`signal reaction: ${emojiLabel} from ${senderName}`);
|
||||||
const target = resolveSignalReactionTarget(reaction);
|
const targets = resolveSignalReactionTargets(reaction);
|
||||||
const shouldNotify = shouldEmitSignalReactionNotification({
|
const shouldNotify = shouldEmitSignalReactionNotification({
|
||||||
mode: reactionMode,
|
mode: reactionMode,
|
||||||
account,
|
account,
|
||||||
target,
|
targets,
|
||||||
sender,
|
sender,
|
||||||
allowlist: reactionAllowlist,
|
allowlist: reactionAllowlist,
|
||||||
});
|
});
|
||||||
@@ -433,7 +439,7 @@ export async function monitorSignalProvider(
|
|||||||
emojiLabel,
|
emojiLabel,
|
||||||
actorLabel: senderName,
|
actorLabel: senderName,
|
||||||
messageId,
|
messageId,
|
||||||
targetLabel: target?.display,
|
targetLabel: targets[0]?.display,
|
||||||
groupLabel,
|
groupLabel,
|
||||||
});
|
});
|
||||||
const senderId = formatSignalSenderId(sender);
|
const senderId = formatSignalSenderId(sender);
|
||||||
|
|||||||
Reference in New Issue
Block a user