Anticlaw / src /heartbeat.ts
Luis Milke
chore: setup proper gitignore and remove debug garbage
5348690
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import { handleMessage, pushNotification } from './agent.js';
import { bot } from './bot.js';
import { env } from './config.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Interval in milliseconds (Default: 30 minutes)
const HEARTBEAT_INTERVAL_MS = 30 * 60 * 1000;
let heartbeatInterval: NodeJS.Timeout | null = null;
const HEARTBEAT_FILE_PATH = path.join(__dirname, '../skills/HEARTBEAT.md');
export function startHeartbeat() {
if (heartbeatInterval) {
console.log('Heartbeat is already running.');
return;
}
console.log(`⏱️ Starting system heartbeat (Interval: ${HEARTBEAT_INTERVAL_MS / 1000 / 60} minutes)`);
heartbeatInterval = setInterval(async () => {
try {
await triggerHeartbeat();
} catch (error) {
console.error('Error during heartbeat execution:', error);
}
}, HEARTBEAT_INTERVAL_MS);
}
export function stopHeartbeat() {
if (heartbeatInterval) {
clearInterval(heartbeatInterval);
heartbeatInterval = null;
console.log('⏱️ System heartbeat stopped.');
}
}
export async function triggerHeartbeat(isManualTrigger = false) {
if (isManualTrigger) {
console.log('⏱️ Manual heartbeat triggered.');
} else {
console.log('⏱️ Executing scheduled heartbeat...');
}
let heartbeatContent = '';
// 1. Read the HEARTBEAT.md file
try {
if (fs.existsSync(HEARTBEAT_FILE_PATH)) {
heartbeatContent = fs.readFileSync(HEARTBEAT_FILE_PATH, 'utf-8');
} else {
console.log('No HEARTBEAT.md found in skills directory. Skipping heartbeat.');
return;
}
} catch (e) {
console.error('Failed to read HEARTBEAT.md:', e);
return;
}
// 2. Construct the invisible prompt
const hiddenPrompt = `[SYSTEM HEARTBEAT WAKEUP]\nThis is an automated background heartbeat. Please review your instructions and act accordingly:\n\n${heartbeatContent}`;
// 3. Invoke the agent loop directly (without triggering Telegram 'typing' directly from here)
const response = await handleMessage(hiddenPrompt, undefined, undefined, true); // true = isBackground
// 4. Evaluate response
const cleanResponse = response.trim();
const strippedResponse = cleanResponse.replace(/\\/g, '');
if (strippedResponse.includes('HEARTBEAT_OK')) {
console.log('⏱️ Heartbeat complete: No action required (HEARTBEAT_OK detected).');
// Do nothing, remain silent.
} else {
console.log('⏱️ Heartbeat generated active response. Notifying user.');
// The agent decided to say something or take an action. Forward it to Telegram AND the app!
try {
await bot.api.sendMessage(env.TELEGRAM_USER_ID, `*(Auto-Heartbeat)*\n\n${cleanResponse}`);
pushNotification('heartbeat', cleanResponse);
} catch (botErr) {
console.error('Failed to send heartbeat message to Telegram:', botErr);
}
}
}