main
John Lauer v1.3.3 (insiders): Grok Bot's computer state: computerReachable on readiness, chat and webhook verbs, fast no_transcript on a blank window, no caption-button thread names; Send to AI uses Hydrogen's AI picker; insiders-first publishing. 85caf75 5d ago
"""Layered readiness / prewarm / status / uninstall — host-app probes, zero downloads."""

from __future__ import annotations

from grok_detect import detect_host, HOST_APP

try:
    from handlers import send as send_mod
except Exception:  # pragma: no cover - circular-import guard at import time
    send_mod = None

try:
    from handlers.webhook import load_webhook, url_host
except Exception:
    load_webhook = None
    url_host = None


def handle_readiness(_args: dict) -> dict:
    """Read-only: is Grok Bot present / running?

    Never blocks on a download. Bridge detects host apps; it does not auto-install
    them. If Grok Bot is missing, grok_install installs it — do not send the user to a download page.
    Cursor is last-resort fallback only if Grok Bot.exe is missing.
    """
    info = detect_host()
    installed = bool(info.get("hostAppInstalled"))
    running = bool(info.get("running"))
    fallback = info.get("hostAppFallback")
    ready = installed  # installed is enough; grok_open launches if not running
    if installed and not fallback:
        state = (
            "Grok Bot is installed"
            + (" and running." if running else " but not running — grok_open launches it in the background.")
        )
    elif installed and fallback:
        state = (
            "Grok Bot.exe is missing; Cursor is a last-resort fallback host"
            + (" and is running." if running else " (not running) — grok_open launches Cursor in the background. Prefer Grok Bot.")
        )
    else:
        state = (
            "Grok Bot isn't installed. grok_install {} installs it for you in the background "
            "(public update feed, signature-checked, per-user, no admin prompt); poll "
            "grok_install_status. Do not send the user to download it themselves."
        )
    out = {
        "success": True,
        "ready": ready,
        "hostApp": HOST_APP,
        "hostAppInstalled": installed,
        "hostAppRunning": running,
        "hostAppFallback": fallback,
        "layers": {
            "hostAppInstalled": installed,
            "hostAppRunning": running,
            "hostAppFallback": fallback,
        },
        "paths": info.get("paths") or [],
        "windowsHint": info.get("windowsHint"),
        "_hint": "Read-only host-app detection (no download, no launch). " + state,
    }
    if running:
        # Wiki #5: "Couldn't Reach Grok Bot's Computer" used to be invisible here.
        from handlers.chat import probe_computer
        out.update(probe_computer())
        if out.get("computerReachable") is False:
            out["_hint"] += (" Grok Bot's computer (the agent box) is DOWN: webhook events will get "
                             "HTTP 200 but no reply. The user presses Recover / Retry in Grok Bot.")
    if not installed:
        out["errorCode"] = "host_app_not_installed"
        out["_next"] = "grok_install {} then poll grok_install_status {}, then grok_open {}. Do not send the user to a download page."
    return out


def handle_prewarm(args: dict) -> dict:
    """Cheap re-detect. {wait:true} just re-detects; there is nothing to download."""
    info = detect_host()
    waited = bool(args.get("wait"))
    return {
        "success": True,
        "prewarmed": True,
        "waited": waited,
        "hostApp": HOST_APP,
        "hostAppInstalled": bool(info.get("hostAppInstalled")),
        "hostAppRunning": bool(info.get("running")),
        "hostAppFallback": info.get("hostAppFallback"),
        "paths": info.get("paths") or [],
        "_hint": (
            "grok_prewarm is a no-op re-detect — Grok Bot is a host app, not a runtime asset. "
            "Nothing was downloaded. Use grok_readiness for the layered probe, grok_open to launch."
        ),
    }


def handle_status(_args: dict) -> dict:
    """Detect + last grok_send + inbox path + webhook bound? (never the key)."""
    info = detect_host()
    last = None
    inbox = None
    if send_mod is None:
        try:
            from handlers import send as _send
        except Exception:
            _send = None
    else:
        _send = send_mod
    if _send is not None:
        last = _send.last_send()
        inbox = str(_send.inbox_path())
    webhook_bound = False
    webhook_host = None
    if load_webhook is not None:
        cfg = load_webhook()
        webhook_bound = bool(cfg.get("url"))
        webhook_host = url_host(cfg["url"]) if webhook_bound else None
    return {
        "success": True,
        "hostApp": HOST_APP,
        "hostAppInstalled": bool(info.get("hostAppInstalled")),
        "hostAppRunning": bool(info.get("running")),
        "hostAppFallback": info.get("hostAppFallback"),
        "paths": info.get("paths") or [],
        "windowsHint": info.get("windowsHint"),
        "lastSend": last,
        "inboxPath": inbox,
        "webhookBound": webhook_bound,
        "webhookHost": webhook_host,
        "_hint": (
            "Status is detect + last grok_send + the durable inbox path "
            "(~/.adom/grok-inbox.jsonl) + whether ~/.adom/grok-webhook.json has a url "
            "(host only; the sender key is never returned). "
            "Catalog: grok_describe. Message: grok_send {\"message\":\"...\"}."
        ),
    }


def handle_uninstall(_args: dict) -> dict:
    """Tell the caller to bridge_uninstall {name:grok}. Do not kill Grok Bot or Cursor."""
    return {
        "success": True,
        "uninstalled": False,
        "killedHostApp": False,
        "grokBotLeftRunning": True,
        "cursorLeftRunning": True,
        "_hint": (
            "grok_uninstall does NOT kill Grok Bot.exe or Cursor and does not delete the window. "
            "Ask Adom Bridge to drop this runtime with: "
            "adom-bridge bridge_uninstall '{\"name\":\"grok\"}'. "
            "Container skills come off with ./uninstall.sh (or adom-wiki pkg uninstall adom/grok-bridge)."
        ),
        "_next": "adom-bridge bridge_uninstall {\"name\":\"grok\"}",
    }