--- scripts/watcher.py 2026-10-05 15:36:41.321744391 +0000 +++ scripts/watcher.py 2026-10-05 15:36:41.321744391 +0000 @@ -49,6 +49,7 @@ BLUE_CLICK_Y = 425 MASTER_PROFILES_DIR = SYSTEM_DIR / "profiles" # 只讀 master LOGS_DIR = SYSTEM_DIR / "logs" +LOGS_DIR.mkdir(parents=True, exist_ok=True) # kit fix v1: 唔經 start-all.sh 起都唔會「第一個 log 就 crash」 RECALL_MSG = "Continue SESSION 2147483647" RECALL_BASE = 2147483647 # 固定起始數字(owner 2026-10-04 14:58 HKT:每次推減1,重啟重置) _recall_counter = RECALL_BASE # 內存計數器,重啟 watcher 即重置 @@ -63,6 +64,8 @@ _last_sent_num = num return f"Continue SESSION {num}" CHECK_INTERVAL = 30 # 秒 +# host 8568:DISPLAY 唔准寫死。由 env 攞;冇就 fallback ':99' 並一定 log(唔准靜靜 fallback)。 +DISPLAY = os.environ.get("DISPLAY") or ":99" def load_seats(): seats = {} @@ -72,7 +75,7 @@ if not line or line.startswith("#"): continue parts = line.split() - if len(parts) >= 6: + if len(parts) >= 5: # kit fix v1: 5 欄都收(原本 >=6 會靜默掉行) sid, profile, vpn, url, account = parts[0], parts[1], parts[2], parts[3], parts[4] status = parts[5] if len(parts) > 5 else "active" seats[sid] = { @@ -128,7 +131,7 @@ result = subprocess.run( ['xdotool', 'search', '--onlyvisible', '--name', 'Arena'], capture_output=True, text=True, timeout=5, - env={**os.environ, 'DISPLAY': ':99'} + env={**os.environ, 'DISPLAY': DISPLAY} ) wids = result.stdout.strip().split() if not wids: @@ -139,20 +142,20 @@ geom = subprocess.run( ['xdotool', 'getwindowgeometry', wid], capture_output=True, text=True, timeout=5, - env={**os.environ, 'DISPLAY': ':99'} + env={**os.environ, 'DISPLAY': DISPLAY} ).stdout if '1408x900' not in geom: # 試較返 subprocess.run( ['xdotool', 'windowsize', wid, '1408', '900'], - timeout=5, env={**os.environ, 'DISPLAY': ':99'} + timeout=5, env={**os.environ, 'DISPLAY': DISPLAY} ) time.sleep(1) # 再驗 geom2 = subprocess.run( ['xdotool', 'getwindowgeometry', wid], capture_output=True, text=True, timeout=5, - env={**os.environ, 'DISPLAY': ':99'} + env={**os.environ, 'DISPLAY': DISPLAY} ).stdout if '1408x900' not in geom2: log(sid, f"🚨 window {wid} 較唔返 1408x900,呢個 cycle 唔運行") @@ -963,7 +966,7 @@ try: out = subprocess.run(['xdotool', 'search', '--name', 'Chrome'], capture_output=True, text=True, timeout=5, - env={**__import__('os').environ, 'DISPLAY': ':99'}) + env={**__import__('os').environ, 'DISPLAY': DISPLAY}) wids = [w for w in out.stdout.strip().split('\n') if w] # 搵最大嗰個(browser 主視窗) best, best_area = None, 0 @@ -971,7 +974,7 @@ try: g = subprocess.run(['xdotool', 'getwindowgeometry', wid], capture_output=True, text=True, timeout=3, - env={**__import__('os').environ, 'DISPLAY': ':99'}) + env={**__import__('os').environ, 'DISPLAY': DISPLAY}) for line in g.stdout.split('\n'): if 'Geometry:' in line: parts = line.split()[1].split('x') @@ -991,7 +994,7 @@ out = subprocess.run(['import', '-window', str(window_id), '-crop', f'1x1+{x}+{y}', '-depth', '8', 'txt:-'], capture_output=True, text=True, timeout=5, - env={**__import__('os').environ, 'DISPLAY': ':99'}) + env={**__import__('os').environ, 'DISPLAY': DISPLAY}) import re m = re.search(r'#([0-9a-fA-F]{6})', out.stdout) return '#' + m.group(1).lower() if m else None @@ -1027,7 +1030,7 @@ def _xdotool_click(window_id, x, y): """跟 CA:xdotool 三步點擊""" import subprocess, time - env = {**__import__('os').environ, 'DISPLAY': ':99'} + env = {**__import__('os').environ, 'DISPLAY': DISPLAY} try: subprocess.run(['xdotool', 'windowraise', str(window_id)], env=env, timeout=3, capture_output=True) time.sleep(0.2) @@ -1155,7 +1158,7 @@ def ca_checkbox_click(window_id, x, y): """CA 式:用 xdotool 做 OS 層級滑鼠點擊(唔係 DOM click)""" import subprocess - env = {'DISPLAY': ':99'} + env = {'DISPLAY': DISPLAY} try: subprocess.run(['xdotool', 'windowraise', str(window_id)], env={**os.environ, **env}, timeout=3, capture_output=True) subprocess.run(['xdotool', 'windowactivate', '--sync', str(window_id)], env={**os.environ, **env}, timeout=3, capture_output=True) @@ -1401,11 +1404,14 @@ } ts = int(_t.time()) # 經 SSH 寫去 server bus(host inbox) - _sp.run([ + _r = _sp.run([ "/home/hatch/workspace/ssh-newserver.sh", f"cat > /root/muse-mesh/inbox/host/{ts}-watcher-{sid.lower()}.json && chmod 600 /root/muse-mesh/inbox/host/{ts}-watcher-{sid.lower()}.json" ], input=_json.dumps(msg).encode(), timeout=30, capture_output=True) - log(sid, "已寫 bus 訊息去 server:conversation_too_long(host 會掃到)") + if _r.returncode != 0: + log(sid, f"⚠️ bus 寫入失敗 rc={_r.returncode}:{( _r.stderr or b'')[:200]!r}(人手要知)") + else: + log(sid, "已寫 bus 訊息去 server:conversation_too_long(host 會掃到)") except Exception as e: log(sid, f"寫 bus 訊息出錯: {e}") # 停呢個 watcher,等 Muse AI 開新 URL