sentienceapi 0.90.9__py3-none-any.whl → 0.90.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sentienceapi might be problematic. Click here for more details.
- sentience/__init__.py +1 -1
- sentience/browser.py +83 -2
- sentience/cloud_tracing.py +93 -1
- sentience/extension/release.json +1 -1
- sentience/text_search.py +41 -0
- sentience/trace_indexing/__init__.py +6 -6
- sentience/trace_indexing/index_schema.py +14 -14
- sentience/trace_indexing/indexer.py +13 -19
- {sentienceapi-0.90.9.dist-info → sentienceapi-0.90.12.dist-info}/METADATA +1 -1
- {sentienceapi-0.90.9.dist-info → sentienceapi-0.90.12.dist-info}/RECORD +14 -14
- {sentienceapi-0.90.9.dist-info → sentienceapi-0.90.12.dist-info}/WHEEL +0 -0
- {sentienceapi-0.90.9.dist-info → sentienceapi-0.90.12.dist-info}/entry_points.txt +0 -0
- {sentienceapi-0.90.9.dist-info → sentienceapi-0.90.12.dist-info}/licenses/LICENSE.md +0 -0
- {sentienceapi-0.90.9.dist-info → sentienceapi-0.90.12.dist-info}/top_level.txt +0 -0
sentience/__init__.py
CHANGED
sentience/browser.py
CHANGED
|
@@ -33,6 +33,8 @@ class SentienceBrowser:
|
|
|
33
33
|
proxy: str | None = None,
|
|
34
34
|
user_data_dir: str | None = None,
|
|
35
35
|
storage_state: str | Path | StorageState | dict | None = None,
|
|
36
|
+
record_video_dir: str | Path | None = None,
|
|
37
|
+
record_video_size: dict[str, int] | None = None,
|
|
36
38
|
):
|
|
37
39
|
"""
|
|
38
40
|
Initialize Sentience browser
|
|
@@ -57,6 +59,14 @@ class SentienceBrowser:
|
|
|
57
59
|
- StorageState object
|
|
58
60
|
- Dictionary with 'cookies' and/or 'origins' keys
|
|
59
61
|
If provided, browser starts with pre-injected authentication.
|
|
62
|
+
record_video_dir: Optional directory path to save video recordings.
|
|
63
|
+
If provided, browser will record video of all pages.
|
|
64
|
+
Videos are saved as .webm files in the specified directory.
|
|
65
|
+
If None, no video recording is performed.
|
|
66
|
+
record_video_size: Optional video resolution as dict with 'width' and 'height' keys.
|
|
67
|
+
Examples: {"width": 1280, "height": 800} (default)
|
|
68
|
+
{"width": 1920, "height": 1080} (1080p)
|
|
69
|
+
If None, defaults to 1280x800.
|
|
60
70
|
"""
|
|
61
71
|
self.api_key = api_key
|
|
62
72
|
# Only set api_url if api_key is provided, otherwise None (free tier)
|
|
@@ -80,6 +90,10 @@ class SentienceBrowser:
|
|
|
80
90
|
self.user_data_dir = user_data_dir
|
|
81
91
|
self.storage_state = storage_state
|
|
82
92
|
|
|
93
|
+
# Video recording support
|
|
94
|
+
self.record_video_dir = record_video_dir
|
|
95
|
+
self.record_video_size = record_video_size or {"width": 1280, "height": 800}
|
|
96
|
+
|
|
83
97
|
self.playwright: Playwright | None = None
|
|
84
98
|
self.context: BrowserContext | None = None
|
|
85
99
|
self.page: Page | None = None
|
|
@@ -209,6 +223,17 @@ class SentienceBrowser:
|
|
|
209
223
|
launch_params["ignore_https_errors"] = True
|
|
210
224
|
print(f"🌐 [Sentience] Using proxy: {proxy_config.server}")
|
|
211
225
|
|
|
226
|
+
# Add video recording if configured
|
|
227
|
+
if self.record_video_dir:
|
|
228
|
+
video_dir = Path(self.record_video_dir)
|
|
229
|
+
video_dir.mkdir(parents=True, exist_ok=True)
|
|
230
|
+
launch_params["record_video_dir"] = str(video_dir)
|
|
231
|
+
launch_params["record_video_size"] = self.record_video_size
|
|
232
|
+
print(f"🎥 [Sentience] Recording video to: {video_dir}")
|
|
233
|
+
print(
|
|
234
|
+
f" Resolution: {self.record_video_size['width']}x{self.record_video_size['height']}"
|
|
235
|
+
)
|
|
236
|
+
|
|
212
237
|
# Launch persistent context (required for extensions)
|
|
213
238
|
# Note: We pass headless=False to launch_persistent_context because we handle
|
|
214
239
|
# headless mode via the --headless=new arg above. This is a Playwright workaround.
|
|
@@ -390,15 +415,71 @@ class SentienceBrowser:
|
|
|
390
415
|
|
|
391
416
|
return False
|
|
392
417
|
|
|
393
|
-
def close(self) -> None:
|
|
394
|
-
"""
|
|
418
|
+
def close(self, output_path: str | Path | None = None) -> str | None:
|
|
419
|
+
"""
|
|
420
|
+
Close browser and cleanup
|
|
421
|
+
|
|
422
|
+
Args:
|
|
423
|
+
output_path: Optional path to rename the video file to.
|
|
424
|
+
If provided, the recorded video will be moved to this location.
|
|
425
|
+
Useful for giving videos meaningful names instead of random hashes.
|
|
426
|
+
|
|
427
|
+
Returns:
|
|
428
|
+
Path to video file if recording was enabled, None otherwise
|
|
429
|
+
Note: Video files are saved automatically by Playwright when context closes.
|
|
430
|
+
If multiple pages exist, returns the path to the first page's video.
|
|
431
|
+
"""
|
|
432
|
+
temp_video_path = None
|
|
433
|
+
|
|
434
|
+
# Get video path before closing (if recording was enabled)
|
|
435
|
+
# Note: Playwright saves videos when pages/context close, but we can get the
|
|
436
|
+
# expected path before closing. The actual file will be available after close.
|
|
437
|
+
if self.record_video_dir:
|
|
438
|
+
try:
|
|
439
|
+
# Try to get video path from the first page
|
|
440
|
+
if self.page and self.page.video:
|
|
441
|
+
temp_video_path = self.page.video.path()
|
|
442
|
+
# If that fails, check all pages in the context
|
|
443
|
+
elif self.context:
|
|
444
|
+
for page in self.context.pages:
|
|
445
|
+
if page.video:
|
|
446
|
+
temp_video_path = page.video.path()
|
|
447
|
+
break
|
|
448
|
+
except Exception:
|
|
449
|
+
# Video path might not be available until after close
|
|
450
|
+
# In that case, we'll return None and user can check the directory
|
|
451
|
+
pass
|
|
452
|
+
|
|
453
|
+
# Close context (this triggers video file finalization)
|
|
395
454
|
if self.context:
|
|
396
455
|
self.context.close()
|
|
456
|
+
|
|
457
|
+
# Close playwright
|
|
397
458
|
if self.playwright:
|
|
398
459
|
self.playwright.stop()
|
|
460
|
+
|
|
461
|
+
# Clean up extension directory
|
|
399
462
|
if self._extension_path and os.path.exists(self._extension_path):
|
|
400
463
|
shutil.rmtree(self._extension_path)
|
|
401
464
|
|
|
465
|
+
# Rename/move video if output_path is specified
|
|
466
|
+
final_path = temp_video_path
|
|
467
|
+
if temp_video_path and output_path and os.path.exists(temp_video_path):
|
|
468
|
+
try:
|
|
469
|
+
output_path = str(output_path)
|
|
470
|
+
# Ensure parent directory exists
|
|
471
|
+
Path(output_path).parent.mkdir(parents=True, exist_ok=True)
|
|
472
|
+
shutil.move(temp_video_path, output_path)
|
|
473
|
+
final_path = output_path
|
|
474
|
+
except Exception as e:
|
|
475
|
+
import warnings
|
|
476
|
+
|
|
477
|
+
warnings.warn(f"Failed to rename video file: {e}")
|
|
478
|
+
# Return original path if rename fails
|
|
479
|
+
final_path = temp_video_path
|
|
480
|
+
|
|
481
|
+
return final_path
|
|
482
|
+
|
|
402
483
|
def __enter__(self):
|
|
403
484
|
"""Context manager entry"""
|
|
404
485
|
self.start()
|
sentience/cloud_tracing.py
CHANGED
|
@@ -213,7 +213,10 @@ class CloudTraceSink(TraceSink):
|
|
|
213
213
|
if on_progress:
|
|
214
214
|
on_progress(compressed_size, compressed_size)
|
|
215
215
|
|
|
216
|
-
#
|
|
216
|
+
# Upload trace index file
|
|
217
|
+
self._upload_index()
|
|
218
|
+
|
|
219
|
+
# Call /v1/traces/complete to report file sizes
|
|
217
220
|
self._complete_trace()
|
|
218
221
|
|
|
219
222
|
# Delete file only on successful upload
|
|
@@ -244,6 +247,95 @@ class CloudTraceSink(TraceSink):
|
|
|
244
247
|
# Non-fatal: log but don't crash
|
|
245
248
|
print(f"⚠️ Failed to generate trace index: {e}")
|
|
246
249
|
|
|
250
|
+
def _upload_index(self) -> None:
|
|
251
|
+
"""
|
|
252
|
+
Upload trace index file to cloud storage.
|
|
253
|
+
|
|
254
|
+
Called after successful trace upload to provide fast timeline rendering.
|
|
255
|
+
The index file enables O(1) step lookups without parsing the entire trace.
|
|
256
|
+
"""
|
|
257
|
+
# Construct index file path (same as trace file with .index.json extension)
|
|
258
|
+
index_path = Path(str(self._path).replace(".jsonl", ".index.json"))
|
|
259
|
+
|
|
260
|
+
if not index_path.exists():
|
|
261
|
+
if self.logger:
|
|
262
|
+
self.logger.warning("Index file not found, skipping index upload")
|
|
263
|
+
return
|
|
264
|
+
|
|
265
|
+
try:
|
|
266
|
+
# Request index upload URL from API
|
|
267
|
+
if not self.api_key:
|
|
268
|
+
# No API key - skip index upload
|
|
269
|
+
if self.logger:
|
|
270
|
+
self.logger.info("No API key provided, skipping index upload")
|
|
271
|
+
return
|
|
272
|
+
|
|
273
|
+
response = requests.post(
|
|
274
|
+
f"{self.api_url}/v1/traces/index_upload",
|
|
275
|
+
headers={"Authorization": f"Bearer {self.api_key}"},
|
|
276
|
+
json={"run_id": self.run_id},
|
|
277
|
+
timeout=10,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
if response.status_code != 200:
|
|
281
|
+
if self.logger:
|
|
282
|
+
self.logger.warning(
|
|
283
|
+
f"Failed to get index upload URL: HTTP {response.status_code}"
|
|
284
|
+
)
|
|
285
|
+
return
|
|
286
|
+
|
|
287
|
+
upload_data = response.json()
|
|
288
|
+
index_upload_url = upload_data.get("upload_url")
|
|
289
|
+
|
|
290
|
+
if not index_upload_url:
|
|
291
|
+
if self.logger:
|
|
292
|
+
self.logger.warning("No upload URL in index upload response")
|
|
293
|
+
return
|
|
294
|
+
|
|
295
|
+
# Read and compress index file
|
|
296
|
+
with open(index_path, "rb") as f:
|
|
297
|
+
index_data = f.read()
|
|
298
|
+
|
|
299
|
+
compressed_index = gzip.compress(index_data)
|
|
300
|
+
index_size = len(compressed_index)
|
|
301
|
+
|
|
302
|
+
if self.logger:
|
|
303
|
+
self.logger.info(f"Index file size: {index_size / 1024:.2f} KB")
|
|
304
|
+
|
|
305
|
+
print(f"📤 [Sentience] Uploading trace index ({index_size} bytes)...")
|
|
306
|
+
|
|
307
|
+
# Upload index to cloud storage
|
|
308
|
+
index_response = requests.put(
|
|
309
|
+
index_upload_url,
|
|
310
|
+
data=compressed_index,
|
|
311
|
+
headers={
|
|
312
|
+
"Content-Type": "application/json",
|
|
313
|
+
"Content-Encoding": "gzip",
|
|
314
|
+
},
|
|
315
|
+
timeout=30,
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
if index_response.status_code == 200:
|
|
319
|
+
print("✅ [Sentience] Trace index uploaded successfully")
|
|
320
|
+
|
|
321
|
+
# Delete local index file after successful upload
|
|
322
|
+
try:
|
|
323
|
+
os.remove(index_path)
|
|
324
|
+
except Exception:
|
|
325
|
+
pass # Ignore cleanup errors
|
|
326
|
+
else:
|
|
327
|
+
if self.logger:
|
|
328
|
+
self.logger.warning(
|
|
329
|
+
f"Index upload failed: HTTP {index_response.status_code}"
|
|
330
|
+
)
|
|
331
|
+
print(f"⚠️ [Sentience] Index upload failed: HTTP {index_response.status_code}")
|
|
332
|
+
|
|
333
|
+
except Exception as e:
|
|
334
|
+
# Non-fatal: log but don't crash
|
|
335
|
+
if self.logger:
|
|
336
|
+
self.logger.warning(f"Error uploading trace index: {e}")
|
|
337
|
+
print(f"⚠️ [Sentience] Error uploading trace index: {e}")
|
|
338
|
+
|
|
247
339
|
def _complete_trace(self) -> None:
|
|
248
340
|
"""
|
|
249
341
|
Call /v1/traces/complete to report file sizes to gateway.
|
sentience/extension/release.json
CHANGED
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"state": "uploaded",
|
|
68
68
|
"size": 78091,
|
|
69
69
|
"digest": "sha256:e281f8b755b61da4b8015d6172064aa9a337c14133ceceff4ab29199ee53307e",
|
|
70
|
-
"download_count":
|
|
70
|
+
"download_count": 2,
|
|
71
71
|
"created_at": "2025-12-29T03:57:09Z",
|
|
72
72
|
"updated_at": "2025-12-29T03:57:09Z",
|
|
73
73
|
"browser_download_url": "https://github.com/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/download/v2.0.7/extension-files.tar.gz"
|
sentience/text_search.py
CHANGED
|
@@ -88,6 +88,47 @@ def find_text_rect(
|
|
|
88
88
|
# Limit max_results to prevent performance issues
|
|
89
89
|
max_results = min(max_results, 100)
|
|
90
90
|
|
|
91
|
+
# CRITICAL: Wait for extension injection to complete (CSP-resistant architecture)
|
|
92
|
+
# The new architecture loads injected_api.js asynchronously, so window.sentience
|
|
93
|
+
# may not be immediately available after page load
|
|
94
|
+
try:
|
|
95
|
+
browser.page.wait_for_function(
|
|
96
|
+
"typeof window.sentience !== 'undefined'",
|
|
97
|
+
timeout=5000, # 5 second timeout
|
|
98
|
+
)
|
|
99
|
+
except Exception as e:
|
|
100
|
+
# Gather diagnostics if wait fails
|
|
101
|
+
try:
|
|
102
|
+
diag = browser.page.evaluate(
|
|
103
|
+
"""() => ({
|
|
104
|
+
sentience_defined: typeof window.sentience !== 'undefined',
|
|
105
|
+
extension_id: document.documentElement.dataset.sentienceExtensionId || 'not set',
|
|
106
|
+
url: window.location.href
|
|
107
|
+
})"""
|
|
108
|
+
)
|
|
109
|
+
except Exception:
|
|
110
|
+
diag = {"error": "Could not gather diagnostics"}
|
|
111
|
+
|
|
112
|
+
raise RuntimeError(
|
|
113
|
+
f"Sentience extension failed to inject window.sentience API. "
|
|
114
|
+
f"Is the extension loaded? Diagnostics: {diag}"
|
|
115
|
+
) from e
|
|
116
|
+
|
|
117
|
+
# Verify findTextRect method exists (for older extension versions that don't have it)
|
|
118
|
+
try:
|
|
119
|
+
has_find_text_rect = browser.page.evaluate(
|
|
120
|
+
"typeof window.sentience.findTextRect !== 'undefined'"
|
|
121
|
+
)
|
|
122
|
+
if not has_find_text_rect:
|
|
123
|
+
raise RuntimeError(
|
|
124
|
+
"window.sentience.findTextRect is not available. "
|
|
125
|
+
"Please update the Sentience extension to the latest version."
|
|
126
|
+
)
|
|
127
|
+
except RuntimeError:
|
|
128
|
+
raise
|
|
129
|
+
except Exception as e:
|
|
130
|
+
raise RuntimeError(f"Failed to verify findTextRect availability: {e}") from e
|
|
131
|
+
|
|
91
132
|
# Call the extension's findTextRect method
|
|
92
133
|
result_dict = browser.page.evaluate(
|
|
93
134
|
"""
|
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
Trace indexing module for Sentience SDK.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from .indexer import build_trace_index, write_trace_index, read_step_events
|
|
6
5
|
from .index_schema import (
|
|
7
|
-
TraceIndex,
|
|
8
|
-
StepIndex,
|
|
9
|
-
TraceSummary,
|
|
10
|
-
TraceFileInfo,
|
|
11
|
-
SnapshotInfo,
|
|
12
6
|
ActionInfo,
|
|
7
|
+
SnapshotInfo,
|
|
13
8
|
StepCounters,
|
|
9
|
+
StepIndex,
|
|
10
|
+
TraceFileInfo,
|
|
11
|
+
TraceIndex,
|
|
12
|
+
TraceSummary,
|
|
14
13
|
)
|
|
14
|
+
from .indexer import build_trace_index, read_step_events, write_trace_index
|
|
15
15
|
|
|
16
16
|
__all__ = [
|
|
17
17
|
"build_trace_index",
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
Type definitions for trace index schema using concrete classes.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from dataclasses import dataclass, field
|
|
6
|
-
from typing import
|
|
5
|
+
from dataclasses import asdict, dataclass, field
|
|
6
|
+
from typing import List, Literal, Optional
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
@dataclass
|
|
@@ -27,7 +27,7 @@ class TraceSummary:
|
|
|
27
27
|
event_count: int
|
|
28
28
|
step_count: int
|
|
29
29
|
error_count: int
|
|
30
|
-
final_url:
|
|
30
|
+
final_url: str | None
|
|
31
31
|
|
|
32
32
|
def to_dict(self) -> dict:
|
|
33
33
|
return asdict(self)
|
|
@@ -37,9 +37,9 @@ class TraceSummary:
|
|
|
37
37
|
class SnapshotInfo:
|
|
38
38
|
"""Snapshot metadata for index."""
|
|
39
39
|
|
|
40
|
-
snapshot_id:
|
|
41
|
-
digest:
|
|
42
|
-
url:
|
|
40
|
+
snapshot_id: str | None = None
|
|
41
|
+
digest: str | None = None
|
|
42
|
+
url: str | None = None
|
|
43
43
|
|
|
44
44
|
def to_dict(self) -> dict:
|
|
45
45
|
return asdict(self)
|
|
@@ -49,10 +49,10 @@ class SnapshotInfo:
|
|
|
49
49
|
class ActionInfo:
|
|
50
50
|
"""Action metadata for index."""
|
|
51
51
|
|
|
52
|
-
type:
|
|
53
|
-
target_element_id:
|
|
54
|
-
args_digest:
|
|
55
|
-
success:
|
|
52
|
+
type: str | None = None
|
|
53
|
+
target_element_id: int | None = None
|
|
54
|
+
args_digest: str | None = None
|
|
55
|
+
success: bool | None = None
|
|
56
56
|
|
|
57
57
|
def to_dict(self) -> dict:
|
|
58
58
|
return asdict(self)
|
|
@@ -77,14 +77,14 @@ class StepIndex:
|
|
|
77
77
|
|
|
78
78
|
step_index: int
|
|
79
79
|
step_id: str
|
|
80
|
-
goal:
|
|
80
|
+
goal: str | None
|
|
81
81
|
status: Literal["ok", "error", "partial"]
|
|
82
82
|
ts_start: str
|
|
83
83
|
ts_end: str
|
|
84
84
|
offset_start: int
|
|
85
85
|
offset_end: int
|
|
86
|
-
url_before:
|
|
87
|
-
url_after:
|
|
86
|
+
url_before: str | None
|
|
87
|
+
url_after: str | None
|
|
88
88
|
snapshot_before: SnapshotInfo
|
|
89
89
|
snapshot_after: SnapshotInfo
|
|
90
90
|
action: ActionInfo
|
|
@@ -104,7 +104,7 @@ class TraceIndex:
|
|
|
104
104
|
created_at: str
|
|
105
105
|
trace_file: TraceFileInfo
|
|
106
106
|
summary: TraceSummary
|
|
107
|
-
steps:
|
|
107
|
+
steps: list[StepIndex] = field(default_factory=list)
|
|
108
108
|
|
|
109
109
|
def to_dict(self) -> dict:
|
|
110
110
|
"""Convert to dictionary for JSON serialization."""
|
|
@@ -10,13 +10,13 @@ from pathlib import Path
|
|
|
10
10
|
from typing import Any, Dict, List
|
|
11
11
|
|
|
12
12
|
from .index_schema import (
|
|
13
|
-
TraceIndex,
|
|
14
|
-
StepIndex,
|
|
15
|
-
TraceSummary,
|
|
16
|
-
TraceFileInfo,
|
|
17
|
-
SnapshotInfo,
|
|
18
13
|
ActionInfo,
|
|
14
|
+
SnapshotInfo,
|
|
19
15
|
StepCounters,
|
|
16
|
+
StepIndex,
|
|
17
|
+
TraceFileInfo,
|
|
18
|
+
TraceIndex,
|
|
19
|
+
TraceSummary,
|
|
20
20
|
)
|
|
21
21
|
|
|
22
22
|
|
|
@@ -34,7 +34,7 @@ def _normalize_text(text: str | None, max_len: int = 80) -> str:
|
|
|
34
34
|
return normalized
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
def _round_bbox(bbox:
|
|
37
|
+
def _round_bbox(bbox: dict[str, float], precision: int = 2) -> dict[str, int]:
|
|
38
38
|
"""Round bbox coordinates to reduce noise (default: 2px precision)."""
|
|
39
39
|
return {
|
|
40
40
|
"x": round(bbox.get("x", 0) / precision) * precision,
|
|
@@ -44,7 +44,7 @@ def _round_bbox(bbox: Dict[str, float], precision: int = 2) -> Dict[str, int]:
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
def _compute_snapshot_digest(snapshot_data:
|
|
47
|
+
def _compute_snapshot_digest(snapshot_data: dict[str, Any]) -> str:
|
|
48
48
|
"""
|
|
49
49
|
Compute stable digest of snapshot for diffing.
|
|
50
50
|
|
|
@@ -62,9 +62,7 @@ def _compute_snapshot_digest(snapshot_data: Dict[str, Any]) -> str:
|
|
|
62
62
|
"id": elem.get("id"),
|
|
63
63
|
"role": elem.get("role", ""),
|
|
64
64
|
"text_norm": _normalize_text(elem.get("text")),
|
|
65
|
-
"bbox": _round_bbox(
|
|
66
|
-
elem.get("bbox", {"x": 0, "y": 0, "width": 0, "height": 0})
|
|
67
|
-
),
|
|
65
|
+
"bbox": _round_bbox(elem.get("bbox", {"x": 0, "y": 0, "width": 0, "height": 0})),
|
|
68
66
|
"is_primary": elem.get("is_primary", False),
|
|
69
67
|
"is_clickable": elem.get("is_clickable", False),
|
|
70
68
|
}
|
|
@@ -89,7 +87,7 @@ def _compute_snapshot_digest(snapshot_data: Dict[str, Any]) -> str:
|
|
|
89
87
|
return f"sha256:{digest}"
|
|
90
88
|
|
|
91
89
|
|
|
92
|
-
def _compute_action_digest(action_data:
|
|
90
|
+
def _compute_action_digest(action_data: dict[str, Any]) -> str:
|
|
93
91
|
"""
|
|
94
92
|
Compute digest of action args for privacy + determinism.
|
|
95
93
|
|
|
@@ -152,8 +150,8 @@ def build_trace_index(trace_path: str) -> TraceIndex:
|
|
|
152
150
|
error_count = 0
|
|
153
151
|
final_url = None
|
|
154
152
|
|
|
155
|
-
steps_by_id:
|
|
156
|
-
step_order:
|
|
153
|
+
steps_by_id: dict[str, StepIndex] = {}
|
|
154
|
+
step_order: list[str] = [] # Track order of first appearance
|
|
157
155
|
|
|
158
156
|
# Stream through file, tracking byte offsets
|
|
159
157
|
with open(trace_path, "rb") as f:
|
|
@@ -228,9 +226,7 @@ def build_trace_index(trace_path: str) -> TraceIndex:
|
|
|
228
226
|
)
|
|
229
227
|
step.url_before = step.url_before or url
|
|
230
228
|
|
|
231
|
-
step.snapshot_after = SnapshotInfo(
|
|
232
|
-
snapshot_id=snapshot_id, digest=digest, url=url
|
|
233
|
-
)
|
|
229
|
+
step.snapshot_after = SnapshotInfo(snapshot_id=snapshot_id, digest=digest, url=url)
|
|
234
230
|
step.url_after = url
|
|
235
231
|
step.counters.snapshots += 1
|
|
236
232
|
final_url = url
|
|
@@ -311,9 +307,7 @@ def write_trace_index(trace_path: str, index_path: str | None = None) -> str:
|
|
|
311
307
|
return index_path
|
|
312
308
|
|
|
313
309
|
|
|
314
|
-
def read_step_events(
|
|
315
|
-
trace_path: str, offset_start: int, offset_end: int
|
|
316
|
-
) -> List[Dict[str, Any]]:
|
|
310
|
+
def read_step_events(trace_path: str, offset_start: int, offset_end: int) -> list[dict[str, Any]]:
|
|
317
311
|
"""
|
|
318
312
|
Read events for a specific step using byte offsets from index.
|
|
319
313
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
sentience/__init__.py,sha256=
|
|
1
|
+
sentience/__init__.py,sha256=DETDLYDNA_A5llT6mjgcvB4FPuCELiD8ppsz3hvTEbs,3410
|
|
2
2
|
sentience/actions.py,sha256=RT8AoR_CNyp6bMCYhkToWH1WB8cyB8dcFJZMhfskTeo,13239
|
|
3
3
|
sentience/agent.py,sha256=rTAaQGiuZIohrJZXpCK-dZWuoAR3k_dQodjFLBkrCxY,24527
|
|
4
4
|
sentience/agent_config.py,sha256=blyCUh2pjMOI6YQGLWP8mSi6GTnpzWLWem4nwYg3ngA,1489
|
|
5
5
|
sentience/base_agent.py,sha256=861XkIJeig9nqdt7f2wfFKyxanGZtDhnrgsMDdXtkFM,2979
|
|
6
|
-
sentience/browser.py,sha256=
|
|
6
|
+
sentience/browser.py,sha256=jB59HfZHHUOKTORA7gsaQlIKEZyxwivvW4VU9swyy_k,21311
|
|
7
7
|
sentience/cli.py,sha256=R95DgCSJmezuymAdfL6fzUdB1HpCF3iMWcLO73KJ8eY,3851
|
|
8
|
-
sentience/cloud_tracing.py,sha256=
|
|
8
|
+
sentience/cloud_tracing.py,sha256=KxVIT-HQW9EaX5GWC9DEVM2cdObomD3hkPNEnTceSpc,13541
|
|
9
9
|
sentience/conversational_agent.py,sha256=UUgltp-EkAbAzE6Pa_S72t7fWbdIwb3C2YppXzPx2jA,17207
|
|
10
10
|
sentience/expect.py,sha256=BFSRocNJr6ZAusb6fy4OgfDNCukGfu5jto760bFN12E,2851
|
|
11
11
|
sentience/formatting.py,sha256=0_UklIUd94a4W32gTorjxoXNE03qdffR-omfTA5eHF0,1828
|
|
@@ -19,7 +19,7 @@ sentience/read.py,sha256=qoSbahcNumvb0mUe-WcWrKrkKCEqya6Ac7wqXFe3SRI,3490
|
|
|
19
19
|
sentience/recorder.py,sha256=RWzkJP6LWiFAJUfbXQS3aAnRgl7aDrWuKw3slp-gR1Y,12496
|
|
20
20
|
sentience/screenshot.py,sha256=1UJjRvPxPpiL3w1_MBvO_UPMAsmt-uyRujTYA50jim0,1607
|
|
21
21
|
sentience/snapshot.py,sha256=zFQa-jGh8MBPuyvZZ3kam_d8qN93UtmigCvj9m2V5Ts,9947
|
|
22
|
-
sentience/text_search.py,sha256=
|
|
22
|
+
sentience/text_search.py,sha256=UF7-Dlo69Stx_vkfcu5ss3dKCVgyWODrafJoJXU9NCw,5753
|
|
23
23
|
sentience/tracer_factory.py,sha256=Fc_kb8ffJVGdy91cnxpxU2xjFZUyS2OiaI2pqtvl2lk,7643
|
|
24
24
|
sentience/tracing.py,sha256=ciYSW3QXcO0BYDtjeKQLfpAysObBIKQKCAlviwU74AQ,7240
|
|
25
25
|
sentience/utils.py,sha256=ryxiDVE_sUPHYmYnWYXADuyciD_ZawmsITDZNUeDVJg,8116
|
|
@@ -28,19 +28,19 @@ sentience/extension/background.js,sha256=hnK32hVtHJrLtzBeXt2beRmMzr1l0U82r8zZEXy
|
|
|
28
28
|
sentience/extension/content.js,sha256=UIuFCDTVTkXdaiXB8LMf0OP8SB_WMrMoaav5ZBXmeIo,10442
|
|
29
29
|
sentience/extension/injected_api.js,sha256=Pr5j9MGJTInPOKfKKNuaMDjOJSK3ygM-HlmWAUUS-Yg,63790
|
|
30
30
|
sentience/extension/manifest.json,sha256=MfkfsS5zVxb-scqJllN8aubrRN9vsznQ3-Aw69pZ47c,897
|
|
31
|
-
sentience/extension/release.json,sha256=
|
|
31
|
+
sentience/extension/release.json,sha256=ikdgGGy_Jf6r_P6dKOUUaVZOQYMIRHz3ok5dVclxoT8,6437
|
|
32
32
|
sentience/extension/test-content.js,sha256=RX6A42W-5pjP-avqGwrRq_GVp5yX2NqBDlDLc-SWL5g,156
|
|
33
33
|
sentience/extension/pkg/sentience_core.d.ts,sha256=qrTEIR2WPkk1MmaSQzEpRyagbE9VirHowzZpbj41qeQ,1981
|
|
34
34
|
sentience/extension/pkg/sentience_core.js,sha256=zldlOubec0pbNYhquwDzoQKuBF4SUVcIeXW_qLx0dVA,17904
|
|
35
35
|
sentience/extension/pkg/sentience_core_bg.wasm,sha256=HtzBY8z4XgRgHHUqHQGm84EEQJirpf25wSVTXg-iJ1g,102522
|
|
36
36
|
sentience/extension/pkg/sentience_core_bg.wasm.d.ts,sha256=O3c3HaUqmB6Aob6Pt0n3GEtTxM4VGeaClaA_-z2m2J4,517
|
|
37
37
|
sentience/schemas/trace_v1.json,sha256=XpByPqRH1xzHmD7YeuaDE5ZaEb3L5fz762djcwXqPV8,7593
|
|
38
|
-
sentience/trace_indexing/__init__.py,sha256=
|
|
39
|
-
sentience/trace_indexing/index_schema.py,sha256=
|
|
40
|
-
sentience/trace_indexing/indexer.py,sha256=
|
|
41
|
-
sentienceapi-0.90.
|
|
42
|
-
sentienceapi-0.90.
|
|
43
|
-
sentienceapi-0.90.
|
|
44
|
-
sentienceapi-0.90.
|
|
45
|
-
sentienceapi-0.90.
|
|
46
|
-
sentienceapi-0.90.
|
|
38
|
+
sentience/trace_indexing/__init__.py,sha256=urjLuqqXCQE8pnwpYBqoMKnzZSqFJkM1SHmFve9RVE8,499
|
|
39
|
+
sentience/trace_indexing/index_schema.py,sha256=Hz9igHFrHATTZOqKNiLhESjnGbDPTyRTejMGT1AKC8g,2180
|
|
40
|
+
sentience/trace_indexing/indexer.py,sha256=m-p57Va0cFlCpuMWkXNSHqODpGzpESZzoOkt3OiejlM,10811
|
|
41
|
+
sentienceapi-0.90.12.dist-info/licenses/LICENSE.md,sha256=Pl8BWUOOkFtYpgVhNwCl3YKzRvUpYid2utlJELIgr_8,3783
|
|
42
|
+
sentienceapi-0.90.12.dist-info/METADATA,sha256=DblGVp6eXuV5WD44nR-auKp93Ig7iiXVoG-LajlznVc,26131
|
|
43
|
+
sentienceapi-0.90.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
44
|
+
sentienceapi-0.90.12.dist-info/entry_points.txt,sha256=HdW1BvgRJm3ZAbbqrwTvDWE2KbmVz-Ue0wllW-mLmvA,49
|
|
45
|
+
sentienceapi-0.90.12.dist-info/top_level.txt,sha256=A9IKao--8PsFFz5vDfBIXWHgN6oh3HkMQSiQWgUTUBQ,10
|
|
46
|
+
sentienceapi-0.90.12.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|