diff --git a/extensions/telegram/src/format.test.ts b/extensions/telegram/src/format.test.ts
index 8d4e5dc189e4..8abda0c38156 100644
--- a/extensions/telegram/src/format.test.ts
+++ b/extensions/telegram/src/format.test.ts
@@ -206,6 +206,28 @@ describe("markdownToTelegramHtml", () => {
).toBe('docs');
});
+ it("keeps unsupported markdown link hrefs as visible text in rich HTML", () => {
+ expect(
+ markdownToTelegramRichHtml(
+ "[scripts/yougile.py](/home/dankar/.openclaw/workspace-yougile/scripts/yougile.py#L41)",
+ ),
+ ).toBe("scripts/yougile.py");
+ expect(markdownToTelegramRichHtml("[config](./openclaw.json)")).toBe("config");
+ expect(markdownToTelegramRichHtml("[docs](https://example.com/docs)")).toBe(
+ 'docs',
+ );
+ expect(markdownToTelegramRichHtml("[user](tg://user?id=123)")).toBe(
+ 'user',
+ );
+ expect(markdownToTelegramRichHtml("[support](mailto:user@example.com)")).toBe(
+ 'support',
+ );
+ expect(markdownToTelegramRichHtml("[call](tel:+123456789)")).toBe(
+ 'call',
+ );
+ expect(markdownToTelegramRichHtml("[back](#top)")).toBe('back');
+ });
+
it("preserves Markdown heading levels in rich HTML", () => {
expect(markdownToTelegramRichHtml("# Title\n\n### Detail")).toBe(
"
tags to prevent Telegram from generating
@@ -51,6 +55,11 @@ function buildTelegramLink(link: MarkdownLinkSpan, text: string) {
if (link.start === link.end) {
return null;
}
+ // Telegram rich links reject local or relative hrefs; keep the label visible
+ // instead of letting one unsupported link drop the whole message.
+ if (!isTelegramRichLinkHref(href)) {
+ return null;
+ }
// Suppress auto-linkified file references (e.g. README.md → http://README.md)
const label = text.slice(link.start, link.end);
if (isAutoLinkedFileRef(href, label)) {
diff --git a/extensions/telegram/src/send.test.ts b/extensions/telegram/src/send.test.ts
index 02820156e6c7..f7030679b33a 100644
--- a/extensions/telegram/src/send.test.ts
+++ b/extensions/telegram/src/send.test.ts
@@ -1044,6 +1044,35 @@ describe("sendMessageTelegram", () => {
expect(botRawApi.sendRichMessage.mock.calls[0]?.[0]?.rich_message.html).toBe(markdown);
});
+ it.each([
+ {
+ name: "local path",
+ markdown:
+ "See [scripts/yougile.py](/home/user/.openclaw/workspace/scripts/yougile.py#L41) and [docs](https://example.com/docs)",
+ rejectedAnchor: 'scripts/yougile.py",
+ },
+ {
+ name: "relative path",
+ markdown: "Edit [config](./openclaw.json) or see [docs](https://example.com/docs)",
+ rejectedAnchor: ' {
+ botApi.sendMessage.mockResolvedValue({ message_id: 48, chat: { id: "123" } });
+
+ await sendMessageTelegram("123", testCase.markdown, {
+ cfg: { channels: { telegram: { richMessages: true } } },
+ token: "tok",
+ });
+
+ expect(botRawApi.sendRichMessage).toHaveBeenCalledTimes(1);
+ const richHtml = String(botRawApi.sendRichMessage.mock.calls[0]?.[0]?.rich_message.html ?? "");
+ expect(richHtml).not.toContain(testCase.rejectedAnchor);
+ expect(richHtml).toContain(testCase.visibleLabel);
+ expect(richHtml).toContain('docs');
+ });
+
it("renders complex markdown into HTML text", async () => {
botApi.sendMessage.mockResolvedValue({ message_id: 46, chat: { id: "123" } });
const markdown = [