rf-mcp 0.28.dev1__tar.gz → 0.28.dev2__tar.gz
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.
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/.gitignore +4 -1
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/PKG-INFO +1 -1
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/pyproject.toml +1 -1
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/execution/keyword_executor.py +13 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/execution/page_source_service.py +0 -297
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/container.py +0 -33
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/__init__.py +0 -53
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/server.py +38 -5
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/rf_libdoc_integration.py +22 -10
- rf_mcp-0.28.dev1/src/robotmcp/domains/action/__init__.py +0 -72
- rf_mcp-0.28.dev1/src/robotmcp/domains/action/aggregates.py +0 -540
- rf_mcp-0.28.dev1/src/robotmcp/domains/action/events.py +0 -276
- rf_mcp-0.28.dev1/src/robotmcp/domains/action/response_builder.py +0 -449
- rf_mcp-0.28.dev1/src/robotmcp/domains/action/services.py +0 -500
- rf_mcp-0.28.dev1/src/robotmcp/domains/action/value_objects.py +0 -478
- rf_mcp-0.28.dev1/src/robotmcp/domains/element_registry/__init__.py +0 -62
- rf_mcp-0.28.dev1/src/robotmcp/domains/element_registry/aggregates.py +0 -484
- rf_mcp-0.28.dev1/src/robotmcp/domains/element_registry/entities.py +0 -166
- rf_mcp-0.28.dev1/src/robotmcp/domains/element_registry/events.py +0 -210
- rf_mcp-0.28.dev1/src/robotmcp/domains/element_registry/repository.py +0 -250
- rf_mcp-0.28.dev1/src/robotmcp/domains/element_registry/value_objects.py +0 -386
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/LICENSE +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/README.md +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/examples/plugins/doctest_plugin/README.md +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/examples/plugins/doctest_plugin/pyproject.toml +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/examples/plugins/sample_plugin/README.md +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/__init__.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/adapters/__init__.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/adapters/adapter_factory.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/adapters/browser_adapter.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/adapters/playwright_adapter.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/adapters/selenium_adapter.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/attach/__init__.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/attach/mcp_attach.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/__init__.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/browser/__init__.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/browser/browser_library_manager.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/execution/__init__.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/execution/execution_coordinator.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/execution/external_rf_client.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/execution/locator_converter.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/execution/mobile_capability_service.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/execution/rf_native_context_manager.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/execution/session_manager.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/execution/suite_execution_service.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/keyword_matcher.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/library_recommender.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/nlp_processor.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/state_manager.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/test_builder.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/variables/__init__.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/variables/variable_resolver.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/config/__init__.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/config/library_registry.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/core/__init__.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/core/dynamic_keyword_orchestrator.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/core/event_bus.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/core/keyword_discovery.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/core/library_manager.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/core/session_manager.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/instruction/__init__.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/instruction/adapters/__init__.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/instruction/adapters/fastmcp_adapter.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/instruction/aggregates.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/instruction/entities.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/instruction/events.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/instruction/repository.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/instruction/security.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/instruction/services.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/instruction/templates/api_focused.txt +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/instruction/templates/browser_focused.txt +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/instruction/templates/detailed.txt +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/instruction/templates/discovery_first.txt +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/instruction/templates/minimal.txt +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/instruction/templates/standard.txt +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/instruction/value_objects.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/shared/__init__.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/shared/kernel.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/snapshot/__init__.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/snapshot/aggregates.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/snapshot/diff_service.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/snapshot/entities.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/snapshot/events.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/snapshot/list_folding.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/snapshot/models.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/snapshot/repository.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/snapshot/services.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/snapshot/value_objects.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/timeout/__init__.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/timeout/aggregates.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/timeout/entities.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/timeout/events.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/timeout/keyword_classifier.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/timeout/services.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/timeout/value_objects.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/frontend/__init__.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/frontend/api.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/frontend/apps.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/frontend/asgi.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/frontend/bridge.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/frontend/config.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/frontend/controller.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/frontend/devserver.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/frontend/django_app.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/frontend/static/frontend/app.js +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/frontend/static/frontend/base.css +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/frontend/templates/frontend/index.html +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/frontend/templates/frontend/layout.html +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/frontend/urls.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/frontend/views.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/models/__init__.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/models/browser_models.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/models/config_models.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/models/execution_models.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/models/library_models.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/models/session_models.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/optimization/__init__.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/optimization/collector.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/optimization/compression_learner.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/optimization/folding_learner.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/optimization/instruction_hooks.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/optimization/instruction_learner.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/optimization/page_analyzer.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/optimization/pattern_store.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/optimization/ref_learner.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/optimization/timeout_learner.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/plugins/__init__.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/plugins/base.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/plugins/builtin/__init__.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/plugins/builtin/browser_plugin.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/plugins/builtin/definitions.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/plugins/builtin/requests_plugin.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/plugins/builtin/selenium_plugin.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/plugins/contracts.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/plugins/discovery.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/plugins/manager.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/__init__.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/argument_processor.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/enhanced_response_serializer.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/enhanced_serialization_integration.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/hints.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/libdoc_argument_parser.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/library_checker.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/library_detection.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/library_detector.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/library_loading_validator.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/response_serializer.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/rf_native_type_converter.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/rf_variables_compatibility.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/sampling.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/server_integration.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/session_resolution.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/token_efficient_output.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/validation.py +0 -0
- {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/tests/e2e/README.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rf-mcp
|
|
3
|
-
Version: 0.28.
|
|
3
|
+
Version: 0.28.dev2
|
|
4
4
|
Summary: Robot Framework MCP Server - Natural Language Test Automation Bridge
|
|
5
5
|
Project-URL: Homepage, https://github.com/manykarim/rf-mcp
|
|
6
6
|
Project-URL: Repository, https://github.com/manykarim/rf-mcp
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "rf-mcp"
|
|
7
|
-
version = "0.28.
|
|
7
|
+
version = "0.28.dev02"
|
|
8
8
|
description = "Robot Framework MCP Server - Natural Language Test Automation Bridge"
|
|
9
9
|
authors = [{ name = "Many Kasiriha" }]
|
|
10
10
|
license = { text = "Apache-2.0" }
|
|
@@ -2547,6 +2547,19 @@ class KeywordExecutor:
|
|
|
2547
2547
|
]
|
|
2548
2548
|
base_response["resolved_arguments"] = serialized_resolved_args
|
|
2549
2549
|
|
|
2550
|
+
else:
|
|
2551
|
+
# Unrecognized detail_level — fall back to minimal (includes output)
|
|
2552
|
+
logger.warning(
|
|
2553
|
+
f"Unrecognized detail_level '{detail_level}', "
|
|
2554
|
+
f"falling back to 'minimal'. Valid values: minimal, standard, full"
|
|
2555
|
+
)
|
|
2556
|
+
raw_output = result.get("output", "")
|
|
2557
|
+
base_response["output"] = self.response_serializer.serialize_for_response(
|
|
2558
|
+
raw_output
|
|
2559
|
+
)
|
|
2560
|
+
if "assigned_variables" in result:
|
|
2561
|
+
base_response["assigned_variables"] = result["assigned_variables"]
|
|
2562
|
+
|
|
2550
2563
|
return base_response
|
|
2551
2564
|
|
|
2552
2565
|
def get_supported_detail_levels(self) -> List[str]:
|
{rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/execution/page_source_service.py
RENAMED
|
@@ -994,300 +994,3 @@ class PageSourceService:
|
|
|
994
994
|
except Exception as e:
|
|
995
995
|
logger.debug(f"Direct mobile source retrieval failed: {e}")
|
|
996
996
|
return None
|
|
997
|
-
|
|
998
|
-
# =========================================================================
|
|
999
|
-
# DDD Integration Methods - Using domains for optimized snapshot capture
|
|
1000
|
-
# =========================================================================
|
|
1001
|
-
|
|
1002
|
-
async def capture_optimized_snapshot(
|
|
1003
|
-
self,
|
|
1004
|
-
session: ExecutionSession,
|
|
1005
|
-
browser_library_manager: Any,
|
|
1006
|
-
snapshot_mode: str = "incremental",
|
|
1007
|
-
selector: Optional[str] = None,
|
|
1008
|
-
include_refs: bool = True,
|
|
1009
|
-
) -> Dict[str, Any]:
|
|
1010
|
-
"""Capture ARIA snapshot with full optimization pipeline.
|
|
1011
|
-
|
|
1012
|
-
Uses the DDD domains for:
|
|
1013
|
-
- Token optimization via list folding
|
|
1014
|
-
- Incremental diff mode for reduced responses
|
|
1015
|
-
- Element ref registration for subsequent actions
|
|
1016
|
-
- Performance metrics collection
|
|
1017
|
-
|
|
1018
|
-
Args:
|
|
1019
|
-
session: The execution session
|
|
1020
|
-
browser_library_manager: Browser library manager instance
|
|
1021
|
-
snapshot_mode: "full", "incremental", or "none"
|
|
1022
|
-
selector: Optional CSS selector to scope the snapshot
|
|
1023
|
-
include_refs: Whether to include element refs in response
|
|
1024
|
-
|
|
1025
|
-
Returns:
|
|
1026
|
-
Dict with snapshot content, refs, and metrics
|
|
1027
|
-
"""
|
|
1028
|
-
import time
|
|
1029
|
-
start_time = time.perf_counter()
|
|
1030
|
-
|
|
1031
|
-
try:
|
|
1032
|
-
from robotmcp.container import get_container
|
|
1033
|
-
|
|
1034
|
-
container = get_container()
|
|
1035
|
-
|
|
1036
|
-
# 1. Capture raw ARIA snapshot
|
|
1037
|
-
aria_result = await self._capture_browser_aria_snapshot(
|
|
1038
|
-
session,
|
|
1039
|
-
browser_library_manager,
|
|
1040
|
-
selector=selector or "css=html",
|
|
1041
|
-
)
|
|
1042
|
-
|
|
1043
|
-
if not aria_result.get("success"):
|
|
1044
|
-
return {
|
|
1045
|
-
"success": False,
|
|
1046
|
-
"error": aria_result.get("error", "Failed to capture ARIA snapshot"),
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
raw_aria = aria_result.get("content", "")
|
|
1050
|
-
if not raw_aria:
|
|
1051
|
-
return {
|
|
1052
|
-
"success": False,
|
|
1053
|
-
"error": "Empty ARIA snapshot returned",
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
# 2. Build PageSnapshot using domain
|
|
1057
|
-
from robotmcp.domains.snapshot import PageSnapshot, AriaTree
|
|
1058
|
-
|
|
1059
|
-
capture_duration_ms = (time.perf_counter() - start_time) * 1000
|
|
1060
|
-
|
|
1061
|
-
# Parse ARIA YAML into AriaTree
|
|
1062
|
-
try:
|
|
1063
|
-
aria_tree = AriaTree.from_yaml(raw_aria)
|
|
1064
|
-
except Exception as parse_err:
|
|
1065
|
-
logger.warning(f"Failed to parse ARIA YAML: {parse_err}")
|
|
1066
|
-
# Create a simple tree from raw content
|
|
1067
|
-
from robotmcp.domains.snapshot import AriaNode, AriaRole, ElementRef
|
|
1068
|
-
aria_tree = AriaTree(
|
|
1069
|
-
root=AriaNode(
|
|
1070
|
-
ref=ElementRef.from_index(0),
|
|
1071
|
-
role=AriaRole("document"),
|
|
1072
|
-
name="(parse error)",
|
|
1073
|
-
)
|
|
1074
|
-
)
|
|
1075
|
-
|
|
1076
|
-
snapshot = PageSnapshot.create(
|
|
1077
|
-
session_id=session.session_id,
|
|
1078
|
-
aria_tree=aria_tree,
|
|
1079
|
-
url=session.browser_state.current_url,
|
|
1080
|
-
title=session.browser_state.page_title,
|
|
1081
|
-
)
|
|
1082
|
-
|
|
1083
|
-
# 3. Get compression settings from learned patterns
|
|
1084
|
-
page_type = self._classify_page_type(session)
|
|
1085
|
-
compression_settings = container.get_compression_settings(page_type)
|
|
1086
|
-
|
|
1087
|
-
# 4. Apply list folding if beneficial
|
|
1088
|
-
fold_threshold = compression_settings.get("fold_threshold", 0.85)
|
|
1089
|
-
if compression_settings.get("apply_folding", True):
|
|
1090
|
-
snapshot = snapshot.fold_lists(threshold=fold_threshold)
|
|
1091
|
-
|
|
1092
|
-
# 5. Handle snapshot mode
|
|
1093
|
-
if snapshot_mode == "none":
|
|
1094
|
-
snapshot_content = "[Snapshot omitted]"
|
|
1095
|
-
elif snapshot_mode == "incremental":
|
|
1096
|
-
previous = container.get_cached_snapshot(session.session_id)
|
|
1097
|
-
if previous:
|
|
1098
|
-
snapshot_content = snapshot.get_incremental_diff(previous)
|
|
1099
|
-
else:
|
|
1100
|
-
snapshot_content = snapshot.to_yaml()
|
|
1101
|
-
else: # full
|
|
1102
|
-
snapshot_content = snapshot.to_yaml()
|
|
1103
|
-
|
|
1104
|
-
# 6. Update element registry
|
|
1105
|
-
refs = {}
|
|
1106
|
-
if include_refs:
|
|
1107
|
-
registry = container.get_element_registry(session.session_id)
|
|
1108
|
-
# Register elements from snapshot
|
|
1109
|
-
refs = self._update_element_registry_from_snapshot(
|
|
1110
|
-
registry, snapshot
|
|
1111
|
-
)
|
|
1112
|
-
|
|
1113
|
-
# 7. Cache snapshot for incremental mode
|
|
1114
|
-
container.cache_snapshot(session.session_id, snapshot)
|
|
1115
|
-
|
|
1116
|
-
# 8. Record metrics
|
|
1117
|
-
latency_ms = (time.perf_counter() - start_time) * 1000
|
|
1118
|
-
container.record_snapshot_metrics(
|
|
1119
|
-
raw_chars=len(raw_aria),
|
|
1120
|
-
aria_chars=len(snapshot_content),
|
|
1121
|
-
latency_ms=latency_ms,
|
|
1122
|
-
page_type=page_type,
|
|
1123
|
-
fold_threshold=fold_threshold,
|
|
1124
|
-
)
|
|
1125
|
-
|
|
1126
|
-
return {
|
|
1127
|
-
"success": True,
|
|
1128
|
-
"content": snapshot_content,
|
|
1129
|
-
"refs": refs,
|
|
1130
|
-
"token_estimate": snapshot.estimate_tokens(),
|
|
1131
|
-
"node_count": snapshot.aria_tree.node_count,
|
|
1132
|
-
"interactive_count": snapshot.aria_tree.interactive_count,
|
|
1133
|
-
"compression_applied": snapshot.compression_applied,
|
|
1134
|
-
"latency_ms": latency_ms,
|
|
1135
|
-
"mode": snapshot_mode,
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
except Exception as e:
|
|
1139
|
-
logger.error(f"Error in optimized snapshot capture: {e}")
|
|
1140
|
-
return {
|
|
1141
|
-
"success": False,
|
|
1142
|
-
"error": f"Snapshot capture failed: {str(e)}",
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
def _classify_page_type(self, session: ExecutionSession) -> str:
|
|
1146
|
-
"""Classify the current page type for optimization.
|
|
1147
|
-
|
|
1148
|
-
Args:
|
|
1149
|
-
session: The execution session
|
|
1150
|
-
|
|
1151
|
-
Returns:
|
|
1152
|
-
Page type string (e.g., "search_results", "form", "article")
|
|
1153
|
-
"""
|
|
1154
|
-
try:
|
|
1155
|
-
from robotmcp.container import get_container
|
|
1156
|
-
|
|
1157
|
-
container = get_container()
|
|
1158
|
-
analyzer = container.page_analyzer
|
|
1159
|
-
|
|
1160
|
-
# Analyze based on URL and title
|
|
1161
|
-
url = session.browser_state.current_url or ""
|
|
1162
|
-
title = session.browser_state.page_title or ""
|
|
1163
|
-
|
|
1164
|
-
classification = analyzer.classify_page(url, title)
|
|
1165
|
-
return classification.page_type
|
|
1166
|
-
|
|
1167
|
-
except Exception as e:
|
|
1168
|
-
logger.debug(f"Page classification failed: {e}")
|
|
1169
|
-
return "unknown"
|
|
1170
|
-
|
|
1171
|
-
def _update_element_registry_from_snapshot(
|
|
1172
|
-
self,
|
|
1173
|
-
registry: Any, # ElementRegistry
|
|
1174
|
-
snapshot: Any, # PageSnapshot
|
|
1175
|
-
) -> Dict[str, str]:
|
|
1176
|
-
"""Update element registry with refs from snapshot.
|
|
1177
|
-
|
|
1178
|
-
Args:
|
|
1179
|
-
registry: The element registry to update
|
|
1180
|
-
snapshot: The page snapshot with elements
|
|
1181
|
-
|
|
1182
|
-
Returns:
|
|
1183
|
-
Dict mapping ref -> brief description
|
|
1184
|
-
"""
|
|
1185
|
-
refs = {}
|
|
1186
|
-
try:
|
|
1187
|
-
# Iterate through snapshot's ARIA tree
|
|
1188
|
-
for node in snapshot.aria_tree.root.traverse():
|
|
1189
|
-
ref_str = str(node.ref)
|
|
1190
|
-
description = node.display_name
|
|
1191
|
-
|
|
1192
|
-
# Register in registry (converts aria to locator)
|
|
1193
|
-
# The registry will create appropriate locators based on node properties
|
|
1194
|
-
try:
|
|
1195
|
-
registry.register_from_aria_node(node)
|
|
1196
|
-
refs[ref_str] = description
|
|
1197
|
-
except Exception as reg_err:
|
|
1198
|
-
logger.debug(f"Failed to register ref {ref_str}: {reg_err}")
|
|
1199
|
-
|
|
1200
|
-
except Exception as e:
|
|
1201
|
-
logger.warning(f"Failed to update element registry: {e}")
|
|
1202
|
-
|
|
1203
|
-
return refs
|
|
1204
|
-
|
|
1205
|
-
def get_element_registry(self, session_id: str) -> Any:
|
|
1206
|
-
"""Get the element registry for a session.
|
|
1207
|
-
|
|
1208
|
-
Args:
|
|
1209
|
-
session_id: The session ID
|
|
1210
|
-
|
|
1211
|
-
Returns:
|
|
1212
|
-
ElementRegistry instance
|
|
1213
|
-
"""
|
|
1214
|
-
from robotmcp.container import get_container
|
|
1215
|
-
return get_container().get_element_registry(session_id)
|
|
1216
|
-
|
|
1217
|
-
def get_locator_for_ref(self, session_id: str, ref: str) -> Optional[str]:
|
|
1218
|
-
"""Get the browser locator for an element ref.
|
|
1219
|
-
|
|
1220
|
-
Args:
|
|
1221
|
-
session_id: The session ID
|
|
1222
|
-
ref: Element reference (e.g., "e1", "e42")
|
|
1223
|
-
|
|
1224
|
-
Returns:
|
|
1225
|
-
Browser-compatible locator string or None
|
|
1226
|
-
"""
|
|
1227
|
-
try:
|
|
1228
|
-
from robotmcp.container import get_container
|
|
1229
|
-
from robotmcp.domains.shared.kernel import ElementRef
|
|
1230
|
-
|
|
1231
|
-
registry = get_container().get_element_registry(session_id)
|
|
1232
|
-
element_ref = ElementRef(ref)
|
|
1233
|
-
|
|
1234
|
-
if not registry.has_ref(element_ref):
|
|
1235
|
-
return None
|
|
1236
|
-
|
|
1237
|
-
locator = registry.get_locator(element_ref)
|
|
1238
|
-
return locator.to_browser_library()
|
|
1239
|
-
|
|
1240
|
-
except Exception as e:
|
|
1241
|
-
logger.warning(f"Failed to get locator for ref {ref}: {e}")
|
|
1242
|
-
return None
|
|
1243
|
-
|
|
1244
|
-
def validate_ref(self, session_id: str, ref: str) -> Dict[str, Any]:
|
|
1245
|
-
"""Validate an element ref exists and is not stale.
|
|
1246
|
-
|
|
1247
|
-
Args:
|
|
1248
|
-
session_id: The session ID
|
|
1249
|
-
ref: Element reference to validate
|
|
1250
|
-
|
|
1251
|
-
Returns:
|
|
1252
|
-
Dict with validation result
|
|
1253
|
-
"""
|
|
1254
|
-
try:
|
|
1255
|
-
from robotmcp.container import get_container
|
|
1256
|
-
from robotmcp.domains.shared.kernel import ElementRef
|
|
1257
|
-
|
|
1258
|
-
registry = get_container().get_element_registry(session_id)
|
|
1259
|
-
|
|
1260
|
-
# Validate ref format
|
|
1261
|
-
try:
|
|
1262
|
-
element_ref = ElementRef(ref)
|
|
1263
|
-
except ValueError as e:
|
|
1264
|
-
return {
|
|
1265
|
-
"valid": False,
|
|
1266
|
-
"error": f"Invalid ref format: {e}",
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
if registry.is_stale():
|
|
1270
|
-
return {
|
|
1271
|
-
"valid": False,
|
|
1272
|
-
"error": "Registry is stale. Capture a new snapshot to get fresh refs.",
|
|
1273
|
-
}
|
|
1274
|
-
|
|
1275
|
-
# Check if ref exists in registry
|
|
1276
|
-
if element_ref not in registry.refs:
|
|
1277
|
-
available_refs = [str(r) for r in list(registry.refs.keys())[:10]]
|
|
1278
|
-
return {
|
|
1279
|
-
"valid": False,
|
|
1280
|
-
"error": f"Ref '{ref}' not found. Available refs: {available_refs}",
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
return {
|
|
1284
|
-
"valid": True,
|
|
1285
|
-
"ref": ref,
|
|
1286
|
-
"locator": self.get_locator_for_ref(session_id, ref),
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
except Exception as e:
|
|
1290
|
-
return {
|
|
1291
|
-
"valid": False,
|
|
1292
|
-
"error": str(e),
|
|
1293
|
-
}
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
This container wires together the various DDD bounded contexts:
|
|
4
4
|
- Snapshot Context: ARIA tree capture and token optimization
|
|
5
|
-
- Element Registry Context: Ref-to-locator mapping
|
|
6
|
-
- Action Context: Pre-validation and response filtering
|
|
7
5
|
- Timeout Context: Dual timeout management
|
|
8
6
|
|
|
9
7
|
Usage:
|
|
@@ -22,8 +20,6 @@ from typing import Dict, Optional, TYPE_CHECKING
|
|
|
22
20
|
|
|
23
21
|
if TYPE_CHECKING:
|
|
24
22
|
from robotmcp.domains.timeout import TimeoutService, TimeoutPolicy
|
|
25
|
-
from robotmcp.domains.action import PreValidator
|
|
26
|
-
from robotmcp.domains.element_registry import ElementRegistry
|
|
27
23
|
from robotmcp.domains.snapshot import PageSnapshot
|
|
28
24
|
from robotmcp.optimization import (
|
|
29
25
|
PatternStore,
|
|
@@ -48,11 +44,9 @@ class ServiceContainer:
|
|
|
48
44
|
pattern_store: Shared pattern storage for learned optimizations
|
|
49
45
|
performance_collector: Centralized performance metrics
|
|
50
46
|
timeout_service: Timeout management service
|
|
51
|
-
pre_validator: Pre-validation service for actions
|
|
52
47
|
page_analyzer: Page complexity analyzer
|
|
53
48
|
|
|
54
49
|
Session-specific services (keyed by session_id):
|
|
55
|
-
- Element registries
|
|
56
50
|
- Timeout policies
|
|
57
51
|
- Snapshot caches
|
|
58
52
|
"""
|
|
@@ -63,13 +57,9 @@ class ServiceContainer:
|
|
|
63
57
|
default=None, repr=False
|
|
64
58
|
)
|
|
65
59
|
_timeout_service: Optional["TimeoutService"] = field(default=None, repr=False)
|
|
66
|
-
_pre_validator: Optional["PreValidator"] = field(default=None, repr=False)
|
|
67
60
|
_page_analyzer: Optional["PageAnalyzer"] = field(default=None, repr=False)
|
|
68
61
|
|
|
69
62
|
# Session-scoped registries
|
|
70
|
-
_element_registries: Dict[str, "ElementRegistry"] = field(
|
|
71
|
-
default_factory=dict, repr=False
|
|
72
|
-
)
|
|
73
63
|
_timeout_policies: Dict[str, "TimeoutPolicy"] = field(
|
|
74
64
|
default_factory=dict, repr=False
|
|
75
65
|
)
|
|
@@ -103,14 +93,6 @@ class ServiceContainer:
|
|
|
103
93
|
self._timeout_service = TimeoutService()
|
|
104
94
|
return self._timeout_service
|
|
105
95
|
|
|
106
|
-
@property
|
|
107
|
-
def pre_validator(self) -> "PreValidator":
|
|
108
|
-
"""Get the pre-validation service."""
|
|
109
|
-
if self._pre_validator is None:
|
|
110
|
-
from robotmcp.domains.action import PreValidator
|
|
111
|
-
self._pre_validator = PreValidator()
|
|
112
|
-
return self._pre_validator
|
|
113
|
-
|
|
114
96
|
@property
|
|
115
97
|
def page_analyzer(self) -> "PageAnalyzer":
|
|
116
98
|
"""Get the page complexity analyzer."""
|
|
@@ -119,20 +101,6 @@ class ServiceContainer:
|
|
|
119
101
|
self._page_analyzer = PageAnalyzer()
|
|
120
102
|
return self._page_analyzer
|
|
121
103
|
|
|
122
|
-
def get_element_registry(self, session_id: str) -> "ElementRegistry":
|
|
123
|
-
"""Get or create an element registry for a session.
|
|
124
|
-
|
|
125
|
-
Args:
|
|
126
|
-
session_id: The session identifier
|
|
127
|
-
|
|
128
|
-
Returns:
|
|
129
|
-
ElementRegistry for the session
|
|
130
|
-
"""
|
|
131
|
-
if session_id not in self._element_registries:
|
|
132
|
-
from robotmcp.domains.element_registry import ElementRegistry
|
|
133
|
-
self._element_registries[session_id] = ElementRegistry.create_for_session(session_id)
|
|
134
|
-
return self._element_registries[session_id]
|
|
135
|
-
|
|
136
104
|
def get_timeout_policy(self, session_id: str) -> "TimeoutPolicy":
|
|
137
105
|
"""Get or create a timeout policy for a session.
|
|
138
106
|
|
|
@@ -174,7 +142,6 @@ class ServiceContainer:
|
|
|
174
142
|
Args:
|
|
175
143
|
session_id: The session to clear
|
|
176
144
|
"""
|
|
177
|
-
self._element_registries.pop(session_id, None)
|
|
178
145
|
self._timeout_policies.pop(session_id, None)
|
|
179
146
|
self._snapshot_cache.pop(session_id, None)
|
|
180
147
|
logger.debug(f"Cleared container data for session {session_id}")
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
This package contains the DDD implementation for:
|
|
4
4
|
- Snapshot Context: Accessibility tree generation and compression
|
|
5
|
-
- Element Registry Context: Element reference mapping and stale detection
|
|
6
|
-
- Action Context: Tool execution and response filtering
|
|
7
5
|
- Timeout Context: Timeout configuration and enforcement
|
|
8
6
|
|
|
9
7
|
For architecture details, see:
|
|
@@ -37,33 +35,6 @@ from robotmcp.domains.timeout import (
|
|
|
37
35
|
TimeoutContextManager,
|
|
38
36
|
)
|
|
39
37
|
|
|
40
|
-
from robotmcp.domains.action import (
|
|
41
|
-
# Value Objects
|
|
42
|
-
ActionParameters,
|
|
43
|
-
ExecutionId,
|
|
44
|
-
FilteredResponse,
|
|
45
|
-
PreValidationResult,
|
|
46
|
-
ResponseConfig,
|
|
47
|
-
# Protocols
|
|
48
|
-
BrowserAdapter,
|
|
49
|
-
ElementRegistry,
|
|
50
|
-
Locator,
|
|
51
|
-
# Services
|
|
52
|
-
PreValidator,
|
|
53
|
-
ResponseBuilder,
|
|
54
|
-
IncrementalResponseBuilder,
|
|
55
|
-
# Aggregates
|
|
56
|
-
ActionExecution,
|
|
57
|
-
RetryableActionExecution,
|
|
58
|
-
# Events
|
|
59
|
-
ActionCompleted,
|
|
60
|
-
ActionFailed,
|
|
61
|
-
ActionRetrying,
|
|
62
|
-
ActionStarted,
|
|
63
|
-
PreValidationCompleted,
|
|
64
|
-
TimeoutExceeded as ActionTimeoutExceeded,
|
|
65
|
-
)
|
|
66
|
-
|
|
67
38
|
__all__ = [
|
|
68
39
|
# Shared Kernel
|
|
69
40
|
"AriaNode",
|
|
@@ -87,28 +58,4 @@ __all__ = [
|
|
|
87
58
|
# Timeout Domain - Services
|
|
88
59
|
"TimeoutService",
|
|
89
60
|
"TimeoutContextManager",
|
|
90
|
-
# Action Domain - Value Objects
|
|
91
|
-
"ActionParameters",
|
|
92
|
-
"ExecutionId",
|
|
93
|
-
"FilteredResponse",
|
|
94
|
-
"PreValidationResult",
|
|
95
|
-
"ResponseConfig",
|
|
96
|
-
# Action Domain - Protocols
|
|
97
|
-
"BrowserAdapter",
|
|
98
|
-
"ElementRegistry",
|
|
99
|
-
"Locator",
|
|
100
|
-
# Action Domain - Services
|
|
101
|
-
"PreValidator",
|
|
102
|
-
"ResponseBuilder",
|
|
103
|
-
"IncrementalResponseBuilder",
|
|
104
|
-
# Action Domain - Aggregates
|
|
105
|
-
"ActionExecution",
|
|
106
|
-
"RetryableActionExecution",
|
|
107
|
-
# Action Domain - Events
|
|
108
|
-
"ActionCompleted",
|
|
109
|
-
"ActionFailed",
|
|
110
|
-
"ActionRetrying",
|
|
111
|
-
"ActionStarted",
|
|
112
|
-
"PreValidationCompleted",
|
|
113
|
-
"ActionTimeoutExceeded",
|
|
114
61
|
]
|
|
@@ -2329,6 +2329,18 @@ async def manage_session(
|
|
|
2329
2329
|
name, value = item.split("=", 1)
|
|
2330
2330
|
data[name.strip()] = value
|
|
2331
2331
|
|
|
2332
|
+
if not data:
|
|
2333
|
+
return {
|
|
2334
|
+
"success": False,
|
|
2335
|
+
"action": "set_variables",
|
|
2336
|
+
"session_id": session_id,
|
|
2337
|
+
"error": "No variables to set. Provide a non-empty 'variables' parameter.",
|
|
2338
|
+
"hint": (
|
|
2339
|
+
"Pass variables as a dict: variables={\"MY_VAR\": \"value\"} "
|
|
2340
|
+
"or as a list: variables=[\"MY_VAR=value\", \"OTHER=123\"]"
|
|
2341
|
+
),
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2332
2344
|
set_kw = {
|
|
2333
2345
|
"test": "Set Test Variable",
|
|
2334
2346
|
"suite": "Set Suite Variable",
|
|
@@ -2336,22 +2348,34 @@ async def manage_session(
|
|
|
2336
2348
|
}.get(scope.lower(), "Set Suite Variable")
|
|
2337
2349
|
|
|
2338
2350
|
results: Dict[str, bool] = {}
|
|
2351
|
+
errors: Dict[str, str] = {}
|
|
2339
2352
|
client = _get_external_client_if_configured()
|
|
2340
2353
|
if client is not None:
|
|
2341
2354
|
for name, value in data.items():
|
|
2342
2355
|
try:
|
|
2343
2356
|
resp = client.set_variable(name, value, scope=scope)
|
|
2344
2357
|
results[name] = bool(resp.get("success"))
|
|
2345
|
-
|
|
2358
|
+
if not results[name]:
|
|
2359
|
+
errors[name] = resp.get("error", "Bridge returned success=false")
|
|
2360
|
+
except Exception as e:
|
|
2346
2361
|
results[name] = False
|
|
2347
|
-
|
|
2362
|
+
errors[name] = str(e)
|
|
2363
|
+
result_payload: Dict[str, Any] = {
|
|
2348
2364
|
"success": all(results.values()),
|
|
2349
2365
|
"action": "set_variables",
|
|
2350
2366
|
"session_id": session_id,
|
|
2351
|
-
"set":
|
|
2367
|
+
"set": [k for k, v in results.items() if v],
|
|
2352
2368
|
"scope": scope,
|
|
2353
2369
|
"external": True,
|
|
2354
2370
|
}
|
|
2371
|
+
if errors:
|
|
2372
|
+
result_payload["errors"] = errors
|
|
2373
|
+
result_payload["hint"] = (
|
|
2374
|
+
"Some variables failed to set via bridge. "
|
|
2375
|
+
"Check that the RF process is running and the bridge is reachable. "
|
|
2376
|
+
"Use manage_attach(action='status') to verify bridge health."
|
|
2377
|
+
)
|
|
2378
|
+
return result_payload
|
|
2355
2379
|
|
|
2356
2380
|
for name, value in data.items():
|
|
2357
2381
|
# Pass value directly with ${name} syntax - RF 7 handles Python
|
|
@@ -2364,6 +2388,8 @@ async def manage_session(
|
|
|
2364
2388
|
use_context=True,
|
|
2365
2389
|
)
|
|
2366
2390
|
results[name] = bool(res.get("success"))
|
|
2391
|
+
if not results[name]:
|
|
2392
|
+
errors[name] = res.get("error", "Keyword execution returned success=false")
|
|
2367
2393
|
|
|
2368
2394
|
# Track ALL manage_session variables for *** Variables *** section generation
|
|
2369
2395
|
# Variables set via manage_session (any scope) should be included in the Variables
|
|
@@ -2384,13 +2410,20 @@ async def manage_session(
|
|
|
2384
2410
|
except Exception as track_error:
|
|
2385
2411
|
logger.warning(f"Failed to track manage_session variables: {track_error}")
|
|
2386
2412
|
|
|
2387
|
-
|
|
2413
|
+
result_payload = {
|
|
2388
2414
|
"success": all(results.values()),
|
|
2389
2415
|
"action": "set_variables",
|
|
2390
2416
|
"session_id": session_id,
|
|
2391
|
-
"set":
|
|
2417
|
+
"set": [k for k, v in results.items() if v],
|
|
2392
2418
|
"scope": scope,
|
|
2393
2419
|
}
|
|
2420
|
+
if errors:
|
|
2421
|
+
result_payload["errors"] = errors
|
|
2422
|
+
result_payload["hint"] = (
|
|
2423
|
+
"Some variables failed to set. Ensure the session was initialized "
|
|
2424
|
+
"with manage_session(action='init') first and that BuiltIn library is loaded."
|
|
2425
|
+
)
|
|
2426
|
+
return result_payload
|
|
2394
2427
|
|
|
2395
2428
|
if action_norm in {"import_variables", "load_variables"}:
|
|
2396
2429
|
if not variable_file_path:
|
|
@@ -284,20 +284,32 @@ class RobotFrameworkDocStorage:
|
|
|
284
284
|
return keywords
|
|
285
285
|
|
|
286
286
|
def search_keywords(self, pattern: str) -> List[RFKeywordInfo]:
|
|
287
|
-
"""Search for keywords matching a pattern.
|
|
287
|
+
"""Search for keywords matching a pattern.
|
|
288
|
+
|
|
289
|
+
Supports glob wildcards (*, ?, []) for name matching.
|
|
290
|
+
Plain text without wildcards uses substring matching across
|
|
291
|
+
name, doc, short_doc, and tags.
|
|
292
|
+
"""
|
|
288
293
|
if not HAS_LIBDOC:
|
|
289
294
|
return []
|
|
290
|
-
|
|
291
|
-
|
|
295
|
+
|
|
296
|
+
import fnmatch
|
|
297
|
+
|
|
298
|
+
is_glob = any(c in pattern for c in "*?[")
|
|
299
|
+
pattern_lower = pattern.lower()
|
|
292
300
|
matches = []
|
|
293
|
-
|
|
301
|
+
|
|
294
302
|
for keyword_info in self.get_all_keywords():
|
|
295
|
-
if
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
303
|
+
if is_glob:
|
|
304
|
+
if fnmatch.fnmatch(keyword_info.name.lower(), pattern_lower):
|
|
305
|
+
matches.append(keyword_info)
|
|
306
|
+
else:
|
|
307
|
+
if (pattern_lower in keyword_info.name.lower() or
|
|
308
|
+
pattern_lower in keyword_info.doc.lower() or
|
|
309
|
+
pattern_lower in keyword_info.short_doc.lower() or
|
|
310
|
+
any(pattern_lower in tag.lower() for tag in keyword_info.tags)):
|
|
311
|
+
matches.append(keyword_info)
|
|
312
|
+
|
|
301
313
|
return matches
|
|
302
314
|
|
|
303
315
|
def get_library_documentation(self, library_name: str) -> Optional[RFLibraryInfo]:
|