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.
This commit is contained in:
Kilo
2026-05-06 17:53:44 +00:00
Unverified
parent 20d5cc08fc
commit 2b07fff073

View File

@@ -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,', '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.', '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', '## SELF-LEARNING',
'You have PERSISTENT MEMORY across sessions. You remember lessons, patterns, gotchas,', 'You have PERSISTENT MEMORY across sessions. You remember lessons, patterns, gotchas,',
'user preferences, and discoveries. When you learn something useful, save it to memory.', '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 key = buildSessionKey(ctx.chat.id, ctx.message?.message_thread_id);
const user = ctx.from?.username || ctx.from?.first_name || 'Unknown'; const user = ctx.from?.username || ctx.from?.first_name || 'Unknown';
const prefix = isVoice ? '🎤' : '💬'; 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)}`); logger.info(`${prefix} ${user}: ${text.substring(0, 80)}`);
await queueRequest(key, text, async () => { await queueRequest(key, text, async () => {
@@ -1488,6 +1509,31 @@ export async function initBot(config, api, tools, skills, agents) {
function killStaleProcess(pid) { function killStaleProcess(pid) {
if (!isProcessAlive(pid)) return false; 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 { try {
process.kill(pid, 'SIGTERM'); process.kill(pid, 'SIGTERM');
logger.warn(` Sent SIGTERM to stale PID ${pid}`); logger.warn(` Sent SIGTERM to stale PID ${pid}`);