frontend-perception-engine 0.1.0__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.
Files changed (50) hide show
  1. frontend_perception_engine-0.1.0.dist-info/METADATA +235 -0
  2. frontend_perception_engine-0.1.0.dist-info/RECORD +50 -0
  3. frontend_perception_engine-0.1.0.dist-info/WHEEL +5 -0
  4. frontend_perception_engine-0.1.0.dist-info/entry_points.txt +2 -0
  5. frontend_perception_engine-0.1.0.dist-info/top_level.txt +1 -0
  6. navigation/__init__.py +1 -0
  7. navigation/browser_use/__init__.py +17 -0
  8. navigation/browser_use/agent_runner.py +165 -0
  9. navigation/browser_use/hints.py +155 -0
  10. navigation/browser_use/integration.py +46 -0
  11. navigation/browser_use/llm.py +70 -0
  12. navigation/codeGraph/__init__.py +4 -0
  13. navigation/codeGraph/crg_impl.py +170 -0
  14. navigation/codeGraph/factory.py +24 -0
  15. navigation/codeGraph/interface.py +78 -0
  16. navigation/codeGraph/null_impl.py +47 -0
  17. navigation/mcp/__init__.py +4 -0
  18. navigation/mcp/__main__.py +4 -0
  19. navigation/mcp/diff.py +28 -0
  20. navigation/mcp/envelope.py +50 -0
  21. navigation/mcp/handlers.py +791 -0
  22. navigation/mcp/instructions.py +36 -0
  23. navigation/mcp/resources.py +82 -0
  24. navigation/mcp/scan_registry.py +47 -0
  25. navigation/mcp/server.py +229 -0
  26. navigation/mcp/session_store.py +87 -0
  27. navigation/mcp/tools.py +332 -0
  28. navigation/perception/__init__.py +76 -0
  29. navigation/perception/artifacts.py +19 -0
  30. navigation/perception/auth_gate.py +57 -0
  31. navigation/perception/budget.py +55 -0
  32. navigation/perception/cdp_hub.py +122 -0
  33. navigation/perception/dev_insights.py +625 -0
  34. navigation/perception/exploration.py +99 -0
  35. navigation/perception/feature_flags.py +83 -0
  36. navigation/perception/file_upload.py +84 -0
  37. navigation/perception/flow_graph.py +121 -0
  38. navigation/perception/form_probe.py +148 -0
  39. navigation/perception/iframe_context.py +76 -0
  40. navigation/perception/observation.py +97 -0
  41. navigation/perception/preflight.py +91 -0
  42. navigation/perception/rich_editors.py +90 -0
  43. navigation/perception/route_guards.py +136 -0
  44. navigation/perception/runner.py +138 -0
  45. navigation/perception/scan.py +74 -0
  46. navigation/perception/scripted_actions.py +67 -0
  47. navigation/perception/state_manager.py +114 -0
  48. navigation/perception/verification.py +117 -0
  49. navigation/perception/virtual_scroll.py +84 -0
  50. navigation/perception/websocket_observer.py +79 -0
