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,',
'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}`);