fix: keep telegram room events fully quiet · openclaw/openclaw@4b11d65
steipete
·
2026-05-16
·
via Recent Commits to openclaw:main
| Original file line number | Diff line number | Diff line change |
|---|
@@ -46,6 +46,42 @@ describe("telegram inbound turn delivery", () => {
|
46 | 46 | end(); |
47 | 47 | }); |
48 | 48 | |
| 49 | +it("matches provider-prefixed Telegram targets for delivery correlation", () => { |
| 50 | +let count = 0; |
| 51 | +const end = beginTelegramInboundTurnDeliveryCorrelation("sess:prefixed", { |
| 52 | +outboundTo: "-100123", |
| 53 | +markInboundTurnDelivered: () => { |
| 54 | +count += 1; |
| 55 | +}, |
| 56 | +}); |
| 57 | + |
| 58 | +notifyTelegramInboundTurnOutboundSuccess({ |
| 59 | +sessionKey: "sess:prefixed", |
| 60 | +to: "telegram:-100123", |
| 61 | +}); |
| 62 | + |
| 63 | +expect(count).toBe(1); |
| 64 | +end(); |
| 65 | +}); |
| 66 | + |
| 67 | +it("matches Telegram topic targets by conversation for delivery correlation", () => { |
| 68 | +let count = 0; |
| 69 | +const end = beginTelegramInboundTurnDeliveryCorrelation("sess:topic", { |
| 70 | +outboundTo: "-100123", |
| 71 | +markInboundTurnDelivered: () => { |
| 72 | +count += 1; |
| 73 | +}, |
| 74 | +}); |
| 75 | + |
| 76 | +notifyTelegramInboundTurnOutboundSuccess({ |
| 77 | +sessionKey: "sess:topic", |
| 78 | +to: "telegram:-100123:topic:77", |
| 79 | +}); |
| 80 | + |
| 81 | +expect(count).toBe(1); |
| 82 | +end(); |
| 83 | +}); |
| 84 | + |
49 | 85 | it("keeps user-request and room-event delivery correlations separate", () => { |
50 | 86 | let userRequestCount = 0; |
51 | 87 | let roomEventCount = 0; |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -9,6 +9,30 @@ type ActiveTurn = {
|
9 | 9 | |
10 | 10 | const registry = new Map<string, ActiveTurn>(); |
11 | 11 | |
| 12 | +function normalizeTelegramDeliveryTarget(value: string): string { |
| 13 | +return value |
| 14 | +.trim() |
| 15 | +.toLowerCase() |
| 16 | +.replace(/^(telegram|tg):/u, ""); |
| 17 | +} |
| 18 | + |
| 19 | +function stripTelegramTopicTarget(value: string): string { |
| 20 | +return value.replace(/:topic:\d+$/u, ""); |
| 21 | +} |
| 22 | + |
| 23 | +function telegramDeliveryTargetsMatch(expected: string, actual: string): boolean { |
| 24 | +const expectedTarget = normalizeTelegramDeliveryTarget(expected); |
| 25 | +const actualTarget = normalizeTelegramDeliveryTarget(actual); |
| 26 | +if (expectedTarget === actualTarget) { |
| 27 | +return true; |
| 28 | +} |
| 29 | +const expectedBase = stripTelegramTopicTarget(expectedTarget); |
| 30 | +const actualBase = stripTelegramTopicTarget(actualTarget); |
| 31 | +return ( |
| 32 | +expectedBase === actualBase && (expectedTarget === expectedBase || actualTarget === actualBase) |
| 33 | +); |
| 34 | +} |
| 35 | + |
12 | 36 | export function resolveTelegramInboundTurnDeliveryCorrelationKey( |
13 | 37 | sessionKey: string | undefined, |
14 | 38 | inboundTurnKind?: TelegramInboundTurnDeliveryKind | string, |
@@ -52,7 +76,7 @@ export function notifyTelegramInboundTurnOutboundSuccess(params: {
|
52 | 76 | return; |
53 | 77 | } |
54 | 78 | const turn = registry.get(key); |
55 | | -if (!turn || turn.outboundTo !== params.to) { |
| 79 | +if (!turn || !telegramDeliveryTargetsMatch(turn.outboundTo, params.to)) { |
56 | 80 | return; |
57 | 81 | } |
58 | 82 | if (turn.outboundAccountId && params.accountId && params.accountId !== turn.outboundAccountId) { |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -4785,6 +4785,43 @@ describe("sendPolicy deny — suppress delivery, not processing (#53328)", () =>
|
4785 | 4785 | expect(dispatcher.sendToolResult).not.toHaveBeenCalled(); |
4786 | 4786 | }); |
4787 | 4787 | |
| 4788 | +it("suppresses marked runtime failure notices for room events", async () => { |
| 4789 | +setNoAbort(); |
| 4790 | +sessionStoreMocks.currentEntry = { |
| 4791 | +sessionId: "s1", |
| 4792 | +updatedAt: 0, |
| 4793 | +sendPolicy: "allow", |
| 4794 | +}; |
| 4795 | +const dispatcher = createDispatcher(); |
| 4796 | +const failureNotice = setReplyPayloadMetadata( |
| 4797 | +{ text: "⚠️ You've reached your Codex subscription usage limit." }, |
| 4798 | +{ deliverDespiteSourceReplySuppression: true }, |
| 4799 | +); |
| 4800 | +const replyResolver = vi.fn(async () => failureNotice satisfies ReplyPayload); |
| 4801 | +const ctx = buildTestCtx({ |
| 4802 | +ChatType: "group", |
| 4803 | +InboundTurnKind: "room_event", |
| 4804 | +SessionKey: "test:session", |
| 4805 | +}); |
| 4806 | + |
| 4807 | +const result = await dispatchReplyFromConfig({ |
| 4808 | + ctx, |
| 4809 | +cfg: emptyConfig, |
| 4810 | + dispatcher, |
| 4811 | + replyResolver, |
| 4812 | +replyOptions: { |
| 4813 | +sourceReplyDeliveryMode: "message_tool_only", |
| 4814 | +}, |
| 4815 | +}); |
| 4816 | + |
| 4817 | +expect(replyResolver).toHaveBeenCalledTimes(1); |
| 4818 | +expect(result.queuedFinal).toBe(false); |
| 4819 | +expect(result.sourceReplyDeliveryMode).toBe("message_tool_only"); |
| 4820 | +expect(dispatcher.sendFinalReply).not.toHaveBeenCalled(); |
| 4821 | +expect(dispatcher.sendBlockReply).not.toHaveBeenCalled(); |
| 4822 | +expect(dispatcher.sendToolResult).not.toHaveBeenCalled(); |
| 4823 | +}); |
| 4824 | + |
4788 | 4825 | it("mirrors internal source reply payloads into the active transcript", async () => { |
4789 | 4826 | setNoAbort(); |
4790 | 4827 | sessionStoreMocks.currentEntry = { |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -1599,6 +1599,7 @@ export async function dispatchReplyFromConfig(
|
1599 | 1599 | let finalDeliveryFailed = false; |
1600 | 1600 | const shouldDeliverDespiteSourceReplySuppression = (reply: ReplyPayload) => |
1601 | 1601 | suppressAutomaticSourceDelivery && |
| 1602 | +ctx.InboundTurnKind !== "room_event" && |
1602 | 1603 | !sendPolicyDenied && |
1603 | 1604 | getReplyPayloadMetadata(reply)?.deliverDespiteSourceReplySuppression === true; |
1604 | 1605 | for (const reply of replies) { |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。