fix(cron): support Telegram topic/thread ID in delivery target
When delivering cron job output to Telegram, the 'to' field now supports specifying a topic (forum thread) ID in addition to the chat ID. Supported formats: - chatId (plain chat ID or @username) - chatId:topicId (chat ID with numeric topic ID) - chatId:topic:topicId (alternative format with explicit marker) This enables cron jobs to deliver messages to specific forum topics rather than always going to the main/general topic. Adds parseTelegramTarget helper function with unit tests.
This commit is contained in:
parent
53c037a197
commit
24a6595e81
@ -20,7 +20,10 @@ vi.mock("../agents/model-catalog.js", () => ({
|
|||||||
|
|
||||||
import { loadModelCatalog } from "../agents/model-catalog.js";
|
import { loadModelCatalog } from "../agents/model-catalog.js";
|
||||||
import { runEmbeddedPiAgent } from "../agents/pi-embedded.js";
|
import { runEmbeddedPiAgent } from "../agents/pi-embedded.js";
|
||||||
import { runCronIsolatedAgentTurn } from "./isolated-agent.js";
|
import {
|
||||||
|
parseTelegramTarget,
|
||||||
|
runCronIsolatedAgentTurn,
|
||||||
|
} from "./isolated-agent.js";
|
||||||
|
|
||||||
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||||
const base = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-cron-"));
|
const base = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-cron-"));
|
||||||
@ -598,3 +601,47 @@ describe("runCronIsolatedAgentTurn", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("parseTelegramTarget", () => {
|
||||||
|
it("parses plain chatId", () => {
|
||||||
|
expect(parseTelegramTarget("-1001234567890")).toEqual({
|
||||||
|
chatId: "-1001234567890",
|
||||||
|
topicId: undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("parses @username", () => {
|
||||||
|
expect(parseTelegramTarget("@mychannel")).toEqual({
|
||||||
|
chatId: "@mychannel",
|
||||||
|
topicId: undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("parses chatId:topicId format", () => {
|
||||||
|
expect(parseTelegramTarget("-1001234567890:123")).toEqual({
|
||||||
|
chatId: "-1001234567890",
|
||||||
|
topicId: 123,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("parses chatId:topic:topicId format", () => {
|
||||||
|
expect(parseTelegramTarget("-1001234567890:topic:456")).toEqual({
|
||||||
|
chatId: "-1001234567890",
|
||||||
|
topicId: 456,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("trims whitespace", () => {
|
||||||
|
expect(parseTelegramTarget(" -1001234567890:99 ")).toEqual({
|
||||||
|
chatId: "-1001234567890",
|
||||||
|
topicId: 99,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not treat non-numeric suffix as topicId", () => {
|
||||||
|
expect(parseTelegramTarget("-1001234567890:abc")).toEqual({
|
||||||
|
chatId: "-1001234567890:abc",
|
||||||
|
topicId: undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@ -46,6 +46,36 @@ import { resolveTelegramToken } from "../telegram/token.js";
|
|||||||
import { normalizeE164 } from "../utils.js";
|
import { normalizeE164 } from "../utils.js";
|
||||||
import type { CronJob } from "./types.js";
|
import type { CronJob } from "./types.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a Telegram delivery target into chatId and optional topicId.
|
||||||
|
* Supports formats:
|
||||||
|
* - `chatId` (plain chat ID or @username)
|
||||||
|
* - `chatId:topicId` (chat ID with topic/thread ID)
|
||||||
|
* - `chatId:topic:topicId` (alternative format with explicit "topic" marker)
|
||||||
|
*/
|
||||||
|
export function parseTelegramTarget(to: string): {
|
||||||
|
chatId: string;
|
||||||
|
topicId: number | undefined;
|
||||||
|
} {
|
||||||
|
const trimmed = to.trim();
|
||||||
|
|
||||||
|
// Try format: chatId:topic:topicId
|
||||||
|
const topicMatch = /^(.+?):topic:(\d+)$/.exec(trimmed);
|
||||||
|
if (topicMatch) {
|
||||||
|
return { chatId: topicMatch[1], topicId: parseInt(topicMatch[2], 10) };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try format: chatId:topicId (where topicId is numeric)
|
||||||
|
// Be careful not to match @username or other non-numeric suffixes
|
||||||
|
const colonMatch = /^(.+):(\d+)$/.exec(trimmed);
|
||||||
|
if (colonMatch) {
|
||||||
|
return { chatId: colonMatch[1], topicId: parseInt(colonMatch[2], 10) };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plain chatId, no topic
|
||||||
|
return { chatId: trimmed, topicId: undefined };
|
||||||
|
}
|
||||||
|
|
||||||
export type RunCronAgentTurnResult = {
|
export type RunCronAgentTurnResult = {
|
||||||
status: "ok" | "error" | "skipped";
|
status: "ok" | "error" | "skipped";
|
||||||
summary?: string;
|
summary?: string;
|
||||||
@ -436,7 +466,7 @@ export async function runCronIsolatedAgentTurn(params: {
|
|||||||
summary: "Delivery skipped (no Telegram chatId).",
|
summary: "Delivery skipped (no Telegram chatId).",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const chatId = resolvedDelivery.to;
|
const { chatId, topicId } = parseTelegramTarget(resolvedDelivery.to);
|
||||||
const textLimit = resolveTextChunkLimit(params.cfg, "telegram");
|
const textLimit = resolveTextChunkLimit(params.cfg, "telegram");
|
||||||
try {
|
try {
|
||||||
for (const payload of payloads) {
|
for (const payload of payloads) {
|
||||||
@ -450,6 +480,7 @@ export async function runCronIsolatedAgentTurn(params: {
|
|||||||
await params.deps.sendMessageTelegram(chatId, chunk, {
|
await params.deps.sendMessageTelegram(chatId, chunk, {
|
||||||
verbose: false,
|
verbose: false,
|
||||||
token: telegramToken || undefined,
|
token: telegramToken || undefined,
|
||||||
|
messageThreadId: topicId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -461,6 +492,7 @@ export async function runCronIsolatedAgentTurn(params: {
|
|||||||
verbose: false,
|
verbose: false,
|
||||||
mediaUrl: url,
|
mediaUrl: url,
|
||||||
token: telegramToken || undefined,
|
token: telegramToken || undefined,
|
||||||
|
messageThreadId: topicId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user