@inetafrica/open-claudia 2.6.28 → 2.6.29

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.
@@ -12,6 +12,7 @@ const { TEMP_DIR, FILES_DIR } = require("../../core/config");
12
12
  const { canonicalForChannel } = require("../../core/identity");
13
13
  const { inlineKeyboardToPortable } = require("../types");
14
14
  const { createKazeeSocket } = require("./socket");
15
+ const { normalize } = require("./format");
15
16
 
16
17
  class KazeeAdapter {
17
18
  constructor({ id = "kazee", url, token, ownerUserId, botUserId }) {
@@ -98,7 +99,7 @@ class KazeeAdapter {
98
99
  });
99
100
  }
100
101
 
101
- _handleInboundMessage(msg) {
102
+ async _handleInboundMessage(msg) {
102
103
  if (!msg) return;
103
104
  const senderId = String(msg.sender?._id || msg.sender?.id || msg.senderId || "");
104
105
  if (this.botUserId && senderId === String(this.botUserId)) return; // ignore our own echoes
@@ -169,6 +170,37 @@ class KazeeAdapter {
169
170
  }
170
171
  }
171
172
 
173
+ // Voice/audio distinction: chat-central has no "voice" type, so voice
174
+ // notes arrive as "audio". Detect them by mimeType (ogg/opus) and
175
+ // promote to "voice" so the router can transcribe them correctly.
176
+ if (envelope.type === "audio" && envelope.media?.length) {
177
+ const mime = (envelope.media[0].mimeType || "").toLowerCase();
178
+ if (mime.includes("ogg") || mime.includes("opus")) {
179
+ envelope.type = "voice";
180
+ envelope.media[0].type = "voice";
181
+ }
182
+ }
183
+
184
+ // Reply-to context: fetch the replied-to message content so the router
185
+ // has the same context Telegram provides. Best-effort — failure is silent.
186
+ if (msg.replyTo) {
187
+ try {
188
+ const data = await this._request("GET", `/message/${encodeURIComponent(String(msg.replyTo))}`);
189
+ const m = data?.message || data;
190
+ if (m && (m._id || m.id)) {
191
+ envelope.reply = {
192
+ text: m.content || "",
193
+ from: {
194
+ id: String(m.sender?._id || m.sender?.id || ""),
195
+ name: m.sender?.name || m.sender?.displayName || "",
196
+ username: m.sender?.username || "",
197
+ },
198
+ messageId: m._id || m.id,
199
+ };
200
+ }
201
+ } catch (e) { /* best-effort — envelope is still valid without reply context */ }
202
+ }
203
+
172
204
  this._emit("message", envelope);
173
205
  }
174
206
 
@@ -184,24 +216,42 @@ class KazeeAdapter {
184
216
  const body = {
185
217
  chat_id: channelId,
186
218
  sender: this.botUserId,
187
- content: text,
219
+ content: normalize(text),
188
220
  type: buttons ? "interactive" : "text",
189
221
  findMeCode: `oc-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
190
222
  };
191
223
  if (opts.replyTo) body.replyTo = opts.replyTo;
192
224
  if (buttons) body.interactive = { buttons };
193
- try {
194
- const res = await this._request("POST", `/chat/${encodeURIComponent(channelId)}/message`, body);
195
- return res?.message?._id || res?.messageId || res?._id || null;
196
- } catch (e) {
197
- console.error("Kazee send error:", e.message);
198
- return null;
225
+
226
+ for (let attempt = 0; attempt < 3; attempt++) {
227
+ try {
228
+ const res = await this._request("POST", `/chat/${encodeURIComponent(channelId)}/message`, body);
229
+ return res?.message?._id || res?.messageId || res?._id || null;
230
+ } catch (e) {
231
+ const errMsg = e.message || "";
232
+ // Don't retry on client errors (4xx) — they won't self-heal.
233
+ const statusMatch = errMsg.match(/→ (\d{3})/);
234
+ const status = statusMatch ? parseInt(statusMatch[1], 10) : 0;
235
+ if (status >= 400 && status < 500) {
236
+ console.error("Kazee send error (not retrying):", errMsg);
237
+ return null;
238
+ }
239
+ if (attempt < 2) {
240
+ const delay = (attempt + 1) * 1000;
241
+ console.error(`Kazee send error (attempt ${attempt + 1}/3, retrying in ${delay}ms):`, errMsg);
242
+ await new Promise((r) => setTimeout(r, delay));
243
+ continue;
244
+ }
245
+ console.error("Kazee send error (exhausted retries):", errMsg);
246
+ return null;
247
+ }
199
248
  }
249
+ return null;
200
250
  }
201
251
 
202
252
  async edit(channelId, messageId, text, opts = {}) {
203
253
  const buttons = this._normalizeKeyboard(opts.keyboard);
204
- const body = { content: text };
254
+ const body = { content: normalize(text) };
205
255
  if (buttons) {
206
256
  body.type = "interactive";
207
257
  body.interactive = { buttons };
@@ -1,9 +1,27 @@
1
- // Kazee uses standard markdown end-to-end. Telegram-flavoured asterisks
2
- // and underscores still render fine, so this is mostly a hook for future
3
- // divergence (image embeds, code-block fences, etc.).
1
+ // Kazee renders standard markdown end-to-end. This module normalizes text
2
+ // before sending: converts any stray Telegram HTML tags (which the model may
3
+ // accidentally emit) into their markdown equivalents so they render correctly
4
+ // instead of appearing as raw tag noise.
5
+
6
+ function htmlToMarkdown(text) {
7
+ return String(text || "")
8
+ .replace(/<pre><code[^>]*>([\s\S]*?)<\/code><\/pre>/gi, (_, code) => "```\n" + code + "\n```")
9
+ .replace(/<pre>([\s\S]*?)<\/pre>/gi, (_, code) => "```\n" + code + "\n```")
10
+ .replace(/<code>([\s\S]*?)<\/code>/gi, "`$1`")
11
+ .replace(/<b>([\s\S]*?)<\/b>/gi, "**$1**")
12
+ .replace(/<strong>([\s\S]*?)<\/strong>/gi, "**$1**")
13
+ .replace(/<i>([\s\S]*?)<\/i>/gi, "_$1_")
14
+ .replace(/<em>([\s\S]*?)<\/em>/gi, "_$1_")
15
+ .replace(/<s>([\s\S]*?)<\/s>/gi, "~~$1~~")
16
+ .replace(/<strike>([\s\S]*?)<\/strike>/gi, "~~$1~~")
17
+ .replace(/<del>([\s\S]*?)<\/del>/gi, "~~$1~~")
18
+ .replace(/<a href="([^"]+)">([\s\S]*?)<\/a>/gi, "[$2]($1)")
19
+ .replace(/<[^>]+>/g, "")
20
+ .replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#(\d+);/g, (_, n) => String.fromCodePoint(Number(n)));
21
+ }
4
22
 
5
23
  function normalize(text) {
6
- return String(text || "");
24
+ return htmlToMarkdown(String(text || ""));
7
25
  }
8
26
 
9
27
  module.exports = { normalize };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "2.6.28",
3
+ "version": "2.6.29",
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": {