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:
@@ -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}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user