mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 09:30:43 +00:00
test(telegram): hide split message context files
This commit is contained in:
@@ -1,159 +0,0 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const transcribeFirstAudioMock = vi.fn();
|
||||
const DEFAULT_MODEL = "anthropic/claude-opus-4-5";
|
||||
const DEFAULT_WORKSPACE = "/tmp/openclaw";
|
||||
const DEFAULT_MENTION_PATTERN = "\\bbot\\b";
|
||||
|
||||
vi.mock("./media-understanding.runtime.js", () => ({
|
||||
transcribeFirstAudio: (...args: unknown[]) => transcribeFirstAudioMock(...args),
|
||||
}));
|
||||
|
||||
const { buildTelegramMessageContextForTest } =
|
||||
await import("./bot-message-context.test-harness.js");
|
||||
|
||||
async function buildGroupVoiceContext(params: {
|
||||
messageId: number;
|
||||
chatId: number;
|
||||
title: string;
|
||||
date: number;
|
||||
fromId: number;
|
||||
firstName: string;
|
||||
fileId: string;
|
||||
mediaPath: string;
|
||||
groupDisableAudioPreflight?: boolean;
|
||||
topicDisableAudioPreflight?: boolean;
|
||||
}) {
|
||||
const groupConfig = {
|
||||
requireMention: true,
|
||||
...(params.groupDisableAudioPreflight === undefined
|
||||
? {}
|
||||
: { disableAudioPreflight: params.groupDisableAudioPreflight }),
|
||||
};
|
||||
const topicConfig =
|
||||
params.topicDisableAudioPreflight === undefined
|
||||
? undefined
|
||||
: { disableAudioPreflight: params.topicDisableAudioPreflight };
|
||||
|
||||
return buildTelegramMessageContextForTest({
|
||||
message: {
|
||||
message_id: params.messageId,
|
||||
chat: { id: params.chatId, type: "supergroup", title: params.title },
|
||||
date: params.date,
|
||||
text: undefined,
|
||||
from: { id: params.fromId, first_name: params.firstName },
|
||||
voice: { file_id: params.fileId },
|
||||
},
|
||||
allMedia: [{ path: params.mediaPath, contentType: "audio/ogg" }],
|
||||
options: { forceWasMentioned: true },
|
||||
cfg: {
|
||||
agents: { defaults: { model: DEFAULT_MODEL, workspace: DEFAULT_WORKSPACE } },
|
||||
channels: { telegram: {} },
|
||||
messages: { groupChat: { mentionPatterns: [DEFAULT_MENTION_PATTERN] } },
|
||||
},
|
||||
resolveGroupActivation: () => true,
|
||||
resolveGroupRequireMention: () => true,
|
||||
resolveTelegramGroupConfig: () => ({
|
||||
groupConfig,
|
||||
topicConfig,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
function expectTranscriptRendered(
|
||||
ctx: Awaited<ReturnType<typeof buildGroupVoiceContext>>,
|
||||
transcript: string,
|
||||
) {
|
||||
expect(ctx).not.toBeNull();
|
||||
expect(ctx?.ctxPayload?.BodyForAgent).toBe(transcript);
|
||||
expect(ctx?.ctxPayload?.Body).toContain(transcript);
|
||||
expect(ctx?.ctxPayload?.Body).not.toContain("<media:audio>");
|
||||
}
|
||||
|
||||
function expectAudioPlaceholderRendered(ctx: Awaited<ReturnType<typeof buildGroupVoiceContext>>) {
|
||||
expect(ctx).not.toBeNull();
|
||||
expect(ctx?.ctxPayload?.Body).toContain("<media:audio>");
|
||||
}
|
||||
|
||||
describe("buildTelegramMessageContext audio transcript body", () => {
|
||||
beforeEach(() => {
|
||||
transcribeFirstAudioMock.mockReset();
|
||||
});
|
||||
|
||||
it("uses preflight transcript as BodyForAgent for mention-gated group voice messages", async () => {
|
||||
transcribeFirstAudioMock.mockResolvedValueOnce("hey bot please help");
|
||||
|
||||
const ctx = await buildGroupVoiceContext({
|
||||
messageId: 1,
|
||||
chatId: -1001234567890,
|
||||
title: "Test Group",
|
||||
date: 1700000000,
|
||||
fromId: 42,
|
||||
firstName: "Alice",
|
||||
fileId: "voice-1",
|
||||
mediaPath: "/tmp/voice.ogg",
|
||||
});
|
||||
|
||||
expect(transcribeFirstAudioMock).toHaveBeenCalledTimes(1);
|
||||
expectTranscriptRendered(ctx, "hey bot please help");
|
||||
});
|
||||
|
||||
it("skips preflight transcription when disableAudioPreflight is true", async () => {
|
||||
transcribeFirstAudioMock.mockClear();
|
||||
|
||||
const ctx = await buildGroupVoiceContext({
|
||||
messageId: 2,
|
||||
chatId: -1001234567891,
|
||||
title: "Test Group 2",
|
||||
date: 1700000100,
|
||||
fromId: 43,
|
||||
firstName: "Bob",
|
||||
fileId: "voice-2",
|
||||
mediaPath: "/tmp/voice2.ogg",
|
||||
groupDisableAudioPreflight: true,
|
||||
});
|
||||
|
||||
expect(transcribeFirstAudioMock).not.toHaveBeenCalled();
|
||||
expectAudioPlaceholderRendered(ctx);
|
||||
});
|
||||
|
||||
it("uses topic disableAudioPreflight=false to override group disableAudioPreflight=true", async () => {
|
||||
transcribeFirstAudioMock.mockResolvedValueOnce("topic override transcript");
|
||||
|
||||
const ctx = await buildGroupVoiceContext({
|
||||
messageId: 3,
|
||||
chatId: -1001234567892,
|
||||
title: "Test Group 3",
|
||||
date: 1700000200,
|
||||
fromId: 44,
|
||||
firstName: "Cara",
|
||||
fileId: "voice-3",
|
||||
mediaPath: "/tmp/voice3.ogg",
|
||||
groupDisableAudioPreflight: true,
|
||||
topicDisableAudioPreflight: false,
|
||||
});
|
||||
|
||||
expect(transcribeFirstAudioMock).toHaveBeenCalledTimes(1);
|
||||
expectTranscriptRendered(ctx, "topic override transcript");
|
||||
});
|
||||
|
||||
it("uses topic disableAudioPreflight=true to override group disableAudioPreflight=false", async () => {
|
||||
transcribeFirstAudioMock.mockClear();
|
||||
|
||||
const ctx = await buildGroupVoiceContext({
|
||||
messageId: 4,
|
||||
chatId: -1001234567893,
|
||||
title: "Test Group 4",
|
||||
date: 1700000300,
|
||||
fromId: 45,
|
||||
firstName: "Dan",
|
||||
fileId: "voice-4",
|
||||
mediaPath: "/tmp/voice4.ogg",
|
||||
groupDisableAudioPreflight: false,
|
||||
topicDisableAudioPreflight: true,
|
||||
});
|
||||
|
||||
expect(transcribeFirstAudioMock).not.toHaveBeenCalled();
|
||||
expectAudioPlaceholderRendered(ctx);
|
||||
});
|
||||
});
|
||||
@@ -1,147 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildTelegramMessageContextForTest } from "./bot-message-context.test-harness.js";
|
||||
import { TELEGRAM_FORUM_SERVICE_FIELDS } from "./forum-service-message.js";
|
||||
|
||||
describe("buildTelegramMessageContext implicitMention forum service messages", () => {
|
||||
/**
|
||||
* Build a group message context where the user sends a message inside a
|
||||
* forum topic that has `reply_to_message` pointing to a message from the
|
||||
* bot. Callers control whether the reply target looks like a forum service
|
||||
* message (carries `forum_topic_created` etc.) or a real bot reply.
|
||||
*/
|
||||
async function buildGroupReplyCtx(params: {
|
||||
replyToMessageText?: string;
|
||||
replyToMessageCaption?: string;
|
||||
replyFromIsBot?: boolean;
|
||||
replyFromId?: number;
|
||||
/** Extra fields on reply_to_message (e.g. forum_topic_created). */
|
||||
replyToMessageExtra?: Record<string, unknown>;
|
||||
}) {
|
||||
const BOT_ID = 7; // matches test harness primaryCtx.me.id
|
||||
return await buildTelegramMessageContextForTest({
|
||||
message: {
|
||||
message_id: 100,
|
||||
chat: { id: -1001234567890, type: "supergroup", title: "Forum Group" },
|
||||
date: 1700000000,
|
||||
text: "hello everyone",
|
||||
from: { id: 42, first_name: "Alice" },
|
||||
reply_to_message: {
|
||||
message_id: 1,
|
||||
text: params.replyToMessageText ?? undefined,
|
||||
...(params.replyToMessageCaption != null
|
||||
? { caption: params.replyToMessageCaption }
|
||||
: {}),
|
||||
from: {
|
||||
id: params.replyFromId ?? BOT_ID,
|
||||
first_name: "OpenClaw",
|
||||
is_bot: params.replyFromIsBot ?? true,
|
||||
},
|
||||
...params.replyToMessageExtra,
|
||||
},
|
||||
},
|
||||
resolveGroupActivation: () => true,
|
||||
resolveGroupRequireMention: () => true,
|
||||
resolveTelegramGroupConfig: () => ({
|
||||
groupConfig: { requireMention: true },
|
||||
topicConfig: undefined,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
it("does NOT trigger implicitMention for forum_topic_created service message", async () => {
|
||||
// Bot auto-generated "Topic created" message carries forum_topic_created.
|
||||
const ctx = await buildGroupReplyCtx({
|
||||
replyToMessageText: undefined,
|
||||
replyFromIsBot: true,
|
||||
replyToMessageExtra: {
|
||||
forum_topic_created: { name: "New Topic", icon_color: 0x6fb9f0 },
|
||||
},
|
||||
});
|
||||
|
||||
// With requireMention and no explicit @mention, the message should be
|
||||
// skipped (null) because implicitMention should NOT fire.
|
||||
expect(ctx).toBeNull();
|
||||
});
|
||||
|
||||
it.each(TELEGRAM_FORUM_SERVICE_FIELDS)(
|
||||
"does NOT trigger implicitMention for %s service message",
|
||||
async (field) => {
|
||||
const ctx = await buildGroupReplyCtx({
|
||||
replyToMessageText: undefined,
|
||||
replyFromIsBot: true,
|
||||
replyToMessageExtra: { [field]: {} },
|
||||
});
|
||||
|
||||
expect(ctx).toBeNull();
|
||||
},
|
||||
);
|
||||
|
||||
it("does NOT trigger implicitMention for forum_topic_closed service message", async () => {
|
||||
const ctx = await buildGroupReplyCtx({
|
||||
replyToMessageText: undefined,
|
||||
replyFromIsBot: true,
|
||||
replyToMessageExtra: { forum_topic_closed: {} },
|
||||
});
|
||||
|
||||
expect(ctx).toBeNull();
|
||||
});
|
||||
|
||||
it("does NOT trigger implicitMention for general_forum_topic_hidden service message", async () => {
|
||||
const ctx = await buildGroupReplyCtx({
|
||||
replyToMessageText: undefined,
|
||||
replyFromIsBot: true,
|
||||
replyToMessageExtra: { general_forum_topic_hidden: {} },
|
||||
});
|
||||
|
||||
expect(ctx).toBeNull();
|
||||
});
|
||||
|
||||
it("DOES trigger implicitMention for real bot replies (non-empty text)", async () => {
|
||||
const ctx = await buildGroupReplyCtx({
|
||||
replyToMessageText: "Here is my answer",
|
||||
replyFromIsBot: true,
|
||||
});
|
||||
|
||||
// Real bot reply → implicitMention fires → message is NOT skipped.
|
||||
expect(ctx).not.toBeNull();
|
||||
expect(ctx?.ctxPayload?.WasMentioned).toBe(true);
|
||||
});
|
||||
|
||||
it("DOES trigger implicitMention for bot media messages with caption", async () => {
|
||||
// Media messages from the bot have caption but no text — they should
|
||||
// still count as real bot replies, not service messages.
|
||||
const ctx = await buildGroupReplyCtx({
|
||||
replyToMessageText: undefined,
|
||||
replyToMessageCaption: "Check out this image",
|
||||
replyFromIsBot: true,
|
||||
});
|
||||
|
||||
expect(ctx).not.toBeNull();
|
||||
expect(ctx?.ctxPayload?.WasMentioned).toBe(true);
|
||||
});
|
||||
|
||||
it("DOES trigger implicitMention for bot sticker/voice (no text, no caption, no service field)", async () => {
|
||||
// Stickers, voice notes, and captionless photos have neither text nor
|
||||
// caption, but they are NOT service messages — they are legitimate bot
|
||||
// replies that should trigger implicitMention.
|
||||
const ctx = await buildGroupReplyCtx({
|
||||
replyToMessageText: undefined,
|
||||
replyFromIsBot: true,
|
||||
// No forum_topic_* fields → not a service message
|
||||
});
|
||||
|
||||
expect(ctx).not.toBeNull();
|
||||
expect(ctx?.ctxPayload?.WasMentioned).toBe(true);
|
||||
});
|
||||
|
||||
it("does NOT trigger implicitMention when reply is from a different user", async () => {
|
||||
const ctx = await buildGroupReplyCtx({
|
||||
replyToMessageText: "some message",
|
||||
replyFromIsBot: false,
|
||||
replyFromId: 999,
|
||||
});
|
||||
|
||||
// Different user's message → not an implicit mention → skipped.
|
||||
expect(ctx).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -1,160 +0,0 @@
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
getRecordedUpdateLastRoute,
|
||||
loadTelegramMessageContextRouteHarness,
|
||||
recordInboundSessionMock,
|
||||
} from "./bot-message-context.route-test-support.js";
|
||||
|
||||
let buildTelegramMessageContextForTest: typeof import("./bot-message-context.test-harness.js").buildTelegramMessageContextForTest;
|
||||
let clearRuntimeConfigSnapshot: typeof import("openclaw/plugin-sdk/config-runtime").clearRuntimeConfigSnapshot;
|
||||
let setRuntimeConfigSnapshot: typeof import("openclaw/plugin-sdk/config-runtime").setRuntimeConfigSnapshot;
|
||||
|
||||
describe("buildTelegramMessageContext named-account DM fallback", () => {
|
||||
const baseCfg = {
|
||||
agents: { defaults: { model: "anthropic/claude-opus-4-5", workspace: "/tmp/openclaw" } },
|
||||
channels: { telegram: {} },
|
||||
messages: { groupChat: { mentionPatterns: [] } },
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
clearRuntimeConfigSnapshot();
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
({ clearRuntimeConfigSnapshot, setRuntimeConfigSnapshot, buildTelegramMessageContextForTest } =
|
||||
await loadTelegramMessageContextRouteHarness());
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
recordInboundSessionMock.mockClear();
|
||||
});
|
||||
|
||||
function getLastUpdateLastRoute(): { sessionKey?: string } | undefined {
|
||||
return getRecordedUpdateLastRoute() as { sessionKey?: string } | undefined;
|
||||
}
|
||||
|
||||
function buildNamedAccountDmMessage(messageId = 1) {
|
||||
return {
|
||||
message_id: messageId,
|
||||
chat: { id: 814912386, type: "private" as const },
|
||||
date: 1700000000 + messageId - 1,
|
||||
text: "hello",
|
||||
from: { id: 814912386, first_name: "Alice" },
|
||||
};
|
||||
}
|
||||
|
||||
async function buildNamedAccountDmContext(accountId = "atlas", messageId = 1) {
|
||||
setRuntimeConfigSnapshot(baseCfg);
|
||||
return await buildTelegramMessageContextForTest({
|
||||
cfg: baseCfg,
|
||||
accountId,
|
||||
message: buildNamedAccountDmMessage(messageId),
|
||||
});
|
||||
}
|
||||
|
||||
it("allows DM through for a named account with no explicit binding", async () => {
|
||||
setRuntimeConfigSnapshot(baseCfg);
|
||||
|
||||
const ctx = await buildTelegramMessageContextForTest({
|
||||
cfg: baseCfg,
|
||||
accountId: "atlas",
|
||||
message: {
|
||||
message_id: 1,
|
||||
chat: { id: 814912386, type: "private" },
|
||||
date: 1700000000,
|
||||
text: "hello",
|
||||
from: { id: 814912386, first_name: "Alice" },
|
||||
},
|
||||
});
|
||||
|
||||
expect(ctx).not.toBeNull();
|
||||
expect(ctx?.route.matchedBy).toBe("default");
|
||||
expect(ctx?.route.accountId).toBe("atlas");
|
||||
});
|
||||
|
||||
it("uses a per-account session key for named-account DMs", async () => {
|
||||
const ctx = await buildNamedAccountDmContext();
|
||||
|
||||
expect(ctx?.ctxPayload?.SessionKey).toBe("agent:main:telegram:atlas:direct:814912386");
|
||||
});
|
||||
|
||||
it("keeps named-account fallback lastRoute on the isolated DM session", async () => {
|
||||
const ctx = await buildNamedAccountDmContext();
|
||||
|
||||
expect(ctx?.ctxPayload?.SessionKey).toBe("agent:main:telegram:atlas:direct:814912386");
|
||||
expect(getLastUpdateLastRoute()?.sessionKey).toBe("agent:main:telegram:atlas:direct:814912386");
|
||||
});
|
||||
|
||||
it("isolates sessions between named accounts that share the default agent", async () => {
|
||||
const atlas = await buildNamedAccountDmContext("atlas", 1);
|
||||
const skynet = await buildNamedAccountDmContext("skynet", 2);
|
||||
|
||||
expect(atlas?.ctxPayload?.SessionKey).toBe("agent:main:telegram:atlas:direct:814912386");
|
||||
expect(skynet?.ctxPayload?.SessionKey).toBe("agent:main:telegram:skynet:direct:814912386");
|
||||
expect(atlas?.ctxPayload?.SessionKey).not.toBe(skynet?.ctxPayload?.SessionKey);
|
||||
});
|
||||
|
||||
it("keeps identity-linked peer canonicalization in the named-account fallback path", async () => {
|
||||
const cfg = {
|
||||
...baseCfg,
|
||||
session: {
|
||||
identityLinks: {
|
||||
"alice-shared": ["telegram:814912386"],
|
||||
},
|
||||
},
|
||||
};
|
||||
setRuntimeConfigSnapshot(cfg);
|
||||
|
||||
const ctx = await buildTelegramMessageContextForTest({
|
||||
cfg,
|
||||
accountId: "atlas",
|
||||
message: {
|
||||
message_id: 1,
|
||||
chat: { id: 999999999, type: "private" },
|
||||
date: 1700000000,
|
||||
text: "hello",
|
||||
from: { id: 814912386, first_name: "Alice" },
|
||||
},
|
||||
});
|
||||
|
||||
expect(ctx?.ctxPayload?.SessionKey).toBe("agent:main:telegram:atlas:direct:alice-shared");
|
||||
});
|
||||
|
||||
it("still drops named-account group messages without an explicit binding", async () => {
|
||||
setRuntimeConfigSnapshot(baseCfg);
|
||||
|
||||
const ctx = await buildTelegramMessageContextForTest({
|
||||
cfg: baseCfg,
|
||||
accountId: "atlas",
|
||||
options: { forceWasMentioned: true },
|
||||
resolveGroupActivation: () => true,
|
||||
message: {
|
||||
message_id: 1,
|
||||
chat: { id: -1001234567890, type: "supergroup", title: "Test Group" },
|
||||
date: 1700000000,
|
||||
text: "@bot hello",
|
||||
from: { id: 814912386, first_name: "Alice" },
|
||||
},
|
||||
});
|
||||
|
||||
expect(ctx).toBeNull();
|
||||
});
|
||||
|
||||
it("uses the main session key for default-account DMs", async () => {
|
||||
setRuntimeConfigSnapshot(baseCfg);
|
||||
|
||||
const ctx = await buildTelegramMessageContextForTest({
|
||||
cfg: baseCfg,
|
||||
message: {
|
||||
message_id: 1,
|
||||
chat: { id: 42, type: "private" },
|
||||
date: 1700000000,
|
||||
text: "hello",
|
||||
from: { id: 42, first_name: "Alice" },
|
||||
},
|
||||
});
|
||||
|
||||
expect(ctx?.ctxPayload?.SessionKey).toBe("agent:main:main");
|
||||
expect(getLastUpdateLastRoute()?.sessionKey).toBe("agent:main:main");
|
||||
});
|
||||
});
|
||||
@@ -1,60 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildTelegramMessageContextForTest } from "./bot-message-context.test-harness.js";
|
||||
import {
|
||||
isTelegramForumServiceMessage,
|
||||
TELEGRAM_FORUM_SERVICE_FIELDS,
|
||||
} from "./forum-service-message.js";
|
||||
|
||||
describe("isTelegramForumServiceMessage", () => {
|
||||
it("returns true for any Telegram forum service field", () => {
|
||||
for (const field of TELEGRAM_FORUM_SERVICE_FIELDS) {
|
||||
expect(isTelegramForumServiceMessage({ [field]: {} })).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("returns false for normal messages and non-objects", () => {
|
||||
expect(isTelegramForumServiceMessage({ text: "hello" })).toBe(false);
|
||||
expect(isTelegramForumServiceMessage(null)).toBe(false);
|
||||
expect(isTelegramForumServiceMessage("topic created")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildTelegramMessageContext sender prefix", () => {
|
||||
async function buildCtx(params: { messageId: number; options?: Record<string, unknown> }) {
|
||||
return await buildTelegramMessageContextForTest({
|
||||
message: {
|
||||
message_id: params.messageId,
|
||||
chat: { id: -99, type: "supergroup", title: "Dev Chat" },
|
||||
date: 1700000000,
|
||||
text: "hello",
|
||||
from: { id: 42, first_name: "Alice" },
|
||||
},
|
||||
options: params.options,
|
||||
});
|
||||
}
|
||||
|
||||
it("prefixes group bodies with sender label", async () => {
|
||||
const ctx = await buildCtx({ messageId: 1 });
|
||||
|
||||
expect(ctx).not.toBeNull();
|
||||
const body = ctx?.ctxPayload?.Body ?? "";
|
||||
expect(body).toContain("Alice (42): hello");
|
||||
});
|
||||
|
||||
it("sets MessageSid from message_id", async () => {
|
||||
const ctx = await buildCtx({ messageId: 12345 });
|
||||
|
||||
expect(ctx).not.toBeNull();
|
||||
expect(ctx?.ctxPayload?.MessageSid).toBe("12345");
|
||||
});
|
||||
|
||||
it("respects messageIdOverride option", async () => {
|
||||
const ctx = await buildCtx({
|
||||
messageId: 12345,
|
||||
options: { messageIdOverride: "67890" },
|
||||
});
|
||||
|
||||
expect(ctx).not.toBeNull();
|
||||
expect(ctx?.ctxPayload?.MessageSid).toBe("67890");
|
||||
});
|
||||
});
|
||||
@@ -1,99 +0,0 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import {
|
||||
clearRuntimeConfigSnapshot,
|
||||
setRuntimeConfigSnapshot,
|
||||
} from "openclaw/plugin-sdk/config-runtime";
|
||||
import {
|
||||
clearSessionStoreCacheForTest,
|
||||
loadSessionStore,
|
||||
updateSessionStore,
|
||||
} from "openclaw/plugin-sdk/config-runtime";
|
||||
import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest";
|
||||
import { createSuiteTempRootTracker } from "../../../src/test-helpers/temp-dir.js";
|
||||
import { buildTelegramMessageContextForTest } from "./bot-message-context.test-harness.js";
|
||||
|
||||
const TELEGRAM_DIRECT_KEY = "agent:main:telegram:direct:7463849194";
|
||||
|
||||
describe("Telegram direct session recreation after delete", () => {
|
||||
const suiteRootTracker = createSuiteTempRootTracker({
|
||||
prefix: "openclaw-telegram-context-recreate-",
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
await suiteRootTracker.setup();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
clearRuntimeConfigSnapshot();
|
||||
clearSessionStoreCacheForTest();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await suiteRootTracker.cleanup();
|
||||
});
|
||||
|
||||
it("records a deleted direct session again when the next DM is processed", async () => {
|
||||
const tempDir = await suiteRootTracker.make("direct");
|
||||
const storePath = path.join(tempDir, "sessions.json");
|
||||
const cfg = {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "openai/gpt-5.4",
|
||||
workspace: "/tmp/openclaw",
|
||||
},
|
||||
},
|
||||
channels: { telegram: {} },
|
||||
messages: { groupChat: { mentionPatterns: [] } },
|
||||
session: {
|
||||
dmScope: "per-channel-peer" as const,
|
||||
store: storePath,
|
||||
},
|
||||
};
|
||||
setRuntimeConfigSnapshot(cfg as never);
|
||||
await fs.writeFile(
|
||||
storePath,
|
||||
JSON.stringify(
|
||||
{
|
||||
[TELEGRAM_DIRECT_KEY]: {
|
||||
sessionId: "old-session",
|
||||
updatedAt: 1_700_000_000_000,
|
||||
chatType: "direct",
|
||||
channel: "telegram",
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"utf-8",
|
||||
);
|
||||
await updateSessionStore(storePath, (store) => {
|
||||
delete store[TELEGRAM_DIRECT_KEY];
|
||||
});
|
||||
|
||||
const context = await buildTelegramMessageContextForTest({
|
||||
cfg,
|
||||
message: {
|
||||
message_id: 2,
|
||||
chat: { id: 7463849194, type: "private" },
|
||||
date: 1_700_000_001,
|
||||
text: "hello again",
|
||||
from: { id: 7463849194, first_name: "Alice" },
|
||||
},
|
||||
sessionRuntime: null,
|
||||
});
|
||||
|
||||
const store = loadSessionStore(storePath, { skipCache: true });
|
||||
expect(context?.ctxPayload?.SessionKey).toBe(TELEGRAM_DIRECT_KEY);
|
||||
expect(store[TELEGRAM_DIRECT_KEY]).toEqual(
|
||||
expect.objectContaining({
|
||||
lastChannel: "telegram",
|
||||
lastTo: "telegram:7463849194",
|
||||
origin: expect.objectContaining({
|
||||
provider: "telegram",
|
||||
chatType: "direct",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,146 +0,0 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { buildTelegramMessageContextForTest } from "./bot-message-context.test-harness.js";
|
||||
|
||||
const internalHookMocks = vi.hoisted(() => ({
|
||||
createInternalHookEvent: vi.fn(
|
||||
(type: string, action: string, sessionKey: string, context: Record<string, unknown>) => ({
|
||||
type,
|
||||
action,
|
||||
sessionKey,
|
||||
context,
|
||||
timestamp: new Date(),
|
||||
messages: [],
|
||||
}),
|
||||
),
|
||||
triggerInternalHook: vi.fn(async () => undefined),
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/hook-runtime", () => {
|
||||
return {
|
||||
createInternalHookEvent: internalHookMocks.createInternalHookEvent,
|
||||
fireAndForgetHook: (task: Promise<unknown>) => void task,
|
||||
toInternalMessageReceivedContext: (context: Record<string, unknown>) => ({
|
||||
...context,
|
||||
metadata: { to: context.to },
|
||||
}),
|
||||
triggerInternalHook: internalHookMocks.triggerInternalHook,
|
||||
};
|
||||
});
|
||||
|
||||
function makeGroupMessage(text: string) {
|
||||
return {
|
||||
message_id: 42,
|
||||
chat: { id: -1001234567890, type: "supergroup" as const, title: "Test Group" },
|
||||
date: 1_700_000_000,
|
||||
text,
|
||||
from: { id: 99, first_name: "Alice", username: "alice" },
|
||||
};
|
||||
}
|
||||
|
||||
describe("telegram mention-skip silent ingest", () => {
|
||||
it("emits internal message:received when ingest is enabled", async () => {
|
||||
internalHookMocks.createInternalHookEvent.mockClear();
|
||||
internalHookMocks.triggerInternalHook.mockClear();
|
||||
|
||||
const result = await buildTelegramMessageContextForTest({
|
||||
message: makeGroupMessage("hello without mention"),
|
||||
cfg: {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "anthropic/sonnet-4.6",
|
||||
workspace: "/tmp/openclaw",
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
telegram: {
|
||||
groups: {
|
||||
"*": {
|
||||
requireMention: true,
|
||||
ingest: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
groupChat: {
|
||||
mentionPatterns: ["@bot"],
|
||||
},
|
||||
},
|
||||
} as never,
|
||||
resolveGroupRequireMention: () => true,
|
||||
resolveTelegramGroupConfig: () => ({
|
||||
groupConfig: {
|
||||
requireMention: true,
|
||||
ingest: true,
|
||||
},
|
||||
topicConfig: undefined,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(internalHookMocks.createInternalHookEvent).toHaveBeenCalledWith(
|
||||
"message",
|
||||
"received",
|
||||
expect.stringContaining("telegram"),
|
||||
expect.objectContaining({
|
||||
channelId: "telegram",
|
||||
content: "hello without mention",
|
||||
}),
|
||||
);
|
||||
expect(internalHookMocks.triggerInternalHook).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("uses wildcard ingest when a specific group override omits ingest", async () => {
|
||||
internalHookMocks.createInternalHookEvent.mockClear();
|
||||
internalHookMocks.triggerInternalHook.mockClear();
|
||||
|
||||
const result = await buildTelegramMessageContextForTest({
|
||||
message: makeGroupMessage("hello without mention"),
|
||||
cfg: {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "anthropic/sonnet-4.6",
|
||||
workspace: "/tmp/openclaw",
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
telegram: {
|
||||
groups: {
|
||||
"*": {
|
||||
requireMention: true,
|
||||
ingest: true,
|
||||
},
|
||||
"-1001234567890": {
|
||||
requireMention: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
groupChat: {
|
||||
mentionPatterns: ["@bot"],
|
||||
},
|
||||
},
|
||||
} as never,
|
||||
resolveGroupRequireMention: () => true,
|
||||
resolveTelegramGroupConfig: () => ({
|
||||
groupConfig: {
|
||||
requireMention: true,
|
||||
},
|
||||
topicConfig: undefined,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(internalHookMocks.createInternalHookEvent).toHaveBeenCalledWith(
|
||||
"message",
|
||||
"received",
|
||||
expect.stringContaining("telegram"),
|
||||
expect.objectContaining({
|
||||
channelId: "telegram",
|
||||
content: "hello without mention",
|
||||
}),
|
||||
);
|
||||
expect(internalHookMocks.triggerInternalHook).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user