@iletai/nzb 1.3.5 → 1.3.6
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/copilot/orchestrator.js +4 -3
- package/dist/store/db.js +20 -9
- package/dist/telegram/bot.js +60 -14
- package/package.json +1 -1
|
@@ -395,14 +395,14 @@ export async function sendToOrchestrator(prompt, source, callback, onToolEvent,
|
|
|
395
395
|
processQueue();
|
|
396
396
|
});
|
|
397
397
|
// Deliver response to user FIRST, then log best-effort
|
|
398
|
-
callback(finalContent, true);
|
|
399
398
|
try {
|
|
400
399
|
logMessage("out", sourceLabel, finalContent);
|
|
401
400
|
}
|
|
402
401
|
catch {
|
|
403
402
|
/* best-effort */
|
|
404
403
|
}
|
|
405
|
-
// Log both sides of the conversation
|
|
404
|
+
// Log both sides of the conversation before delivery so we have the row ID
|
|
405
|
+
let assistantLogId;
|
|
406
406
|
try {
|
|
407
407
|
const telegramMsgId = source.type === "telegram" ? source.messageId : undefined;
|
|
408
408
|
logConversation(logRole, prompt, sourceLabel, telegramMsgId);
|
|
@@ -411,11 +411,12 @@ export async function sendToOrchestrator(prompt, source, callback, onToolEvent,
|
|
|
411
411
|
/* best-effort */
|
|
412
412
|
}
|
|
413
413
|
try {
|
|
414
|
-
logConversation("assistant", finalContent, sourceLabel);
|
|
414
|
+
assistantLogId = logConversation("assistant", finalContent, sourceLabel);
|
|
415
415
|
}
|
|
416
416
|
catch {
|
|
417
417
|
/* best-effort */
|
|
418
418
|
}
|
|
419
|
+
callback(finalContent, true, { assistantLogId });
|
|
419
420
|
// Auto-continue: if the response was cut short by timeout, automatically
|
|
420
421
|
// send a follow-up "Continue" message so the user doesn't have to
|
|
421
422
|
if (finalContent.includes("⏱ Response was cut short (timeout)")) {
|
package/dist/store/db.js
CHANGED
|
@@ -100,7 +100,6 @@ export function getDb() {
|
|
|
100
100
|
removeMemory: db.prepare(`DELETE FROM memories WHERE id = ?`),
|
|
101
101
|
memorySummary: db.prepare(`SELECT id, category, content FROM memories ORDER BY category, last_accessed DESC`),
|
|
102
102
|
getConversationByMsgId: db.prepare(`SELECT id FROM conversation_log WHERE telegram_msg_id = ? LIMIT 1`),
|
|
103
|
-
getConversationAround: db.prepare(`SELECT role, content, source, ts FROM conversation_log WHERE id BETWEEN ? - 4 AND ? + 4 ORDER BY id ASC`),
|
|
104
103
|
};
|
|
105
104
|
}
|
|
106
105
|
return db;
|
|
@@ -119,23 +118,35 @@ export function deleteState(key) {
|
|
|
119
118
|
getDb(); // ensure init
|
|
120
119
|
stmtCache.deleteState.run(key);
|
|
121
120
|
}
|
|
122
|
-
/** Log a conversation turn (user, assistant, or system) with optional Telegram message ID. */
|
|
121
|
+
/** Log a conversation turn (user, assistant, or system) with optional Telegram message ID. Returns the row ID. */
|
|
123
122
|
export function logConversation(role, content, source, telegramMsgId) {
|
|
124
123
|
getDb(); // ensure init
|
|
125
|
-
stmtCache.logConversation.run(role, content, source, telegramMsgId ?? null);
|
|
124
|
+
const result = stmtCache.logConversation.run(role, content, source, telegramMsgId ?? null);
|
|
126
125
|
// Keep last 200 entries to support context recovery after session loss
|
|
127
126
|
logInsertCount++;
|
|
128
127
|
if (logInsertCount % 50 === 0) {
|
|
129
128
|
stmtCache.pruneConversation.run();
|
|
130
129
|
}
|
|
130
|
+
return result.lastInsertRowid;
|
|
131
131
|
}
|
|
132
|
-
/** Get conversation context around a Telegram message ID (±
|
|
132
|
+
/** Get conversation context around a Telegram message ID (±4 rows using proper subquery). */
|
|
133
133
|
export function getConversationContext(telegramMsgId) {
|
|
134
|
-
getDb();
|
|
134
|
+
const db = getDb();
|
|
135
135
|
const row = stmtCache.getConversationByMsgId.get(telegramMsgId);
|
|
136
136
|
if (!row)
|
|
137
137
|
return undefined;
|
|
138
|
-
|
|
138
|
+
// Fetch 4 rows before + the target + 4 rows after (handles ID gaps from pruning)
|
|
139
|
+
const rows = db.prepare(`
|
|
140
|
+
SELECT role, content, source, ts FROM (
|
|
141
|
+
SELECT * FROM conversation_log WHERE id < ? ORDER BY id DESC LIMIT 4
|
|
142
|
+
)
|
|
143
|
+
UNION ALL
|
|
144
|
+
SELECT role, content, source, ts FROM conversation_log WHERE id = ?
|
|
145
|
+
UNION ALL
|
|
146
|
+
SELECT role, content, source, ts FROM (
|
|
147
|
+
SELECT * FROM conversation_log WHERE id > ? ORDER BY id ASC LIMIT 4
|
|
148
|
+
)
|
|
149
|
+
`).all(row.id, row.id, row.id);
|
|
139
150
|
if (rows.length === 0)
|
|
140
151
|
return undefined;
|
|
141
152
|
return rows
|
|
@@ -146,10 +157,10 @@ export function getConversationContext(telegramMsgId) {
|
|
|
146
157
|
})
|
|
147
158
|
.join("\n");
|
|
148
159
|
}
|
|
149
|
-
/**
|
|
150
|
-
export function
|
|
160
|
+
/** Set Telegram message ID on a specific conversation_log row (race-free). */
|
|
161
|
+
export function setConversationTelegramMsgId(rowId, telegramMsgId) {
|
|
151
162
|
const db = getDb();
|
|
152
|
-
db.prepare(`UPDATE conversation_log SET telegram_msg_id = ? WHERE id =
|
|
163
|
+
db.prepare(`UPDATE conversation_log SET telegram_msg_id = ? WHERE id = ?`).run(telegramMsgId, rowId);
|
|
153
164
|
}
|
|
154
165
|
/** Get recent conversation history formatted for injection into system message. */
|
|
155
166
|
export function getRecentConversation(limit = 20) {
|
package/dist/telegram/bot.js
CHANGED
|
@@ -454,10 +454,11 @@ export function createBot() {
|
|
|
454
454
|
userPrompt = `[Replying to: "${quoted}"]\n\n${userPrompt}`;
|
|
455
455
|
}
|
|
456
456
|
}
|
|
457
|
-
sendToOrchestrator(userPrompt, { type: "telegram", chatId, messageId: userMessageId }, (text, done) => {
|
|
457
|
+
sendToOrchestrator(userPrompt, { type: "telegram", chatId, messageId: userMessageId }, (text, done, meta) => {
|
|
458
458
|
if (done) {
|
|
459
459
|
finalized = true;
|
|
460
460
|
stopTyping();
|
|
461
|
+
const assistantLogId = meta?.assistantLogId;
|
|
461
462
|
const elapsed = ((Date.now() - handlerStartTime) / 1000).toFixed(1);
|
|
462
463
|
void logInfo(`✅ Response done (${elapsed}s, ${toolHistory.length} tools, ${text.length} chars)`);
|
|
463
464
|
// Wait for in-flight edits to finish before sending the final response
|
|
@@ -513,11 +514,13 @@ export function createBot() {
|
|
|
513
514
|
await bot.api.setMessageReaction(chatId, userMessageId, [{ type: "emoji", emoji: "👍" }]);
|
|
514
515
|
}
|
|
515
516
|
catch { }
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
517
|
+
if (assistantLogId) {
|
|
518
|
+
try {
|
|
519
|
+
const { setConversationTelegramMsgId } = await import("../store/db.js");
|
|
520
|
+
setConversationTelegramMsgId(assistantLogId, placeholderMsgId);
|
|
521
|
+
}
|
|
522
|
+
catch { }
|
|
519
523
|
}
|
|
520
|
-
catch { }
|
|
521
524
|
return;
|
|
522
525
|
}
|
|
523
526
|
catch {
|
|
@@ -527,11 +530,13 @@ export function createBot() {
|
|
|
527
530
|
await bot.api.setMessageReaction(chatId, userMessageId, [{ type: "emoji", emoji: "👍" }]);
|
|
528
531
|
}
|
|
529
532
|
catch { }
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
+
if (assistantLogId) {
|
|
534
|
+
try {
|
|
535
|
+
const { setConversationTelegramMsgId } = await import("../store/db.js");
|
|
536
|
+
setConversationTelegramMsgId(assistantLogId, placeholderMsgId);
|
|
537
|
+
}
|
|
538
|
+
catch { }
|
|
533
539
|
}
|
|
534
|
-
catch { }
|
|
535
540
|
return;
|
|
536
541
|
}
|
|
537
542
|
catch {
|
|
@@ -541,6 +546,7 @@ export function createBot() {
|
|
|
541
546
|
}
|
|
542
547
|
// Multi-chunk or edit fallthrough: send new chunks FIRST, then delete placeholder
|
|
543
548
|
const totalChunks = chunks.length;
|
|
549
|
+
let firstSentMsgId;
|
|
544
550
|
const sendChunk = async (chunk, fallback, index) => {
|
|
545
551
|
const isFirst = index === 0 && !placeholderMsgId;
|
|
546
552
|
// Pagination header for multi-chunk messages
|
|
@@ -548,9 +554,11 @@ export function createBot() {
|
|
|
548
554
|
const opts = isFirst
|
|
549
555
|
? { parse_mode: "MarkdownV2", reply_parameters: replyParams }
|
|
550
556
|
: { parse_mode: "MarkdownV2" };
|
|
551
|
-
await ctx
|
|
557
|
+
const sent = await ctx
|
|
552
558
|
.reply(pageTag + chunk, opts)
|
|
553
559
|
.catch(() => ctx.reply(pageTag + fallback, isFirst ? { reply_parameters: replyParams } : {}));
|
|
560
|
+
if (index === 0 && sent)
|
|
561
|
+
firstSentMsgId = sent.message_id;
|
|
554
562
|
};
|
|
555
563
|
let sendSucceeded = false;
|
|
556
564
|
try {
|
|
@@ -567,7 +575,9 @@ export function createBot() {
|
|
|
567
575
|
if (i > 0)
|
|
568
576
|
await new Promise((r) => setTimeout(r, 300));
|
|
569
577
|
const pageTag = fallbackChunks.length > 1 ? `📄 ${i + 1}/${fallbackChunks.length}\n` : "";
|
|
570
|
-
await ctx.reply(pageTag + fallbackChunks[i], i === 0 ? { reply_parameters: replyParams } : {});
|
|
578
|
+
const sent = await ctx.reply(pageTag + fallbackChunks[i], i === 0 ? { reply_parameters: replyParams } : {});
|
|
579
|
+
if (i === 0 && sent)
|
|
580
|
+
firstSentMsgId = sent.message_id;
|
|
571
581
|
}
|
|
572
582
|
sendSucceeded = true;
|
|
573
583
|
}
|
|
@@ -584,6 +594,15 @@ export function createBot() {
|
|
|
584
594
|
/* ignore — placeholder stays but user has the real message */
|
|
585
595
|
}
|
|
586
596
|
}
|
|
597
|
+
// Track bot message ID for reply-to context lookups
|
|
598
|
+
const botMsgId = firstSentMsgId ?? placeholderMsgId;
|
|
599
|
+
if (assistantLogId && botMsgId) {
|
|
600
|
+
try {
|
|
601
|
+
const { setConversationTelegramMsgId } = await import("../store/db.js");
|
|
602
|
+
setConversationTelegramMsgId(assistantLogId, botMsgId);
|
|
603
|
+
}
|
|
604
|
+
catch { }
|
|
605
|
+
}
|
|
587
606
|
// React ✅ on the user's original message to signal completion
|
|
588
607
|
try {
|
|
589
608
|
await bot.api.setMessageReaction(chatId, userMessageId, [{ type: "emoji", emoji: "👍" }]);
|
|
@@ -769,6 +788,20 @@ export function createBot() {
|
|
|
769
788
|
await ctx.reply("❌ Voice too long (max 5 min).", { reply_parameters: { message_id: userMessageId } });
|
|
770
789
|
return;
|
|
771
790
|
}
|
|
791
|
+
// If voice is a reply, include context
|
|
792
|
+
let voiceReplyContext = "";
|
|
793
|
+
const voiceReplyMsg = ctx.message.reply_to_message;
|
|
794
|
+
if (voiceReplyMsg && "text" in voiceReplyMsg && voiceReplyMsg.text) {
|
|
795
|
+
const { getConversationContext } = await import("../store/db.js");
|
|
796
|
+
const context = getConversationContext(voiceReplyMsg.message_id);
|
|
797
|
+
if (context) {
|
|
798
|
+
voiceReplyContext = `[Continuing from earlier conversation:]\n---\n${context}\n---\n\n`;
|
|
799
|
+
}
|
|
800
|
+
else {
|
|
801
|
+
const quoted = voiceReplyMsg.text.length > 500 ? voiceReplyMsg.text.slice(0, 500) + "…" : voiceReplyMsg.text;
|
|
802
|
+
voiceReplyContext = `[Replying to: "${quoted}"]\n\n`;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
772
805
|
try {
|
|
773
806
|
const file = await ctx.api.getFile(ctx.message.voice.file_id);
|
|
774
807
|
const filePath = file.file_path;
|
|
@@ -819,31 +852,44 @@ export function createBot() {
|
|
|
819
852
|
else {
|
|
820
853
|
prompt = `[User sent a voice message (${duration}s), saved at: ${localPath}. No OPENAI_API_KEY configured for transcription. You can tell the user to set it up in ~/.nzb/.env]`;
|
|
821
854
|
}
|
|
822
|
-
sendToOrchestrator(prompt, { type: "telegram", chatId, messageId: userMessageId }, (text, done) => {
|
|
855
|
+
sendToOrchestrator(voiceReplyContext + prompt, { type: "telegram", chatId, messageId: userMessageId }, (text, done, meta) => {
|
|
823
856
|
if (done) {
|
|
857
|
+
const assistantLogId = meta?.assistantLogId;
|
|
824
858
|
const formatted = toTelegramMarkdown(text);
|
|
825
859
|
const chunks = chunkMessage(formatted);
|
|
826
860
|
const fallbackChunks = chunkMessage(text);
|
|
827
861
|
void (async () => {
|
|
862
|
+
let firstMsgId;
|
|
828
863
|
for (let i = 0; i < chunks.length; i++) {
|
|
829
864
|
if (i > 0)
|
|
830
865
|
await new Promise((r) => setTimeout(r, 300));
|
|
831
866
|
const pageTag = chunks.length > 1 ? `📄 ${i + 1}/${chunks.length}\n` : "";
|
|
832
867
|
try {
|
|
833
|
-
await ctx.api.sendMessage(chatId, pageTag + chunks[i], {
|
|
868
|
+
const sent = await ctx.api.sendMessage(chatId, pageTag + chunks[i], {
|
|
834
869
|
parse_mode: "MarkdownV2",
|
|
835
870
|
reply_parameters: i === 0 ? { message_id: userMessageId } : undefined,
|
|
836
871
|
});
|
|
872
|
+
if (i === 0)
|
|
873
|
+
firstMsgId = sent.message_id;
|
|
837
874
|
}
|
|
838
875
|
catch {
|
|
839
876
|
try {
|
|
840
|
-
await ctx.api.sendMessage(chatId, pageTag + (fallbackChunks[i] ?? chunks[i]), {
|
|
877
|
+
const sent = await ctx.api.sendMessage(chatId, pageTag + (fallbackChunks[i] ?? chunks[i]), {
|
|
841
878
|
reply_parameters: i === 0 ? { message_id: userMessageId } : undefined,
|
|
842
879
|
});
|
|
880
|
+
if (i === 0)
|
|
881
|
+
firstMsgId = sent.message_id;
|
|
843
882
|
}
|
|
844
883
|
catch { }
|
|
845
884
|
}
|
|
846
885
|
}
|
|
886
|
+
if (assistantLogId && firstMsgId) {
|
|
887
|
+
try {
|
|
888
|
+
const { setConversationTelegramMsgId } = await import("../store/db.js");
|
|
889
|
+
setConversationTelegramMsgId(assistantLogId, firstMsgId);
|
|
890
|
+
}
|
|
891
|
+
catch { }
|
|
892
|
+
}
|
|
847
893
|
try {
|
|
848
894
|
await ctx.api.setMessageReaction(chatId, userMessageId, [{ type: "emoji", emoji: "👍" }]);
|
|
849
895
|
}
|