diff --git a/CHANGELOG.md b/CHANGELOG.md index e26bfd6eb27..dd90b3a99be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,7 @@ Docs: https://docs.openclaw.ai - Telegram/photos: preflight Telegram photo dimension and aspect-ratio rules, and fall back to document sends when image metadata is invalid or unavailable so photo uploads stop failing with `PHOTO_INVALID_DIMENSIONS`. (#52545) Thanks @hnshah. - Feishu/groups: when `groupPolicy` is `open`, stop implicitly requiring @mentions for unset `requireMention`, so image, file, audio, and other non-text group messages reach the bot unless operators explicitly keep mention gating on. (#54058) Thanks @byungsker. - Agents/cron: mark best-effort announce runs as not delivered when any payload fails, and log those partial delivery failures instead of silently reporting success. (#42535) Thanks @MoerAI. +- CLI/Telegram topics: route `message thread create` through Telegram `topic-create` with the required topic `name` field so Telegram forum topic creation works from the CLI again. (#54336) Thanks @andyliu. ## 2026.3.23 diff --git a/src/cli/program/message/register.thread.test.ts b/src/cli/program/message/register.thread.test.ts index af7e8bd5745..c7fd3409c9b 100644 --- a/src/cli/program/message/register.thread.test.ts +++ b/src/cli/program/message/register.thread.test.ts @@ -1,14 +1,89 @@ -import { describe, expect, it } from "vitest"; -import { __test__ } from "./register.thread.js"; +import { Command } from "commander"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import type { MessageCliHelpers } from "./helpers.js"; +import { registerMessageThreadCommands } from "./register.thread.js"; -describe("resolveThreadCreateAction", () => { - it("maps telegram thread create to topic-create", () => { - expect(__test__.resolveThreadCreateAction({ channel: "telegram" })).toBe("topic-create"); - expect(__test__.resolveThreadCreateAction({ channel: " Telegram " })).toBe("topic-create"); +function createHelpers(runMessageAction: MessageCliHelpers["runMessageAction"]): MessageCliHelpers { + return { + withMessageBase: (command) => command.option("--channel ", "Channel"), + withMessageTarget: (command) => command.option("-t, --target ", "Target"), + withRequiredMessageTarget: (command) => command.requiredOption("-t, --target ", "Target"), + runMessageAction, + }; +} + +describe("registerMessageThreadCommands", () => { + const runMessageAction = vi.fn( + async (_action: string, _opts: Record) => undefined, + ); + + beforeEach(() => { + runMessageAction.mockClear(); }); - it("keeps thread-create for non-telegram channels", () => { - expect(__test__.resolveThreadCreateAction({ channel: "discord" })).toBe("thread-create"); - expect(__test__.resolveThreadCreateAction({ channel: undefined })).toBe("thread-create"); + it("routes Telegram thread create to topic-create with Telegram params", async () => { + const message = new Command().exitOverride(); + registerMessageThreadCommands(message, createHelpers(runMessageAction)); + + await message.parseAsync( + [ + "thread", + "create", + "--channel", + " Telegram ", + "-t", + "-1001234567890", + "--thread-name", + "Build Updates", + "-m", + "hello", + ], + { from: "user" }, + ); + + expect(runMessageAction).toHaveBeenCalledWith( + "topic-create", + expect.objectContaining({ + channel: " Telegram ", + target: "-1001234567890", + name: "Build Updates", + message: "hello", + }), + ); + const telegramCall = runMessageAction.mock.calls.at(0); + expect(telegramCall?.[1]).not.toHaveProperty("threadName"); + }); + + it("keeps non-Telegram thread create on thread-create params", async () => { + const message = new Command().exitOverride(); + registerMessageThreadCommands(message, createHelpers(runMessageAction)); + + await message.parseAsync( + [ + "thread", + "create", + "--channel", + "discord", + "-t", + "channel:123", + "--thread-name", + "Build Updates", + "-m", + "hello", + ], + { from: "user" }, + ); + + expect(runMessageAction).toHaveBeenCalledWith( + "thread-create", + expect.objectContaining({ + channel: "discord", + target: "channel:123", + threadName: "Build Updates", + message: "hello", + }), + ); + const discordCall = runMessageAction.mock.calls.at(0); + expect(discordCall?.[1]).not.toHaveProperty("name"); }); }); diff --git a/src/cli/program/message/register.thread.ts b/src/cli/program/message/register.thread.ts index 5f3b5519fe6..7df3007c568 100644 --- a/src/cli/program/message/register.thread.ts +++ b/src/cli/program/message/register.thread.ts @@ -1,9 +1,22 @@ import type { Command } from "commander"; import type { MessageCliHelpers } from "./helpers.js"; -function resolveThreadCreateAction(opts: { channel?: unknown }) { +function resolveThreadCreateRequest(opts: Record) { const channel = typeof opts.channel === "string" ? opts.channel.trim().toLowerCase() : ""; - return channel === "telegram" ? "topic-create" : "thread-create"; + if (channel !== "telegram") { + return { + action: "thread-create" as const, + params: opts, + }; + } + const { threadName, ...rest } = opts; + return { + action: "topic-create" as const, + params: { + ...rest, + name: typeof threadName === "string" ? threadName : undefined, + }, + }; } export function registerMessageThreadCommands(message: Command, helpers: MessageCliHelpers) { @@ -22,7 +35,8 @@ export function registerMessageThreadCommands(message: Command, helpers: Message .option("-m, --message ", "Initial thread message text") .option("--auto-archive-min ", "Thread auto-archive minutes") .action(async (opts) => { - await helpers.runMessageAction(resolveThreadCreateAction(opts), opts); + const request = resolveThreadCreateRequest(opts); + await helpers.runMessageAction(request.action, request.params); }); helpers @@ -59,4 +73,4 @@ export function registerMessageThreadCommands(message: Command, helpers: Message }); } -export const __test__ = { resolveThreadCreateAction }; +export const __test__ = { resolveThreadCreateRequest };