diff --git a/src/telegram/format.test.ts b/src/telegram/format.test.ts
index 48e95343750..6b0e1944f70 100644
--- a/src/telegram/format.test.ts
+++ b/src/telegram/format.test.ts
@@ -95,6 +95,18 @@ describe("markdownToTelegramHtml", () => {
expect(res).toBe('bold');
});
+ it("wraps punctuated file references in code tags", () => {
+ const res = markdownToTelegramHtml("See README.md. Also (backup.sh).");
+ expect(res).toContain("README.md.");
+ expect(res).toContain("(backup.sh).");
+ });
+
+ it("keeps .co domains as links", () => {
+ const res = markdownToTelegramHtml("Visit t.co and openclaw.co");
+ expect(res).toContain('t.co');
+ expect(res).toContain('openclaw.co');
+ });
+
it("renders spoiler tags", () => {
const res = markdownToTelegramHtml("the answer is ||42||");
expect(res).toBe("the answer is 42");
diff --git a/src/telegram/format.ts b/src/telegram/format.ts
index dae60ff1d96..f919a917f9f 100644
--- a/src/telegram/format.ts
+++ b/src/telegram/format.ts
@@ -139,22 +139,52 @@ function escapeRegex(str: string): string {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
-export function wrapFileReferencesInHtml(html: string): string {
- // Build regex pattern for all tracked extensions (escape metacharacters for safety)
- const extensionsPattern = Array.from(FILE_EXTENSIONS_WITH_TLD).map(escapeRegex).join("|");
+const FILE_EXTENSIONS_PATTERN = Array.from(FILE_EXTENSIONS_WITH_TLD).map(escapeRegex).join("|");
+const AUTO_LINKED_ANCHOR_PATTERN = /]*>\1<\/a>/gi;
+const FILE_REFERENCE_PATTERN = new RegExp(
+ `(^|[^a-zA-Z0-9_\\-/])([a-zA-Z0-9_.\\-./]+\\.(?:${FILE_EXTENSIONS_PATTERN}))(?=$|[^a-zA-Z0-9_\\-/])`,
+ "gi",
+);
+const ORPHANED_TLD_PATTERN = new RegExp(
+ `([^a-zA-Z0-9]|^)([A-Za-z]\\.(?:${FILE_EXTENSIONS_PATTERN}))(?=[^a-zA-Z0-9/]|$)`,
+ "g",
+);
+const HTML_TAG_PATTERN = /(<\/?)([a-zA-Z][a-zA-Z0-9-]*)\b[^>]*?>/gi;
+function wrapStandaloneFileRef(match: string, prefix: string, filename: string): string {
+ if (filename.startsWith("//")) {
+ return match;
+ }
+ if (/https?:\/\/$/i.test(prefix)) {
+ return match;
+ }
+ return `${prefix}${escapeHtml(filename)}`;
+}
+
+function wrapSegmentFileRefs(
+ text: string,
+ codeDepth: number,
+ preDepth: number,
+ anchorDepth: number,
+): string {
+ if (!text || codeDepth > 0 || preDepth > 0 || anchorDepth > 0) {
+ return text;
+ }
+ const wrappedStandalone = text.replace(FILE_REFERENCE_PATTERN, wrapStandaloneFileRef);
+ return wrappedStandalone.replace(ORPHANED_TLD_PATTERN, (match, prefix: string, tld: string) =>
+ prefix === ">" ? match : `${prefix}${escapeHtml(tld)}`,
+ );
+}
+
+export function wrapFileReferencesInHtml(html: string): string {
// Safety-net: de-linkify auto-generated anchors where href="http://