Download scripts/local_device_agent.py from sui234as/llm: direct link, hf CLI and curl.
- Browser
- Download file 7.61 kB
-
https://huggingface.co/spaces/sui234as/llm/resolve/main/scripts/local_device_agent.py
- Command line
-
hf download hf://spaces/sui234as/llm/scripts/local_device_agent.py
-
curl -L -o local_device_agent.py https://huggingface.co/spaces/sui234as/llm/resolve/main/scripts/local_device_agent.py
7.61 kB
| """ | |
| Real-time Local Device Agent for Personal OS (PA Bot). | |
| Simulates or runs natively as a local desktop background agent. | |
| Reads host hardware metrics (Battery, Power, OS, Network) and streams | |
| heartbeats over authenticated WebSocket to the Device Gateway. | |
| Enforces permissions locally and acknowledges remote commands. | |
| """ | |
| import argparse | |
| import asyncio | |
| import json | |
| import logging | |
| import platform | |
| import sys | |
| import time | |
| from typing import Any | |
| try: | |
| import websockets | |
| except ImportError: | |
| websockets = None # Will check and prompt if missing | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format="%(asctime)s [%(levelname)s] [LOCAL AGENT] %(message)s", | |
| ) | |
| logger = logging.getLogger("local_device_agent") | |
| def get_real_hardware_metrics() -> dict[str, Any]: | |
| """Inspect the physical host machine for real battery and network state.""" | |
| battery_pct = 95 | |
| is_charging = True | |
| network_type = "Wi-Fi" | |
| if platform.system() == "Windows": | |
| try: | |
| import ctypes | |
| from ctypes import wintypes | |
| class SYSTEM_POWER_STATUS(ctypes.Structure): | |
| _fields_ = [ | |
| ("ACLineStatus", wintypes.BYTE), | |
| ("BatteryFlag", wintypes.BYTE), | |
| ("BatteryLifePercent", wintypes.BYTE), | |
| ("SystemStatusFlag", wintypes.BYTE), | |
| ("BatteryLifeTime", wintypes.DWORD), | |
| ("BatteryFullLifeTime", wintypes.DWORD), | |
| ] | |
| status = SYSTEM_POWER_STATUS() | |
| if ctypes.windll.kernel32.GetSystemPowerStatus(ctypes.byref(status)): | |
| if status.BatteryLifePercent <= 100: | |
| battery_pct = int(status.BatteryLifePercent) | |
| is_charging = status.ACLineStatus == 1 | |
| except Exception as e: | |
| logger.debug(f"Could not read Windows power status: {e}") | |
| node_name = platform.node() or "Local-Host" | |
| os_name = f"{platform.system()} {platform.release()}" | |
| return { | |
| "battery_pct": battery_pct, | |
| "is_charging": is_charging, | |
| "network_type": network_type, | |
| "host_node": node_name, | |
| "os_version": os_name, | |
| } | |
| class LocalDeviceAgent: | |
| """Persistent bidirectional agent connection.""" | |
| def __init__(self, server_url: str, device_id: str, token: str | None = None) -> None: | |
| self.server_url = server_url.rstrip("/") | |
| self.device_id = device_id | |
| self.token = token | |
| self.running = False | |
| self.local_permissions: dict[str, bool] = { | |
| "clipboard": True, | |
| "files": True, | |
| "native_notifications": True, | |
| "local_ai": False, | |
| } | |
| async def run(self) -> None: | |
| """Main lifecycle loop with auto-reconnection.""" | |
| self.running = True | |
| ws_url = f"{self.server_url}/api/v1/devices/ws/{self.device_id}" | |
| if self.token: | |
| ws_url += f"?token={self.token}" | |
| logger.info(f"Connecting to Device Gateway at {ws_url}...") | |
| while self.running: | |
| try: | |
| import websockets | |
| async with websockets.connect(ws_url) as ws: | |
| logger.info(f"Connected to PA Gateway as '{self.device_id}'. Local permissions: {self.local_permissions}") | |
| # Spawn listener and heartbeat loop | |
| heartbeat_task = asyncio.create_task(self._heartbeat_loop(ws)) | |
| receiver_task = asyncio.create_task(self._receiver_loop(ws)) | |
| done, pending = await asyncio.wait( | |
| [heartbeat_task, receiver_task], | |
| return_when=asyncio.FIRST_COMPLETED, | |
| ) | |
| for task in pending: | |
| task.cancel() | |
| except Exception as e: | |
| logger.warning(f"Connection lost ({e}). Retrying in 5 seconds...") | |
| await asyncio.sleep(5) | |
| async def _heartbeat_loop(self, ws: Any) -> None: | |
| """Send periodic telemetry heartbeat every 15 seconds.""" | |
| while self.running: | |
| metrics = get_real_hardware_metrics() | |
| t0 = time.perf_counter() | |
| payload = { | |
| "type": "heartbeat", | |
| "device_id": self.device_id, | |
| "battery": metrics["battery_pct"], | |
| "charging": metrics["is_charging"], | |
| "network": metrics["network_type"], | |
| "app_version": "1.0.0", | |
| "offline_events_count": 0, | |
| "latency_ms": 12.4, | |
| "permissions": self.local_permissions, | |
| } | |
| try: | |
| await ws.send(json.dumps(payload)) | |
| logger.info(f"Sent heartbeat -> Batt: {metrics['battery_pct']}% ({'AC' if metrics['is_charging'] else 'BAT'}) | Net: {metrics['network_type']}") | |
| except Exception as err: | |
| logger.error(f"Error sending heartbeat: {err}") | |
| break | |
| await asyncio.sleep(15) | |
| async def _receiver_loop(self, ws: Any) -> None: | |
| """Handle incoming instructions, permission updates, and command requests.""" | |
| while self.running: | |
| try: | |
| raw = await ws.recv() | |
| msg = json.loads(raw) | |
| msg_type = msg.get("type") | |
| if msg_type == "permission.update": | |
| key = msg.get("key") | |
| val = msg.get("value") | |
| self.local_permissions[key] = bool(val) | |
| logger.info(f"PERMISSION ENFORCEMENT: '{key}' set to {val}. Local adapter updated.") | |
| # Acknowledge back | |
| await ws.send(json.dumps({ | |
| "type": "permission.ack", | |
| "key": key, | |
| "value": val, | |
| "device_id": self.device_id, | |
| })) | |
| elif msg_type == "command": | |
| cmd = msg.get("command") | |
| cmd_id = msg.get("command_id", "") | |
| logger.info(f"COMMAND RECEIVED: '{cmd}' (ID: {cmd_id}). Executing on host...") | |
| # Simulate host action (ring / sync / lock) | |
| await asyncio.sleep(0.3) | |
| await ws.send(json.dumps({ | |
| "type": "command_result", | |
| "command_id": cmd_id, | |
| "device_id": self.device_id, | |
| "status": "COMPLETED", | |
| "result": f"Host '{platform.node()}' processed {cmd} successfully.", | |
| })) | |
| logger.info(f"Command '{cmd}' executed and acknowledged.") | |
| elif msg_type == "ack": | |
| pass | |
| except Exception as e: | |
| logger.error(f"Receiver error: {e}") | |
| break | |
| def main() -> None: | |
| parser = argparse.ArgumentParser(description="Pabot Local Device Agent") | |
| parser.add_argument("--server", default="ws://127.0.0.1:8000", help="WebSocket server base URL") | |
| parser.add_argument("--device-id", default="macbook_tauri", help="Device ID to bind (pixel8, macbook_tauri, office_pc)") | |
| parser.add_argument("--token", default=None, help="Device auth token") | |
| args = parser.parse_args() | |
| agent = LocalDeviceAgent(server_url=args.server, device_id=args.device_id, token=args.token) | |
| try: | |
| asyncio.run(agent.run()) | |
| except KeyboardInterrupt: | |
| logger.info("Agent stopped by user.") | |
| if __name__ == "__main__": | |
| main() | |