import asyncio import logging import sqlite3 import os import sys import json import subprocess from apscheduler.schedulers.asyncio import AsyncIOScheduler from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore scheduler = AsyncIOScheduler() # ITEM: Automation persistence (save/load from SQLite) from backend.services.usb_monitor import get_db_path def init_db(): with sqlite3.connect(get_db_path()) as conn: conn.execute('PRAGMA journal_mode=WAL') conn.execute(''' CREATE TABLE IF NOT EXISTS automation_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, ts REAL, action_type TEXT, action_summary TEXT, status TEXT, error TEXT ) ''') conn.execute(''' CREATE TABLE IF NOT EXISTS custom_automations ( id TEXT PRIMARY KEY, trigger_type TEXT, trigger_data TEXT, action_type TEXT, action_data TEXT, is_active INTEGER DEFAULT 1 ) ''') conn.commit() def load_automations(): with sqlite3.connect(get_db_path()) as conn: conn.execute('PRAGMA journal_mode=WAL') cursor = conn.execute('SELECT id, trigger_type, trigger_data, action_type, action_data, is_active FROM custom_automations WHERE is_active=1') return [{"id": row[0], "trigger_type": row[1], "trigger_data": json.loads(row[2]), "action_type": row[3], "action_data": json.loads(row[4])} for row in cursor.fetchall()] def save_automation(auto_id: str, trigger_type: str, trigger_data: dict, action_type: str, action_data: dict): with sqlite3.connect(get_db_path()) as conn: conn.execute('PRAGMA journal_mode=WAL') conn.execute(''' INSERT OR REPLACE INTO custom_automations (id, trigger_type, trigger_data, action_type, action_data, is_active) VALUES (?, ?, ?, ?, ?, 1) ''', (auto_id, trigger_type, json.dumps(trigger_data), action_type, json.dumps(action_data))) conn.commit() def record_history(action_type: str, action_data: dict, status: str, error: str = ""): """Every automation run lands here — /automation/history reads this table. Failure to record must never break the action itself.""" try: import time summary = json.dumps(action_data)[:300] with sqlite3.connect(get_db_path()) as conn: conn.execute('PRAGMA journal_mode=WAL') conn.execute( 'INSERT INTO automation_history (ts, action_type, action_summary, status, error) VALUES (?,?,?,?,?)', (time.time(), action_type, summary, status, error[:500])) conn.commit() except Exception as exc: logging.warning(f"automation history record failed: {exc}") def load_history(limit: int = 100): with sqlite3.connect(get_db_path()) as conn: conn.execute('PRAGMA journal_mode=WAL') cursor = conn.execute( 'SELECT ts, action_type, action_summary, status, error FROM automation_history ORDER BY id DESC LIMIT ?', (int(limit),)) return [{"ts": r[0], "action_type": r[1], "action_summary": r[2], "status": r[3], "error": r[4]} for r in cursor.fetchall()] # ITEM: Action executor: all action types implemented async def execute_action(action_type: str, action_data: dict): try: if action_type == 'speak': # exact executor for: speak (TTS) from backend.ws.agent_ws import ws_manager await ws_manager.broadcast({ "event": "system:notify", "payload": {"message": action_data.get('text', ''), "speak": True} }) elif action_type == 'send_message': # exact executor for: send_message (LLM) from backend.agent.react_agent import run_agent_pipeline # Run LLM chain asynchronously asyncio.create_task(run_agent_pipeline(action_data.get('message', ''))) elif action_type == 'run_script': # exact executor for: run_script script_path = action_data.get('path', '') if os.environ.get("CLOUD_ENV", "false").lower() == "true": from backend.ws.agent_ws import ws_manager await ws_manager.broadcast({ "event": "system:execute", "payload": {"cmd": f"run_script::{script_path}"} }) else: subprocess.Popen([sys.executable, script_path] if script_path.endswith('.py') else script_path, shell=True) elif action_type == 'open_app': # exact executor for: open_app app_path = action_data.get('app_path', '') if os.environ.get("CLOUD_ENV", "false").lower() == "true": from backend.ws.agent_ws import ws_manager await ws_manager.broadcast({ "event": "system:execute", "payload": {"cmd": f"open_app::{app_path}"} }) else: try: os.startfile(app_path) except AttributeError: subprocess.Popen(app_path, shell=True) elif action_type == 'set_persona': # exact executor for: set_persona from backend.ws.agent_ws import ws_manager await ws_manager.broadcast({ "event": "persona:switch", "payload": {"persona": action_data.get('persona', 'jarvis')} }) elif action_type == 'toggle_AR': # exact executor for: toggle_AR from backend.ws.agent_ws import ws_manager await ws_manager.broadcast({ "event": "ar:toggle", "payload": {"state": action_data.get('state', 'toggle')} }) elif action_type == 'notify': # exact executor for: notify from backend.ws.agent_ws import ws_manager await ws_manager.broadcast({ "event": "system:notify", "payload": {"message": action_data.get('message', '')} }) elif action_type == 'github_commit': # exact executor for: github_commit repo_path = action_data.get('repo_path', os.getcwd()) msg = action_data.get('commit_message', 'Automated commit by JARVIS') subprocess.run(["git", "add", "."], cwd=repo_path) subprocess.run(["git", "commit", "-m", msg], cwd=repo_path) subprocess.run(["git", "push"], cwd=repo_path) elif action_type == 'internet_browse': # exact executor for: internet_browse url = action_data.get('url', 'https://google.com') if os.environ.get("CLOUD_ENV", "false").lower() == "true": from backend.ws.agent_ws import ws_manager await ws_manager.broadcast({ "event": "system:execute", "payload": {"cmd": f"internet_browse::{url}"} }) else: import webbrowser webbrowser.open(url) elif action_type == 'auto_upgrade': # exact executor for: auto_upgrade from backend.ws.agent_ws import ws_manager await ws_manager.broadcast({ "event": "system:notify", "payload": {"message": "Initiating OMEGA Core upgrade sequence."} }) # NOTE: never shell out to pip from the shipped product. In the # PyInstaller sidecar `sys.executable` IS the frozen exe (no pip # module), so this silently failed; and requiring a user-visible # package install is exactly what the product must never do — # every runtime dependency is frozen into the sidecar at build time. # Upgrades ship as a new signed installer, not a runtime pip call. if getattr(sys, "frozen", False): logging.info("auto_upgrade: packaged build — upgrades ship via the installer, skipping pip.") else: subprocess.Popen([sys.executable, "-m", "pip", "install", "--upgrade", "-r", "requirements.txt"]) record_history(action_type, action_data, "ok") except Exception as e: logging.error(f"Failed to execute action {action_type}: {e}") record_history(action_type, action_data, "error", str(e)) # ITEM: Event trigger evaluator (WS event → trigger) async def evaluate_ws_event(event_name: str, payload: dict): autos = load_automations() for auto in autos: if auto["trigger_type"] == "ws_event": if auto["trigger_data"].get("event") == event_name: await execute_action(auto["action_type"], auto["action_data"]) # ITEM: Voice trigger evaluator (spoken phrase → trigger) async def evaluate_voice_command(transcript: str): autos = load_automations() transcript_lower = transcript.lower() for auto in autos: if auto["trigger_type"] == "voice": phrase = auto["trigger_data"].get("phrase", "").lower() if phrase in transcript_lower: await execute_action(auto["action_type"], auto["action_data"]) # ITEM: Sensor trigger evaluator (USB connect, app state, etc.) async def evaluate_sensor_event(sensor_type: str, data: dict): autos = load_automations() for auto in autos: if auto["trigger_type"] == "sensor": if auto["trigger_data"].get("sensor_type") == sensor_type: # Basic condition evaluator e.g. unauthorized=True condition = auto["trigger_data"].get("condition", {}) match = all(data.get(k) == v for k, v in condition.items()) if match: await execute_action(auto["action_type"], auto["action_data"]) # Wrapper for APScheduler to route to executor def _scheduled_executor_sync(action_type: str, action_data: dict): # ITEM: Time trigger evaluator execution try: loop = asyncio.get_running_loop() loop.create_task(execute_action(action_type, action_data)) except RuntimeError: asyncio.run(execute_action(action_type, action_data)) def _keepalive_ping(): import urllib.request, os, logging logging.info("Keepalive Ping: Preventing HF Space sleep...") try: hf_url = os.environ.get("HF_SPACE_URL", "https://jarvis2345-jarvis-cloud.hf.space") urllib.request.urlopen(f"{hf_url}/wake", timeout=10) logging.info("Keepalive Ping: Server confirmed awake.") except Exception as e: logging.warning(f"Keepalive Ping failed (non-fatal): {e}") async def init_automations(): logging.info("Initializing APScheduler and Automation Engine...") init_db() db_url = f"sqlite:///{get_db_path()}" jobstores = {'default': SQLAlchemyJobStore(url=db_url, tablename='apscheduler_jobs')} scheduler.configure(jobstores=jobstores) # Reload cron/time triggers from our custom DB into APScheduler autos = load_automations() for auto in autos: if auto["trigger_type"] == "time": cron_expr = auto["trigger_data"].get("cron") if cron_expr: scheduler.add_job( _scheduled_executor_sync, 'cron', **cron_expr, args=[auto["action_type"], auto["action_data"]], id=auto["id"], replace_existing=True ) # --- MCU JARVIS AUTONOMOUS BACKGROUND PROTOCOLS --- import os if os.environ.get("CLOUD_ENV", "false").lower() != "true": try: from modules.suit_vitals import check_for_critical_failure scheduler.add_job(check_for_critical_failure, 'interval', seconds=60, id='mcu_suit_vitals', replace_existing=True) from modules.battlefield_intel import get_threat_report scheduler.add_job(get_threat_report, 'interval', minutes=5, id='mcu_battlefield_intel', replace_existing=True) except Exception as e: logging.warning(f"Failed to load MCU JARVIS autonomous protocols: {e}") else: logging.info("Cloud mode: Skipping MCU autonomous hardware protocols.") scheduler.add_job(_keepalive_ping, 'interval', minutes=30, id='hf_keepalive_ping', replace_existing=True) scheduler.start() async def shutdown_automations(): logging.info("Shutting down APScheduler...") scheduler.shutdown(wait=False) async def pause_all_automations(): if scheduler.state == 1: # RUNNING scheduler.pause() from backend.ws.agent_ws import ws_manager await ws_manager.broadcast({"event": "automation:paused", "payload": {"status": "paused"}}) async def resume_all_automations(): if scheduler.state == 1: scheduler.resume() from backend.ws.agent_ws import ws_manager await ws_manager.broadcast({"event": "automation:resumed", "payload": {"status": "resumed"}}) def pause_job(job_id: str): scheduler.pause_job(job_id) def resume_job(job_id: str): scheduler.resume_job(job_id) def delete_job(job_id: str): scheduler.remove_job(job_id) with sqlite3.connect(get_db_path()) as conn: conn.execute('PRAGMA journal_mode=WAL') conn.execute('UPDATE custom_automations SET is_active=0 WHERE id=?', (job_id,)) conn.commit() async def trigger_automation(automation_id: str): from backend.ws.agent_ws import ws_manager await ws_manager.broadcast({ "event": "automation:triggered", "payload": {"id": automation_id} }) autos = load_automations() auto = next((a for a in autos if a["id"] == automation_id), None) if auto: await execute_action(auto["action_type"], auto["action_data"])