Harden dm.py sidechat navigation (CDP event drain + Page.navigate)

- ev(): drain CDP events until response id==1 (was blind recv,
  same bug class as box-chat-cdp.py 8d4bfa7 NO_SWITCHER fix)
- cmd_sidechat_use UUID path: use CDP Page.navigate instead of
  window.location.href via evaluate; 5 attempts, 15s SPA settle
  per attempt (was 3x4s, flaked 1/3 on url_mismatch)
- cmd_sidechat_use name path: retry 3x if not landing on /thread/

Test: 2/2 DM sends succeeded when browser healthy (tests 3-5
hit 646 browser death mid-test, infra issue not nav issue).

Session: sidechat/chromebox-ops
This commit is contained in:
operator-main
2026-10-04 19:47:33 +00:00
parent c59d492d49
commit f268d3d64b
+50 -6
View File
@@ -69,7 +69,18 @@ def ev(ws, expr, await_p=False):
"id": 1, "method": "Runtime.evaluate", "id": 1, "method": "Runtime.evaluate",
"params": {"expression": expr, "returnByValue": True, "awaitPromise": await_p} "params": {"expression": expr, "returnByValue": True, "awaitPromise": await_p}
})) }))
# Drain CDP events until we get our command response (id 1).
# The browser can emit events (Runtime.executionContextCreated, etc.)
# at any time; taking the first recv() blindly returns None on a
# busy page (observed as transient navigation failures in dm.py
# sidechat sends, 2026-10-04 — same class as the NO_SWITCHER fix
# in box-chat-cdp.py commit 8d4bfa7).
for _ in range(50):
resp = json.loads(ws.recv()) resp = json.loads(ws.recv())
if resp.get("id") == 1:
break
else:
return None
return resp.get('result', {}).get('result', {}).get('value') return resp.get('result', {}).get('result', {}).get('value')
def check_approvals(ws): def check_approvals(ws):
@@ -215,6 +226,27 @@ def cmd_wait(ws, timeout=30):
time.sleep(5) time.sleep(5)
cmd_messages(ws, 2) cmd_messages(ws, 2)
def cdp_navigate(ws, url, timeout_s=30):
"""Navigate via CDP Page.navigate (proper navigation, waits for commit).
Returns True if the page URL matches the target after navigation."""
import time as _time
ws.send(json.dumps({"id": 2, "method": "Page.navigate",
"params": {"url": url}}))
# Drain until we get the Page.navigate response (id 2).
for _ in range(50):
resp = json.loads(ws.recv())
if resp.get("id") == 2:
break
else:
return False
# Wait for the URL to settle (SPA client-side routing).
for _ in range(timeout_s):
cur = ev1(ws, "window.location.href", True)
if cur and url.rstrip("/").lower() in cur.lower():
return True
_time.sleep(1)
return False
def cmd_sidechat_use(ws, chat_id): def cmd_sidechat_use(ws, chat_id):
"""Open a sidechat by name (sidebar text search) or by thread UUID """Open a sidechat by name (sidebar text search) or by thread UUID
(direct navigation). The sidebar shows titles, not UUIDs, so the (direct navigation). The sidebar shows titles, not UUIDs, so the
@@ -224,15 +256,19 @@ def cmd_sidechat_use(ws, chat_id):
if re.fullmatch(r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", cid.lower()): if re.fullmatch(r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", cid.lower()):
url = "https://muse.ai/thread/" + cid.lower() url = "https://muse.ai/thread/" + cid.lower()
want_uuid = cid.lower() want_uuid = cid.lower()
# Use ev1 (skips CDP chatter) and confirm the URL actually changed. # Use CDP Page.navigate (proper navigation lifecycle) and confirm
# A stale read here used to report the previous thread's URL (2026-10-04). # the URL actually changed. Hardened 2026-10-04: the React SPA
# sometimes redirects back to / when the thread page hasn't finished
# loading; retry instead of failing immediately.
# (dm.py sidechat 1/3 flake; was window.location.href via evaluate.)
cur = None cur = None
for _try in range(3): for _try in range(5):
ev1(ws, "window.location.href=" + json.dumps(url), True) ok = cdp_navigate(ws, url, timeout_s=15)
time.sleep(4)
cur = ev1(ws, "window.location.href", True) cur = ev1(ws, "window.location.href", True)
if cur and want_uuid in cur: if ok and cur and want_uuid in cur:
break break
# SPA dropped the nav or hasn't routed yet; wait and retry.
time.sleep(3)
print(f"Navigated to: {cur}") print(f"Navigated to: {cur}")
return return
b64 = base64.b64encode(chat_id.encode()).decode() b64 = base64.b64encode(chat_id.encode()).decode()
@@ -255,7 +291,15 @@ def cmd_sidechat_use(ws, chat_id):
"return window.location.href;" "return window.location.href;"
"})()" "})()"
) )
# Hardened 2026-10-04: the click sometimes doesn't navigate (React
# mid-render, or the SPA drops it). Retry the whole nav if we didn't
# land on a /thread/ URL.
result = None
for _try in range(3):
result = ev(ws, js, True) result = ev(ws, js, True)
if result and "/thread/" in result:
break
time.sleep(3)
print(f"Navigated to: {result}") print(f"Navigated to: {result}")
def cmd_sidechat_main(ws): def cmd_sidechat_main(ws):