feat: add telegram topic delivery for cron

This commit is contained in:
Peter Steinberger
2026-01-08 11:03:59 +00:00
parent 874c7465a5
commit 4c09acb2a9
2 changed files with 81 additions and 2 deletions

View File

@@ -20,7 +20,10 @@ vi.mock("../agents/model-catalog.js", () => ({
import { loadModelCatalog } from "../agents/model-catalog.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> {
const base = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-cron-"));
@@ -670,3 +673,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,
});
});
});

View File

@@ -50,6 +50,36 @@ import { resolveTelegramToken } from "../telegram/token.js";
import { normalizeE164 } from "../utils.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 = {
status: "ok" | "error" | "skipped";
summary?: string;
@@ -487,7 +517,7 @@ export async function runCronIsolatedAgentTurn(params: {
summary: "Delivery skipped (no Telegram chatId).",
};
}
const chatId = resolvedDelivery.to;
const { chatId, topicId } = parseTelegramTarget(resolvedDelivery.to);
const textLimit = resolveTextChunkLimit(params.cfg, "telegram");
try {
for (const payload of payloads) {
@@ -501,6 +531,7 @@ export async function runCronIsolatedAgentTurn(params: {
await params.deps.sendMessageTelegram(chatId, chunk, {
verbose: false,
token: telegramToken || undefined,
messageThreadId: topicId,
});
}
} else {
@@ -512,6 +543,7 @@ export async function runCronIsolatedAgentTurn(params: {
verbose: false,
mediaUrl: url,
token: telegramToken || undefined,
messageThreadId: topicId,
});
}
}