telegram: use dm thread hook session for plugin commands

This commit is contained in:
Vincent Koc
2026-03-09 11:36:36 -07:00
parent 712821e87d
commit 5d4771e91e
2 changed files with 79 additions and 12 deletions

View File

@@ -270,4 +270,58 @@ describe("registerTelegramNativeCommands", () => {
); );
expect(sendMessage).not.toHaveBeenCalledWith(123, "Command not found."); expect(sendMessage).not.toHaveBeenCalledWith(123, "Command not found.");
}); });
it("uses the DM thread session key for plugin command internal sent hooks", async () => {
const commandHandlers = new Map<string, (ctx: unknown) => Promise<void>>();
pluginCommandMocks.getPluginCommandSpecs.mockReturnValue([
{
name: "plug",
description: "Plugin command",
},
] as never);
pluginCommandMocks.matchPluginCommand.mockReturnValue({
command: { key: "plug", requireAuth: false },
args: undefined,
} as never);
registerTelegramNativeCommands({
...buildParams({
channels: {
telegram: {
dmPolicy: "open",
},
},
}),
bot: {
api: {
setMyCommands: vi.fn().mockResolvedValue(undefined),
sendMessage: vi.fn().mockResolvedValue(undefined),
},
command: vi.fn((name: string, cb: (ctx: unknown) => Promise<void>) => {
commandHandlers.set(name, cb);
}),
} as unknown as Parameters<typeof registerTelegramNativeCommands>[0]["bot"],
});
const handler = commandHandlers.get("plug");
expect(handler).toBeTruthy();
await handler?.({
match: "",
message: {
message_id: 42,
date: Math.floor(Date.now() / 1000),
chat: { id: 12345, type: "private" },
from: { id: 12345, username: "alice" },
message_thread_id: 99,
},
});
expect(deliveryMocks.deliverReplies).toHaveBeenCalledWith(
expect.objectContaining({
sessionKeyForInternalHooks: "agent:main:main:thread:12345:99",
}),
);
});
}); });

View File

@@ -540,6 +540,20 @@ export const registerTelegramNativeCommands = ({
chunkMode: params.chunkMode, chunkMode: params.chunkMode,
linkPreview: telegramCfg.linkPreview, linkPreview: telegramCfg.linkPreview,
}); });
const resolveDmThreadSessionKey = (params: {
baseSessionKey: string;
chatId: string | number;
threadSpec: ReturnType<typeof resolveTelegramThreadSpec>;
}): string => {
const dmThreadId = params.threadSpec.scope === "dm" ? params.threadSpec.id : undefined;
if (dmThreadId == null) {
return params.baseSessionKey;
}
return resolveThreadSessionKeys({
baseSessionKey: params.baseSessionKey,
threadId: `${params.chatId}:${dmThreadId}`,
}).sessionKey;
};
if (commandsToRegister.length > 0 || pluginCatalog.commands.length > 0) { if (commandsToRegister.length > 0 || pluginCatalog.commands.length > 0) {
if (typeof (bot as unknown as { command?: unknown }).command !== "function") { if (typeof (bot as unknown as { command?: unknown }).command !== "function") {
@@ -647,17 +661,11 @@ export const registerTelegramNativeCommands = ({
}); });
return; return;
} }
const baseSessionKey = route.sessionKey; const sessionKey = resolveDmThreadSessionKey({
// DMs: use raw messageThreadId for thread sessions (not resolvedThreadId which is for forums) baseSessionKey: route.sessionKey,
const dmThreadId = threadSpec.scope === "dm" ? threadSpec.id : undefined; chatId,
const threadKeys = threadSpec,
dmThreadId != null });
? resolveThreadSessionKeys({
baseSessionKey,
threadId: `${chatId}:${dmThreadId}`,
})
: null;
const sessionKey = threadKeys?.sessionKey ?? baseSessionKey;
const { skillFilter, groupSystemPrompt } = resolveTelegramGroupPromptSettings({ const { skillFilter, groupSystemPrompt } = resolveTelegramGroupPromptSettings({
groupConfig, groupConfig,
topicConfig, topicConfig,
@@ -833,10 +841,15 @@ export const registerTelegramNativeCommands = ({
return; return;
} }
const { threadSpec, route, mediaLocalRoots, tableMode, chunkMode } = runtimeContext; const { threadSpec, route, mediaLocalRoots, tableMode, chunkMode } = runtimeContext;
const sessionKeyForInternalHooks = resolveDmThreadSessionKey({
baseSessionKey: route.sessionKey,
chatId,
threadSpec,
});
const deliveryBaseOptions = buildCommandDeliveryBaseOptions({ const deliveryBaseOptions = buildCommandDeliveryBaseOptions({
chatId, chatId,
accountId: route.accountId, accountId: route.accountId,
sessionKeyForInternalHooks: route.sessionKey, sessionKeyForInternalHooks,
mirrorIsGroup: isGroup, mirrorIsGroup: isGroup,
mirrorGroupId: isGroup ? String(chatId) : undefined, mirrorGroupId: isGroup ? String(chatId) : undefined,
mediaLocalRoots, mediaLocalRoots,