rf-mcp 0.34.0.dev3__tar.gz → 0.34.0.dev4__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.dev4}/PKG-INFO +1 -1
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/pyproject.toml +1 -1
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/components/execution/desktop_display_safety.py +59 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/components/execution/keyword_executor.py +241 -10
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/plugins/builtin/platynui_plugin.py +104 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.gitignore +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/@msgpackr-extract/msgpackr-extract-linux-x64/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/@standard-schema/spec/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/@standard-schema/spec/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/cross-spawn/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/cross-spawn/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/detect-libc/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/detect-libc/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/effect/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/effect/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/fast-check/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/fast-check/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/find-my-way-ts/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/find-my-way-ts/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/ini/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/ini/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/isexe/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/isexe/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/kubernetes-types/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/msgpackr/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/msgpackr/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/msgpackr-extract/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/msgpackr-extract/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/multipasta/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/multipasta/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/node-gyp-build-optional-packages/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/node-gyp-build-optional-packages/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/pure-rand/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/pure-rand/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/toml/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/toml/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/uuid/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/which/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/which/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/yaml/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/yaml/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/zod/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/zod/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/LICENSE +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/README.md +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/adapters/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/adapters/adapter_factory.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/adapters/browser_adapter.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/adapters/page_state_adapter.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/adapters/playwright_adapter.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/adapters/recovery_adapter.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/adapters/selenium_adapter.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/attach/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/attach/mcp_attach.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/compat/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/compat/fastmcp_compat.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/components/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/components/browser/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/components/browser/browser_library_manager.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/components/execution/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/components/execution/desktop_execution_signals.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/components/execution/desktop_guidance.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/components/execution/desktop_launch_env.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/components/execution/execution_coordinator.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/components/execution/external_rf_client.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/components/execution/locator_arg_introspection.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/components/execution/locator_converter.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/components/execution/mobile_capability_service.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/components/execution/page_source_service.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/components/execution/platynui_focus.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/components/execution/rf_native_context_manager.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/components/execution/session_manager.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/components/execution/suite_execution_service.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/components/execution/ui_tree_service.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/components/keyword_matcher.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/components/library_recommender.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/components/nlp_processor.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/components/state_manager.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/components/test_builder.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/components/variables/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/components/variables/variable_resolver.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/config/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/config/library_registry.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/container.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/core/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/core/dynamic_keyword_orchestrator.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/core/event_bus.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/core/keyword_discovery.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/core/library_manager.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/core/session_manager.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/artifact_output/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/artifact_output/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/artifact_output/entities.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/artifact_output/events.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/artifact_output/services.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/artifact_output/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/batch_execution/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/batch_execution/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/batch_execution/entities.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/batch_execution/events.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/batch_execution/services.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/batch_execution/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/instruction/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/instruction/adapters/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/instruction/adapters/fastmcp_adapter.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/instruction/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/instruction/entities.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/instruction/events.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/instruction/repository.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/instruction/security.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/instruction/services.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/instruction/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/intent/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/intent/adapters/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/intent/adapters/locator_normalizer_adapter.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/intent/adapters/mcp_tool.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/intent/adapters/session_lookup_adapter.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/intent/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/intent/entities.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/intent/events.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/intent/services.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/intent/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/keyword_resolution/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/keyword_resolution/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/keyword_resolution/events.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/keyword_resolution/services.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/keyword_resolution/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/memory/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/memory/adapters/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/memory/adapters/fastmcp_adapter.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/memory/adapters/sqlite_vec_adapter.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/memory/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/memory/entities.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/memory/events.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/memory/repository.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/memory/services.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/memory/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/recovery/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/recovery/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/recovery/entities.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/recovery/events.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/recovery/services.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/recovery/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/response_optimization/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/response_optimization/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/response_optimization/events.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/response_optimization/repository.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/response_optimization/services.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/response_optimization/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/shared/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/shared/kernel.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/snapshot/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/snapshot/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/snapshot/delta_aggregates.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/snapshot/delta_entities.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/snapshot/delta_events.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/snapshot/delta_service.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/snapshot/delta_value_objects.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/snapshot/diff_service.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/snapshot/entities.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/snapshot/events.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/snapshot/list_folding.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/snapshot/models.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/snapshot/repository.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/snapshot/services.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/snapshot/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/timeout/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/timeout/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/timeout/entities.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/timeout/events.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/timeout/keyword_classifier.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/timeout/services.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/timeout/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/token_accounting/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/token_accounting/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/token_accounting/entities.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/token_accounting/events.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/token_accounting/repository.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/token_accounting/services.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/token_accounting/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/tool_profile/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/tool_profile/adapters/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/tool_profile/adapters/fastmcp_adapter.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/tool_profile/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/tool_profile/entities.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/tool_profile/events.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/tool_profile/repository.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/tool_profile/services.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/domains/tool_profile/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/entry.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/frontend/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/frontend/api.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/frontend/apps.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/frontend/asgi.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/frontend/bridge.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/frontend/config.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/frontend/controller.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/frontend/devserver.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/frontend/django_app.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/frontend/static/frontend/app.js +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/frontend/static/frontend/base.css +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/frontend/templates/frontend/index.html +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/frontend/templates/frontend/layout.html +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/frontend/urls.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/frontend/views.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/models/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/models/browser_models.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/models/config_models.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/models/execution_models.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/models/library_models.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/models/session_models.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/onboarding/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/onboarding/adapters.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/onboarding/cli.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/onboarding/codecs.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/onboarding/diagnostics.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/onboarding/installer.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/onboarding/manifest.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/optimization/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/optimization/collector.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/optimization/compression_learner.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/optimization/folding_learner.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/optimization/instruction_hooks.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/optimization/instruction_learner.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/optimization/page_analyzer.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/optimization/pattern_store.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/optimization/ref_learner.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/optimization/timeout_learner.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/plugins/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/plugins/base.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/plugins/builtin/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/plugins/builtin/browser_plugin.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/plugins/builtin/definitions.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/plugins/builtin/requests_plugin.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/plugins/builtin/selenium_plugin.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/plugins/contracts.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/plugins/discovery.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/plugins/manager.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/server.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/utils/__init__.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/utils/argument_processor.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/utils/enhanced_response_serializer.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/utils/enhanced_serialization_integration.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/utils/hints.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/utils/libdoc_argument_parser.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/utils/library_checker.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/utils/library_detection.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/utils/library_detector.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/utils/library_loading_validator.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/utils/logging_setup.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/utils/requests_guidance.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/utils/response_serializer.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/utils/rf_libdoc_integration.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/utils/rf_native_type_converter.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/utils/rf_variables_compatibility.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/utils/sampling.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/utils/server_integration.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/utils/session_resolution.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/utils/token_efficient_output.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/src/robotmcp/utils/validation.py +0 -0
- {rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/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.dev4
|
|
4
4
|
Summary: Robot Framework MCP Server - Natural Language Test Automation Bridge
|
|
5
5
|
Project-URL: Homepage, https://github.com/manykarim/rf-mcp
|
|
6
6
|
Project-URL: Repository, https://github.com/manykarim/rf-mcp
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "rf-mcp"
|
|
7
|
-
version = "0.34.0.
|
|
7
|
+
version = "0.34.0.dev4"
|
|
8
8
|
description = "Robot Framework MCP Server - Natural Language Test Automation Bridge"
|
|
9
9
|
authors = [{ name = "Many Kasiriha" }]
|
|
10
10
|
license = { text = "Apache-2.0" }
|
|
@@ -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.dev4}/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,97 @@ 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 (change: fix-platynui-windows-runtime): a desktop keyboard
|
|
1918
|
+
# keyword that raises or returns failure may have been killed
|
|
1919
|
+
# mid-chord, leaving a modifier physically held at the OS level
|
|
1920
|
+
# (the operator's keyboard wedged until reboot). Release all
|
|
1921
|
+
# modifiers on failure/exception. A SUCCESSFUL Keyboard Press
|
|
1922
|
+
# legitimately holds a key for a later Keyboard Release, so we do
|
|
1923
|
+
# NOT release on success. release_all_modifiers() is a no-op when
|
|
1924
|
+
# nothing is held.
|
|
1925
|
+
_kbd = self._is_desktop_keyboard_keyword(session, keyword)
|
|
1926
|
+
try:
|
|
1927
|
+
result = await self._execute_keyword_serialized(
|
|
1928
|
+
session, keyword, arguments, browser_library_manager,
|
|
1929
|
+
detail_level, library_prefix, assign_to, use_context,
|
|
1930
|
+
timeout_ms,
|
|
1931
|
+
record=record,
|
|
1932
|
+
pre_validate_timeout_ms=pre_validate_timeout_ms,
|
|
1933
|
+
)
|
|
1934
|
+
except BaseException:
|
|
1935
|
+
# An exception (incl. CancelledError from a killed run) is the
|
|
1936
|
+
# canonical "killed mid-chord" case. Release SYNCHRONOUSLY here
|
|
1937
|
+
# so it is guaranteed to run even while the task is being
|
|
1938
|
+
# cancelled (an awaited offload could be interrupted before it
|
|
1939
|
+
# dispatches). keyboard_release is a fast input dispatch, not a
|
|
1940
|
+
# tree query, so the brief on-loop cost is acceptable.
|
|
1941
|
+
if _kbd:
|
|
1942
|
+
self._release_desktop_modifiers()
|
|
1943
|
+
raise
|
|
1944
|
+
if (
|
|
1945
|
+
_kbd
|
|
1946
|
+
and isinstance(result, dict)
|
|
1947
|
+
and not result.get("success", True)
|
|
1948
|
+
# F2: a steering-confidence downgrade means the native keyword
|
|
1949
|
+
# actually SUCCEEDED (the key is held as intended) — releasing
|
|
1950
|
+
# would corrupt a deliberate Press/Release chord. Only release
|
|
1951
|
+
# on a genuine execution failure.
|
|
1952
|
+
and not self._is_steering_downgrade(result)
|
|
1953
|
+
):
|
|
1954
|
+
# F4: offload the release off the event loop on the normal
|
|
1955
|
+
# failure-return path (no cancellation concern here).
|
|
1956
|
+
await asyncio.to_thread(self._release_desktop_modifiers)
|
|
1957
|
+
# F16 steering (wording only): point the agent at the atomic
|
|
1958
|
+
# Keyboard Type, which cannot leave a key held.
|
|
1959
|
+
try:
|
|
1960
|
+
hints = result.setdefault("hints", [])
|
|
1961
|
+
if isinstance(hints, list):
|
|
1962
|
+
hints.append({
|
|
1963
|
+
"type": "platynui_keyboard_release_safety",
|
|
1964
|
+
"message": (
|
|
1965
|
+
"Released all keyboard modifiers after this "
|
|
1966
|
+
"keyboard keyword failed. Prefer the atomic "
|
|
1967
|
+
"'Keyboard Type <Ctrl+A>' (self-contained "
|
|
1968
|
+
"press+release) over a bare 'Keyboard Press', "
|
|
1969
|
+
"which sends key-DOWN only and must be paired "
|
|
1970
|
+
"with 'Keyboard Release'."
|
|
1971
|
+
),
|
|
1972
|
+
})
|
|
1973
|
+
except Exception: # pragma: no cover - defensive
|
|
1974
|
+
pass
|
|
1975
|
+
return result
|
|
1976
|
+
|
|
1977
|
+
@staticmethod
|
|
1978
|
+
def _is_steering_downgrade(result: dict) -> bool:
|
|
1979
|
+
"""True when a desktop keyword's success was flipped to failure by the
|
|
1980
|
+
steering-confidence gate rather than a genuine execution failure
|
|
1981
|
+
(change: fix-platynui-windows-runtime, F16/F2).
|
|
1982
|
+
|
|
1983
|
+
A steering downgrade means the native keyword DID execute (its key is
|
|
1984
|
+
held as intended), so F16 must not release its modifiers. The gate only
|
|
1985
|
+
downgrades in the RF-success path, so this marker uniquely identifies
|
|
1986
|
+
the case."""
|
|
1987
|
+
try:
|
|
1988
|
+
from robotmcp.components.execution.desktop_execution_signals import (
|
|
1989
|
+
SC_CONTRADICTED,
|
|
1990
|
+
)
|
|
1991
|
+
except Exception: # pragma: no cover - defensive
|
|
1992
|
+
SC_CONTRADICTED = "contradicted"
|
|
1993
|
+
return bool(result.get("steering_confidence") == SC_CONTRADICTED)
|
|
1994
|
+
|
|
1995
|
+
@staticmethod
|
|
1996
|
+
def _release_desktop_modifiers() -> None:
|
|
1997
|
+
"""Best-effort release of all held keyboard modifiers via the PlatynUI
|
|
1998
|
+
runtime (change: fix-platynui-windows-runtime, F16). Never raises."""
|
|
1999
|
+
try:
|
|
2000
|
+
from robotmcp.plugins.builtin.platynui_plugin import (
|
|
2001
|
+
release_all_modifiers,
|
|
1894
2002
|
)
|
|
1895
2003
|
|
|
2004
|
+
release_all_modifiers()
|
|
2005
|
+
except Exception as exc: # pragma: no cover - defensive
|
|
2006
|
+
logger.debug("desktop modifier release failed: %s", exc)
|
|
2007
|
+
|
|
1896
2008
|
async def _execute_keyword_serialized(
|
|
1897
2009
|
self,
|
|
1898
2010
|
session: ExecutionSession,
|
|
@@ -1924,6 +2036,19 @@ class KeywordExecutor:
|
|
|
1924
2036
|
)
|
|
1925
2037
|
|
|
1926
2038
|
ensure_x11_session_env()
|
|
2039
|
+
# F3 (change: fix-platynui-windows-runtime): a caller who
|
|
2040
|
+
# passes an explicit timeout_ms wants desktop query/wait
|
|
2041
|
+
# keywords to wait that long; stash it so the query-settings
|
|
2042
|
+
# default (applied in _execute_keyword_with_context) honors
|
|
2043
|
+
# it. This is PER-CALL — reset to None when absent so a
|
|
2044
|
+
# one-off long timeout never becomes a session-sticky
|
|
2045
|
+
# default that defeats the fast-fail behaviour on later
|
|
2046
|
+
# steps (the short default is restored automatically).
|
|
2047
|
+
session._platynui_query_timeout_ms = (
|
|
2048
|
+
timeout_ms
|
|
2049
|
+
if isinstance(timeout_ms, int) and timeout_ms > 0
|
|
2050
|
+
else None
|
|
2051
|
+
)
|
|
1927
2052
|
# D2: if a recent desktop launch marked the tree stale,
|
|
1928
2053
|
# refresh the cached accessibility tree before the FIRST
|
|
1929
2054
|
# tree-resolving keyword so a newly-launched app resolves by
|
|
@@ -2029,8 +2154,18 @@ class KeywordExecutor:
|
|
|
2029
2154
|
# the AUT window is visible/in-scope (change:
|
|
2030
2155
|
# platynui-focused-execution). FocusError fails fast under
|
|
2031
2156
|
# opt-in policy; otherwise warnings ride along on the step.
|
|
2032
|
-
|
|
2033
|
-
|
|
2157
|
+
# F14 (change: fix-platynui-windows-runtime): the focus
|
|
2158
|
+
# manager drives raw runtime.evaluate() (find/activate/
|
|
2159
|
+
# highlight the AUT window) with NO timeout knob — a broad
|
|
2160
|
+
# or busy-tree query blocks the event loop for tens of
|
|
2161
|
+
# seconds, wedging metadata calls. Offload to a worker
|
|
2162
|
+
# thread so the loop stays free; the global _execution_lock
|
|
2163
|
+
# already serializes desktop dispatch so no native
|
|
2164
|
+
# concurrency is introduced. FocusError still propagates
|
|
2165
|
+
# out of to_thread to the except below.
|
|
2166
|
+
_focus_outcome = await asyncio.to_thread(
|
|
2167
|
+
self._platynui_focus_before_act,
|
|
2168
|
+
session, keyword, arguments,
|
|
2034
2169
|
)
|
|
2035
2170
|
# Resolve a desktop Process launch/recovery executable to an
|
|
2036
2171
|
# absolute path against the server PATH (maintainer-report
|
|
@@ -2052,8 +2187,10 @@ class KeywordExecutor:
|
|
|
2052
2187
|
# so a success-with-no-effect can be flagged afterward
|
|
2053
2188
|
# (change: desktop-input-and-runtime-diagnostics). Native /
|
|
2054
2189
|
# non-reentrant; best-effort.
|
|
2055
|
-
|
|
2056
|
-
|
|
2190
|
+
# F14: native CharacterCount query — also offload off the
|
|
2191
|
+
# loop (change: fix-platynui-windows-runtime).
|
|
2192
|
+
_input_effect_before = await asyncio.to_thread(
|
|
2193
|
+
self._desktop_text_count_before, keyword, arguments
|
|
2057
2194
|
)
|
|
2058
2195
|
except Exception as _focus_exc:
|
|
2059
2196
|
from robotmcp.components.execution.platynui_focus import FocusError
|
|
@@ -2551,7 +2688,13 @@ class KeywordExecutor:
|
|
|
2551
2688
|
# but the target's CharacterCount did not change.
|
|
2552
2689
|
_after = None
|
|
2553
2690
|
if _input_effect_before is not None:
|
|
2554
|
-
|
|
2691
|
+
# F14 (change: fix-platynui-windows-runtime): the
|
|
2692
|
+
# AFTER CharacterCount probe is the same native
|
|
2693
|
+
# runtime.evaluate() as the offloaded BEFORE probe —
|
|
2694
|
+
# offload it off the loop too for symmetry.
|
|
2695
|
+
_after = await asyncio.to_thread(
|
|
2696
|
+
self._desktop_text_count_before, keyword, arguments
|
|
2697
|
+
)
|
|
2555
2698
|
_eh = input_effect_hint(
|
|
2556
2699
|
keyword=keyword,
|
|
2557
2700
|
success=True,
|
|
@@ -3089,6 +3232,90 @@ class KeywordExecutor:
|
|
|
3089
3232
|
return mapped
|
|
3090
3233
|
return None
|
|
3091
3234
|
|
|
3235
|
+
# Short default desktop query timeout (F3). PlatynUI.BareMetal's
|
|
3236
|
+
# QuerySettings default is 30s, so an honest-miss Query/wait waits the full
|
|
3237
|
+
# 30s and stacks across retries (up to ~180s in the Windows eval). A short
|
|
3238
|
+
# default fails fast; a caller can still request a longer wait via
|
|
3239
|
+
# timeout_ms. Overridable via ROBOTMCP_PLATYNUI_QUERY_TIMEOUT_MS.
|
|
3240
|
+
_DEFAULT_PLATYNUI_QUERY_TIMEOUT_MS = 1500
|
|
3241
|
+
|
|
3242
|
+
def _ensure_desktop_query_timeout(self, session) -> None:
|
|
3243
|
+
"""Give desktop PlatynUI query/wait keywords a short default timeout so
|
|
3244
|
+
an honest-miss fails fast (change: fix-platynui-windows-runtime, F3).
|
|
3245
|
+
|
|
3246
|
+
Mirrors ``Set Query Settings scope=SUITE`` by writing a short-timeout
|
|
3247
|
+
``QuerySettings`` into ``${PLATYNUI_QUERY_SETTINGS}`` in the live RF
|
|
3248
|
+
context, once per session (re-applied only if the desired value
|
|
3249
|
+
changed). A caller-supplied ``timeout_ms`` (stashed on the session)
|
|
3250
|
+
overrides the short default. Best-effort; never raises — on any failure
|
|
3251
|
+
the library keeps its 30s default (no worse than before)."""
|
|
3252
|
+
try:
|
|
3253
|
+
checker = getattr(session, "is_desktop_session", None)
|
|
3254
|
+
if not (callable(checker) and checker() is True):
|
|
3255
|
+
return
|
|
3256
|
+
except Exception:
|
|
3257
|
+
return
|
|
3258
|
+
try:
|
|
3259
|
+
default_ms = int(
|
|
3260
|
+
os.environ.get(
|
|
3261
|
+
"ROBOTMCP_PLATYNUI_QUERY_TIMEOUT_MS",
|
|
3262
|
+
str(self._DEFAULT_PLATYNUI_QUERY_TIMEOUT_MS),
|
|
3263
|
+
)
|
|
3264
|
+
)
|
|
3265
|
+
except (ValueError, TypeError):
|
|
3266
|
+
default_ms = self._DEFAULT_PLATYNUI_QUERY_TIMEOUT_MS
|
|
3267
|
+
desired_ms = getattr(session, "_platynui_query_timeout_ms", None) or default_ms
|
|
3268
|
+
if desired_ms <= 0:
|
|
3269
|
+
return
|
|
3270
|
+
if getattr(session, "_platynui_query_timeout_applied_ms", None) == desired_ms:
|
|
3271
|
+
return
|
|
3272
|
+
try:
|
|
3273
|
+
from dataclasses import replace as _dc_replace
|
|
3274
|
+
|
|
3275
|
+
from robot.running.context import EXECUTION_CONTEXTS as _EC
|
|
3276
|
+
from PlatynUI.BareMetal import ( # library-sanctioned mechanism
|
|
3277
|
+
PLATYNUI_QUERY_SETTINGS,
|
|
3278
|
+
QuerySettings,
|
|
3279
|
+
)
|
|
3280
|
+
|
|
3281
|
+
ctx = _EC.current
|
|
3282
|
+
if ctx is None:
|
|
3283
|
+
return
|
|
3284
|
+
variables = ctx.variables
|
|
3285
|
+
name = f"${{{PLATYNUI_QUERY_SETTINGS}}}"
|
|
3286
|
+
# Preserve any non-timeout query settings already in scope
|
|
3287
|
+
# (retry_interval / ignore_exceptions) instead of resetting them to
|
|
3288
|
+
# class defaults — only the timeout is ours to change.
|
|
3289
|
+
base = None
|
|
3290
|
+
try:
|
|
3291
|
+
if PLATYNUI_QUERY_SETTINGS in variables:
|
|
3292
|
+
existing = variables[name]
|
|
3293
|
+
if isinstance(existing, QuerySettings):
|
|
3294
|
+
base = existing
|
|
3295
|
+
except Exception:
|
|
3296
|
+
base = None
|
|
3297
|
+
timeout_s = desired_ms / 1000.0
|
|
3298
|
+
settings = (
|
|
3299
|
+
_dc_replace(base, timeout=timeout_s)
|
|
3300
|
+
if base is not None
|
|
3301
|
+
else QuerySettings(timeout=timeout_s)
|
|
3302
|
+
)
|
|
3303
|
+
# set_suite(children=True): a bare write into the SUITE store is
|
|
3304
|
+
# invisible because rf-mcp already holds a live TEST scope copied
|
|
3305
|
+
# from suite BEFORE the write, and PlatynUI resolves the variable
|
|
3306
|
+
# via variables.current (the test scope). set_suite(children=True)
|
|
3307
|
+
# both persists at SUITE (surviving per-step test-scope resets) AND
|
|
3308
|
+
# propagates down to the current/test scope so the library sees it
|
|
3309
|
+
# immediately (mirrors Set Query Settings scope=SUITE).
|
|
3310
|
+
variables.set_suite(name, settings, children=True)
|
|
3311
|
+
session._platynui_query_timeout_applied_ms = desired_ms
|
|
3312
|
+
logger.debug(
|
|
3313
|
+
"Applied desktop query timeout %sms via ${PLATYNUI_QUERY_SETTINGS}",
|
|
3314
|
+
desired_ms,
|
|
3315
|
+
)
|
|
3316
|
+
except Exception as exc: # pragma: no cover - env/internals dependent
|
|
3317
|
+
logger.debug("desktop query-settings apply skipped: %s", exc)
|
|
3318
|
+
|
|
3092
3319
|
def _inject_timeout_into_arguments(
|
|
3093
3320
|
self,
|
|
3094
3321
|
keyword: str,
|
|
@@ -3675,6 +3902,10 @@ class KeywordExecutor:
|
|
|
3675
3902
|
except Exception:
|
|
3676
3903
|
pass
|
|
3677
3904
|
|
|
3905
|
+
# F3: give desktop query/wait keywords a short fast-fail timeout
|
|
3906
|
+
# now that the RF context exists (no-op for non-desktop sessions).
|
|
3907
|
+
self._ensure_desktop_query_timeout(session)
|
|
3908
|
+
|
|
3678
3909
|
# Execute keyword using RF native context with session variables
|
|
3679
3910
|
result = await asyncio.to_thread(
|
|
3680
3911
|
self.rf_native_context.execute_keyword_with_context,
|
|
@@ -162,6 +162,17 @@ _RUNTIME_STATE = "new"
|
|
|
162
162
|
# Last bind/connect failure text (for classification). None once open.
|
|
163
163
|
_RUNTIME_LAST_ERROR: Optional[str] = None
|
|
164
164
|
|
|
165
|
+
# Stuck-key safety net (change: fix-platynui-windows-runtime, F16). ``Keyboard
|
|
166
|
+
# Press`` sends key-DOWN only, and runs that time out / are killed mid-chord can
|
|
167
|
+
# leave a modifier physically held at the OS level — the operator's keyboard is
|
|
168
|
+
# then wedged until reboot. Releasing keys that are not held is a proven no-op,
|
|
169
|
+
# so a broad release sequence is always safe. Left+right variants + AltGr + Win
|
|
170
|
+
# cover every modifier the native keyboard recognizes.
|
|
171
|
+
_RELEASE_ALL_SEQUENCE = "<LCtrl+RCtrl+LAlt+RAlt+AltGr+LShift+RShift+LWin+RWin>"
|
|
172
|
+
# atexit/SIGTERM release handlers are registered exactly once, on first runtime
|
|
173
|
+
# bind (guarded under _RUNTIME_LOCK), so a killed process still releases keys.
|
|
174
|
+
_RELEASE_HANDLERS_REGISTERED = False
|
|
175
|
+
|
|
165
176
|
|
|
166
177
|
def runtime_unavailable_reason() -> Optional[str]:
|
|
167
178
|
"""Classify why the PlatynUI runtime is unavailable, or None when it is
|
|
@@ -225,6 +236,9 @@ def get_runtime():
|
|
|
225
236
|
_RUNTIME = pn.Runtime()
|
|
226
237
|
_RUNTIME_STATE = "open"
|
|
227
238
|
_RUNTIME_LAST_ERROR = None
|
|
239
|
+
# First successful bind: arm the stuck-key safety net so a killed
|
|
240
|
+
# process still releases held modifiers (F16). Under _RUNTIME_LOCK.
|
|
241
|
+
_register_release_handlers_once()
|
|
228
242
|
except Exception as exc: # pragma: no cover - env dependent
|
|
229
243
|
logger.debug("PlatynUI runtime broker bind failed: %s", exc)
|
|
230
244
|
_RUNTIME = None
|
|
@@ -235,6 +249,72 @@ def get_runtime():
|
|
|
235
249
|
return _RUNTIME
|
|
236
250
|
|
|
237
251
|
|
|
252
|
+
def release_all_modifiers() -> bool:
|
|
253
|
+
"""Best-effort release of every keyboard modifier (change:
|
|
254
|
+
fix-platynui-windows-runtime, F16).
|
|
255
|
+
|
|
256
|
+
Releases Ctrl/Alt/AltGr/Shift/Win (left+right) against the ALREADY-OPEN
|
|
257
|
+
native runtime. Releasing a key that is not held is a no-op, so this is
|
|
258
|
+
always safe to call from teardown, keyword-failure ``finally`` paths, and
|
|
259
|
+
process-exit handlers. Never raises; never starts the runtime just to
|
|
260
|
+
release (teardown must not spin up the native broker). Returns True when a
|
|
261
|
+
release was dispatched.
|
|
262
|
+
"""
|
|
263
|
+
if _RUNTIME is None or _RUNTIME_STATE != "open":
|
|
264
|
+
return False
|
|
265
|
+
try:
|
|
266
|
+
_RUNTIME.keyboard_release(_RELEASE_ALL_SEQUENCE)
|
|
267
|
+
return True
|
|
268
|
+
except Exception as exc: # pragma: no cover - env dependent
|
|
269
|
+
logger.debug("release_all_modifiers failed: %s", exc)
|
|
270
|
+
return False
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _register_release_handlers_once() -> None:
|
|
274
|
+
"""Arm atexit + SIGTERM handlers that release held modifiers on process
|
|
275
|
+
exit (F16). Called once from ``get_runtime`` under ``_RUNTIME_LOCK`` after
|
|
276
|
+
the first successful bind — the safety net for a run killed mid-chord (e.g.
|
|
277
|
+
a ``claude -p`` process terminated before ``on_session_end`` runs). Never
|
|
278
|
+
raises."""
|
|
279
|
+
global _RELEASE_HANDLERS_REGISTERED
|
|
280
|
+
if _RELEASE_HANDLERS_REGISTERED:
|
|
281
|
+
return
|
|
282
|
+
_RELEASE_HANDLERS_REGISTERED = True
|
|
283
|
+
try:
|
|
284
|
+
import atexit
|
|
285
|
+
|
|
286
|
+
atexit.register(release_all_modifiers)
|
|
287
|
+
except Exception as exc: # pragma: no cover - defensive
|
|
288
|
+
logger.debug("atexit release registration failed: %s", exc)
|
|
289
|
+
try:
|
|
290
|
+
import signal
|
|
291
|
+
|
|
292
|
+
# Chain to any prior SIGTERM handler so we don't swallow shutdown.
|
|
293
|
+
_prior = signal.getsignal(signal.SIGTERM)
|
|
294
|
+
|
|
295
|
+
def _release_on_sigterm(signum, frame): # pragma: no cover - signal path
|
|
296
|
+
release_all_modifiers()
|
|
297
|
+
if callable(_prior) and _prior not in (
|
|
298
|
+
signal.SIG_DFL,
|
|
299
|
+
signal.SIG_IGN,
|
|
300
|
+
):
|
|
301
|
+
_prior(signum, frame)
|
|
302
|
+
elif _prior == signal.SIG_IGN:
|
|
303
|
+
# Prior handler explicitly ignored SIGTERM — preserve that.
|
|
304
|
+
return
|
|
305
|
+
else:
|
|
306
|
+
# SIG_DFL, None, or unknown: restore the default and re-raise so
|
|
307
|
+
# the process still terminates on SIGTERM (never leave it
|
|
308
|
+
# un-killable by swallowing the signal).
|
|
309
|
+
signal.signal(signal.SIGTERM, signal.SIG_DFL)
|
|
310
|
+
os.kill(os.getpid(), signum)
|
|
311
|
+
|
|
312
|
+
signal.signal(signal.SIGTERM, _release_on_sigterm)
|
|
313
|
+
except (ValueError, OSError, AttributeError) as exc:
|
|
314
|
+
# signal.signal only works on the main thread; best-effort otherwise.
|
|
315
|
+
logger.debug("SIGTERM release registration skipped: %s", exc)
|
|
316
|
+
|
|
317
|
+
|
|
238
318
|
def runtime_state() -> str:
|
|
239
319
|
return _RUNTIME_STATE
|
|
240
320
|
|
|
@@ -555,9 +635,33 @@ class PlatynUILibraryPlugin(StaticLibraryPlugin):
|
|
|
555
635
|
libraries.append(preference)
|
|
556
636
|
if any("platynui" in str(lib).lower() for lib in libraries):
|
|
557
637
|
ensure_x11_session_env()
|
|
638
|
+
# Arm the stuck-key release handlers HERE, on the main thread
|
|
639
|
+
# (session creation runs on the event loop). The first runtime
|
|
640
|
+
# bind often happens inside the F14 to_thread-offloaded focus
|
|
641
|
+
# call on a WORKER thread, where signal.signal() raises — so
|
|
642
|
+
# arming only at bind time would silently skip the SIGTERM net
|
|
643
|
+
# (and default SIGTERM also skips atexit), defeating the F16
|
|
644
|
+
# process-kill safety net. Registration is idempotent and the
|
|
645
|
+
# release itself no-ops until the runtime is open. F16.
|
|
646
|
+
_register_release_handlers_once()
|
|
647
|
+
# Defensively clear any modifier a PRIOR crashed run left held
|
|
648
|
+
# (no-op unless the runtime is already open) — F16.
|
|
649
|
+
release_all_modifiers()
|
|
558
650
|
except Exception as exc: # pragma: no cover - defensive
|
|
559
651
|
logger.debug("PlatynUI session-start hook failed: %s", exc)
|
|
560
652
|
|
|
653
|
+
def on_session_end(self, session: "ExecutionSession") -> None:
|
|
654
|
+
"""Release any held keyboard modifiers when a PlatynUI desktop session
|
|
655
|
+
closes (change: fix-platynui-windows-runtime, F16).
|
|
656
|
+
|
|
657
|
+
``session_manager.on_session_end`` invokes this for every plugin; the
|
|
658
|
+
release is a no-op when the runtime is not open or nothing is held, so
|
|
659
|
+
it is safe for non-desktop sessions too. Never raises."""
|
|
660
|
+
try:
|
|
661
|
+
release_all_modifiers()
|
|
662
|
+
except Exception as exc: # pragma: no cover - defensive
|
|
663
|
+
logger.debug("PlatynUI session-end release failed: %s", exc)
|
|
664
|
+
|
|
561
665
|
def before_keyword_execution(
|
|
562
666
|
self,
|
|
563
667
|
session: "ExecutionSession",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rf_mcp-0.34.0.dev3 → rf_mcp-0.34.0.dev4}/.kilo/node_modules/@standard-schema/spec/README.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|