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.
Files changed (155) hide show
  1. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/.gitignore +4 -1
  2. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/PKG-INFO +1 -1
  3. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/pyproject.toml +1 -1
  4. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/execution/keyword_executor.py +13 -0
  5. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/execution/page_source_service.py +0 -297
  6. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/container.py +0 -33
  7. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/__init__.py +0 -53
  8. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/server.py +38 -5
  9. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/rf_libdoc_integration.py +22 -10
  10. rf_mcp-0.28.dev1/src/robotmcp/domains/action/__init__.py +0 -72
  11. rf_mcp-0.28.dev1/src/robotmcp/domains/action/aggregates.py +0 -540
  12. rf_mcp-0.28.dev1/src/robotmcp/domains/action/events.py +0 -276
  13. rf_mcp-0.28.dev1/src/robotmcp/domains/action/response_builder.py +0 -449
  14. rf_mcp-0.28.dev1/src/robotmcp/domains/action/services.py +0 -500
  15. rf_mcp-0.28.dev1/src/robotmcp/domains/action/value_objects.py +0 -478
  16. rf_mcp-0.28.dev1/src/robotmcp/domains/element_registry/__init__.py +0 -62
  17. rf_mcp-0.28.dev1/src/robotmcp/domains/element_registry/aggregates.py +0 -484
  18. rf_mcp-0.28.dev1/src/robotmcp/domains/element_registry/entities.py +0 -166
  19. rf_mcp-0.28.dev1/src/robotmcp/domains/element_registry/events.py +0 -210
  20. rf_mcp-0.28.dev1/src/robotmcp/domains/element_registry/repository.py +0 -250
  21. rf_mcp-0.28.dev1/src/robotmcp/domains/element_registry/value_objects.py +0 -386
  22. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/LICENSE +0 -0
  23. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/README.md +0 -0
  24. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/examples/plugins/doctest_plugin/README.md +0 -0
  25. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/examples/plugins/doctest_plugin/pyproject.toml +0 -0
  26. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/examples/plugins/sample_plugin/README.md +0 -0
  27. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/__init__.py +0 -0
  28. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/adapters/__init__.py +0 -0
  29. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/adapters/adapter_factory.py +0 -0
  30. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/adapters/browser_adapter.py +0 -0
  31. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/adapters/playwright_adapter.py +0 -0
  32. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/adapters/selenium_adapter.py +0 -0
  33. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/attach/__init__.py +0 -0
  34. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/attach/mcp_attach.py +0 -0
  35. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/__init__.py +0 -0
  36. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/browser/__init__.py +0 -0
  37. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/browser/browser_library_manager.py +0 -0
  38. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/execution/__init__.py +0 -0
  39. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/execution/execution_coordinator.py +0 -0
  40. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/execution/external_rf_client.py +0 -0
  41. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/execution/locator_converter.py +0 -0
  42. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/execution/mobile_capability_service.py +0 -0
  43. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/execution/rf_native_context_manager.py +0 -0
  44. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/execution/session_manager.py +0 -0
  45. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/execution/suite_execution_service.py +0 -0
  46. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/keyword_matcher.py +0 -0
  47. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/library_recommender.py +0 -0
  48. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/nlp_processor.py +0 -0
  49. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/state_manager.py +0 -0
  50. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/test_builder.py +0 -0
  51. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/variables/__init__.py +0 -0
  52. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/components/variables/variable_resolver.py +0 -0
  53. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/config/__init__.py +0 -0
  54. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/config/library_registry.py +0 -0
  55. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/core/__init__.py +0 -0
  56. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/core/dynamic_keyword_orchestrator.py +0 -0
  57. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/core/event_bus.py +0 -0
  58. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/core/keyword_discovery.py +0 -0
  59. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/core/library_manager.py +0 -0
  60. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/core/session_manager.py +0 -0
  61. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/instruction/__init__.py +0 -0
  62. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/instruction/adapters/__init__.py +0 -0
  63. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/instruction/adapters/fastmcp_adapter.py +0 -0
  64. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/instruction/aggregates.py +0 -0
  65. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/instruction/entities.py +0 -0
  66. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/instruction/events.py +0 -0
  67. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/instruction/repository.py +0 -0
  68. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/instruction/security.py +0 -0
  69. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/instruction/services.py +0 -0
  70. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/instruction/templates/api_focused.txt +0 -0
  71. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/instruction/templates/browser_focused.txt +0 -0
  72. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/instruction/templates/detailed.txt +0 -0
  73. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/instruction/templates/discovery_first.txt +0 -0
  74. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/instruction/templates/minimal.txt +0 -0
  75. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/instruction/templates/standard.txt +0 -0
  76. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/instruction/value_objects.py +0 -0
  77. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/shared/__init__.py +0 -0
  78. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/shared/kernel.py +0 -0
  79. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/snapshot/__init__.py +0 -0
  80. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/snapshot/aggregates.py +0 -0
  81. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/snapshot/diff_service.py +0 -0
  82. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/snapshot/entities.py +0 -0
  83. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/snapshot/events.py +0 -0
  84. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/snapshot/list_folding.py +0 -0
  85. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/snapshot/models.py +0 -0
  86. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/snapshot/repository.py +0 -0
  87. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/snapshot/services.py +0 -0
  88. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/snapshot/value_objects.py +0 -0
  89. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/timeout/__init__.py +0 -0
  90. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/timeout/aggregates.py +0 -0
  91. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/timeout/entities.py +0 -0
  92. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/timeout/events.py +0 -0
  93. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/timeout/keyword_classifier.py +0 -0
  94. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/timeout/services.py +0 -0
  95. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/domains/timeout/value_objects.py +0 -0
  96. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/frontend/__init__.py +0 -0
  97. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/frontend/api.py +0 -0
  98. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/frontend/apps.py +0 -0
  99. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/frontend/asgi.py +0 -0
  100. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/frontend/bridge.py +0 -0
  101. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/frontend/config.py +0 -0
  102. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/frontend/controller.py +0 -0
  103. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/frontend/devserver.py +0 -0
  104. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/frontend/django_app.py +0 -0
  105. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/frontend/static/frontend/app.js +0 -0
  106. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/frontend/static/frontend/base.css +0 -0
  107. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/frontend/templates/frontend/index.html +0 -0
  108. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/frontend/templates/frontend/layout.html +0 -0
  109. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/frontend/urls.py +0 -0
  110. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/frontend/views.py +0 -0
  111. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/models/__init__.py +0 -0
  112. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/models/browser_models.py +0 -0
  113. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/models/config_models.py +0 -0
  114. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/models/execution_models.py +0 -0
  115. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/models/library_models.py +0 -0
  116. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/models/session_models.py +0 -0
  117. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/optimization/__init__.py +0 -0
  118. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/optimization/collector.py +0 -0
  119. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/optimization/compression_learner.py +0 -0
  120. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/optimization/folding_learner.py +0 -0
  121. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/optimization/instruction_hooks.py +0 -0
  122. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/optimization/instruction_learner.py +0 -0
  123. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/optimization/page_analyzer.py +0 -0
  124. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/optimization/pattern_store.py +0 -0
  125. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/optimization/ref_learner.py +0 -0
  126. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/optimization/timeout_learner.py +0 -0
  127. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/plugins/__init__.py +0 -0
  128. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/plugins/base.py +0 -0
  129. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/plugins/builtin/__init__.py +0 -0
  130. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/plugins/builtin/browser_plugin.py +0 -0
  131. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/plugins/builtin/definitions.py +0 -0
  132. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/plugins/builtin/requests_plugin.py +0 -0
  133. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/plugins/builtin/selenium_plugin.py +0 -0
  134. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/plugins/contracts.py +0 -0
  135. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/plugins/discovery.py +0 -0
  136. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/plugins/manager.py +0 -0
  137. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/__init__.py +0 -0
  138. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/argument_processor.py +0 -0
  139. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/enhanced_response_serializer.py +0 -0
  140. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/enhanced_serialization_integration.py +0 -0
  141. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/hints.py +0 -0
  142. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/libdoc_argument_parser.py +0 -0
  143. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/library_checker.py +0 -0
  144. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/library_detection.py +0 -0
  145. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/library_detector.py +0 -0
  146. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/library_loading_validator.py +0 -0
  147. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/response_serializer.py +0 -0
  148. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/rf_native_type_converter.py +0 -0
  149. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/rf_variables_compatibility.py +0 -0
  150. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/sampling.py +0 -0
  151. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/server_integration.py +0 -0
  152. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/session_resolution.py +0 -0
  153. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/token_efficient_output.py +0 -0
  154. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/src/robotmcp/utils/validation.py +0 -0
  155. {rf_mcp-0.28.dev1 → rf_mcp-0.28.dev2}/tests/e2e/README.md +0 -0
@@ -223,4 +223,7 @@ playwright*.txt
223
223
  .swarm/
224
224
  *.robot
225
225
 
226
- tests/e2e/metrics/*.json
226
+ tests/e2e/metrics/*.json
227
+
228
+ tests/experiments/*
229
+ resources/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rf-mcp
3
- Version: 0.28.dev1
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.dev01"
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]:
@@ -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
- except Exception:
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
- return {
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": list(results.keys()),
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
- return {
2413
+ result_payload = {
2388
2414
  "success": all(results.values()),
2389
2415
  "action": "set_variables",
2390
2416
  "session_id": session_id,
2391
- "set": list(results.keys()),
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
- pattern = pattern.lower()
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 (pattern in keyword_info.name.lower() or
296
- pattern in keyword_info.doc.lower() or
297
- pattern in keyword_info.short_doc.lower() or
298
- any(pattern in tag.lower() for tag in keyword_info.tags)):
299
- matches.append(keyword_info)
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]: