Install and update artemis-use with task diagnostics and plugin guidance. Preserve cross-device file receipts, review canonical reports, guard stage transitions and reduce small history summaries. Improve exploration lifecycle, request recovery and browser live connections.
123 lines
5.3 KiB
Python
123 lines
5.3 KiB
Python
"""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
|