@@ -0,0 +1,235 @@
1
+ Metadata-Version: 2.4
2
+ Name: frontend-perception-engine
3
+ Version: 0.1.0
4
+ Summary: Frontend perception engine with optional CRG-powered navigation hints for Browser Use.
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: browser-use>=0.13.3
8
+ Requires-Dist: code-review-graph>=2.3.6
9
+ Requires-Dist: python-dotenv>=1.0.0
10
+ Requires-Dist: mcp>=1.0.0
11
+ Provides-Extra: aws
12
+ Requires-Dist: boto3>=1.35.0; extra == "aws"
13
+
14
+ # Frontend Perception Engine (CRG as Optional Library)
15
+
16
+ This project implements a frontend navigation layer where:
17
+
18
+ - Browser Use stays the browser automation engine.
19
+ - Code Review Graph (CRG) is used only as an optional knowledge source.
20
+ - Browser execution continues even when CRG is unavailable.
21
+
22
+ ## Architecture
23
+
24
+ ```text
25
+ Cursor / Claude
26
+
27
+ Our MCP
28
+
29
+ Frontend Navigation Layer
30
+
31
+ Browser Use
32
+
33
+ Browser
34
+ ```
35
+
36
+ CRG is integrated behind `ICodeGraph`, so it can be replaced later by another backend such as `FrontendInteractionGraph` without changing Browser Use orchestration.
37
+
38
+ ## Dependency Integration
39
+
40
+ CRG is integrated as a dependency (not forked, not modified):
41
+
42
+ - `code-review-graph>=2.3.6`
43
+ - `browser-use>=0.13.3`
44
+
45
+ Install:
46
+
47
+ ```bash
48
+ pip install -e .
49
+ ```
50
+
51
+ ## Frontend Perception MCP (No LLM in Server)
52
+
53
+ The MCP server is deterministic runtime only (browser observation + actions + verify).
54
+ Your coding agent (Cursor/Claude/Codex) remains the brain.
55
+
56
+ ### Install
57
+
58
+ Development install:
59
+
60
+ ```bash
61
+ pip install -e .
62
+ ```
63
+
64
+ Published package usage (when installed from index):
65
+
66
+ ```bash
67
+ pip install frontend-perception-engine
68
+ ```
69
+
70
+ ### Run MCP server
71
+
72
+ Using module entrypoint:
73
+
74
+ ```bash
75
+ python -m navigation.mcp
76
+ ```
77
+
78
+ Using script entrypoint:
79
+
80
+ ```bash
81
+ frontend-perception-mcp
82
+ ```
83
+
84
+ Using `uvx` (no local install in current environment):
85
+
86
+ ```bash
87
+ uvx --from frontend-perception-engine frontend-perception-mcp
88
+ ```
89
+
90
+ ### Cursor MCP config
91
+
92
+ ```json
93
+ {
94
+ "mcpServers": {
95
+ "frontend-perception": {
96
+ "command": "python",
97
+ "args": ["-m", "navigation.mcp"],
98
+ "env": {
99
+ "PYTHONPATH": "C:/Users/usman/Projects/frontend-perception-engine/src"
100
+ }
101
+ }
102
+ }
103
+ }
104
+ ```
105
+
106
+ ### Runtime prerequisites
107
+
108
+ - Start the sandbox app: `cd sandbox && npm run dev`
109
+ - Default URL used by tests/tools: `http://localhost:5173`
110
+ - No API keys are required to run the MCP path itself
111
+
112
+ ## CRG Documentation and Public API Notes
113
+
114
+ The integration uses CRG public tool functions from:
115
+
116
+ - `code_review_graph.tools.build.build_or_update_graph`
117
+ - `code_review_graph.tools.query.query_graph`
118
+ - `code_review_graph.tools.query.semantic_search_nodes`
119
+ - `code_review_graph.tools.query.get_impact_radius`
120
+ - `code_review_graph.tools.query.list_graph_stats`
121
+ - `code_review_graph.tools.query.traverse_graph_func`
122
+
123
+ ### Graph lifecycle
124
+
125
+ - Initialize graph (incremental/minimal): `build_or_update_graph(full_rebuild=False, postprocess="minimal")`
126
+ - Refresh graph (incremental): `build_or_update_graph(full_rebuild=False)`
127
+ - Rebuild graph (full): `build_or_update_graph(full_rebuild=True)`
128
+
129
+ ### Incremental indexing
130
+
131
+ CRG incremental path is handled by `incremental_update` under the hood and can detect changed files from VCS (`base=HEAD~1` by default).
132
+
133
+ ### Watch mode
134
+
135
+ CRG supports continuous updates via:
136
+
137
+ - CLI: `code-review-graph watch`
138
+ - API internals: `code_review_graph.incremental.watch` and `start_watch_thread`
139
+
140
+ This wrapper does not require watch mode, but is compatible with repositories kept fresh by CRG watch/daemon.
141
+
142
+ ### Querying
143
+
144
+ - Pattern queries (neighbors/file relationships): `query_graph`
145
+ - Search (hybrid semantic + keyword): `semantic_search_nodes`
146
+ - Blast radius / route impact: `get_impact_radius`
147
+ - Traversal/path-like exploration: `traverse_graph_func`
148
+ - Stats/health: `list_graph_stats`
149
+
150
+ ## Wrapper Layer
151
+
152
+ All CRG coupling is isolated in:
153
+
154
+ `src/navigation/codeGraph/`
155
+
156
+ Public contract:
157
+
158
+ - `initialize()`
159
+ - `refresh()`
160
+ - `rebuild()`
161
+ - `search()`
162
+ - `shortest_path()`
163
+ - `get_neighbors()`
164
+ - `get_component()`
165
+ - `get_file()`
166
+ - `get_route()`
167
+ - `query()`
168
+
169
+ Future-oriented methods are already represented on `ICodeGraph`:
170
+
171
+ - `findNavigationHint(...)` style equivalent via `find_navigation_hint(...)`
172
+ - `find_relevant_components(...)`
173
+ - `find_likely_route(...)`
174
+ - `find_related_files(...)`
175
+ - `find_button_candidates(...)`
176
+ - `find_component_hierarchy(...)`
177
+ - `find_entry_point(...)`
178
+
179
+ ## Browser Use Integration
180
+
181
+ `BrowserUseNavigator` provides a lightweight dry-run timeline for tests.
182
+
183
+ `PerceptionAgentRunner` runs a **real Browser Use agent** with optional graph hints injected via `extend_system_message`. Graph output is never a mandatory stage — if CRG or AWS credentials are missing, the agent either skips hints or reports a clear error.
184
+
185
+ ### Live agent (Bedrock Nova)
186
+
187
+ 1. Start the sandbox:
188
+
189
+ ```bash
190
+ cd sandbox && npm run dev
191
+ ```
192
+
193
+ 2. Configure AWS (copy `.env.example` → `.env`):
194
+
195
+ ```bash
196
+ AWS_ACCESS_KEY_ID=...
197
+ AWS_SECRET_ACCESS_KEY=...
198
+ AWS_REGION=us-east-1
199
+ BEDROCK_MODEL=amazon.nova-pro-v1:0
200
+ SANDBOX_URL=http://localhost:5173
201
+ ```
202
+
203
+ 3. Install AWS extra and run:
204
+
205
+ ```bash
206
+ pip install -e ".[aws]"
207
+ python src/run_agent.py --task "Add Pulse Watch to cart and complete checkout"
208
+ ```
209
+
210
+ Dry-run (graph hints only, no browser):
211
+
212
+ ```bash
213
+ python src/run_agent.py --dry-run --task "Log in as admin and open admin report"
214
+ ```
215
+
216
+ Flags:
217
+
218
+ - `--no-graph` — disable CRG hints
219
+ - `--headless` — headless browser
220
+ - `--max-steps 25` — step limit
221
+ - `--url http://localhost:5174` — custom sandbox URL
222
+
223
+ ## Demo
224
+
225
+ Run:
226
+
227
+ ```bash
228
+ python src/demo.py
229
+ ```
230
+
231
+ Expected behavior:
232
+
233
+ 1. Browser Use execution starts.
234
+ 2. Optional code graph query is attempted.
235
+ 3. Browser Use continues regardless of query success.
@@ -0,0 +1,50 @@
1
+ navigation/__init__.py,sha256=PFr_V0xYQhWjKk5oc83cYg_JcNZ2FEKTsjXlnxmkyB8,25
2
+ navigation/browser_use/__init__.py,sha256=9Kn0IycRaPL_TJzhLllJ-x0IC3qX9T01cclQPTTj7pg,571
3
+ navigation/browser_use/agent_runner.py,sha256=8b3MhnMFfuIp7EcOPH-r0sXhM_KyI_-6rCiEY7L3CYE,5515
4
+ navigation/browser_use/hints.py,sha256=hlI7s27THrn_LPM6scXxpCG5SdQgVEGW-YXm9ly-jNs,5820
5
+ navigation/browser_use/integration.py,sha256=isJWR4WAoUvf42UiD4-KCJnAxhcHbPXGfITaHRTyFbo,1606
6
+ navigation/browser_use/llm.py,sha256=6avNVsUSFMQ-YnxFNcXzEocbUG7_7AsXe8oJnW4bgKQ,2264
7
+ navigation/codeGraph/__init__.py,sha256=TxbCfLdv5W7D9iX3D17Pv7F7JvPrqepOu_aTOrNGEWo,162
8
+ navigation/codeGraph/crg_impl.py,sha256=XufOmUj8AZRjmbkC4nifWwWzzmDjz_dwuWbGctWgc1w,7424
9
+ navigation/codeGraph/factory.py,sha256=709P9REWEf5j1GOFgXueSOPNNQ6PIc1QkKivwtSOP3E,774
10
+ navigation/codeGraph/interface.py,sha256=QZXalLUGN1or6YvnvW_yeAbCfpXQZoHolH55HhDsKrI,2916
11
+ navigation/codeGraph/null_impl.py,sha256=gd8aj6tTDwEVfphkJ_D0NHX0nF70o28zKp247bwx5sw,1674
12
+ navigation/mcp/__init__.py,sha256=K8WWeP6YjK0gb1oRSXZhpD1xl6Vi1Mik59dEhPty_g4,132
13
+ navigation/mcp/__main__.py,sha256=Z5XW_4J0QbOX5nH0KzYtoMs2jyUz290JK-fnWD1Ak_A,82
14
+ navigation/mcp/diff.py,sha256=i4nWWOQCRY2bigGcGmyNG-IdAB_PGx_Kj2P-IsV3dnY,1072
15
+ navigation/mcp/envelope.py,sha256=2kf_MOnkysK2i-IpR7XhJU-dXlKObopRWKKn1FST4rw,1467
16
+ navigation/mcp/handlers.py,sha256=9t8DHtj0KcWeQEhT5aplCGvBJGdRtz-ubrQGGcmELqg,27146
17
+ navigation/mcp/instructions.py,sha256=glYPaoRuUCjgdSe8SKR0BZHvh9kQNT-QHEQDTfI8sDY,1932
18
+ navigation/mcp/resources.py,sha256=Blq5P4XJwnXLaq23s1XHLqu6PQqE7cK8QSa5ZnwwABk,3371
19
+ navigation/mcp/scan_registry.py,sha256=V0J20AaGAXG3LQhjWdGLtZ5c19aK8KX7i-1FFrOTpTA,1192
20
+ navigation/mcp/server.py,sha256=eSkhFEt14e7YPG_7wc94vgUJPxJMCgoK4rckXAYcPeg,8444
21
+ navigation/mcp/session_store.py,sha256=MLjts9tni3P6gNdpOM83PKIzBf82CwfbKeT3xrcJdzU,2645
22
+ navigation/mcp/tools.py,sha256=fhnffshXwm5es4N9vp7IOaiZ_09o9Q0SHvCRjpk8zcU,14429
23
+ navigation/perception/__init__.py,sha256=vSUXp490x56flZrnCUCSrhY--_qlhaiqwA-m443Tr10,2538
24
+ navigation/perception/artifacts.py,sha256=Xw1R3c2sZBDvbL7VxIq0qt9gijon6KVDir8eFZEh4Nk,591
25
+ navigation/perception/auth_gate.py,sha256=-L8iR7-zrhQKEbFm4fQOWoJ-IfFr5QgFbnA3EhTVXNU,1690
26
+ navigation/perception/budget.py,sha256=qcjJJbN9NRLt0lKPyGcvI3165i7CG5dBM4m0CLIy0_k,2068
27
+ navigation/perception/cdp_hub.py,sha256=QkzKX5mFS7UV3GyRi9Ci4S62hWLFWpT0UplEBBeB6iA,4797
28
+ navigation/perception/dev_insights.py,sha256=N6DG3N_F-ZoUdVVbXPnPvU5xMFO7ygy7ibmhYRRivhk,23049
29
+ navigation/perception/exploration.py,sha256=tYRo7JeI875qiO4vud6q9oBW9f7iz-lHeLFVp8PgLis,3017
30
+ navigation/perception/feature_flags.py,sha256=MyyTyR3r1mPwBangnXYepUZW208v6ojrQ665s1cqaYI,2659
31
+ navigation/perception/file_upload.py,sha256=Wb2pIcJ7Ivay564oSAPSZ3T7_xlY7oXzLiDpJOdEgcM,2795
32
+ navigation/perception/flow_graph.py,sha256=0XoTe3b9talf4_OepK071lTSWESXObSmtUkSPq-z1l4,3979
33
+ navigation/perception/form_probe.py,sha256=JJw-ldlbZTDAWfdeTiIY_6csbTwrl1HIN1t162mad_8,4826
34
+ navigation/perception/iframe_context.py,sha256=JF_gqLp2bPF85juIMLep6xYUFusZMKX9-llEZqWX-ME,2358
35
+ navigation/perception/observation.py,sha256=KwiLwpjMUsieJu0ehXU0Y80PZQJYDeNqpJG9IvB3Ee8,3415
36
+ navigation/perception/preflight.py,sha256=CAHesJItqLwQctxDiGU6jFjfq0alTbwHVD1ufEk8Zoo,2436
37
+ navigation/perception/rich_editors.py,sha256=n2DmvF9VtiKMXE2_WHkfFE5RSJZzE6OwfJ6yFMIypDQ,3172
38
+ navigation/perception/route_guards.py,sha256=3OT9Iszr6ZHptebkp5Pg_53C0WZGjsAyFNMX_Z4a2LM,4475
39
+ navigation/perception/runner.py,sha256=LekITqY8lIjTBV6RZ0OU89VeEf7V0jZefvP--ssSNTM,5477
40
+ navigation/perception/scan.py,sha256=1NcomG4zBxHRw59pRpG7Zg9XZq-PDp-X9x109oVcQn4,2305
41
+ navigation/perception/scripted_actions.py,sha256=rTTC8qngqWxC1umPkCF64Jrz6IJ9DsWLuPR4OORXDQA,2228
42
+ navigation/perception/state_manager.py,sha256=018JcETIIcaReWRkNLNavh9CXNc8RfmDlmXmWuyVmYM,4013
43
+ navigation/perception/verification.py,sha256=2_FNwvKAR7cz7ygDpDMKkbG9ZxgwWIIEfoQ8bMvlT_A,4221
44
+ navigation/perception/virtual_scroll.py,sha256=bjvxWVcg0LupP3cxvb0e1QBiGDp1u0ETSXViJzlmnBc,2507
45
+ navigation/perception/websocket_observer.py,sha256=UY2dUED4Xfe8oPzxJGwA6iDo3q0Jx5yzpRMBOCAHuZ4,2348
46
+ frontend_perception_engine-0.1.0.dist-info/METADATA,sha256=_yfn-nDw0bF_OMUjDkhUxgZJXQYZN6UAfjlvgd12DQE,5859
47
+ frontend_perception_engine-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
48
+ frontend_perception_engine-0.1.0.dist-info/entry_points.txt,sha256=Fh3U8gGnkQGRxlGbntvstQWTwR4yQ8d40PiXnrWttsU,71
49
+ frontend_perception_engine-0.1.0.dist-info/top_level.txt,sha256=lduxtTnjhATakWa5QPv5nkzURLRdjC3F0fjz1Ea_kS0,11
50
+ frontend_perception_engine-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ frontend-perception-mcp = navigation.mcp.server:main
@@ -0,0 +1 @@
1
+ navigation
navigation/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __all__: list[str] = []
@@ -0,0 +1,17 @@
1
+ from .agent_runner import AgentRunResult, PerceptionAgentRunner
2
+ from .hints import GraphHintResolver, NavigationHint, format_hints_for_agent
3
+ from .integration import BrowserUseNavigator, BrowserUseStep
4
+ from .llm import create_bedrock_llm, credentials_available, get_bedrock_config
5
+
6
+ __all__ = [
7
+ "AgentRunResult",
8
+ "BrowserUseNavigator",
9
+ "BrowserUseStep",
10
+ "GraphHintResolver",
11
+ "NavigationHint",
12
+ "PerceptionAgentRunner",
13
+ "create_bedrock_llm",
14
+ "credentials_available",
15
+ "format_hints_for_agent",
16
+ "get_bedrock_config",
17
+ ]
@@ -0,0 +1,165 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import os
5
+ from dataclasses import dataclass, field
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ from navigation.codeGraph.interface import ICodeGraph
10
+
11
+ from .hints import GraphHintResolver, NavigationHint, format_hints_for_agent, hint_to_step_details
12
+ from .integration import BrowserUseStep
13
+ from .llm import create_bedrock_llm, credentials_available
14
+
15
+
16
+ @dataclass(slots=True)
17
+ class AgentRunResult:
18
+ task: str
19
+ success: bool
20
+ steps_taken: int
21
+ final_result: str | None
22
+ hint: NavigationHint
23
+ timeline: list[BrowserUseStep] = field(default_factory=list)
24
+ error: str | None = None
25
+
26
+ def summary(self) -> str:
27
+ lines = [
28
+ f"Task: {self.task}",
29
+ f"Success: {self.success}",
30
+ f"Steps: {self.steps_taken}",
31
+ f"Graph hints used: {self.hint.ok} ({len(self.hint.hits)} hits)",
32
+ ]
33
+ if self.final_result:
34
+ lines.append(f"Result: {self.final_result[:500]}")
35
+ if self.error:
36
+ lines.append(f"Error: {self.error}")
37
+ return "\n".join(lines)
38
+
39
+
40
+ class PerceptionAgentRunner:
41
+ """
42
+ Runs a real Browser Use agent with optional CRG navigation hints.
43
+
44
+ Graph hints are injected via extend_system_message — never a hard pipeline stage.
45
+ If the graph or Bedrock credentials are missing, the agent still attempts the task.
46
+ """
47
+
48
+ def __init__(
49
+ self,
50
+ code_graph: ICodeGraph | None = None,
51
+ *,
52
+ start_url: str | None = None,
53
+ model: str | None = None,
54
+ region: str | None = None,
55
+ max_steps: int = 25,
56
+ headless: bool = False,
57
+ use_vision: bool = True,
58
+ ) -> None:
59
+ self.code_graph = code_graph
60
+ self.start_url = start_url or os.getenv("SANDBOX_URL", "http://localhost:5173")
61
+ self.model = model
62
+ self.region = region
63
+ self.max_steps = max_steps
64
+ self.headless = headless
65
+ self.use_vision = use_vision
66
+ self.hint_resolver = GraphHintResolver(code_graph)
67
+ self.timeline: list[BrowserUseStep] = []
68
+
69
+ def _record(self, action: str, details: dict[str, Any]) -> None:
70
+ self.timeline.append(BrowserUseStep(action=action, details=details))
71
+
72
+ async def run(self, task: str) -> AgentRunResult:
73
+ self.timeline = []
74
+ self._record("browser_use.start", {"task": task, "start_url": self.start_url})
75
+
76
+ hint = self.hint_resolver.resolve(task)
77
+ self._record("code_graph.query", hint_to_step_details(hint))
78
+
79
+ if not credentials_available():
80
+ self._record("browser_use.skip", {"reason": "missing_aws_credentials"})
81
+ return AgentRunResult(
82
+ task=task,
83
+ success=False,
84
+ steps_taken=0,
85
+ final_result=None,
86
+ hint=hint,
87
+ timeline=self.timeline,
88
+ error="AWS credentials not configured. Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.",
89
+ )
90
+
91
+ try:
92
+ from browser_use import Agent, BrowserProfile
93
+ except ImportError as exc:
94
+ return AgentRunResult(
95
+ task=task,
96
+ success=False,
97
+ steps_taken=0,
98
+ final_result=None,
99
+ hint=hint,
100
+ timeline=self.timeline,
101
+ error=f"browser-use not installed: {exc}",
102
+ )
103
+
104
+ llm = create_bedrock_llm(model=self.model, region=self.region)
105
+ graph_context = format_hints_for_agent(hint, self.start_url)
106
+
107
+ full_task = (
108
+ f"{task}\n\n"
109
+ f"Open {self.start_url} first if you are not already on the Navigation Maze app."
110
+ )
111
+
112
+ profile = BrowserProfile(headless=self.headless)
113
+
114
+ self._record(
115
+ "browser_use.agent_init",
116
+ {
117
+ "model": self.model or os.getenv("BEDROCK_MODEL", "amazon.nova-pro-v1:0"),
118
+ "hint_injected": hint.ok,
119
+ "max_steps": self.max_steps,
120
+ },
121
+ )
122
+
123
+ agent = Agent(
124
+ task=full_task,
125
+ llm=llm,
126
+ browser_profile=profile,
127
+ use_vision=self.use_vision,
128
+ extend_system_message=graph_context,
129
+ initial_actions=[{"navigate": {"url": self.start_url, "new_tab": False}}],
130
+ )
131
+
132
+ try:
133
+ history = await agent.run(max_steps=self.max_steps)
134
+ final = history.final_result()
135
+ success = bool(history.is_successful())
136
+ self._record(
137
+ "browser_use.done",
138
+ {
139
+ "success": success,
140
+ "steps": len(history.history),
141
+ "final_result": (final or "")[:300],
142
+ },
143
+ )
144
+ return AgentRunResult(
145
+ task=task,
146
+ success=success,
147
+ steps_taken=len(history.history),
148
+ final_result=final,
149
+ hint=hint,
150
+ timeline=self.timeline,
151
+ )
152
+ except Exception as exc:
153
+ self._record("browser_use.error", {"error": str(exc)})
154
+ return AgentRunResult(
155
+ task=task,
156
+ success=False,
157
+ steps_taken=len(self.timeline),
158
+ final_result=None,
159
+ hint=hint,
160
+ timeline=self.timeline,
161
+ error=str(exc),
162
+ )
163
+
164
+ def run_sync(self, task: str) -> AgentRunResult:
165
+ return asyncio.run(self.run(task))
@@ -0,0 +1,155 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Any
5
+
6
+ from navigation.codeGraph.interface import GraphQueryResult, ICodeGraph
7
+
8
+
9
+ @dataclass(slots=True)
10
+ class NavigationHint:
11
+ task: str
12
+ summary: str
13
+ hits: list[dict[str, Any]] = field(default_factory=list)
14
+ related_files: list[str] = field(default_factory=list)
15
+ route_files: list[str] = field(default_factory=list)
16
+ ok: bool = False
17
+
18
+
19
+ class GraphHintResolver:
20
+ """Resolve optional navigation hints from ICodeGraph without coupling Browser Use to CRG."""
21
+
22
+ STOP_WORDS = {
23
+ "the", "a", "an", "and", "or", "to", "as", "in", "on", "for", "open",
24
+ "navigate", "locate", "complete", "continue", "flow", "changes", "save",
25
+ "with", "from", "into", "then", "that", "this", "your", "user",
26
+ }
27
+
28
+ def __init__(self, code_graph: ICodeGraph | None) -> None:
29
+ self.code_graph = code_graph
30
+
31
+ @classmethod
32
+ def extract_keywords(cls, task: str) -> list[str]:
33
+ words = [w.strip(".,!?").lower() for w in task.split()]
34
+ return [w for w in words if w not in cls.STOP_WORDS and len(w) > 2]
35
+
36
+ NAV_PRIORITY = ("checkout", "cart", "login", "admin", "settings", "shop", "profile", "payment", "shipping")
37
+
38
+ def resolve(self, task: str) -> NavigationHint:
39
+ if self.code_graph is None:
40
+ return NavigationHint(task=task, summary="Graph unavailable", ok=False)
41
+
42
+ hits: list[dict[str, Any]] = []
43
+ related_files: list[str] = []
44
+ route_files: list[str] = []
45
+ summary = "No graph hints found"
46
+ keywords = self.extract_keywords(task)
47
+ priority = [k for k in self.NAV_PRIORITY if k in keywords]
48
+ search_order = priority + [k for k in keywords if k not in priority]
49
+
50
+ hint = self.code_graph.find_navigation_hint(task)
51
+ if hint.ok and (hint.payload or {}).get("results"):
52
+ hits = list(hint.payload["results"])
53
+ summary = hint.summary
54
+
55
+ if not hits:
56
+ for kw in search_order:
57
+ result = self.code_graph.search(kw, limit=5)
58
+ if result.ok and (result.payload or {}).get("results"):
59
+ for item in result.payload["results"]:
60
+ if item not in hits:
61
+ hits.append(item)
62
+ summary = result.summary
63
+ if kw in self.NAV_PRIORITY:
64
+ break
65
+
66
+ if not hits:
67
+ for kw in search_order[:2]:
68
+ result = self.code_graph.find_entry_point(kw)
69
+ if result.ok and (result.payload or {}).get("results"):
70
+ hits = list(result.payload["results"])
71
+ summary = result.summary
72
+ break
73
+
74
+ for kw in search_order[:3]:
75
+ related = self.code_graph.find_related_files(kw)
76
+ if related.ok:
77
+ for item in (related.payload or {}).get("results") or []:
78
+ fp = item.get("file_path") or item.get("name")
79
+ if fp and fp not in related_files:
80
+ related_files.append(fp)
81
+
82
+ keywords = self.extract_keywords(task)
83
+ if {"checkout", "cart", "shop"} & set(keywords):
84
+ route = self.code_graph.get_route(
85
+ changed_files=[
86
+ "src/pages/shop/Cart.jsx",
87
+ "src/pages/shop/checkout/ShippingStep.jsx",
88
+ ],
89
+ )
90
+ if route.ok:
91
+ route_files = list((route.payload or {}).get("impacted_files") or [])[:8]
92
+
93
+ return NavigationHint(
94
+ task=task,
95
+ summary=summary,
96
+ hits=hits[:8],
97
+ related_files=related_files[:8],
98
+ route_files=route_files,
99
+ ok=bool(hits or related_files or route_files),
100
+ )
101
+
102
+
103
+ def format_hints_for_agent(hint: NavigationHint, start_url: str) -> str:
104
+ """Turn graph hints into advisory context for Browser Use's extend_system_message."""
105
+ if not hint.ok:
106
+ return (
107
+ "## Code graph hints\n"
108
+ "No codebase hints are available for this task. Rely on the live page only.\n"
109
+ f"Start URL: {start_url}\n"
110
+ )
111
+
112
+ lines = [
113
+ "## Code graph hints (advisory only)",
114
+ "These come from a static code graph. Prefer what you see in the browser if they conflict.",
115
+ f"Start URL: {start_url}",
116
+ f"Graph summary: {hint.summary}",
117
+ "",
118
+ ]
119
+
120
+ if hint.hits:
121
+ lines.append("Relevant code entities:")
122
+ for item in hint.hits:
123
+ name = item.get("name") or item.get("qualified_name") or "unknown"
124
+ fp = item.get("file_path") or item.get("file") or ""
125
+ kind = item.get("kind") or ""
126
+ suffix = f" ({fp})" if fp else ""
127
+ lines.append(f"- {name}{' [' + kind + ']' if kind else ''}{suffix}")
128
+
129
+ if hint.related_files:
130
+ lines.append("")
131
+ lines.append("Related files:")
132
+ for fp in hint.related_files:
133
+ lines.append(f"- {fp}")
134
+
135
+ if hint.route_files:
136
+ lines.append("")
137
+ lines.append("Likely route / impact files for shop-checkout flow:")
138
+ for fp in hint.route_files:
139
+ lines.append(f"- {fp}")
140
+
141
+ lines.append("")
142
+ lines.append("Use breadcrumbs, navbar links, buttons, and forms visible on the page.")
143
+ return "\n".join(lines)
144
+
145
+
146
+ def hint_to_step_details(hint: NavigationHint, raw: GraphQueryResult | None = None) -> dict[str, Any]:
147
+ return {
148
+ "ok": hint.ok,
149
+ "summary": hint.summary,
150
+ "error": raw.error if raw else None,
151
+ "hits": len(hint.hits),
152
+ "top_hit": (hint.hits[0].get("name") if hint.hits else None),
153
+ "related_files": hint.related_files[:3],
154
+ "route_files": hint.route_files[:3],
155
+ }
@@ -0,0 +1,46 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Any
5
+
6
+ from navigation.codeGraph.interface import ICodeGraph
7
+
8
+ from .hints import GraphHintResolver, hint_to_step_details
9
+
10
+
11
+ @dataclass(slots=True)
12
+ class BrowserUseStep:
13
+ action: str
14
+ details: dict[str, Any] = field(default_factory=dict)
15
+
16
+
17
+ class BrowserUseNavigator:
18
+ """
19
+ Lightweight orchestration timeline for tests and dry-runs.
20
+ For real browser automation with Bedrock Nova, use PerceptionAgentRunner.
21
+ """
22
+
23
+ def __init__(self, code_graph: ICodeGraph | None = None) -> None:
24
+ self.code_graph = code_graph
25
+ self.timeline: list[BrowserUseStep] = []
26
+ self.hint_resolver = GraphHintResolver(code_graph)
27
+
28
+ def execute(self, task: str) -> list[BrowserUseStep]:
29
+ self.timeline.append(BrowserUseStep(action="browser_use.start", details={"task": task}))
30
+
31
+ hint = self.hint_resolver.resolve(task)
32
+ self.timeline.append(BrowserUseStep(action="code_graph.query", details=hint_to_step_details(hint)))
33
+
34
+ self.timeline.append(
35
+ BrowserUseStep(
36
+ action="browser_use.navigate",
37
+ details={
38
+ "task": task,
39
+ "hint_used": hint.ok,
40
+ "mode": "dry_run",
41
+ "note": "Use PerceptionAgentRunner for live Browser Use + Bedrock",
42
+ },
43
+ )
44
+ )
45
+ self.timeline.append(BrowserUseStep(action="browser_use.continue", details={"status": "ok"}))
46
+ return self.timeline