jarvis-cloud / backend /tasks /executor.py
Jarvis2345's picture
Upload folder using huggingface_hub
01ab0a4 verified
Raw
History Blame Contribute Delete
6.55 kB
# backend/tasks/executor.py
import asyncio
from dataclasses import dataclass, field
from typing import Any
from uuid import uuid4
@dataclass
class Task:
id: str = field(default_factory=lambda: str(uuid4()))
name: str = ""
steps: list[dict] = field(default_factory=list)
current_step: int = 0
status: str = "pending" # pending | running | paused | completed | failed | cancelled
result: Any = None
error: str = ""
permission_tier: str = "approval_required" # pre_approved | approval_required | explicit_each_time | biometric
class TaskExecutor:
def __init__(self, app_handle, permission_gateway):
self.tasks: dict[str, Task] = {}
self.app_handle = app_handle
self.permission_gateway = permission_gateway
# Load db path
import sys, os
if getattr(sys, 'frozen', False):
self.db_path = os.path.join(os.environ.get('APPDATA', os.path.expanduser('~')), 'JARVIS_OS', 'memory.db')
else:
self.db_path = os.path.join(os.getcwd(), 'memory.db')
self._recover_tasks()
def _recover_tasks(self):
import sqlite3
try:
with sqlite3.connect(self.db_path) as conn:
conn.execute('PRAGMA journal_mode=WAL')
cursor = conn.cursor()
cursor.execute("SELECT id, name, status, steps, current_step, result, error, permission_tier FROM tasks")
for row in cursor.fetchall():
status = row[2]
# Safety: Fail any tasks that were running/paused during a hard crash
if status in ["running", "paused"]:
status = "failed"
cursor.execute("UPDATE tasks SET status=?, error=? WHERE id=?",
("failed", "System rebooted mid-execution", row[0]))
conn.commit()
t = Task(
id=row[0], name=row[1], status=status,
steps=[], # Callables cannot be easily serialized to JSON, so we leave steps empty on recovery
current_step=row[4], result=row[5], error=row[6], permission_tier=row[7]
)
self.tasks[t.id] = t
except Exception as e:
import logging; logging.getLogger(__name__).error(f"Swallowed exception: {e}") # Tables might not exist yet
def _save_task(self, task: Task):
import sqlite3, json
try:
with sqlite3.connect(self.db_path) as conn:
conn.execute('PRAGMA journal_mode=WAL')
cursor = conn.cursor()
# We do not serialize the actual 'handler' since it's a function reference.
# We just serialize a dummy representation of steps for UI if needed.
steps_json = json.dumps([{"name": getattr(s.get('handler'), '__name__', 'step')} for s in task.steps])
cursor.execute('''
INSERT INTO tasks (id, name, status, steps, current_step, result, error, permission_tier)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(id) DO UPDATE SET
status=excluded.status,
current_step=excluded.current_step,
result=excluded.result,
error=excluded.error
''', (task.id, task.name, task.status, steps_json, task.current_step, str(task.result) if task.result else "", task.error, task.permission_tier))
conn.commit()
except Exception as e:
import logging; logging.getLogger(__name__).error(f"Swallowed exception: {e}")
async def submit(self, task: Task) -> str:
self.tasks[task.id] = task
self._save_task(task)
asyncio.create_task(self._run(task))
return task.id
async def _run(self, task: Task):
task.status = "running"
self._save_task(task)
self._emit("task:updated", task)
for i, step in enumerate(task.steps):
task.current_step = i
self._save_task(task)
self._emit("task:step_started", {"task_id": task.id, "step": step})
allowed = await self.permission_gateway.check(task.permission_tier, step)
if not allowed:
task.status = "paused"
self._save_task(task)
self._emit("task:awaiting_permission", {"task_id": task.id, "step": step})
allowed = await self.permission_gateway.wait_for_user_response(task.id)
if not allowed:
task.status = "cancelled"
self._save_task(task)
self._emit("task:updated", task)
return
try:
result = await step["handler"](**step["params"])
task.result = result
self._save_task(task)
self._emit("task:step_completed", {"task_id": task.id, "step": step, "result": result})
except Exception as e:
task.status = "failed"
task.error = str(e)
self._save_task(task)
self._emit("task:updated", task)
return
task.status = "completed"
self._save_task(task)
self._emit("task:updated", task)
def cancel(self, task_id: str):
task = self.tasks.get(task_id)
if task:
task.status = "cancelled"
self._save_task(task)
self._emit("task:updated", task)
def _emit(self, event: str, payload):
try:
from backend.ws.agent_ws import ws_manager
import json
from dataclasses import asdict
# Serialize payload to dict safely
if hasattr(payload, '__dataclass_fields__'):
safe_payload = asdict(payload)
else:
# Fallback via json roundtrip to handle non-serializable objects cleanly
safe_payload = json.loads(json.dumps(payload, default=str))
asyncio.create_task(ws_manager.broadcast({"event": event, "payload": safe_payload}))
except Exception as e:
import logging; logging.getLogger(__name__).error(f"Swallowed exception: {e}")