@inetafrica/open-claudia 2.2.17 → 2.2.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/bot-agent.js +29 -6
- package/channels/telegram/format.js +21 -13
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v2.2.18
|
|
4
|
+
- Fix the legacy Telegram send/edit path so replies default to Telegram HTML parse mode and go through the shared formatter, preventing raw `<b>`, `<a>`, and related tags from leaking in chat.
|
|
5
|
+
- Preserve safe Telegram HTML tags before Markdown conversion, so underscores inside link URLs such as Higgsfield `wan2_2_video` are not misread as italics and do not break rendered links.
|
|
6
|
+
|
|
3
7
|
## v2.2.17
|
|
4
8
|
- Telegram output now uses `parse_mode: "HTML"` instead of legacy Markdown. The Telegram adapter normalizes both model-authored Telegram HTML and ordinary Markdown/CommonMark into Telegram's safe HTML subset before sending, so `<b>...</b>`, `**bold**`, backticks, links, headings, code blocks, strikethrough, and spoilers render cleanly instead of leaking literal markup or being rejected by Telegram. Messages still fall back to plain text if Telegram rejects the markup.
|
|
5
9
|
- Updated the Telegram system prompt to ask agents for short mobile-readable Telegram HTML (`<b>`, `<code>`, `<pre>`, `<a>`) with bullet-style layout. Relay sends and slash-command responses now use the same HTML parse mode as normal assistant replies.
|
package/bot-agent.js
CHANGED
|
@@ -6,6 +6,7 @@ const https = require("https");
|
|
|
6
6
|
const cron = require("node-cron");
|
|
7
7
|
const Vault = require("./vault");
|
|
8
8
|
const CONFIG_DIR = require("./config-dir");
|
|
9
|
+
const { telegramHtml } = require("./channels/telegram/format");
|
|
9
10
|
|
|
10
11
|
// ── Graceful shutdown & error handling ─────────────────────────────
|
|
11
12
|
process.on("SIGINT", () => process.exit(0));
|
|
@@ -653,14 +654,17 @@ function projectKeyboard() {
|
|
|
653
654
|
}
|
|
654
655
|
|
|
655
656
|
async function send(text, opts = {}) {
|
|
657
|
+
const parseMode = opts.parseMode || "HTML";
|
|
658
|
+
const body = parseMode === "HTML" ? telegramHtml(text) : text;
|
|
659
|
+
const fallbackBody = String(text ?? "");
|
|
656
660
|
const o = {};
|
|
657
|
-
if (
|
|
661
|
+
if (parseMode) o.parse_mode = parseMode;
|
|
658
662
|
if (opts.keyboard) o.reply_markup = opts.keyboard;
|
|
659
663
|
if (opts.replyTo) o.reply_to_message_id = opts.replyTo;
|
|
660
664
|
|
|
661
665
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
662
666
|
try {
|
|
663
|
-
const msg = await bot.sendMessage(CHAT_ID,
|
|
667
|
+
const msg = await bot.sendMessage(CHAT_ID, body, o);
|
|
664
668
|
return msg.message_id;
|
|
665
669
|
} catch (e) {
|
|
666
670
|
const errMsg = e.message || "";
|
|
@@ -680,10 +684,17 @@ async function send(text, opts = {}) {
|
|
|
680
684
|
continue;
|
|
681
685
|
}
|
|
682
686
|
|
|
683
|
-
// Parse mode failed — retry
|
|
684
|
-
if (
|
|
687
|
+
// Parse mode failed — retry once as plain text, but do not leak raw HTML tags.
|
|
688
|
+
if (parseMode && o.parse_mode) {
|
|
689
|
+
console.error("Send parse-mode failed, retrying plain text:", errMsg);
|
|
685
690
|
delete o.parse_mode;
|
|
686
|
-
|
|
691
|
+
try {
|
|
692
|
+
const msg = await bot.sendMessage(CHAT_ID, fallbackBody, o);
|
|
693
|
+
return msg.message_id;
|
|
694
|
+
} catch (plainErr) {
|
|
695
|
+
console.error("Send plain-text fallback error:", plainErr.message || plainErr);
|
|
696
|
+
return null;
|
|
697
|
+
}
|
|
687
698
|
}
|
|
688
699
|
|
|
689
700
|
console.error("Send error:", errMsg);
|
|
@@ -695,16 +706,28 @@ async function send(text, opts = {}) {
|
|
|
695
706
|
}
|
|
696
707
|
|
|
697
708
|
async function editMessage(messageId, text, opts = {}) {
|
|
709
|
+
const parseMode = opts.parseMode || "HTML";
|
|
710
|
+
const body = parseMode === "HTML" ? telegramHtml(text) : text;
|
|
711
|
+
const fallbackBody = String(text ?? "");
|
|
698
712
|
try {
|
|
699
713
|
const o = { chat_id: CHAT_ID, message_id: messageId };
|
|
714
|
+
if (parseMode) o.parse_mode = parseMode;
|
|
700
715
|
if (opts.keyboard) o.reply_markup = opts.keyboard;
|
|
701
|
-
await bot.editMessageText(
|
|
716
|
+
await bot.editMessageText(body, o);
|
|
702
717
|
} catch (e) {
|
|
703
718
|
const errMsg = e.message || "";
|
|
704
719
|
// Rate limited — skip this update (next interval will catch up)
|
|
705
720
|
if (errMsg.includes("retry after")) return;
|
|
706
721
|
// Message unchanged — ignore
|
|
707
722
|
if (errMsg.includes("message is not modified")) return;
|
|
723
|
+
if (parseMode) {
|
|
724
|
+
try {
|
|
725
|
+
const fallback = { chat_id: CHAT_ID, message_id: messageId };
|
|
726
|
+
if (opts.keyboard) fallback.reply_markup = opts.keyboard;
|
|
727
|
+
await bot.editMessageText(fallbackBody, fallback);
|
|
728
|
+
return;
|
|
729
|
+
} catch (e2) {}
|
|
730
|
+
}
|
|
708
731
|
// Log anything unexpected
|
|
709
732
|
if (!errMsg.includes("message to edit not found")) {
|
|
710
733
|
console.error("Edit error:", errMsg);
|
|
@@ -39,29 +39,33 @@ function stashCode(text) {
|
|
|
39
39
|
return { text: out, blocks };
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
function
|
|
42
|
+
function stashAllowedHtmlTags(text) {
|
|
43
|
+
const tags = [];
|
|
44
|
+
const stash = (html) => {
|
|
45
|
+
const token = `\u0000OCTGHTML${tags.length}\u0000`;
|
|
46
|
+
tags.push(html);
|
|
47
|
+
return token;
|
|
48
|
+
};
|
|
43
49
|
let out = text;
|
|
44
50
|
|
|
45
|
-
//
|
|
51
|
+
// Hide model-authored Telegram HTML tags from Markdown conversion. This keeps
|
|
52
|
+
// underscores in hrefs such as wan2_2_video from becoming bogus <i> tags.
|
|
46
53
|
for (const tag of ["b", "strong", "i", "em", "u", "s", "strike", "del", "code", "pre", "blockquote"]) {
|
|
47
54
|
const open = new RegExp(`<${tag}>`, "gi");
|
|
48
55
|
const close = new RegExp(`</${tag}>`, "gi");
|
|
49
|
-
out = out.replace(open, `<${tag}>`).replace(close, `</${tag}>`);
|
|
56
|
+
out = out.replace(open, () => stash(`<${tag}>`)).replace(close, () => stash(`</${tag}>`));
|
|
50
57
|
}
|
|
51
58
|
|
|
52
|
-
// Telegram spoiler HTML.
|
|
53
59
|
out = out
|
|
54
|
-
.replace(/<span class="tg-spoiler">/gi, '<span class="tg-spoiler">')
|
|
55
|
-
.replace(/<\/span>/gi, "</span>");
|
|
60
|
+
.replace(/<span class="tg-spoiler">/gi, () => stash('<span class="tg-spoiler">'))
|
|
61
|
+
.replace(/<\/span>/gi, () => stash("</span>"));
|
|
56
62
|
|
|
57
|
-
// Safe links only. Quotes are not escaped by escapeHtml(), but href content
|
|
58
|
-
// is still constrained to http(s)/mailto/tg schemes to avoid broken markup.
|
|
59
63
|
out = out.replace(/<a href="([^"<>]+)">/gi, (_, href) => {
|
|
60
64
|
if (!/^(https?:|mailto:|tg:)/i.test(href)) return "";
|
|
61
|
-
return `<a href="${href}"
|
|
62
|
-
}).replace(/<\/a>/gi, "</a>");
|
|
65
|
+
return stash(`<a href="${href}">`);
|
|
66
|
+
}).replace(/<\/a>/gi, () => stash("</a>"));
|
|
63
67
|
|
|
64
|
-
return out;
|
|
68
|
+
return { text: out, tags };
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
function convertMarkdownToHtml(text) {
|
|
@@ -92,8 +96,12 @@ function convertMarkdownToHtml(text) {
|
|
|
92
96
|
function telegramHtml(text) {
|
|
93
97
|
const { text: withoutCode, blocks } = stashCode(text);
|
|
94
98
|
let out = escapeHtml(withoutCode);
|
|
95
|
-
|
|
96
|
-
out = convertMarkdownToHtml(
|
|
99
|
+
const { text: withoutHtmlTags, tags } = stashAllowedHtmlTags(out);
|
|
100
|
+
out = convertMarkdownToHtml(withoutHtmlTags);
|
|
101
|
+
|
|
102
|
+
tags.forEach((html, i) => {
|
|
103
|
+
out = out.replace(`\u0000OCTGHTML${i}\u0000`, html);
|
|
104
|
+
});
|
|
97
105
|
|
|
98
106
|
blocks.forEach((html, i) => {
|
|
99
107
|
out = out.replace(`\u0000OCTGCODE${i}\u0000`, html);
|
package/package.json
CHANGED