@iletai/nzb 1.1.7 → 1.2.0
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/dist/config.js +3 -0
- package/dist/telegram/bot.js +26 -28
- package/dist/telegram/log-channel.js +35 -0
- package/package.json +2 -2
package/dist/config.js
CHANGED
|
@@ -12,6 +12,7 @@ const configSchema = z.object({
|
|
|
12
12
|
COPILOT_MODEL: z.string().optional(),
|
|
13
13
|
WORKER_TIMEOUT: z.string().optional(),
|
|
14
14
|
SHOW_REASONING: z.string().optional(),
|
|
15
|
+
LOG_CHANNEL_ID: z.string().optional(),
|
|
15
16
|
NODE_EXTRA_CA_CERTS: z.string().optional(),
|
|
16
17
|
});
|
|
17
18
|
const raw = configSchema.parse(process.env);
|
|
@@ -33,12 +34,14 @@ const parsedWorkerTimeout = raw.WORKER_TIMEOUT ? Number(raw.WORKER_TIMEOUT) : DE
|
|
|
33
34
|
if (!Number.isInteger(parsedWorkerTimeout) || parsedWorkerTimeout <= 0) {
|
|
34
35
|
throw new Error(`WORKER_TIMEOUT must be a positive integer (ms), got: "${raw.WORKER_TIMEOUT}"`);
|
|
35
36
|
}
|
|
37
|
+
const parsedLogChannelId = raw.LOG_CHANNEL_ID ? raw.LOG_CHANNEL_ID.trim() : undefined;
|
|
36
38
|
export const DEFAULT_MODEL = "claude-sonnet-4.6";
|
|
37
39
|
let _copilotModel = raw.COPILOT_MODEL || DEFAULT_MODEL;
|
|
38
40
|
export const config = {
|
|
39
41
|
telegramBotToken: raw.TELEGRAM_BOT_TOKEN,
|
|
40
42
|
authorizedUserId: parsedUserId,
|
|
41
43
|
apiPort: parsedPort,
|
|
44
|
+
logChannelId: parsedLogChannelId,
|
|
42
45
|
workerTimeoutMs: parsedWorkerTimeout,
|
|
43
46
|
get copilotModel() {
|
|
44
47
|
return _copilotModel;
|
package/dist/telegram/bot.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { appendFileSync } from "fs";
|
|
2
1
|
import { Bot, InlineKeyboard } from "grammy";
|
|
3
2
|
import { Agent as HttpsAgent } from "https";
|
|
4
3
|
import { config, persistEnvVar, persistModel } from "../config.js";
|
|
@@ -7,6 +6,7 @@ import { listSkills } from "../copilot/skills.js";
|
|
|
7
6
|
import { restartDaemon } from "../daemon.js";
|
|
8
7
|
import { searchMemories } from "../store/db.js";
|
|
9
8
|
import { chunkMessage, formatToolSummaryExpandable, toTelegramMarkdown } from "./formatter.js";
|
|
9
|
+
import { initLogChannel, logDebug, logError, logInfo } from "./log-channel.js";
|
|
10
10
|
let bot;
|
|
11
11
|
const startedAt = Date.now();
|
|
12
12
|
// Inline keyboard menu for quick actions
|
|
@@ -41,6 +41,7 @@ export function createBot() {
|
|
|
41
41
|
},
|
|
42
42
|
});
|
|
43
43
|
console.log("[nzb] Telegram bot using direct HTTPS agent (proxy bypass)");
|
|
44
|
+
initLogChannel(bot);
|
|
44
45
|
// Auth middleware — only allow the authorized user
|
|
45
46
|
bot.use(async (ctx, next) => {
|
|
46
47
|
if (config.authorizedUserId !== undefined && ctx.from?.id !== config.authorizedUserId) {
|
|
@@ -244,6 +245,8 @@ export function createBot() {
|
|
|
244
245
|
const chatId = ctx.chat.id;
|
|
245
246
|
const userMessageId = ctx.message.message_id;
|
|
246
247
|
const replyParams = { message_id: userMessageId };
|
|
248
|
+
const msgPreview = ctx.message.text.length > 80 ? ctx.message.text.slice(0, 80) + "…" : ctx.message.text;
|
|
249
|
+
void logInfo(`📩 Message: ${msgPreview}`);
|
|
247
250
|
// Typing indicator — keeps sending "typing" action every 4s until the final
|
|
248
251
|
// response is delivered. We use bot.api directly for reliability, and await the
|
|
249
252
|
// first call so the user sees typing immediately before any async work begins.
|
|
@@ -325,12 +328,9 @@ export function createBot() {
|
|
|
325
328
|
.catch(() => { });
|
|
326
329
|
};
|
|
327
330
|
const onToolEvent = (event) => {
|
|
328
|
-
try {
|
|
329
|
-
appendFileSync("/tmp/nzb-tool-debug.log", `${new Date().toISOString()} BOT ${event.type} ${event.toolName}\n`);
|
|
330
|
-
}
|
|
331
|
-
catch { }
|
|
332
331
|
console.log(`[nzb] Bot received tool event: ${event.type} ${event.toolName}`);
|
|
333
332
|
if (event.type === "tool_start") {
|
|
333
|
+
void logDebug(`🔧 Tool start: ${event.toolName}`);
|
|
334
334
|
currentToolName = event.toolName;
|
|
335
335
|
toolHistory.push({ name: event.toolName, startTime: Date.now() });
|
|
336
336
|
const existingText = lastEditedText.replace(/^🔧 .*\n\n/, "");
|
|
@@ -374,11 +374,14 @@ export function createBot() {
|
|
|
374
374
|
if (done) {
|
|
375
375
|
finalized = true;
|
|
376
376
|
stopTyping();
|
|
377
|
+
const elapsed = ((Date.now() - handlerStartTime) / 1000).toFixed(1);
|
|
378
|
+
void logInfo(`✅ Response done (${elapsed}s, ${toolHistory.length} tools, ${text.length} chars)`);
|
|
377
379
|
// Wait for in-flight edits to finish before sending the final response
|
|
378
380
|
void editChain.then(async () => {
|
|
379
381
|
// Format error messages with a distinct visual
|
|
380
382
|
const isError = text.startsWith("Error:");
|
|
381
383
|
if (isError) {
|
|
384
|
+
void logError(`Response error: ${text.slice(0, 200)}`);
|
|
382
385
|
const errorText = `⚠️ ${text}`;
|
|
383
386
|
if (placeholderMsgId) {
|
|
384
387
|
try {
|
|
@@ -412,21 +415,9 @@ export function createBot() {
|
|
|
412
415
|
}
|
|
413
416
|
const formatted = toTelegramMarkdown(textWithMeta);
|
|
414
417
|
let fullFormatted = formatted;
|
|
415
|
-
try {
|
|
416
|
-
appendFileSync("/tmp/nzb-tool-debug.log", `${new Date().toISOString()} FINAL showReasoning=${config.showReasoning} toolHistory=${toolHistory.length}\n`);
|
|
417
|
-
}
|
|
418
|
-
catch { }
|
|
419
418
|
if (config.showReasoning && toolHistory.length > 0) {
|
|
420
419
|
const expandable = formatToolSummaryExpandable(toolHistory.map((t) => ({ name: t.name, durationMs: t.durationMs })));
|
|
421
420
|
fullFormatted += expandable;
|
|
422
|
-
try {
|
|
423
|
-
appendFileSync("/tmp/nzb-tool-debug.log", `${new Date().toISOString()} EXPANDABLE=${JSON.stringify(expandable)}\n`);
|
|
424
|
-
}
|
|
425
|
-
catch { }
|
|
426
|
-
try {
|
|
427
|
-
appendFileSync("/tmp/nzb-tool-debug.log", `${new Date().toISOString()} FULL_LAST200=${JSON.stringify(fullFormatted.slice(-200))}\n`);
|
|
428
|
-
}
|
|
429
|
-
catch { }
|
|
430
421
|
}
|
|
431
422
|
const chunks = chunkMessage(fullFormatted);
|
|
432
423
|
const fallbackChunks = chunkMessage(textWithMeta);
|
|
@@ -446,18 +437,10 @@ export function createBot() {
|
|
|
446
437
|
}
|
|
447
438
|
}
|
|
448
439
|
}
|
|
449
|
-
// Multi-chunk or
|
|
450
|
-
if (placeholderMsgId) {
|
|
451
|
-
try {
|
|
452
|
-
await bot.api.deleteMessage(chatId, placeholderMsgId);
|
|
453
|
-
}
|
|
454
|
-
catch {
|
|
455
|
-
/* ignore */
|
|
456
|
-
}
|
|
457
|
-
}
|
|
440
|
+
// Multi-chunk or edit fallthrough: send new chunks FIRST, then delete placeholder
|
|
458
441
|
const totalChunks = chunks.length;
|
|
459
442
|
const sendChunk = async (chunk, fallback, index) => {
|
|
460
|
-
const isFirst = index === 0;
|
|
443
|
+
const isFirst = index === 0 && !placeholderMsgId;
|
|
461
444
|
// Pagination header for multi-chunk messages
|
|
462
445
|
const pageTag = totalChunks > 1 ? `📄 ${index + 1}/${totalChunks}\n` : "";
|
|
463
446
|
const opts = isFirst
|
|
@@ -467,12 +450,14 @@ export function createBot() {
|
|
|
467
450
|
.reply(pageTag + chunk, opts)
|
|
468
451
|
.catch(() => ctx.reply(pageTag + fallback, isFirst ? { reply_parameters: replyParams } : {}));
|
|
469
452
|
};
|
|
453
|
+
let sendSucceeded = false;
|
|
470
454
|
try {
|
|
471
455
|
for (let i = 0; i < chunks.length; i++) {
|
|
472
456
|
if (i > 0)
|
|
473
457
|
await new Promise((r) => setTimeout(r, 300));
|
|
474
458
|
await sendChunk(chunks[i], fallbackChunks[i] ?? chunks[i], i);
|
|
475
459
|
}
|
|
460
|
+
sendSucceeded = true;
|
|
476
461
|
}
|
|
477
462
|
catch {
|
|
478
463
|
try {
|
|
@@ -482,11 +467,21 @@ export function createBot() {
|
|
|
482
467
|
const pageTag = fallbackChunks.length > 1 ? `📄 ${i + 1}/${fallbackChunks.length}\n` : "";
|
|
483
468
|
await ctx.reply(pageTag + fallbackChunks[i], i === 0 ? { reply_parameters: replyParams } : {});
|
|
484
469
|
}
|
|
470
|
+
sendSucceeded = true;
|
|
485
471
|
}
|
|
486
472
|
catch {
|
|
487
473
|
/* nothing more we can do */
|
|
488
474
|
}
|
|
489
475
|
}
|
|
476
|
+
// Only delete placeholder AFTER new messages sent successfully
|
|
477
|
+
if (placeholderMsgId && sendSucceeded) {
|
|
478
|
+
try {
|
|
479
|
+
await bot.api.deleteMessage(chatId, placeholderMsgId);
|
|
480
|
+
}
|
|
481
|
+
catch {
|
|
482
|
+
/* ignore — placeholder stays but user has the real message */
|
|
483
|
+
}
|
|
484
|
+
}
|
|
490
485
|
});
|
|
491
486
|
}
|
|
492
487
|
else {
|
|
@@ -536,7 +531,10 @@ export async function startBot() {
|
|
|
536
531
|
}
|
|
537
532
|
bot
|
|
538
533
|
.start({
|
|
539
|
-
onStart: () =>
|
|
534
|
+
onStart: () => {
|
|
535
|
+
console.log("[nzb] Telegram bot connected");
|
|
536
|
+
void logInfo(`🚀 NZB v${process.env.npm_package_version || "?"} started (model: ${config.copilotModel})`);
|
|
537
|
+
},
|
|
540
538
|
})
|
|
541
539
|
.catch((err) => {
|
|
542
540
|
if (err?.error_code === 401) {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { config } from "../config.js";
|
|
2
|
+
let botRef;
|
|
3
|
+
/** Initialize the log channel with a bot reference */
|
|
4
|
+
export function initLogChannel(bot) {
|
|
5
|
+
botRef = bot;
|
|
6
|
+
}
|
|
7
|
+
const ICONS = {
|
|
8
|
+
info: "ℹ️",
|
|
9
|
+
warn: "⚠️",
|
|
10
|
+
error: "🔴",
|
|
11
|
+
debug: "🔍",
|
|
12
|
+
};
|
|
13
|
+
/** Send a log message to the configured Telegram channel */
|
|
14
|
+
export async function sendLog(level, message) {
|
|
15
|
+
if (!botRef || !config.logChannelId)
|
|
16
|
+
return;
|
|
17
|
+
const icon = ICONS[level];
|
|
18
|
+
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
|
+
try {
|
|
21
|
+
await botRef.api.sendMessage(config.logChannelId, text, { parse_mode: "HTML" });
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// best-effort — don't crash if log channel is unreachable
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** Convenience wrappers */
|
|
28
|
+
export const logInfo = (msg) => sendLog("info", msg);
|
|
29
|
+
export const logWarn = (msg) => sendLog("warn", msg);
|
|
30
|
+
export const logError = (msg) => sendLog("error", msg);
|
|
31
|
+
export const logDebug = (msg) => sendLog("debug", msg);
|
|
32
|
+
function escapeHtml(text) {
|
|
33
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=log-channel.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iletai/nzb",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
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"
|
|
@@ -61,4 +61,4 @@
|
|
|
61
61
|
"tsx": "^4.21.0",
|
|
62
62
|
"typescript": "^5.9.3"
|
|
63
63
|
}
|
|
64
|
-
}
|
|
64
|
+
}
|