@iletai/nzb 1.3.4 → 1.3.5

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.
@@ -404,7 +404,8 @@ export async function sendToOrchestrator(prompt, source, callback, onToolEvent,
404
404
  }
405
405
  // Log both sides of the conversation after delivery
406
406
  try {
407
- logConversation(logRole, prompt, sourceLabel);
407
+ const telegramMsgId = source.type === "telegram" ? source.messageId : undefined;
408
+ logConversation(logRole, prompt, sourceLabel, telegramMsgId);
408
409
  }
409
410
  catch {
410
411
  /* best-effort */
package/dist/store/db.js CHANGED
@@ -33,9 +33,19 @@ export function getDb() {
33
33
  role TEXT NOT NULL CHECK(role IN ('user', 'assistant', 'system')),
34
34
  content TEXT NOT NULL,
35
35
  source TEXT NOT NULL DEFAULT 'unknown',
36
+ telegram_msg_id INTEGER,
36
37
  ts DATETIME DEFAULT CURRENT_TIMESTAMP
37
38
  )
38
39
  `);
40
+ // Migrate: add telegram_msg_id column if missing
41
+ try {
42
+ db.prepare(`SELECT telegram_msg_id FROM conversation_log LIMIT 1`).get();
43
+ }
44
+ catch {
45
+ db.exec(`ALTER TABLE conversation_log ADD COLUMN telegram_msg_id INTEGER`);
46
+ }
47
+ // Index for fast reply-to lookups
48
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_conv_telegram_msg ON conversation_log (telegram_msg_id)`);
39
49
  db.exec(`
40
50
  CREATE TABLE IF NOT EXISTS memories (
41
51
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -60,6 +70,7 @@ export function getDb() {
60
70
  role TEXT NOT NULL CHECK(role IN ('user', 'assistant', 'system')),
61
71
  content TEXT NOT NULL,
62
72
  source TEXT NOT NULL DEFAULT 'unknown',
73
+ telegram_msg_id INTEGER,
63
74
  ts DATETIME DEFAULT CURRENT_TIMESTAMP
64
75
  )
65
76
  `);
