@iletai/nzb 1.6.3 → 1.6.4

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.
@@ -212,7 +212,8 @@ export async function sendWorkerNotification(message) {
212
212
  if (!bot || config.authorizedUserId === undefined)
213
213
  return;
214
214
  try {
215
- await bot.api.sendMessage(config.authorizedUserId, message);
215
+ const { truncateForTelegram } = await import("./formatter.js");
216
+ await bot.api.sendMessage(config.authorizedUserId, truncateForTelegram(message));
216
217
  }
217
218
  catch {
218
219
  // best-effort — don't crash if notification fails
@@ -85,6 +85,12 @@ function findSafeSplitIndex(text, targetIndex) {
85
85
  }
86
86
  return targetIndex;
87
87
  }
88
+ /** Truncate text to fit within Telegram's message length limit. */
89
+ export function truncateForTelegram(text, limit = TELEGRAM_MAX_LENGTH) {
90
+ if (text.length <= limit)
91
+ return text;
92
+ return text.slice(0, limit - 4) + " ⋯";
93
+ }
88
94
  /**
89
95
  * Split a long message into chunks that fit within Telegram's message limit.
90
96
  * Full HTML-aware: tracks all open tags (pre, code, blockquote, b, i, s, u, a, tg-spoiler)
@@ -1,8 +1,9 @@
1
1
  import { config, persistEnvVar, persistModel } from "../../config.js";
2
- import { cancelCurrentMessage, compactSession, getQueueSize, getWorkers, resetSession } from "../../copilot/orchestrator.js";
2
+ import { cancelCurrentMessage, compactSession, getQueueSize, getWorkers, resetSession, } from "../../copilot/orchestrator.js";
3
3
  import { listSkills } from "../../copilot/skills.js";
4
4
  import { restartDaemon } from "../../daemon.js";
5
5
  import { searchMemories } from "../../store/db.js";
6
+ import { chunkMessage } from "../formatter.js";
6
7
  import { buildSettingsText, formatMemoryList } from "../menus.js";
7
8
  import { getReactionHelpText } from "./reactions.js";
8
9
  export function registerCommandHandlers(bot, deps) {
@@ -133,7 +134,11 @@ export function registerCommandHandlers(bot, deps) {
133
134
  await ctx.reply("No memories stored.");
134
135
  }
135
136
  else {
136
- await ctx.reply(formatMemoryList(memories), { parse_mode: "HTML" });
137
+ const formatted = formatMemoryList(memories);
138
+ const chunks = chunkMessage(formatted);
139
+ for (const chunk of chunks) {
140
+ await ctx.reply(chunk, { parse_mode: "HTML" });
141
+ }
137
142
  }
138
143
  });
139
144
  bot.command("skills", async (ctx) => {
@@ -143,7 +148,11 @@ export function registerCommandHandlers(bot, deps) {
143
148
  }
144
149
  else {
145
150
  const lines = skills.map((s) => `• ${s.name} (${s.source}) — ${s.description}`);
146
- await ctx.reply(lines.join("\n"));
151
+ const text = lines.join("\n");
152
+ const chunks = chunkMessage(text);
153
+ for (const chunk of chunks) {
154
+ await ctx.reply(chunk);
155
+ }
147
156
  }
148
157
  });
149
158
  bot.command("workers", async (ctx) => {
@@ -153,7 +162,11 @@ export function registerCommandHandlers(bot, deps) {
153
162
  }
154
163
  else {
155
164
  const lines = workers.map((w) => `• ${w.name} (${w.workingDir}) — ${w.status}`);
156
- await ctx.reply(lines.join("\n"));
165
+ const text = lines.join("\n");
166
+ const chunks = chunkMessage(text);
167
+ for (const chunk of chunks) {
168
+ await ctx.reply(chunk);
169
+ }
157
170
  }
158
171
  });
159
172
  bot.command("status", async (ctx) => {
@@ -206,7 +219,11 @@ export function registerCommandHandlers(bot, deps) {
206
219
  await ctx.reply("No memories stored.");
207
220
  }
208
221
  else {
209
- await ctx.reply(formatMemoryList(memories), { parse_mode: "HTML" });
222
+ const formatted = formatMemoryList(memories);
223
+ const chunks = chunkMessage(formatted);
224
+ for (const chunk of chunks) {
225
+ await ctx.reply(chunk, { parse_mode: "HTML" });
226
+ }
210
227
  }
211
228
  });
212
229
  bot.hears("🔄 Restart", async (ctx) => {
@@ -67,7 +67,7 @@ export function registerInlineQueryHandler(bot) {
67
67
  try {
68
68
  const chatId = ctx.chat?.id;
69
69
  if (chatId) {
70
- const truncated = text.length > 4000 ? text.slice(0, 4000) + "\n\n⋯" : text;
70
+ const truncated = text.length > 3900 ? text.slice(0, 3900) + "\n\n⋯" : text;
71
71
  await bot.api.sendMessage(chatId, `🔍 <b>Detailed Answer:</b>\n\n${escapeHtml(truncated)}`, {
72
72
  parse_mode: "HTML",
73
73
  });
@@ -10,15 +10,18 @@ const ICONS = {
10
10
  error: "🔴",
11
11
  debug: "🔍",
12
12
  };
13
+ const MAX_LOG_LENGTH = 4096;
13
14
  /** Send a log message to the configured Telegram channel */
14
15
  export async function sendLog(level, message) {
15
16
  if (!botRef || !config.logChannelId)
16
17
  return;
17
18
  const icon = ICONS[level];
18
19
  const timestamp = new Date().toISOString().replace("T", " ").slice(0, 19);
19
- const text = `${icon} <b>[${level.toUpperCase()}]</b> <code>${timestamp}</code>\n${escapeHtml(message)}`;
20
+ const header = `${icon} <b>[${level.toUpperCase()}]</b> <code>${timestamp}</code>\n`;
21
+ const maxBody = MAX_LOG_LENGTH - header.length - 4;
22
+ const body = message.length > maxBody ? escapeHtml(message.slice(0, maxBody)) + " ⋯" : escapeHtml(message);
20
23
  try {
21
- await botRef.api.sendMessage(config.logChannelId, text, { parse_mode: "HTML" });
24
+ await botRef.api.sendMessage(config.logChannelId, header + body, { parse_mode: "HTML" });
22
25
  }
23
26
  catch {
24
27
  // best-effort — don't crash if log channel is unreachable
@@ -3,7 +3,7 @@ import { config, persistEnvVar, persistModel } from "../config.js";
3
3
  import { cancelCurrentMessage, getQueueSize, getWorkers } from "../copilot/orchestrator.js";
4
4
  import { listSkills } from "../copilot/skills.js";
5
5
  import { searchMemories } from "../store/db.js";
6
- import { escapeHtml } from "./formatter.js";
6
+ import { chunkMessage, escapeHtml, truncateForTelegram } from "./formatter.js";
7
7
  // Worker timeout presets (ms → display label)
8
8
  export const TIMEOUT_PRESETS = [
9
9
  { ms: 600_000, label: "10min" },
@@ -174,7 +174,7 @@ export function createMenus(getUptimeStr) {
174
174
  }
175
175
  else {
176
176
  const lines = workers.map((w) => `• ${w.name} (${w.workingDir}) — ${w.status}`);
177
- await ctx.reply(lines.join("\n"));
177
+ await ctx.reply(truncateForTelegram(lines.join("\n")));
178
178
  }
179
179
  })
180
180
  .text("🧠 Skills", async (ctx) => {
@@ -185,7 +185,7 @@ export function createMenus(getUptimeStr) {
185
185
  }
186
186
  else {
187
187
  const lines = skills.map((s) => `• ${s.name} (${s.source}) — ${s.description}`);
188
- await ctx.reply(lines.join("\n"));
188
+ await ctx.reply(truncateForTelegram(lines.join("\n")));
189
189
  }
190
190
  })
191
191
  .row()
@@ -196,7 +196,11 @@ export function createMenus(getUptimeStr) {
196
196
  await ctx.reply("No memories stored.");
197
197
  }
198
198
  else {
199
- await ctx.reply(formatMemoryList(memories), { parse_mode: "HTML" });
199
+ const formatted = formatMemoryList(memories);
200
+ const chunks = chunkMessage(formatted);
201
+ for (const chunk of chunks) {
202
+ await ctx.reply(chunk, { parse_mode: "HTML" });
203
+ }
200
204
  }
201
205
  })
202
206
  .submenu("⚙️ Settings", "settings-menu", async (ctx) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iletai/nzb",
3
- "version": "1.6.3",
3
+ "version": "1.6.4",
4
4
  "description": "NZB — a personal AI assistant for developers, built on the GitHub Copilot SDK",
5
5
  "bin": {
6
6
  "nzb": "dist/cli.js"