@inetafrica/open-claudia 2.2.17 → 2.2.19
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/.env.example +2 -0
- package/CHANGELOG.md +7 -0
- package/bot-agent.js +34 -9
- package/channels/telegram/format.js +21 -13
- package/core/config.js +2 -0
- package/core/handlers.js +5 -2
- package/core/runner.js +2 -2
- package/package.json +1 -1
package/.env.example
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v2.2.19
|
|
4
|
+
- Add Claude Opus 4.8 (`claude-opus-4-8`) to the Claude model picker and make it the default Claude Code model used when no per-user model override is selected. The default can still be overridden with `CLAUDE_MODEL`, now documented in `.env.example`.
|
|
5
|
+
|
|
6
|
+
## v2.2.18
|
|
7
|
+
- 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.
|
|
8
|
+
- 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.
|
|
9
|
+
|
|
3
10
|
## v2.2.17
|
|
4
11
|
- 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
12
|
- 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));
|
|
@@ -77,6 +78,7 @@ const CHAT_ID = CHAT_IDS[0]; // Primary owner chat for crons/notifications
|
|
|
77
78
|
const WORKSPACE = config.WORKSPACE;
|
|
78
79
|
const CLAUDE_PATH = config.CLAUDE_PATH;
|
|
79
80
|
const CURSOR_PATH = config.CURSOR_PATH || null;
|
|
81
|
+
const DEFAULT_CLAUDE_MODEL = config.CLAUDE_MODEL || process.env.CLAUDE_MODEL || "claude-opus-4-8";
|
|
80
82
|
|
|
81
83
|
// Resolve Cursor Agent CLI (optional)
|
|
82
84
|
let resolvedCursorPath = CURSOR_PATH;
|
|
@@ -653,14 +655,17 @@ function projectKeyboard() {
|
|
|
653
655
|
}
|
|
654
656
|
|
|
655
657
|
async function send(text, opts = {}) {
|
|
658
|
+
const parseMode = opts.parseMode || "HTML";
|
|
659
|
+
const body = parseMode === "HTML" ? telegramHtml(text) : text;
|
|
660
|
+
const fallbackBody = String(text ?? "");
|
|
656
661
|
const o = {};
|
|
657
|
-
if (
|
|
662
|
+
if (parseMode) o.parse_mode = parseMode;
|
|
658
663
|
if (opts.keyboard) o.reply_markup = opts.keyboard;
|
|
659
664
|
if (opts.replyTo) o.reply_to_message_id = opts.replyTo;
|
|
660
665
|
|
|
661
666
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
662
667
|
try {
|
|
663
|
-
const msg = await bot.sendMessage(CHAT_ID,
|
|
668
|
+
const msg = await bot.sendMessage(CHAT_ID, body, o);
|
|
664
669
|
return msg.message_id;
|
|
665
670
|
} catch (e) {
|
|
666
671
|
const errMsg = e.message || "";
|
|
@@ -680,10 +685,17 @@ async function send(text, opts = {}) {
|
|
|
680
685
|
continue;
|
|
681
686
|
}
|
|
682
687
|
|
|
683
|
-
// Parse mode failed — retry
|
|
684
|
-
if (
|
|
688
|
+
// Parse mode failed — retry once as plain text, but do not leak raw HTML tags.
|
|
689
|
+
if (parseMode && o.parse_mode) {
|
|
690
|
+
console.error("Send parse-mode failed, retrying plain text:", errMsg);
|
|
685
691
|
delete o.parse_mode;
|
|
686
|
-
|
|
692
|
+
try {
|
|
693
|
+
const msg = await bot.sendMessage(CHAT_ID, fallbackBody, o);
|
|
694
|
+
return msg.message_id;
|
|
695
|
+
} catch (plainErr) {
|
|
696
|
+
console.error("Send plain-text fallback error:", plainErr.message || plainErr);
|
|
697
|
+
return null;
|
|
698
|
+
}
|
|
687
699
|
}
|
|
688
700
|
|
|
689
701
|
console.error("Send error:", errMsg);
|
|
@@ -695,16 +707,28 @@ async function send(text, opts = {}) {
|
|
|
695
707
|
}
|
|
696
708
|
|
|
697
709
|
async function editMessage(messageId, text, opts = {}) {
|
|
710
|
+
const parseMode = opts.parseMode || "HTML";
|
|
711
|
+
const body = parseMode === "HTML" ? telegramHtml(text) : text;
|
|
712
|
+
const fallbackBody = String(text ?? "");
|
|
698
713
|
try {
|
|
699
714
|
const o = { chat_id: CHAT_ID, message_id: messageId };
|
|
715
|
+
if (parseMode) o.parse_mode = parseMode;
|
|
700
716
|
if (opts.keyboard) o.reply_markup = opts.keyboard;
|
|
701
|
-
await bot.editMessageText(
|
|
717
|
+
await bot.editMessageText(body, o);
|
|
702
718
|
} catch (e) {
|
|
703
719
|
const errMsg = e.message || "";
|
|
704
720
|
// Rate limited — skip this update (next interval will catch up)
|
|
705
721
|
if (errMsg.includes("retry after")) return;
|
|
706
722
|
// Message unchanged — ignore
|
|
707
723
|
if (errMsg.includes("message is not modified")) return;
|
|
724
|
+
if (parseMode) {
|
|
725
|
+
try {
|
|
726
|
+
const fallback = { chat_id: CHAT_ID, message_id: messageId };
|
|
727
|
+
if (opts.keyboard) fallback.reply_markup = opts.keyboard;
|
|
728
|
+
await bot.editMessageText(fallbackBody, fallback);
|
|
729
|
+
return;
|
|
730
|
+
} catch (e2) {}
|
|
731
|
+
}
|
|
708
732
|
// Log anything unexpected
|
|
709
733
|
if (!errMsg.includes("message to edit not found")) {
|
|
710
734
|
console.error("Edit error:", errMsg);
|
|
@@ -1125,7 +1149,7 @@ function buildClaudeArgs(prompt, opts = {}) {
|
|
|
1125
1149
|
"--append-system-prompt", buildSystemPrompt()];
|
|
1126
1150
|
if (opts.continueSession) args.push("--continue");
|
|
1127
1151
|
else if (lastSessionId && !opts.fresh) args.push("--resume", lastSessionId);
|
|
1128
|
-
|
|
1152
|
+
args.push("--model", settings.model || DEFAULT_CLAUDE_MODEL);
|
|
1129
1153
|
if (settings.effort) args.push("--effort", settings.effort);
|
|
1130
1154
|
if (settings.budget) args.push("--max-budget-usd", String(settings.budget));
|
|
1131
1155
|
if (settings.permissionMode) args.push("--permission-mode", settings.permissionMode);
|
|
@@ -1172,7 +1196,7 @@ async function runClaudeChat(prompt, cwd, replyToMsgId) {
|
|
|
1172
1196
|
const args = ["-p", "--verbose", "--output-format", "stream-json",
|
|
1173
1197
|
"--append-system-prompt", buildSystemPrompt(),
|
|
1174
1198
|
"--dangerously-skip-permissions"];
|
|
1175
|
-
|
|
1199
|
+
args.push("--model", settings.model || DEFAULT_CLAUDE_MODEL);
|
|
1176
1200
|
args.push(contextPrompt);
|
|
1177
1201
|
|
|
1178
1202
|
const proc = spawn(CLAUDE_PATH, args, {
|
|
@@ -1637,7 +1661,8 @@ bot.onText(/\/model$/, (msg) => {
|
|
|
1637
1661
|
[{ text: "GPT-5.4", callback_data: "m:gpt-5.4-medium" }, { text: "GPT-5.4 High", callback_data: "m:gpt-5.4-high" }],
|
|
1638
1662
|
[{ text: "Auto", callback_data: "m:auto" }, { text: "Default", callback_data: "m:default" }],
|
|
1639
1663
|
] : [
|
|
1640
|
-
[{ text: "Opus", callback_data: "m:opus" }, { text: "
|
|
1664
|
+
[{ text: "Opus 4.8", callback_data: "m:claude-opus-4-8" }, { text: "Opus 4.7", callback_data: "m:claude-opus-4-7" }],
|
|
1665
|
+
[{ text: "Sonnet", callback_data: "m:sonnet" }, { text: "Haiku", callback_data: "m:haiku" }],
|
|
1641
1666
|
[{ text: "Default", callback_data: "m:default" }],
|
|
1642
1667
|
];
|
|
1643
1668
|
const label = settings.backend === "cursor" ? "Cursor Agent" : "Claude Code";
|
|
@@ -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/core/config.js
CHANGED
|
@@ -47,6 +47,7 @@ const WORKSPACE = config.WORKSPACE;
|
|
|
47
47
|
const CLAUDE_PATH = config.CLAUDE_PATH;
|
|
48
48
|
const CURSOR_PATH = config.CURSOR_PATH || null;
|
|
49
49
|
const CODEX_PATH = config.CODEX_PATH || null;
|
|
50
|
+
const DEFAULT_CLAUDE_MODEL = config.CLAUDE_MODEL || process.env.CLAUDE_MODEL || "claude-opus-4-8";
|
|
50
51
|
const AUTO_COMPACT_TOKENS = parseInt(config.AUTO_COMPACT_TOKENS || process.env.AUTO_COMPACT_TOKENS || "380000", 10);
|
|
51
52
|
const MIN_COMPACT_INTERVAL_MS = parseInt(config.MIN_COMPACT_INTERVAL_MS || process.env.MIN_COMPACT_INTERVAL_MS || "1800000", 10);
|
|
52
53
|
const PROJECT_TRANSCRIPTS = configTruthy(config.PROJECT_TRANSCRIPTS || process.env.PROJECT_TRANSCRIPTS, true);
|
|
@@ -187,6 +188,7 @@ module.exports = {
|
|
|
187
188
|
TOKEN, CHAT_IDS, CHAT_ID,
|
|
188
189
|
WORKSPACE,
|
|
189
190
|
CLAUDE_PATH,
|
|
191
|
+
DEFAULT_CLAUDE_MODEL,
|
|
190
192
|
CURSOR_PATH,
|
|
191
193
|
CODEX_PATH,
|
|
192
194
|
resolvedCursorPath,
|
package/core/handlers.js
CHANGED
|
@@ -9,7 +9,7 @@ const { execSync } = require("child_process");
|
|
|
9
9
|
const {
|
|
10
10
|
WORKSPACE, FULL_PATH, AUTO_COMPACT_TOKENS, CONFIG_DIR,
|
|
11
11
|
config, saveEnvKey,
|
|
12
|
-
CHAT_ID, resolvedCursorPath, resolvedCodexPath, SOUL_FILE,
|
|
12
|
+
CHAT_ID, DEFAULT_CLAUDE_MODEL, resolvedCursorPath, resolvedCodexPath, SOUL_FILE,
|
|
13
13
|
} = require("./config");
|
|
14
14
|
const { register } = require("./commands");
|
|
15
15
|
const { send, deleteMessage } = require("./io");
|
|
@@ -535,8 +535,11 @@ register({
|
|
|
535
535
|
const rows = [];
|
|
536
536
|
rows.push([{ text: "── Claude ──", callback_data: "noop" }]);
|
|
537
537
|
rows.push([
|
|
538
|
+
{ text: "Opus 4.8", callback_data: "mb:claude:claude-opus-4-8" },
|
|
538
539
|
{ text: "Opus 4.7", callback_data: "mb:claude:claude-opus-4-7" },
|
|
539
540
|
{ text: "Opus 4.6", callback_data: "mb:claude:claude-opus-4-6" },
|
|
541
|
+
]);
|
|
542
|
+
rows.push([
|
|
540
543
|
{ text: "Sonnet 4.6", callback_data: "mb:claude:claude-sonnet-4-6" },
|
|
541
544
|
{ text: "Haiku", callback_data: "mb:claude:claude-haiku-4-5-20251001" },
|
|
542
545
|
]);
|
|
@@ -563,7 +566,7 @@ register({
|
|
|
563
566
|
{ text: "o4-mini", callback_data: "mb:codex:o4-mini" },
|
|
564
567
|
]);
|
|
565
568
|
}
|
|
566
|
-
rows.push([{ text:
|
|
569
|
+
rows.push([{ text: `Default (Claude: ${DEFAULT_CLAUDE_MODEL})`, callback_data: "m:default" }]);
|
|
567
570
|
const beLabel = settings.backend === "cursor" ? "Cursor" : settings.backend === "codex" ? "Codex" : "Claude";
|
|
568
571
|
send(`Current: ${beLabel} · ${settings.model || "default"}\n\nPick a model — backend switches automatically.\nOr type /model <name>.`, { keyboard: { inline_keyboard: rows } });
|
|
569
572
|
},
|
package/core/runner.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
const { spawn } = require("child_process");
|
|
7
7
|
const {
|
|
8
|
-
CLAUDE_PATH, resolvedCursorPath, resolvedCodexPath,
|
|
8
|
+
CLAUDE_PATH, DEFAULT_CLAUDE_MODEL, resolvedCursorPath, resolvedCodexPath,
|
|
9
9
|
AUTO_COMPACT_TOKENS, MIN_COMPACT_INTERVAL_MS, MAX_PROCESS_TIMEOUT, COMPACT_SUMMARY_TIMEOUT, botSubprocessEnv,
|
|
10
10
|
} = require("./config");
|
|
11
11
|
const { currentState, saveState, recordSession, userOwnsClaudeSession } = require("./state");
|
|
@@ -124,7 +124,7 @@ function buildClaudeArgs(prompt, opts = {}) {
|
|
|
124
124
|
state.lastSessionId = null;
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
|
-
|
|
127
|
+
args.push("--model", settings.model || DEFAULT_CLAUDE_MODEL);
|
|
128
128
|
if (settings.effort) args.push("--effort", settings.effort);
|
|
129
129
|
if (settings.budget) args.push("--max-budget-usd", String(settings.budget));
|
|
130
130
|
if (settings.permissionMode) args.push("--permission-mode", settings.permissionMode);
|
package/package.json
CHANGED