Spaces:
Running
Running
| 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'); | |
| const versionControl = require('./skills/version_control'); | |
| const autoCron = require('./skills/infinite_money_cron'); | |
| // Start the autonomous routines | |
| autoCron.startJobs(); | |
| // 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; | |
| // --- Core Action Commands --- | |
| if (userText === '/commit') { | |
| const res = versionControl.commitChanges("Manual user commit"); | |
| if (res.success) { | |
| await apiCaller.sendTelegramMessage(chatId, `β <b>Version Logged</b>\nMessage: ${res.message}\nCommit: ${res.version}`); | |
| } else { | |
| await apiCaller.sendTelegramMessage(chatId, `β οΈ ${res.error}`); | |
| } | |
| return; | |
| } | |
| if (userText === '/revert') { | |
| const res = versionControl.revertToLastWorking(); | |
| if (res.success) { | |
| await apiCaller.sendTelegramMessage(chatId, `βͺ <b>Rolled Back</b>\nReverted changes to the last stable version.`); | |
| } else { | |
| await apiCaller.sendTelegramMessage(chatId, `β οΈ Revert failed: ${res.error}`); | |
| } | |
| return; | |
| } | |
| if (userText === '/log') { | |
| const res = versionControl.getLog(); | |
| await apiCaller.sendTelegramMessage(chatId, `π <b>Version History:</b>\n\n<pre>${res.log}</pre>`); | |
| return; | |
| } | |
| // 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}`); | |
| }); | |