@@ -83,11 +94,13 @@ export function getDb() {
83
94
  getState: db.prepare(`SELECT value FROM nzb_state WHERE key = ?`),
84
95
  setState: db.prepare(`INSERT OR REPLACE INTO nzb_state (key, value) VALUES (?, ?)`),
85
96
  deleteState: db.prepare(`DELETE FROM nzb_state WHERE key = ?`),
86
- logConversation: db.prepare(`INSERT INTO conversation_log (role, content, source) VALUES (?, ?, ?)`),
97
+ logConversation: db.prepare(`INSERT INTO conversation_log (role, content, source, telegram_msg_id) VALUES (?, ?, ?, ?)`),
87
98
  pruneConversation: db.prepare(`DELETE FROM conversation_log WHERE id NOT IN (SELECT id FROM conversation_log ORDER BY id DESC LIMIT 200)`),
88
99
  addMemory: db.prepare(`INSERT INTO memories (category, content, source) VALUES (?, ?, ?)`),
89
100
  removeMemory: db.prepare(`DELETE FROM memories WHERE id = ?`),
90
101
  memorySummary: db.prepare(`SELECT id, category, content FROM memories ORDER BY category, last_accessed DESC`),
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`),
91
104
  };
92
105
  }
93
106
  return db;
@@ -106,16 +119,38 @@ export function deleteState(key) {
106
119
  getDb(); // ensure init
107
120
  stmtCache.deleteState.run(key);
108
121
  }
109
- /** Log a conversation turn (user, assistant, or system). */
110
- export function logConversation(role, content, source) {
122
+ /** Log a conversation turn (user, assistant, or system) with optional Telegram message ID. */
123
+ export function logConversation(role, content, source, telegramMsgId) {
111
124
  getDb(); // ensure init
112
- stmtCache.logConversation.run(role, content, source);
125
+ stmtCache.logConversation.run(role, content, source, telegramMsgId ?? null);
113
126
  // Keep last 200 entries to support context recovery after session loss
114
127
  logInsertCount++;
115
128
  if (logInsertCount % 50 === 0) {
116
129
  stmtCache.pruneConversation.run();
117
130
  }
118
131
  }
132
+ /** Get conversation context around a Telegram message ID (±2 turns). */
133
+ export function getConversationContext(telegramMsgId) {
134
+ getDb(); // ensure init
135
+ const row = stmtCache.getConversationByMsgId.get(telegramMsgId);
136
+ if (!row)
137
+ return undefined;
138
+ const rows = stmtCache.getConversationAround.all(row.id, row.id);
139
+ if (rows.length === 0)
140
+ return undefined;
141
+ return rows
142
+ .map((r) => {
143
+ const tag = r.role === "user" ? "You" : r.role === "assistant" ? "NZB" : "System";
144
+ const content = r.content.length > 400 ? r.content.slice(0, 400) + "…" : r.content;
145
+ return `${tag}: ${content}`;
146
+ })
147
+ .join("\n");
148
+ }
149
+ /** Update the most recent assistant log entry with its Telegram message ID (for reply-to lookups). */
150
+ export function updateLastAssistantTelegramMsgId(telegramMsgId) {
151
+ const db = getDb();
152
+ db.prepare(`UPDATE conversation_log SET telegram_msg_id = ? WHERE id = (SELECT id FROM conversation_log WHERE role = 'assistant' ORDER BY id DESC LIMIT 1)`).run(telegramMsgId);
153
+ }
119
154
  /** Get recent conversation history formatted for injection into system message. */
120
155
  export function getRecentConversation(limit = 20) {
121
156
  const db = getDb();
@@ -439,12 +439,20 @@ export function createBot() {
439
439
  const onUsage = (usage) => {
440
440
  usageInfo = usage;
441
441
  };
442
- // If user replies to a message, include that message's text as context
442
+ // If user replies to a message, include surrounding conversation context
443
443
  let userPrompt = ctx.message.text;
444
444
  const replyMsg = ctx.message.reply_to_message;
445
445
  if (replyMsg && "text" in replyMsg && replyMsg.text) {
446
- const quoted = replyMsg.text.length > 500 ? replyMsg.text.slice(0, 500) + "…" : replyMsg.text;
447
- userPrompt = `[Replying to: "${quoted}"]\n\n${userPrompt}`;
446
+ // Try to find full conversation context around the replied message
447
+ const { getConversationContext } = await import("../store/db.js");
448
+ const context = getConversationContext(replyMsg.message_id);
449
+ if (context) {
450
+ userPrompt = `[Continuing from earlier conversation:]\n---\n${context}\n---\n\n[Your reply]: ${userPrompt}`;
451
+ }
452
+ else {
453
+ const quoted = replyMsg.text.length > 500 ? replyMsg.text.slice(0, 500) + "…" : replyMsg.text;
454
+ userPrompt = `[Replying to: "${quoted}"]\n\n${userPrompt}`;
455
+ }
448
456
  }
449
457
  sendToOrchestrator(userPrompt, { type: "telegram", chatId, messageId: userMessageId }, (text, done) => {
450
458
  if (done) {
@@ -505,6 +513,11 @@ export function createBot() {
505
513
  await bot.api.setMessageReaction(chatId, userMessageId, [{ type: "emoji", emoji: "👍" }]);
506
514
  }
507
515
  catch { }
516
+ try {
517
+ const { updateLastAssistantTelegramMsgId } = await import("../store/db.js");
518
+ updateLastAssistantTelegramMsgId(placeholderMsgId);
519
+ }
520
+ catch { }
508
521
  return;
509
522
  }
510
523
  catch {
@@ -514,6 +527,11 @@ export function createBot() {
514
527
  await bot.api.setMessageReaction(chatId, userMessageId, [{ type: "emoji", emoji: "👍" }]);
515
528
  }
516
529
  catch { }
530
+ try {
531
+ const { updateLastAssistantTelegramMsgId } = await import("../store/db.js");
532
+ updateLastAssistantTelegramMsgId(placeholderMsgId);
533
+ }
534
+ catch { }
517
535
  return;
518
536
  }
519
537
  catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iletai/nzb",
3
- "version": "1.3.4",
3
+ "version": "1.3.5",
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"