@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 CHANGED
@@ -16,6 +16,8 @@ KAZEE_DEBUG_EVENTS=
16
16
 
17
17
  WORKSPACE=/path/to/your/workspace
18
18
  CLAUDE_PATH=/path/to/claude
19
+ # Default Claude Code model when users haven't selected one with /model.
20
+ CLAUDE_MODEL=claude-opus-4-8
19
21
  CURSOR_PATH=
20
22
  CODEX_PATH=
21
23
  AUTO_COMPACT_TOKENS=280000
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 (opts.parseMode) o.parse_mode = opts.parseMode;
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, text, o);
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 without it
684
- if (opts.parseMode && o.parse_mode) {
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
- continue;
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(text, o);
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
- if (settings.model) args.push("--model", settings.model);
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
- if (settings.model) args.push("--model", settings.model);
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: "Sonnet", callback_data: "m:sonnet" }, { text: "Haiku", callback_data: "m:haiku" }],
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 restoreAllowedHtmlTags(text) {
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
- // Allow model-authored Telegram HTML tags after escaping the rest of the text.
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(`&lt;${tag}&gt;`, "gi");
48
55
  const close = new RegExp(`&lt;/${tag}&gt;`, "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(/&lt;span class="tg-spoiler"&gt;/gi, '<span class="tg-spoiler">')
55
- .replace(/&lt;\/span&gt;/gi, "</span>");
60
+ .replace(/&lt;span class="tg-spoiler"&gt;/gi, () => stash('<span class="tg-spoiler">'))
61
+ .replace(/&lt;\/span&gt;/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(/&lt;a href="([^"<>]+)"&gt;/gi, (_, href) => {
60
64
  if (!/^(https?:|mailto:|tg:)/i.test(href)) return "";
61
- return `<a href="${href}">`;
62
- }).replace(/&lt;\/a&gt;/gi, "</a>");
65
+ return stash(`<a href="${href}">`);
66
+ }).replace(/&lt;\/a&gt;/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
- out = restoreAllowedHtmlTags(out);
96
- out = convertMarkdownToHtml(out);
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: "Default (current backend)", callback_data: "m:default" }]);
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
- if (settings.model) args.push("--model", settings.model);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "2.2.17",
3
+ "version": "2.2.19",
4
4
  "description": "Your always-on AI coding assistant — Claude Code, Cursor Agent, and OpenAI Codex via Telegram or Kazee Chat",
5
5
  "main": "bot.js",
6
6
  "bin": {