@iletai/nzb 1.3.2 → 1.3.3

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.
@@ -419,6 +419,14 @@ export async function sendToOrchestrator(prompt, source, callback, onToolEvent,
419
419
  // send a follow-up "Continue" message so the user doesn't have to
420
420
  if (finalContent.includes("⏱ Response was cut short (timeout)")) {
421
421
  console.log("[nzb] Auto-continuing after timeout…");
422
+ // Notify user that auto-continue is happening
423
+ if (source.type === "telegram") {
424
+ try {
425
+ const { sendProactiveMessage } = await import("../telegram/bot.js");
426
+ await sendProactiveMessage("🔄 Auto-continuing...");
427
+ }
428
+ catch { }
429
+ }
422
430
  await sleep(1000);
423
431
  void sendToOrchestrator("Continue from where you left off. Do not repeat what was already said.", source, callback, onToolEvent, onUsage);
424
432
  }
@@ -1,6 +1,6 @@
1
1
  import { autoRetry } from "@grammyjs/auto-retry";
2
2
  import { Menu } from "@grammyjs/menu";
3
- import { Bot } from "grammy";
3
+ import { Bot, Keyboard } from "grammy";
4
4
  import { Agent as HttpsAgent } from "https";
5
5
  import { config, persistEnvVar, persistModel } from "../config.js";
6
6
  import { cancelCurrentMessage, getQueueSize, getWorkers, sendToOrchestrator } from "../copilot/orchestrator.js";
@@ -80,7 +80,7 @@ const mainMenu = new Menu("main-menu")
80
80
  await ctx.reply("No memories stored.");
81
81
  }
82
82
  else {
83
- await ctx.reply(formatMemoryList(memories));
83
+ await ctx.reply(formatMemoryList(memories), { parse_mode: "HTML" });
84
84
  }
85
85
  })
86
86
  .submenu("⚙️ Settings", "settings-menu", async (ctx) => {
@@ -110,18 +110,23 @@ const CATEGORY_ICONS = {
110
110
  routine: "🔄",
111
111
  };
112
112
  function formatMemoryList(memories) {
113
- // Group by category
114
113
  const groups = {};
115
114
  for (const m of memories) {
116
115
  (groups[m.category] ??= []).push(m);
117
116
  }
117
+ for (const items of Object.values(groups)) {
118
+ items.sort((a, b) => a.id - b.id);
119
+ }
118
120
  const sections = Object.entries(groups).map(([cat, items]) => {
119
121
  const icon = CATEGORY_ICONS[cat] || "📝";
120
- const header = `${icon} ${cat.charAt(0).toUpperCase() + cat.slice(1)}`;
121
- const lines = items.map((m) => ` #${m.id} ${m.content}`);
122
+ const header = `${icon} <b>${escapeHtml(cat.charAt(0).toUpperCase() + cat.slice(1))}</b>`;
123
+ const lines = items.map((m) => `${m.id}. ${escapeHtml(m.content)}`);
122
124
  return `${header}\n${lines.join("\n")}`;
123
125
  });
124
- return `🧠 Memory (${memories.length})\n\n${sections.join("\n\n")}`;
126
+ return `🧠 <b>${memories.length} memories</b>\n\n${sections.join("\n\n")}`;
127
+ }
128
+ function escapeHtml(text) {
129
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
125
130
  }
126
131
  export function createBot() {
127
132
  if (!config.telegramBotToken) {
@@ -152,8 +157,17 @@ export function createBot() {
152
157
  });
153
158
  // Register interactive menu plugin
154
159
  bot.use(mainMenu);
155
- // /start and /helpwith inline menu
156
- bot.command("start", (ctx) => ctx.reply("NZB is online. Send me anything, or use the menu below:", { reply_markup: mainMenu }));
160
+ // Persistent reply keyboardquick actions always visible below chat input
161
+ const replyKeyboard = new Keyboard()
162
+ .text("📊 Status").text("❌ Cancel").row()
163
+ .text("🧠 Memory").text("🔄 Restart")
164
+ .resized()
165
+ .persistent();
166
+ // /start and /help — with inline menu + reply keyboard
167
+ bot.command("start", async (ctx) => {
168
+ await ctx.reply("NZB is online. Quick actions below ⬇️", { reply_markup: replyKeyboard });
169
+ await ctx.reply("Or use the menu:", { reply_markup: mainMenu });
170
+ });
157
171
  bot.command("help", (ctx) => ctx.reply("I'm NZB, your AI daemon.\n\n" +
158
172
  "Just send me a message and I'll handle it.\n\n" +
159
173
  "Commands:\n" +
@@ -207,7 +221,7 @@ export function createBot() {
207
221
  await ctx.reply("No memories stored.");
208
222
  }
209
223
  else {
210
- await ctx.reply(formatMemoryList(memories));
224
+ await ctx.reply(formatMemoryList(memories), { parse_mode: "HTML" });
211
225
  }
212
226
  });
213
227
  bot.command("skills", async (ctx) => {
@@ -261,6 +275,35 @@ export function createBot() {
261
275
  `🤖 Model: ${config.copilotModel}\n` +
262
276
  ` └ Dùng /model <name> để đổi`, { reply_markup: settingsMenu });
263
277
  });
278
+ // Reply keyboard button handlers — intercept before general text handler
279
+ bot.hears("📊 Status", async (ctx) => {
280
+ const workers = Array.from(getWorkers().values());
281
+ const lines = [
282
+ "📊 NZB Status",
283
+ `Model: ${config.copilotModel}`,
284
+ `Uptime: ${getUptimeStr()}`,
285
+ `Workers: ${workers.length} active`,
286
+ `Queue: ${getQueueSize()} pending`,
287
+ ];
288
+ await ctx.reply(lines.join("\n"));
289
+ });
290
+ bot.hears("❌ Cancel", async (ctx) => {
291
+ const cancelled = await cancelCurrentMessage();
292
+ await ctx.reply(cancelled ? "Cancelled." : "Nothing to cancel.");
293
+ });
294
+ bot.hears("🧠 Memory", async (ctx) => {
295
+ const memories = searchMemories(undefined, undefined, 50);
296
+ if (memories.length === 0) {
297
+ await ctx.reply("No memories stored.");
298
+ }
299
+ else {
300
+ await ctx.reply(formatMemoryList(memories), { parse_mode: "HTML" });
301
+ }
302
+ });
303
+ bot.hears("🔄 Restart", async (ctx) => {
304
+ await ctx.reply("Restarting NZB...");
305
+ setTimeout(() => { restartDaemon().catch(console.error); }, 500);
306
+ });
264
307
  // Handle all text messages — progressive streaming with tool event feedback
265
308
  bot.on("message:text", async (ctx) => {
266
309
  const chatId = ctx.chat.id;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iletai/nzb",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
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"