rf-mcp 0.34.0.dev3__tar.gz → 0.34.0.dev5__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 (263) hide show
  1. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/PKG-INFO +4 -2
  2. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/README.md +1 -1
  3. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/pyproject.toml +10 -1
  4. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/desktop_display_safety.py +59 -0
  5. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/desktop_guidance.py +1 -0
  6. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/keyword_executor.py +309 -10
  7. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/builtin/platynui_plugin.py +378 -0
  8. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.gitignore +0 -0
  9. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/@msgpackr-extract/msgpackr-extract-linux-x64/README.md +0 -0
  10. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/@standard-schema/spec/LICENSE +0 -0
  11. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/@standard-schema/spec/README.md +0 -0
  12. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/cross-spawn/LICENSE +0 -0
  13. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/cross-spawn/README.md +0 -0
  14. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/detect-libc/LICENSE +0 -0
  15. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/detect-libc/README.md +0 -0
  16. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/effect/LICENSE +0 -0
  17. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/effect/README.md +0 -0
  18. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/fast-check/LICENSE +0 -0
  19. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/fast-check/README.md +0 -0
  20. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/find-my-way-ts/LICENSE +0 -0
  21. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/find-my-way-ts/README.md +0 -0
  22. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/ini/LICENSE +0 -0
  23. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/ini/README.md +0 -0
  24. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/isexe/LICENSE +0 -0
  25. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/isexe/README.md +0 -0
  26. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/kubernetes-types/README.md +0 -0
  27. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/msgpackr/LICENSE +0 -0
  28. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/msgpackr/README.md +0 -0
  29. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/msgpackr-extract/LICENSE +0 -0
  30. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/msgpackr-extract/README.md +0 -0
  31. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/multipasta/LICENSE +0 -0
  32. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/multipasta/README.md +0 -0
  33. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/node-gyp-build-optional-packages/LICENSE +0 -0
  34. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/node-gyp-build-optional-packages/README.md +0 -0
  35. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/pure-rand/LICENSE +0 -0
  36. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/pure-rand/README.md +0 -0
  37. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/toml/LICENSE +0 -0
  38. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/toml/README.md +0 -0
  39. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/uuid/README.md +0 -0
  40. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/which/LICENSE +0 -0
  41. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/which/README.md +0 -0
  42. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/yaml/LICENSE +0 -0
  43. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/yaml/README.md +0 -0
  44. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/zod/LICENSE +0 -0
  45. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/zod/README.md +0 -0
  46. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/LICENSE +0 -0
  47. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/__init__.py +0 -0
  48. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/adapters/__init__.py +0 -0
  49. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/adapters/adapter_factory.py +0 -0
  50. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/adapters/browser_adapter.py +0 -0
  51. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/adapters/page_state_adapter.py +0 -0
  52. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/adapters/playwright_adapter.py +0 -0
  53. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/adapters/recovery_adapter.py +0 -0
  54. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/adapters/selenium_adapter.py +0 -0
  55. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/attach/__init__.py +0 -0
  56. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/attach/mcp_attach.py +0 -0
  57. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/compat/__init__.py +0 -0
  58. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/compat/fastmcp_compat.py +0 -0
  59. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/__init__.py +0 -0
  60. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/browser/__init__.py +0 -0
  61. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/browser/browser_library_manager.py +0 -0
  62. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/__init__.py +0 -0
  63. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/desktop_execution_signals.py +0 -0
  64. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/desktop_launch_env.py +0 -0
  65. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/execution_coordinator.py +0 -0
  66. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/external_rf_client.py +0 -0
  67. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/locator_arg_introspection.py +0 -0
  68. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/locator_converter.py +0 -0
  69. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/mobile_capability_service.py +0 -0
  70. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/page_source_service.py +0 -0
  71. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/platynui_focus.py +0 -0
  72. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/rf_native_context_manager.py +0 -0
  73. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/session_manager.py +0 -0
  74. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/suite_execution_service.py +0 -0
  75. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/ui_tree_service.py +0 -0
  76. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/keyword_matcher.py +0 -0
  77. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/library_recommender.py +0 -0
  78. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/nlp_processor.py +0 -0
  79. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/state_manager.py +0 -0
  80. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/test_builder.py +0 -0
  81. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/variables/__init__.py +0 -0
  82. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/variables/variable_resolver.py +0 -0
  83. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/config/__init__.py +0 -0
  84. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/config/library_registry.py +0 -0
  85. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/container.py +0 -0
  86. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/core/__init__.py +0 -0
  87. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/core/dynamic_keyword_orchestrator.py +0 -0
  88. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/core/event_bus.py +0 -0
  89. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/core/keyword_discovery.py +0 -0
  90. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/core/library_manager.py +0 -0
  91. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/core/session_manager.py +0 -0
  92. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/__init__.py +0 -0
  93. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/artifact_output/__init__.py +0 -0
  94. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/artifact_output/aggregates.py +0 -0
  95. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/artifact_output/entities.py +0 -0
  96. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/artifact_output/events.py +0 -0
  97. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/artifact_output/services.py +0 -0
  98. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/artifact_output/value_objects.py +0 -0
  99. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/batch_execution/__init__.py +0 -0
  100. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/batch_execution/aggregates.py +0 -0
  101. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/batch_execution/entities.py +0 -0
  102. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/batch_execution/events.py +0 -0
  103. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/batch_execution/services.py +0 -0
  104. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/batch_execution/value_objects.py +0 -0
  105. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/instruction/__init__.py +0 -0
  106. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/instruction/adapters/__init__.py +0 -0
  107. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/instruction/adapters/fastmcp_adapter.py +0 -0
  108. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/instruction/aggregates.py +0 -0
  109. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/instruction/entities.py +0 -0
  110. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/instruction/events.py +0 -0
  111. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/instruction/repository.py +0 -0
  112. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/instruction/security.py +0 -0
  113. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/instruction/services.py +0 -0
  114. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/instruction/value_objects.py +0 -0
  115. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/intent/__init__.py +0 -0
  116. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/intent/adapters/__init__.py +0 -0
  117. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/intent/adapters/locator_normalizer_adapter.py +0 -0
  118. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/intent/adapters/mcp_tool.py +0 -0
  119. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/intent/adapters/session_lookup_adapter.py +0 -0
  120. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/intent/aggregates.py +0 -0
  121. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/intent/entities.py +0 -0
  122. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/intent/events.py +0 -0
  123. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/intent/services.py +0 -0
  124. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/intent/value_objects.py +0 -0
  125. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/keyword_resolution/__init__.py +0 -0
  126. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/keyword_resolution/aggregates.py +0 -0
  127. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/keyword_resolution/events.py +0 -0
  128. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/keyword_resolution/services.py +0 -0
  129. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/keyword_resolution/value_objects.py +0 -0
  130. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/memory/__init__.py +0 -0
  131. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/memory/adapters/__init__.py +0 -0
  132. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/memory/adapters/fastmcp_adapter.py +0 -0
  133. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/memory/adapters/sqlite_vec_adapter.py +0 -0
  134. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/memory/aggregates.py +0 -0
  135. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/memory/entities.py +0 -0
  136. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/memory/events.py +0 -0
  137. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/memory/repository.py +0 -0
  138. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/memory/services.py +0 -0
  139. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/memory/value_objects.py +0 -0
  140. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/recovery/__init__.py +0 -0
  141. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/recovery/aggregates.py +0 -0
  142. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/recovery/entities.py +0 -0
  143. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/recovery/events.py +0 -0
  144. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/recovery/services.py +0 -0
  145. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/recovery/value_objects.py +0 -0
  146. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/response_optimization/__init__.py +0 -0
  147. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/response_optimization/aggregates.py +0 -0
  148. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/response_optimization/events.py +0 -0
  149. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/response_optimization/repository.py +0 -0
  150. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/response_optimization/services.py +0 -0
  151. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/response_optimization/value_objects.py +0 -0
  152. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/shared/__init__.py +0 -0
  153. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/shared/kernel.py +0 -0
  154. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/__init__.py +0 -0
  155. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/aggregates.py +0 -0
  156. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/delta_aggregates.py +0 -0
  157. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/delta_entities.py +0 -0
  158. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/delta_events.py +0 -0
  159. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/delta_service.py +0 -0
  160. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/delta_value_objects.py +0 -0
  161. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/diff_service.py +0 -0
  162. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/entities.py +0 -0
  163. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/events.py +0 -0
  164. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/list_folding.py +0 -0
  165. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/models.py +0 -0
  166. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/repository.py +0 -0
  167. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/services.py +0 -0
  168. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/value_objects.py +0 -0
  169. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/timeout/__init__.py +0 -0
  170. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/timeout/aggregates.py +0 -0
  171. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/timeout/entities.py +0 -0
  172. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/timeout/events.py +0 -0
  173. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/timeout/keyword_classifier.py +0 -0
  174. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/timeout/services.py +0 -0
  175. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/timeout/value_objects.py +0 -0
  176. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/token_accounting/__init__.py +0 -0
  177. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/token_accounting/aggregates.py +0 -0
  178. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/token_accounting/entities.py +0 -0
  179. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/token_accounting/events.py +0 -0
  180. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/token_accounting/repository.py +0 -0
  181. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/token_accounting/services.py +0 -0
  182. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/token_accounting/value_objects.py +0 -0
  183. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/tool_profile/__init__.py +0 -0
  184. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/tool_profile/adapters/__init__.py +0 -0
  185. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/tool_profile/adapters/fastmcp_adapter.py +0 -0
  186. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/tool_profile/aggregates.py +0 -0
  187. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/tool_profile/entities.py +0 -0
  188. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/tool_profile/events.py +0 -0
  189. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/tool_profile/repository.py +0 -0
  190. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/tool_profile/services.py +0 -0
  191. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/tool_profile/value_objects.py +0 -0
  192. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/entry.py +0 -0
  193. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/__init__.py +0 -0
  194. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/api.py +0 -0
  195. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/apps.py +0 -0
  196. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/asgi.py +0 -0
  197. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/bridge.py +0 -0
  198. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/config.py +0 -0
  199. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/controller.py +0 -0
  200. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/devserver.py +0 -0
  201. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/django_app.py +0 -0
  202. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/static/frontend/app.js +0 -0
  203. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/static/frontend/base.css +0 -0
  204. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/templates/frontend/index.html +0 -0
  205. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/templates/frontend/layout.html +0 -0
  206. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/urls.py +0 -0
  207. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/views.py +0 -0
  208. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/models/__init__.py +0 -0
  209. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/models/browser_models.py +0 -0
  210. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/models/config_models.py +0 -0
  211. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/models/execution_models.py +0 -0
  212. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/models/library_models.py +0 -0
  213. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/models/session_models.py +0 -0
  214. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/onboarding/__init__.py +0 -0
  215. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/onboarding/adapters.py +0 -0
  216. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/onboarding/cli.py +0 -0
  217. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/onboarding/codecs.py +0 -0
  218. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/onboarding/diagnostics.py +0 -0
  219. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/onboarding/installer.py +0 -0
  220. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/onboarding/manifest.py +0 -0
  221. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/optimization/__init__.py +0 -0
  222. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/optimization/collector.py +0 -0
  223. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/optimization/compression_learner.py +0 -0
  224. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/optimization/folding_learner.py +0 -0
  225. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/optimization/instruction_hooks.py +0 -0
  226. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/optimization/instruction_learner.py +0 -0
  227. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/optimization/page_analyzer.py +0 -0
  228. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/optimization/pattern_store.py +0 -0
  229. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/optimization/ref_learner.py +0 -0
  230. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/optimization/timeout_learner.py +0 -0
  231. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/__init__.py +0 -0
  232. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/base.py +0 -0
  233. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/builtin/__init__.py +0 -0
  234. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/builtin/browser_plugin.py +0 -0
  235. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/builtin/definitions.py +0 -0
  236. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/builtin/requests_plugin.py +0 -0
  237. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/builtin/selenium_plugin.py +0 -0
  238. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/contracts.py +0 -0
  239. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/discovery.py +0 -0
  240. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/manager.py +0 -0
  241. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/server.py +0 -0
  242. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/__init__.py +0 -0
  243. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/argument_processor.py +0 -0
  244. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/enhanced_response_serializer.py +0 -0
  245. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/enhanced_serialization_integration.py +0 -0
  246. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/hints.py +0 -0
  247. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/libdoc_argument_parser.py +0 -0
  248. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/library_checker.py +0 -0
  249. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/library_detection.py +0 -0
  250. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/library_detector.py +0 -0
  251. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/library_loading_validator.py +0 -0
  252. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/logging_setup.py +0 -0
  253. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/requests_guidance.py +0 -0
  254. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/response_serializer.py +0 -0
  255. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/rf_libdoc_integration.py +0 -0
  256. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/rf_native_type_converter.py +0 -0
  257. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/rf_variables_compatibility.py +0 -0
  258. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/sampling.py +0 -0
  259. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/server_integration.py +0 -0
  260. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/session_resolution.py +0 -0
  261. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/token_efficient_output.py +0 -0
  262. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/validation.py +0 -0
  263. {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/visual_guidance.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rf-mcp
3
- Version: 0.34.0.dev3
3
+ Version: 0.34.0.dev5
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
@@ -21,9 +21,11 @@ Requires-Dist: tomlkit>=0.12.0
21
21
  Provides-Extra: all
22
22
  Requires-Dist: django<5.0,>=4.2; extra == 'all'
23
23
  Requires-Dist: model2vec>=0.4.0; extra == 'all'
24
+ Requires-Dist: platynui-cli==0.13.0.dev2; (python_version >= '3.12') and extra == 'all'
24
25
  Requires-Dist: robotframework-appiumlibrary; extra == 'all'
25
26
  Requires-Dist: robotframework-browser; extra == 'all'
26
27
  Requires-Dist: robotframework-databaselibrary; extra == 'all'
28
+ Requires-Dist: robotframework-platynui==0.13.0.dev2; (python_version >= '3.12') and extra == 'all'
27
29
  Requires-Dist: robotframework-requests; extra == 'all'
28
30
  Requires-Dist: robotframework-seleniumlibrary; extra == 'all'
29
31
  Requires-Dist: sqlite-vec>=0.1.6; extra == 'all'
@@ -228,7 +230,7 @@ uv pip install --pre rf-mcp[desktop] # PlatynUI new-core (Rust) native desktop
228
230
  uv pip install rf-mcp[frontend] # Django-based web frontend dashboard
229
231
  uv pip install rf-mcp[memory] # Persistent semantic memory (sqlite-vec + model2vec)
230
232
  uv pip install rf-mcp[semantic] # Sentence-transformers for find_keywords embedding ranking
231
- uv pip install rf-mcp[all] # All optional Robot Framework libraries (desktop is separate — prerelease/3.12+)
233
+ uv pip install rf-mcp[all] # All optional Robot Framework libraries (includes desktop on Python 3.12+)
232
234
 
233
235
  # Alternatively, add to an existing uv project
234
236
  uv init
@@ -171,7 +171,7 @@ uv pip install --pre rf-mcp[desktop] # PlatynUI new-core (Rust) native desktop
171
171
  uv pip install rf-mcp[frontend] # Django-based web frontend dashboard
172
172
  uv pip install rf-mcp[memory] # Persistent semantic memory (sqlite-vec + model2vec)
173
173
  uv pip install rf-mcp[semantic] # Sentence-transformers for find_keywords embedding ranking
174
- uv pip install rf-mcp[all] # All optional Robot Framework libraries (desktop is separate — prerelease/3.12+)
174
+ uv pip install rf-mcp[all] # All optional Robot Framework libraries (includes desktop on Python 3.12+)
175
175
 
176
176
  # Alternatively, add to an existing uv project
177
177
  uv init
@@ -4,7 +4,10 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "rf-mcp"
7
- version = "0.34.0.dev3"
7
+ # 0.34.0.dev5: harden-platynui-stuck-key-release — extend the platynui-windows-runtime
8
+ # stuck-key net from modifiers-only to a tracked held-key registry (releases
9
+ # non-modifier keys too) plus persisted-state recovery across hard process kills.
10
+ version = "0.34.0.dev5"
8
11
  description = "Robot Framework MCP Server - Natural Language Test Automation Bridge"
9
12
  authors = [{ name = "Many Kasiriha" }]
10
13
  license = { text = "Apache-2.0" }
@@ -65,6 +68,12 @@ all = [
65
68
  "sqlite-vec>=0.1.6",
66
69
  "model2vec>=0.4.0",
67
70
  "tiktoken>=0.7.0",
71
+ # Desktop automation is part of [all]. Referenced as a self-extra so the
72
+ # PlatynUI pins stay defined in exactly one place (see `desktop` above) —
73
+ # bumping them there is enough. The `python_version >= "3.12"` markers ride
74
+ # along, so on 3.10/3.11 this contributes nothing and [all] still resolves.
75
+ # The exact-pinned prerelease is accepted without a global --pre.
76
+ "rf-mcp[desktop]",
68
77
  ]
69
78
  # NOTE: dev tooling lives in [dependency-groups].dev (PEP 735), which is what
70
79
  # `uv sync` / CI use. The former [project.optional-dependencies].dev extra was
@@ -40,12 +40,39 @@ ISOLATION_MARKER_ENV = "ROBOTMCP_PLATYNUI_ISOLATED_DISPLAY"
40
40
  ISOLATION_XPID_ENV = "ROBOTMCP_PLATYNUI_ISOLATED_XPID"
41
41
  ALLOW_ACTIVE_ENV = "ROBOTMCP_PLATYNUI_ALLOW_ACTIVE_DESKTOP"
42
42
  SAFETY_GUARD_ENV = "ROBOTMCP_PLATYNUI_SAFETY_GUARD" # "warn" => log, don't block
43
+ # Strict opt-IN to enforce isolation on Windows (change:
44
+ # fix-platynui-windows-runtime, F4). Windows has no headless-isolation model
45
+ # (no Xvfb/Xephyr equivalent), so the guard is permissive by default there;
46
+ # operators who insist on isolation set this to refuse instead.
47
+ REQUIRE_ISOLATED_ENV = "ROBOTMCP_PLATYNUI_REQUIRE_ISOLATED"
43
48
 
44
49
  _X_SERVER_BINARIES = ("xvfb", "xephyr", "xorg", "/x ", "/x\x00", "x11")
45
50
 
46
51
  ISOLATED = "isolated"
47
52
  ACTIVE = "active"
48
53
  UNKNOWN = "unknown"
54
+ # Windows has exactly one interactive desktop and no X11 isolated-display
55
+ # model; the X11/EWMH/marker probes never apply (change:
56
+ # fix-platynui-windows-runtime, F4).
57
+ WINDOWS = "windows"
58
+
59
+ # Windows-accurate isolation note — NEVER the Linux nested-X-server recipe.
60
+ _WINDOWS_ISOLATION_NOTE = (
61
+ "On Windows there is no headless nested-display isolation model. Desktop "
62
+ "automation drives the active session's desktop. To isolate it, run rf-mcp "
63
+ "in a dedicated Windows user session or over RDP to a separate session."
64
+ )
65
+
66
+
67
+ def _is_windows() -> bool:
68
+ """True on Windows (patchable via ``os.name`` in tests)."""
69
+ return os.name == "nt"
70
+
71
+
72
+ def require_isolated(environ: Optional[Dict[str, str]] = None) -> bool:
73
+ """Strict opt-in to enforce isolation on Windows (F4)."""
74
+ env = environ if environ is not None else os.environ
75
+ return env.get(REQUIRE_ISOLATED_ENV, "").strip().lower() in {"1", "true", "yes"}
49
76
 
50
77
 
51
78
  def _bound_display(environ: Dict[str, str]) -> Optional[str]:
@@ -201,6 +228,14 @@ def classify_bound_display_detailed(
201
228
  ``none`` (no display bound / probe inconclusive).
202
229
  """
203
230
  env = environ if environ is not None else os.environ
231
+ # Windows: no X11/EWMH/marker model applies — classify as `windows` before
232
+ # any probe (change: fix-platynui-windows-runtime, F4).
233
+ if _is_windows():
234
+ return {
235
+ "display": None,
236
+ "isolation": WINDOWS,
237
+ "isolation_source": "windows_console",
238
+ }
204
239
  display = _bound_display(env)
205
240
  if _has_isolation_marker(env, display):
206
241
  # Corroborate the marker against a recorded X-server PID (change:
@@ -286,6 +321,30 @@ def evaluate_safety(
286
321
  "classification": classification, "enforcing": True,
287
322
  "allowed": True, "bypassed": False, "reason": None,
288
323
  }
324
+ if classification == WINDOWS:
325
+ # F4: Windows has no isolated-display model — refusing every keyword
326
+ # (with an impossible Xephyr recipe) blocks all Windows automation.
327
+ # Allow by default with a one-time active-desktop warning; a strict
328
+ # opt-in enforces isolation for operators who want it. No Xephyr recipe.
329
+ if require_isolated(env):
330
+ return {
331
+ "classification": classification, "enforcing": True,
332
+ "allowed": False, "bypassed": False,
333
+ "reason": (
334
+ "ROBOTMCP_PLATYNUI_REQUIRE_ISOLATED is set and this is a "
335
+ "Windows active desktop. " + _WINDOWS_ISOLATION_NOTE
336
+ ),
337
+ "windows_note": _WINDOWS_ISOLATION_NOTE,
338
+ }
339
+ return {
340
+ "classification": classification, "enforcing": True,
341
+ "allowed": True, "bypassed": False,
342
+ "reason": (
343
+ "PlatynUI will drive the ACTIVE Windows desktop. "
344
+ + _WINDOWS_ISOLATION_NOTE
345
+ ),
346
+ "windows_note": _WINDOWS_ISOLATION_NOTE,
347
+ }
289
348
  reason = (
290
349
  f"bound display classified '{classification}': refusing desktop input "
291
350
  "to avoid leaking onto the user's active session. Run on an isolated "
@@ -30,6 +30,7 @@ _LOCATOR_CRIB: List[str] = [
30
30
  "Launch the app first (Process.Start Process), then Query for its control:Frame before acting.",
31
31
  "Read a value back with Get Attribute on the scoped node; GTK text views expose native:Text.CharacterCount (not live text content).",
32
32
  "Take Screenshot signature is (descriptor, filename, rect) — the FIRST positional is a node descriptor, not a filename; pass filename= for a path.",
33
+ "Prefer the atomic 'Keyboard Type' (self-contained press+release) for shortcuts, e.g. Keyboard Type <descriptor> <Ctrl+A>. A bare 'Keyboard Press' sends key-DOWN ONLY and MUST be paired with 'Keyboard Release' — an unpaired Press (esp. a non-modifier like a letter/F-key/Escape) leaves a key physically held on the OS.",
33
34
  "For the full locator cookbook call get_locator_guidance (topic defaults to the desktop/PlatynUI chapter).",
34
35
  ]
35
36
 
@@ -535,6 +535,22 @@ class KeywordExecutor:
535
535
  logger.debug("desktop input-effect snapshot skipped: %s", exc)
536
536
  return None
537
537
 
538
+ @staticmethod
539
+ def _is_desktop_keyboard_keyword(session, keyword) -> bool:
540
+ """True when this is a PlatynUI keyboard keyword on a desktop session
541
+ (change: fix-platynui-windows-runtime, F16). Used to release held
542
+ modifiers when a keyboard keyword fails or is killed mid-chord."""
543
+ try:
544
+ is_desktop = getattr(session, "is_desktop_session", None)
545
+ if not (callable(is_desktop) and is_desktop() is True):
546
+ return False
547
+ norm = str(keyword or "").lower().rsplit(".", 1)[-1].strip()
548
+ return norm in (
549
+ "keyboard press", "keyboard type", "keyboard release",
550
+ )
551
+ except Exception: # pragma: no cover - defensive
552
+ return False
553
+
538
554
  def _platynui_safety_guard(self, session, keyword):
539
555
  """Active-desktop safety guard for PlatynUI interaction keywords
540
556
  (change: platynui-desktop-safety-isolation).
@@ -565,6 +581,18 @@ class KeywordExecutor:
565
581
  "PlatynUI safety guard: refused on %s desktop",
566
582
  outcome["classification"],
567
583
  )
584
+ elif outcome["classification"] == "windows":
585
+ # F4: one-time warning that Windows automation drives the active
586
+ # desktop (there is no isolated-display model on Windows).
587
+ if not getattr(session, "_platynui_windows_warned", False):
588
+ try:
589
+ session._platynui_windows_warned = True
590
+ except Exception:
591
+ pass
592
+ logger.warning(
593
+ "PlatynUI: driving the ACTIVE Windows desktop — %s",
594
+ outcome.get("reason") or "",
595
+ )
568
596
  return outcome
569
597
 
570
598
  def _platynui_focus_before_act(self, session, keyword, arguments):
@@ -1886,13 +1914,165 @@ class KeywordExecutor:
1886
1914
  # causing it to go to stderr instead of the MCP transport.
1887
1915
  # The lock also prevents concurrent Selenium WebDriver access.
1888
1916
  async with self._execution_lock:
1889
- return await self._execute_keyword_serialized(
1890
- session, keyword, arguments, browser_library_manager,
1891
- detail_level, library_prefix, assign_to, use_context, timeout_ms,
1892
- record=record,
1893
- pre_validate_timeout_ms=pre_validate_timeout_ms,
1917
+ # F16 + harden-platynui-stuck-key-release: a desktop keyboard keyword
1918
+ # that raises or returns failure may have been killed mid-chord,
1919
+ # leaving a key physically held at the OS level (the operator's
1920
+ # keyboard wedged). We track the EXACT keys held (incl. non-modifiers)
1921
+ # so teardown/failure/exit release precisely those. A SUCCESSFUL
1922
+ # Keyboard Press legitimately holds a key for a later Keyboard
1923
+ # Release, so we do NOT release on success — but we DO record it.
1924
+ _kbd = self._is_desktop_keyboard_keyword(session, keyword)
1925
+ _kbd_kind, _kbd_seq = (
1926
+ self._desktop_keyboard_op(keyword, arguments) if _kbd else (None, None)
1927
+ )
1928
+ # Record intended-held keys BEFORE dispatch so a kill mid-call is
1929
+ # covered by the persisted state file (Press holds; Type holds then
1930
+ # releases within the same call).
1931
+ if _kbd_kind in ("press", "type") and _kbd_seq:
1932
+ self._record_pressed_keys(_kbd_seq)
1933
+ try:
1934
+ result = await self._execute_keyword_serialized(
1935
+ session, keyword, arguments, browser_library_manager,
1936
+ detail_level, library_prefix, assign_to, use_context,
1937
+ timeout_ms,
1938
+ record=record,
1939
+ pre_validate_timeout_ms=pre_validate_timeout_ms,
1940
+ )
1941
+ except BaseException:
1942
+ # An exception (incl. CancelledError from a killed run) is the
1943
+ # canonical "killed mid-chord" case. Release SYNCHRONOUSLY here
1944
+ # so it is guaranteed to run even while the task is being
1945
+ # cancelled (an awaited offload could be interrupted before it
1946
+ # dispatches). keyboard_release is a fast input dispatch, not a
1947
+ # tree query, so the brief on-loop cost is acceptable.
1948
+ if _kbd:
1949
+ self._release_desktop_keys()
1950
+ raise
1951
+ _released_here = False
1952
+ if (
1953
+ _kbd
1954
+ and isinstance(result, dict)
1955
+ and not result.get("success", True)
1956
+ # F2: a steering-confidence downgrade means the native keyword
1957
+ # actually SUCCEEDED (the key is held as intended) — releasing
1958
+ # would corrupt a deliberate Press/Release chord. Only release
1959
+ # on a genuine execution failure.
1960
+ and not self._is_steering_downgrade(result)
1961
+ ):
1962
+ # F4: offload the release off the event loop on the normal
1963
+ # failure-return path (no cancellation concern here). Releases
1964
+ # the exact tracked held set (incl. non-modifiers) + clears state.
1965
+ await asyncio.to_thread(self._release_desktop_keys)
1966
+ _released_here = True
1967
+ # Steering: point the agent at the atomic Keyboard Type, which
1968
+ # cannot leave a key held.
1969
+ try:
1970
+ hints = result.setdefault("hints", [])
1971
+ if isinstance(hints, list):
1972
+ hints.append({
1973
+ "type": "platynui_keyboard_release_safety",
1974
+ "message": (
1975
+ "Released all held keyboard keys (including any "
1976
+ "non-modifier such as a letter/F-key/Escape) "
1977
+ "after this keyboard keyword failed. Prefer the "
1978
+ "atomic 'Keyboard Type <Ctrl+A>' (self-contained "
1979
+ "press+release) over a bare 'Keyboard Press', "
1980
+ "which sends key-DOWN only and MUST be paired "
1981
+ "with 'Keyboard Release'."
1982
+ ),
1983
+ })
1984
+ except Exception: # pragma: no cover - defensive
1985
+ pass
1986
+ # On a SUCCESSFUL Keyboard Type / Keyboard Release, the paired key-UP
1987
+ # already happened — clear those keys from the registry. (Press keeps
1988
+ # its recorded keys held until an explicit Keyboard Release.)
1989
+ if not _released_here and _kbd_kind in ("type", "release") and _kbd_seq:
1990
+ self._record_released_keys(_kbd_seq)
1991
+ return result
1992
+
1993
+ @staticmethod
1994
+ def _desktop_keyboard_op(keyword, arguments):
1995
+ """Return ``(kind, sequence)`` for a desktop keyboard keyword, where
1996
+ kind is ``"press"``/``"type"``/``"release"`` and sequence is the key
1997
+ text argument (2nd positional, or a ``text=`` named arg). Non-raising;
1998
+ returns ``(None, None)`` when it cannot classify. change:
1999
+ harden-platynui-stuck-key-release."""
2000
+ try:
2001
+ norm = str(keyword or "").lower().rsplit(".", 1)[-1].strip()
2002
+ kind = {
2003
+ "keyboard press": "press",
2004
+ "keyboard type": "type",
2005
+ "keyboard release": "release",
2006
+ }.get(norm)
2007
+ if kind is None:
2008
+ return (None, None)
2009
+ seq = None
2010
+ args = list(arguments or [])
2011
+ for a in args:
2012
+ if isinstance(a, str) and a.startswith("text="):
2013
+ seq = a[len("text="):]
2014
+ break
2015
+ if seq is None and len(args) >= 2:
2016
+ seq = args[1]
2017
+ return (kind, seq)
2018
+ except Exception: # pragma: no cover - defensive
2019
+ return (None, None)
2020
+
2021
+ @staticmethod
2022
+ def _record_pressed_keys(sequence) -> None:
2023
+ """Record keys a desktop Press/Type is about to hold. Never raises.
2024
+ change: harden-platynui-stuck-key-release."""
2025
+ try:
2026
+ from robotmcp.plugins.builtin.platynui_plugin import record_pressed_keys
2027
+
2028
+ record_pressed_keys(sequence)
2029
+ except Exception as exc: # pragma: no cover - defensive
2030
+ logger.debug("record_pressed_keys failed: %s", exc)
2031
+
2032
+ @staticmethod
2033
+ def _record_released_keys(sequence) -> None:
2034
+ """Remove keys released by a desktop Release/Type from the registry.
2035
+ Never raises. change: harden-platynui-stuck-key-release."""
2036
+ try:
2037
+ from robotmcp.plugins.builtin.platynui_plugin import record_released_keys
2038
+
2039
+ record_released_keys(sequence)
2040
+ except Exception as exc: # pragma: no cover - defensive
2041
+ logger.debug("record_released_keys failed: %s", exc)
2042
+
2043
+ @staticmethod
2044
+ def _is_steering_downgrade(result: dict) -> bool:
2045
+ """True when a desktop keyword's success was flipped to failure by the
2046
+ steering-confidence gate rather than a genuine execution failure
2047
+ (change: fix-platynui-windows-runtime, F16/F2).
2048
+
2049
+ A steering downgrade means the native keyword DID execute (its key is
2050
+ held as intended), so F16 must not release its modifiers. The gate only
2051
+ downgrades in the RF-success path, so this marker uniquely identifies
2052
+ the case."""
2053
+ try:
2054
+ from robotmcp.components.execution.desktop_execution_signals import (
2055
+ SC_CONTRADICTED,
2056
+ )
2057
+ except Exception: # pragma: no cover - defensive
2058
+ SC_CONTRADICTED = "contradicted"
2059
+ return bool(result.get("steering_confidence") == SC_CONTRADICTED)
2060
+
2061
+ @staticmethod
2062
+ def _release_desktop_keys() -> None:
2063
+ """Best-effort release of the EXACT tracked held-key set (incl.
2064
+ non-modifiers) via the PlatynUI runtime, clearing the registry + state
2065
+ file. Falls back to a modifier blast internally. Never raises.
2066
+ change: harden-platynui-stuck-key-release."""
2067
+ try:
2068
+ from robotmcp.plugins.builtin.platynui_plugin import (
2069
+ release_tracked_keys,
1894
2070
  )
1895
2071
 
2072
+ release_tracked_keys()
2073
+ except Exception as exc: # pragma: no cover - defensive
2074
+ logger.debug("desktop key release failed: %s", exc)
2075
+
1896
2076
  async def _execute_keyword_serialized(
1897
2077
  self,
1898
2078
  session: ExecutionSession,
@@ -1924,6 +2104,19 @@ class KeywordExecutor:
1924
2104
  )
1925
2105
 
1926
2106
  ensure_x11_session_env()
2107
+ # F3 (change: fix-platynui-windows-runtime): a caller who
2108
+ # passes an explicit timeout_ms wants desktop query/wait
2109
+ # keywords to wait that long; stash it so the query-settings
2110
+ # default (applied in _execute_keyword_with_context) honors
2111
+ # it. This is PER-CALL — reset to None when absent so a
2112
+ # one-off long timeout never becomes a session-sticky
2113
+ # default that defeats the fast-fail behaviour on later
2114
+ # steps (the short default is restored automatically).
2115
+ session._platynui_query_timeout_ms = (
2116
+ timeout_ms
2117
+ if isinstance(timeout_ms, int) and timeout_ms > 0
2118
+ else None
2119
+ )
1927
2120
  # D2: if a recent desktop launch marked the tree stale,
1928
2121
  # refresh the cached accessibility tree before the FIRST
1929
2122
  # tree-resolving keyword so a newly-launched app resolves by
@@ -2029,8 +2222,18 @@ class KeywordExecutor:
2029
2222
  # the AUT window is visible/in-scope (change:
2030
2223
  # platynui-focused-execution). FocusError fails fast under
2031
2224
  # opt-in policy; otherwise warnings ride along on the step.
2032
- _focus_outcome = self._platynui_focus_before_act(
2033
- session, keyword, arguments
2225
+ # F14 (change: fix-platynui-windows-runtime): the focus
2226
+ # manager drives raw runtime.evaluate() (find/activate/
2227
+ # highlight the AUT window) with NO timeout knob — a broad
2228
+ # or busy-tree query blocks the event loop for tens of
2229
+ # seconds, wedging metadata calls. Offload to a worker
2230
+ # thread so the loop stays free; the global _execution_lock
2231
+ # already serializes desktop dispatch so no native
2232
+ # concurrency is introduced. FocusError still propagates
2233
+ # out of to_thread to the except below.
2234
+ _focus_outcome = await asyncio.to_thread(
2235
+ self._platynui_focus_before_act,
2236
+ session, keyword, arguments,
2034
2237
  )
2035
2238
  # Resolve a desktop Process launch/recovery executable to an
2036
2239
  # absolute path against the server PATH (maintainer-report
@@ -2052,8 +2255,10 @@ class KeywordExecutor:
2052
2255
  # so a success-with-no-effect can be flagged afterward
2053
2256
  # (change: desktop-input-and-runtime-diagnostics). Native /
2054
2257
  # non-reentrant; best-effort.
2055
- _input_effect_before = self._desktop_text_count_before(
2056
- keyword, arguments
2258
+ # F14: native CharacterCount query — also offload off the
2259
+ # loop (change: fix-platynui-windows-runtime).
2260
+ _input_effect_before = await asyncio.to_thread(
2261
+ self._desktop_text_count_before, keyword, arguments
2057
2262
  )
2058
2263
  except Exception as _focus_exc:
2059
2264
  from robotmcp.components.execution.platynui_focus import FocusError
@@ -2551,7 +2756,13 @@ class KeywordExecutor:
2551
2756
  # but the target's CharacterCount did not change.
2552
2757
  _after = None
2553
2758
  if _input_effect_before is not None:
2554
- _after = self._desktop_text_count_before(keyword, arguments)
2759
+ # F14 (change: fix-platynui-windows-runtime): the
2760
+ # AFTER CharacterCount probe is the same native
2761
+ # runtime.evaluate() as the offloaded BEFORE probe —
2762
+ # offload it off the loop too for symmetry.
2763
+ _after = await asyncio.to_thread(
2764
+ self._desktop_text_count_before, keyword, arguments
2765
+ )
2555
2766
  _eh = input_effect_hint(
2556
2767
  keyword=keyword,
2557
2768
  success=True,
@@ -3089,6 +3300,90 @@ class KeywordExecutor:
3089
3300
  return mapped
3090
3301
  return None
3091
3302
 
3303
+ # Short default desktop query timeout (F3). PlatynUI.BareMetal's
3304
+ # QuerySettings default is 30s, so an honest-miss Query/wait waits the full
3305
+ # 30s and stacks across retries (up to ~180s in the Windows eval). A short
3306
+ # default fails fast; a caller can still request a longer wait via
3307
+ # timeout_ms. Overridable via ROBOTMCP_PLATYNUI_QUERY_TIMEOUT_MS.
3308
+ _DEFAULT_PLATYNUI_QUERY_TIMEOUT_MS = 1500
3309
+
3310
+ def _ensure_desktop_query_timeout(self, session) -> None:
3311
+ """Give desktop PlatynUI query/wait keywords a short default timeout so
3312
+ an honest-miss fails fast (change: fix-platynui-windows-runtime, F3).
3313
+
3314
+ Mirrors ``Set Query Settings scope=SUITE`` by writing a short-timeout
3315
+ ``QuerySettings`` into ``${PLATYNUI_QUERY_SETTINGS}`` in the live RF
3316
+ context, once per session (re-applied only if the desired value
3317
+ changed). A caller-supplied ``timeout_ms`` (stashed on the session)
3318
+ overrides the short default. Best-effort; never raises — on any failure
3319
+ the library keeps its 30s default (no worse than before)."""
3320
+ try:
3321
+ checker = getattr(session, "is_desktop_session", None)
3322
+ if not (callable(checker) and checker() is True):
3323
+ return
3324
+ except Exception:
3325
+ return
3326
+ try:
3327
+ default_ms = int(
3328
+ os.environ.get(
3329
+ "ROBOTMCP_PLATYNUI_QUERY_TIMEOUT_MS",
3330
+ str(self._DEFAULT_PLATYNUI_QUERY_TIMEOUT_MS),
3331
+ )
3332
+ )
3333
+ except (ValueError, TypeError):
3334
+ default_ms = self._DEFAULT_PLATYNUI_QUERY_TIMEOUT_MS
3335
+ desired_ms = getattr(session, "_platynui_query_timeout_ms", None) or default_ms
3336
+ if desired_ms <= 0:
3337
+ return
3338
+ if getattr(session, "_platynui_query_timeout_applied_ms", None) == desired_ms:
3339
+ return
3340
+ try:
3341
+ from dataclasses import replace as _dc_replace
3342
+
3343
+ from robot.running.context import EXECUTION_CONTEXTS as _EC
3344
+ from PlatynUI.BareMetal import ( # library-sanctioned mechanism
3345
+ PLATYNUI_QUERY_SETTINGS,
3346
+ QuerySettings,
3347
+ )
3348
+
3349
+ ctx = _EC.current
3350
+ if ctx is None:
3351
+ return
3352
+ variables = ctx.variables
3353
+ name = f"${{{PLATYNUI_QUERY_SETTINGS}}}"
3354
+ # Preserve any non-timeout query settings already in scope
3355
+ # (retry_interval / ignore_exceptions) instead of resetting them to
3356
+ # class defaults — only the timeout is ours to change.
3357
+ base = None
3358
+ try:
3359
+ if PLATYNUI_QUERY_SETTINGS in variables:
3360
+ existing = variables[name]
3361
+ if isinstance(existing, QuerySettings):
3362
+ base = existing
3363
+ except Exception:
3364
+ base = None
3365
+ timeout_s = desired_ms / 1000.0
3366
+ settings = (
3367
+ _dc_replace(base, timeout=timeout_s)
3368
+ if base is not None
3369
+ else QuerySettings(timeout=timeout_s)
3370
+ )
3371
+ # set_suite(children=True): a bare write into the SUITE store is
3372
+ # invisible because rf-mcp already holds a live TEST scope copied
3373
+ # from suite BEFORE the write, and PlatynUI resolves the variable
3374
+ # via variables.current (the test scope). set_suite(children=True)
3375
+ # both persists at SUITE (surviving per-step test-scope resets) AND
3376
+ # propagates down to the current/test scope so the library sees it
3377
+ # immediately (mirrors Set Query Settings scope=SUITE).
3378
+ variables.set_suite(name, settings, children=True)
3379
+ session._platynui_query_timeout_applied_ms = desired_ms
3380
+ logger.debug(
3381
+ "Applied desktop query timeout %sms via ${PLATYNUI_QUERY_SETTINGS}",
3382
+ desired_ms,
3383
+ )
3384
+ except Exception as exc: # pragma: no cover - env/internals dependent
3385
+ logger.debug("desktop query-settings apply skipped: %s", exc)
3386
+
3092
3387
  def _inject_timeout_into_arguments(
3093
3388
  self,
3094
3389
  keyword: str,
@@ -3675,6 +3970,10 @@ class KeywordExecutor:
3675
3970
  except Exception:
3676
3971
  pass
3677
3972
 
3973
+ # F3: give desktop query/wait keywords a short fast-fail timeout
3974
+ # now that the RF context exists (no-op for non-desktop sessions).
3975
+ self._ensure_desktop_query_timeout(session)
3976
+
3678
3977
  # Execute keyword using RF native context with session variables
3679
3978
  result = await asyncio.to_thread(
3680
3979
  self.rf_native_context.execute_keyword_with_context,