app
Grok Bot
Public Made by Adomby adom
Grok Bridge lets Adom talk to Grok Bot. Adom's AI environment is for electrical engineers: any provider, any desktop power tool, one place.
main
John Lauer
v1.3.8 (insiders): Hidden Grok Bot window: chat verbs read it in place, grok_open no longer re-shows it on its own (sho…
0270b3a
5d ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921
"""Window verbs: grok_open / grok_screenshot / grok_list_windows via ab desktop_* callbacks.
cliRequired verbs (shell_execute, pull_file, send_files) are NEVER used from this
HTTP callback. All driving goes through caller_identity.ad_callback.
"""
from __future__ import annotations
import json
import os
import subprocess
import sys
import time
import caller_identity
from grok_detect import (
HOST_APP,
detect_host,
process_name_filter,
window_title_needles,
_cursor_running,
_grok_bot_running,
)
# Chromium switches every launch of the host carries. Without them a background
# (never-shown, occluded) window does not paint, and the page is invisible to UIA:
# grok_chat_read / grok_chat_status saw only the caption buttons (measured on
# AdomLapper 2026-10-14; also the "blank for minutes" of wiki #5). Grok Bot's own
# auto-update relaunch ("--updated") drops them, so grok_open {restart:true} exists.
HOST_FLAGS = [
"--force-renderer-accessibility",
"--disable-features=CalculateNativeWinOcclusion",
"--disable-backgrounding-occluded-windows",
"--disable-renderer-backgrounding",
]
_RESTART_REASON_MIN = 10
# Grok Bot owns small secondary top-level windows titled "Grok Bot" too (a 240x30 strip
# was captured instead of the main window once its main window was hidden: wiki #5).
# Anything smaller than this is never "the" window.
STRAY_MIN_W = 400
STRAY_MIN_H = 300
_MAIN_FILE = os.path.join(os.path.expanduser("~"), ".adom", "grok-main-window.json")
def _cb(command: str, args: dict | None = None, timeout: int = 30) -> dict:
"""Best-effort ab callback. Never raises."""
return caller_identity.ad_callback(command, args or {}, on_behalf=True, timeout=timeout)
def _callback_ok(res: dict | None) -> bool:
if not isinstance(res, dict):
return False
if res.get("success") is False:
return False
err = (res.get("error") or "")
if "not in env" in err or "callback failed" in err.lower():
return False
return True
def _parse_output_field(blob: dict):
"""ad_callback may wrap the real payload in blob['output'] (JSON object/array)."""
raw = blob.get("output")
if isinstance(raw, (dict, list)):
return raw
if not isinstance(raw, str):
return None
s = raw.strip()
if not s or s[0] not in "{[":
return None
try:
parsed = json.loads(s)
except json.JSONDecodeError:
return None
if isinstance(parsed, (dict, list)):
return parsed
return None
def flatten_callback(blob):
"""Unwrap ad_callback wrappers: output JSON string, then data dict.
1. json.loads(blob['output']) when it is a JSON object/array and merge
2. unwrap blob['data'] if dict
"""
if isinstance(blob, list):
return blob
if not isinstance(blob, dict):
return blob
merged = dict(blob)
parsed = _parse_output_field(blob)
if isinstance(parsed, list):
return parsed
if isinstance(parsed, dict):
merged.update(parsed)
data = merged.get("data")
if isinstance(data, dict):
inner = dict(merged)
inner.update(data)
merged = inner
elif isinstance(data, list):
return data
return merged
def _add_unique_dicts(out: list[dict], seen: set, items) -> None:
for item in items or []:
if not isinstance(item, dict):
continue
h = item.get("hwnd")
if h is None:
h = item.get("handle")
pid = item.get("pid") if item.get("pid") is not None else item.get("id")
try:
if h is not None:
key = ("hwnd", int(h))
elif pid is not None:
key = ("pid", int(pid) if str(pid).lstrip("-").isdigit() else pid)
else:
key = ("obj", id(item), str(item.get("title") or item.get("name") or ""))
except (TypeError, ValueError):
key = ("obj", id(item), str(item.get("title") or item.get("name") or ""))
if key in seen:
continue
seen.add(key)
out.append(item)
def _as_windows(blob) -> list[dict]:
"""Collect window dicts from a desktop_list_windows / desktop_find_window blob.
Unwraps output JSON + data dict. Collects windows/items/result lists AND
blob['best'] when it has hwnd/title (find_window shape).
"""
if isinstance(blob, list):
return [w for w in blob if isinstance(w, dict)]
if not isinstance(blob, dict):
return []
parsed = _parse_output_field(blob)
if isinstance(parsed, list):
windows = [w for w in parsed if isinstance(w, dict)]
best = blob.get("best")
if isinstance(best, dict) and (best.get("hwnd") or best.get("title") or best.get("handle")):
windows.append(best)
return windows
merged = flatten_callback(blob)
if isinstance(merged, list):
return [w for w in merged if isinstance(w, dict)]
if not isinstance(merged, dict):
return []
out: list[dict] = []
seen: set = set()
for key in ("windows", "items", "result"):
val = merged.get(key)
if isinstance(val, list):
_add_unique_dicts(out, seen, val)
data = merged.get("data")
if isinstance(data, list):
_add_unique_dicts(out, seen, data)
best = merged.get("best")
if isinstance(best, dict) and (best.get("hwnd") or best.get("title") or best.get("handle")):
_add_unique_dicts(out, seen, [best])
if not out and (merged.get("hwnd") or merged.get("title") or merged.get("handle")):
_add_unique_dicts(out, seen, [merged])
return out
def _as_processes(blob) -> list[dict]:
"""Same unwrap as _as_windows: output JSON, data dict, processes/items/result, best."""
if isinstance(blob, list):
return [p for p in blob if isinstance(p, dict)]
if not isinstance(blob, dict):
return []
parsed = _parse_output_field(blob)
if isinstance(parsed, list):
return [p for p in parsed if isinstance(p, dict)]
merged = flatten_callback(blob)
if isinstance(merged, list):
return [p for p in merged if isinstance(p, dict)]
if not isinstance(merged, dict):
return []
out: list[dict] = []
seen: set = set()
for key in ("processes", "items", "result"):
val = merged.get(key)
if isinstance(val, list):
_add_unique_dicts(out, seen, val)
data = merged.get("data")
if isinstance(data, list):
_add_unique_dicts(out, seen, data)
best = merged.get("best")
if isinstance(best, dict) and (best.get("pid") or best.get("name") or best.get("imageName")):
_add_unique_dicts(out, seen, [best])
return out
def _hwnd_of(win: dict | None):
if not isinstance(win, dict):
return None
h = win.get("hwnd") or win.get("handle")
if h is None:
return None
try:
return int(h)
except (TypeError, ValueError):
return None
def _title_of(win: dict | None) -> str:
if not isinstance(win, dict):
return ""
return str(win.get("title") or win.get("name") or "")
# Browser windows whose tab title mentions Grok Bot (the sign-in pages "Sign in to
# Grok Bot", "Return to Grok Bot", the wiki page) are NOT the host app.
_BROWSER_MARKERS = (
"google chrome", "microsoft edge", "microsoft\u200b edge", "mozilla firefox",
"brave", "opera", "vivaldi", "chrome for testing", "ai-grok-bot",
)
def _is_host_impostor(title: str) -> bool:
"""Any browser window (Chrome / Edge / Firefox / ...) or ai-grok-bot is NOT the Grok Bot host."""
tl = (title or "").lower()
return any(m in tl for m in _BROWSER_MARKERS)
def _title_rank(title: str) -> int:
tl = (title or "").strip().lower()
if _is_host_impostor(title):
return 9
if tl == "grok bot":
return 0
if "grok bot" in tl:
return 1
if "cursor" in tl:
return 2
return 9
def _dedupe_windows(windows: list[dict]) -> list[dict]:
seen = set()
out = []
for w in windows:
h = _hwnd_of(w)
key = h if h is not None else (id(w), _title_of(w))
if key in seen:
continue
seen.add(key)
out.append(w)
return out
def collect_grok_windows(title_contains: str | None = None) -> tuple[list[dict], bool]:
"""List Grok Bot windows. Prefer desktop_find_window (best.hwnd) over list_windows.
titleContains: exact "Grok Bot" first. Bare "Grok" is too greedy (Chrome "ai-grok-bot").
"""
info = detect_host()
needles = [title_contains] if (title_contains and str(title_contains).strip()) else window_title_needles(info)
windows: list[dict] = []
ab_up = False
def _ingest(blob) -> None:
nonlocal ab_up
ab_up = ab_up or _callback_ok(blob)
windows.extend(_as_windows(blob))
def _hosts() -> list[dict]:
# Title must actually name the host: ab's desktop_list_windows can ignore
# titleContains and return every window on the desktop.
return [w for w in windows
if not _is_host_impostor(_title_of(w)) and _title_rank(_title_of(w)) < 9]
for needle in needles:
needle = (needle or "").strip()
if not needle:
continue
_ingest(_cb("desktop_find_window", {"titleContains": needle}))
if _hosts():
break
if not _hosts():
for needle in needles:
needle = (needle or "").strip()
if not needle:
continue
_ingest(_cb("desktop_list_windows", {"titleContains": needle}))
if _hosts():
break
filtered = _hosts()
filtered.sort(key=lambda w: _title_rank(_title_of(w)))
return _dedupe_windows(filtered), ab_up
def _wh(w: dict | None) -> tuple[int, int]:
r = (w or {}).get("rect") or {}
try:
return int(r.get("width") or 0), int(r.get("height") or 0)
except (TypeError, ValueError):
return 0, 0
def _is_stray(w: dict | None) -> bool:
"""True for a window too small to be the main Grok Bot window. Unknown size counts
as not stray (some ab shapes carry no rect)."""
wd, ht = _wh(w)
if not wd and not ht:
return False
return wd < STRAY_MIN_W or ht < STRAY_MIN_H
def _remember_main(hwnd) -> None:
try:
os.makedirs(os.path.dirname(_MAIN_FILE), exist_ok=True)
with open(_MAIN_FILE, "w", encoding="utf-8") as f:
json.dump({"hwnd": int(hwnd), "at": time.time()}, f)
except Exception:
pass
def remembered_main_hwnd() -> int | None:
try:
with open(_MAIN_FILE, encoding="utf-8") as f:
return int(json.load(f).get("hwnd")) or None
except Exception:
return None
# What the last pick saw, for verbs that want to say "main window hidden".
LAST_PICK: dict = {}
def pick_grok_window(windows: list[dict] | None = None, *, allow_cursor: bool = True) -> dict | None:
"""Best title rank, never a stray, then the LARGEST visible window."""
if windows is None:
windows, _ = collect_grok_windows()
ranked = []
strays = []
for w in windows or []:
rank = _title_rank(_title_of(w))
if rank == 2 and not allow_cursor:
continue
if rank >= 9:
continue
if _is_stray(w):
strays.append(w)
continue
wd, ht = _wh(w)
ranked.append((rank, -(wd * ht), w))
LAST_PICK.clear()
LAST_PICK["strays"] = [{"hwnd": _hwnd_of(w), "size": "%dx%d" % _wh(w)} for w in strays]
if not ranked:
LAST_PICK["mainWindowHidden"] = bool(strays)
return None
ranked.sort(key=lambda x: (x[0], x[1]))
best = ranked[0][2]
LAST_PICK["mainWindowHidden"] = False
if _hwnd_of(best):
_remember_main(_hwnd_of(best))
return best
def grok_hwnd(*, allow_cursor: bool = True) -> tuple[int | None, dict | None]:
win = pick_grok_window(allow_cursor=allow_cursor)
return _hwnd_of(win), win
def read_target_hwnd(*, allow_cursor: bool = True) -> tuple[int | None, dict | None, bool]:
"""(hwnd, win, hidden) for READ verbs: the visible main window, else the remembered hidden
main window (its page text stays readable through the hwnd while hidden)."""
hwnd, win = grok_hwnd(allow_cursor=allow_cursor)
if hwnd:
return hwnd, win, False
if main_window_state()["mainWindowHidden"]:
h = remembered_main_hwnd()
if h:
return h, {"hwnd": h, "title": HOST_APP}, True
return None, None, False
def main_window_state() -> dict:
"""Fields for callers when no main window is visible. Hidden = only strays visible, or
no window at all while Grok Bot runs and a main hwnd is remembered (grok_close hides it)."""
hidden = bool(LAST_PICK.get("mainWindowHidden"))
if not hidden and remembered_main_hwnd():
try:
hidden = bool(_grok_bot_running())
except Exception:
hidden = False
return {"mainWindowHidden": hidden,
"strayWindows": LAST_PICK.get("strays") or [],
"rememberedMainHwnd": remembered_main_hwnd()}
def show_hidden_main() -> tuple[int | None, dict | None, dict | None]:
"""Re-show the remembered main window. This ACTIVATES it: callers gate on foregroundReason."""
h = remembered_main_hwnd()
if not h:
return None, None, None
res = _cb("desktop_set_window_state", {"hwnd": h, "state": "show"})
if not _callback_ok(res) or (isinstance(res, dict) and res.get("error")):
return None, None, res
_cb("desktop_set_window_state", {"hwnd": h, "state": "restore"})
time.sleep(0.5)
hwnd, win = grok_hwnd(allow_cursor=False)
return hwnd, win, res
def window_rect(win: dict | None) -> dict:
"""Physical px {x,y,w,h} from a desktop_list_windows entry (ab physical px)."""
if not isinstance(win, dict):
return {}
rect = win.get("rect") or win.get("windowRect") or win.get("bounds") or {}
if not isinstance(rect, dict):
rect = {}
x = rect.get("x", rect.get("left", win.get("x")))
y = rect.get("y", rect.get("top", win.get("y")))
w = rect.get("w", rect.get("width", win.get("width")))
h = rect.get("h", rect.get("height", win.get("height")))
out = {}
for k, v in (("x", x), ("y", y), ("w", w), ("h", h)):
if v is None:
continue
try:
out[k] = int(v)
except (TypeError, ValueError):
continue
return out
def window_is_maximized(win: dict | None) -> bool:
if not isinstance(win, dict):
return False
if win.get("maximized") or win.get("isMaximized"):
return True
state = str(win.get("state") or win.get("showState") or win.get("showCmd") or "").lower()
return state in ("maximized", "maximize", "zoomed")
def window_is_minimized(win: dict | None) -> bool:
if not isinstance(win, dict):
return False
if win.get("minimized") or win.get("isMinimized"):
return True
state = str(win.get("state") or win.get("showState") or win.get("showCmd") or "").lower()
if state in ("minimized", "minimize", "hidden", "hide"):
return True
rect = window_rect(win)
return rect.get("x", 0) <= -30000 or rect.get("y", 0) <= -30000
def launch_host(argv: list | None = None) -> dict:
"""Launch Grok Bot.exe (background). Optional argv (e.g. a grokbot:// deep link).
desktop_open_url refuses grokbot:// (http/https/mailto/ftp only), so deep links
are passed as arguments to the host exe instead.
"""
info = detect_host()
exe = info.get("exe")
launch_args = {"background": True, "noActivate": True, "detached": True,
"args": HOST_FLAGS + list(argv or [])}
if exe:
launch_args["path"] = exe
else:
launch_args["name"] = HOST_APP
return _cb("launch", launch_args)
CREATE_NO_WINDOW = getattr(subprocess, "CREATE_NO_WINDOW", 0)
_POLL_SECONDS = 12
_POLL_INTERVAL = 1.2
_FG_REASON_MIN = 10
def _ps_quote(s: str) -> str:
return "'" + str(s).replace("'", "''") + "'"
def _host_alive(info: dict | None = None) -> tuple:
"""Return (hwnd, win, process_up) without stealing focus."""
info = info or detect_host()
allow = bool(info.get("hostAppFallback"))
hwnd, win = grok_hwnd(allow_cursor=allow)
if hwnd:
return hwnd, win, True
filt = process_name_filter(info)
procs = _as_processes(_cb("process_list", {"nameFilter": filt}))
if procs:
return None, None, True
if filt != "Grok Bot":
extra = _as_processes(_cb("process_list", {"nameFilter": "Grok Bot"}))
if extra:
return None, None, True
if info.get("hostAppFallback"):
running = _cursor_running()
else:
running = _grok_bot_running()
return None, None, bool(running)
def _poll_host(info: dict, timeout: float = _POLL_SECONDS) -> tuple:
deadline = time.time() + timeout
last = (None, None, False)
while time.time() < deadline:
last = _host_alive(info)
if last[0] or last[2]:
return last
time.sleep(_POLL_INTERVAL)
return last
def _start_process_minimized(exe: str, extra_args: list | None = None) -> dict:
"""Windows Start-Process -WindowStyle Minimized. Never shell_execute. Never Cursor unless that is the last-resort exe."""
if not exe:
return {"success": False, "error": "no exe path"}
if sys.platform != "win32":
return {"success": False, "via": "Start-Process", "error": "Start-Process fallback is Windows-only"}
cmd = (
"$p = Start-Process -FilePath " + _ps_quote(exe)
+ " -WindowStyle Minimized -PassThru"
)
if extra_args:
quoted = ",".join(_ps_quote(a) for a in extra_args)
cmd = (
"$p = Start-Process -FilePath " + _ps_quote(exe)
+ " -ArgumentList " + quoted
+ " -WindowStyle Minimized -PassThru"
)
cmd += "; if ($p) { Write-Output ('pid=' + $p.Id) } else { Write-Output 'no-process' }"
try:
r = subprocess.run(
["powershell", "-NoProfile", "-NonInteractive", "-Command", cmd],
capture_output=True, text=True, timeout=20,
creationflags=CREATE_NO_WINDOW,
)
out = ((r.stdout or "") + (r.stderr or "")).strip()
ok = r.returncode == 0 and "pid=" in out.lower()
return {
"success": ok or r.returncode == 0,
"via": "Start-Process -WindowStyle Minimized",
"returncode": r.returncode,
"outputPreview": out[:300],
}
except Exception as e:
return {"success": False, "via": "Start-Process", "error": str(e)}
def host_launch_flags_ok() -> bool | None:
"""True when the running Grok Bot main process carries HOST_FLAGS, False when it does
not (it was started by its own updater or by hand), None when unknown."""
try:
procs = _as_processes(_cb("process_list", {"name": "Grok Bot"}, timeout=10))
except Exception:
return None
mains = []
for p in procs:
cmd = str(p.get("commandLine") or p.get("cmdline") or p.get("CommandLine") or "")
if "grok bot.exe" in cmd.lower() and "--type=" not in cmd:
mains.append(cmd)
if not mains:
return None
return any("--force-renderer-accessibility" in c for c in mains)
RESTART_HINT = (
"Grok Bot was started without the accessibility / no-occlusion switches (its own auto-update "
"relaunch, or the user started it), so its page is invisible to UIA. Fix: grok_open "
"{\"restart\":true,\"reason\":\"<why>\"} relaunches it in the background with them; its bots run "
"in Grok's cloud, so nothing is lost."
)
def handle_list_windows(args: dict) -> dict:
info = detect_host()
title = (args.get("titleContains") or "").strip()
windows, listed_ok = collect_grok_windows(title or None)
filt = process_name_filter(info)
procs = _cb("process_list", {"nameFilter": filt})
grok_procs = _cb("process_list", {"nameFilter": "Grok Bot"}) if filt != "Grok Bot" else None
processes = _as_processes(procs)
if grok_procs:
extra = _as_processes(grok_procs)
seen = {p.get("pid") or p.get("id") for p in processes}
for p in extra:
key = p.get("pid") or p.get("id")
if key not in seen:
processes.append(p)
seen.add(key)
ab_up = listed_ok or _callback_ok(procs)
fallback = info.get("hostAppFallback")
empty_hint = (
"Empty? Grok Bot likely isn't running — grok_open launches it in the background."
if not windows else
"To capture one, grok_screenshot {}. To queue a message, grok_send. "
"App control: grok_close / grok_resize / grok_record_start."
)
fallback_hint = ""
if fallback:
fallback_hint = (
" Grok Bot.exe was not found; Cursor is a last-resort fallback host. Never kill Cursor."
)
return {
"success": True,
"hostApp": HOST_APP,
"hostAppFallback": fallback,
"windows": windows,
"processes": processes,
"count": len(windows),
"abCallbackReached": ab_up,
"windowEtiquette": "background",
"_hint": (
"Listed via ab desktop_find_window (preferred; best.hwnd) then desktop_list_windows, "
"titleContains exact Grok Bot (not bare Grok / Chrome ai-grok-bot), and "
f"process_list nameFilter {filt!r}. Read-only — no focus steal. "
+ empty_hint
+ fallback_hint
+ ("" if ab_up else " ab loopback (ADOM_DIRECT_API_URL) was not reachable — this process can still detect Grok Bot on disk.")
),
}
def handle_screenshot(args: dict) -> dict:
title = (args.get("titleContains") or "").strip()
info = detect_host()
# Explicit titleContains only: ab's titleContains picks the TOPMOST match, which can be a
# 240x30 secondary strip (wiki #5). By default capture the picked main window by hwnd.
needles = [title] if title else []
last = None
for needle in needles:
last = _cb("desktop_screenshot_window", {"titleContains": needle})
if _callback_ok(last) and not last.get("error"):
shot = dict(last)
shot.setdefault("success", True)
shot.setdefault("titleContains", needle)
shot.setdefault("windowEtiquette", "background")
shot.setdefault("openedInBackground", True)
shot["_hint"] = (
f"Screenshot via ab desktop_screenshot_window titleContains={needle!r}. "
"Window was NOT raised. PNG is on the desktop; use the adom-bridge CLI "
"to pull it — this HTTP callback never calls pull_file / send_files / shell_execute. "
+ (shot.get("_hint") or "")
)
return shot
hwnd, win = grok_hwnd(allow_cursor=bool(info.get("hostAppFallback")))
if hwnd:
last = _cb("desktop_screenshot_window", {"hwnd": hwnd})
if _callback_ok(last) and not last.get("error"):
shot = dict(last)
shot.setdefault("success", True)
shot.setdefault("hwnd", hwnd)
shot.setdefault("windowEtiquette", "background")
shot.setdefault("openedInBackground", True)
shot["_hint"] = (
f"Screenshot via ab desktop_screenshot_window hwnd={hwnd}. "
"Window was NOT raised. PNG is on the desktop; pull it with the CLI."
)
return shot
ms = main_window_state()
if ms["mainWindowHidden"]:
out = {"success": False, "errorCode": "main_window_hidden",
"error": "Grok Bot's main window is hidden; only small secondary windows are visible.",
"_hint": ("A hidden window cannot be captured, and a stray strip is not Grok Bot. Showing "
"it takes focus, so only if the user needs it: grok_open {\"foreground\":true,"
"\"foregroundReason\":\"...\"}, then grok_screenshot. grok_chat_read still reads it hidden."),
"_next": "grok_chat_read {} (works hidden), or grok_open {\"foreground\":true,\"foregroundReason\":\"...\"}"}
out.update(ms)
return out
return {
"success": False,
"errorCode": "window_not_found",
"error": "No Grok Bot window to screenshot.",
"lastCallback": last,
"hostAppFallback": info.get("hostAppFallback"),
"_hint": (
"desktop_screenshot_window found no titleContains Grok Bot or Grok. "
"Call grok_open to launch Grok Bot in the background, then grok_list_windows, then retry. "
"Do not steal focus to take the shot."
+ (
" Cursor is only a last-resort fallback if Grok Bot.exe is missing."
if info.get("hostAppFallback") else ""
)
),
"_next": "grok_open {} then grok_list_windows {} then grok_screenshot {}",
}
def handle_open(args: dict) -> dict:
"""Launch Grok Bot if installed; if running, list windows. Background by default.
Poll process_list / hwnd after launch. Fall back to Start-Process -WindowStyle
Minimized when ab launch / desktop_open report success but no process appears.
Never shell_execute. Never kill Cursor.
"""
info = detect_host()
installed = bool(info.get("hostAppInstalled"))
exe = info.get("exe")
fallback = info.get("hostAppFallback")
want_fg = bool(args.get("foreground") or args.get("showToUser"))
reason = (args.get("foregroundReason") or args.get("reason") or "").strip()
if want_fg and len(reason) < _FG_REASON_MIN:
return {
"success": False,
"errorCode": "foreground_reason_required",
"hwnd": None,
"opened": False,
"alreadyOpen": False,
"launched": False,
"windowEtiquette": "background",
"openedInBackground": True,
"_hint": (
"foreground requested without a foregroundReason of at least "
f"{_FG_REASON_MIN} characters — pass foreground:true AND "
"foregroundReason:'<why the user needs to see this>'. "
"Default is BACKGROUND so Grok Bot never steals the keyboard."
),
"_next": "Re-call grok_open {\"foreground\":true,\"foregroundReason\":\"...\"} or omit foreground.",
}
fallback_note = (
" Grok Bot.exe was not found; Cursor is a LAST-RESORT fallback host. Prefer Grok Bot."
if fallback else ""
)
restarted = None
if args.get("restart"):
why = str(args.get("reason") or "").strip()
if fallback:
return {"success": False, "errorCode": "restart_refused_cursor",
"_hint": "Grok Bot.exe is missing and the host is Cursor: never kill Cursor. Nothing restarted."}
if len(why) < _RESTART_REASON_MIN:
return {"success": False, "errorCode": "reason_required",
"_hint": f"grok_open {{restart:true}} closes the user's Grok Bot window; pass reason "
f"(at least {_RESTART_REASON_MIN} characters) saying why.",
"_next": "grok_open {\"restart\":true,\"reason\":\"Grok Bot page invisible to UIA after its auto-update\"}"}
restarted = _cb("process_kill", {"name": "Grok Bot.exe", "reason": why})
time.sleep(3)
hwnd0, win0, proc0 = _host_alive(info)
hidden_fix = None
if proc0 and not hwnd0 and not fallback and restarted is None:
hidden_state = main_window_state()
# Showing a hidden window activates it (ab has no show-without-activate), so it
# is a foreground action: only with foreground:true + foregroundReason.
if want_fg and len(reason) >= _FG_REASON_MIN:
h2, w2, res = show_hidden_main()
else:
h2, w2, res = None, None, None
hidden_fix = dict(hidden_state, shown=bool(h2), showCallback=res)
if h2:
hwnd0, win0 = h2, w2
listed = handle_list_windows(args) if (hwnd0 or proc0) else None
def _envelope(*, success, launched, already_open, opened, hwnd, win, via, callback, host_running, extra_hint=""):
title = _title_of(win) if win else None
etiq = "foreground" if (want_fg and len(reason) >= _FG_REASON_MIN) else "background"
out = {
"success": success,
"alreadyRunning": bool(already_open or (proc0 and not launched)),
"alreadyOpen": bool(already_open),
"opened": bool(opened),
"launched": bool(launched),
"hwnd": hwnd,
"title": title,
"via": via,
"hostApp": HOST_APP,
"hostAppInstalled": installed,
"hostAppRunning": bool(host_running),
"hostAppFallback": fallback,
"exe": exe,
"windows": (listed or handle_list_windows(args)).get("windows") or [] if (hwnd or host_running) else [],
"openedInBackground": etiq == "background",
"windowEtiquette": etiq,
}
if callback is not None:
out["callback"] = callback
if not success:
out["errorCode"] = out.get("errorCode") or "launch_did_not_start"
out["_hint"] = extra_hint + fallback_note
out["_foregroundHint"] = (
"Window is in the BACKGROUND. To show it: grok_open "
"{\"foreground\":true,\"foregroundReason\":\"<why the user needs to see this>\"}."
if etiq == "background" else
"Raised because foregroundReason was provided."
)
return out
if hwnd0 or proc0:
if want_fg and hwnd0 and len(reason) >= _FG_REASON_MIN:
_cb("desktop_set_window_state", {"hwnd": hwnd0, "state": "restore"})
flags_ok = None if fallback else host_launch_flags_ok()
env = _envelope(
success=True,
launched=False,
already_open=bool(hwnd0),
opened=bool(hwnd0),
hwnd=hwnd0,
win=win0,
via="already-open",
callback=None,
host_running=True,
extra_hint=(
"Grok Bot is already running — nothing launched, focus not stolen. "
"hwnd/opened/alreadyOpen populated so callers can tell launched vs already up. "
"To queue a message: grok_send. App control: grok_close / grok_resize / grok_screenshot."
+ (" " + RESTART_HINT if flags_ok is False else "")
),
)
env["launchedWithAccessibility"] = flags_ok
if hidden_fix is not None:
env.update({k: hidden_fix[k] for k in ("mainWindowHidden", "strayWindows", "rememberedMainHwnd")})
env["shownHiddenMain"] = hidden_fix["shown"]
if hidden_fix["shown"]:
env["_hint"] = "Grok Bot's main window was hidden; shown again because foregroundReason was given. " + env["_hint"]
elif hidden_fix.get("rememberedMainHwnd"):
env["_hint"] = ("Grok Bot is running with its main window HIDDEN (not shown: showing it takes "
"focus). grok_chat_read / grok_chat_status still read it in place. To show it: "
"grok_open {\"foreground\":true,\"foregroundReason\":\"<why the user needs to see it>\"}. " + env["_hint"])
env["_next"] = "grok_open {\"foreground\":true,\"foregroundReason\":\"...\"} only if the user needs to see it"
else:
env["_hint"] = ("Grok Bot is running but its main window is hidden and its hwnd is not remembered "
"on this PC. grok_open {\"restart\":true,\"reason\":\"...\"} relaunches it in the "
"background. " + env["_hint"])
if restarted is not None:
env["restartKill"] = restarted
env["_hint"] = "Restart asked, but Grok Bot was still running after the kill. " + env["_hint"]
return env
launch_name = fallback if fallback else HOST_APP
launch_args = {"background": True, "noActivate": True, "detached": True, "args": list(HOST_FLAGS)}
if exe:
launch_args["path"] = exe
else:
launch_args["name"] = launch_name
launched_cb = _cb("launch", launch_args)
via = "launch"
hwnd, win, proc = _poll_host(info, timeout=_POLL_SECONDS)
if not hwnd and not proc:
open_args = {"background": True}
if exe:
open_args["path"] = exe
else:
open_args["target"] = launch_name
launched_cb = _cb("desktop_open", open_args)
via = "desktop_open"
hwnd, win, proc = _poll_host(info, timeout=_POLL_SECONDS)
start_fb = None
if not hwnd and not proc:
start_fb = _start_process_minimized(exe or launch_name, HOST_FLAGS)
via = start_fb.get("via") or "Start-Process -WindowStyle Minimized"
launched_cb = start_fb
hwnd, win, proc = _poll_host(info, timeout=20)
if want_fg and hwnd and len(reason) >= _FG_REASON_MIN:
_cb("desktop_set_window_state", {"hwnd": hwnd, "state": "restore"})
really_up = bool(hwnd or proc)
if not installed and not really_up:
return {
"success": False,
"errorCode": "host_app_not_installed",
"hwnd": None,
"opened": False,
"alreadyOpen": False,
"launched": False,
"hostApp": HOST_APP,
"hostAppInstalled": False,
"hostAppRunning": False,
"hostAppFallback": fallback,
"via": via,
"callback": launched_cb,
"openedInBackground": True,
"windowEtiquette": "background",
"_hint": (
"Grok Bot isn't installed — want me to open it if present? "
"Tried ab launch / desktop_open then Start-Process minimized "
"(typical path C:\\\\Users\\\\john\\\\AppData\\\\Local\\\\Programs\\\\Grok Bot\\\\Grok Bot.exe). "
"Cursor is a last-resort fallback only if Grok Bot.exe is missing. "
"Do not send the user to download the host app themselves."
),
"_next": "Retry grok_open after Grok Bot exists. Cursor fallback only if Grok Bot.exe is missing.",
}
hint = (
f"Asked ab then polled process_list/window (via={via}). "
+ ("Process/window appeared. " if really_up else "NO process after launch/desktop_open poll + Start-Process minimized fallback. ")
+ ("Cursor last-resort fallback. " if fallback else "Grok Bot ")
+ (f"at {exe} " if exe else "(by name) ")
+ "in the BACKGROUND. Poll grok_list_windows / grok_chat_read; then grok_screenshot or grok_send."
)
if start_fb is not None:
hint += " Start-Process -WindowStyle Minimized used because launch/desktop_open did not take."
out = _envelope(
success=really_up,
launched=really_up,
already_open=False,
opened=bool(hwnd),
hwnd=hwnd,
win=win,
via=via,
callback=launched_cb,
host_running=really_up,
extra_hint=hint,
)
out["launchedWithAccessibility"] = True if via != "desktop_open" else None
if restarted is not None:
out["restarted"] = True
if not really_up:
out["errorCode"] = "launch_did_not_start"
out["_next"] = "Retry grok_open {}; confirm Grok Bot.exe path via grok_readiness."
return out