sentienceapi 0.90.9__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 +153 -0
- sentience/actions.py +439 -0
- sentience/agent.py +687 -0
- sentience/agent_config.py +43 -0
- sentience/base_agent.py +101 -0
- sentience/browser.py +409 -0
- sentience/cli.py +130 -0
- sentience/cloud_tracing.py +292 -0
- sentience/conversational_agent.py +509 -0
- sentience/expect.py +92 -0
- sentience/extension/background.js +233 -0
- sentience/extension/content.js +298 -0
- sentience/extension/injected_api.js +1473 -0
- sentience/extension/manifest.json +36 -0
- sentience/extension/pkg/sentience_core.d.ts +51 -0
- sentience/extension/pkg/sentience_core.js +529 -0
- sentience/extension/pkg/sentience_core_bg.wasm +0 -0
- sentience/extension/pkg/sentience_core_bg.wasm.d.ts +10 -0
- sentience/extension/release.json +115 -0
- sentience/extension/test-content.js +4 -0
- sentience/formatting.py +59 -0
- sentience/generator.py +202 -0
- sentience/inspector.py +185 -0
- sentience/llm_provider.py +431 -0
- sentience/models.py +406 -0
- sentience/overlay.py +115 -0
- sentience/query.py +303 -0
- sentience/read.py +96 -0
- sentience/recorder.py +369 -0
- sentience/schemas/trace_v1.json +216 -0
- sentience/screenshot.py +54 -0
- sentience/snapshot.py +282 -0
- sentience/text_search.py +107 -0
- sentience/trace_indexing/__init__.py +27 -0
- sentience/trace_indexing/index_schema.py +111 -0
- sentience/trace_indexing/indexer.py +363 -0
- sentience/tracer_factory.py +211 -0
- sentience/tracing.py +285 -0
- sentience/utils.py +296 -0
- sentience/wait.py +73 -0
- sentienceapi-0.90.9.dist-info/METADATA +878 -0
- sentienceapi-0.90.9.dist-info/RECORD +46 -0
- sentienceapi-0.90.9.dist-info/WHEEL +5 -0
- sentienceapi-0.90.9.dist-info/entry_points.txt +2 -0
- sentienceapi-0.90.9.dist-info/licenses/LICENSE.md +43 -0
- sentienceapi-0.90.9.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
{
|
|
2
|
+
"url": "https://api.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/273122615",
|
|
3
|
+
"assets_url": "https://api.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/273122615/assets",
|
|
4
|
+
"upload_url": "https://uploads.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/273122615/assets{?name,label}",
|
|
5
|
+
"html_url": "https://github.com/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/tag/v2.0.7",
|
|
6
|
+
"id": 273122615,
|
|
7
|
+
"author": {
|
|
8
|
+
"login": "github-actions[bot]",
|
|
9
|
+
"id": 41898282,
|
|
10
|
+
"node_id": "MDM6Qm90NDE4OTgyODI=",
|
|
11
|
+
"avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
|
|
12
|
+
"gravatar_id": "",
|
|
13
|
+
"url": "https://api.github.com/users/github-actions%5Bbot%5D",
|
|
14
|
+
"html_url": "https://github.com/apps/github-actions",
|
|
15
|
+
"followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
|
|
16
|
+
"following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
|
|
17
|
+
"gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
|
|
18
|
+
"starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
|
|
19
|
+
"subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
|
|
20
|
+
"organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
|
|
21
|
+
"repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
|
|
22
|
+
"events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
|
|
23
|
+
"received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
|
|
24
|
+
"type": "Bot",
|
|
25
|
+
"user_view_type": "public",
|
|
26
|
+
"site_admin": false
|
|
27
|
+
},
|
|
28
|
+
"node_id": "RE_kwDOQshiJ84QR4U3",
|
|
29
|
+
"tag_name": "v2.0.7",
|
|
30
|
+
"target_commitish": "main",
|
|
31
|
+
"name": "Release v2.0.7",
|
|
32
|
+
"draft": false,
|
|
33
|
+
"immutable": false,
|
|
34
|
+
"prerelease": false,
|
|
35
|
+
"created_at": "2025-12-29T03:56:13Z",
|
|
36
|
+
"updated_at": "2025-12-29T03:57:09Z",
|
|
37
|
+
"published_at": "2025-12-29T03:57:08Z",
|
|
38
|
+
"assets": [
|
|
39
|
+
{
|
|
40
|
+
"url": "https://api.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/assets/333966751",
|
|
41
|
+
"id": 333966751,
|
|
42
|
+
"node_id": "RA_kwDOQshiJ84T5-2f",
|
|
43
|
+
"name": "extension-files.tar.gz",
|
|
44
|
+
"label": "",
|
|
45
|
+
"uploader": {
|
|
46
|
+
"login": "github-actions[bot]",
|
|
47
|
+
"id": 41898282,
|
|
48
|
+
"node_id": "MDM6Qm90NDE4OTgyODI=",
|
|
49
|
+
"avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
|
|
50
|
+
"gravatar_id": "",
|
|
51
|
+
"url": "https://api.github.com/users/github-actions%5Bbot%5D",
|
|
52
|
+
"html_url": "https://github.com/apps/github-actions",
|
|
53
|
+
"followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
|
|
54
|
+
"following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
|
|
55
|
+
"gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
|
|
56
|
+
"starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
|
|
57
|
+
"subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
|
|
58
|
+
"organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
|
|
59
|
+
"repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
|
|
60
|
+
"events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
|
|
61
|
+
"received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
|
|
62
|
+
"type": "Bot",
|
|
63
|
+
"user_view_type": "public",
|
|
64
|
+
"site_admin": false
|
|
65
|
+
},
|
|
66
|
+
"content_type": "application/gzip",
|
|
67
|
+
"state": "uploaded",
|
|
68
|
+
"size": 78091,
|
|
69
|
+
"digest": "sha256:e281f8b755b61da4b8015d6172064aa9a337c14133ceceff4ab29199ee53307e",
|
|
70
|
+
"download_count": 0,
|
|
71
|
+
"created_at": "2025-12-29T03:57:09Z",
|
|
72
|
+
"updated_at": "2025-12-29T03:57:09Z",
|
|
73
|
+
"browser_download_url": "https://github.com/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/download/v2.0.7/extension-files.tar.gz"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"url": "https://api.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/assets/333966752",
|
|
77
|
+
"id": 333966752,
|
|
78
|
+
"node_id": "RA_kwDOQshiJ84T5-2g",
|
|
79
|
+
"name": "extension-package.zip",
|
|
80
|
+
"label": "",
|
|
81
|
+
"uploader": {
|
|
82
|
+
"login": "github-actions[bot]",
|
|
83
|
+
"id": 41898282,
|
|
84
|
+
"node_id": "MDM6Qm90NDE4OTgyODI=",
|
|
85
|
+
"avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
|
|
86
|
+
"gravatar_id": "",
|
|
87
|
+
"url": "https://api.github.com/users/github-actions%5Bbot%5D",
|
|
88
|
+
"html_url": "https://github.com/apps/github-actions",
|
|
89
|
+
"followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
|
|
90
|
+
"following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
|
|
91
|
+
"gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
|
|
92
|
+
"starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
|
|
93
|
+
"subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
|
|
94
|
+
"organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
|
|
95
|
+
"repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
|
|
96
|
+
"events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
|
|
97
|
+
"received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
|
|
98
|
+
"type": "Bot",
|
|
99
|
+
"user_view_type": "public",
|
|
100
|
+
"site_admin": false
|
|
101
|
+
},
|
|
102
|
+
"content_type": "application/zip",
|
|
103
|
+
"state": "uploaded",
|
|
104
|
+
"size": 80179,
|
|
105
|
+
"digest": "sha256:a025edeb8b6d05bfb25c57f913b68507060653ecbdf616000a46df4cb8dec377",
|
|
106
|
+
"download_count": 0,
|
|
107
|
+
"created_at": "2025-12-29T03:57:09Z",
|
|
108
|
+
"updated_at": "2025-12-29T03:57:09Z",
|
|
109
|
+
"browser_download_url": "https://github.com/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/download/v2.0.7/extension-package.zip"
|
|
110
|
+
}
|
|
111
|
+
],
|
|
112
|
+
"tarball_url": "https://api.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/tarball/v2.0.7",
|
|
113
|
+
"zipball_url": "https://api.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/zipball/v2.0.7",
|
|
114
|
+
"body": "**Full Changelog**: https://github.com/SentienceAPI/Sentience-Geometry-Chrome-Extension/compare/v2.0.6...v2.0.7"
|
|
115
|
+
}
|
sentience/formatting.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Snapshot formatting utilities for LLM prompts.
|
|
3
|
+
|
|
4
|
+
Provides functions to convert Sentience snapshots into text format suitable
|
|
5
|
+
for LLM consumption.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import List
|
|
9
|
+
|
|
10
|
+
from .models import Snapshot
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def format_snapshot_for_llm(snap: Snapshot, limit: int = 50) -> str:
|
|
14
|
+
"""
|
|
15
|
+
Convert snapshot elements to text format for LLM consumption.
|
|
16
|
+
|
|
17
|
+
This is the canonical way Sentience formats DOM state for LLMs.
|
|
18
|
+
The format includes element ID, role, text preview, visual cues,
|
|
19
|
+
position, and importance score.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
snap: Snapshot object with elements
|
|
23
|
+
limit: Maximum number of elements to include (default: 50)
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Formatted string with one element per line
|
|
27
|
+
|
|
28
|
+
Example:
|
|
29
|
+
>>> snap = snapshot(browser)
|
|
30
|
+
>>> formatted = format_snapshot_for_llm(snap, limit=10)
|
|
31
|
+
>>> print(formatted)
|
|
32
|
+
[1] <button> "Sign In" {PRIMARY,CLICKABLE} @ (100,50) (Imp:10)
|
|
33
|
+
[2] <input> "Email address" @ (100,100) (Imp:8)
|
|
34
|
+
...
|
|
35
|
+
"""
|
|
36
|
+
lines: list[str] = []
|
|
37
|
+
|
|
38
|
+
for el in snap.elements[:limit]:
|
|
39
|
+
# Build visual cues string
|
|
40
|
+
cues = []
|
|
41
|
+
if getattr(el.visual_cues, "is_primary", False):
|
|
42
|
+
cues.append("PRIMARY")
|
|
43
|
+
if getattr(el.visual_cues, "is_clickable", False):
|
|
44
|
+
cues.append("CLICKABLE")
|
|
45
|
+
|
|
46
|
+
cues_str = f" {{{','.join(cues)}}}" if cues else ""
|
|
47
|
+
|
|
48
|
+
# Format text preview (truncate to 50 chars)
|
|
49
|
+
text_preview = el.text or ""
|
|
50
|
+
if len(text_preview) > 50:
|
|
51
|
+
text_preview = text_preview[:50] + "..."
|
|
52
|
+
|
|
53
|
+
# Build element line: [ID] <role> "text" {cues} @ (x,y) (Imp:score)
|
|
54
|
+
lines.append(
|
|
55
|
+
f'[{el.id}] <{el.role}> "{text_preview}"{cues_str} '
|
|
56
|
+
f"@ ({int(el.bbox.x)},{int(el.bbox.y)}) (Imp:{el.importance})"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return "\n".join(lines)
|
sentience/generator.py
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Script Generator - converts trace into executable code
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .recorder import Trace, TraceStep
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ScriptGenerator:
|
|
9
|
+
"""Generates Python or TypeScript code from a trace"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, trace: Trace):
|
|
12
|
+
self.trace = trace
|
|
13
|
+
|
|
14
|
+
def generate_python(self) -> str:
|
|
15
|
+
"""Generate Python script from trace"""
|
|
16
|
+
lines = [
|
|
17
|
+
'"""',
|
|
18
|
+
f"Generated script from trace: {self.trace.start_url}",
|
|
19
|
+
f"Created: {self.trace.created_at}",
|
|
20
|
+
'"""',
|
|
21
|
+
"",
|
|
22
|
+
"from sentience import SentienceBrowser, snapshot, find, click, type_text, press",
|
|
23
|
+
"",
|
|
24
|
+
"def main():",
|
|
25
|
+
" with SentienceBrowser(headless=False) as browser:",
|
|
26
|
+
f' browser.page.goto("{self.trace.start_url}")',
|
|
27
|
+
' browser.page.wait_for_load_state("networkidle")',
|
|
28
|
+
"",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
for step in self.trace.steps:
|
|
32
|
+
lines.extend(self._generate_python_step(step, indent=" "))
|
|
33
|
+
|
|
34
|
+
lines.extend(
|
|
35
|
+
[
|
|
36
|
+
"",
|
|
37
|
+
'if __name__ == "__main__":',
|
|
38
|
+
" main()",
|
|
39
|
+
]
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
return "\n".join(lines)
|
|
43
|
+
|
|
44
|
+
def generate_typescript(self) -> str:
|
|
45
|
+
"""Generate TypeScript script from trace"""
|
|
46
|
+
lines = [
|
|
47
|
+
"/**",
|
|
48
|
+
f" * Generated script from trace: {self.trace.start_url}",
|
|
49
|
+
f" * Created: {self.trace.created_at}",
|
|
50
|
+
" */",
|
|
51
|
+
"",
|
|
52
|
+
"import { SentienceBrowser, snapshot, find, click, typeText, press } from './src';",
|
|
53
|
+
"",
|
|
54
|
+
"async function main() {",
|
|
55
|
+
" const browser = new SentienceBrowser(undefined, false);",
|
|
56
|
+
"",
|
|
57
|
+
" try {",
|
|
58
|
+
" await browser.start();",
|
|
59
|
+
f" await browser.getPage().goto('{self.trace.start_url}');",
|
|
60
|
+
" await browser.getPage().waitForLoadState('networkidle');",
|
|
61
|
+
"",
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
for step in self.trace.steps:
|
|
65
|
+
lines.extend(self._generate_typescript_step(step, indent=" "))
|
|
66
|
+
|
|
67
|
+
lines.extend(
|
|
68
|
+
[
|
|
69
|
+
" } finally {",
|
|
70
|
+
" await browser.close();",
|
|
71
|
+
" }",
|
|
72
|
+
"}",
|
|
73
|
+
"",
|
|
74
|
+
"main().catch(console.error);",
|
|
75
|
+
]
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
return "\n".join(lines)
|
|
79
|
+
|
|
80
|
+
def _generate_python_step(self, step: TraceStep, indent: str = "") -> list[str]:
|
|
81
|
+
"""Generate Python code for a single step"""
|
|
82
|
+
lines = []
|
|
83
|
+
|
|
84
|
+
if step.type == "navigation":
|
|
85
|
+
lines.append(f"{indent}# Navigate to {step.url}")
|
|
86
|
+
lines.append(f'{indent}browser.page.goto("{step.url}")')
|
|
87
|
+
lines.append(f'{indent}browser.page.wait_for_load_state("networkidle")')
|
|
88
|
+
|
|
89
|
+
elif step.type == "click":
|
|
90
|
+
if step.selector:
|
|
91
|
+
# Use semantic selector
|
|
92
|
+
lines.append(f"{indent}# Click: {step.selector}")
|
|
93
|
+
lines.append(f"{indent}snap = snapshot(browser)")
|
|
94
|
+
lines.append(f'{indent}element = find(snap, "{step.selector}")')
|
|
95
|
+
lines.append(f"{indent}if element:")
|
|
96
|
+
lines.append(f"{indent} click(browser, element.id)")
|
|
97
|
+
lines.append(f"{indent}else:")
|
|
98
|
+
lines.append(f'{indent} raise Exception("Element not found: {step.selector}")')
|
|
99
|
+
elif step.element_id is not None:
|
|
100
|
+
# Fallback to element ID
|
|
101
|
+
lines.append(f"{indent}# TODO: replace with semantic selector")
|
|
102
|
+
lines.append(f"{indent}click(browser, {step.element_id})")
|
|
103
|
+
lines.append("")
|
|
104
|
+
|
|
105
|
+
elif step.type == "type":
|
|
106
|
+
if step.selector:
|
|
107
|
+
lines.append(f"{indent}# Type into: {step.selector}")
|
|
108
|
+
lines.append(f"{indent}snap = snapshot(browser)")
|
|
109
|
+
lines.append(f'{indent}element = find(snap, "{step.selector}")')
|
|
110
|
+
lines.append(f"{indent}if element:")
|
|
111
|
+
lines.append(f'{indent} type_text(browser, element.id, "{step.text}")')
|
|
112
|
+
lines.append(f"{indent}else:")
|
|
113
|
+
lines.append(f'{indent} raise Exception("Element not found: {step.selector}")')
|
|
114
|
+
elif step.element_id is not None:
|
|
115
|
+
lines.append(f"{indent}# TODO: replace with semantic selector")
|
|
116
|
+
lines.append(f'{indent}type_text(browser, {step.element_id}, "{step.text}")')
|
|
117
|
+
lines.append("")
|
|
118
|
+
|
|
119
|
+
elif step.type == "press":
|
|
120
|
+
lines.append(f"{indent}# Press key: {step.key}")
|
|
121
|
+
lines.append(f'{indent}press(browser, "{step.key}")')
|
|
122
|
+
lines.append("")
|
|
123
|
+
|
|
124
|
+
return lines
|
|
125
|
+
|
|
126
|
+
def _generate_typescript_step(self, step: TraceStep, indent: str = "") -> list[str]:
|
|
127
|
+
"""Generate TypeScript code for a single step"""
|
|
128
|
+
lines = []
|
|
129
|
+
|
|
130
|
+
if step.type == "navigation":
|
|
131
|
+
lines.append(f"{indent}// Navigate to {step.url}")
|
|
132
|
+
lines.append(f"{indent}await browser.getPage().goto('{step.url}');")
|
|
133
|
+
lines.append(f"{indent}await browser.getPage().waitForLoadState('networkidle');")
|
|
134
|
+
|
|
135
|
+
elif step.type == "click":
|
|
136
|
+
if step.selector:
|
|
137
|
+
lines.append(f"{indent}// Click: {step.selector}")
|
|
138
|
+
lines.append(f"{indent}const snap = await snapshot(browser);")
|
|
139
|
+
lines.append(f"{indent}const element = find(snap, '{step.selector}');")
|
|
140
|
+
lines.append(f"{indent}if (element) {{")
|
|
141
|
+
lines.append(f"{indent} await click(browser, element.id);")
|
|
142
|
+
lines.append(f"{indent}}} else {{")
|
|
143
|
+
lines.append(f"{indent} throw new Error('Element not found: {step.selector}');")
|
|
144
|
+
lines.append(f"{indent}}}")
|
|
145
|
+
elif step.element_id is not None:
|
|
146
|
+
lines.append(f"{indent}// TODO: replace with semantic selector")
|
|
147
|
+
lines.append(f"{indent}await click(browser, {step.element_id});")
|
|
148
|
+
lines.append("")
|
|
149
|
+
|
|
150
|
+
elif step.type == "type":
|
|
151
|
+
if step.selector:
|
|
152
|
+
lines.append(f"{indent}// Type into: {step.selector}")
|
|
153
|
+
lines.append(f"{indent}const snap = await snapshot(browser);")
|
|
154
|
+
lines.append(f"{indent}const element = find(snap, '{step.selector}');")
|
|
155
|
+
lines.append(f"{indent}if (element) {{")
|
|
156
|
+
lines.append(f"{indent} await typeText(browser, element.id, '{step.text}');")
|
|
157
|
+
lines.append(f"{indent}}} else {{")
|
|
158
|
+
lines.append(f"{indent} throw new Error('Element not found: {step.selector}');")
|
|
159
|
+
lines.append(f"{indent}}}")
|
|
160
|
+
elif step.element_id is not None:
|
|
161
|
+
lines.append(f"{indent}// TODO: replace with semantic selector")
|
|
162
|
+
lines.append(f"{indent}await typeText(browser, {step.element_id}, '{step.text}');")
|
|
163
|
+
lines.append("")
|
|
164
|
+
|
|
165
|
+
elif step.type == "press":
|
|
166
|
+
lines.append(f"{indent}// Press key: {step.key}")
|
|
167
|
+
lines.append(f"{indent}await press(browser, '{step.key}');")
|
|
168
|
+
lines.append("")
|
|
169
|
+
|
|
170
|
+
return lines
|
|
171
|
+
|
|
172
|
+
def save_python(self, filepath: str) -> None:
|
|
173
|
+
"""Generate and save Python script"""
|
|
174
|
+
code = self.generate_python()
|
|
175
|
+
with open(filepath, "w") as f:
|
|
176
|
+
f.write(code)
|
|
177
|
+
|
|
178
|
+
def save_typescript(self, filepath: str) -> None:
|
|
179
|
+
"""Generate and save TypeScript script"""
|
|
180
|
+
code = self.generate_typescript()
|
|
181
|
+
with open(filepath, "w") as f:
|
|
182
|
+
f.write(code)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def generate(trace: Trace, language: str = "py") -> str:
|
|
186
|
+
"""
|
|
187
|
+
Generate script from trace
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
trace: Trace object
|
|
191
|
+
language: 'py' or 'ts'
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Generated code as string
|
|
195
|
+
"""
|
|
196
|
+
generator = ScriptGenerator(trace)
|
|
197
|
+
if language == "py":
|
|
198
|
+
return generator.generate_python()
|
|
199
|
+
elif language == "ts":
|
|
200
|
+
return generator.generate_typescript()
|
|
201
|
+
else:
|
|
202
|
+
raise ValueError(f"Unsupported language: {language}. Use 'py' or 'ts'")
|
sentience/inspector.py
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Inspector tool - helps developers see what the agent "sees"
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .browser import SentienceBrowser
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Inspector:
|
|
9
|
+
"""Inspector for debugging - shows element info on hover/click"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, browser: SentienceBrowser):
|
|
12
|
+
self.browser = browser
|
|
13
|
+
self._active = False
|
|
14
|
+
self._last_element_id: int | None = None
|
|
15
|
+
|
|
16
|
+
def start(self) -> None:
|
|
17
|
+
"""Start inspection mode - prints element info on mouse move/click"""
|
|
18
|
+
if not self.browser.page:
|
|
19
|
+
raise RuntimeError("Browser not started. Call browser.start() first.")
|
|
20
|
+
|
|
21
|
+
self._active = True
|
|
22
|
+
|
|
23
|
+
# Inject inspector script into page
|
|
24
|
+
self.browser.page.evaluate(
|
|
25
|
+
"""
|
|
26
|
+
(() => {
|
|
27
|
+
// Remove existing inspector if any
|
|
28
|
+
if (window.__sentience_inspector_active) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
window.__sentience_inspector_active = true;
|
|
33
|
+
window.__sentience_last_element_id = null;
|
|
34
|
+
|
|
35
|
+
// Get element at point
|
|
36
|
+
function getElementAtPoint(x, y) {
|
|
37
|
+
const el = document.elementFromPoint(x, y);
|
|
38
|
+
if (!el) return null;
|
|
39
|
+
|
|
40
|
+
// Find element in registry
|
|
41
|
+
if (window.sentience_registry) {
|
|
42
|
+
for (let i = 0; i < window.sentience_registry.length; i++) {
|
|
43
|
+
if (window.sentience_registry[i] === el) {
|
|
44
|
+
return i;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Mouse move handler
|
|
52
|
+
function handleMouseMove(e) {
|
|
53
|
+
if (!window.__sentience_inspector_active) return;
|
|
54
|
+
|
|
55
|
+
const elementId = getElementAtPoint(e.clientX, e.clientY);
|
|
56
|
+
if (elementId === null || elementId === window.__sentience_last_element_id) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
window.__sentience_last_element_id = elementId;
|
|
61
|
+
|
|
62
|
+
// Get element info from snapshot if available
|
|
63
|
+
if (window.sentience && window.sentience_registry) {
|
|
64
|
+
const el = window.sentience_registry[elementId];
|
|
65
|
+
if (el) {
|
|
66
|
+
const rect = el.getBoundingClientRect();
|
|
67
|
+
const text = el.getAttribute('aria-label') ||
|
|
68
|
+
el.value ||
|
|
69
|
+
el.placeholder ||
|
|
70
|
+
el.alt ||
|
|
71
|
+
(el.innerText || '').substring(0, 50);
|
|
72
|
+
|
|
73
|
+
const role = el.getAttribute('role') || el.tagName.toLowerCase();
|
|
74
|
+
|
|
75
|
+
console.log(`[Sentience Inspector] Element #${elementId}: role=${role}, text="${text}", bbox=(${Math.round(rect.x)}, ${Math.round(rect.y)}, ${Math.round(rect.width)}, ${Math.round(rect.height)})`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Click handler
|
|
81
|
+
function handleClick(e) {
|
|
82
|
+
if (!window.__sentience_inspector_active) return;
|
|
83
|
+
|
|
84
|
+
e.preventDefault();
|
|
85
|
+
e.stopPropagation();
|
|
86
|
+
|
|
87
|
+
const elementId = getElementAtPoint(e.clientX, e.clientY);
|
|
88
|
+
if (elementId === null) return;
|
|
89
|
+
|
|
90
|
+
// Get full element info
|
|
91
|
+
if (window.sentience && window.sentience_registry) {
|
|
92
|
+
const el = window.sentience_registry[elementId];
|
|
93
|
+
if (el) {
|
|
94
|
+
const rect = el.getBoundingClientRect();
|
|
95
|
+
const info = {
|
|
96
|
+
id: elementId,
|
|
97
|
+
tag: el.tagName.toLowerCase(),
|
|
98
|
+
role: el.getAttribute('role') || 'generic',
|
|
99
|
+
text: el.getAttribute('aria-label') ||
|
|
100
|
+
el.value ||
|
|
101
|
+
el.placeholder ||
|
|
102
|
+
el.alt ||
|
|
103
|
+
(el.innerText || '').substring(0, 100),
|
|
104
|
+
bbox: {
|
|
105
|
+
x: Math.round(rect.x),
|
|
106
|
+
y: Math.round(rect.y),
|
|
107
|
+
width: Math.round(rect.width),
|
|
108
|
+
height: Math.round(rect.height)
|
|
109
|
+
},
|
|
110
|
+
attributes: {
|
|
111
|
+
id: el.id || null,
|
|
112
|
+
class: el.className || null,
|
|
113
|
+
name: el.name || null,
|
|
114
|
+
type: el.type || null
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
console.log('[Sentience Inspector] Clicked element:', JSON.stringify(info, null, 2));
|
|
119
|
+
|
|
120
|
+
// Also try to get from snapshot if available
|
|
121
|
+
window.sentience.snapshot({ limit: 100 }).then(snap => {
|
|
122
|
+
const element = snap.elements.find(el => el.id === elementId);
|
|
123
|
+
if (element) {
|
|
124
|
+
console.log('[Sentience Inspector] Snapshot element:', JSON.stringify(element, null, 2));
|
|
125
|
+
}
|
|
126
|
+
}).catch(() => {});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Add event listeners
|
|
132
|
+
document.addEventListener('mousemove', handleMouseMove, true);
|
|
133
|
+
document.addEventListener('click', handleClick, true);
|
|
134
|
+
|
|
135
|
+
// Store cleanup function
|
|
136
|
+
window.__sentience_inspector_cleanup = () => {
|
|
137
|
+
document.removeEventListener('mousemove', handleMouseMove, true);
|
|
138
|
+
document.removeEventListener('click', handleClick, true);
|
|
139
|
+
window.__sentience_inspector_active = false;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
console.log('[Sentience Inspector] ✅ Inspection mode active. Hover elements to see info, click to see full details.');
|
|
143
|
+
})();
|
|
144
|
+
"""
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
def stop(self) -> None:
|
|
148
|
+
"""Stop inspection mode"""
|
|
149
|
+
if not self.browser.page:
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
self._active = False
|
|
153
|
+
|
|
154
|
+
# Cleanup inspector
|
|
155
|
+
self.browser.page.evaluate(
|
|
156
|
+
"""
|
|
157
|
+
() => {
|
|
158
|
+
if (window.__sentience_inspector_cleanup) {
|
|
159
|
+
window.__sentience_inspector_cleanup();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
"""
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
def __enter__(self):
|
|
166
|
+
"""Context manager entry"""
|
|
167
|
+
self.start()
|
|
168
|
+
return self
|
|
169
|
+
|
|
170
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
171
|
+
"""Context manager exit"""
|
|
172
|
+
self.stop()
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def inspect(browser: SentienceBrowser) -> Inspector:
|
|
176
|
+
"""
|
|
177
|
+
Create an inspector instance
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
browser: SentienceBrowser instance
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
Inspector instance
|
|
184
|
+
"""
|
|
185
|
+
return Inspector(browser)
|