fix(telegram): route bound group native commands

This commit is contained in:
Peter Steinberger
2026-05-02 12:47:00 +01:00
parent 05a5fa81a0
commit e607ad4ab0
4 changed files with 79 additions and 23 deletions

View File

@@ -32,6 +32,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Telegram: honor runtime conversation bindings for native slash commands in bound top-level groups, so commands like `/status@bot` route to the active non-`main` session instead of falling back to the default route. Fixes #75405; supersedes #75558. Thanks @ziptbm and @yfge.
- Models CLI: restore `openclaw models list --provider <id>` catalog and registry fallback rows for unconfigured providers, so provider-specific verification commands no longer report "No models found." Fixes #75517; supersedes #75615. Thanks @lotsoftick and @koshaji.
- Gateway/macOS: write LaunchAgent services with a canonical system PATH and stop preserving old plist PATH entries, so Volta, asdf, fnm, and pnpm shell paths no longer affect gateway child-process Node resolution. Fixes #75233; supersedes #75246. Thanks @nphyde2.
- Slack/hooks: preserve bot alert attachment text in message-received hook content when command text is blank. Fixes #76035; refs #76036. Thanks @amsminn.

View File

@@ -82,6 +82,30 @@ export function createTelegramPrivateCommandContext(params?: {
};
}
export function createTelegramGroupCommandContext(params?: {
match?: string;
messageId?: number;
date?: number;
chatId?: number;
title?: string;
userId?: number;
username?: string;
}) {
return {
match: params?.match ?? "",
message: {
message_id: params?.messageId ?? 2,
date: params?.date ?? Math.floor(Date.now() / 1000),
chat: {
id: params?.chatId ?? -1001234567890,
type: "supergroup" as const,
title: params?.title ?? "OpenClaw",
},
from: { id: params?.userId ?? 200, username: params?.username ?? "bob" },
},
};
}
export function createTelegramTopicCommandContext(params?: {
match?: string;
messageId?: number;

View File

@@ -4,6 +4,7 @@ import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { TelegramNativeCommandDeps } from "./bot-native-command-deps.runtime.js";
import {
createDeferred,
createTelegramGroupCommandContext,
createNativeCommandTestParams,
createTelegramPrivateCommandContext,
createTelegramTopicCommandContext,
@@ -873,6 +874,40 @@ describe("registerTelegramNativeCommands — session metadata", () => {
);
});
it("routes Telegram native commands through bound top-level group sessions", async () => {
sessionBindingMocks.resolveByConversation.mockReturnValue({
bindingId: "default:-1001234567890",
targetSessionKey: "agent:codex-acp:session-group",
});
const { handler } = registerAndResolveStatusHandler({
cfg: {},
allowFrom: ["200"],
groupAllowFrom: ["200"],
});
await handler(createTelegramGroupCommandContext());
expect(sessionBindingMocks.resolveByConversation).toHaveBeenCalledWith({
channel: "telegram",
accountId: "default",
conversationId: "-1001234567890",
});
const dispatchCall = (
replyMocks.dispatchReplyWithBufferedBlockDispatcher.mock.calls as unknown as Array<
[{ ctx?: { CommandTargetSessionKey?: string; OriginatingTo?: string } }]
>
)[0]?.[0];
expect(dispatchCall?.ctx?.CommandTargetSessionKey).toBe("agent:codex-acp:session-group");
expect(dispatchCall?.ctx?.OriginatingTo).toBe("telegram:-1001234567890");
const sessionMetaCall = (
sessionMocks.recordSessionMetaFromInbound.mock.calls as unknown as Array<
[{ sessionKey?: string }]
>
)[0]?.[0];
expect(sessionMetaCall?.sessionKey).toBe("agent:codex-acp:session-group");
expect(sessionBindingMocks.touch).toHaveBeenCalledWith("default:-1001234567890", undefined);
});
it.each(["new", "reset"] as const)(
"preserves the topic-qualified origin target for native /%s in forum topics",
async (commandName) => {

View File

@@ -105,31 +105,27 @@ export function resolveTelegramConversationRoute(params: {
let configuredBindingSessionKey = configuredRoute.boundSessionKey ?? "";
route = configuredRoute.route;
const threadBindingConversationId =
const runtimeBindingConversationId =
params.replyThreadId != null
? `${params.chatId}:topic:${params.replyThreadId}`
: !params.isGroup
? String(params.chatId)
: undefined;
if (threadBindingConversationId) {
const runtimeRoute = resolveRuntimeConversationBindingRoute({
route,
conversation: {
channel: "telegram",
accountId: params.accountId,
conversationId: threadBindingConversationId,
},
});
route = runtimeRoute.route;
if (runtimeRoute.bindingRecord) {
configuredBinding = null;
configuredBindingSessionKey = "";
logVerbose(
runtimeRoute.boundSessionKey
? `telegram: routed via bound conversation ${threadBindingConversationId} -> ${runtimeRoute.boundSessionKey}`
: `telegram: plugin-bound conversation ${threadBindingConversationId}`,
);
}
: String(params.chatId);
const runtimeRoute = resolveRuntimeConversationBindingRoute({
route,
conversation: {
channel: "telegram",
accountId: params.accountId,
conversationId: runtimeBindingConversationId,
},
});
route = runtimeRoute.route;
if (runtimeRoute.bindingRecord) {
configuredBinding = null;
configuredBindingSessionKey = "";
logVerbose(
runtimeRoute.boundSessionKey
? `telegram: routed via bound conversation ${runtimeBindingConversationId} -> ${runtimeRoute.boundSessionKey}`
: `telegram: plugin-bound conversation ${runtimeBindingConversationId}`,
);
}
return {