VinOS Agent
Initial baseline commit
3a6714e
require('dotenv').config();
const dns = require('node:dns');
dns.setDefaultResultOrder('ipv4first');
const express = require('express');
const cors = require('cors');
const path = require('path');
const fs = require('fs');
const memory = require('./skills/memory');
const apiCaller = require('./skills/api_caller');
const app = express();
app.use(cors());
app.use(express.json());
app.use(express.static(path.join(__dirname, 'public')));
// API Routes
app.get('/api/profile', (req, res) => {
const db = memory.readDB();
res.json(db.user_profile_snapshot);
});
app.get('/api/sprints', (req, res) => {
const db = memory.readDB();
res.json(db.active_sprints || []);
});
app.get('/api/status', (req, res) => {
const status = {
openrouter: !!process.env.OPENROUTER_API_KEY,
groq: !!process.env.GROQ_API_KEY,
telegram: !!process.env.TELEGRAM_BOT_TOKEN,
mayar: !!process.env.MAYAR_API_KEY,
whop: !!process.env.WHOP_API_KEY,
apify: !!process.env.APIFY_API_KEY
};
res.json(status);
});
const dailyPulse = require('./use_cases/daily_pulse');
const offerArchitect = require('./use_cases/offer_architect');
app.post('/api/generate-image', async (req, res) => {
const { prompt } = req.body;
if (!prompt) return res.status(400).json({ error: "Prompt is required" });
const result = await apiCaller.generateNanoBananaImage(prompt);
res.json(result);
});
app.post('/api/usecase/pulse', async (req, res) => {
const result = await dailyPulse();
res.json(result);
});
app.post('/api/usecase/offer', async (req, res) => {
const { topic } = req.body;
if (!topic) return res.status(400).json({ error: "Topic is required" });
const result = await offerArchitect(topic);
res.json(result);
});
// Daily Check-in Mechanism (Simplified)
const DAILY_INTERVAL = 24 * 60 * 60 * 1000; // 24 hours
const chatID = process.env.TELEGRAM_CHAT_ID;
const botToken = process.env.TELEGRAM_BOT_TOKEN;
if (chatID && botToken) {
console.log(`Telegram Bot Active for Chat ID: ${chatID}`);
// Optional: Send immediate boot notification to confirm connection
// apiCaller.sendTelegramMessage(chatID, "<b>VinOS Core Online</b>\nSystem is calibrated. Ready for Token Gashapon experiments.");
setInterval(async () => {
console.log("Triggering daily check-in...");
await apiCaller.sendTelegramMessage(chatID, "<b>VinOS Daily Check-in</b>\nHow is the Token Gashapon project coming along? Ready for the next arbitrage move?");
}, DAILY_INTERVAL);
}
// Webhook ingestion (for Mayar/Whop)
app.post('/api/webhook', (req, res) => {
const data = req.body;
console.log("General Webhook received:", data);
res.status(200).send({ status: 'success' });
});
const { logTelegramMessage } = require('./skills/api_caller');
// SSE clients list for real-time push
const sseClients = [];
app.get('/api/telegram-stream', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
sseClients.push(res);
req.on('close', () => sseClients.splice(sseClients.indexOf(res), 1));
});
const broadcastSSE = (data) => sseClients.forEach(c => c.write(`data: ${JSON.stringify(data)}\n\n`));
app.get('/api/telegram-messages', (req, res) => {
const db = memory.readDB();
res.json(db.telegram_log || []);
});
app.post('/api/send-telegram', async (req, res) => {
const { message } = req.body;
if (!message) return res.status(400).json({ error: 'Message required' });
const chatId = process.env.TELEGRAM_CHAT_ID;
const result = await apiCaller.sendTelegramMessage(chatId, `<b>[VinOS Dashboard]</b> ${message}`);
broadcastSSE({ direction: 'OUT', chatId, text: `[VinOS Dashboard]: ${message}`, ts: new Date().toISOString() });
res.json(result);
});
const voiceTranscriber = require('./skills/voice_transcriber');
const intentRouter = require('./skills/intent_router');
const conversationMemory = require('./skills/conversation_memory');
const playbookManager = require('./skills/playbook_manager');
// Helper to handle resolved intents
async function handleVinIntent(chatId, from, userText, confirmed = false) {
const db = memory.readDB();
const autoMode = db.user_profile_snapshot?.automatic_mode || false;
// 1. Resolve intent
const { intent, params } = await intentRouter.resolveIntent(userText);
// Safety check: if not confirmed and NOT in auto-mode, ask for confirmation
if (!confirmed && !autoMode && (intent === 'gashapon' || intent === 'pulse' || intent === 'offer')) {
if (!db.pending_commands) db.pending_commands = {};
db.pending_commands[chatId] = { intent, params, userText, ts: Date.now() };
memory.writeDB(db);
const confirmationMsg = `🤖 <b>Intent:</b> <i>${intent}</i>\n<b>Params:</b> <i>${params || 'none'}</i>\n\nShould I execute? (Reply: <b>Confirm</b> / <b>Cancel</b>)\n<i>Tip: Use /auto on for speed mode.</i>`;
return await apiCaller.sendTelegramMessage(chatId, confirmationMsg);
}
// 2. Route to appropriate skill
switch (intent) {
case 'remember':
const playbookId = playbookManager.savePlaybook(`User memory trigger`, params, ['user-defined']);
await apiCaller.sendTelegramMessage(chatId, `🧠 <b>Playbook Saved</b>\nI'll remember this approach for the future.\n<i>Trigger: ${params.substring(0, 50)}...</i>`);
break;
case 'recall':
const playbooks = playbookManager.searchPlaybooks(params);
if (playbooks.length > 0) {
const results = playbooks.slice(0, 2).map(pb => `📌 <b>${pb.trigger}</b>\n${pb.solution}`).join('\n\n');
await apiCaller.sendTelegramMessage(chatId, `🔍 <b>Found in Playbooks:</b>\n\n${results}`);
} else {
await apiCaller.sendTelegramMessage(chatId, `🤷 No playbooks found for "${params}".`);
}
break;
case 'gashapon':
const prompt = params || userText;
await apiCaller.sendTelegramMessage(chatId, "🌀 <i>Spinning the Gashapon for:</i> " + prompt);
const gResult = await apiCaller.generateNanoBananaImage(prompt);
if (gResult.success) {
await apiCaller.sendTelegramMessage(chatId, `✨ <b>Gashapon Result!</b>\n<a href="${gResult.image_url}">View Image</a>`);
} else {
await apiCaller.sendTelegramMessage(chatId, "❌ Gashapon failed: " + gResult.error);
}
break;
case 'pulse':
await apiCaller.sendTelegramMessage(chatId, "📊 <i>Scanning market...</i>");
await dailyPulse();
break;
case 'offer':
const topic = params || userText;
await apiCaller.sendTelegramMessage(chatId, `💡 <i>Architecting offer for:</i> ${topic}`);
await offerArchitect(topic);
break;
case 'clarify':
await apiCaller.sendTelegramMessage(chatId, `🤔 <b>I need a bit more info:</b>\n${params}`);
break;
case 'chat':
case 'research':
case 'plan':
case 'execute':
case 'analyze':
case 'create':
default:
await processOrchestratorIntent(chatId, intent, userText, params);
break;
}
}
// Orchestrator handler that uses Memory + Playbooks
async function processOrchestratorIntent(chatId, intent, userText, params) {
// 1. Get Conversation History
const history = conversationMemory.getHistory(chatId);
// 2. Load core persona
const persona = fs.readFileSync(path.join(__dirname, 'prompts/vin_personality.md'), 'utf8');
// 3. Auto-search Playbooks for relevant context BEFORE thinking
const relatedPlaybooks = playbookManager.searchPlaybooks(userText).slice(0, 1);
let playbookContext = "";
if (relatedPlaybooks.length > 0) {
playbookContext = `\n\n# Relevant Playbook Found:\nUse this proven approach if applicable:\nTrigger: ${relatedPlaybooks[0].trigger}\nSolution: ${relatedPlaybooks[0].solution}`;
playbookManager.trackUsage(relatedPlaybooks[0].id);
}
const systemContent = persona + playbookContext + `\n\n[System Note: Your current intent classification for the upcoming message is: ${intent}]`;
const messages = [
{ role: "system", content: systemContent },
...history,
{ role: "user", content: userText }
];
// Save user message to rolling memory
conversationMemory.addMessage(chatId, "user", userText);
// Call LLM
const chatResult = await apiCaller.callOpenRouter(messages);
if (chatResult.success) {
// Save Vin's response to rolling memory
conversationMemory.addMessage(chatId, "assistant", chatResult.data);
await apiCaller.sendTelegramMessage(chatId, chatResult.data);
} else {
await apiCaller.sendTelegramMessage(chatId, "I'm having trouble thinking clearly right now. Try again in a moment.");
}
}
// Telegram Webhook
app.post('/api/telegram-webhook', async (req, res) => {
// 1. ACK immediately to Telegram to stop retries
res.status(200).send({ status: 'received' });
// 2. Process in background
(async () => {
const update = req.body;
console.log("Telegram Update received:", JSON.stringify(update));
const message = update.message;
if (!message) return;
const chatId = message.chat.id;
const from = message.from?.first_name || 'User';
let userText = message.text;
// Handle /auto on/off commands
if (userText && userText.toLowerCase().startsWith('/auto')) {
const mode = userText.toLowerCase().includes('on') ? true : false;
const db = memory.readDB();
if (!db.user_profile_snapshot) db.user_profile_snapshot = {};
db.user_profile_snapshot.automatic_mode = mode;
memory.writeDB(db);
const status = mode ? "🟢 <b>ON</b> (Speed Mode)" : "🟡 <b>OFF</b> (Safety Mode)";
return await apiCaller.sendTelegramMessage(chatId, `<b>Automatic Mode:</b> ${status}`);
}
// Handle Confirm/Cancel early
if (userText && (userText.toLowerCase() === 'confirm' || userText.toLowerCase() === 'cancel')) {
const db = memory.readDB();
const pending = db.pending_commands?.[chatId];
if (userText.toLowerCase() === 'confirm' && pending) {
delete db.pending_commands[chatId];
memory.writeDB(db);
await apiCaller.sendTelegramMessage(chatId, "✅ <b>Confirmed.</b> Running now...");
return await handleVinIntent(chatId, from, pending.userText, true);
} else if (userText.toLowerCase() === 'cancel' && pending) {
delete db.pending_commands[chatId];
memory.writeDB(db);
return await apiCaller.sendTelegramMessage(chatId, "🤝 <b>Cancelled.</b> What else can I do for you?");
} else {
return await apiCaller.sendTelegramMessage(chatId, "No pending command found to confirm or cancel.");
}
}
if (message.voice) {
await apiCaller.sendTelegramMessage(chatId, "🎤 <i>Received audio... Transcribing...</i>");
const transcription = await voiceTranscriber.transcribeVoice(message.voice.file_id);
if (transcription) {
userText = transcription;
apiCaller.logTelegramMessage('IN', chatId, `[${from} (Voice)]: ${userText}`);
broadcastSSE({ direction: 'IN', chatId, text: `[${from} (Voice)]: ${userText}`, ts: new Date().toISOString() });
} else {
return await apiCaller.sendTelegramMessage(chatId, "❌ Transcription failed. Could you try again?");
}
} else if (userText) {
const isControl = /^(confirm|cancel|\/auto|\/start)/i.test(userText);
if (!isControl) {
await apiCaller.sendTelegramMessage(chatId, "⚡ <i>Instruction received. Processing...</i>");
}
apiCaller.logTelegramMessage('IN', chatId, `[${from}]: ${userText}`);
broadcastSSE({ direction: 'IN', chatId, text: `[${from}]: ${userText}`, ts: new Date().toISOString() });
}
// Process commands or NL
if (userText) {
if (userText.startsWith('/start')) {
await apiCaller.sendTelegramMessage(chatId, "<b>VinOS Core Online</b>");
} else {
await handleVinIntent(chatId, from, userText);
}
}
})().catch(err => console.error("Webhook processing error:", err));
});
const PORT = process.env.PORT || 7860;
app.listen(PORT, '0.0.0.0', () => {
console.log(`VinOS Core Online: http://0.0.0.0:${PORT}`);
});