Restore followup/DM reliability fixes wiped by 19:43Z tree-clean
Re-applies three workstreams lost when 793d3d7 committed over uncommitted
edits, reconciled against the parallel track's committed dm.py changes:
- followup-sweeper.py: backfill thread_uuid after successful nudge sends;
record final_nudge_target=main on final-nudge routing (C1/C2)
- response-harvester.py: resolve followups on main-chat replies when
final_nudge_target=main (C3); harvest ALL [RESULT] markers per message
- dm.py: pre-send placement gate (fail closed when post-nav URL lacks the
target thread UUID; skips main) — purely additive over 793d3d7+f268d3d
- sidechat_manager.py: wait_for_chat_list() settle-poll for list population
race (sidebar button renders before titles load)
- new: bin/tests/test_followup_fixes.py (25 tests), bin/placement-audit.py,
bin/dm-log-taxonomy.py, bin/session-probe.py,
docs/SIDECHAT-RELIABILITY.md, docs/UUID-ROTATION.md
Verified: 25/25 tests pass, py_compile clean, sweeper/harvester dry-runs clean.
Known limitation: gate catches wrong-placement, not wrong-mapping (false
autoprovision adopting the parked thread needs a creation check).
This commit is contained in:
+40
-1
@@ -4,6 +4,7 @@ Robust sidechat management for muse-chat-api.py.
|
||||
|
||||
Provides:
|
||||
- ensure_sidebar(ws): Opens sidebar if closed, with retry
|
||||
- wait_for_chat_list(ws, name=None): Polls until sidebar list content loads
|
||||
- list_sidechats(ws): Returns list of side chat names, with retry
|
||||
- All operations logged for audit
|
||||
|
||||
@@ -56,13 +57,51 @@ def ensure_sidebar(ws, max_retries=3):
|
||||
return !!document.querySelector('[data-testid="hatch-chat-compose"]');
|
||||
})()""")
|
||||
|
||||
def wait_for_chat_list(ws, name=None, timeout_s=15):
|
||||
"""Poll until the sidebar chat list content has loaded (and optionally
|
||||
contains `name`, case-insensitive substring match).
|
||||
|
||||
Readiness requires at least one plausible chat-title line under the
|
||||
"Side chats" header -- the header itself renders before items populate,
|
||||
and the compose button that ensure_sidebar() keys on renders earlier
|
||||
still. Clicking a chat title before the list is ready is a known
|
||||
nav-miss contributor (the click lands nowhere and the SPA stays on /).
|
||||
|
||||
Per-poll CDP errors are treated as not-ready (browser may be mid-render
|
||||
or mid-flap). Returns True when ready, False on timeout. Note: an
|
||||
account with genuinely zero sidechats will always time out here.
|
||||
"""
|
||||
import re as _re
|
||||
want = (name or "").strip().lower()
|
||||
deadline = time.time() + timeout_s
|
||||
while time.time() < deadline:
|
||||
try:
|
||||
text = _ev(ws, "document.body.innerText") or ""
|
||||
except Exception:
|
||||
text = ""
|
||||
idx = text.find("Side chats")
|
||||
if idx != -1:
|
||||
section = text[idx:idx + 2000].lower()
|
||||
lines = [l.strip() for l in section.split("\n")]
|
||||
titles = [l for l in lines[1:21]
|
||||
if 5 < len(l) < 80
|
||||
and not _re.fullmatch(r"\d+[mh]", l)
|
||||
and l != "unread updates"]
|
||||
if titles and (not want or want in section):
|
||||
return True
|
||||
time.sleep(1)
|
||||
return False
|
||||
|
||||
def list_sidechats(ws):
|
||||
"""
|
||||
List side chat names. Returns list of strings.
|
||||
Ensures sidebar is open first.
|
||||
Ensures sidebar is open first, then waits for the list content to
|
||||
populate before scraping (the open signal fires before content loads).
|
||||
"""
|
||||
if not ensure_sidebar(ws):
|
||||
return []
|
||||
if not wait_for_chat_list(ws):
|
||||
return []
|
||||
|
||||
result = _ev(ws, """(() => {
|
||||
const text = document.body.innerText;
|
||||
|
||||
Reference in New Issue
Block a user