mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 21:20:43 +00:00
test(telegram): cover message context perf guards
This commit is contained in:
@@ -25,6 +25,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Docker/Gateway: harden the gateway container by dropping `NET_RAW` and `NET_ADMIN` capabilities and enabling `no-new-privileges` in the bundled `docker-compose.yml`. Thanks @VintageAyu.
|
||||
- Telegram: accept plugin-owned numeric forum-topic targets in the agent message tool and keep reply-dispatch provider chunks behind a real stable runtime alias during in-place package updates. Fixes #77137. Thanks @richardmqq.
|
||||
- Telegram/streaming: keep draft preview rotation from reusing a pre-tool assistant preview after visible tool or media output lands between compaction replay and the next assistant message. Thanks @vincentkoc.
|
||||
- Telegram/performance: skip non-forum topic-cache setup, defer status reaction variant work until reactions are needed, and reuse ack reaction gating during message context assembly. Thanks @vincentkoc.
|
||||
- Channels/WhatsApp: support explicit WhatsApp Channel/Newsletter `@newsletter` outbound message targets with channel session metadata instead of DM routing. Fixes #13417; carries forward the narrow outbound target idea from #13424. Thanks @vincentkoc and @agentz-manfred.
|
||||
- TTS/telephony: honor provider voice/model overrides in telephony synthesis providers so Google Meet agent speech logs match the backend that actually produced the audio. Thanks @vincentkoc.
|
||||
- Voice Call/realtime: bound the paced Twilio audio queue and close overloaded realtime streams before provider audio can pile up behind the websocket backpressure guard. Thanks @vincentkoc.
|
||||
|
||||
@@ -244,6 +244,29 @@ describe("buildTelegramMessageContext group sessions without forum", () => {
|
||||
expect(ctxWithThread?.ctxPayload?.SessionKey).toBe(ctxWithoutThread?.ctxPayload?.SessionKey);
|
||||
});
|
||||
|
||||
it("does not add a topic-cache store lookup for non-forum group reply threads", async () => {
|
||||
const resolveStorePath = vi.fn(() => "/tmp/openclaw/session-store.json");
|
||||
|
||||
const ctx = await buildTelegramMessageContextForTest({
|
||||
message: {
|
||||
message_id: 9,
|
||||
chat: { id: -1001234567890, type: "supergroup", title: "Test Group" },
|
||||
date: 1700000008,
|
||||
text: "@bot hello",
|
||||
message_thread_id: 42,
|
||||
from: { id: 42, first_name: "Alice" },
|
||||
},
|
||||
options: { forceWasMentioned: true },
|
||||
resolveGroupActivation: () => true,
|
||||
sessionRuntime: { resolveStorePath },
|
||||
});
|
||||
|
||||
expect(ctx).not.toBeNull();
|
||||
expect(ctx?.isForum).toBe(false);
|
||||
expect(ctx?.ctxPayload?.MessageThreadId).toBeUndefined();
|
||||
expect(resolveStorePath).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("uses topic session for forum groups with message_thread_id", async () => {
|
||||
const ctx = await buildContext({
|
||||
message_id: 1,
|
||||
|
||||
146
extensions/telegram/src/bot-message-context.reactions.test.ts
Normal file
146
extensions/telegram/src/bot-message-context.reactions.test.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { BuildTelegramMessageContextParams } from "./bot-message-context.types.js";
|
||||
|
||||
const inboundBodyMock = vi.hoisted(() =>
|
||||
vi.fn(async () => ({
|
||||
bodyText: "hello",
|
||||
rawBody: "hello",
|
||||
historyKey: undefined,
|
||||
commandAuthorized: false,
|
||||
effectiveWasMentioned: false,
|
||||
canDetectMention: true,
|
||||
shouldBypassMention: false,
|
||||
stickerCacheHit: false,
|
||||
locationData: undefined,
|
||||
})),
|
||||
);
|
||||
|
||||
vi.mock("./bot-message-context.body.js", () => ({
|
||||
resolveTelegramInboundBody: (...args: unknown[]) => inboundBodyMock(...args),
|
||||
}));
|
||||
|
||||
const { buildTelegramMessageContextForTest } =
|
||||
await import("./bot-message-context.test-harness.js");
|
||||
|
||||
type CreateStatusReactionController = NonNullable<
|
||||
NonNullable<BuildTelegramMessageContextParams["runtime"]>["createStatusReactionController"]
|
||||
>;
|
||||
type StatusReactionControllerParams = Parameters<CreateStatusReactionController>[0];
|
||||
|
||||
function createStatusReactionControllerStub() {
|
||||
const controller = {
|
||||
setQueued: vi.fn(async () => undefined),
|
||||
setThinking: vi.fn(async () => undefined),
|
||||
setTool: vi.fn(async () => undefined),
|
||||
setCompacting: vi.fn(async () => undefined),
|
||||
cancelPending: vi.fn(),
|
||||
setDone: vi.fn(async () => undefined),
|
||||
setError: vi.fn(async () => undefined),
|
||||
clear: vi.fn(async () => undefined),
|
||||
restoreInitial: vi.fn(async () => undefined),
|
||||
};
|
||||
const createStatusReactionController = vi.fn((params: StatusReactionControllerParams) => {
|
||||
return controller;
|
||||
});
|
||||
return { controller, createStatusReactionController };
|
||||
}
|
||||
|
||||
describe("buildTelegramMessageContext reactions", () => {
|
||||
beforeEach(() => {
|
||||
inboundBodyMock.mockClear();
|
||||
});
|
||||
|
||||
it("does not create status reactions when the ack gate blocks an unmentioned group message", async () => {
|
||||
const setMessageReaction = vi.fn(async () => undefined);
|
||||
const { createStatusReactionController } = createStatusReactionControllerStub();
|
||||
|
||||
const ctx = await buildTelegramMessageContextForTest({
|
||||
message: {
|
||||
message_id: 12,
|
||||
chat: { id: -1001234567890, type: "group", title: "Ops" },
|
||||
date: 1_700_000_000,
|
||||
text: "hello",
|
||||
from: { id: 42, first_name: "Alice" },
|
||||
},
|
||||
cfg: {
|
||||
agents: {
|
||||
defaults: { model: "anthropic/claude-opus-4-5", workspace: "/tmp/openclaw" },
|
||||
},
|
||||
channels: {
|
||||
telegram: {
|
||||
groupPolicy: "open",
|
||||
groups: { "*": { requireMention: true } },
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
ackReaction: "👀",
|
||||
groupChat: { mentionPatterns: [] },
|
||||
statusReactions: { enabled: true },
|
||||
},
|
||||
},
|
||||
ackReactionScope: "group-mentions",
|
||||
botApi: { setMessageReaction },
|
||||
runtime: { createStatusReactionController },
|
||||
resolveGroupActivation: () => true,
|
||||
resolveGroupRequireMention: () => true,
|
||||
resolveTelegramGroupConfig: () => ({
|
||||
groupConfig: { requireMention: true },
|
||||
topicConfig: undefined,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(ctx).not.toBeNull();
|
||||
expect(ctx?.ackReactionPromise).toBeNull();
|
||||
expect(ctx?.statusReactionController).toBeNull();
|
||||
expect(createStatusReactionController).not.toHaveBeenCalled();
|
||||
expect(setMessageReaction).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps Telegram status reaction variants available for configured emoji fallbacks", async () => {
|
||||
const setMessageReaction = vi.fn(async () => undefined);
|
||||
const { controller, createStatusReactionController } = createStatusReactionControllerStub();
|
||||
|
||||
const ctx = await buildTelegramMessageContextForTest({
|
||||
message: {
|
||||
message_id: 34,
|
||||
chat: {
|
||||
id: 1234,
|
||||
type: "private",
|
||||
available_reactions: [{ type: "emoji", emoji: "👍" }],
|
||||
},
|
||||
date: 1_700_000_000,
|
||||
text: "hello",
|
||||
from: { id: 42, first_name: "Alice" },
|
||||
},
|
||||
cfg: {
|
||||
agents: {
|
||||
defaults: { model: "anthropic/claude-opus-4-5", workspace: "/tmp/openclaw" },
|
||||
},
|
||||
channels: { telegram: { dmPolicy: "open", allowFrom: ["*"] } },
|
||||
messages: {
|
||||
ackReaction: "👀",
|
||||
groupChat: { mentionPatterns: [] },
|
||||
statusReactions: {
|
||||
enabled: true,
|
||||
emojis: { done: "✅" },
|
||||
},
|
||||
},
|
||||
},
|
||||
ackReactionScope: "direct",
|
||||
botApi: { setMessageReaction },
|
||||
runtime: { createStatusReactionController },
|
||||
});
|
||||
|
||||
await expect(ctx?.ackReactionPromise).resolves.toBe(true);
|
||||
expect(controller.setQueued).toHaveBeenCalledTimes(1);
|
||||
expect(createStatusReactionController).toHaveBeenCalledTimes(1);
|
||||
|
||||
const params = createStatusReactionController.mock.calls[0]?.[0];
|
||||
expect(params?.initialEmoji).toBe("👀");
|
||||
expect(params?.emojis?.done).toBe("✅");
|
||||
|
||||
await params?.adapter.setReaction("✅");
|
||||
|
||||
expect(setMessageReaction).toHaveBeenCalledWith(1234, 34, [{ type: "emoji", emoji: "👍" }]);
|
||||
});
|
||||
});
|
||||
@@ -18,6 +18,8 @@ type BuildTelegramMessageContextForTestParams = {
|
||||
options?: BuildTelegramMessageContextParams["options"];
|
||||
cfg?: Record<string, unknown>;
|
||||
accountId?: string;
|
||||
ackReactionScope?: BuildTelegramMessageContextParams["ackReactionScope"];
|
||||
botApi?: Record<string, unknown>;
|
||||
runtime?: BuildTelegramMessageContextParams["runtime"];
|
||||
sessionRuntime?: BuildTelegramMessageContextParams["sessionRuntime"] | null;
|
||||
resolveGroupActivation?: BuildTelegramMessageContextParams["resolveGroupActivation"];
|
||||
@@ -67,6 +69,7 @@ export async function buildTelegramMessageContextForTest(
|
||||
api: {
|
||||
sendChatAction: vi.fn(),
|
||||
setMessageReaction: vi.fn(),
|
||||
...params.botApi,
|
||||
},
|
||||
} as never,
|
||||
cfg: (params.cfg ?? baseTelegramMessageContextConfig) as never,
|
||||
@@ -82,7 +85,7 @@ export async function buildTelegramMessageContextForTest(
|
||||
dmPolicy: "open",
|
||||
allowFrom: ["*"],
|
||||
groupAllowFrom: [],
|
||||
ackReactionScope: "off",
|
||||
ackReactionScope: params.ackReactionScope ?? "off",
|
||||
logger: { info: vi.fn() },
|
||||
resolveGroupActivation: params.resolveGroupActivation ?? (() => undefined),
|
||||
resolveGroupRequireMention: params.resolveGroupRequireMention ?? (() => false),
|
||||
|
||||
Reference in New Issue
Block a user