(function() {
    'use strict';

    var DEFAULT_BASE_URL = 'https://api.z.ai/api/coding/paas/v4';
    var DEFAULT_MODEL = 'glm-5.1';
    var STORAGE_KEY = 'zai_chat_';
    var MODE_PROMPTS = {
        chat: 'You are a helpful, knowledgeable AI assistant. Be concise and accurate.',
        coding: 'You are an expert coding assistant with internal tool access. Use action tags when execution is needed: [CREATE_FILE ...][/CREATE_FILE], [RUN_COMMAND]...[/RUN_COMMAND], [BUILD_APK project], [INSTALL_APK path], [VENV_SETUP], [VENV_PIP_INSTALL package]. Keep responses concise and complete.',
        brainstorm: 'You are a creative brainstorming partner. Generate diverse ideas, explore unconventional angles, build on concepts, and help evaluate trade-offs. Think freely and expansively. Present ideas in organized lists or tables when appropriate.',
        agentic: 'You are an autonomous agent with full control of this app: internal Linux, terminal, virtual env, build tools, device UI, and EXTERNAL network access.\n\n## File Operations:\n[CREATE_FILE path/to/file.ext]\ncontents\n[/CREATE_FILE]\n\n## Shell (any command — runs inside app sandbox):\n[RUN_COMMAND]\ncommand\n[/RUN_COMMAND]\n\n## External SSH — run commands on remote machines:\n[SSH_EXEC user@host command]\n[SSH_UPLOAD local_path user@host:remote_path]\n[SSH_DOWNLOAD user@host:remote_path local_path]\n\n## External network tools:\n[REMOTE_EXEC host command]\n[CURL_EXEC url]\n\n## Build:\n[BUILD_APK project_name]\n[INSTALL_APK /path/to/file.apk]\n\n## In-App Virtual Env:\n[VENV_SETUP]\n[VENV_PIP_INSTALL package_name]\n\n## AutoGLM Device Control:\n[DEVICE_TAP x y]\n[DEVICE_LONG_PRESS x y]\n[DEVICE_SWIPE startX startY endX endY]\n[DEVICE_TYPE text]\n[DEVICE_PRESS_BACK]\n[DEVICE_PRESS_HOME]\n[DEVICE_PRESS_RECENTS]\n[DEVICE_SCREENSHOT]\n[DEVICE_UI_TREE]\n[DEVICE_CLICK_TEXT button text]\n[DEVICE_CLICK_ID com.example:id/viewId]\n[DEVICE_LAUNCH com.example.app]\n[DEVICE_CURRENT_APP]\n\n## Hermes Agent:\n[HERMES_INSTALL]\n[HERMES_EXEC command]\n\n## Rules:\n1. Prefer internal sandbox tools first\n2. Use SSH/REMOTE actions for external machines — user must approve each one\n3. Use [CREATE_FILE], then [BUILD_APK], then [INSTALL_APK]\n4. Use [DEVICE_*] for device control; start with [DEVICE_UI_TREE]\n5. Generate complete files, never stubs\n6. For Java: package ai.z.app, target SDK 36\n7. For SSH: assume openssh-client is available, use key-based auth when possible\n8. Output [TASK_COMPLETE] only when all work is done'
    };

    var BUILD_SCRIPT = [
        '#!/system/bin/sh',
        'set -e',
        'PROJECT_DIR=$(pwd)',
        'BUILD_DIR="$PROJECT_DIR/build"',
        '',
        'AAPT2=$(command -v aapt2 2>/dev/null || echo "$TOOLS/bin/aapt2")',
        'ECJ=$(command -v ecj 2>/dev/null || echo "$TOOLS/bin/ecj")',
        'D8=$(command -v d8 2>/dev/null || echo "$TOOLS/bin/d8")',
        'APKSIGNER=$(command -v apksigner 2>/dev/null || echo "$TOOLS/bin/apksigner")',
        '',
        'if [ ! -x "$AAPT2" ] && [ ! -f "$AAPT2" ]; then echo "[BUILD FAILED] aapt2 not found"; exit 1; fi',
        'if [ ! -f "$ECJ" ]; then echo "[BUILD FAILED] ecj not found"; exit 1; fi',
        'if [ ! -f "$D8" ]; then echo "[BUILD FAILED] d8 not found"; exit 1; fi',
        '',
        'ANDROID_JAR=$(dirname "$AAPT2")/../share/aapt2/android.jar',
        'if [ ! -f "$ANDROID_JAR" ]; then ANDROID_JAR="$TOOLS/share/android.jar"; fi',
        'if [ ! -f "$ANDROID_JAR" ]; then ANDROID_JAR="$PREFIX/share/aapt2/android.jar"; fi',
        'if [ ! -f "$ANDROID_JAR" ]; then echo "[BUILD FAILED] android.jar not found"; exit 1; fi',
        '',
        'rm -rf "$BUILD_DIR"',
        'mkdir -p "$BUILD_DIR/gen" "$BUILD_DIR/classes" "$BUILD_DIR/apk"',
        '',
        'echo "[*] Starting build..."',
        '',
        'if [ -d "app/src/main/res" ] && [ "$(find app/src/main/res -type f 2>/dev/null | head -1)" ]; then',
        '  echo "[*] Compiling resources..."',
        '  "$AAPT2" compile --dir app/src/main/res -o "$BUILD_DIR/compiled_resources.zip" 2>&1 || { echo "[BUILD FAILED] Resource compilation failed"; exit 1; }',
        '  RES_ARG="-R $BUILD_DIR/compiled_resources.zip"',
        'else',
        '  echo "[*] No resources, skipping..."',
        '  RES_ARG=""',
        'fi',
        '',
        'echo "[*] Linking APK..."',
        '"$AAPT2" link -o "$BUILD_DIR/app.unsigned.apk" \\',
        '  -I "$ANDROID_JAR" \\',
        '  --manifest app/src/main/AndroidManifest.xml \\',
        '  $RES_ARG \\',
        '  --java "$BUILD_DIR/gen" 2>&1 || { echo "[BUILD FAILED] APK linking failed"; exit 1; }',
        '',
        'echo "[*] Compiling Java..."',
        'rm -f "$BUILD_DIR/sources.txt"',
        'find app/src/main/java -name "*.java" >> "$BUILD_DIR/sources.txt" 2>/dev/null',
        'find "$BUILD_DIR/gen" -name "*.java" >> "$BUILD_DIR/sources.txt" 2>/dev/null',
        'if [ ! -s "$BUILD_DIR/sources.txt" ]; then echo "[BUILD FAILED] No Java source files found"; exit 1; fi',
        'sh "$ECJ" -source 11 -target 11 -classpath "$ANDROID_JAR" -d "$BUILD_DIR/classes" @"$BUILD_DIR/sources.txt" 2>&1 || { echo "[BUILD FAILED] Java compilation failed"; exit 1; }',
        '',
        'echo "[*] Converting to DEX..."',
        'find "$BUILD_DIR/classes" -name "*.class" > "$BUILD_DIR/classfiles.txt"',
        'if [ ! -s "$BUILD_DIR/classfiles.txt" ]; then echo "[BUILD FAILED] No class files compiled"; exit 1; fi',
        'sh "$D8" --output "$BUILD_DIR" @"$BUILD_DIR/classfiles.txt" 2>&1 || { echo "[BUILD FAILED] D8 failed"; exit 1; }',
        'if [ ! -f "$BUILD_DIR/classes.dex" ]; then echo "[BUILD FAILED] classes.dex not found"; exit 1; fi',
        '',
        'echo "[*] Packaging..."',
        'cd "$BUILD_DIR/apk"',
        'unzip -o ../app.unsigned.apk > /dev/null 2>&1',
        'cp ../classes.dex .',
        'rm -rf META-INF',
        'zip -r ../app.unaligned.apk . > /dev/null 2>&1',
        'cd "$PROJECT_DIR"',
        '',
        'echo "[*] Signing..."',
        'KEYSTORE="$HOME/.android-debug.keystore"',
        'if [ ! -f "$KEYSTORE" ]; then',
        '  KEYTOOL=$(command -v keytool 2>/dev/null)',
        '  if [ -n "$KEYTOOL" ]; then',
        '    "$KEYTOOL" -genkeypair -v -keystore "$KEYSTORE" -storepass android \\',
        '      -alias androiddebugkey -keypass android -keyalg RSA -keysize 2048 \\',
        '      -validity 10000 -dname "CN=Debug,O=Debug,C=US" 2>/dev/null',
        '  else',
        '    openssl genrsa -out /tmp/dbg.key 2048 2>/dev/null && \\',
        '    openssl req -new -key /tmp/dbg.key -out /tmp/dbg.csr -subj "/CN=Debug/O=Debug/C=US" 2>/dev/null && \\',
        '    openssl x509 -req -days 10000 -in /tmp/dbg.csr -signkey /tmp/dbg.key -out /tmp/dbg.crt 2>/dev/null && \\',
        '    openssl pkcs12 -export -in /tmp/dbg.crt -inkey /tmp/dbg.key -out "$KEYSTORE" -name androiddebugkey -password pass:android 2>/dev/null && \\',
        '    rm -f /tmp/dbg.key /tmp/dbg.csr /tmp/dbg.crt',
        '  fi',
        'fi',
        '',
        'sh "$APKSIGNER" sign --ks "$KEYSTORE" --ks-pass pass:android --ks-key-alias androiddebugkey --key-pass pass:android "$BUILD_DIR/app.unaligned.apk" 2>&1 || true',
        'mv "$BUILD_DIR/app.unaligned.apk" "$BUILD_DIR/app-signed.apk"',
        '',
        'APK_PATH="$BUILD_DIR/app-signed.apk"',
        'APK_SIZE=$(du -h "$APK_PATH" 2>/dev/null | cut -f1)',
        'echo "[BUILD OK] APK: $APK_PATH ($APK_SIZE)"'
    ].join('\n');

    var state = {
        apiKey: '',
        baseUrl: DEFAULT_BASE_URL,
        model: DEFAULT_MODEL,
        temperature: 0.7,
        maxTokens: 4096,
        streaming: true,
        webSearch: false,
        currentMode: 'chat',
        theme: 'dark',
        conversations: [],
        activeConversationId: null,
        isGenerating: false,
        abortController: null,
        streamingConvId: null,
        streamingContent: '',
        streamingResponseDiv: null,
        terminalOpen: false,
        keepAwake: false,
        autoDeploy: true,
        autoInstallBuiltApk: true,
        lastBuiltApkPath: '',
        maxRetries: 10,
        autoContinue: true,
        maxAutoContinue: 5
    };

    function $(sel) { return document.querySelector(sel); }
    function $$(sel) { return document.querySelectorAll(sel); }

    function loadState() {
        try {
            state.apiKey = localStorage.getItem(STORAGE_KEY + 'apiKey') || '';
            state.baseUrl = localStorage.getItem(STORAGE_KEY + 'baseUrl') || DEFAULT_BASE_URL;
            state.model = localStorage.getItem(STORAGE_KEY + 'model') || DEFAULT_MODEL;
            state.temperature = parseFloat(localStorage.getItem(STORAGE_KEY + 'temperature')) || 0.7;
            state.maxTokens = parseInt(localStorage.getItem(STORAGE_KEY + 'maxTokens')) || 4096;
            state.streaming = localStorage.getItem(STORAGE_KEY + 'streaming') !== 'false';
            state.webSearch = localStorage.getItem(STORAGE_KEY + 'webSearch') === 'true';
            state.currentMode = localStorage.getItem(STORAGE_KEY + 'currentMode') || 'chat';
            state.theme = localStorage.getItem(STORAGE_KEY + 'theme') || 'dark';
            state.terminalOpen = localStorage.getItem(STORAGE_KEY + 'terminalOpen') === 'true';
            state.keepAwake = localStorage.getItem(STORAGE_KEY + 'keepAwake') === 'true';
            state.autoDeploy = localStorage.getItem(STORAGE_KEY + 'autoDeploy') !== 'false';
            state.autoInstallBuiltApk = localStorage.getItem(STORAGE_KEY + 'autoInstallBuiltApk') !== 'false';
            state.maxRetries = parseInt(localStorage.getItem(STORAGE_KEY + 'maxRetries')) || 10;
            state.autoContinue = localStorage.getItem(STORAGE_KEY + 'autoContinue') !== 'false';
            state.maxAutoContinue = parseInt(localStorage.getItem(STORAGE_KEY + 'maxAutoContinue')) || 5;
            var convData = localStorage.getItem(STORAGE_KEY + 'conversations');
            state.conversations = convData ? JSON.parse(convData) : [];
            state.activeConversationId = localStorage.getItem(STORAGE_KEY + 'activeConv') || null;
        } catch(e) { console.error('Load state error:', e); }
    }

    function saveState() {
        try {
            localStorage.setItem(STORAGE_KEY + 'apiKey', state.apiKey);
            localStorage.setItem(STORAGE_KEY + 'baseUrl', state.baseUrl);
            localStorage.setItem(STORAGE_KEY + 'model', state.model);
            localStorage.setItem(STORAGE_KEY + 'temperature', state.temperature.toString());
            localStorage.setItem(STORAGE_KEY + 'maxTokens', state.maxTokens.toString());
            localStorage.setItem(STORAGE_KEY + 'streaming', state.streaming.toString());
            localStorage.setItem(STORAGE_KEY + 'webSearch', state.webSearch.toString());
            localStorage.setItem(STORAGE_KEY + 'currentMode', state.currentMode);
            localStorage.setItem(STORAGE_KEY + 'theme', state.theme);
            localStorage.setItem(STORAGE_KEY + 'terminalOpen', state.terminalOpen.toString());
            localStorage.setItem(STORAGE_KEY + 'keepAwake', state.keepAwake.toString());
            localStorage.setItem(STORAGE_KEY + 'autoDeploy', state.autoDeploy.toString());
            localStorage.setItem(STORAGE_KEY + 'autoInstallBuiltApk', state.autoInstallBuiltApk.toString());
            localStorage.setItem(STORAGE_KEY + 'maxRetries', state.maxRetries.toString());
            localStorage.setItem(STORAGE_KEY + 'autoContinue', state.autoContinue.toString());
            localStorage.setItem(STORAGE_KEY + 'maxAutoContinue', state.maxAutoContinue.toString());
            localStorage.setItem(STORAGE_KEY + 'conversations', JSON.stringify(state.conversations));
            localStorage.setItem(STORAGE_KEY + 'activeConv', state.activeConversationId || '');
        } catch(e) { console.error('Save state error:', e); }
    }

    function genId() { return Date.now().toString(36) + Math.random().toString(36).substr(2, 9); }

    function getConversation(id) {
        var targetId = id || state.activeConversationId;
        if (!targetId) return null;
        return state.conversations.find(function(c) { return c.id === targetId; });
    }

    function flushStreamingToConversation() {
        if (state.streamingConvId && state.streamingContent) {
            var conv = getConversation(state.streamingConvId);
            if (conv) {
                var lastMsg = conv.messages[conv.messages.length - 1];
                if (lastMsg && lastMsg.role === 'assistant' && lastMsg._streaming) {
                    lastMsg.content = state.streamingContent;
                    delete lastMsg._streaming;
                } else {
                    conv.messages.push({ role: 'assistant', content: state.streamingContent });
                }
                saveState();
            }
        }
        state.streamingConvId = null;
        state.streamingContent = '';
        state.streamingResponseDiv = null;
    }

    function newConversation() {
        flushStreamingToConversation();
        if (state.isGenerating) {
            stopGeneration();
        }
        var conv = {
            id: genId(),
            title: 'New Chat',
            mode: state.currentMode,
            messages: [],
            files: [],
            createdAt: Date.now()
        };
        state.conversations.unshift(conv);
        state.activeConversationId = conv.id;
        state.isGenerating = false;
        state.abortController = null;
        saveState();
        renderConversationList();
        renderMessages();
        updateHeader();
        updateSendButton();
        updateTerminalVisibility();
    }

    function switchConversation(id) {
        if (id === state.activeConversationId) {
            closeSidebar();
            return;
        }
        flushStreamingToConversation();
        if (state.isGenerating) {
            stopGeneration();
            state.isGenerating = false;
            state.abortController = null;
        }
        state.activeConversationId = id;
        var conv = getConversation();
        if (conv) {
            state.currentMode = conv.mode || 'chat';
            updateModeSelector();
        }
        saveState();
        renderConversationList();
        renderMessages();
        updateHeader();
        updateSendButton();
        updateTerminalVisibility();
        closeSidebar();
    }

    function deleteConversation(id) {
        if (state.streamingConvId === id) {
            flushStreamingToConversation();
            if (state.isGenerating) stopGeneration();
            state.isGenerating = false;
        }
        state.conversations = state.conversations.filter(function(c) { return c.id !== id; });
        if (state.activeConversationId === id) {
            state.activeConversationId = state.conversations.length > 0 ? state.conversations[0].id : null;
        }
        saveState();
        renderConversationList();
        renderMessages();
        updateHeader();
        updateTerminalVisibility();
    }

    function updateHeader() {
        var conv = getConversation();
        $('#conversation-title').textContent = conv ? conv.title : 'Z.AI Chat';
        $('#current-mode-label').textContent = state.currentMode.charAt(0).toUpperCase() + state.currentMode.slice(1);
    }

    function showScreen(name) {
        $$('.screen').forEach(function(s) { s.classList.remove('active'); });
        $('#' + name + '-screen').classList.add('active');
    }

    function autoResize(el) {
        el.style.height = 'auto';
        el.style.height = Math.min(el.scrollHeight, 120) + 'px';
    }

    function renderConversationList() {
        var list = $('#conversation-list');
        if (!list) return;
        list.innerHTML = '';
        state.conversations.forEach(function(conv) {
            var div = document.createElement('div');
            div.className = 'conv-item' + (conv.id === state.activeConversationId ? ' active' : '');
            var msgCount = conv.messages.length;
            div.innerHTML = '<span class="conv-title">' + escapeHtml(conv.title) +
                (msgCount > 0 ? ' <span style="color:var(--text-muted);font-size:11px">(' + msgCount + ')</span>' : '') +
                '</span>' +
                '<button class="conv-delete" data-id="' + conv.id + '">&times;</button>';
            div.addEventListener('click', function(e) {
                if (e.target.classList.contains('conv-delete')) {
                    e.stopPropagation();
                    deleteConversation(e.target.dataset.id);
                    return;
                }
                switchConversation(conv.id);
            });
            list.appendChild(div);
        });
    }

    function escapeHtml(text) {
        var d = document.createElement('div');
        d.textContent = text;
        return d.innerHTML;
    }

    function escapeAttr(text) {
        return (text || '').replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/'/g, '&#39;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
    }

    function renderMarkdown(text) {
        if (typeof marked !== 'undefined') {
            marked.setOptions({
                highlight: function(code, lang) {
                    if (typeof hljs !== 'undefined' && lang && hljs.getLanguage(lang)) {
                        try { return hljs.highlight(code, { language: lang }).value; } catch(e) {}
                    }
                    return code;
                },
                breaks: true,
                gfm: true
            });
            return marked.parse(text);
        }
        return text.replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\n/g, '<br>');
    }

    var EXT_MAP = {
        python: 'py', py: 'py', javascript: 'js', js: 'js', typescript: 'ts', ts: 'ts',
        java: 'java', kotlin: 'kt', kt: 'kt', html: 'html', css: 'css', json: 'json',
        xml: 'xml', yaml: 'yml', yml: 'yml', markdown: 'md', md: 'md', sql: 'sql',
        shell: 'sh', bash: 'sh', sh: 'sh', powershell: 'ps1', dockerfile: 'Dockerfile',
        ruby: 'rb', go: 'go', rust: 'rs', c: 'c', cpp: 'cpp', csharp: 'cs',
        swift: 'swift', php: 'php', perl: 'pl', scala: 'scala', groovy: 'groovy',
        gradle: 'gradle', properties: 'properties', toml: 'toml', ini: 'ini',
        dart: 'dart', lua: 'lua', r: 'r', protobuf: 'proto'
    };

    function guessFileName(code, lang) {
        var firstLine = code.trim().split('\n')[0];
        if (/^(\/|\.\/|\.\.\/|[A-Za-z]:\\|[a-zA-Z0-9_\-]+\.[a-zA-Z]{1,10})/.test(firstLine) &&
            firstLine.length < 120 && firstLine.split('\n').length === 1 &&
            /\.\w+$/.test(firstLine)) {
            return firstLine.replace(/^.*[\/\\]/, '');
        }
        var ext = EXT_MAP[lang] || lang || 'txt';
        return 'code.' + ext;
    }

    function downloadFile(content, filename) {
        var blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
        var url = URL.createObjectURL(blob);
        var a = document.createElement('a');
        a.href = url;
        a.download = filename;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        setTimeout(function() { URL.revokeObjectURL(url); }, 5000);
    }

    function addCodeHeaders(container) {
        container.querySelectorAll('pre code').forEach(function(block) {
            var pre = block.parentElement;
            var lang = (block.className.match(/language-(\w+)/) || [])[1] || 'code';
            if (!pre.previousElementSibling || !pre.previousElementSibling.classList.contains('code-header')) {
                var header = document.createElement('div');
                header.className = 'code-header';
                var fileName = guessFileName(block.textContent, lang);
                header.innerHTML = '<span>' + escapeHtml(lang) + '</span>' +
                    '<div class="code-header-actions">' +
                    '<button class="download-btn" data-filename="' + escapeHtml(fileName) + '">Save</button>' +
                    '<button class="copy-btn">Copy</button></div>';
                pre.parentElement.insertBefore(header, pre);
                header.querySelector('.copy-btn').addEventListener('click', function() {
                    navigator.clipboard.writeText(block.textContent).then(function() {
                        this.textContent = 'Copied!';
                        setTimeout(function() { this.textContent = 'Copy'; }.bind(this), 2000);
                    }.bind(this));
                });
                header.querySelector('.download-btn').addEventListener('click', function() {
                    var fn = this.getAttribute('data-filename');
                    var code = block.textContent;
                    if (fn && /\.\w+$/.test(fn)) {
                        var lines = code.split('\n');
                        if (lines[0].trim() === fn || lines[0].trim().endsWith('/' + fn)) {
                            code = lines.slice(1).join('\n');
                        }
                    }
                    downloadFile(code, fn);
                    this.textContent = 'Saved!';
                    setTimeout(function() { this.textContent = 'Save'; }.bind(this), 2000);
                });
            }
        });
    }

    function highlightFilePaths(html) {
        return html.replace(
            /(^|[\s(>])(\/(?:[\w\-\.]+\/){1,}[\w\-\.]+\.\w{1,10})([\s)<,]|$)/gm,
            '$1<span class="filepath-badge" tabindex="0">$2</span>$3'
        );
    }

    function addFilePathHandlers(container) {
        container.querySelectorAll('.filepath-badge').forEach(function(badge) {
            badge.addEventListener('click', function() {
                var path = this.textContent;
                navigator.clipboard.writeText(path).then(function() {
                    badge.style.background = 'var(--success)';
                    badge.style.borderColor = 'var(--success)';
                    badge.style.color = 'white';
                    setTimeout(function() {
                        badge.style.background = '';
                        badge.style.borderColor = '';
                        badge.style.color = '';
                    }, 1500);
                });
            });
        });
    }

    function renderMessages() {
        var container = $('#messages');
        if (!container) return;
        container.innerHTML = '';
        var conv = getConversation();
        if (!conv || conv.messages.length === 0) {
            container.innerHTML = '<div class="message system">Start a conversation with Z.AI</div>';
            return;
        }
        conv.messages.forEach(function(msg) {
            appendMessage(msg.role, msg.content, container, false);
        });
        container.scrollTop = container.scrollHeight;
        updateTerminalContent();
    }

    function appendMessage(role, content, container, animate) {
        container = container || $('#messages');
        var div = document.createElement('div');
        div.className = 'message ' + role;
        if (animate === false) div.style.animation = 'none';

        if (role === 'assistant') {
            div.innerHTML = renderMarkdown(content);
            addCodeHeaders(div);
            var processed = highlightFilePaths(div.innerHTML);
            if (processed !== div.innerHTML) {
                div.innerHTML = processed;
                addFilePathHandlers(div);
            }
            var actionBar = document.createElement('div');
            actionBar.className = 'msg-actions';
            actionBar.innerHTML =
                '<button class="msg-action-btn msg-copy-btn">Copy</button>' +
                '<button class="msg-action-btn msg-save-btn">Save .txt</button>';
            div.appendChild(actionBar);
            actionBar.querySelector('.msg-copy-btn').addEventListener('click', function() {
                var btn = this;
                navigator.clipboard.writeText(content).then(function() {
                    btn.textContent = 'Copied!';
                    btn.classList.add('copied');
                    setTimeout(function() { btn.textContent = 'Copy'; btn.classList.remove('copied'); }, 2000);
                });
            });
            actionBar.querySelector('.msg-save-btn').addEventListener('click', function() {
                var btn = this;
                var conv = getConversation();
                var fn = (conv ? conv.title : 'response').replace(/[^a-zA-Z0-9_\-]/g, '_').substring(0, 40);
                downloadFile(content, fn + '.txt');
                btn.textContent = 'Saved!';
                setTimeout(function() { btn.textContent = 'Save .txt'; }, 2000);
            });

            if (state.currentMode === 'coding' || state.currentMode === 'agentic') {
                var actions = parseAiActions(content);
                addActionButtons(div, actions);
            }
        } else {
            div.textContent = content;
        }
        container.appendChild(div);
        container.scrollTop = container.scrollHeight;
        return div;
    }

    function updateStreamingMessage(div, content) {
        div.innerHTML = renderMarkdown(content);
        addCodeHeaders(div);
        var processed = highlightFilePaths(div.innerHTML);
        if (processed !== div.innerHTML) {
            div.innerHTML = processed;
            addFilePathHandlers(div);
        }
        var actions = div.querySelector('.msg-actions');
        if (!actions) {
            var actionBar = document.createElement('div');
            actionBar.className = 'msg-actions';
            actionBar.innerHTML =
                '<button class="msg-action-btn msg-copy-btn">Copy</button>' +
                '<button class="msg-action-btn msg-save-btn">Save .txt</button>';
            div.appendChild(actionBar);
        }
        $('#messages').scrollTop = $('#messages').scrollHeight;
    }

    function showThinking() {
        var container = $('#messages');
        var div = document.createElement('div');
        div.className = 'message assistant';
        div.id = 'thinking-msg';
        div.innerHTML = '<div class="thinking-indicator"><div class="thinking-dots"><span></span><span></span><span></span></div> Thinking...</div>';
        container.appendChild(div);
        container.scrollTop = container.scrollHeight;
    }

    function removeThinking() {
        var el = $('#thinking-msg');
        if (el) el.remove();
    }

    async function sendMessage() {
        var input = $('#message-input');
        var text = input.value.trim();
        if (!text || state.isGenerating) return;

        if (!state.apiKey) {
            showScreen('setup');
            return;
        }

        if ((state.currentMode === 'coding' || state.currentMode === 'agentic') && !termState.devToolsInstalled) {
            checkDevEnvironment();
        }

        if (!state.activeConversationId) {
            newConversation();
        }

        var conv = getConversation();
        if (!conv) return;

        conv.mode = state.currentMode;

        if (conv.messages.length === 0) {
            conv.title = text.substring(0, 50) + (text.length > 50 ? '...' : '');
            updateHeader();
            renderConversationList();
        }

        conv.messages.push({ role: 'user', content: text });
        saveState();
        input.value = '';
        autoResize(input);
        updateSendButton();
        appendMessage('user', text);

        state.isGenerating = true;
        state.streamingConvId = conv.id;
        state.streamingContent = '';
        updateSendButton();
        showThinking();
        if (state.keepAwake) setWakeLock(true);

        var requestBody = null;
        var responseDiv = null;

        try {
            var systemPrompt = MODE_PROMPTS[state.currentMode] || MODE_PROMPTS.chat;

            if (Shell && (state.currentMode === 'agentic' || state.currentMode === 'coding')) {
                var wsCtx = await getWorkspaceContext();
                if (wsCtx) systemPrompt += '\n\n## Current Device Context:\n' + wsCtx;
            }

            var apiMessages = [{ role: 'system', content: systemPrompt }];
            conv.messages.forEach(function(m) {
                if (m.role === 'user' || (m.role === 'assistant' && !m._streaming)) {
                    apiMessages.push({ role: m.role, content: m.content });
                }
            });

            requestBody = {
                model: state.model,
                messages: apiMessages,
                temperature: state.temperature,
                max_tokens: state.maxTokens,
                stream: state.streaming
            };

            if (state.webSearch) {
                requestBody.tools = [{
                    type: 'web_search',
                    web_search: { search_query: text, search_result: true }
                }];
            }

            removeThinking();
            responseDiv = appendMessage('assistant', '');
            state.streamingResponseDiv = responseDiv;

            if (state.streaming) {
                await streamResponseWithRetry(requestBody, responseDiv, conv);
            } else {
                var result = await apiRequestWithRetry(requestBody);
                var content = result.choices[0].message.content;
                updateStreamingMessage(responseDiv, content);
                state.streamingContent = content;
                conv.messages.push({ role: 'assistant', content: content });
            }
        } catch(err) {
            removeThinking();
            if (err.name !== 'AbortError') {
                if (state.streamingContent) {
                    conv.messages.push({ role: 'assistant', content: state.streamingContent, _streaming: false });
                }
                appendRetryMessage(err, requestBody, conv);
            } else if (state.streamingContent) {
                conv.messages.push({ role: 'assistant', content: state.streamingContent, _streaming: false });
            }
        } finally {
            state.abortController = null;
            state.streamingConvId = null;
            state.streamingResponseDiv = null;
            saveState();
            updateTerminalContent();

            if (state.autoDeploy && (state.currentMode === 'coding' || state.currentMode === 'agentic')) {
                var finalContent = state.streamingContent || '';
                if (!finalContent && conv && conv.messages.length) {
                    var last = conv.messages[conv.messages.length - 1];
                    if (last && last.role === 'assistant') finalContent = last.content;
                }
                if (finalContent) {
                    var autoActions = parseAiActions(finalContent);
                    if (autoActions.length > 0) {
                        trackFilesFromActions(autoActions, conv);
                        await autoExecuteActions(autoActions, conv);
                    }
                }
                renderFileTree();
            }

            if (state.autoContinue && (state.currentMode === 'coding' || state.currentMode === 'agentic')) {
                await autoContinueIfNeeded(conv);
            }

            state.isGenerating = false;
            updateSendButton();
            if (state.keepAwake) setWakeLock(false);
        }
    }

    function isNetworkError(err) {
        if (!err) return false;
        var msg = (err.message || '').toLowerCase();
        var name = (err.name || '').toLowerCase();
        return name === 'typeerror' || name === 'networkerror' ||
            msg.indexOf('failed to fetch') >= 0 ||
            msg.indexOf('network') >= 0 ||
            msg.indexOf('load failed') >= 0 ||
            msg.indexOf('connection') >= 0 ||
            msg.indexOf('net::') >= 0 ||
            msg.indexOf('interrupted') >= 0;
    }

    function sleep(ms) {
        return new Promise(function(r) { setTimeout(r, ms); });
    }

    async function apiRequestWithRetry(body, maxRetries) {
        maxRetries = maxRetries || 3;
        var lastErr;
        for (var attempt = 0; attempt < maxRetries; attempt++) {
            try {
                return await apiRequest(body);
            } catch(err) {
                lastErr = err;
                if (!isNetworkError(err) || attempt >= maxRetries - 1) throw err;
                var delay = 1000 * Math.pow(2, attempt);
                appendMessage('system', 'Connection lost. Retrying in ' + (delay / 1000) + 's... (attempt ' + (attempt + 2) + '/' + maxRetries + ')');
                await sleep(delay);
            }
        }
        throw lastErr;
    }

    var _streamAutoSaveCounter = 0;

    async function streamResponseWithRetry(body, responseDiv, conv, maxRetries) {
        maxRetries = maxRetries || 3;
        var lastErr;
        for (var attempt = 0; attempt < maxRetries; attempt++) {
            try {
                await streamResponse(body, responseDiv, conv, attempt > 0);
                return;
            } catch(err) {
                lastErr = err;
                if (err.name === 'AbortError') throw err;
                if (!isNetworkError(err)) throw err;
                if (attempt >= maxRetries - 1) throw err;

                if (state.streamingContent) {
                    conv.messages.push({ role: 'assistant', content: state.streamingContent, _streaming: true });
                    saveState();
                }

                var delay = 1500 * Math.pow(2, attempt);
                var retryNotice = document.createElement('div');
                retryNotice.className = 'message system';
                retryNotice.innerHTML = '<div style="display:flex;align-items:center;gap:6px;justify-content:center">' +
                    '<div class="thinking-dots"><span></span><span></span><span></span></div>' +
                    ' Reconnecting... (attempt ' + (attempt + 2) + '/' + maxRetries + ')</div>';
                $('#messages').appendChild(retryNotice);
                $('#messages').scrollTop = $('#messages').scrollHeight;

                await sleep(delay);
                if (retryNotice.parentElement) retryNotice.remove();

                var lastAssistant = '';
                for (var mi = conv.messages.length - 1; mi >= 0; mi--) {
                    if (conv.messages[mi].role === 'assistant' && !conv.messages[mi]._streaming) {
                        lastAssistant = conv.messages[mi].content;
                        break;
                    }
                }

                body.messages = body.messages.filter(function(m) { return m.role !== 'assistant'; });
                if (state.streamingContent) {
                    body.messages.push({ role: 'assistant', content: state.streamingContent });
                }
                body.stream = true;

                if (responseDiv && responseDiv.parentElement) {
                    var currentText = state.streamingContent || lastAssistant;
                    updateStreamingMessage(responseDiv, currentText + '\n\n*--- connection interrupted, resuming ---*\n');
                    state.streamingContent = currentText;
                }
            }
        }
        throw lastErr;
    }

    async function streamResponse(body, responseDiv, conv, isRetry) {
        state.abortController = new AbortController();
        body.stream = true;

        var url = state.baseUrl.replace(/\/+$/, '') + '/chat/completions';
        var resp = await fetch(url, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Authorization': 'Bearer ' + state.apiKey,
                'Accept-Language': 'en-US,en'
            },
            body: JSON.stringify(body),
            signal: state.abortController.signal
        });

        if (!resp.ok) {
            var errData = {};
            try { errData = await resp.json(); } catch(e) {}
            throw new Error(errData.error?.message || 'API error ' + resp.status);
        }

        var reader = resp.body.getReader();
        var decoder = new TextDecoder();
        var fullContent = '';
        var buffer = '';
        _streamAutoSaveCounter = 0;

        while (true) {
            var chunk = await reader.read();
            if (chunk.done) break;

            buffer += decoder.decode(chunk.value, { stream: true });
            var lines = buffer.split('\n');
            buffer = lines.pop() || '';

            for (var i = 0; i < lines.length; i++) {
                var line = lines[i].trim();
                if (!line || !line.startsWith('data:')) continue;
                var data = line.substring(5).trim();
                if (data === '[DONE]') break;

                try {
                    var parsed = JSON.parse(data);
                    var delta = parsed.choices && parsed.choices[0] && parsed.choices[0].delta;
                    if (delta && delta.content) {
                        fullContent += delta.content;
                        state.streamingContent = fullContent;
                        if (responseDiv && responseDiv.parentElement) {
                            updateStreamingMessage(responseDiv, fullContent);
                        }
                        _streamAutoSaveCounter++;
                        if (_streamAutoSaveCounter % 20 === 0) {
                            _saveStreamingProgress(conv, fullContent);
                        }
                    }
                } catch(e) {}
            }
        }

        conv.messages.push({ role: 'assistant', content: fullContent });
        state.streamingContent = '';
    }

    function _saveStreamingProgress(conv, content) {
        if (!conv) return;
        var last = conv.messages[conv.messages.length - 1];
        if (last && last.role === 'assistant' && last._streaming) {
            last.content = content;
        } else {
            conv.messages.push({ role: 'assistant', content: content, _streaming: true });
        }
        saveState();
    }

    function stopGeneration() {
        if (state.abortController) {
            state.abortController.abort();
        }
        flushStreamingToConversation();
    }

    async function apiRequest(body) {
        var url = state.baseUrl.replace(/\/+$/, '') + '/chat/completions';
        var resp = await fetch(url, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Authorization': 'Bearer ' + state.apiKey,
                'Accept-Language': 'en-US,en'
            },
            body: JSON.stringify(body)
        });
        if (!resp.ok) {
            var errData = {};
            try { errData = await resp.json(); } catch(e) {}
            throw new Error(errData.error?.message || 'API error ' + resp.status);
        }
        return await resp.json();
    }

    function appendRetryMessage(err, requestBody, conv) {
        var container = $('#messages');
        var div = document.createElement('div');
        div.className = 'message system';
        var isNet = isNetworkError(err);
        div.innerHTML = '<div style="text-align:center">' +
            '<div style="color:var(--danger);margin-bottom:8px">' +
            (isNet ? 'Connection interrupted' : escapeHtml(err.message || 'Request failed')) +
            '</div>' +
            (isNet ? '<button class="retry-btn" id="retry-btn">Retry</button>' : '') +
            '</div>';
        container.appendChild(div);
        container.scrollTop = container.scrollHeight;

        if (isNet) {
            setTimeout(function() {
                var btn = $('#retry-btn');
                if (btn) btn.addEventListener('click', function() {
                    if (div.parentElement) div.remove();
                    retryLastRequest(requestBody, conv);
                });
            }, 50);
        }
    }

    async function retryLastRequest(requestBody, conv) {
        if (!requestBody || !conv) return;
        state.isGenerating = true;
        state.streamingConvId = conv.id;
        updateSendButton();

        var responseDiv = appendMessage('assistant', '');
        state.streamingResponseDiv = responseDiv;

        try {
            if (state.streaming) {
                requestBody.stream = true;
                await streamResponseWithRetry(requestBody, responseDiv, conv);
            } else {
                var result = await apiRequestWithRetry(requestBody);
                var content = result.choices[0].message.content;
                updateStreamingMessage(responseDiv, content);
                state.streamingContent = content;
                conv.messages.push({ role: 'assistant', content: content });
            }
        } catch(err) {
            removeThinking();
            if (state.streamingContent) {
                conv.messages.push({ role: 'assistant', content: state.streamingContent, _streaming: false });
            }
            appendRetryMessage(err, requestBody, conv);
        } finally {
            state.isGenerating = false;
            state.abortController = null;
            state.streamingConvId = null;
            state.streamingResponseDiv = null;
            updateSendButton();
            saveState();
            updateTerminalContent();
        }
    }

    function setupVisibilityHandler() {
        document.addEventListener('visibilitychange', function() {
            if (document.visibilityState === 'hidden') {
                flushStreamingToConversation();
            }
        });

        window.addEventListener('online', function() {
            var msg = $('#offline-msg');
            if (msg) msg.remove();
        });

        window.addEventListener('offline', function() {
            var container = $('#messages');
            if (container && !$('#offline-msg')) {
                var div = document.createElement('div');
                div.className = 'message system';
                div.id = 'offline-msg';
                div.innerHTML = '<div style="color:var(--warning)">You are offline. Messages will be saved and sent when connection is restored.</div>';
                container.appendChild(div);
                container.scrollTop = container.scrollHeight;
            }
        });
    }

    function updateSendButton() {
        var input = $('#message-input');
        var sendBtn = $('#send-btn');
        var stopBtn = $('#stop-btn');

        if (state.isGenerating) {
            sendBtn.style.display = 'none';
            stopBtn.style.display = 'flex';
        } else {
            sendBtn.style.display = 'flex';
            stopBtn.style.display = 'none';
            sendBtn.disabled = !input.value.trim();
        }
    }

    function updateModeSelector() {
        $$('.mode-btn').forEach(function(btn) {
            btn.classList.toggle('active', btn.dataset.mode === state.currentMode);
        });
    }

    function openSidebar() {
        $('#sidebar').classList.add('open');
        $('#sidebar-overlay').classList.add('active');
    }

    function closeSidebar() {
        $('#sidebar').classList.remove('open');
        $('#sidebar-overlay').classList.remove('active');
    }

    function applyTheme(theme) {
        state.theme = theme;
        document.documentElement.setAttribute('data-theme', theme);
        var headerBtn = $('#theme-toggle-header');
        if (headerBtn) {
            headerBtn.innerHTML = theme === 'dark' ? '&#9788;' : '&#9790;';
            headerBtn.title = theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode';
        }
        var settingsToggle = $('#settings-darkmode');
        if (settingsToggle) settingsToggle.checked = (theme === 'dark');
        var metaTheme = document.querySelector('meta[name="theme-color"]');
        if (metaTheme) metaTheme.content = theme === 'dark' ? '#1a1a2e' : '#ffffff';
        saveState();
    }

    function toggleTheme() {
        applyTheme(state.theme === 'dark' ? 'light' : 'dark');
    }

    // ---- Terminal Panel ----

    function parseTerminalEntries(content) {
        if (!content) return [];
        var entries = [];

        var toolRegex = /\[(CREATE_FILE|EDIT_FILE|DELETE_FILE|RUN_COMMAND|SEARCH|READ_FILE|BUILD|TEST)\]\s*\(([^)]*)\)\s*\n?([\s\S]*?)(?=\n\[|$)/gi;
        var match;
        while ((match = toolRegex.exec(content)) !== null) {
            entries.push({
                type: 'tool',
                action: match[1],
                target: match[2].trim(),
                body: match[3].trim()
            });
        }

        var codeBlockRegex = /```(\w*)\n([\s\S]*?)```/gi;
        var idx = 0;
        while ((match = codeBlockRegex.exec(content)) !== null) {
            var isTool = false;
            for (var e = 0; e < entries.length; e++) {
                if (entries[e].type === 'tool' && match.index >= content.indexOf(entries[e].body) - 20) {
                    isTool = true;
                    break;
                }
            }
            if (!isTool) {
                idx++;
                var firstLine = match[2].trim().split('\n')[0];
                var isFilePath = /^(\/|\.\/|\.\.\/|[A-Za-z]:\\|[a-zA-Z0-9_\-]+\.[a-zA-Z]{1,4}$)/.test(firstLine) && firstLine.length < 120 && firstLine.split('\n').length === 1;
                entries.push({
                    type: 'code',
                    lang: match[1] || 'text',
                    code: match[2].trim(),
                    index: idx,
                    fileName: isFilePath ? firstLine : null
                });
            }
        }

        entries.sort(function(a, b) {
            return content.indexOf(a.type === 'tool' ? '[' + a.action : '```' + (a.lang || '')) -
                   content.indexOf(b.type === 'tool' ? '[' + b.action : '```' + (b.lang || ''));
        });

        return entries;
    }

    function renderTerminalEntry(entry) {
        if (entry.type === 'tool') {
            var actionIcon = { CREATE_FILE: '+', EDIT_FILE: '~', DELETE_FILE: '-', RUN_COMMAND: '>', SEARCH: '?', READ_FILE: 'R', BUILD: 'B', TEST: 'T' };
            var actionColor = { CREATE_FILE: 'var(--success)', EDIT_FILE: 'var(--warning)', DELETE_FILE: 'var(--danger)', RUN_COMMAND: 'var(--accent)', SEARCH: 'var(--text-secondary)', READ_FILE: 'var(--text-muted)', BUILD: 'var(--accent)', TEST: 'var(--success)' };
            var icon = actionIcon[entry.action] || '>';
            var color = actionColor[entry.action] || 'var(--accent)';
            var html = '<div class="term-entry term-tool" style="border-left:3px solid ' + color + '">';
            html += '<div class="term-tool-header"><span class="term-action" style="color:' + color + '">[' + icon + '] ' + escapeHtml(entry.action) + '</span>';
            if (entry.target) html += ' <span class="term-target">' + escapeHtml(entry.target) + '</span>';
            html += '</div>';
            if (entry.body) {
                html += '<pre class="term-code">' + escapeHtml(entry.body.substring(0, 2000)) + (entry.body.length > 2000 ? '\n... (truncated)' : '') + '</pre>';
            }
            html += '</div>';
            return html;
        }
        if (entry.type === 'code') {
            var label = entry.fileName ? escapeHtml(entry.fileName) : escapeHtml(entry.lang || 'code');
            var displayCode = entry.fileName ? entry.code.split('\n').slice(1).join('\n') : entry.code;
            if (!displayCode.trim()) displayCode = entry.code;
            var html = '<div class="term-entry term-code-block">';
            html += '<div class="term-file-header"><span class="term-lang">' + label + '</span>';
            html += '<button class="term-copy-btn" data-code="' + escapeHtml(entry.code).replace(/"/g, '&quot;') + '">Copy</button></div>';
            html += '<pre class="term-code">' + escapeHtml(displayCode.substring(0, 3000)) + (displayCode.length > 3000 ? '\n... (truncated)' : '') + '</pre>';
            html += '</div>';
            return html;
        }
        return '';
    }

    function updateTerminalContent() {
        var termBody = $('#terminal-body');
        if (!termBody) return;
        termBody.innerHTML = '';

        var conv = getConversation();
        if (!conv) return;

        var lastAssistant = null;
        for (var i = conv.messages.length - 1; i >= 0; i--) {
            if (conv.messages[i].role === 'assistant') {
                lastAssistant = conv.messages[i];
                break;
            }
        }

        if (!lastAssistant && !state.streamingContent) {
            termBody.innerHTML = '<div class="term-empty">No code output yet. Use Coding or Agentic mode to generate code.</div>';
            return;
        }

        var content = state.streamingContent || (lastAssistant ? lastAssistant.content : '');
        var entries = parseTerminalEntries(content);

        if (entries.length === 0) {
            termBody.innerHTML = '<div class="term-empty">No structured code blocks or tool calls detected in response.</div>';
            return;
        }

        entries.forEach(function(entry) {
            termBody.innerHTML += renderTerminalEntry(entry);
        });

        termBody.querySelectorAll('.term-copy-btn').forEach(function(btn) {
            btn.addEventListener('click', function() {
                var code = this.getAttribute('data-code').replace(/&quot;/g, '"').replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>');
                navigator.clipboard.writeText(code).then(function() {
                    this.textContent = 'Copied!';
                    setTimeout(function() { this.textContent = 'Copy'; }.bind(this), 2000);
                }.bind(this));
            });
        });

        if (state.terminalOpen) {
            termBody.scrollTop = termBody.scrollHeight;
        }
    }

    function updateTerminalVisibility() {
        var panel = $('#terminal-panel');
        var toggleBtn = $('#terminal-toggle');
        var fileTreeBtn = $('#file-tree-btn');
        if (!panel || !toggleBtn) return;

        var isDevMode = (state.currentMode === 'coding' || state.currentMode === 'agentic');
        if (isDevMode) {
            panel.style.display = 'flex';
            toggleBtn.style.display = 'flex';
            if (fileTreeBtn) fileTreeBtn.style.display = '';
        } else {
            panel.style.display = 'none';
            toggleBtn.style.display = 'none';
            if (fileTreeBtn) fileTreeBtn.style.display = 'none';
            state.terminalOpen = false;
            closeFileTree();
        }

        if (state.terminalOpen && isDevMode) {
            panel.classList.add('open');
        } else {
            panel.classList.remove('open');
        }

        var label = toggleBtn.querySelector('.terminal-label');
        if (label) label.textContent = state.terminalOpen ? 'Hide Terminal' : 'Show Terminal';
    }

    function toggleTerminal() {
        state.terminalOpen = !state.terminalOpen;
        var panel = $('#terminal-panel');
        if (panel) panel.classList.toggle('open', state.terminalOpen);
        var label = $('#terminal-toggle .terminal-label');
        if (label) label.textContent = state.terminalOpen ? 'Hide Terminal' : 'Show Terminal';
        if (state.terminalOpen) updateTerminalContent();
        saveState();
    }

    // ---- Terminal & Shell System ----

    var Shell = null;
    var Installer = null;
    var Wake = null;
    var Bootstrap = null;
    var termState = {
        history: [],
        historyIndex: -1,
        cwd: null,
        homeDir: null,
        toolsDir: null,
        projectsDir: null,
        isRunning: false,
        activePid: null,
        activeStreamId: null,
        devToolsInstalled: false,
        javaToolsInstalled: false,
        hasProot: false,
        prootPath: '',
        nativeLibDir: '',
        hermesPath: '',
        hermesVenv: '',
        autoglmEnabled: false,
        commandQueue: []
    };
    var approvalState = null;

    function initShellPlugins() {
        try {
            Shell = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Shell;
            Installer = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Installer;
            FileManager = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.FileManager;
            Wake = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Wake;
            Bootstrap = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Bootstrap;
        } catch(e) {}
        if (!Shell) console.warn('Shell plugin not available');
        if (!Installer) console.warn('Installer plugin not available');
        if (!FileManager) console.warn('FileManager plugin not available');
        if (!Wake) console.warn('Wake plugin not available');
        if (!Bootstrap) console.warn('Bootstrap plugin not available');
    }

    async function setWakeLock(on) {
        if (!Wake) return;
        try {
            if (on) { await Wake.acquire(); } else { await Wake.release(); }
        } catch(e) { console.warn('WakeLock error:', e); }
    }

    async function shellExec(command, cwd, stream) {
        if (!Shell) return { output: '[Shell plugin not available]\n', exitCode: -1 };
        try {
            var opts = { command: command, stream: !!stream };
            if (cwd) opts.cwd = cwd;
            var result = await Shell.execute(opts);
            return result;
        } catch(e) {
            return { output: '[Error: ' + e.message + ']\n', exitCode: -1 };
        }
    }

    function needsApprovalForCommand(command) {
        var cmd = (command || '').trim();
        return /(^|\s)(ssh|scp|sftp|curl|wget|adb|am\s+start|pm\s+install|install\s+|chmod|chown|rm\s+-rf|mv\s+|cp\s+|python|python3|pip|npm|git\s+push|git\s+pull)/i.test(cmd);
    }

    async function shellWriteFile(path, content) {
        if (!Shell) return false;
        try {
            await Shell.writeFile({ path: path, content: content });
            return true;
        } catch(e) { return false; }
    }

    async function shellReadFile(path) {
        if (!Shell) return null;
        try {
            var result = await Shell.readFile({ path: path });
            return result.content;
        } catch(e) { return null; }
    }

    async function shellMkdirs(path) {
        if (!Shell) return false;
        try { await Shell.mkdirs({ path: path }); return true; } catch(e) { return false; }
    }

    async function installApk(path) {
        if (!Installer) { termPrint('[Installer plugin not available]', 'err'); return; }
        try {
            await Installer.installApk({ path: path });
            termPrint('[APK install triggered: ' + path + ']', 'success');
        } catch(e) {
            termPrint('[Install failed: ' + e.message + ']', 'err');
        }
    }

    async function openFile(path, mimeType) {
        if (!FileManager) { termPrint('[FileManager plugin not available]', 'err'); return; }
        try {
            await FileManager.openFile({ path: path, mimeType: mimeType || '' });
            termPrint('[Open file triggered: ' + path + ']', 'success');
        } catch(e) {
            termPrint('[Open failed: ' + e.message + ']', 'err');
        }
    }

    function requestApproval(title, body, onApprove) {
        approvalState = { onApprove: onApprove };
        var modal = $('#approval-modal');
        var titleEl = $('#approval-title');
        var bodyEl = $('#approval-body');
        if (titleEl) titleEl.textContent = title || 'Approval required';
        if (bodyEl) bodyEl.textContent = body || '';
        if (modal) modal.style.display = 'flex';
    }

    function closeApproval() {
        var modal = $('#approval-modal');
        if (modal) modal.style.display = 'none';
        approvalState = null;
    }

    async function askApproval(title, body, action) {
        return new Promise(function(resolve) {
            requestApproval(title, body, async function() {
                try {
                    var result = await action();
                    resolve(result);
                } catch(e) {
                    resolve(false);
                } finally {
                    closeApproval();
                }
            });
        });
    }

    async function listFiles(path) {
        if (!FileManager) return null;
        try { return await FileManager.listFiles({ path: path }); } catch(e) { return null; }
    }

    function isApkPath(path) {
        return !!path && path.toLowerCase().endsWith('.apk');
    }

    function isOpenablePath(path) {
        return !!path && /\.(apk|apks|zip|json|txt|log|md|html?|xml|js|java|kt|png|jpg|jpeg|webp)$/i.test(path);
    }

    async function getDeviceInfo() {
        if (!Installer) return {};
        try { return await Installer.getDeviceInfo(); } catch(e) { return {}; }
    }

    async function getWorkspaceContext() {
        var ctx = '';
        try {
            var info = await getDeviceInfo();
            ctx += 'Device: ' + (info.manufacturer || '') + ' ' + (info.model || '') + ', Android ' + (info.release || '') + ' (SDK ' + (info.sdk || '') + '), ABI: ' + (info.abi || '') + '\n';

            var envResult = await shellExec('echo "HOME=$HOME CWD=$(pwd) TERMUX=${TERMUX_VERSION:-none}"', termState.cwd || termState.homeDir, false);
            ctx += 'Environment: ' + (envResult.output || '').trim() + '\n';

            var toolCheck = await shellExec('which aapt2 d8 ecj javac apksigner zipalign 2>/dev/null; echo "---"; which tsu su 2>/dev/null || echo "no-root"', termState.homeDir, false);
            ctx += 'Available tools: ' + (toolCheck.output || '').trim().replace(/\n---\n/, '\nRoot access: ') + '\n';

            var projectDir = termState.projectsDir || (termState.homeDir + '/projects');
            var lsResult = await shellExec('find ' + projectDir + ' -type f 2>/dev/null | head -30 || echo "no-projects"', termState.homeDir, false);
            if (lsResult.output && lsResult.output.indexOf('no-projects') < 0 && lsResult.output.trim()) {
                ctx += 'Project files:\n' + lsResult.output.trim() + '\n';
            }
        } catch(e) {}
        return ctx || null;
    }

    function termPrint(text, className) {
        var output = $('#term-output');
        if (!output) return;
        var line = document.createElement('div');
        line.className = 'term-line' + (className ? ' term-' + className : '');
        line.textContent = text;
        output.appendChild(line);
        output.scrollTop = output.scrollHeight;
    }

    function termPrintHtml(html, className) {
        var output = $('#term-output');
        if (!output) return;
        var line = document.createElement('div');
        line.className = 'term-line' + (className ? ' term-' + className : '');
        line.innerHTML = html;
        output.appendChild(line);
        output.scrollTop = output.scrollHeight;
    }

    async function termExec(command) {
        if (!command.trim()) return;
        if (termState.isRunning) return;

        if (needsApprovalForCommand(command)) {
            await askApproval('Run command?', command, function() {
                return shellExec(command, termState.cwd, false).then(function(result) {
                    termPrint('$ ' + command, 'cmd');
                    if (result.output) termPrint(result.output.replace(/\n$/, ''), '');
                    if (result.exitCode !== 0 && result.exitCode !== undefined) termPrint('[exit code: ' + result.exitCode + ']', 'err');
                    return true;
                });
            });
            return;
        }

        termState.history.push(command);
        termState.historyIndex = termState.history.length;
        termPrint('$ ' + command, 'cmd');

        var isCd = command.trim().startsWith('cd ');

        termState.isRunning = true;
        updateTermButtons();
        var input = $('#term-input');
        if (input) input.disabled = true;

        try {
            var result = await shellExec(command, termState.cwd, false);

            if (result.output) {
                termPrint(result.output.replace(/\n$/, ''), '');
            }
            if (result.exitCode !== 0 && result.exitCode !== undefined) {
                termPrint('[exit code: ' + result.exitCode + ']', result.exitCode > 0 ? 'err' : '');
            }

            if (isCd && result.exitCode === 0) {
                var target = command.trim().substring(3).trim();
                if (target === '~' || target === '') {
                    termState.cwd = termState.homeDir;
                } else {
                    var cwdResult = await shellExec('pwd', termState.cwd, false);
                    if (cwdResult.exitCode === 0 && cwdResult.output) {
                        termState.cwd = cwdResult.output.trim();
                    }
                }
                updateCwdDisplay();
            }
        } catch(e) {
            termPrint('[Error: ' + e.message + ']', 'err');
        } finally {
            termState.isRunning = false;
            updateTermButtons();
            if (input) input.disabled = false;
            if (input) input.focus();
        }
    }

    async function termExecStreaming(command) {
        if (!command.trim() || termState.isRunning) return;

        termState.history.push(command);
        termState.historyIndex = termState.history.length;
        termPrint('$ ' + command, 'cmd');

        termState.isRunning = true;
        updateTermButtons();
        var input = $('#term-input');
        if (input) input.disabled = true;

        try {
            var result = await shellExec(command, termState.cwd, true);
            termState.activePid = result.pid;
            termState.activeStreamId = result.streamId;

            if (Shell) {
                Shell.addListener(result.streamId, function(event) {
                    if (event.data) {
                        termPrint(event.data.replace(/\n$/, ''), '');
                    }
                    if (event.done) {
                        termState.isRunning = false;
                        termState.activePid = null;
                        termState.activeStreamId = null;
                        updateTermButtons();
                        if (input) { input.disabled = false; input.focus(); }
                        processCommandQueue();
                    }
                });
            }
        } catch(e) {
            termPrint('[Error: ' + e.message + ']', 'err');
            termState.isRunning = false;
            updateTermButtons();
            if (input) { input.disabled = false; input.focus(); }
            processCommandQueue();
        }
    }

    async function processCommandQueue() {
        if (termState.commandQueue.length === 0 || termState.isRunning) return;
        var next = termState.commandQueue.shift();
        await termExec(next);
    }

    function termQueueCommand(command) {
        if (termState.isRunning) {
            termState.commandQueue.push(command);
        } else {
            termExec(command);
        }
    }

    async function updateCwdDisplay() {
        var display = $('#term-cwd-display');
        if (!display) return;
        if (!termState.cwd && Shell) {
            try {
                var env = await Shell.getEnv();
                termState.cwd = env.CWD;
                termState.homeDir = env.HOME;
                termState.toolsDir = env.TOOLS;
                termState.projectsDir = env.PROJECTS;
            } catch(e) {}
        }
        var cwd = termState.cwd || '~';
        if (termState.homeDir && cwd.startsWith(termState.homeDir)) {
            cwd = '~' + cwd.substring(termState.homeDir.length);
        }
        display.textContent = cwd;
    }

    function updateTermButtons() {
        var runBtn = $('#term-run-btn');
        var stopBtn = $('#term-stop-btn');
        if (runBtn) runBtn.style.display = termState.isRunning ? 'none' : 'flex';
        if (stopBtn) stopBtn.style.display = termState.isRunning ? 'flex' : 'none';
    }

    // ---- AI Action Parser ----

    function parseAiActions(content) {
        var actions = [];
        var createActionRegex = /\[CREATE_FILE\s+([^\]]+)\]\n([\s\S]*?)\[\/CREATE_FILE\]/gi;
        var runCmdRegex = /\[RUN_COMMAND\]\n([\s\S]*?)\[\/RUN_COMMAND\]/gi;
        var buildApkRegex = /\[BUILD_APK\s+([^\]]+)\]/gi;
        var installApkRegex = /\[INSTALL_APK\s+([^\]]+)\]/gi;
        var deviceTapRegex = /\[DEVICE_TAP\s+(\d+)\s+(\d+)\]/gi;
        var deviceLongPressRegex = /\[DEVICE_LONG_PRESS\s+(\d+)\s+(\d+)\]/gi;
        var deviceSwipeRegex = /\[DEVICE_SWIPE\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\]/gi;
        var deviceTypeRegex = /\[DEVICE_TYPE\s+([^\]]+)\]/gi;
        var devicePressBackRegex = /\[DEVICE_PRESS_BACK\]/gi;
        var devicePressHomeRegex = /\[DEVICE_PRESS_HOME\]/gi;
        var devicePressRecentsRegex = /\[DEVICE_PRESS_RECENTS\]/gi;
        var deviceScreenshotRegex = /\[DEVICE_SCREENSHOT\]/gi;
        var deviceUiTreeRegex = /\[DEVICE_UI_TREE\]/gi;
        var deviceClickTextRegex = /\[DEVICE_CLICK_TEXT\s+([^\]]+)\]/gi;
        var deviceClickIdRegex = /\[DEVICE_CLICK_ID\s+([^\]]+)\]/gi;
        var deviceLaunchRegex = /\[DEVICE_LAUNCH\s+([^\]]+)\]/gi;
        var deviceCurrentAppRegex = /\[DEVICE_CURRENT_APP\]/gi;
        var sshExecRegex = /\[SSH_EXEC\s+([^\s]+)\s+([^\]]+)\]/gi;
        var sshUploadRegex = /\[SSH_UPLOAD\s+([^\s]+)\s+([^\]]+)\]/gi;
        var sshDownloadRegex = /\[SSH_DOWNLOAD\s+([^\s]+)\s+([^\]]+)\]/gi;
        var remoteExecRegex = /\[REMOTE_EXEC\s+([^\s]+)\s+([^\]]+)\]/gi;
        var curlExecRegex = /\[CURL_EXEC\s+([^\]]+)\]/gi;
        var hermesInstallRegex = /\[HERMES_INSTALL\]/gi;
        var hermesExecRegex = /\[HERMES_EXEC\s+([^\]]+)\]/gi;
        var venvSetupRegex = /\[VENV_SETUP\]/gi;
        var venvPipInstallRegex = /\[VENV_PIP_INSTALL\s+([^\]]+)\]/gi;
        var codeBlockFileRegex = /```(\w+)\s*\n([\s\S]*?)```/gi;
        var match;

        while ((match = createActionRegex.exec(content)) !== null) {
            actions.push({ type: 'create_file', path: match[1].trim(), content: match[2] });
        }
        while ((match = runCmdRegex.exec(content)) !== null) {
            actions.push({ type: 'run_command', command: match[1].trim() });
        }
        while ((match = buildApkRegex.exec(content)) !== null) {
            actions.push({ type: 'build_apk', project: match[1].trim() });
        }
        while ((match = installApkRegex.exec(content)) !== null) {
            actions.push({ type: 'install_apk', path: match[1].trim() });
        }
        while ((match = deviceTapRegex.exec(content)) !== null) {
            actions.push({ type: 'device_tap', x: parseInt(match[1]), y: parseInt(match[2]) });
        }
        while ((match = deviceLongPressRegex.exec(content)) !== null) {
            actions.push({ type: 'device_long_press', x: parseInt(match[1]), y: parseInt(match[2]) });
        }
        while ((match = deviceSwipeRegex.exec(content)) !== null) {
            actions.push({ type: 'device_swipe', startX: parseInt(match[1]), startY: parseInt(match[2]), endX: parseInt(match[3]), endY: parseInt(match[4]) });
        }
        while ((match = deviceTypeRegex.exec(content)) !== null) {
            actions.push({ type: 'device_type', text: match[1].trim() });
        }
        while ((match = devicePressBackRegex.exec(content)) !== null) {
            actions.push({ type: 'device_press_back' });
        }
        while ((match = devicePressHomeRegex.exec(content)) !== null) {
            actions.push({ type: 'device_press_home' });
        }
        while ((match = devicePressRecentsRegex.exec(content)) !== null) {
            actions.push({ type: 'device_press_recents' });
        }
        while ((match = deviceScreenshotRegex.exec(content)) !== null) {
            actions.push({ type: 'device_screenshot' });
        }
        while ((match = deviceUiTreeRegex.exec(content)) !== null) {
            actions.push({ type: 'device_ui_tree' });
        }
        while ((match = deviceClickTextRegex.exec(content)) !== null) {
            actions.push({ type: 'device_click_text', text: match[1].trim() });
        }
        while ((match = deviceClickIdRegex.exec(content)) !== null) {
            actions.push({ type: 'device_click_id', viewId: match[1].trim() });
        }
        while ((match = deviceLaunchRegex.exec(content)) !== null) {
            actions.push({ type: 'device_launch', pkg: match[1].trim() });
        }
        while ((match = deviceCurrentAppRegex.exec(content)) !== null) {
            actions.push({ type: 'device_current_app' });
        }
        while ((match = hermesInstallRegex.exec(content)) !== null) {
            actions.push({ type: 'hermes_install' });
        }
        while ((match = hermesExecRegex.exec(content)) !== null) {
            actions.push({ type: 'hermes_exec', command: match[1].trim() });
        }
        while ((match = venvSetupRegex.exec(content)) !== null) {
            actions.push({ type: 'venv_setup' });
        }
        while ((match = venvPipInstallRegex.exec(content)) !== null) {
            actions.push({ type: 'venv_pip_install', packages: match[1].trim() });
        }
        while ((match = sshExecRegex.exec(content)) !== null) {
            actions.push({ type: 'ssh_exec', host: match[1].trim(), command: match[2].trim() });
        }
        while ((match = sshUploadRegex.exec(content)) !== null) {
            actions.push({ type: 'ssh_upload', localPath: match[1].trim(), remotePath: match[2].trim() });
        }
        while ((match = sshDownloadRegex.exec(content)) !== null) {
            actions.push({ type: 'ssh_download', remotePath: match[1].trim(), localPath: match[2].trim() });
        }
        while ((match = remoteExecRegex.exec(content)) !== null) {
            actions.push({ type: 'remote_exec', host: match[1].trim(), command: match[2].trim() });
        }
        while ((match = curlExecRegex.exec(content)) !== null) {
            actions.push({ type: 'curl_exec', url: match[1].trim() });
        }
        while ((match = codeBlockFileRegex.exec(content)) !== null) {
            var lang = match[1];
            var code = match[2];
            var firstLine = code.trim().split('\n')[0];
            if (/^(\/|\.\/|\.\.\/|[A-Za-z]:\\)/.test(firstLine) && firstLine.length < 120 && /\.\w+$/.test(firstLine.split('\n')[0])) {
                var filePath = firstLine.trim();
                var fileContent = code.trim().split('\n').slice(1).join('\n');
                actions.push({ type: 'create_file', path: filePath, content: fileContent });
            }
        }
        return actions;
    }

    function addActionButtons(div, actions) {
        if (actions.length === 0) return;
        var hasFiles = actions.some(function(a) { return a.type === 'create_file'; });
        var hasCommands = actions.some(function(a) { return a.type === 'run_command'; });
        var hasBuild = actions.some(function(a) { return a.type === 'build_apk'; });
        var hasInstall = actions.some(function(a) { return a.type === 'install_apk'; });
        var hasDevice = actions.some(function(a) { return a.type && a.type.indexOf('device_') === 0; });
        var hasHermes = actions.some(function(a) { return a.type && a.type.indexOf('hermes_') === 0; });

        var actionBar = document.createElement('div');
        actionBar.className = 'msg-actions';

        if (hasFiles) {
            var deployBtn = document.createElement('button');
            deployBtn.className = 'deploy-btn';
            deployBtn.innerHTML = '&#9654; Deploy Files';
            deployBtn.addEventListener('click', function() { deployActions(actions); });
            actionBar.appendChild(deployBtn);
        }
        if (hasBuild) {
            var buildBtn = document.createElement('button');
            buildBtn.className = 'deploy-btn';
            buildBtn.style.background = 'linear-gradient(135deg, var(--accent), #a855f7)';
            buildBtn.innerHTML = '&#128230; Build APK';
            buildBtn.addEventListener('click', function() { buildFromActions(actions); });
            actionBar.appendChild(buildBtn);
        }
        if (hasInstall) {
            actions.forEach(function(action) {
                if (action.type === 'install_apk') {
                    var installBtn = document.createElement('button');
                    installBtn.className = 'install-apk-btn';
                    installBtn.innerHTML = '&#128241; Install APK';
                    installBtn.addEventListener('click', function() { installApk(action.path); });
                    actionBar.appendChild(installBtn);
                }
            });
        }

        div.appendChild(actionBar);
    }

    async function deployActions(actions) {
        showScreen('terminal');
        termPrint('\n--- Deploying files ---', 'info');

        for (var i = 0; i < actions.length; i++) {
            var action = actions[i];
            if (action.type === 'create_file') {
                var path = action.path;
                if (!path.startsWith('/')) {
                    path = (termState.projectsDir || termState.homeDir + '/projects') + '/' + path;
                }
                var dir = path.substring(0, path.lastIndexOf('/'));
                await shellMkdirs(dir);
                var ok = await shellWriteFile(path, action.content);
                if (ok) {
                    termPrint('  [+] ' + path + ' (' + action.content.length + ' bytes)', 'success');
                } else {
                    termPrint('  [!] Failed: ' + path, 'err');
                }
            }
        }
        termPrint('--- Deploy complete ---\n', 'info');
    }

    function showStatusToast(message, type) {
        var existing = $('#status-toast');
        if (existing) existing.remove();
        var toast = document.createElement('div');
        toast.id = 'status-toast';
        toast.style.cssText = 'position:fixed;bottom:80px;left:50%;transform:translateX(-50%);z-index:1000;' +
            'padding:10px 20px;border-radius:20px;font-size:13px;font-weight:600;max-width:90%;' +
            'text-align:center;pointer-events:none;opacity:0;transition:opacity 0.3s;' +
            'background:' + (type === 'success' ? 'var(--success)' : type === 'err' ? 'var(--danger)' : 'var(--accent)') +
            ';color:white;box-shadow:0 4px 12px rgba(0,0,0,0.3)';
        toast.textContent = message;
        document.body.appendChild(toast);
        requestAnimationFrame(function() { toast.style.opacity = '1'; });
        setTimeout(function() {
            toast.style.opacity = '0';
            setTimeout(function() { if (toast.parentElement) toast.remove(); }, 300);
        }, 3000);
    }

    var _agenticRetryCount = 0;

    function getMaxRetries() {
        return state.maxRetries || 10;
    }

    function getMaxAutoContinue() {
        return state.maxAutoContinue || 5;
    }

    function getFileLanguage(path) {
        var ext = (path || '').split('.').pop().toLowerCase();
        var map = {
            'java': 'java', 'xml': 'xml', 'html': 'html', 'css': 'css',
            'js': 'javascript', 'json': 'json', 'md': 'markdown',
            'py': 'python', 'kt': 'kotlin', 'gradle': 'groovy',
            'properties': 'properties', 'txt': 'text', 'sh': 'shell',
            'yaml': 'yaml', 'yml': 'yaml', 'toml': 'toml',
            'c': 'c', 'cpp': 'cpp', 'h': 'c', 'rs': 'rust',
            'sql': 'sql', 'svg': 'svg', 'png': 'image', 'jpg': 'image'
        };
        return map[ext] || ext;
    }

    function trackFile(conv, path, content) {
        if (!conv || !path) return;
        if (!conv.files) conv.files = [];
        var lang = getFileLanguage(path);
        var idx = -1;
        for (var i = 0; i < conv.files.length; i++) {
            if (conv.files[i].path === path) { idx = i; break; }
        }
        var entry = { path: path, content: content, language: lang, timestamp: Date.now() };
        if (idx >= 0) {
            conv.files[idx] = entry;
        } else {
            conv.files.push(entry);
        }
        saveState();
    }

    function trackFilesFromActions(actions, conv) {
        if (!conv) return;
        for (var i = 0; i < actions.length; i++) {
            if (actions[i].type === 'create_file') {
                trackFile(conv, actions[i].path, actions[i].content);
            }
        }
    }

    function resolveFilePath(path) {
        if (!path) return '';
        if (path.startsWith('/')) return path;
        return (termState.projectsDir || termState.homeDir + '/projects') + '/' + path;
    }

    function getLastAssistantContent(conv) {
        if (!conv || !conv.messages) return '';
        for (var i = conv.messages.length - 1; i >= 0; i--) {
            if (conv.messages[i].role === 'assistant' && !conv.messages[i]._streaming) {
                return conv.messages[i].content;
            }
        }
        return '';
    }

    function isTaskComplete(content) {
        if (!content) return true;
        if (content.indexOf('[TASK_COMPLETE]') >= 0) return true;
        var hasAction = content.indexOf('[CREATE_FILE') >= 0 ||
            content.indexOf('[RUN_COMMAND]') >= 0 ||
            content.indexOf('[BUILD_APK') >= 0 ||
            content.indexOf('[INSTALL_APK') >= 0 ||
            content.indexOf('[DEVICE_') >= 0 ||
            content.indexOf('[HERMES_') >= 0 ||
            content.indexOf('[VENV_') >= 0 ||
            content.indexOf('[SSH_') >= 0 ||
            content.indexOf('[REMOTE_EXEC') >= 0 ||
            content.indexOf('[CURL_EXEC') >= 0;
        var hasCodeBlock = content.indexOf('```') >= 0;
        if (!hasCodeBlock && !hasAction && content.length < 300) return true;
        if ((content.match(/```/g) || []).length % 2 !== 0) return false;
        if ((content.match(/\[CREATE_FILE/g) || []).length > (content.match(/\[\/CREATE_FILE\]/g) || []).length) return false;
        if ((content.match(/\[RUN_COMMAND\]/g) || []).length > (content.match(/\[\/RUN_COMMAND\]/g) || []).length) return false;
        if (hasAction) return false;
        return true;
    }

    async function autoContinueIfNeeded(conv) {
        if (!state.autoContinue) return;
        if (state.currentMode !== 'coding' && state.currentMode !== 'agentic') return;
        if (!conv || !state.apiKey) return;

        var maxCont = getMaxAutoContinue();
        var count = 0;

        while (count < maxCont) {
            var last = getLastAssistantContent(conv);
            if (isTaskComplete(last)) break;

            count++;
            termPrint('\n[>] Auto-continuing (' + count + '/' + maxCont + ')...', 'info');
            showStatusToast('Auto-continuing (' + count + '/' + maxCont + ')...', 'info');

            var contMsg = 'Your previous response was cut off or the task is not fully complete. ' +
                'Continue from EXACTLY where you left off. Complete ALL remaining files, commands, builds, and installations. ' +
                'When the ENTIRE task is fully done, output [TASK_COMPLETE] on its own line.';

            conv.messages.push({ role: 'user', content: contMsg });
            saveState();
            appendMessage('user', contMsg);

            state.isGenerating = true;
            state.streamingConvId = conv.id;
            state.streamingContent = '';
            updateSendButton();
            if (state.keepAwake) setWakeLock(true);

            var contError = false;
            try {
                var sysPrompt = MODE_PROMPTS[state.currentMode] || MODE_PROMPTS.chat;
                if (Shell && (state.currentMode === 'agentic' || state.currentMode === 'coding')) {
                    var wsCtx = await getWorkspaceContext();
                    if (wsCtx) sysPrompt += '\n\n## Current Device Context:\n' + wsCtx;
                }

                var apiMsgs = [{ role: 'system', content: sysPrompt }];
                conv.messages.forEach(function(m) {
                    if (m.role === 'user' || (m.role === 'assistant' && !m._streaming)) {
                        apiMsgs.push({ role: m.role, content: m.content });
                    }
                });

                var reqBody = {
                    model: state.model,
                    messages: apiMsgs,
                    temperature: state.temperature,
                    max_tokens: state.maxTokens,
                    stream: state.streaming
                };

                var respDiv = appendMessage('assistant', '');
                state.streamingResponseDiv = respDiv;

                if (state.streaming) {
                    await streamResponseWithRetry(reqBody, respDiv, conv);
                } else {
                    var res = await apiRequestWithRetry(reqBody);
                    var cont = res.choices[0].message.content;
                    updateStreamingMessage(respDiv, cont);
                    state.streamingContent = cont;
                    conv.messages.push({ role: 'assistant', content: cont });
                }
            } catch(err) {
                if (state.streamingContent) {
                    conv.messages.push({ role: 'assistant', content: state.streamingContent });
                }
                if (err.name !== 'AbortError') {
                    termPrint('[!] Auto-continue error: ' + (err.message || err), 'err');
                }
                contError = true;
            }

            state.isGenerating = false;
            state.abortController = null;
            state.streamingConvId = null;
            state.streamingResponseDiv = null;
            updateSendButton();
            saveState();
            if (state.keepAwake) setWakeLock(false);

            if (contError) break;

            var contContent = state.streamingContent || '';
            state.streamingContent = '';
            if (state.autoDeploy && contContent) {
                var contActions = parseAiActions(contContent);
                if (contActions.length > 0) {
                    trackFilesFromActions(contActions, conv);
                    await autoExecuteActions(contActions, conv);
                }
            }
            renderFileTree();
        }

        if (count > 0) {
            var finalCheck = getLastAssistantContent(conv);
            if (isTaskComplete(finalCheck)) {
                termPrint('\n[v] Task completed after ' + count + ' auto-continue(s)', 'success');
                showStatusToast('Task completed!', 'success');
            } else if (count >= maxCont) {
                termPrint('\n[!] Max auto-continues reached (' + maxCont + ')', 'warning');
            }
        }
    }

    function buildFileTreeData(files) {
        var root = { name: '', children: {}, files: [] };
        for (var f = 0; f < files.length; f++) {
            var parts = files[f].path.split('/').filter(Boolean);
            var cur = root;
            for (var i = 0; i < parts.length - 1; i++) {
                if (!cur.children[parts[i]]) {
                    cur.children[parts[i]] = { name: parts[i], children: {}, files: [] };
                }
                cur = cur.children[parts[i]];
            }
            cur.files.push({
                name: parts[parts.length - 1],
                path: files[f].path,
                language: files[f].language,
                timestamp: files[f].timestamp
            });
        }
        return root;
    }

    function renderTreeHtml(node, convId, depth) {
        var html = '';
        var indent = depth * 20;
        var dirs = Object.keys(node.children).sort();
        for (var d = 0; d < dirs.length; d++) {
            var child = node.children[dirs[d]];
            html += '<div class="ftree-folder">';
            html += '<div class="ftree-node ftree-dir" style="padding-left:' + (indent + 8) + 'px">';
            html += '<span class="ftree-arrow">&#9654;</span>';
            html += '<span class="ftree-dirname">' + escapeHtml(dirs[d]) + '</span>';
            html += '</div>';
            html += '<div class="ftree-children">';
            html += renderTreeHtml(child, convId, depth + 1);
            html += '</div></div>';
        }
        for (var fi = 0; fi < node.files.length; fi++) {
            var file = node.files[fi];
            html += '<div class="ftree-node ftree-file" style="padding-left:' + (indent + 20) + 'px" ';
            html += 'data-conv="' + convId + '" data-path="' + escapeAttr(file.path) + '">';
            html += '<span class="ftree-ext">' + escapeHtml(file.language || '?') + '</span>';
            html += '<span class="ftree-fname">' + escapeHtml(file.name) + '</span>';
            if (isApkPath(file.path)) {
                html += '<span class="ftree-badge">APK</span>';
            }
            html += '</div>';
        }
        return html;
    }

    function renderFileTree() {
        var body = $('#file-tree-body');
        if (!body) return;
        var conv = getConversation();
        if (!conv || !conv.files || conv.files.length === 0) {
            body.innerHTML = '<div class="ftree-empty">No files yet.<br>AI-generated files appear here.</div>';
            var badge = $('#file-tree-count');
            if (badge) badge.textContent = '0 files';
            return;
        }
        var tree = buildFileTreeData(conv.files);
        body.innerHTML = renderTreeHtml(tree, conv.id, 0);
        var badge = $('#file-tree-count');
        if (badge) badge.textContent = conv.files.length + ' file' + (conv.files.length !== 1 ? 's' : '');
    }

    async function renderDeviceFiles(rootPath) {
        var body = $('#file-tree-body');
        if (!body) return;
        var result = await listFiles(rootPath);
        if (!result) {
            body.innerHTML = '<div class="ftree-empty">File manager unavailable.</div>';
            return;
        }
        var items = result.items || [];
        if (items.length === 0) {
            body.innerHTML = '<div class="ftree-empty">Folder is empty.</div>';
            return;
        }
        var html = '<div class="device-files-root">';
        for (var i = 0; i < items.length; i++) {
            var item = items[i];
            html += '<div class="ftree-node ftree-file device-file-row" data-path="' + escapeAttr(item.path) + '">';
            html += '<span class="ftree-ext">' + escapeHtml(item.directory ? 'dir' : (item.mimeType || '?')) + '</span>';
            html += '<span class="ftree-fname">' + escapeHtml(item.name) + '</span>';
            html += item.directory ? '<span class="ftree-badge">DIR</span>' : (isApkPath(item.path) ? '<span class="ftree-badge">APK</span>' : '');
            html += '</div>';
        }
        html += '</div>';
        body.innerHTML = html;
    }

    async function renderFileManagerHome() {
        var body = $('#file-tree-body');
        if (!body) return;
        if (!FileManager) {
            body.innerHTML = '<div class="ftree-empty">File manager unavailable.</div>';
            return;
        }
        try {
            var result = await FileManager.getRoots();
            var roots = result.roots || [];
            var html = '<div class="device-files-root">';
            for (var i = 0; i < roots.length; i++) {
                var item = roots[i];
                html += '<div class="ftree-node ftree-file device-file-row" data-path="' + escapeAttr(item.path) + '">';
                html += '<span class="ftree-ext">root</span>';
                html += '<span class="ftree-fname">' + escapeHtml(item.name) + '</span>';
                html += '<span class="ftree-badge">OPEN</span>';
                html += '</div>';
            }
            html += '</div>';
            body.innerHTML = html;
            var title = document.querySelector('#file-tree-panel h3');
            if (title) title.textContent = 'File Manager';
            var count = $('#file-tree-count');
            if (count) count.textContent = roots.length + ' roots';
        } catch(e) {
            body.innerHTML = '<div class="ftree-empty">File manager unavailable.</div>';
        }
    }

    function toggleFileTree() {
        var panel = $('#file-tree-panel');
        var overlay = $('#file-tree-overlay');
        if (!panel) return;
        var isOpen = panel.classList.contains('open');
        if (isOpen) {
            panel.classList.remove('open');
            if (overlay) overlay.classList.remove('active');
        } else {
            renderFileTree();
            panel.classList.add('open');
            if (overlay) overlay.classList.add('active');
        }
    }

    function toggleDeviceFileManager() {
        var panel = $('#file-tree-panel');
        var overlay = $('#file-tree-overlay');
        if (!panel) return;
        if (panel.classList.contains('open')) {
            panel.classList.remove('open');
            if (overlay) overlay.classList.remove('active');
            return;
        }
        panel.classList.add('open');
        if (overlay) overlay.classList.add('active');
        renderFileManagerHome();
    }

    function showProjectFiles() {
        renderFileTree();
        var title = document.querySelector('#file-tree-panel h3');
        if (title) title.textContent = 'Project Files';
        var count = $('#file-tree-count');
        if (count) count.textContent = (getConversation() && getConversation().files ? getConversation().files.length : 0) + ' files';
    }

    function closeFileTree() {
        var panel = $('#file-tree-panel');
        var overlay = $('#file-tree-overlay');
        if (panel) panel.classList.remove('open');
        if (overlay) overlay.classList.remove('active');
    }

    function openFileViewer(convId, path) {
        var conv = getConversation(convId);
        if (!conv || !conv.files) return;
        var file = null;
        for (var i = 0; i < conv.files.length; i++) {
            if (conv.files[i].path === path) { file = conv.files[i]; break; }
        }
        if (!file) return;

        var viewer = $('#file-viewer');
        var nameEl = $('#file-viewer-name');
        var langEl = $('#file-viewer-lang');
        var contentEl = $('#file-viewer-content');
        var textareaEl = $('#file-viewer-textarea');
        var bodyEl = $('#file-viewer-body');
        var editorEl = $('#file-viewer-editor');
        var saveBtn = $('#file-viewer-save');
        var editBtn = $('#file-viewer-edit');

        if (!viewer || !nameEl || !langEl || !contentEl || !textareaEl || !bodyEl || !editorEl || !saveBtn || !editBtn) return;
        nameEl.textContent = file.path;
        langEl.textContent = file.language;
        contentEl.textContent = file.content;
        textareaEl.value = file.content;
        bodyEl.style.display = '';
        editorEl.style.display = 'none';
        saveBtn.style.display = 'none';
        editBtn.style.display = '';
        editBtn.textContent = 'Edit';

        viewer.style.display = 'flex';
        viewer.dataset.convId = convId;
        viewer.dataset.path = path;
    }

    async function openFileFromTree(path) {
        if (isApkPath(path)) {
            await askApproval('Install APK?', path, function() { return installApk(path); });
            return;
        }
        var fileManagerTitle = document.querySelector('#file-tree-panel h3');
        if (fileManagerTitle && fileManagerTitle.textContent === 'File Manager') {
            await renderDeviceFiles(path);
            return;
        }
        await openFile(path, '');
    }

    function closeFileViewer() {
        var viewer = $('#file-viewer');
        if (viewer) viewer.style.display = 'none';
    }

    function toggleFileEdit() {
        var bodyEl = $('#file-viewer-body');
        var editorEl = $('#file-viewer-editor');
        var saveBtn = $('#file-viewer-save');
        var editBtn = $('#file-viewer-edit');

        if (bodyEl.style.display !== 'none') {
            bodyEl.style.display = 'none';
            editorEl.style.display = '';
            saveBtn.style.display = '';
            editBtn.textContent = 'View';
        } else {
            bodyEl.style.display = '';
            editorEl.style.display = 'none';
            saveBtn.style.display = 'none';
            editBtn.textContent = 'Edit';
        }
    }

    async function saveFileEdit() {
        var viewer = $('#file-viewer');
        var textareaEl = $('#file-viewer-textarea');
        if (!viewer || !textareaEl) return;

        var convId = viewer.dataset.convId;
        var path = viewer.dataset.path;
        var newContent = textareaEl.value;

        var conv = getConversation(convId);
        if (!conv) return;

        var absPath = resolveFilePath(path);
        var dir = absPath.substring(0, absPath.lastIndexOf('/'));
        await shellMkdirs(dir);
        var ok = await shellWriteFile(absPath, newContent);

        if (ok) {
            trackFile(conv, path, newContent);
            showStatusToast('File saved: ' + path, 'success');
            termPrint('[OK] Saved: ' + path + ' (' + newContent.length + ' bytes)', 'success');
            $('#file-viewer-content').textContent = newContent;
            toggleFileEdit();
            renderFileTree();
        } else {
            showStatusToast('Failed to save file', 'err');
        }
    }

    async function ensureBuildTools() {
        var check = await shellExec('command -v aapt2 >/dev/null 2>&1 && command -v ecj >/dev/null 2>&1 && (command -v d8 >/dev/null 2>&1 || command -v dx >/dev/null 2>&1)', termState.homeDir, false);
        if (check.exitCode === 0) {
            termState.devToolsInstalled = true;
            return true;
        }

        var javaCheck = await shellExec('test -f "$TOOLS/jars/ecj.jar" && test -f "$TOOLS/jars/d8.jar" && test -f "$TOOLS/jars/apksigner.jar"', termState.homeDir, false);
        if (javaCheck.exitCode === 0 && termState.nativeLibDir) {
            var aapt2Check = await shellExec('test -f "$TOOLS/bin/aapt2"', termState.homeDir, false);
            if (aapt2Check.exitCode === 0) {
                termState.devToolsInstalled = true;
                termState.javaToolsInstalled = true;
                return true;
            }
        }

        if (Shell) {
            var env = await Shell.getEnv();
            termState.homeDir = env.HOME;
            termState.toolsDir = env.TOOLS;
            termState.projectsDir = env.PROJECTS;
            termState.cwd = env.CWD || env.HOME;
            termState.hasProot = env.hasProot === true;
            termState.prootPath = env.prootPath || '';
            termState.nativeLibDir = env.nativeLibDir || '';
        }

        termPrint('[*] Setting up Java build tools (app_process virtual JVM)...', 'info');
        showStatusToast('Installing build tools...', 'info');

        var javaOk = await setupJavaTools();
        if (javaOk) return true;

        var prefix = termState.homeDir ? termState.homeDir.replace('/home', '') : '';
        var prefixUsr = prefix + '/usr';
        try { await Bootstrap.fixPermissions(); } catch(e) {}

        termPrint('[*] Trying Termux pkg install...', 'info');
        var pkgOk = await tryPkgInstall(prefixUsr);
        if (pkgOk) return true;

        if (termState.prootPath) {
            termPrint('[*] Trying PRoot...', 'info');
            var prootOk = await tryProotExec(termState.prootPath, prefixUsr, prefixUsr + '/bin/pkg', prefixUsr + '/bin/apt');
            if (prootOk) return true;
        }

        termPrint('[*] Checking for Termux...', 'info');
        var termuxOk = await tryTermuxInstall();
        if (termuxOk) return true;

        termPrint('[!] All strategies failed. Install Termux from F-Droid:', 'err');
        termPrint('    https://f-droid.org/en/packages/com.termux/', 'warning');
        termPrint('    Then: pkg update && pkg install aapt2 ecj dx apksigner', 'warning');
        termState.devToolsInstalled = false;
        return false;
    }

    async function setupJavaTools() {
        if (!Shell || !Bootstrap) return false;

        var toolsDir = termState.toolsDir;
        var jarsDir = toolsDir + '/jars';
        var binDir = toolsDir + '/bin';
        var nativeLibDir = termState.nativeLibDir;

        await shellExec('mkdir -p "' + jarsDir + '" "' + binDir + '"', termState.homeDir, false);

        termPrint('[*] Extracting bundled JARs from APK assets...', 'info');
        try {
            var extractResult = await Bootstrap.extractAsset({src: 'jars/ecj.jar', dest: jarsDir + '/ecj.jar'});
            termPrint('[OK] ecj.jar extracted (' + Math.round(extractResult.size/1024) + ' KB)', 'success');
        } catch(e) {
            termPrint('[!] ecj.jar extract failed: ' + e.message, 'err');
            return false;
        }

        try {
            var extractResult = await Bootstrap.extractAsset({src: 'jars/apksigner.jar', dest: jarsDir + '/apksigner.jar'});
            termPrint('[OK] apksigner.jar extracted (' + Math.round(extractResult.size/1024) + ' KB)', 'success');
        } catch(e) {
            termPrint('[!] apksigner.jar extract failed: ' + e.message, 'err');
            return false;
        }

        var d8Test = await shellExec('test -f "' + jarsDir + '/d8.jar"', termState.homeDir, false);
        if (d8Test.exitCode !== 0) {
            termPrint('[*] Extracting d8.jar from APK assets...', 'info');
            try {
                var d8Extract = await Bootstrap.extractAsset({src: 'jars/d8.jar', dest: jarsDir + '/d8.jar'});
                termPrint('[OK] d8.jar extracted (' + Math.round(d8Extract.size/1024/1024) + ' MB)', 'success');
            } catch(e) {
                termPrint('[!] d8.jar extract failed: ' + e.message, 'warning');
                termPrint('[*] Downloading d8.jar (~18MB)...', 'info');
                try {
                    await Bootstrap.downloadFile({url: 'https://dl.google.com/android/repository/build-tools_r36-linux.zip', dest: toolsDir + '/build-tools.zip'});
                    await shellExec('cd "' + toolsDir + '" && unzip -o build-tools.zip "*/lib/d8.jar" 2>&1 && mv */lib/d8.jar jars/d8.jar && rm -rf build-tools.zip android-*', termState.homeDir, false);
                } catch(e2) {
                    termPrint('[!] d8.jar download also failed: ' + e2.message, 'err');
                }
            }
        }

        var d8Check = await shellExec('test -f "' + jarsDir + '/d8.jar" && test -s "' + jarsDir + '/d8.jar"', termState.homeDir, false);
        if (d8Check.exitCode !== 0) {
            termPrint('[!] d8.jar not available', 'err');
            return false;
        }
        termPrint('[OK] d8.jar ready', 'success');

        var aapt2Check = await shellExec('test -f "' + binDir + '/aapt2"', termState.homeDir, false);
        if (aapt2Check.exitCode !== 0) {
            termPrint('[*] Installing aapt2 from Termux repo...', 'info');
            try {
                var aapt2Result = await Bootstrap.installAapt2({toolsDir: toolsDir});
                termPrint('[OK] aapt2 installed (' + Math.round(aapt2Result.size/1024) + ' KB)', 'success');
            } catch(e) {
                termPrint('[!] aapt2 install failed: ' + e.message, 'warning');
                termPrint('[*] Build will work for resource-less APKs only', 'info');
            }
        }

        termPrint('[*] Creating app_process wrapper scripts...', 'info');
        var wrappers = {
            ecj: '#!/system/bin/sh\nexec /system/bin/app_process /system/bin --nice-name=zaichat -Djava.class.path=' + jarsDir + '/ecj.jar org.eclipse.jdt.internal.compiler.batch.Main "$@"',
            d8: '#!/system/bin/sh\nexec /system/bin/app_process /system/bin --nice-name=zaichat -Djava.class.path=' + jarsDir + '/d8.jar com.android.tools.r8.D8 "$@"',
            apksigner: '#!/system/bin/sh\nexec /system/bin/app_process /system/bin --nice-name=zaichat -jar ' + jarsDir + '/apksigner.jar "$@"'
        };

        for (var name in wrappers) {
            try {
                await Shell.writeFile({path: binDir + '/' + name, content: wrappers[name]});
                await shellExec('chmod 755 "' + binDir + '/' + name + '"', termState.homeDir, false);
            } catch(e) {
                termPrint('[!] Failed to create ' + name + ' wrapper: ' + e.message, 'err');
            }
        }
        termPrint('[OK] Wrapper scripts created (ecj, d8, apksigner)', 'success');

        var verify = await shellExec('test -f "' + jarsDir + '/ecj.jar" && test -f "' + jarsDir + '/d8.jar" && test -f "' + jarsDir + '/apksigner.jar" && test -x "' + binDir + '/ecj" && test -x "' + binDir + '/aapt2"', termState.homeDir, false);
        if (verify.exitCode === 0) {
            termPrint('[OK] Java build environment ready!', 'success');
            showStatusToast('Build tools ready!', 'success');
            termState.devToolsInstalled = true;
            termState.javaToolsInstalled = true;
            return true;
        }

        termPrint('[OK] Java tools ready (aapt2 optional)', 'success');
        termState.devToolsInstalled = true;
        termState.javaToolsInstalled = true;
        return true;
    }

    async function tryPkgInstall(prefixUsr) {
        var pkgBin = prefixUsr + '/bin/pkg';
        var pkgTest = await shellExec('test -f "' + pkgBin + '"', termState.homeDir, false);
        if (pkgTest.exitCode !== 0) return false;

        var cmd = 'export PREFIX="' + prefixUsr + '" LD_LIBRARY_PATH="' + prefixUsr + '/lib" PATH="' + prefixUsr + '/bin:/system/bin:$PATH" && sh "' + pkgBin + '" update -y 2>&1 && sh "' + pkgBin + '" install -y aapt2 ecj dx apksigner 2>&1';
        var result = await shellExec(cmd, termState.homeDir, false);
        if (result.output && result.output.length > 200) {
            termPrint(result.output.substring(result.output.length - 200), '');
        }
        return await toolsReady();
    }

    async function toolsReady() {
        var termuxCheck = await shellExec('command -v aapt2 >/dev/null 2>&1 && command -v ecj >/dev/null 2>&1', termState.homeDir, false);
        if (termuxCheck.exitCode === 0) {
            showStatusToast('Build tools installed!', 'success');
            termState.devToolsInstalled = true;
            return true;
        }
        if (termState.toolsDir) {
            var javaCheck = await shellExec('test -f "' + termState.toolsDir + '/jars/ecj.jar" && test -f "' + termState.toolsDir + '/jars/d8.jar"', termState.homeDir, false);
            if (javaCheck.exitCode === 0) {
                termState.devToolsInstalled = true;
                termState.javaToolsInstalled = true;
                return true;
            }
        }
        return false;
    }

    async function tryProotExec(prootCmd, prefixUsr, pkgBin, aptBin) {
        var hasPkg = await shellExec('test -f "' + pkgBin + '"', termState.homeDir, false);
        var pkgCmd = 'sh /usr/bin/pkg update -y 2>&1 && sh /usr/bin/pkg install -y aapt2 ecj dx apksigner 2>&1';
        if (hasPkg.exitCode !== 0) pkgCmd = 'sh /usr/bin/apt update -y 2>&1 && sh /usr/bin/apt install -y aapt2 ecj dx apksigner 2>&1';
        var wrappedCmd = prootCmd + ' -0 -b /dev -b /proc -b /sys -r ' + prefixUsr + ' /bin/sh -c \'' + pkgCmd.replace(/'/g, "'\\''") + '\'';

        termPrint('[*] Running pkg via PRoot...', 'info');
        var result = await shellExec(wrappedCmd, termState.homeDir, false);
        if (result.output) {
            var out = result.output;
            if (out.length > 2000) out = out.substring(0, 1000) + '\n... truncated ...\n' + out.substring(out.length - 800);
            termPrint(out.replace(/\n$/, ''), '');
        }
        if (result.exitCode === 0 || result.output.indexOf('Setting up') !== -1) {
            if (await toolsReady()) return true;
        }
        termPrint('[!] PRoot strategy failed (exit ' + result.exitCode + ')', 'err');
        return false;
    }

    async function tryTermuxInstall() {
        if (!Bootstrap) return false;
        var termuxInfo;
        try { termuxInfo = await Bootstrap.isTermuxInstalled(); } catch(e) { return false; }

        if (!termuxInfo.installed) {
            termPrint('[!] Termux not installed. Opening F-Droid...', 'warning');
            try { await Bootstrap.openTermuxPage(); } catch(e) {}
            termPrint('[*] After installing Termux:', 'info');
            termPrint('    1. Open Termux app', 'info');
            termPrint('    2. Run: pkg update && pkg install aapt2 ecj dx apksigner', 'info');
            termPrint('    3. Come back to Z.AI Chat', 'info');
            return false;
        }

        termPrint('[OK] Termux detected! Sending install command...', 'success');
        try {
            await Bootstrap.runInTermux({command: 'pkg update -y && pkg install -y aapt2 ecj dx apksigner'});
            termPrint('[*] Command sent to Termux. Waiting...', 'info');
            await new Promise(function(r) { setTimeout(r, 15000); });
            if (await toolsReady()) return true;
            await new Promise(function(r) { setTimeout(r, 30000); });
            if (await toolsReady()) return true;
        } catch(e) {
            termPrint('[!] Termux RUN_COMMAND failed: ' + e.message, 'err');
        }
        return false;
    }

    async function checkAutoGLMStatus() {
        var AutoGLM = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AutoGLM;
        var statusEl = $('#autoglm-status');
        var btn = $('#autoglm-enable-btn');
        if (!AutoGLM || !statusEl) return;
        try {
            var result = await AutoGLM.isEnabled();
            if (result.enabled) {
                statusEl.innerHTML = '<span style="color:var(--success)">Device control enabled</span>';
                if (btn) { btn.textContent = 'Device Control Active'; btn.disabled = true; }
            } else {
                statusEl.innerHTML = '<span style="color:var(--warning)">Not enabled — tap to open Settings</span>';
            }
        } catch(e) {
            statusEl.innerHTML = '<span style="color:var(--text-secondary)">Checking status...</span>';
        }
    }

    async function checkDevEnvironment() {
        if (state.currentMode !== 'coding' && state.currentMode !== 'agentic') return;

        if (!Bootstrap) return;
        try {
            var bsStatus = await Bootstrap.getStatus();
            if (!bsStatus.installed) {
                showDevToolsBanner('Termux not installed. Tap Dev Setup to install Linux environment + build tools.');
                return;
            }
        } catch(e) {}

        if (termState.devToolsInstalled) return;

        var termuxCheck = await shellExec('command -v aapt2 >/dev/null 2>&1 && command -v ecj >/dev/null 2>&1', termState.homeDir, false);
        if (termuxCheck.exitCode === 0) {
            termState.devToolsInstalled = true;
            return;
        }

        if (termState.toolsDir) {
            var javaCheck = await shellExec('test -f "' + termState.toolsDir + '/jars/ecj.jar" && test -f "' + termState.toolsDir + '/jars/d8.jar"', termState.homeDir, false);
            if (javaCheck.exitCode === 0) {
                termState.devToolsInstalled = true;
                termState.javaToolsInstalled = true;
                return;
            }
        }

        showDevToolsBanner('Build tools not installed. Tap to auto-install via Java virtual environment.');
    }

    function showDevToolsBanner(msg) {
        var existing = $('#dev-tools-banner');
        if (existing) existing.remove();

        var container = $('#messages');
        if (!container) return;

        var banner = document.createElement('div');
        banner.id = 'dev-tools-banner';
        banner.className = 'dev-tools-banner';
        banner.innerHTML = '<span class="dtb-icon">&#9888;</span> ' +
            '<span class="dtb-msg">' + msg + '</span>' +
            '<button class="dtb-install-btn">Install</button>' +
            '<button class="dtb-dismiss-btn">&times;</button>';

        container.insertBefore(banner, container.firstChild);

        banner.querySelector('.dtb-install-btn').addEventListener('click', async function() {
            var btn = this;
            btn.textContent = 'Installing...';
            btn.disabled = true;

            var bsStatus;
            try { bsStatus = await Bootstrap.getStatus(); } catch(e) { bsStatus = { installed: false }; }

            if (!bsStatus.installed) {
                try {
                    await Bootstrap.install();
                    try { await Bootstrap.fixPermissions(); } catch(e) {}
                    if (Shell) {
                        var env = await Shell.getEnv();
                        termState.homeDir = env.HOME;
                        termState.toolsDir = env.TOOLS;
                        termState.projectsDir = env.PROJECTS;
                        termState.cwd = env.CWD || env.HOME;
                    }
                    updateCwdDisplay();
                    await shellExec('echo shell-ok', termState.homeDir, false);
                } catch(e) {
                    btn.textContent = 'Bootstrap failed: ' + e.message;
                    return;
                }
            }

            var ok = await ensureBuildTools();
            if (ok) {
                banner.remove();
                showStatusToast('All tools installed!', 'success');
            } else {
                btn.textContent = 'Retry';
                btn.disabled = false;
            }
        });

        banner.querySelector('.dtb-dismiss-btn').addEventListener('click', function() {
            banner.remove();
        });
    }

    async function autoExecuteActions(actions, conv) {
        var hasFiles = actions.some(function(a) { return a.type === 'create_file'; });
        var hasBuild = actions.some(function(a) { return a.type === 'build_apk'; });
        var hasInstall = actions.some(function(a) { return a.type === 'install_apk'; });
        var hasCommands = actions.some(function(a) { return a.type === 'run_command'; });
        var hasDevice = actions.some(function(a) { return a.type && a.type.indexOf('device_') === 0; });
        var hasHermes = actions.some(function(a) { return a.type && a.type.indexOf('hermes_') === 0; });
        var hasVenv = actions.some(function(a) { return a.type && a.type.indexOf('venv_') === 0; });
        var hasSsh = actions.some(function(a) { return a.type && a.type.indexOf('ssh_') === 0; });
        var hasRemote = actions.some(function(a) { return a.type === 'remote_exec' || a.type === 'curl_exec'; });

        if (!hasFiles && !hasBuild && !hasInstall && !hasCommands && !hasDevice && !hasHermes && !hasVenv && !hasSsh && !hasRemote) return;
        _agenticRetryCount = 0;
        var resultLog = [];

        var fileCount = actions.filter(function(a) { return a.type === 'create_file'; }).length;
        if (fileCount > 0) {
            showStatusToast('Deploying ' + fileCount + ' file' + (fileCount > 1 ? 's' : '') + '...', 'info');
            for (var i = 0; i < actions.length; i++) {
                if (actions[i].type === 'create_file') {
                    var deployOk = await autoDeployFile(actions[i]);
                    resultLog.push(deployOk);
                }
            }
            showStatusToast(fileCount + ' file' + (fileCount > 1 ? 's' : '') + ' deployed', 'success');
        }

        if (hasCommands) {
            for (var c = 0; c < actions.length; c++) {
                if (actions[c].type === 'run_command') {
                    var cmd = actions[c].command;
                    showStatusToast('Running: ' + cmd.substring(0, 40), 'info');
                    var cmdApproved = true;
                    if (needsApprovalForCommand(cmd)) {
                        cmdApproved = await askApproval('Run command?', cmd, function() { return true; });
                    }
                    if (!cmdApproved) {
                        termPrint('[DENIED] ' + cmd, 'warning');
                        resultLog.push('DENIED: ' + cmd.substring(0, 60));
                        continue;
                    }
                    var cmdResult = await shellExec(cmd, termState.cwd, false);
                    resultLog.push('CMD: ' + cmd.substring(0, 60) + '\nexit: ' + (cmdResult.exitCode !== undefined ? cmdResult.exitCode : '?') + '\n' + (cmdResult.output || '').substring(0, 500));
                    if (cmdResult.output) {
                        termPrint('$ ' + cmd, 'cmd');
                        termPrint(cmdResult.output.replace(/\n$/, ''), cmdResult.exitCode === 0 ? '' : 'err');
                    }
                }
            }
        }

        if (hasSsh) {
            for (var s = 0; s < actions.length; s++) {
                var sAct = actions[s];
                if (sAct.type.indexOf('ssh_') !== 0) continue;
                var sshCmd = '';
                var sshLabel = '';
                if (sAct.type === 'ssh_exec') {
                    sshCmd = 'ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 ' + sAct.host + ' ' + sAct.command;
                    sshLabel = 'SSH: ' + sAct.host + ' → ' + sAct.command.substring(0, 60);
                } else if (sAct.type === 'ssh_upload') {
                    sshCmd = 'scp -o StrictHostKeyChecking=no -o ConnectTimeout=10 ' + sAct.localPath + ' ' + sAct.remotePath;
                    sshLabel = 'SCP upload: ' + sAct.localPath + ' → ' + sAct.remotePath;
                } else if (sAct.type === 'ssh_download') {
                    sshCmd = 'scp -o StrictHostKeyChecking=no -o ConnectTimeout=10 ' + sAct.remotePath + ' ' + sAct.localPath;
                    sshLabel = 'SCP download: ' + sAct.remotePath + ' → ' + sAct.localPath;
                }
                var sshOk = await askApproval(sshLabel, sshCmd, function() { return true; });
                if (!sshOk) {
                    termPrint('[DENIED] ' + sshLabel, 'warning');
                    resultLog.push('DENIED: ' + sshLabel);
                    continue;
                }
                termPrint('$ ' + sshCmd, 'cmd');
                var sshResult = await shellExec(sshCmd, termState.cwd, false);
                resultLog.push('SSH: ' + sshLabel + '\nexit: ' + (sshResult.exitCode !== undefined ? sshResult.exitCode : '?') + '\n' + (sshResult.output || '').substring(0, 500));
                termPrint((sshResult.output || '').replace(/\n$/, ''), sshResult.exitCode === 0 ? '' : 'err');
                if (sshResult.exitCode !== 0 && sshResult.exitCode !== undefined) {
                    termPrint('[SSH exit: ' + sshResult.exitCode + ']', 'err');
                }
            }
        }

        if (hasRemote) {
            for (var r = 0; r < actions.length; r++) {
                var rAct = actions[r];
                if (rAct.type === 'remote_exec') {
                    var reCmd = 'ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 ' + rAct.host + ' ' + rAct.command;
                    var reOk = await askApproval('Remote exec: ' + rAct.host, reCmd, function() { return true; });
                    if (!reOk) { termPrint('[DENIED] ' + rAct.host, 'warning'); continue; }
                    termPrint('$ ' + reCmd, 'cmd');
                    var reResult = await shellExec(reCmd, termState.cwd, false);
                    resultLog.push('REMOTE: ' + rAct.host + '\n' + (reResult.output || '').substring(0, 500));
                    termPrint((reResult.output || '').replace(/\n$/, ''), reResult.exitCode === 0 ? '' : 'err');
                } else if (rAct.type === 'curl_exec') {
                    var curlCmd = 'curl -sL -o - ' + rAct.url;
                    var curlOk = await askApproval('Fetch URL?', rAct.url, function() { return true; });
                    if (!curlOk) { termPrint('[DENIED] curl ' + rAct.url, 'warning'); continue; }
                    termPrint('$ ' + curlCmd, 'cmd');
                    var curlResult = await shellExec(curlCmd, termState.cwd, false);
                    resultLog.push('CURL: ' + rAct.url + '\n' + (curlResult.output || '').substring(0, 500));
                    termPrint((curlResult.output || '').replace(/\n$/, ''), '');
                }
            }
        }

        if (hasDevice) {
            for (var d = 0; d < actions.length; d++) {
                var act = actions[d];
                if (act.type.indexOf('device_') !== 0) continue;
                try {
                    var devResult = await executeDeviceAction(act);
                    resultLog.push(devResult);
                    termPrint(devResult, '');
                } catch(e) {
                    resultLog.push('DEVICE_ERROR: ' + e.message);
                    termPrint('[!] Device: ' + e.message, 'err');
                }
            }
        }

        if (hasHermes) {
            for (var h = 0; h < actions.length; h++) {
                var hAct = actions[h];
                if (hAct.type.indexOf('hermes_') !== 0) continue;
                try {
                    var hermesResult = await executeHermesAction(hAct);
                    resultLog.push(hermesResult);
                    termPrint(hermesResult, '');
                } catch(e) {
                    resultLog.push('HERMES_ERROR: ' + e.message);
                    termPrint('[!] Hermes: ' + e.message, 'err');
                }
            }
        }

        if (hasVenv) {
            for (var v = 0; v < actions.length; v++) {
                var vAct = actions[v];
                if (vAct.type.indexOf('venv_') !== 0) continue;
                try {
                    var venvResult = await executeVirtualEnvAction(vAct);
                    resultLog.push(venvResult);
                    termPrint(venvResult, '');
                } catch(e) {
                    resultLog.push('VENV_ERROR: ' + e.message);
                    termPrint('[!] Venv: ' + e.message, 'err');
                }
            }
        }

        if (hasBuild) {
            showStatusToast('Building APK...', 'info');
            var toolsReady = await ensureBuildTools();
            if (!toolsReady) {
                var buildFail = '[BUILD FAILED] Build tools not available. Install: pkg install aapt2 ecj dx apksigner';
                resultLog.push(buildFail);
                await agenticRetryOnError(buildFail, conv);
                return;
            }
            var buildResult = await autoBuildApk(actions);
            resultLog.push(buildResult);
            if (buildResult.indexOf('[BUILD FAILED]') >= 0) {
                await agenticRetryOnError(buildResult, conv);
                return;
            }
        }

        if (hasInstall) {
            for (var j = 0; j < actions.length; j++) {
                if (actions[j].type === 'install_apk') {
                    showStatusToast('Installing APK...', 'info');
                    await installApk(actions[j].path);
                    resultLog.push('INSTALL: ' + actions[j].path);
                }
            }
        }
    }

    async function executeDeviceAction(action) {
        var AutoGLM = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AutoGLM;
        if (!AutoGLM) throw new Error('AutoGLM plugin not available');

        switch (action.type) {
            case 'device_tap':
                await AutoGLM.tap({ x: action.x, y: action.y });
                return '[DEVICE] Tap(' + action.x + ', ' + action.y + ')';
            case 'device_long_press':
                await AutoGLM.longPress({ x: action.x, y: action.y });
                return '[DEVICE] LongPress(' + action.x + ', ' + action.y + ')';
            case 'device_swipe':
                await AutoGLM.swipe({ startX: action.startX, startY: action.startY, endX: action.endX, endY: action.endY });
                return '[DEVICE] Swipe(' + action.startX + ',' + action.startY + ' -> ' + action.endX + ',' + action.endY + ')';
            case 'device_type':
                await AutoGLM.typeText({ text: action.text });
                return '[DEVICE] Type: "' + action.text + '"';
            case 'device_press_back':
                await AutoGLM.pressBack();
                return '[DEVICE] Press Back';
            case 'device_press_home':
                await AutoGLM.pressHome();
                return '[DEVICE] Press Home';
            case 'device_press_recents':
                await AutoGLM.pressRecents();
                return '[DEVICE] Press Recents';
            case 'device_screenshot':
                var ssResult = await AutoGLM.takeScreenshot({});
                return '[DEVICE] Screenshot: ' + (ssResult.path || 'saved');
            case 'device_ui_tree':
                var treeResult = await AutoGLM.getUITree({});
                var tree = treeResult.tree || '{}';
                return '[DEVICE] UI Tree: ' + tree.substring(0, 2000);
            case 'device_click_text':
                var clickResult = await AutoGLM.clickByText({ text: action.text });
                return '[DEVICE] Click "' + action.text + '": ' + (clickResult.ok ? 'OK' : 'not found');
            case 'device_click_id':
                var idResult = await AutoGLM.clickNode({ viewId: action.viewId });
                return '[DEVICE] Click ID "' + action.viewId + '": ' + (idResult.ok ? 'OK' : 'not found');
            case 'device_launch':
                await AutoGLM.launchApp({ package: action.pkg });
                return '[DEVICE] Launch: ' + action.pkg;
            case 'device_current_app':
                var appResult = await AutoGLM.getCurrentApp({});
                return '[DEVICE] Current app: ' + (appResult.package || 'unknown');
            default:
                return '[DEVICE] Unknown action: ' + action.type;
        }
    }

    async function executeHermesAction(action) {
        var Bootstrap = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Bootstrap;
        if (!Bootstrap) throw new Error('Bootstrap plugin not available');

        switch (action.type) {
            case 'hermes_install':
                showStatusToast('Installing Hermes agent...', 'info');
                var installResult = await Bootstrap.installHermes({});
                termState.hermesPath = installResult.path;
                termState.hermesVenv = installResult.venv;
                return '[HERMES] Installed: ' + installResult.path;
            case 'hermes_exec':
                if (!termState.hermesVenv) {
                    try {
                        var status = await Bootstrap.installHermes({});
                        termState.hermesPath = status.path;
                        termState.hermesVenv = status.venv;
                    } catch(e) {
                        throw new Error('Hermes not installed: ' + e.message);
                    }
                }
                showStatusToast('Hermes: ' + action.command.substring(0, 30), 'info');
                var execResult = await Bootstrap.hermesExec({ command: action.command, venv: termState.hermesVenv });
                var hermesOutput = execResult.output || '';
                if (hermesOutput.length > 3000) hermesOutput = hermesOutput.substring(0, 1500) + '\n...truncated...\n' + hermesOutput.substring(hermesOutput.length - 1000);
                return '[HERMES] ' + action.command + '\n' + hermesOutput;
            default:
                return '[HERMES] Unknown action: ' + action.type;
        }
    }

    async function executeVirtualEnvAction(action) {
        var bsPlugin = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Bootstrap;
        if (!bsPlugin) throw new Error('Bootstrap plugin not available');
        var venvPath = termState.homeDir ? (termState.homeDir.replace(/\/home$/, '') + '/venv/default') : '';

        switch (action.type) {
            case 'venv_setup': {
                showStatusToast('Setting up in-app virtual environment...', 'info');
                var setup = await bsPlugin.setupVirtualEnv({ venv: venvPath });
                termState.venvPath = setup.venv;
                return '[VENV] Ready: ' + setup.venv;
            }
            case 'venv_pip_install': {
                if (!termState.venvPath) {
                    var init = await bsPlugin.setupVirtualEnv({ venv: venvPath });
                    termState.venvPath = init.venv;
                }
                showStatusToast('Installing module(s): ' + action.packages, 'info');
                var installed = await bsPlugin.venvPipInstall({ venv: termState.venvPath, packages: action.packages });
                return '[VENV] pip install ' + action.packages + '\n' + (installed.output || '').substring(0, 1200);
            }
            default:
                return '[VENV] Unknown action: ' + action.type;
        }
    }

    async function autoDeployFile(action) {
        var path = action.path;
        if (!path.startsWith('/')) {
            path = (termState.projectsDir || termState.homeDir + '/projects') + '/' + path;
        }
        var dir = path.substring(0, path.lastIndexOf('/'));
        await shellMkdirs(dir);
        var ok = await shellWriteFile(path, action.content);
        if (ok) {
            return '[OK] ' + path + ' (' + action.content.length + ' bytes)';
        } else {
            return '[FAIL] ' + path;
        }
    }

    async function autoBuildApk(actions) {
        var projectDir = termState.projectsDir || (termState.homeDir + '/projects');

        for (var i = 0; i < actions.length; i++) {
            if (actions[i].type === 'create_file') {
                await autoDeployFile(actions[i]);
            }
        }

        var toolsDir = termState.toolsDir || (termState.homeDir + '/tools');
        var buildScriptPath = toolsDir + '/build.sh';
        await shellMkdirs(toolsDir);
        await shellWriteFile(buildScriptPath, BUILD_SCRIPT);
        await shellExec('chmod +x ' + buildScriptPath, termState.homeDir, false);

        termPrint('\n--- Building APK ---', 'info');
        var result = await shellExec('cd ' + projectDir + ' && sh ' + buildScriptPath, termState.homeDir, false);
        var output = result.output || '';
        termPrint(output.replace(/\n$/, ''), result.exitCode === 0 ? '' : 'err');

        if (output.indexOf('[BUILD OK]') >= 0) {
            var apkPath = projectDir + '/build/app-signed.apk';
            var verifyResult = await shellExec('ls -la ' + apkPath + ' 2>&1', termState.homeDir, false);
            if (verifyResult.output && verifyResult.output.indexOf('No such file') < 0) {
                var sizeMatch = verifyResult.output.match(/(\d+)\s+/);
                var sizeInfo = sizeMatch ? ' (' + Math.round(parseInt(sizeMatch[1]) / 1024) + ' KB)' : '';
                var builtName = projectDir.split('/').pop() + '.apk';
                var Installer = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Installer;
                var exportedPath = apkPath;
                try {
                    if (Installer) {
                        var exportResult = await Installer.exportApk({ path: apkPath, name: builtName });
                        exportedPath = exportResult.path || apkPath;
                        state.lastBuiltApkPath = exportedPath;
                    }
                } catch(e) {
                    termPrint('[!] Export to real folder failed: ' + e.message, 'warning');
                }
                termPrint('\n[VERIFIED] APK built successfully: ' + exportedPath + sizeInfo, 'success');
                if (state.autoInstallBuiltApk && Installer) {
                    try {
                        await Installer.installApk({ path: exportedPath });
                        termPrint('[OK] Installer opened for built APK', 'success');
                    } catch(e) {
                        termPrint('[!] Auto-install failed: ' + e.message, 'warning');
                    }
                }
                return '[BUILD OK] ' + exportedPath;
            } else {
                termPrint('\n[VERIFY FAILED] Build claimed success but APK not found at ' + apkPath, 'err');
                return '[BUILD FAILED] APK file not found after build. Output:\n' + output.substring(0, 1000);
            }
        } else if (output.indexOf('[BUILD FAILED]') >= 0) {
            return '[BUILD FAILED] ' + output;
        } else if (result.exitCode !== 0) {
            return '[BUILD FAILED] exit=' + result.exitCode + '\n' + output.substring(0, 4000);
        }
        return output.substring(0, 500);
    }

    async function agenticRetryOnError(errorOutput, conv) {
        _agenticRetryCount++;
        if (_agenticRetryCount > getMaxRetries()) {
            termPrint('\n[!] Max retries reached (' + getMaxRetries() + '). Fix manually or ask again.', 'err');
            showStatusToast('Build failed after ' + getMaxRetries() + ' retries', 'err');
            return;
        }

        termPrint('\n[!] Build failed. Asking AI to fix (attempt ' + _agenticRetryCount + '/' + getMaxRetries() + ')...', 'warning');
        showStatusToast('Build failed — AI auto-fixing (attempt ' + _agenticRetryCount + ')...', 'err');

        if (!conv || !state.apiKey) return;

        var fixMessage = 'The build failed with this error:\n\n```\n' +
            errorOutput.substring(0, 4000) +
            '\n```\n\nPlease fix the code and rebuild. Write ALL corrected files and use [BUILD_APK] again.';

        if (state.keepAwake) setWakeLock(true);

        try {
            var conv2 = getConversation(conv.id) || conv;
            conv2.messages.push({ role: 'user', content: fixMessage });
            saveState();
            appendMessage('user', fixMessage);

            state.isGenerating = true;
            state.streamingConvId = conv2.id;
            state.streamingContent = '';
            updateSendButton();
            showThinking();

            var systemPrompt = MODE_PROMPTS.agentic;
            var apiMessages = [{ role: 'system', content: systemPrompt }];
            conv2.messages.forEach(function(m) {
                if (m.role === 'user' || (m.role === 'assistant' && !m._streaming)) {
                    apiMessages.push({ role: m.role, content: m.content });
                }
            });

            var requestBody = {
                model: state.model,
                messages: apiMessages,
                temperature: state.temperature,
                max_tokens: state.maxTokens,
                stream: state.streaming
            };

            removeThinking();
            var responseDiv = appendMessage('assistant', '');
            state.streamingResponseDiv = responseDiv;

            if (state.streaming) {
                await streamResponseWithRetry(requestBody, responseDiv, conv2);
            } else {
                var res = await apiRequestWithRetry(requestBody);
                updateStreamingMessage(responseDiv, res.choices[0].message.content);
                state.streamingContent = res.choices[0].message.content;
                conv2.messages.push({ role: 'assistant', content: res.choices[0].message.content });
            }

            var fixContent = state.streamingContent || '';
            if (fixContent) {
                conv2.messages.push({ role: 'assistant', content: fixContent });
            }
        } catch(err) {
            termPrint('[!] Auto-fix failed: ' + err.message, 'err');
        } finally {
            state.isGenerating = false;
            state.abortController = null;
            state.streamingConvId = null;
            state.streamingResponseDiv = null;
            updateSendButton();
            saveState();
            if (state.keepAwake) setWakeLock(false);

            var fixActions = parseAiActions(state.streamingContent || '');
            if (fixActions.length > 0 && _agenticRetryCount <= getMaxRetries()) {
                await autoExecuteActions(fixActions, conv);
            }
        }
    }

    async function buildFromActions(actions) {
        showScreen('terminal');
        termPrint('\n--- Building APK ---', 'info');

        var buildResult = await autoBuildApk(actions);
        termPrint(buildResult, buildResult.indexOf('[BUILD FAILED]') >= 0 ? 'err' : 'success');
        if (state.autoInstallBuiltApk && buildResult.indexOf('[BUILD OK]') >= 0) {
            var Installer = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Installer;
            if (Installer && state.lastBuiltApkPath) {
                await Installer.installApk({ path: state.lastBuiltApkPath });
            }
        }
    }

    // ---- Dev Tools Setup ----

    var DEV_TOOLS = [
        { name: 'bash', url: 'https://github.com/termux/termux-packages/releases/download/bash-v5.2.21/bash-v5.2.21-aarch64.zip', type: 'binary' },
        { name: 'coreutils', url: 'https://github.com/termux/termux-packages/releases/download/coreutils-9.4/coreutils-9.4-aarch64.zip', type: 'binary' }
    ];

    async function checkDevTools() {
        if (!Bootstrap) return false;
        try {
            var status = await Bootstrap.getStatus();
            return status.installed === true;
        } catch(e) { return false; }
    }

    async function setupDevTools() {
        if (!Bootstrap) {
            alert('Bootstrap plugin not available');
            return;
        }

        var btn = $('#devsetup-install-btn');
        var progress = $('#devsetup-progress');
        var progressFill = $('#devsetup-progress-fill');
        var progressText = $('#devsetup-progress-text');
        var statusEl = $('#devsetup-status');

        btn.disabled = true;
        btn.querySelector('.btn-text').textContent = 'Installing...';
        btn.querySelector('.btn-loader').style.display = 'inline-block';
        progress.style.display = 'block';
        progressText.textContent = 'Starting...';

        Bootstrap.addListener('bootstrap-progress', function(event) {
            if (progressFill) progressFill.style.width = event.percent + '%';
            if (progressText) progressText.textContent = event.message;
        });

        try {
            await Bootstrap.install();

            progressText.textContent = 'Fixing file permissions...';
            try { await Bootstrap.fixPermissions(); } catch(e) {}

            if (Shell) {
                var env = await Shell.getEnv();
                termState.homeDir = env.HOME;
                termState.toolsDir = env.TOOLS;
                termState.projectsDir = env.PROJECTS;
                termState.cwd = env.CWD || env.HOME;
            }
            updateCwdDisplay();

            var shellTest = await shellExec('echo OK', termState.homeDir, false);
            if (shellTest.exitCode !== 0) {
                statusEl.innerHTML = '<p style="color:var(--danger)">Shell test failed: ' + (shellTest.output || 'unknown error') + '</p>' +
                    '<p>Try restarting the app.</p>';
                btn.disabled = false;
                btn.querySelector('.btn-text').textContent = 'Retry Install';
                btn.querySelector('.btn-loader').style.display = 'none';
                return;
            }

            statusEl.innerHTML = '<p style="color:var(--success);font-size:16px;font-weight:700">&#10004; Termux environment installed!</p>' +
                '<p id="devsetup-tools-status">Installing build tools (aapt2, ecj, d8, apksigner)...</p>';

            var toolsOk = await ensureBuildTools();
            var toolsStatus = $('#devsetup-tools-status');
            if (toolsStatus) {
                toolsStatus.innerHTML = toolsOk
                    ? '<span style="color:var(--success)">&#10004; All tools installed — ready to build!</span>'
                    : '<span style="color:var(--warning)">Build tools not installed.<br>1. Install Termux from F-Droid<br>2. Run: pkg install aapt2 ecj dx apksigner<br>3. Z.AI Chat will auto-detect Termux tools.</span>';
            }

            btn.querySelector('.btn-text').textContent = 'Installed';
            btn.querySelector('.btn-loader').style.display = 'none';
        } catch(e) {
            statusEl.innerHTML = '<p style="color:var(--danger)">Install failed: ' + e.message + '</p>' +
                '<p>Check your internet connection and try again.</p>';
            btn.disabled = false;
            btn.querySelector('.btn-text').textContent = 'Retry Install';
            btn.querySelector('.btn-loader').style.display = 'none';
        }
    }

    // ---- Init Terminal ----

    function initTerminal() {
        var termInput = $('#term-input');
        var termRunBtn = $('#term-run-btn');
        var termStopBtn = $('#term-stop-btn');
        var termBackBtn = $('#term-back-btn');
        var termSetupBtn = $('#term-setup-tools-btn');
        var devsetupBtn = $('#devsetup-install-btn');
        var devsetupBackBtn = $('#devsetup-back-btn');

        if (termInput) {
            termInput.addEventListener('keydown', function(e) {
                if (e.key === 'Enter') {
                    e.preventDefault();
                    var cmd = termInput.value;
                    termInput.value = '';
                    termExec(cmd);
                } else if (e.key === 'ArrowUp') {
                    e.preventDefault();
                    if (termState.historyIndex > 0) {
                        termState.historyIndex--;
                        termInput.value = termState.history[termState.historyIndex] || '';
                    }
                } else if (e.key === 'ArrowDown') {
                    e.preventDefault();
                    if (termState.historyIndex < termState.history.length - 1) {
                        termState.historyIndex++;
                        termInput.value = termState.history[termState.historyIndex] || '';
                    } else {
                        termState.historyIndex = termState.history.length;
                        termInput.value = '';
                    }
                }
            });
        }

        if (termRunBtn) {
            termRunBtn.addEventListener('click', function() {
                var cmd = termInput.value;
                termInput.value = '';
                termExec(cmd);
            });
        }

        if (termStopBtn) {
            termStopBtn.addEventListener('click', async function() {
                if (Shell && termState.activePid) {
                    try { await Shell.kill({ pid: termState.activePid }); } catch(e) {}
                }
                termState.isRunning = false;
                termState.activePid = null;
                updateTermButtons();
                termPrint('[Process killed]', 'warning');
                if (termInput) { termInput.disabled = false; termInput.focus(); }
            });
        }

        if (termBackBtn) {
            termBackBtn.addEventListener('click', function() {
                showScreen('chat');
            });
        }

        if (termSetupBtn) {
            termSetupBtn.addEventListener('click', function() {
                showScreen('devsetup');
            });
        }

        if (devsetupBtn) {
            devsetupBtn.addEventListener('click', function() {
                setupDevTools();
            });
        }

        if (devsetupBackBtn) {
            devsetupBackBtn.addEventListener('click', function() {
                showScreen('terminal');
            });
        }

        var autoglmBtn = $('#autoglm-enable-btn');
        if (autoglmBtn) {
            checkAutoGLMStatus();
            autoglmBtn.addEventListener('click', function() {
                var AutoGLM = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AutoGLM;
                if (AutoGLM) {
                    AutoGLM.openSettings();
                }
            });
        }

        var hermesBtn = $('#hermes-install-btn');
        if (hermesBtn) {
            hermesBtn.addEventListener('click', async function() {
                var Bootstrap = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Bootstrap;
                if (!Bootstrap) return;
                hermesBtn.disabled = true;
                hermesBtn.textContent = 'Installing Hermes...';
                try {
                    var result = await Bootstrap.installHermes({});
                    hermesBtn.textContent = 'Hermes Installed!';
                    hermesBtn.disabled = true;
                    termState.hermesPath = result.path;
                    termState.hermesVenv = result.venv;
                    termPrint('[OK] Hermes installed: ' + result.path, 'success');
                } catch(e) {
                    hermesBtn.textContent = 'Install Failed - Retry';
                    hermesBtn.disabled = false;
                    termPrint('[!] Hermes install failed: ' + e.message, 'err');
                }
            });
        }

        $$('.term-quick-btn').forEach(function(btn) {
            btn.addEventListener('click', function() {
                var cmd = this.dataset.cmd;
                if (termInput) {
                    termInput.value = cmd;
                    termInput.focus();
                    if (!cmd.endsWith(' ')) {
                        termExec(cmd);
                        termInput.value = '';
                    }
                }
            });
        });

        updateCwdDisplay();

        if (Shell) {
            Shell.getEnv().then(function(env) {
                termState.homeDir = env.HOME;
                termState.toolsDir = env.TOOLS;
                termState.projectsDir = env.PROJECTS;
                termState.cwd = env.CWD || env.HOME;
                updateCwdDisplay();
                termPrint('Z.AI Terminal v3.3.0', 'info');
                termPrint('Home: ' + termState.homeDir, 'info');
                termPrint('Type "help" for commands, "setup" for dev tools\n', 'info');
            }).catch(function() {});
        }
    }

    // ---- Terminal command handler ----

    var origTermExec = termExec;
    termExec = async function(command) {
        if (!command.trim()) return;

        var lower = command.trim().toLowerCase();
        if (lower === 'help') {
            termPrint('$ help', 'cmd');
            termPrint('Z.AI Terminal Commands:', 'info');
            termPrint('  help        - Show this help', '');
            termPrint('  setup       - Open dev tools setup', '');
            termPrint('  sysinfo     - Show device info', '');
            termPrint('  create NAME - Create new Android project', '');
            termPrint('  install APK - Install an APK file', '');
            termPrint('  clear       - Clear terminal', '');
            termPrint('  exit        - Back to chat', '');
            termPrint('', '');
            termPrint('Shell: Any standard Linux command works here.', '');
            termPrint('Tip: Use "setup" to install build tools (aapt2, d8, ecj)\n', '');
            return;
        }
        if (lower === 'setup') {
            showScreen('devsetup');
            return;
        }
        if (lower === 'sysinfo') {
            termPrint('$ sysinfo', 'cmd');
            var info = await getDeviceInfo();
            termPrint('Device: ' + (info.manufacturer || '') + ' ' + (info.model || ''), '');
            termPrint('Android: ' + (info.release || '?') + ' (SDK ' + (info.sdk || '?') + ')', '');
            termPrint('ABI: ' + (info.abi || '?'), '');
            termPrint('Files: ' + (info.filesDir || '?'), '');
            termPrint('Package: ' + (info.package || '?') + '\n', '');
            return;
        }
        if (lower.startsWith('create ')) {
            var name = command.trim().substring(7).trim();
            termPrint('$ create ' + name, 'cmd');
            var projectDir = termState.projectsDir || (termState.homeDir + '/projects');
            await shellExec('sh ' + termState.toolsDir + '/setup.sh', termState.homeDir, false);
            await shellExec('sh ' + termState.toolsDir + '/create-project.sh ' + name, termState.homeDir, false);
            return;
        }
        if (lower.startsWith('install ')) {
            var path = command.trim().substring(8).trim();
            termPrint('$ install ' + path, 'cmd');
            await installApk(path);
            return;
        }
        if (lower === 'clear') {
            var output = $('#term-output');
            if (output) output.innerHTML = '';
            return;
        }
        if (lower === 'exit') {
            showScreen('chat');
            return;
        }

        await origTermExec(command);
    };

    // ---- Rest of init ----

    async function testConnection(apiKey, baseUrl) {
        var url = (baseUrl || state.baseUrl).replace(/\/+$/, '') + '/chat/completions';
        var resp = await fetch(url, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Authorization': 'Bearer ' + apiKey,
                'Accept-Language': 'en-US,en'
            },
            body: JSON.stringify({
                model: state.model,
                messages: [{ role: 'user', content: 'Hi' }],
                max_tokens: 10
            })
        });
        if (!resp.ok) {
            var errData = {};
            try { errData = await resp.json(); } catch(e) {}
            throw new Error(errData.error?.message || 'Connection failed (' + resp.status + ')');
        }
        return await resp.json();
    }

    function populateSettings() {
        $('#settings-token').value = state.apiKey;
        $('#settings-url').value = state.baseUrl;
        $('#settings-model').value = state.model;
        $('#settings-temp').value = state.temperature;
        $('#temp-value').textContent = state.temperature;
        $('#settings-tokens').value = state.maxTokens;
        $('#tokens-value').textContent = state.maxTokens;
        $('#settings-websearch').checked = state.webSearch;
        $('#settings-streaming').checked = state.streaming;
        $('#settings-autodeploy').checked = state.autoDeploy;
        $('#settings-keepawake').checked = state.keepAwake;
        $('#settings-maxretries').value = state.maxRetries;
        $('#retries-value').textContent = state.maxRetries;
        $('#settings-autocontinue').checked = state.autoContinue;
        $('#settings-maxautocontinue').value = state.maxAutoContinue;
        $('#autocont-value').textContent = state.maxAutoContinue;
    }

    function saveSettings() {
        state.apiKey = $('#settings-token').value.trim();
        state.baseUrl = $('#settings-url').value.trim();
        state.model = $('#settings-model').value;
        state.temperature = parseFloat($('#settings-temp').value);
        state.maxTokens = parseInt($('#settings-tokens').value);
        state.webSearch = $('#settings-websearch').checked;
        state.streaming = $('#settings-streaming').checked;
        saveState();
    }

    function exportConversations() {
        var data = JSON.stringify(state.conversations, null, 2);
        var blob = new Blob([data], { type: 'application/json' });
        var url = URL.createObjectURL(blob);
        var a = document.createElement('a');
        a.href = url;
        a.download = 'zai-chat-export-' + new Date().toISOString().slice(0, 10) + '.json';
        a.click();
        URL.revokeObjectURL(url);
    }

    function init() {
        loadState();

        initShellPlugins();

        if (state.apiKey) {
            showScreen('chat');
            if (state.activeConversationId) {
                var conv = getConversation();
                if (conv) {
                    state.currentMode = conv.mode || 'chat';
                }
            }
            renderConversationList();
            renderMessages();
            updateHeader();
            updateModeSelector();
            updateTerminalVisibility();
            $('#api-token').value = state.apiKey;
            $('#base-url').value = state.baseUrl;
        }

        $('#connect-btn').addEventListener('click', async function() {
            var btn = this;
            var apiKey = $('#api-token').value.trim();
            var baseUrl = $('#base-url').value;
            var errorEl = $('#setup-error');

            if (!apiKey) {
                errorEl.textContent = 'Please enter your API key';
                errorEl.style.display = 'block';
                return;
            }

            btn.disabled = true;
            btn.querySelector('.btn-text').textContent = 'Connecting...';
            btn.querySelector('.btn-loader').style.display = 'inline-block';
            errorEl.style.display = 'none';

            try {
                await testConnection(apiKey, baseUrl);
                state.apiKey = apiKey;
                state.baseUrl = baseUrl;
                saveState();
                showScreen('chat');
                newConversation();
            } catch(err) {
                errorEl.textContent = err.message;
                errorEl.style.display = 'block';
            } finally {
                btn.disabled = false;
                btn.querySelector('.btn-text').textContent = 'Connect';
                btn.querySelector('.btn-loader').style.display = 'none';
            }
        });

        $('#api-token').addEventListener('keydown', function(e) {
            if (e.key === 'Enter') $('#connect-btn').click();
        });

        $('#message-input').addEventListener('input', function() {
            autoResize(this);
            updateSendButton();
        });

        $('#message-input').addEventListener('keydown', function(e) {
            if (e.key === 'Enter' && !e.shiftKey) {
                e.preventDefault();
                sendMessage();
            }
        });

        $('#send-btn').addEventListener('click', sendMessage);
        $('#stop-btn').addEventListener('click', stopGeneration);

        $$('.mode-btn').forEach(function(btn) {
            btn.addEventListener('click', function() {
                var mode = this.dataset.mode;
                if (mode === 'terminal') {
                    showScreen('terminal');
                    return;
                }
                state.currentMode = mode;
                updateModeSelector();
                updateHeader();
                updateTerminalVisibility();
                checkDevEnvironment();
                saveState();
            });
        });

        $('#menu-btn').addEventListener('click', openSidebar);
        $('#sidebar-close').addEventListener('click', closeSidebar);
        $('#sidebar-overlay').addEventListener('click', closeSidebar);

        $('#new-chat-btn').addEventListener('click', function() { newConversation(); });
        $('#new-chat-sidebar').addEventListener('click', function() { newConversation(); closeSidebar(); });

        $('#settings-btn').addEventListener('click', function() {
            populateSettings();
            showScreen('settings');
        });

        $('#settings-back').addEventListener('click', function() {
            saveSettings();
            showScreen('chat');
        });

        $('#settings-temp').addEventListener('input', function() {
            $('#temp-value').textContent = this.value;
        });

        $('#settings-tokens').addEventListener('input', function() {
            $('#tokens-value').textContent = this.value;
        });

        $('#settings-token').addEventListener('change', saveSettings);
        $('#settings-url').addEventListener('change', saveSettings);
        $('#settings-model').addEventListener('change', saveSettings);
        $('#settings-websearch').addEventListener('change', saveSettings);
        $('#settings-streaming').addEventListener('change', saveSettings);
        $('#settings-autodeploy').addEventListener('change', function() {
            state.autoDeploy = this.checked;
            saveState();
        });
        $('#settings-keepawake').addEventListener('change', function() {
            state.keepAwake = this.checked;
            saveState();
        });

        $('#settings-maxretries').addEventListener('input', function() {
            $('#retries-value').textContent = this.value;
        });
        $('#settings-maxretries').addEventListener('change', function() {
            state.maxRetries = parseInt(this.value) || 10;
            saveState();
        });

        $('#settings-autocontinue').addEventListener('change', function() {
            state.autoContinue = this.checked;
            saveState();
        });
        $('#settings-maxautocontinue').addEventListener('input', function() {
            $('#autocont-value').textContent = this.value;
        });
        $('#settings-maxautocontinue').addEventListener('change', function() {
            state.maxAutoContinue = parseInt(this.value) || 5;
            saveState();
        });

        $('#file-tree-btn').addEventListener('click', toggleDeviceFileManager);
        $('#device-files-btn').addEventListener('click', function() {
            var panel = $('#file-tree-panel');
            if (panel && panel.classList.contains('open')) {
                showProjectFiles();
            } else {
                toggleDeviceFileManager();
            }
        });
        $('#file-tree-close').addEventListener('click', closeFileTree);
        $('#file-tree-overlay').addEventListener('click', closeFileTree);
        $('#file-tree-body').addEventListener('click', function(e) {
            var node = e.target.closest('.ftree-file');
            if (node) {
                if (node.dataset.conv) {
                    openFileViewer(node.dataset.conv, node.dataset.path);
                } else {
                    var title = document.querySelector('#file-tree-panel h3');
                    if (title && title.textContent === 'File Manager') {
                        renderDeviceFiles(node.dataset.path);
                    } else {
                        openFileFromTree(node.dataset.path);
                    }
                }
            } else {
                node = e.target.closest('.ftree-dir');
                if (node) {
                    var folder = node.parentElement;
                    folder.classList.toggle('open');
                    var arrow = node.querySelector('.ftree-arrow');
                    if (arrow) arrow.innerHTML = folder.classList.contains('open') ? '&#9660;' : '&#9654;';
                }
            }
        });
        $('#file-viewer-close').addEventListener('click', closeFileViewer);
        $('#file-viewer-edit').addEventListener('click', toggleFileEdit);
        $('#file-viewer-save').addEventListener('click', saveFileEdit);
        $('#approval-allow').addEventListener('click', function() {
            if (approvalState && approvalState.onApprove) approvalState.onApprove();
        });
        $('#approval-deny').addEventListener('click', closeApproval);

        $('#theme-toggle-header').addEventListener('click', toggleTheme);

        $('#settings-darkmode').addEventListener('change', function() {
            applyTheme(this.checked ? 'dark' : 'light');
        });

        $('#terminal-toggle').addEventListener('click', toggleTerminal);

        $('#export-btn').addEventListener('click', exportConversations);

        $('#clear-btn').addEventListener('click', function() {
            if (confirm('Clear all conversations? This cannot be undone.')) {
                state.conversations = [];
                state.activeConversationId = null;
                saveState();
                renderConversationList();
                renderMessages();
                updateHeader();
                updateTerminalContent();
            }
        });

        updateModeSelector();
        updateSendButton();
        applyTheme(state.theme);
        setupVisibilityHandler();
        initTerminal();
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();
