From e5737aadaaa3f96d915e01de5440b02bdf906796 Mon Sep 17 00:00:00 2001 From: rcholic Date: Thu, 1 Jan 2026 23:40:31 -0800 Subject: [PATCH 1/4] trace should include snapshot elements --- sentience/agent.py | 42 ++++++----------------------- sentience/trace_indexing/indexer.py | 9 +++++-- 2 files changed, 15 insertions(+), 36 deletions(-) diff --git a/sentience/agent.py b/sentience/agent.py index b507514..81e71cc 100644 --- a/sentience/agent.py +++ b/sentience/agent.py @@ -198,29 +198,16 @@ def act( # noqa: C901 # Emit snapshot trace event if tracer is enabled if self.tracer: - # Include element data for live overlay visualization - # Use filtered_elements for overlay (only relevant elements) - elements_data = [ - { - "id": el.id, - "bbox": { - "x": el.bbox.x, - "y": el.bbox.y, - "width": el.bbox.width, - "height": el.bbox.height, - }, - "role": el.role, - "text": el.text[:50] if el.text else "", # Truncate for brevity - } - for el in filtered_elements[:50] # Limit to first 50 for performance - ] + # Include ALL elements with full data for DOM tree display + # Use snap.elements (all elements) not filtered_elements + elements_data = [el.model_dump() for el in snap.elements] # Build snapshot event data snapshot_data = { "url": snap.url, "element_count": len(snap.elements), "timestamp": snap.timestamp, - "elements": elements_data, # Add element data for overlay + "elements": elements_data, # Full element data for DOM tree } # Always include screenshot in trace event for studio viewer compatibility @@ -983,29 +970,16 @@ async def act( # noqa: C901 # Emit snapshot trace event if tracer is enabled if self.tracer: - # Include element data for live overlay visualization - # Use filtered_elements for overlay (only relevant elements) - elements_data = [ - { - "id": el.id, - "bbox": { - "x": el.bbox.x, - "y": el.bbox.y, - "width": el.bbox.width, - "height": el.bbox.height, - }, - "role": el.role, - "text": el.text[:50] if el.text else "", # Truncate for brevity - } - for el in filtered_elements[:50] # Limit to first 50 for performance - ] + # Include ALL elements with full data for DOM tree display + # Use snap.elements (all elements) not filtered_elements + elements_data = [el.model_dump() for el in snap.elements] # Build snapshot event data snapshot_data = { "url": snap.url, "element_count": len(snap.elements), "timestamp": snap.timestamp, - "elements": elements_data, # Add element data for overlay + "elements": elements_data, # Full element data for DOM tree } # Always include screenshot in trace event for studio viewer compatibility diff --git a/sentience/trace_indexing/indexer.py b/sentience/trace_indexing/indexer.py index d9d5c7e..63432f9 100644 --- a/sentience/trace_indexing/indexer.py +++ b/sentience/trace_indexing/indexer.py @@ -58,13 +58,18 @@ def _compute_snapshot_digest(snapshot_data: dict[str, Any]) -> str: # Canonicalize elements canonical_elements = [] for elem in elements: + # Extract is_primary and is_clickable from visual_cues if present + visual_cues = elem.get("visual_cues", {}) + is_primary = visual_cues.get("is_primary", False) if isinstance(visual_cues, dict) else elem.get("is_primary", False) + is_clickable = visual_cues.get("is_clickable", False) if isinstance(visual_cues, dict) else elem.get("is_clickable", False) + canonical_elem = { "id": elem.get("id"), "role": elem.get("role", ""), "text_norm": _normalize_text(elem.get("text")), "bbox": _round_bbox(elem.get("bbox", {"x": 0, "y": 0, "width": 0, "height": 0})), - "is_primary": elem.get("is_primary", False), - "is_clickable": elem.get("is_clickable", False), + "is_primary": is_primary, + "is_clickable": is_clickable, } canonical_elements.append(canonical_elem) From f2ea9b9ec4c2e8aae3868bd1315b7a055977a470 Mon Sep 17 00:00:00 2001 From: rcholic Date: Fri, 2 Jan 2026 05:57:48 -0800 Subject: [PATCH 2/4] use trace cloud path in index --- sentience/cloud_tracing.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/sentience/cloud_tracing.py b/sentience/cloud_tracing.py index 019a564..732554d 100644 --- a/sentience/cloud_tracing.py +++ b/sentience/cloud_tracing.py @@ -322,10 +322,27 @@ def _upload_index(self) -> None: self.logger.warning("No upload URL in index upload response") return - # Read and compress index file - with open(index_path, "rb") as f: - index_data = f.read() + # Read index file and update trace_file.path to cloud storage path + with open(index_path, "r", encoding="utf-8") as f: + index_json = json.load(f) + # Extract cloud storage path from trace upload URL + # upload_url format: https://...digitaloceanspaces.com/traces/{run_id}.jsonl.gz + # Extract path: traces/{run_id}.jsonl.gz + try: + from urllib.parse import urlparse + parsed_url = urlparse(self.upload_url) + # Extract path after domain (e.g., /traces/run-123.jsonl.gz -> traces/run-123.jsonl.gz) + cloud_trace_path = parsed_url.path.lstrip("/") + # Update trace_file.path in index + if "trace_file" in index_json and isinstance(index_json["trace_file"], dict): + index_json["trace_file"]["path"] = cloud_trace_path + except Exception as e: + if self.logger: + self.logger.warning(f"Failed to extract cloud path from upload URL: {e}") + + # Serialize updated index to JSON + index_data = json.dumps(index_json, indent=2).encode("utf-8") compressed_index = gzip.compress(index_data) index_size = len(compressed_index) self.index_file_size_bytes = index_size # Track index file size From 4addc27ea12ea6e608834b19d73600ef6d446331 Mon Sep 17 00:00:00 2001 From: rcholic Date: Fri, 2 Jan 2026 06:08:28 -0800 Subject: [PATCH 3/4] fix to seq number --- sentience/cloud_tracing.py | 8 ++++++-- sentience/trace_indexing/indexer.py | 14 +++++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/sentience/cloud_tracing.py b/sentience/cloud_tracing.py index 732554d..55871c8 100644 --- a/sentience/cloud_tracing.py +++ b/sentience/cloud_tracing.py @@ -270,7 +270,10 @@ def _generate_index(self) -> None: try: from .trace_indexing import write_trace_index - write_trace_index(str(self._path)) + # Use frontend format to ensure 'step' field is present (1-based) + # Frontend derives sequence from step.step - 1, so step must be valid + index_path = Path(str(self._path).replace(".jsonl", ".index.json")) + write_trace_index(str(self._path), str(index_path), frontend_format=True) except Exception as e: # Non-fatal: log but don't crash print(f"⚠️ Failed to generate trace index: {e}") @@ -323,7 +326,7 @@ def _upload_index(self) -> None: return # Read index file and update trace_file.path to cloud storage path - with open(index_path, "r", encoding="utf-8") as f: + with open(index_path, encoding="utf-8") as f: index_json = json.load(f) # Extract cloud storage path from trace upload URL @@ -331,6 +334,7 @@ def _upload_index(self) -> None: # Extract path: traces/{run_id}.jsonl.gz try: from urllib.parse import urlparse + parsed_url = urlparse(self.upload_url) # Extract path after domain (e.g., /traces/run-123.jsonl.gz -> traces/run-123.jsonl.gz) cloud_trace_path = parsed_url.path.lstrip("/") diff --git a/sentience/trace_indexing/indexer.py b/sentience/trace_indexing/indexer.py index 63432f9..842baf0 100644 --- a/sentience/trace_indexing/indexer.py +++ b/sentience/trace_indexing/indexer.py @@ -60,9 +60,17 @@ def _compute_snapshot_digest(snapshot_data: dict[str, Any]) -> str: for elem in elements: # Extract is_primary and is_clickable from visual_cues if present visual_cues = elem.get("visual_cues", {}) - is_primary = visual_cues.get("is_primary", False) if isinstance(visual_cues, dict) else elem.get("is_primary", False) - is_clickable = visual_cues.get("is_clickable", False) if isinstance(visual_cues, dict) else elem.get("is_clickable", False) - + is_primary = ( + visual_cues.get("is_primary", False) + if isinstance(visual_cues, dict) + else elem.get("is_primary", False) + ) + is_clickable = ( + visual_cues.get("is_clickable", False) + if isinstance(visual_cues, dict) + else elem.get("is_clickable", False) + ) + canonical_elem = { "id": elem.get("id"), "role": elem.get("role", ""), From 13dc4c61e80206e837f8964973818549e23137a4 Mon Sep 17 00:00:00 2001 From: rcholic Date: Fri, 2 Jan 2026 06:27:54 -0800 Subject: [PATCH 4/4] bump version --- pyproject.toml | 2 +- sentience/__init__.py | 2 +- sentience/tracing.py | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e8a14f4..1aa1b9d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "sentienceapi" -version = "0.91.0" +version = "0.91.1" description = "Python SDK for Sentience AI Agent Browser Automation" readme = "README.md" requires-python = ">=3.11" diff --git a/sentience/__init__.py b/sentience/__init__.py index ab5e062..20c337c 100644 --- a/sentience/__init__.py +++ b/sentience/__init__.py @@ -70,7 +70,7 @@ ) from .wait import wait_for -__version__ = "0.91.0" +__version__ = "0.91.1" __all__ = [ # Core SDK diff --git a/sentience/tracing.py b/sentience/tracing.py index 4c0a5ba..39a9bb7 100644 --- a/sentience/tracing.py +++ b/sentience/tracing.py @@ -230,7 +230,10 @@ def _generate_index(self) -> None: try: from .trace_indexing import write_trace_index - write_trace_index(str(self.path)) + # Use frontend format to ensure 'step' field is present (1-based) + # Frontend derives sequence from step.step - 1, so step must be valid + index_path = Path(self.path).with_suffix(".index.json") + write_trace_index(str(self.path), str(index_path), frontend_format=True) except Exception as e: # Non-fatal: log but don't crash print(f"⚠️ Failed to generate trace index: {e}")