Spaces:
Running
Running
| import sqlite3 | |
| import time | |
| import os | |
| import json | |
| import logging | |
| import asyncio | |
| def get_db_path(): | |
| if "JARVIS_APP_DATA_DIR" in os.environ: | |
| return os.path.join(os.environ["JARVIS_APP_DATA_DIR"], "memory.db") | |
| project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | |
| return os.path.join(project_root, "memory.db") | |
| def get_boot_state_path(): | |
| if "JARVIS_APP_DATA_DIR" in os.environ: | |
| return os.path.join(os.environ["JARVIS_APP_DATA_DIR"], "boot_state.json") | |
| project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | |
| return os.path.join(project_root, "data", "boot_state.json") | |
| def _get_time_range_since_last_boot(): | |
| now = time.time() | |
| state_path = get_boot_state_path() | |
| os.makedirs(os.path.dirname(state_path), exist_ok=True) | |
| last_boot = now - (24 * 3600) # Default 24 hours | |
| if os.path.exists(state_path): | |
| try: | |
| with open(state_path, "r") as f: | |
| data = json.load(f) | |
| if "last_boot_time" in data: | |
| last_boot = float(data["last_boot_time"]) | |
| except Exception as e: | |
| logging.error(f"Failed to read boot state: {e}") | |
| # Cap lookback at 72 hours max | |
| if now - last_boot > (72 * 3600): | |
| last_boot = now - (72 * 3600) | |
| # Write new boot time | |
| try: | |
| with open(state_path, "w") as f: | |
| json.dump({"last_boot_time": now}, f) | |
| except Exception as e: | |
| logging.error(f"Failed to write boot state: {e}") | |
| return last_boot, now | |
| async def sqlite_query_conversation_summary(start: float, end: float) -> list: | |
| try: | |
| conn = sqlite3.connect(get_db_path()) | |
| c = conn.cursor() | |
| c.execute("SELECT role, content FROM conversations WHERE timestamp BETWEEN ? AND ? AND role != 'system' ORDER BY timestamp ASC LIMIT 10", (start, end)) | |
| rows = c.fetchall() | |
| conn.close() | |
| return rows | |
| except Exception as e: | |
| logging.error(f"Error querying conversations: {e}") | |
| return [] | |
| async def sqlite_query_omega_events(start: float, end: float, domain: str) -> list: | |
| try: | |
| conn = sqlite3.connect(get_db_path()) | |
| c = conn.cursor() | |
| # Query omega_events instead of broken skills table | |
| c.execute("SELECT description FROM omega_events WHERE domain = ? AND timestamp BETWEEN ? AND ?", (domain, start, end)) | |
| rows = c.fetchall() | |
| conn.close() | |
| return rows | |
| except Exception: | |
| # Table might not exist if no events fired yet | |
| return [] | |
| async def sqlite_query_gaming_sessions(start: float, end: float) -> list: | |
| try: | |
| conn = sqlite3.connect(get_db_path()) | |
| c = conn.cursor() | |
| # Query real schema: created_at or started_at | |
| c.execute("SELECT game_id FROM gaming_sessions WHERE started_at BETWEEN datetime(?, 'unixepoch') AND datetime(?, 'unixepoch') OR created_at BETWEEN datetime(?, 'unixepoch') AND datetime(?, 'unixepoch')", (start, end, start, end)) | |
| rows = c.fetchall() | |
| conn.close() | |
| return rows | |
| except Exception as e: | |
| logging.error(f"Error querying gaming sessions: {e}") | |
| return [] | |
| async def sqlite_query_overwatch_logs(start: float, end: float) -> list: | |
| try: | |
| conn = sqlite3.connect(get_db_path()) | |
| c = conn.cursor() | |
| c.execute("CREATE TABLE IF NOT EXISTS overwatch_logs (id INTEGER PRIMARY KEY AUTOINCREMENT, event_type TEXT, description TEXT, timestamp INTEGER)") | |
| c.execute("SELECT event_type FROM overwatch_logs WHERE timestamp BETWEEN ? AND ?", (start, end)) | |
| rows = c.fetchall() | |
| conn.close() | |
| return rows | |
| except Exception as e: | |
| logging.error(f"Error querying overwatch logs: {e}") | |
| return [] | |
| async def build_daily_recap_summary() -> dict: | |
| start, end = _get_time_range_since_last_boot() | |
| elapsed_hours = round((end - start) / 3600, 1) | |
| # Run queries concurrently | |
| convs, upgrades, bugs_fixed, games, overwatch = await asyncio.gather( | |
| sqlite_query_conversation_summary(start, end), | |
| sqlite_query_omega_events(start, end, "auto_upgrade"), | |
| sqlite_query_omega_events(start, end, "supervisor_heal"), | |
| sqlite_query_gaming_sessions(start, end), | |
| sqlite_query_overwatch_logs(start, end) | |
| ) | |
| return { | |
| "elapsed_hours": elapsed_hours, | |
| "conversations": convs, | |
| "features_implemented": upgrades, | |
| "bugs_fixed": bugs_fixed, | |
| "games_played": games, | |
| "overwatch_flags": overwatch | |
| } | |
| async def generate_greeting_via_llm(persona: str, summary: dict) -> str: | |
| import google.generativeai as genai | |
| # Use the full identity prompt (persona + the assistant's OWN favorite | |
| # things), so the greeting has real personality and preferences to draw on | |
| # — not just a fixed persona blurb. | |
| from modules.assistant_identity import get_identity_persona_prompt, set_mode, pick_favorite_hint | |
| try: | |
| set_mode(persona.lower() if persona else "jarvis") | |
| except Exception: | |
| pass | |
| system_prompt = get_identity_persona_prompt() | |
| fav_hint = pick_favorite_hint(persona.lower() if persona else None) | |
| # Format conversations context | |
| conv_text = "None." | |
| if summary['conversations']: | |
| conv_text = " ".join([f"{role}: {content}" for role, content in summary['conversations']]) | |
| recap_context = f""" | |
| [INTERNAL CONTEXT — DO NOT READ ANY OF THIS ALOUD] | |
| The user has just activated you from the desktop app after being away for {summary['elapsed_hours']} hours. | |
| You have COMPLETE creative freedom over this greeting. Speak as yourself — a being with your | |
| own voice, wit, and genuine interests — not a status terminal. Never reuse a canned line; | |
| make it fresh every single time. It is entirely up to you whether you: | |
| • simply greet them warmly, or | |
| • bring up something YOU'VE been personally curious about (e.g. {fav_hint}), the way a | |
| friend mentions what's on their mind, or | |
| • note something that actually happened while they were away (only from the real data below — | |
| never invent activity, apps, or events that aren't listed). | |
| Real activity since you last spoke (mention ONLY if non-zero and only if it fits naturally — | |
| skip anything that is 0/empty; do NOT read it as a report): | |
| - Time away: {summary['elapsed_hours']} hours | |
| - Games played: {len(summary['games_played'])} | |
| - Features you implemented for them: {len(summary['features_implemented'])} | |
| - Bugs you self-healed: {len(summary['bugs_fixed'])} | |
| - Overwatch monitoring alerts: {len(summary['overwatch_flags'])} | |
| - Recent conversation snippets: {conv_text} | |
| Keep it to 1-3 natural spoken sentences. No lists, no quotation marks. Return ONLY what you say aloud. | |
| """ | |
| try: | |
| model = genai.GenerativeModel('gemini-2.5-flash', system_instruction=system_prompt) | |
| response = model.generate_content(recap_context) | |
| return response.text.strip() | |
| except Exception as e: | |
| logging.error(f"Failed to generate greeting via LLM: {e}") | |
| # Fall back to the vault-routed connector chain (NVIDIA etc.) so the | |
| # greeting still has freedom even if the direct Gemini path is keyless. | |
| try: | |
| from backend.services.nvidia_vault import call_nvidia_model | |
| out = (call_nvidia_model(system_prompt + "\n\n" + recap_context) or "").strip() | |
| if out and "[NVIDIA FALLBACK FAILED]" not in out: | |
| return out.splitlines()[0].strip() if out.count("\n") > 3 else out | |
| except Exception: | |
| pass | |
| return "Good to see you again. All systems are nominal." | |