# -*- coding: utf-8 -*-
import os
import sys
import json
import re
import urllib.request
import urllib.error
import hashlib
import subprocess
import threading
import time
import argparse
from datetime import datetime, timezone

# Initialize Windows ANSI Terminal support natively
try:
    if sys.platform == 'win32':
        import ctypes
        kernel32 = ctypes.windll.kernel32
        kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7)
except Exception:
    pass

if sys.stdout.encoding != 'utf-8':
    try:
        sys.stdout.reconfigure(encoding='utf-8')
    except AttributeError:
        pass

def print_system(msg):
    print(f"\033[93m[SYSTEM] {msg}\033[0m")

def print_system_log(msg):
    print(f"\033[36m[SYSTEM LOG] {msg}\033[0m")

def print_success(msg):
    print(f"\033[92m[SYSTEM] {msg}\033[0m")

def print_error(msg):
    print(f"\033[91m[ERROR] {msg}\033[0m")

class AgentOSP:
    def __init__(self, selftest=False):
        self.selftest = selftest
        self.BaseDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
        self.history = []
        self.active_model = "your-model-id"
        self.api_endpoint = "https://api.example.com/v1/chat/completions"
        self.api_key = ""
        self.active_provider = "default"
        
        self.ConfigDir = os.path.normpath(os.path.join(self.BaseDir, "Config"))
        self.KeyDir = os.path.normpath(os.path.join(self.ConfigDir, "Keys"))
        self.LegacyKeyPath = os.path.normpath(os.path.join(self.ConfigDir, "GenAI_Key.txt"))
        self.KeyPath = self.LegacyKeyPath
        self.MemFile = os.path.normpath(os.path.join(self.BaseDir, "Memory/chat_history.json"))
        self.ConfigPath = os.path.normpath(os.path.join(self.ConfigDir, "agent_config.json"))
        self.ProviderPath = os.path.normpath(os.path.join(self.ConfigDir, "provider.json"))
        self.ProvidersPath = os.path.normpath(os.path.join(self.ConfigDir, "providers.json"))
        self.ProviderExamplePath = os.path.normpath(os.path.join(self.ConfigDir, "provider.example.json"))
        self.StatePath = os.path.normpath(os.path.join(self.ConfigDir, "active_state.json"))
        
        self.MaxAutoLoops = 12
        self.auto_loop_count = 0
        self.auto_trigger = False
        
        self.load_config()
        if not self.selftest:
            self.load_api_key(required=False)
        
    def normalize_endpoint(self, value):
        if not value:
            return value
        v = str(value).strip()
        if v.rstrip('/').endswith('/v1'):
            return v.rstrip('/') + '/chat/completions'
        return v

    def resolve_key_path(self, rel_path):
        if not rel_path:
            return self.LegacyKeyPath
        if os.path.isabs(rel_path) or re.match(r'^[A-Za-z]:\\', rel_path):
            return os.path.normpath(rel_path)
        return os.path.normpath(os.path.join(self.BaseDir, rel_path))

    def relative_key_path(self):
        try:
            rel = os.path.relpath(self.KeyPath, self.BaseDir)
            if not rel.startswith('..'):
                return rel.replace('/', '\\')
        except Exception:
            pass
        return self.KeyPath

    def resolve_python_command(self):
        candidates = []
        portable = os.path.normpath(os.path.join(self.BaseDir, "Tools", "Python", "python.exe"))
        if os.path.exists(portable):
            candidates.append([portable])
        runtime_path = os.path.normpath(os.path.join(self.ConfigDir, "python_runtime.json"))
        if os.path.exists(runtime_path):
            try:
                with open(runtime_path, "r", encoding="utf-8-sig") as f:
                    runtime = json.load(f)
                command = runtime.get("command") or ""
                if command:
                    if not (os.path.isabs(command) or re.match(r'^[A-Za-z]:\\', command)):
                        command = os.path.normpath(os.path.join(self.BaseDir, command))
                    args = [a for a in str(runtime.get("args") or "").split() if a]
                    candidates.append([command] + args)
            except Exception as e:
                print_system_log(f"Ignoring invalid python_runtime.json: {e}")
        candidates.extend([[sys.executable], ["py", "-3"], ["python"]])
        for candidate in candidates:
            exe = candidate[0]
            if os.path.exists(exe) or exe in (sys.executable, "py", "python"):
                return candidate
        return [sys.executable]


    def load_config(self):
        if os.path.exists(self.ProvidersPath):
            try:
                with open(self.ProvidersPath, "r", encoding="utf-8-sig") as f:
                    provider_set = json.load(f)
                self.active_provider = provider_set.get("activeProvider", self.active_provider)
                providers = provider_set.get("providers", {})
                provider = providers.get(self.active_provider) or providers.get("default") or {}
                if provider:
                    if provider.get("endpoint"):
                        self.api_endpoint = self.normalize_endpoint(provider.get("endpoint"))
                    if provider.get("model"):
                        self.active_model = provider.get("model")
                    if provider.get("keyFile"):
                        self.KeyPath = self.resolve_key_path(provider.get("keyFile"))
                    return
            except Exception as e:
                print_system(f"Warning: Failed to load providers config: {e}")

        provider_path = self.ProviderPath if os.path.exists(self.ProviderPath) else self.ProviderExamplePath
        if os.path.exists(provider_path):
            try:
                with open(provider_path, "r", encoding="utf-8-sig") as f:
                    provider = json.load(f)
                self.api_endpoint = self.normalize_endpoint(provider.get("endpoint") or self.api_endpoint)
                self.active_model = provider.get("model") or self.active_model
                self.active_provider = provider.get("activeProvider") or self.active_provider
                if provider.get("keyFile"):
                    self.KeyPath = self.resolve_key_path(provider.get("keyFile"))
            except Exception as e:
                print_system(f"Warning: Failed to load provider config: {e}")
        elif os.path.exists(self.ConfigPath):
            try:
                with open(self.ConfigPath, "r", encoding="utf-8") as f:
                    config = json.load(f)
                    routing = config.get("model_routing", {})
                    self.active_model = routing.get("AgentOSP", routing.get("AgentOSX", self.active_model))
            except Exception as e:
                print_system(f"Warning: Failed to load config: {e}")

    def save_provider_config(self):
        os.makedirs(self.ConfigDir, exist_ok=True)
        key_rel = self.relative_key_path()
        legacy = {
            "endpoint": self.normalize_endpoint(self.api_endpoint),
            "model": self.active_model,
            "apiKey": "",
            "apiKeySet": os.path.exists(self.KeyPath),
            "keyFile": key_rel,
            "activeProvider": self.active_provider,
            "installedAtUtc": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
        }
        with open(self.ProviderPath, "w", encoding="utf-8") as f:
            json.dump(legacy, f, indent=2)
            f.write("\n")
        provider_set = {"activeProvider": self.active_provider, "providers": {}}
        if os.path.exists(self.ProvidersPath):
            try:
                with open(self.ProvidersPath, "r", encoding="utf-8-sig") as f:
                    provider_set = json.load(f)
            except Exception:
                pass
        provider_set.setdefault("providers", {})
        provider_set["activeProvider"] = self.active_provider
        provider_set["providers"][self.active_provider] = {
            "endpoint": self.normalize_endpoint(self.api_endpoint),
            "model": self.active_model,
            "keyFile": key_rel,
            "apiKeySet": os.path.exists(self.KeyPath),
            "type": "openai-compatible"
        }
        with open(self.ProvidersPath, "w", encoding="utf-8") as f:
            json.dump(provider_set, f, indent=2)
            f.write("\n")

    def switch_provider(self, name):
        if not os.path.exists(self.ProvidersPath):
            print_error("Config/providers.json not found. Run installer or create providers first.")
            return
        with open(self.ProvidersPath, "r", encoding="utf-8-sig") as f:
            provider_set = json.load(f)
        provider = provider_set.get("providers", {}).get(name)
        if not provider:
            print_error(f"Provider '{name}' not found.")
            return
        self.active_provider = name
        self.api_endpoint = self.normalize_endpoint(provider.get("endpoint") or self.api_endpoint)
        self.active_model = provider.get("model") or self.active_model
        self.KeyPath = self.resolve_key_path(provider.get("keyFile"))
        self.load_api_key(required=False)
        self.save_provider_config()
        print_success(f"Active provider switched to: {self.active_provider} | {self.api_endpoint} | {self.active_model}")

    def select_model(self):
        """Interactive provider + model picker."""
        if not os.path.exists(self.ProvidersPath):
            print_error("Config/providers.json not found. Run installer or create providers first.")
            return
        try:
            with open(self.ProvidersPath, "r", encoding="utf-8-sig") as f:
                provider_set = json.load(f)
        except Exception as e:
            print_error(f"Failed to read providers.json: {e}")
            return
        
        providers = provider_set.get("providers", {})
        if not providers:
            print_error("No providers configured.")
            return
        
        provider_names = list(providers.keys())
        print_system("Available providers:")
        for i, name in enumerate(provider_names, 1):
            pdata = providers[name]
            print(f"  {i}. {name}  ({pdata.get('endpoint', 'no endpoint')})")
        
        choice = input("Select provider (number): ").strip()
        try:
            idx = int(choice) - 1
            if idx < 0 or idx >= len(provider_names):
                raise ValueError
            selected_provider = provider_names[idx]
        except ValueError:
            print_error("Invalid selection.")
            return
        
        pdata = providers[selected_provider]
        models = pdata.get("models", [])
        if not models and pdata.get("model"):
            models = [pdata.get("model")]
        if not models:
            print_error(f"No models configured for provider '{selected_provider}'.")
            return
        
        print_system(f"Available models for {selected_provider}:")
        for i, m in enumerate(models, 1):
            marker = " <-- active" if m == self.active_model and selected_provider == self.active_provider else ""
            print(f"  {i}. {m}{marker}")
        
        mchoice = input("Select model (number or type name): ").strip()
        try:
            midx = int(mchoice) - 1
            if midx < 0 or midx >= len(models):
                raise ValueError
            selected_model = models[midx]
        except ValueError:
            selected_model = mchoice
        
        # Switch provider
        self.switch_provider(selected_provider)
        # Override with the model the user actually selected
        self.active_model = selected_model
        self.save_provider_config()
        print_success(f"Switched to provider: {self.active_provider} | model: {self.active_model}")

    def load_api_key(self, required=False):
        self.api_key = ""
        path = self.KeyPath if os.path.exists(self.KeyPath) else self.LegacyKeyPath
        if os.path.exists(path):
            try:
                with open(path, "r", encoding="utf-8-sig") as f:
                    self.api_key = f.read().strip()
            except Exception:
                if required:
                    print_error("API key file not found or unreadable.")
                    sys.exit(1)
        elif required:
            print_error(f"API key missing at '{self.KeyPath}'.")
            sys.exit(1)
            
    def get_workspace_map(self):
        manifest = "CURRENT WORKSPACE FILE MANIFEST:\n"
        for root, dirs, files in os.walk(self.BaseDir):
            if any(p in root for p in [".git", "__pycache__", "temp", "node_modules"]):
                continue
            for file in files:
                full_path = os.path.join(root, file)
                rel_path = os.path.relpath(full_path, self.BaseDir)
                if not any(pattern in rel_path for pattern in ["chat_history.json", "GenAI_Key.txt"]):
                    manifest += f"- {rel_path}\n"
        return manifest
        
    def get_boot_payload(self):
        payload = "[SYSTEM BOOT SEQUENCE]\n"
        for f in ["Config/identity.txt", "Config/user_profile.txt", "Config/environment.txt", "Memory/long_term_context.md"]:
            path = os.path.normpath(os.path.join(self.BaseDir, f))
            if os.path.exists(path):
                try:
                    with open(path, "r", encoding="utf-8", errors="ignore") as file:
                        payload += f"--- {f} ---\n{file.read()}\n"
                except Exception:
                    pass
                    
        manifest_script = os.path.normpath(os.path.join(self.BaseDir, "Skills/PowerShell/Get-SkillsManifest.ps1"))
        if os.path.exists(manifest_script):
            try:
                res = subprocess.run(["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", manifest_script],
                                     capture_output=True, text=True, encoding="utf-8")
                payload += f"--- DYNAMIC SKILLS REGISTRY ---\n{res.stdout}\n"
            except Exception:
                pass
                
        script_path = os.path.abspath(__file__)
        if os.path.exists(script_path):
            try:
                with open(script_path, "r", encoding="utf-8", errors="ignore") as file:
                    payload += f"--- {os.path.basename(script_path)} (Source Code) ---\n{file.read()}\n"
            except Exception:
                pass
                
        payload += f"--- WORKSPACE MAP ---\n{self.get_workspace_map()}\n"
        return payload
        
    def bootstrap_context(self):
        print_system("Bootstrapping AgentOS Context (Config, Source, Map)...")
        boot_payload = self.get_boot_payload()
        
        if not self.history:
            self.history = [
                {"role": "user", "content": f"{boot_payload}\nAcknowledge."},
                {"role": "assistant", "content": "Context pinned to Index 0."}
            ]
        else:
            self.history[0] = {"role": "user", "content": f"{boot_payload}\nAcknowledge."}
            self.history.append({"role": "assistant", "content": "Context pinned to Index 0."})
            
        self.save_history()
        self.update_active_state("Ready")
        print_success("Context loaded.")
        
    def save_history(self):
        os.makedirs(os.path.dirname(self.MemFile), exist_ok=True)
        try:
            history_str = json.dumps(self.history, indent=4)
            history_str = re.sub(r'[^\x09\x0A\x0D\x20-\x7E]', '', history_str)
            with open(self.MemFile, "w", encoding="utf-8") as f:
                f.write(history_str)
        except Exception as e:
            print_error(f"Error saving history: {e}")
            
    def load_history(self):
        if os.path.exists(self.MemFile):
            try:
                with open(self.MemFile, "r", encoding="utf-8") as f:
                    self.history = json.load(f)
                print("\033[92mAgentOS Online (Restored).\033[0m")
                return True
            except Exception:
                pass
        return False
        
    def update_active_state(self, state_val):
        try:
            os.makedirs(os.path.dirname(self.StatePath), exist_ok=True)
            blank_state = {
                "current_project": "Session Reset",
                "current_goal": state_val,
                "timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
            }
            with open(self.StatePath, "w", encoding="utf-8") as f:
                json.dump(blank_state, f, indent=4)
        except Exception:
            pass
            
    def reset_session(self):
        print_system("Resetting session and auto-bootstrapping...")
        if os.path.exists(self.MemFile):
            try:
                os.remove(self.MemFile)
            except Exception:
                pass
        self.history = []
        self.bootstrap_context()
        print_system("Session wiped. Memory and Active State deeply purged & Auto-Bootstrapped.")
        
    def load_file(self, file_path):
        clean_path = file_path.strip().strip('"').strip("'")
        full_path = os.path.normpath(os.path.join(self.BaseDir, clean_path))
        if os.path.exists(full_path):
            try:
                with open(full_path, "r", encoding="utf-8", errors="ignore") as f:
                    content = f.read()
                self.history.append({"role": "user", "content": f"[INJECTED FILE: {clean_path}]\n{content}"})
                print_success(f"File {clean_path} loaded into context.")
            except Exception as e:
                print_error(f"Read Failed: {e}")
        else:
            print_error(f"File not found: {clean_path}")
            
    def display_map(self):
        print_system("Current Workspace File Map:")
        print(self.get_workspace_map())
        
    def handle_paste(self):
        # Dual-mode paste: Grabs Windows OS Clipboard, with manual fallback
        clipboard_text = ""
        try:
            res = subprocess.run(
                ["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", "Get-Clipboard"],
                capture_output=True, text=True, encoding="utf-8", errors="replace"
            )
            if res.returncode == 0 and res.stdout.strip():
                clipboard_text = res.stdout
        except Exception:
            pass

        paste_content = ""
        if clipboard_text:
            cleaned_clip = clipboard_text.strip()
            clip_lines = cleaned_clip.splitlines()
            print_system(f"Detected {len(cleaned_clip)} characters in the Windows OS Clipboard.")
            print("\033[90m" + "-" * 50 + "\033[0m")
            print("--- CLIPBOARD PREVIEW (First 3 lines) ---")
            for line in clip_lines[:3]:
                print(f"  > {line}")
            if len(clip_lines) > 3:
                print(f"  > ... [TRUNCATED {len(clip_lines) - 3} MORE LINES]")
            print("\033[90m" + "-" * 50 + "\033[0m")
            
            ans = input("Use this clipboard content? [Y/n]: ").strip().lower()
            if ans in ["", "y", "yes"]:
                paste_content = clipboard_text
                print_success("Grabbing text directly from OS Clipboard...")
            else:
                print_system("Bypassing clipboard. Falling back to manual console entry.")
                paste_content = self._manual_paste_fallback()
        else:
            print_system("Windows Clipboard is empty or inaccessible.")
            paste_content = self._manual_paste_fallback()

        if paste_content and paste_content.strip():
            cleaned_paste = re.sub(r'[^\x09\x0A\x0D\x20-\x7E]', '', paste_content)
            if len(cleaned_paste) > 25000:
                print(f"\033[93m[WARNING] Paste payload too large ({len(cleaned_paste)} chars).\033[0m")
                paste_path = os.path.normpath(os.path.join(self.BaseDir, "Mailbox/Large_Paste.txt"))
                os.makedirs(os.path.dirname(paste_path), exist_ok=True)
                with open(paste_path, "w", encoding="utf-8") as f:
                    f.write(cleaned_paste)
                self.history.append({"role": "user", "content": f"[SYSTEM] User attempted a massive paste ({len(cleaned_paste)} chars). Saved to Mailbox\\Large_Paste.txt."})
                print_success("Stored massive paste to Mailbox\\Large_Paste.txt and notified context.")
            else:
                self.history.append({"role": "user", "content": f"[PASTED SNIPPET]\n{cleaned_paste}"})
                print_success("Multi-line text securely injected and sanitized.")
        else:
            print_system("Paste operation cancelled (no content captured).")

    def _manual_paste_fallback(self):
        print_system("Enter/Paste multi-line text manually. Type 'END' on a blank line to submit:")
        lines = []
        while True:
            try:
                line = input()
                if line.strip() == "END":
                    break
                lines.append(line)
            except (EOFError, KeyboardInterrupt):
                break
            except Exception as e:
                print_error(f"Console input error: {e}")
                break
        return "\n".join(lines)
                
    def print_help(self):
        print("\n\033[36mAVAILABLE COMMANDS:\033[0m")
        print("  /boot    - Injects core system profiles, source code, and workspace map.")
        print("  /map     - Injects a manifest of all files in the workspace.")
        print("  /load X  - Manually injects file 'X' into context.")
        print("  /providers - Lists configured endpoint/model/key profiles.")
        print("  /provider X - Switches active provider profile.")
        print("  /endpoint URL - Switches active endpoint and persists it.")
        print("  /model   - Interactive provider+model picker.")
        print("  /model X - Switches active model and persists it.")
        print("  /key     - Prompts for API key for the active provider.")
        print("  /session - Wipes current memory (no autoboot). Run /boot to reload context.")
        print("  /paste   - Securely reads OS Clipboard or falls back to console multi-line paste.")
        print("  /exit    - Closes AgentOSP.")
        
    def send_message(self):
        final_messages = self.history
        if len(self.history) > 60:
            final_messages = self.history[0:2] + self.history[-58:]
            
        payload = {
            "model": self.active_model,
            "messages": final_messages
        }
        
        headers = {"Content-Type": "application/json; charset=utf-8"}
        if self.api_key:
            headers["Authorization"] = f"Bearer {self.api_key}"
        
        req_data = json.dumps(payload).encode("utf-8")
        req = urllib.request.Request(self.api_endpoint, data=req_data, headers=headers, method="POST")
        
        class APIQueryThread(threading.Thread):
            def __init__(self, request):
                super().__init__()
                self.request = request
                self.response_data = None
                self.error = None

            def run(self):
                try:
                    with urllib.request.urlopen(self.request) as response:
                        self.response_data = response.read().decode("utf-8")
                    # Clear direct API errors
                except urllib.error.HTTPError as e:
                    self.error = e
                    # Grab error details if returned as a 4xx payload
                    if e.fp:
                        try:
                            self.response_data = e.fp.read().decode("utf-8")
                        except Exception:
                            pass
                except Exception as e:
                    self.error = e

        try:
            query_thread = APIQueryThread(req)
            query_thread.start()

            frames = [
                "<-= [ .     ] =-->",
                "<-= [ .o    ] =-->",
                "<-= [ .o*   ] =-->",
                "<-= [  o*O  ] =-->",
                "<-= [   *O. ] =-->",
                "<-= [    O..] =-->",
                "<-= [     . ] =-->",
                "<-= [      .] =-->",
                "<-= [     . ] =-->",
                "<-= [    O..] =-->",
                "<-= [   *O. ] =-->",
                "<-= [  o*O  ] =-->",
                "<-= [ .o*   ] =-->",
                "<-= [ .o    ] =-->"
            ]
            spinner = ["|", "/", "-", "\\"]
            idx = 0
            
            while query_thread.is_alive():
                frame = frames[idx % len(frames)]
                spin = spinner[idx % len(spinner)]
                sys.stdout.write(f"\r\033[93m[SYSTEM] Querying model {spin} {frame}\033[0m")
                sys.stdout.flush()
                idx += 1
                time.sleep(0.1)
                
            # Clear line
            sys.stdout.write("\r" + " " * 60 + "\r")
            sys.stdout.flush()

            query_thread.join()
            
            # If we threw a connection failure (no body)
            if query_thread.error and not query_thread.response_data:
                raise query_thread.error
                
            resp_data = query_thread.response_data
            resp_json = json.loads(resp_data)
            
            # API Error Payload Extraction
            if "error" in resp_json:
                print_error(f"API Returned Error Payload: {resp_json['error']}")
                return

            choices = resp_json.get("choices", [])
            if not choices:
                print_error(f"API Response contains no choices list: {resp_data}")
                return

            message = choices[0].get("message", {})
            reply = message.get("content")
            
            # Gracefully handle tools, refusal, or missing outputs
            if reply is None:
                if "refusal" in message and message["refusal"]:
                    reply = f"[API REFUSAL] {message['refusal']}"
                elif "tool_calls" in message:
                    reply = f"[API TOOL CALLS] {json.dumps(message['tool_calls'])}"
                else:
                    reply = "[API returned empty message content without details]"
            
            print(f"\n\033[96mAgentOSP:\033[0m")
            display_lines = reply.splitlines()
            for line in display_lines:
                if "[ACTION_REQUIRED]" in line:
                    print(f"\033[38;5;208m{line}\033[0m")
                else:
                    print(f"\033[96m{line}\033[0m")
                    
            self.history.append({"role": "assistant", "content": reply})
            self.save_history()
            
            self.process_autonomous_commands(reply)
                
        except urllib.error.HTTPError as e:
            error_body = e.read().decode("utf-8") if e.fp else ""
            print_error(f"API HTTP Error: {e.code} - {e.reason}")
            if error_body:
                print_system(f"Details: {error_body}")
        except Exception as e:
            print_error(f"Connection/Runtime Error: {e}")
            
    def process_autonomous_commands(self, reply):
        batch_response = ""
        
        # 1. PROCESS ALL FILE SAVES
        save_regex = re.compile(r'\[\s*SAVE_FILE\s*:\s*([^\]`]+)\]\s*(.*?)\s*(?:\[\s*/SAVE_FILE\s*\]|`{3}|$)', re.DOTALL)
        save_matches = save_regex.findall(reply)
        
        for match in save_matches:
            file_path_short = match[0].strip().replace("/", "\\")
            file_content = match[1].strip()
            
            file_content = re.sub(r'^`{3}[a-zA-Z]*\s*', '', file_content)
            file_content = re.sub(r'`{3}$', '', file_content)
            
            full_save_path = os.path.normpath(os.path.join(self.BaseDir, file_path_short))
            print_system_log(f"SAVE_FILE matched. Writing: {file_path_short}")
            
            try:
                os.makedirs(os.path.dirname(full_save_path), exist_ok=True)
                with open(full_save_path, "w", encoding="utf-8") as f:
                    f.write(file_content)
                    
                with open(full_save_path, "r", encoding="utf-8", errors="ignore") as f:
                    lines = f.readlines()
                line_count = len(lines)
                
                head = "".join(lines[0:5])
                tail = "".join(lines[-5:]) if line_count > 5 else ""
                
                verification_payload = f"--- HARD VERIFICATION PREVIEW (HEAD & TAIL) ---\n{head}"
                if line_count > 5:
                    verification_payload += f"\n...[TRUNCATED {line_count} LINES]...\n{tail}"
                verification_payload += "\n--- END VERIFICATION ---"
                
                sha256 = hashlib.sha256()
                with open(full_save_path, "rb") as f:
                    sha256.update(f.read())
                file_hash = sha256.hexdigest().upper()
                file_size = os.path.getsize(full_save_path)
                
                print_success(f"File Saved & Verified ({file_size} bytes).")
                batch_response += f"[SYSTEM: File '{file_path_short}' saved successfully. Size: {file_size} bytes, SHA256: {file_hash}]\n{verification_payload}\n\n"
                self.auto_trigger = True
            except Exception as e:
                print_error(f"Write Failed: {e}")
                batch_response += f"[SYSTEM: Failed to save '{file_path_short}': {e}]\n\n"
                self.auto_trigger = True
                
        # 2. PROCESS FILE DELETIONS
        del_regex = re.compile(r'\[\s*DELETE_FILE\s*:\s*([^\]`]+)\]')
        del_matches = del_regex.findall(reply)
        for path_match in del_matches:
            file_path_short = path_match.strip().replace("/", "\\")
            full_del_path = os.path.normpath(os.path.join(self.BaseDir, file_path_short))
            if os.path.exists(full_del_path):
                try:
                    os.remove(full_del_path)
                    print_system_log(f"Deleted: {file_path_short}")
                    batch_response += f"[SYSTEM: Successfully deleted '{file_path_short}']\n\n"
                    self.auto_trigger = True
                except Exception as e:
                    batch_response += f"[SYSTEM: Failed to delete '{file_path_short}': {e}]\n\n"
                    self.auto_trigger = True
                    
        # 3. PROCESS FILE READS
        read_regex = re.compile(r'\[\s*READ_FILE\s*:\s*([^\]`]+)\]')
        read_matches = read_regex.findall(reply)
        for path_match in read_matches:
            file_path_short = path_match.strip().replace("/", "\\")
            full_read_path = os.path.normpath(os.path.join(self.BaseDir, file_path_short))
            print_system_log(f"READ_FILE matched. Fetching: {file_path_short}")
            if os.path.exists(full_read_path):
                try:
                    with open(full_read_path, "r", encoding="utf-8", errors="ignore") as f:
                        file_content = f.read()
                    batch_response += f"[DATA: {file_path_short}]\n{file_content}\n\n"
                    print_success("File read successfully.")
                    self.auto_trigger = True
                except Exception as e:
                    print_error(f"Read Failed: {e}")
                    batch_response += f"[SYSTEM: Failed to read '{file_path_short}': {e}]\n\n"
                    self.auto_trigger = True
            else:
                batch_response += f"[SYSTEM: File '{file_path_short}' not found.]\n\n"
                self.auto_trigger = True
                
        # 4. PROCESS SKILL RUNS
        run_regex = re.compile(r'\[\s*RUN_SKILL\s*:\s*([^\]`]+)\]')
        run_matches = run_regex.findall(reply)
        for path_match in run_matches:
            skill_path_short = path_match.strip().replace("/", "\\")
            full_skill_path = os.path.normpath(os.path.join(self.BaseDir, skill_path_short))
            print_system_log(f"Executing: {skill_path_short}")
            if os.path.exists(full_skill_path):
                try:
                    # NATIVE PYTHON MULTI-AGENT EXECUTION SUPPORT
                    if skill_path_short.lower().endswith(".py"):
                        res = subprocess.run(self.resolve_python_command() + [full_skill_path],
                                             capture_output=True, text=True, encoding="utf-8", errors="replace")
                    else:
                        res = subprocess.run(["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", full_skill_path],
                                             capture_output=True, text=True, encoding="utf-8", errors="replace")
                    
                    stdout = res.stdout if res.stdout is not None else ""
                    stderr = res.stderr if res.stderr is not None else ""
                    out = stdout + "\n" + stderr
                    
                    batch_response += f"[SYSTEM: Skill Result for '{skill_path_short}':\n{out}]\n\n"
                    print_success("Skill Executed.")
                    self.auto_trigger = True
                except Exception as e:
                    batch_response += f"[SYSTEM: Skill execution failed for '{skill_path_short}': {e}]\n\n"
                    self.auto_trigger = True
            else:
                if "your_script" not in skill_path_short:
                    batch_response += f"[SYSTEM: Skill file '{skill_path_short}' not found.]\n\n"
                    self.auto_trigger = True
                    
        # 5. SYNTAX FALLBACK & AUTO-CORRECTION
        syntax_warning = ""
        if "SAVE_FILE" in reply and not save_matches:
            syntax_warning += "[SYSTEM SYNTAX ERROR: SAVE_FILE detected but failed to parse.]\n\n"
        if "READ_FILE" in reply and not read_matches:
            syntax_warning += "[SYSTEM SYNTAX ERROR: READ_FILE detected but failed to parse.]\n\n"
        if "RUN_SKILL" in reply and not run_matches:
            syntax_warning += "[SYSTEM SYNTAX ERROR: RUN_SKILL detected but failed to parse.]\n\n"
        if "DELETE_FILE" in reply and not del_matches:
            syntax_warning += "[SYSTEM SYNTAX ERROR: DELETE_FILE detected but failed to parse.]\n\n"
            
        if syntax_warning and "Modified print loop block inside AgentOS" not in reply:
            print("\033[93m\n[SYSTEM WARNING] Near-miss syntax detected. Auto-triggering correction chain.\033[0m")
            batch_response += syntax_warning
            self.auto_trigger = True
            
        # 6. DYNAMIC BUDGET AND PAUSE CONTROL
        budget_match = re.search(r'\[\s*SET_BUDGET\s*:\s*(\d+)\s*\]', reply)
        if budget_match:
            self.MaxAutoLoops = int(budget_match.group(1))
            batch_response += f"[SYSTEM: Auto-loop budget dynamically set to {self.MaxAutoLoops}]\n\n"
            print(f"\n\033[36m[SYSTEM] Auto-execution budget updated to {self.MaxAutoLoops}.\033[0m")
            
        if "[PAUSE_LOOP]" in reply:
            self.auto_trigger = False
            print("\n\033[93m[SYSTEM] PAUSE_LOOP executed. Control explicitly returned to user.\033[0m")
            
        if self.auto_trigger and batch_response.strip():
            self.history.append({"role": "user", "content": batch_response.strip()})
            self.save_history()
            
    def selftest_workspace(self):
        required = [
            "Config/agent_config.json",
            "Config/provider.example.json",
            "Skills/PowerShell/Get-SkillsManifest.ps1",
            "Skills/PowerShell/Parse-DroppedFile.ps1",
            "Skills/Python/parse_binary_file.py",
        ]
        missing = [rel for rel in required if not os.path.exists(os.path.join(self.BaseDir, rel))]
        if missing:
            print_error("SELFTEST FAIL: missing " + ", ".join(missing))
            return 1
        print_success(f"SELFTEST PASS: workspace files present; provider={self.active_provider}; endpoint={self.api_endpoint}; model={self.active_model}")
        return 0

    def run(self):
        # Startup: execute /boot once, limit initial auto-loop to 3, then default to 12
        self._startup_boot_done = False
        self._startup_loop_limit = 3
        self.MaxAutoLoops = self._startup_loop_limit
        self.auto_loop_count = 0
        self.auto_trigger = False
        
        print(f"\033[92mAgentOS Online.\033[0m Provider: {self.active_provider} | Model: {self.active_model}")
        print("\033[90mCommands: /help, /exit, /boot, /map, /load, /providers, /provider, /endpoint, /model, /key, /session, /paste, /selftest\033[0m")
        print("\033[90m" + "-" * 66 + "\033[0m")
        
        if not self.load_history():
            self.history = []
        # Run initial bootstrap as if /boot was called, but only once
        self.bootstrap_context()
        self._startup_boot_done = True
        # After initial bootstrap, set the default loop budget
        self.MaxAutoLoops = 12
            
        while True:
            try:
                if self.auto_trigger and self.auto_loop_count < self.MaxAutoLoops:
                    print(f"\n\033[93m[SYSTEM] Auto-Chaining Execution (Loop {self.auto_loop_count + 1}/{self.MaxAutoLoops})...\033[0m")
                    user_input = ""
                    self.auto_loop_count += 1
                    self.auto_trigger = False
                    trigger_api_call = True
                else:
                    if self.auto_loop_count >= self.MaxAutoLoops:
                        print(f"\n\033[93m[WARNING] Max auto-loops ({self.MaxAutoLoops}) reached. Returning control to user.\033[0m")
                    self.auto_loop_count = 0
                    self.auto_trigger = False
                    user_input = input("\nYou: ").strip()
                    trigger_api_call = False
                    
                if not user_input and not self.auto_trigger and not trigger_api_call:
                    continue
                    
                skip_api_call = False
                
                if user_input.startswith("/"):
                    parts = user_input.split(maxsplit=1)
                    command = parts[0].lower()
                    argument = parts[1].strip() if len(parts) > 1 else ""
                    
                    if command == "/selftest":
                        self.selftest_workspace()
                        skip_api_call = True
                    elif command == "/exit":
                        print_system("Terminating AgentOSP session. Goodbye.")
                        break
                    elif command == "/help":
                        self.print_help()
                        skip_api_call = True
                    elif command == "/session":
                        # Wipe session only; no autobootstrap. User runs /boot if desired.
                        if os.path.exists(self.MemFile):
                            try:
                                os.remove(self.MemFile)
                            except Exception:
                                pass
                        self.history = []
                        self.update_active_state("Session Reset")
                        print_system("Session wiped. Memory and Active State purged.")
                        skip_api_call = True
                    elif command == "/boot":
                        self.bootstrap_context()
                        skip_api_call = True
                    elif command == "/map":
                        self.history.append({"role": "user", "content": f"[WORKSPACE MAP INJECTION]\n{self.get_workspace_map()}"})
                        self.save_history()
                        print_success("Workspace map loaded into context.")
                        skip_api_call = True
                    elif command == "/load":
                        if not argument:
                            print_error("Please specify a file path. Example: /load Config\\identity.txt")
                        else:
                            self.load_file(argument)
                        skip_api_call = True
                    elif command == "/providers":
                        if os.path.exists(self.ProvidersPath):
                            with open(self.ProvidersPath, "r", encoding="utf-8-sig") as f:
                                provider_set = json.load(f)
                            print_success(f"Active provider: {self.active_provider}")
                            for pname, pdata in provider_set.get("providers", {}).items():
                                print(f"  - {pname}: {pdata.get('endpoint')} | {pdata.get('model')}")
                        else:
                            print_system(f"No providers.json found. Endpoint: {self.api_endpoint} | model: {self.active_model}")
                        skip_api_call = True
                    elif command == "/provider":
                        if argument:
                            self.switch_provider(argument)
                        else:
                            print_error("Please specify a provider name. Example: /provider ollama-cloud")
                        skip_api_call = True
                    elif command == "/endpoint":
                        if argument:
                            self.api_endpoint = self.normalize_endpoint(argument)
                            self.save_provider_config()
                            print_success(f"Active endpoint updated to: {self.api_endpoint}")
                        else:
                            print_system(f"Active endpoint: {self.api_endpoint}")
                        skip_api_call = True
                    elif command == "/model":
                        if argument:
                            self.active_model = argument
                            self.save_provider_config()
                            print_success(f"Active model updated to: {self.active_model}")
                        else:
                            self.select_model()
                        skip_api_call = True
                    elif command == "/key":
                        os.makedirs(self.KeyDir, exist_ok=True)
                        if self.KeyPath == self.LegacyKeyPath:
                            self.KeyPath = os.path.join(self.KeyDir, f"{self.active_provider}.key.txt")
                        new_key = input(f"API key for provider '{self.active_provider}' (blank for no auth): ").strip()
                        with open(self.KeyPath, "w", encoding="utf-8") as f:
                            f.write(new_key + "\n")
                        self.api_key = new_key
                        self.save_provider_config()
                        print_success(f"API key updated for provider: {self.active_provider}")
                        skip_api_call = True
                    elif command == "/paste":
                        self.handle_paste()
                        if not self.history or self.history[-1]["role"] != "user":
                            skip_api_call = True
                    else:
                        print_error(f"Unrecognized command: {command}. Type /help for valid commands.")
                        skip_api_call = True
                        
                if skip_api_call:
                    continue
                    
                if not user_input.startswith("/"):
                    clean_input = user_input.strip('"').strip("'")
                    if clean_input and os.path.exists(clean_input) and os.path.isfile(clean_input):
                        print_system(f"Drag-and-Drop detected. Parsing: {clean_input}")
                        parser_script = os.path.normpath(os.path.join(self.BaseDir, "Skills/PowerShell/Parse-DroppedFile.ps1"))
                        if os.path.exists(parser_script):
                            try:
                                res = subprocess.run(["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", parser_script, "-FilePath", clean_input],
                                                     capture_output=True, text=True, encoding="utf-8")
                                result_obj = json.loads(res.stdout)
                                if result_obj.get("error"):
                                    print_error(f"Parser Skill Failed: {result_obj['error']}")
                                    continue
                                elif result_obj.get("text_payload"):
                                    self.history.append({"role": "user", "content": result_obj["text_payload"]})
                                else:
                                    self.history.append({"role": "user", "content": str(result_obj)})
                                print_success("File successfully injected via Parse-DroppedFile skill. Sending to AgentOS...")
                                self.save_history()
                            except Exception as e:
                                print_error(f"Execution of Parser Skill failed: {e}")
                                continue
                        else:
                            self.load_file(clean_input)
                            self.save_history()
                    elif user_input:
                        self.history.append({"role": "user", "content": user_input})
                        self.save_history()
                
                self.send_message()
                
            except KeyboardInterrupt:
                print("\n[SYSTEM] Interrupted. Type /exit to terminate.")
                self.auto_trigger = False
            except Exception as e:
                print_error(f"Loop Exception: {e}")
                self.auto_trigger = False

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="AgentOSP Python agent for Mix of Agents")
    parser.add_argument("--selftest", action="store_true", help="verify local workspace without network or API key")
    args = parser.parse_args()
    orchestrator = AgentOSP(selftest=args.selftest)
    if args.selftest:
        sys.exit(orchestrator.selftest_workspace())
    orchestrator.run()