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.
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/PKG-INFO +4 -2
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/README.md +1 -1
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/pyproject.toml +10 -1
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/desktop_display_safety.py +59 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/desktop_guidance.py +1 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/keyword_executor.py +309 -10
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/builtin/platynui_plugin.py +378 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.gitignore +0 -0
- {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
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/@standard-schema/spec/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/@standard-schema/spec/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/cross-spawn/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/cross-spawn/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/detect-libc/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/detect-libc/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/effect/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/effect/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/fast-check/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/fast-check/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/find-my-way-ts/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/find-my-way-ts/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/ini/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/ini/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/isexe/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/isexe/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/kubernetes-types/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/msgpackr/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/msgpackr/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/msgpackr-extract/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/msgpackr-extract/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/multipasta/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/multipasta/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/node-gyp-build-optional-packages/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/node-gyp-build-optional-packages/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/pure-rand/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/pure-rand/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/toml/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/toml/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/uuid/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/which/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/which/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/yaml/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/yaml/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/zod/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/zod/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/adapters/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/adapters/adapter_factory.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/adapters/browser_adapter.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/adapters/page_state_adapter.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/adapters/playwright_adapter.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/adapters/recovery_adapter.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/adapters/selenium_adapter.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/attach/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/attach/mcp_attach.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/compat/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/compat/fastmcp_compat.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/browser/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/browser/browser_library_manager.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/desktop_execution_signals.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/desktop_launch_env.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/execution_coordinator.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/external_rf_client.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/locator_arg_introspection.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/locator_converter.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/mobile_capability_service.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/page_source_service.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/platynui_focus.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/rf_native_context_manager.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/session_manager.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/suite_execution_service.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/ui_tree_service.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/keyword_matcher.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/library_recommender.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/nlp_processor.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/state_manager.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/test_builder.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/variables/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/variables/variable_resolver.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/config/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/config/library_registry.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/container.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/core/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/core/dynamic_keyword_orchestrator.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/core/event_bus.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/core/keyword_discovery.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/core/library_manager.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/core/session_manager.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/artifact_output/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/artifact_output/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/artifact_output/entities.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/artifact_output/events.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/artifact_output/services.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/artifact_output/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/batch_execution/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/batch_execution/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/batch_execution/entities.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/batch_execution/events.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/batch_execution/services.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/batch_execution/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/instruction/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/instruction/adapters/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/instruction/adapters/fastmcp_adapter.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/instruction/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/instruction/entities.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/instruction/events.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/instruction/repository.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/instruction/security.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/instruction/services.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/instruction/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/intent/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/intent/adapters/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/intent/adapters/locator_normalizer_adapter.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/intent/adapters/mcp_tool.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/intent/adapters/session_lookup_adapter.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/intent/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/intent/entities.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/intent/events.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/intent/services.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/intent/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/keyword_resolution/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/keyword_resolution/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/keyword_resolution/events.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/keyword_resolution/services.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/keyword_resolution/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/memory/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/memory/adapters/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/memory/adapters/fastmcp_adapter.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/memory/adapters/sqlite_vec_adapter.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/memory/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/memory/entities.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/memory/events.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/memory/repository.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/memory/services.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/memory/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/recovery/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/recovery/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/recovery/entities.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/recovery/events.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/recovery/services.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/recovery/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/response_optimization/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/response_optimization/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/response_optimization/events.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/response_optimization/repository.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/response_optimization/services.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/response_optimization/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/shared/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/shared/kernel.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/delta_aggregates.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/delta_entities.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/delta_events.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/delta_service.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/delta_value_objects.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/diff_service.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/entities.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/events.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/list_folding.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/models.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/repository.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/services.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/timeout/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/timeout/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/timeout/entities.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/timeout/events.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/timeout/keyword_classifier.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/timeout/services.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/timeout/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/token_accounting/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/token_accounting/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/token_accounting/entities.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/token_accounting/events.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/token_accounting/repository.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/token_accounting/services.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/token_accounting/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/tool_profile/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/tool_profile/adapters/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/tool_profile/adapters/fastmcp_adapter.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/tool_profile/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/tool_profile/entities.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/tool_profile/events.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/tool_profile/repository.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/tool_profile/services.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/tool_profile/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/entry.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/api.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/apps.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/asgi.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/bridge.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/config.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/controller.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/devserver.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/django_app.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/static/frontend/app.js +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/static/frontend/base.css +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/templates/frontend/index.html +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/templates/frontend/layout.html +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/urls.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/views.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/models/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/models/browser_models.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/models/config_models.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/models/execution_models.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/models/library_models.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/models/session_models.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/onboarding/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/onboarding/adapters.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/onboarding/cli.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/onboarding/codecs.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/onboarding/diagnostics.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/onboarding/installer.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/onboarding/manifest.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/optimization/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/optimization/collector.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/optimization/compression_learner.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/optimization/folding_learner.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/optimization/instruction_hooks.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/optimization/instruction_learner.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/optimization/page_analyzer.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/optimization/pattern_store.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/optimization/ref_learner.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/optimization/timeout_learner.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/base.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/builtin/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/builtin/browser_plugin.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/builtin/definitions.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/builtin/requests_plugin.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/builtin/selenium_plugin.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/contracts.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/discovery.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/manager.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/server.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/argument_processor.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/enhanced_response_serializer.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/enhanced_serialization_integration.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/hints.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/libdoc_argument_parser.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/library_checker.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/library_detection.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/library_detector.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/library_loading_validator.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/logging_setup.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/requests_guidance.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/response_serializer.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/rf_libdoc_integration.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/rf_native_type_converter.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/rf_variables_compatibility.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/sampling.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/server_integration.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/session_resolution.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/token_efficient_output.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/validation.py +0 -0
- {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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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 "
|
{rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/desktop_guidance.py
RENAMED
|
@@ -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
|
|
{rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/keyword_executor.py
RENAMED
|
@@ -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
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
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
|
-
|
|
2033
|
-
|
|
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
|
-
|
|
2056
|
-
|
|
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
|
-
|
|
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,
|