Preserve preview harness, editor, email and task improvements

Snapshot current maintainer-preview application changes and regression fixtures for integration into lab. Excludes local runtime data, evaluation outputs and source backups. Focused Python regression selection: 140 passed; full suite not certified.
This commit is contained in:
pewdiepie-archdaemon
2026-10-01 01:34:26 +00:00
parent 86f376ac3a
commit 2e8413a54a
170 changed files with 11010 additions and 1094 deletions
+2
View File
@@ -20,6 +20,7 @@ logger = logging.getLogger(__name__)
from .subprocess_tools import BashTool, HostShellTool, PythonTool
from .web_tools import WebSearchTool, WebFetchTool, PdfExtractTool, PrivateBrowserTool, YouTubeTool
from .weather_tools import WeatherTool
from .media_tools import ExtractTextTool, InspectMediaTool, TranscribeMediaTool
from .filesystem_tools import ReadFileTool, WriteFileTool, EditFileTool, ApplyPatchTool, LsTool, GlobTool, GrepTool, GetWorkspaceTool
from .coding_tools import TodoWriteTool
@@ -39,6 +40,7 @@ TOOL_HANDLERS = {
"host_shell": HostShellTool().execute,
"python": PythonTool().execute,
"web_search": WebSearchTool().execute,
"get_weather": WeatherTool().execute,
"web_fetch": WebFetchTool().execute,
"pdf_extract": PdfExtractTool().execute,
"youtube_tool": YouTubeTool().execute,
+168 -31
View File
@@ -1,6 +1,7 @@
from typing import Any, Dict, List, Optional
import hashlib
import html
import difflib
import logging
import re
from src.constants import MAX_READ_CHARS
@@ -295,10 +296,13 @@ def parse_edit_blocks(content: str) -> list:
# preserve whitespace inside the actual find/replace text.
pattern = (
r'<<<FIND>>>[ \t]*(?:\r?\n)?(.*?)[ \t]*(?:\r?\n)?'
r'<<<REPLACE>>>[ \t]*(?:\r?\n)?(.*?)[ \t]*(?:\r?\n)?<<<END>>>'
r'<<<(REPLACE|REPLACE_ALL)>>>[ \t]*(?:\r?\n)?(.*?)[ \t]*(?:\r?\n)?<<<END>>>'
)
for m in re.finditer(pattern, content, re.DOTALL):
edits.append({"find": m.group(1), "replace": m.group(2)})
edit = {"find": m.group(1), "replace": m.group(3)}
if m.group(2) == 'REPLACE_ALL':
edit['replace_all'] = True
edits.append(edit)
if not edits and "<<<FIND>>>" in content and "<<<REPLACE>>>" in content:
# Some native callers stop generation immediately after the replace
# body. Treat end-of-content as the terminal marker only in that
@@ -711,6 +715,74 @@ class UpdateDocumentTool:
finally:
db.close()
def _document_find_contexts(content, find):
"""Give a failed caller exact contextual anchors instead of a blind retry."""
contexts = []
for index, match in enumerate(re.finditer(re.escape(find), content)):
if index >= 3:
break
start = content.rfind('\n', 0, match.start()) + 1
end = content.find('\n', match.end())
end = len(content) if end < 0 else end
# Rich-text paragraphs are often stored on one HTML line.
for tag in ('p', 'div'):
paragraph = content.rfind(f'<{tag}', 0, match.start())
close = content.find(f'</{tag}>', match.end())
if paragraph >= 0 and close >= 0 and close + len(tag) + 3 - paragraph <= 1200:
start, end = paragraph, close + len(tag) + 3
break
context = content[start:end]
if len(context) <= 1200 and content.count(context) == 1:
contexts.append(context)
return '\nExact unique anchors from the current document:\n' + '\n'.join(contexts) if contexts else ''
def _document_find_repair_hint(content, find, count):
"""Offer bounded exact source text for an unmatched or ambiguous edit."""
if isinstance(count, int) and count > 1:
return _document_find_contexts(content, find)[:900]
if not isinstance(count, int) or count != 0:
return ''
# Minified markup may be one enormous line. Parse tag boundaries instead
# of comparing a small FIND against that entire line. This is evidence for
# a corrected call, never permission to apply a fuzzy replacement.
if find.lstrip().startswith('<'):
from html.parser import HTMLParser
class SourceTags(HTMLParser):
def __init__(self):
super().__init__(convert_charrefs=False)
self.tags = []
def handle_starttag(self, tag, attrs):
raw = self.get_starttag_text()
if raw and len(raw) <= 1000:
self.tags.append(raw)
def handle_startendtag(self, tag, attrs):
self.handle_starttag(tag, attrs)
parser = SourceTags()
parser.feed(content)
matches = difflib.get_close_matches(find, list(dict.fromkeys(parser.tags)), n=2, cutoff=0.7)
if matches:
return 'Copy an exact source fragment into FIND (including its spacing and quotes): ' + ' | '.join(
repr(match) for match in matches)
if re.fullmatch(r"[\w'-]{3,40}", find):
words = re.findall(r"[\w'-]{3,40}", content)
by_lower = {word.casefold(): word for word in words}
matches = difflib.get_close_matches(find.casefold(), by_lower, n=5, cutoff=0.6)
if matches:
return 'Closest words actually in the document: ' + ', '.join(
repr(by_lower[word]) for word in matches)
paragraphs = re.findall(r'<(?:p|div)\b[^>]*>.*?</(?:p|div)>', content, re.S | re.I)
if not paragraphs:
paragraphs = content.splitlines()
candidates = [p for p in paragraphs if len(p) <= 500]
matches = difflib.get_close_matches(find, candidates, n=2, cutoff=0.4)
return 'Closest exact passages in the document: ' + ' | '.join(matches) if matches else ''
class EditDocumentTool:
async def execute(self, content: str, ctx: dict) -> Dict:
"""Apply targeted FIND/REPLACE edits to an existing document."""
@@ -795,34 +867,66 @@ class EditDocumentTool:
return {"error": "No edits applied — FIND text cannot be blank"}
updated_content = doc.current_content
applied = 0
skipped = 0
for edit in edits:
_find = edit["find"]
if _find == edit["replace"]:
logger.warning("edit_document: skipping no-op FIND/REPLACE block")
applied, skipped, no_op_edits = 0, 0, 0
invalid_edits = []
# Validate against evolving content before the database write.
# Only exact unique matches may be saved; report every rejected
# entry explicitly so a partial batch cannot masquerade as complete.
prose = str(doc.language or '').lower() in {'text', 'markdown', 'richtext', 'email', ''}
for edit_number, edit in enumerate(edits, 1):
find = edit['find']
replacement = edit['replace']
if find == replacement:
skipped += 1
no_op_edits += 1
continue
if _find in updated_content:
updated_content = updated_content.replace(_find, edit["replace"], 1)
applied += 1
else:
# Defensive: the active-doc context shows a "N\t" line-number
# gutter for reference. Weaker models sometimes copy that prefix
# into FIND. If the exact match failed, retry with a leading
# "<digits><tab>" stripped from each FIND line — but only use it
# when that stripped form actually matches, so we never corrupt a
# legitimately tab-prefixed document.
_stripped = "\n".join(re.sub(r"^\d+\t", "", _l) for _l in _find.split("\n"))
if _stripped != _find and _stripped in updated_content:
updated_content = updated_content.replace(_stripped, edit["replace"], 1)
applied += 1
logger.info("edit_document: matched after stripping line-number gutter from FIND")
else:
logger.warning(f"edit_document: FIND text not found, skipping: {_find[:80]!r}")
skipped += 1
if find not in updated_content:
stripped = "\n".join(re.sub(r"^\d+\t", "", line) for line in find.split("\n"))
if stripped != find and stripped in updated_content:
find = stripped
count = updated_content.count(find) if find else 0
replace_all = edit.get('replace_all') is True
if count == 0 or (count != 1 and not replace_all):
invalid_edits.append((edit_number, count, edit['find']))
continue
position = updated_content.index(find)
positions = [m.start() for m in re.finditer(re.escape(find), updated_content)] if replace_all else [position]
if prose and re.fullmatch(r"[\w]+", find):
for match_pos in positions:
before = updated_content[match_pos - 1:match_pos] if match_pos else ''
after = updated_content[match_pos + len(find):match_pos + len(find) + 1]
if (before and (before.isalnum() or before == '_')) or (after and (after.isalnum() or after == '_')):
invalid_edits.append((edit_number, 'part of a word', edit['find']))
break
if invalid_edits and invalid_edits[-1][0] == edit_number:
continue
updated_content = updated_content.replace(find, replacement) if replace_all else updated_content[:position] + replacement + updated_content[position + len(find):]
applied += 1
partial_edits = bool(invalid_edits and applied)
if invalid_edits and not partial_edits:
details = '; '.join(
f'#{number} ({reason} matches): {find[:100]!r}'
if isinstance(reason, int) else f'#{number} ({reason}): {find[:100]!r}'
for number, reason, find in invalid_edits[:8]
)
extra = f'; and {len(invalid_edits) - 8} more' if len(invalid_edits) > 8 else ''
return {
'error': f'No edits applied. Invalid FIND entries: {details}{extra}. '
'Do not repeat the unchanged call. Copy FIND exactly from the current source or the hints below, '
'then retry the corrected entries. If no hint identifies the target, read the document first. '
'Other entries were not saved. ' + ' '.join(
f'#{number}: {_document_find_repair_hint(doc.current_content, find, reason)}'
for number, reason, find in invalid_edits[:3]
if _document_find_repair_hint(doc.current_content, find, reason)
),
'exit_code': 1, 'applied': 0,
'invalid_edit_numbers': [number for number, _, _ in invalid_edits],
}
if applied == 0:
if no_op_edits == len(edits):
return {"error": "No edits applied: every FIND and REPLACE pair is identical. Write a changed replacement that fulfills the requested revision; keep FIND copied from the current document."}
return {"error": f"No edits applied — none of the FIND blocks matched the document content (skipped {skipped})"}
missing_id = _missing_document_upload(owner, updated_content)
@@ -855,7 +959,7 @@ class EditDocumentTool:
db.add(ver)
db.commit()
return {
result = {
"action": "edit",
"doc_id": target_id,
"title": doc.title,
@@ -865,6 +969,17 @@ class EditDocumentTool:
"applied": applied,
"skipped": skipped,
}
if partial_edits:
result.update({
'partial': True,
'rejected': len(invalid_edits),
'invalid_edits': [
{'number': number, 'matches': reason, 'find': find[:100],
'hint': _document_find_repair_hint(updated_content, find, reason)}
for number, reason, find in invalid_edits
],
})
return result
except Exception as e:
db.rollback()
return {"error": f"Failed to edit document: {e}"}
@@ -897,8 +1012,8 @@ class SuggestDocumentTool:
return version_error
# Validate that FIND text exists in document
valid = []
for s in suggestions:
valid, invalid = [], []
for number, s in enumerate(suggestions, 1):
find_text = s["find"]
# Browser selections from markdown, rich text, and email are
# rendered text, while the stored document may contain LF
@@ -906,22 +1021,44 @@ class SuggestDocumentTool:
# back to the exact source fragment used by the editor.
source_find = _visible_text_match_source(doc.current_content, find_text)
if source_find is not None:
stored = doc.current_content or ''
if stored.count(source_find) != 1:
invalid.append({'number': number, 'find': find_text[:100],
'reason': 'ambiguous',
'hint': _document_find_contexts(stored, source_find)[:900]})
continue
if re.fullmatch(r"[\w]+", source_find):
pos = stored.index(source_find)
before = stored[pos - 1:pos] if pos else ''
after = stored[pos + len(source_find):pos + len(source_find) + 1]
if (before and before.isalnum()) or (after and after.isalnum()):
invalid.append({'number': number, 'find': find_text[:100],
'reason': 'part of a word', 'hint': ''})
continue
if source_find != find_text:
s = dict(s)
s["find"] = source_find
s["id"] = _stable_suggestion_id(target_id, s)
valid.append(s)
else:
logger.warning(f"suggest_document: FIND text not found, skipping: {find_text[:80]!r}")
invalid.append({'number': number, 'find': find_text[:100],
'reason': 'not found',
'hint': _document_find_repair_hint(doc.current_content or '', find_text, 0)[:900]})
if not valid:
return {"error": "No suggestions matched the document content"}
details = '; '.join(f"#{item['number']} {item['reason']}: {item['find']!r} {item['hint']}"
for item in invalid[:5])
return {'error': 'No suggestions created: ' + details,
'exit_code': 1, 'rejected': len(invalid)}
return {
"action": "suggest",
"doc_id": target_id,
"suggestions": valid,
"count": len(valid),
"partial": bool(invalid),
"rejected": len(invalid),
"invalid_suggestions": invalid,
}
finally:
db.close()
+2 -1
View File
@@ -156,7 +156,7 @@ async def list_sessions(content: str, session_id: Optional[str] = None, owner: O
safe_name = (sess.name or "Untitled").replace("[", "\\[").replace("]", "\\]")
msg_count = getattr(sess, "message_count", 0) or 0
model = getattr(sess, "model", "unknown")
marker = " ← most recent" if i == 0 else ""
marker = " ← current chat" if sid == session_id else (" ← most recent" if i == 0 else "")
lines.append(f"- **[{safe_name}](#session-{sid})** (id: `{sid}`, model: {model}, {msg_count} msgs, last active {_rel(ts)}){marker}")
if not lines:
@@ -166,6 +166,7 @@ async def list_sessions(content: str, session_id: Optional[str] = None, owner: O
"results": (
f"Found {len(rows)} session(s), sorted most-recent first:\n"
+ "\n".join(lines)
+ "\nFor the previous/last chat, exclude the row marked current chat. Use the exact returned ID, not an alias. If the target is ambiguous, ask using chat titles before changing anything."
+ "\n\nAssistant: when replying to the user, preserve the chat-title markdown links exactly as shown, e.g. `[Chat](#session-id)`. Do not rewrite this as a plain, non-clickable table."
)
}
+78
View File
@@ -0,0 +1,78 @@
"""No-key weather lookup backed by Open-Meteo."""
import asyncio
import json
import re
import urllib.parse
import urllib.request
def _get_json(url: str) -> dict:
request = urllib.request.Request(url, headers={"User-Agent": "Odysseus/1.0"})
with urllib.request.urlopen(request, timeout=8) as response:
return json.load(response)
def weather_location_from_query(query: str) -> str | None:
"""Extract a place only from straightforward weather lookup phrasing."""
text = re.sub(r"\s+", " ", query).strip(" ?.! ")
patterns = (
r"^(?:what(?:'s| is) the )?(?:current |today(?:'s)? |tomorrow(?:'s)? )?"
r"(?:weather|forecast)(?: like)? (?:in|for|at) (?P<place>.+)$",
r"^(?:weather|forecast) (?P<place>.+)$",
r"^(?P<place>.+?) (?:weather|forecast)\b.*$",
)
for pattern in patterns:
match = re.match(pattern, text, re.IGNORECASE)
if match:
place = re.sub(r"\b(?:today|tomorrow|now|current)\b.*$", "", match.group("place"), flags=re.IGNORECASE).strip(" ,")
if 1 <= len(place) <= 100:
return place
return None
class WeatherTool:
async def execute(self, content: str, ctx: dict) -> dict:
try:
args = json.loads(content) if content.strip().startswith("{") else {"location": content}
if not isinstance(args, dict):
return {"error": "get_weather expects a location string or JSON object", "exit_code": 1}
location = str(args.get("location") or "").strip()
if not location or len(location) > 160:
return {"error": "get_weather requires a location (up to 160 characters)", "exit_code": 1}
geo_url = "https://geocoding-api.open-meteo.com/v1/search?" + urllib.parse.urlencode({
"name": location, "count": 1, "language": "en", "format": "json",
})
geo = await asyncio.to_thread(_get_json, geo_url)
places = geo.get("results") or []
if not places:
return {"error": f"No location found for {location!r}", "exit_code": 1}
place = places[0]
forecast_url = "https://api.open-meteo.com/v1/forecast?" + urllib.parse.urlencode({
"latitude": place["latitude"],
"longitude": place["longitude"],
"current": "temperature_2m,relative_humidity_2m,precipitation,weather_code,wind_speed_10m",
"daily": "temperature_2m_max,temperature_2m_min,precipitation_probability_max,weather_code",
"forecast_days": 3,
"timezone": place.get("timezone") or "auto",
})
forecast = await asyncio.to_thread(_get_json, forecast_url)
current = forecast.get("current") or {}
daily = forecast.get("daily") or {}
if not current.get("time") or not daily.get("time"):
return {"error": "Weather provider returned incomplete forecast data", "exit_code": 1}
place_parts = list(dict.fromkeys(filter(None, [place.get("name"), place.get("admin1"), place.get("country")])))
data = {
"location": ", ".join(place_parts),
"timezone": forecast.get("timezone"),
"current": current,
"current_units": forecast.get("current_units") or {},
"daily": daily,
"daily_units": forecast.get("daily_units") or {},
"source": forecast_url,
"provider": "Open-Meteo",
}
return {"output": json.dumps(data, ensure_ascii=False), "exit_code": 0, "evidence_status": "available"}
except (OSError, ValueError, KeyError, TypeError) as exc:
return {"error": f"Weather lookup failed: {exc}", "exit_code": 1}
+96 -16
View File
@@ -322,6 +322,37 @@ class WebSearchTool:
"exit_code": 1,
"untrusted_content": True,
}
from .weather_tools import WeatherTool, weather_location_from_query
weather_location = weather_location_from_query(query)
if not sources and time_filter and weather_location:
# A forecast or current fact need not live on a newly published page.
try:
text, sources = await asyncio.wait_for(
loop.run_in_executor(
None,
lambda: comprehensive_web_search(
query, max_pages=max_pages, time_filter=None,
return_sources=True,
),
),
timeout=20,
)
except Exception:
pass
if not sources:
from src.turn_contract import active_turn_contract
contract = active_turn_contract()
policy = ctx.get("tool_policy") if isinstance(ctx, dict) else None
weather_allowed = (
weather_location
and "get_weather" not in (ctx.get("disabled_tools") or ())
and not (policy and policy.blocks("get_weather"))
and not (contract and not contract.permits("get_weather"))
)
if weather_allowed:
weather = await WeatherTool().execute(json.dumps({"location": weather_location}), ctx)
if weather.get("exit_code") == 0:
return weather
if progress_cb:
await progress_cb({
"elapsed_s": 30,
@@ -669,7 +700,7 @@ class WebFetchTool:
except Exception as e:
return {"error": f"web_fetch: {url}: {e}", "exit_code": 1}
err = result.get("error")
text = (result.get("content") or "").strip()
text = (result.get("linked_content") or result.get("content") or "").strip()
title = result.get("title") or ""
if not text:
@@ -711,7 +742,7 @@ class WebFetchTool:
"\n\n[...truncated; re-call web_fetch with query terms to retrieve matching passages]"
if not query else "\n\n[...truncated]"
)
return {"output": output, "exit_code": 0}
return {"output": output, "exit_code": 0, "page_entries": result.get("page_entries") or []}
class PdfExtractTool:
@@ -1840,7 +1871,6 @@ class YouTubeTool:
async def execute(self, content: str, ctx: dict) -> dict:
from services.youtube.youtube_handler import (
extract_youtube_id,
extract_transcript_async,
fetch_youtube_comments,
init_youtube,
@@ -1878,11 +1908,29 @@ class YouTubeTool:
return await self._latest_channel_video(channel, max_results=max_results)
url_or_id = str(args.get("url") or args.get("video_url") or args.get("video_id") or "").strip()
video_id = str(args.get("video_id") or "").strip()
if not video_id and url_or_id:
video_id = extract_youtube_id(url_or_id) or (url_or_id if _looks_like_youtube_video_id(url_or_id) else "")
if not video_id:
return {"error": f"youtube_tool {action}: provide a YouTube video URL or video_id", "exit_code": 1}
# The shared extractor accepts ID prefixes inside text. At the tool
# boundary require the complete target, not a truncated invented ID.
if url_or_id.startswith(('http://', 'https://')):
parsed = urllib.parse.urlparse(url_or_id)
host = (parsed.hostname or '').lower()
if host in {'youtube.com', 'www.youtube.com', 'm.youtube.com', 'music.youtube.com'}:
parts = parsed.path.strip('/').split('/')
candidate = (urllib.parse.parse_qs(parsed.query).get('v', [''])[0]
if parsed.path == '/watch' else
parts[1] if len(parts) == 2 and parts[0] in {'shorts', 'embed', 'live'} else '')
elif host in {'youtu.be', 'www.youtu.be'}:
candidate = parsed.path.strip('/')
else:
candidate = ''
else:
candidate = url_or_id
if (not re.fullmatch(r'[A-Za-z0-9_-]{11}', candidate)
or (args.get('video_id') and str(args['video_id']) != candidate)):
return {"error": f"youtube_tool {action}: invalid video target. Resolve the actual video URL "
"from the user or an observed link. A title or channel page is not a video ID; "
"open the referenced video in the browser or use latest_channel_video first.",
"exit_code": 1, "failure_kind": "invalid_target"}
video_id = candidate
url = url_or_id if url_or_id.startswith(("http://", "https://")) else f"https://www.youtube.com/watch?v={video_id}"
if action == "comments":
@@ -1896,7 +1944,9 @@ class YouTubeTool:
joined = f"{fallback_error}"
if api_error:
joined = f"YouTube Data API unavailable: {api_error}; yt-dlp fallback failed: {fallback_error}"
return {"error": f"youtube_tool comments: {joined}", "exit_code": 1, "untrusted_content": True}
return {"error": f"youtube_tool comments: {joined}", "exit_code": 1,
"failure_kind": "comments_unavailable", "video_url": url,
"untrusted_content": True}
return {"output": self._format_comments(comments_data, url), "exit_code": 0, "untrusted_content": True}
if action == "transcript":
@@ -2664,14 +2714,15 @@ class PrivateBrowserTool:
if err_text:
combined = f"[stderr]\n{err_text}\n\n{combined}".strip()
fill_error = ""
empty_observation = (proc.returncode or 0) == 0 and self._empty_dom_observation(out)
observe_state_change = action in {"open", "fill", "press"} and model_choice
failed_interaction = action in {"click", "fill"} and model_choice and (proc.returncode or 0) != 0
if failed_interaction or ((action == "click" or observe_state_change) and (proc.returncode or 0) == 0):
failed_interaction = action in {"click", "fill"} and (proc.returncode or 0) != 0
if empty_observation or failed_interaction or ((action == "click" or observe_state_change) and (proc.returncode or 0) == 0):
# A click can navigate, replace the DOM, or open a modal. Return
# the settled post-click DOM in the same tool result so callers do
# not race navigation with a separate immediate read and so the
# next conversational turn receives current element refs. A failed
# model-choice interaction also needs refs for a covering dialog
# interaction also needs refs for a covering dialog
# or changed DOM. A successful fill may run input handlers that
# open a modal or replace the field: CLI success is not proof that
# the intended value survived. Observe only; never retry an action.
@@ -2718,7 +2769,8 @@ class PrivateBrowserTool:
if page_errors:
combined = f"{combined}\n\n[page errors]\n{page_errors}".strip()
if len(combined) > MAX_OUTPUT_CHARS:
combined = combined[:MAX_OUTPUT_CHARS] + "\n\n[...truncated]"
from src.browser_observation import compact_browser_observation
combined = compact_browser_observation(combined, budget=MAX_OUTPUT_CHARS)
shopping_hint = self._shopping_landing_hint(combined)
if shopping_hint:
combined = f"{combined}\n\n[{shopping_hint}]"
@@ -2780,7 +2832,7 @@ class PrivateBrowserTool:
deadline = loop.time() + min(timeout_s, 20)
text, observation_note = "", ""
first_rows, rows = [], []
for attempt in range(2 if model_choice else 1):
for attempt in range(2):
proc = None
try:
async with asyncio.timeout(max(0, deadline - loop.time())):
@@ -2821,9 +2873,14 @@ class PrivateBrowserTool:
text, rows = observed, observed_rows
if not attempt:
first_rows = rows
if not snapshots or any(snapshot.strip() != '(empty page)' for snapshot in snapshots):
if not snapshots or not self._empty_dom_observation(observed):
break
commands = [["wait", "1000"], ["snapshot"]]
if not observation_note and self._empty_dom_observation(text):
observation_note = (
"Browser observation incomplete: the page still has no readable content after waiting. "
"Navigation success is not evidence that results loaded. Do not infer page results."
)
fill_error = ""
if verify_fill:
fill_error = unverified
@@ -2845,7 +2902,30 @@ class PrivateBrowserTool:
text = self._snapshot_observation(text)
if observation_note:
text += '\n' + observation_note
return text[:MAX_OUTPUT_CHARS], fill_error
if len(text) > MAX_OUTPUT_CHARS:
from src.browser_observation import compact_browser_observation
text = compact_browser_observation(text, budget=MAX_OUTPUT_CHARS)
return text, fill_error
@staticmethod
def _empty_dom_observation(text: str) -> bool:
"""Recognize empty accessibility scaffolding, not an actual no-results message."""
try:
payload = json.loads(text)
except (ValueError, TypeError):
payload = None
if isinstance(payload, list):
snapshots = [row['result']['snapshot'] for row in payload
if isinstance(row, dict) and row.get('success') is True
and isinstance(row.get('result'), dict)
and isinstance(row['result'].get('snapshot'), str)]
else:
snapshots = [text] if isinstance(text, str) and text.strip() else []
scaffolding = {'- generic', '- main', '- none', '- presentation', '(empty page)'}
return bool(snapshots) and all(
all(line.strip() in scaffolding for line in snapshot.splitlines() if line.strip())
for snapshot in snapshots
)
@staticmethod
def _dialog_first_snapshot(snapshot: str) -> str: