"""Bounded, source-labelled end-of-run evidence from the existing step ledger.""" import base64 from pathlib import Path import re from artemis.data_engine.step_evidence import step_image_name def final_evidence(engine, *, max_sources: int = 8) -> list[dict]: """Attach the last two independently captured frames per resource. These are historical captures, never advertised as live device state. Equal hashes retain both capture labels but share one image block. No screenshots from another resource are substituted for missing evidence. """ if engine is None: return [] steps = engine.storage.get_steps(engine.current_session_id) or [] sources = {} for step in steps: result = step.last_execution_result execution = result.get("execution") if isinstance(result, dict) else None actions = execution if isinstance(execution, list) else step.action_taken actions = actions if isinstance(actions, list) else [actions] for action in actions: if not isinstance(action, dict): continue plugin = action.get("plugin_id") source = action.get("source") or {} if plugin: identity = (plugin, source.get("kind", "other"), source.get("target_id", "")) name = action.get("post_image_name") if len(actions) == 1: name = name or step_image_name(step, "post") pre = action.get("pre_image_name") if action.get("pre_image_valid") else None else: # A pure helper/report's decision frame is not a fresh post-action observation. if str(action.get("action", "")).startswith("plug_"): continue identity = ("device", "android", "primary") name, pre = step.post_image_name, step.pre_image_name if not name and not plugin: continue label = f"Step {step.step_number} post-action; {identity}; {source.get('label', '')}" label += f"; captured_at={source.get('captured_at') or step.timestamp}" attachments = action.get("attachments") if isinstance(attachments, list): paths = [ item.get("path") for item in attachments if isinstance(item, dict) and item.get("path") ] if paths: label += f"; runtime-verified saved attachments from this call={paths[:8]} (storage evidence, not proof of visual correctness)" if pre and pre == name: label += ( "; fresh post-capture identical to pre-capture (not proof of success by itself)" ) frames = sources.pop(identity, []) sources[identity] = [*frames[-1:], (name, label)] selected = list(sources.values())[-max_sources:] blocks = [] attached = set() for frames in selected: for name, label in frames[-2:]: if not name: blocks.append( { "type": "text", "text": f"Recorded evidence: {label}; no post-action image. Earlier captures do not prove the state after this call.", } ) continue if not isinstance(name, str) or not re.fullmatch(r"[0-9a-f]{64}", name): continue path = Path(engine.get_image_path(name)) blocks.append({"type": "text", "text": f"Recorded evidence: {label}; image={name}"}) if name in attached: blocks.append( { "type": "text", "text": f"Same image as the preceding capture with this hash; saved screenshot file={path}. " "Distinct capture events may share one file when their bytes are identical.", } ) continue try: image = path.read_bytes() except OSError: blocks.append( { "type": "text", "text": "Recorded image unavailable; do not infer its contents.", } ) continue blocks.append( { "type": "text", "text": f"Saved screenshot file={path}; readable, bytes={len(image)}. " "This automatic trace screenshot is recorded evidence even without a separately named export.", } ) mime = "image/png" if image.startswith(b"\x89PNG") else "image/jpeg" if image.startswith(b"RIFF") and image[8:12] == b"WEBP": mime = "image/webp" blocks.append( { "type": "image_url", "image_url": {"url": f"data:{mime};base64,{base64.b64encode(image).decode()}"}, } ) attached.add(name) if len(sources) > max_sources: blocks.append( { "type": "text", "text": "Additional resources omitted from this bounded preview; inspect them with get_step_screenshot before judging.", } ) return blocks