From 2b07fff073ec84dc170b135332902d37e9b450fd Mon Sep 17 00:00:00 2001 From: Kilo Date: Wed, 6 May 2026 17:53:44 +0000 Subject: [PATCH] feat: reply context injection + crash-loop guard 1. Reply context: When user replies/tags a message in Telegram, inject the original message text as [Replying to previous message:] prefix so the AI has full context. Previously ignored reply_to_message entirely, causing 'make hero more exciting' to have zero context about which page. 2. System prompt: Added CONTEXT AWARENESS section instructing the AI to use reply context and never ask 'which page?' when context is provided. 3. Crash-loop guard: killStaleProcess now checks /proc/pid/stat to get process age. Skips killing processes younger than 15 seconds, preventing the mutual-kill cycle where systemd restarts before old instance dies. --- src/bot/index.js | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/bot/index.js b/src/bot/index.js index 1209d199..a56e63e5 100644 --- a/src/bot/index.js +++ b/src/bot/index.js @@ -89,6 +89,12 @@ function buildSystemPrompt(svc) { 'You run 24/7 as a Telegram bot. You are NOT a generic GLM model — you are zCode CLI X,', 'a specialized autonomous coding agent. Identify ONLY as zCode CLI X.', '', + '## CONTEXT AWARENESS (CRITICAL)', + 'When the user\'s message starts with [Replying to previous message:], the quoted text is', + 'the message they are responding to. You MUST use that context to understand what they want.', + 'Example: if they reply to a page design with "make hero more exciting", they mean THAT page\'s hero.', + 'Never ask "which page?" or "what are you referring to?" — the reply context tells you.', + '', '## SELF-LEARNING', 'You have PERSISTENT MEMORY across sessions. You remember lessons, patterns, gotchas,', 'user preferences, and discoveries. When you learn something useful, save it to memory.', @@ -1239,6 +1245,21 @@ export async function initBot(config, api, tools, skills, agents) { const key = buildSessionKey(ctx.chat.id, ctx.message?.message_thread_id); const user = ctx.from?.username || ctx.from?.first_name || 'Unknown'; const prefix = isVoice ? '🎤' : '💬'; + + // ── Reply context: inject replied-to message as context (Ruflo pattern) ── + // When user replies/tags a previous message, Telegram sends reply_to_message. + // Without this, the bot sees "make hero more exciting" with ZERO context about which page. + const replyTo = ctx.message?.reply_to_message; + if (replyTo) { + const repliedText = replyTo.text || replyTo.caption || ''; + if (repliedText) { + // Truncate long replies to avoid context bloat + const snippet = repliedText.length > 500 ? repliedText.substring(0, 500) + '…' : repliedText; + text = `[Replying to previous message:\n${snippet}]\n\n${text}`; + logger.info(`📎 Reply context injected (${repliedText.length} chars)`); + } + } + logger.info(`${prefix} ${user}: ${text.substring(0, 80)}…`); await queueRequest(key, text, async () => { @@ -1488,6 +1509,31 @@ export async function initBot(config, api, tools, skills, agents) { function killStaleProcess(pid) { if (!isProcessAlive(pid)) return false; + // Guard: don't kill processes younger than 15 seconds (prevents crash-loop + // where systemd restarts before old instance finishes dying, causing mutual kills) + try { + const stat = fs.readFileSync(`/proc/${pid}/stat`, 'utf8'); + // Field 22 in /proc/pid/stat is starttime (in jiffies since boot) + const fields = stat.split(')'); + if (fields.length > 1) { + const statFields = fields[1].trim().split(/\s+/); + const startTimeTicks = parseInt(statFields[19], 10); + if (!isNaN(startTimeTicks)) { + const bootTime = fs.readFileSync('/proc/stat', 'utf8') + .split('\n').find(l => l.startsWith('btime '))?.split(/\s+/)[1]; + if (bootTime) { + const startTimeMs = (parseInt(bootTime) + startTimeTicks / 100) * 1000; + const age = Date.now() - startTimeMs; + if (age < 15000) { + logger.warn(` Skipping PID ${pid} — only ${Math.round(age / 1000)}s old (crash-loop guard)`); + return false; + } + } + } + } + } catch { + // /proc not available (non-Linux) — skip guard + } try { process.kill(pid, 'SIGTERM'); logger.warn(` Sent SIGTERM to stale PID ${pid}`);