rf-mcp 0.34.0.dev4__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.dev4 → rf_mcp-0.34.0.dev5}/PKG-INFO +4 -2
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/README.md +1 -1
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/pyproject.toml +10 -1
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/desktop_guidance.py +1 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/keyword_executor.py +91 -23
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/builtin/platynui_plugin.py +280 -6
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.gitignore +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/@msgpackr-extract/msgpackr-extract-linux-x64/README.md +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/@standard-schema/spec/LICENSE +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/@standard-schema/spec/README.md +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/cross-spawn/LICENSE +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/cross-spawn/README.md +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/detect-libc/LICENSE +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/detect-libc/README.md +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/effect/LICENSE +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/effect/README.md +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/fast-check/LICENSE +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/fast-check/README.md +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/find-my-way-ts/LICENSE +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/find-my-way-ts/README.md +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/ini/LICENSE +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/ini/README.md +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/isexe/LICENSE +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/isexe/README.md +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/kubernetes-types/README.md +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/msgpackr/LICENSE +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/msgpackr/README.md +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/msgpackr-extract/LICENSE +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/msgpackr-extract/README.md +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/multipasta/LICENSE +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/multipasta/README.md +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/node-gyp-build-optional-packages/LICENSE +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/node-gyp-build-optional-packages/README.md +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/pure-rand/LICENSE +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/pure-rand/README.md +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/toml/LICENSE +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/toml/README.md +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/uuid/README.md +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/which/LICENSE +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/which/README.md +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/yaml/LICENSE +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/yaml/README.md +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/zod/LICENSE +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.kilo/node_modules/zod/README.md +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/LICENSE +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/adapters/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/adapters/adapter_factory.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/adapters/browser_adapter.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/adapters/page_state_adapter.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/adapters/playwright_adapter.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/adapters/recovery_adapter.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/adapters/selenium_adapter.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/attach/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/attach/mcp_attach.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/compat/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/compat/fastmcp_compat.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/browser/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/browser/browser_library_manager.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/desktop_display_safety.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/desktop_execution_signals.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/desktop_launch_env.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/execution_coordinator.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/external_rf_client.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/locator_arg_introspection.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/locator_converter.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/mobile_capability_service.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/page_source_service.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/platynui_focus.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/rf_native_context_manager.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/session_manager.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/suite_execution_service.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/ui_tree_service.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/keyword_matcher.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/library_recommender.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/nlp_processor.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/state_manager.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/test_builder.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/variables/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/variables/variable_resolver.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/config/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/config/library_registry.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/container.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/core/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/core/dynamic_keyword_orchestrator.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/core/event_bus.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/core/keyword_discovery.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/core/library_manager.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/core/session_manager.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/artifact_output/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/artifact_output/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/artifact_output/entities.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/artifact_output/events.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/artifact_output/services.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/artifact_output/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/batch_execution/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/batch_execution/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/batch_execution/entities.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/batch_execution/events.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/batch_execution/services.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/batch_execution/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/instruction/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/instruction/adapters/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/instruction/adapters/fastmcp_adapter.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/instruction/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/instruction/entities.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/instruction/events.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/instruction/repository.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/instruction/security.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/instruction/services.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/instruction/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/intent/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/intent/adapters/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/intent/adapters/locator_normalizer_adapter.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/intent/adapters/mcp_tool.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/intent/adapters/session_lookup_adapter.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/intent/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/intent/entities.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/intent/events.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/intent/services.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/intent/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/keyword_resolution/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/keyword_resolution/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/keyword_resolution/events.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/keyword_resolution/services.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/keyword_resolution/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/memory/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/memory/adapters/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/memory/adapters/fastmcp_adapter.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/memory/adapters/sqlite_vec_adapter.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/memory/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/memory/entities.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/memory/events.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/memory/repository.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/memory/services.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/memory/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/recovery/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/recovery/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/recovery/entities.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/recovery/events.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/recovery/services.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/recovery/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/response_optimization/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/response_optimization/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/response_optimization/events.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/response_optimization/repository.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/response_optimization/services.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/response_optimization/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/shared/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/shared/kernel.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/delta_aggregates.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/delta_entities.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/delta_events.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/delta_service.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/delta_value_objects.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/diff_service.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/entities.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/events.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/list_folding.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/models.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/repository.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/services.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/snapshot/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/timeout/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/timeout/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/timeout/entities.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/timeout/events.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/timeout/keyword_classifier.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/timeout/services.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/timeout/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/token_accounting/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/token_accounting/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/token_accounting/entities.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/token_accounting/events.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/token_accounting/repository.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/token_accounting/services.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/token_accounting/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/tool_profile/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/tool_profile/adapters/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/tool_profile/adapters/fastmcp_adapter.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/tool_profile/aggregates.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/tool_profile/entities.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/tool_profile/events.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/tool_profile/repository.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/tool_profile/services.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/domains/tool_profile/value_objects.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/entry.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/api.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/apps.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/asgi.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/bridge.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/config.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/controller.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/devserver.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/django_app.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/static/frontend/app.js +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/static/frontend/base.css +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/templates/frontend/index.html +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/templates/frontend/layout.html +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/urls.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/frontend/views.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/models/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/models/browser_models.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/models/config_models.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/models/execution_models.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/models/library_models.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/models/session_models.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/onboarding/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/onboarding/adapters.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/onboarding/cli.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/onboarding/codecs.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/onboarding/diagnostics.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/onboarding/installer.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/onboarding/manifest.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/optimization/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/optimization/collector.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/optimization/compression_learner.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/optimization/folding_learner.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/optimization/instruction_hooks.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/optimization/instruction_learner.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/optimization/page_analyzer.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/optimization/pattern_store.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/optimization/ref_learner.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/optimization/timeout_learner.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/base.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/builtin/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/builtin/browser_plugin.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/builtin/definitions.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/builtin/requests_plugin.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/builtin/selenium_plugin.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/contracts.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/discovery.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/plugins/manager.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/server.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/__init__.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/argument_processor.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/enhanced_response_serializer.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/enhanced_serialization_integration.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/hints.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/libdoc_argument_parser.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/library_checker.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/library_detection.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/library_detector.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/library_loading_validator.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/logging_setup.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/requests_guidance.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/response_serializer.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/rf_libdoc_integration.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/rf_native_type_converter.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/rf_variables_compatibility.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/sampling.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/server_integration.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/session_resolution.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/token_efficient_output.py +0 -0
- {rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/utils/validation.py +0 -0
- {rf_mcp-0.34.0.dev4 → 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
|
{rf_mcp-0.34.0.dev4 → 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.dev4 → rf_mcp-0.34.0.dev5}/src/robotmcp/components/execution/keyword_executor.py
RENAMED
|
@@ -1914,15 +1914,22 @@ class KeywordExecutor:
|
|
|
1914
1914
|
# causing it to go to stderr instead of the MCP transport.
|
|
1915
1915
|
# The lock also prevents concurrent Selenium WebDriver access.
|
|
1916
1916
|
async with self._execution_lock:
|
|
1917
|
-
# F16
|
|
1918
|
-
#
|
|
1919
|
-
#
|
|
1920
|
-
#
|
|
1921
|
-
#
|
|
1922
|
-
# legitimately holds a key for a later Keyboard
|
|
1923
|
-
# NOT release on success
|
|
1924
|
-
# nothing is held.
|
|
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.
|
|
1925
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)
|
|
1926
1933
|
try:
|
|
1927
1934
|
result = await self._execute_keyword_serialized(
|
|
1928
1935
|
session, keyword, arguments, browser_library_manager,
|
|
@@ -1939,8 +1946,9 @@ class KeywordExecutor:
|
|
|
1939
1946
|
# dispatches). keyboard_release is a fast input dispatch, not a
|
|
1940
1947
|
# tree query, so the brief on-loop cost is acceptable.
|
|
1941
1948
|
if _kbd:
|
|
1942
|
-
self.
|
|
1949
|
+
self._release_desktop_keys()
|
|
1943
1950
|
raise
|
|
1951
|
+
_released_here = False
|
|
1944
1952
|
if (
|
|
1945
1953
|
_kbd
|
|
1946
1954
|
and isinstance(result, dict)
|
|
@@ -1952,28 +1960,86 @@ class KeywordExecutor:
|
|
|
1952
1960
|
and not self._is_steering_downgrade(result)
|
|
1953
1961
|
):
|
|
1954
1962
|
# F4: offload the release off the event loop on the normal
|
|
1955
|
-
# failure-return path (no cancellation concern here).
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
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.
|
|
1959
1969
|
try:
|
|
1960
1970
|
hints = result.setdefault("hints", [])
|
|
1961
1971
|
if isinstance(hints, list):
|
|
1962
1972
|
hints.append({
|
|
1963
1973
|
"type": "platynui_keyboard_release_safety",
|
|
1964
1974
|
"message": (
|
|
1965
|
-
"Released all keyboard
|
|
1966
|
-
"
|
|
1967
|
-
"
|
|
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 "
|
|
1968
1979
|
"press+release) over a bare 'Keyboard Press', "
|
|
1969
|
-
"which sends key-DOWN only and
|
|
1980
|
+
"which sends key-DOWN only and MUST be paired "
|
|
1970
1981
|
"with 'Keyboard Release'."
|
|
1971
1982
|
),
|
|
1972
1983
|
})
|
|
1973
1984
|
except Exception: # pragma: no cover - defensive
|
|
1974
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)
|
|
1975
1991
|
return result
|
|
1976
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
|
+
|
|
1977
2043
|
@staticmethod
|
|
1978
2044
|
def _is_steering_downgrade(result: dict) -> bool:
|
|
1979
2045
|
"""True when a desktop keyword's success was flipped to failure by the
|
|
@@ -1993,17 +2059,19 @@ class KeywordExecutor:
|
|
|
1993
2059
|
return bool(result.get("steering_confidence") == SC_CONTRADICTED)
|
|
1994
2060
|
|
|
1995
2061
|
@staticmethod
|
|
1996
|
-
def
|
|
1997
|
-
"""Best-effort release of
|
|
1998
|
-
|
|
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."""
|
|
1999
2067
|
try:
|
|
2000
2068
|
from robotmcp.plugins.builtin.platynui_plugin import (
|
|
2001
|
-
|
|
2069
|
+
release_tracked_keys,
|
|
2002
2070
|
)
|
|
2003
2071
|
|
|
2004
|
-
|
|
2072
|
+
release_tracked_keys()
|
|
2005
2073
|
except Exception as exc: # pragma: no cover - defensive
|
|
2006
|
-
logger.debug("desktop
|
|
2074
|
+
logger.debug("desktop key release failed: %s", exc)
|
|
2007
2075
|
|
|
2008
2076
|
async def _execute_keyword_serialized(
|
|
2009
2077
|
self,
|
|
@@ -173,6 +173,21 @@ _RELEASE_ALL_SEQUENCE = "<LCtrl+RCtrl+LAlt+RAlt+AltGr+LShift+RShift+LWin+RWin>"
|
|
|
173
173
|
# bind (guarded under _RUNTIME_LOCK), so a killed process still releases keys.
|
|
174
174
|
_RELEASE_HANDLERS_REGISTERED = False
|
|
175
175
|
|
|
176
|
+
# --- Held-key registry (change: harden-platynui-stuck-key-release) -----------
|
|
177
|
+
#
|
|
178
|
+
# The modifiers-only F16 blast (_RELEASE_ALL_SEQUENCE) cannot lift a stuck
|
|
179
|
+
# NON-modifier key (a bare ``Keyboard Press A``/``<F12>``/``<Escape>`` that was
|
|
180
|
+
# never released). The registry records the EXACT keys rf-mcp is holding so the
|
|
181
|
+
# teardown/failure/exit paths release precisely those, and it is mirrored to a
|
|
182
|
+
# per-PID state file so a HARD kill (SIGKILL/TerminateProcess — which run
|
|
183
|
+
# neither atexit nor SIGTERM) is recovered by the next desktop session start.
|
|
184
|
+
_HELD_KEYS: set[str] = set()
|
|
185
|
+
_HELD_KEYS_LOCK = threading.RLock()
|
|
186
|
+
# Subdir under the OS temp dir holding ``held_<pid>.json`` files. A live process
|
|
187
|
+
# only ever replays a file owned by a DEAD pid, so a concurrent healthy session
|
|
188
|
+
# is never robbed of a key it is deliberately holding.
|
|
189
|
+
_HELD_KEYS_DIRNAME = "robotmcp_platynui_held_keys"
|
|
190
|
+
|
|
176
191
|
|
|
177
192
|
def runtime_unavailable_reason() -> Optional[str]:
|
|
178
193
|
"""Classify why the PlatynUI runtime is unavailable, or None when it is
|
|
@@ -270,6 +285,259 @@ def release_all_modifiers() -> bool:
|
|
|
270
285
|
return False
|
|
271
286
|
|
|
272
287
|
|
|
288
|
+
# --- Held-key registry: normalize / record / release / recover --------------
|
|
289
|
+
#
|
|
290
|
+
# change: harden-platynui-stuck-key-release.
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _normalize_key_tokens(sequence: Optional[str]) -> list[str]:
|
|
294
|
+
"""Parse a PlatynUI key sequence into the individual key tokens that a
|
|
295
|
+
``Keyboard Press`` of that sequence leaves HELD (key-DOWN, no key-UP).
|
|
296
|
+
|
|
297
|
+
Best-effort and never raises. ``<Ctrl+A>`` -> ``["Ctrl", "A"]``; ``<F12>``
|
|
298
|
+
-> ``["F12"]``; a bare ``A`` -> ``["A"]``; literal text ``hi`` ->
|
|
299
|
+
``["h", "i"]``. Bracketed groups split on ``+``; unbracketed characters
|
|
300
|
+
each become their own token. Whitespace and empty tokens are dropped.
|
|
301
|
+
"""
|
|
302
|
+
tokens: list[str] = []
|
|
303
|
+
if not sequence:
|
|
304
|
+
return tokens
|
|
305
|
+
try:
|
|
306
|
+
i = 0
|
|
307
|
+
n = len(sequence)
|
|
308
|
+
while i < n:
|
|
309
|
+
ch = sequence[i]
|
|
310
|
+
if ch == "<":
|
|
311
|
+
end = sequence.find(">", i + 1)
|
|
312
|
+
if end == -1:
|
|
313
|
+
# Unterminated '<' — treat the rest as literal chars.
|
|
314
|
+
for c in sequence[i + 1:]:
|
|
315
|
+
if not c.isspace():
|
|
316
|
+
tokens.append(c)
|
|
317
|
+
break
|
|
318
|
+
inner = sequence[i + 1:end]
|
|
319
|
+
for part in inner.split("+"):
|
|
320
|
+
part = part.strip()
|
|
321
|
+
if part:
|
|
322
|
+
tokens.append(part)
|
|
323
|
+
i = end + 1
|
|
324
|
+
else:
|
|
325
|
+
if not ch.isspace():
|
|
326
|
+
tokens.append(ch)
|
|
327
|
+
i += 1
|
|
328
|
+
except Exception as exc: # pragma: no cover - defensive
|
|
329
|
+
logger.debug("_normalize_key_tokens failed for %r: %s", sequence, exc)
|
|
330
|
+
return tokens
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def _release_sequence_for(tokens) -> str:
|
|
334
|
+
"""Build a PlatynUI release sequence that lifts each token individually,
|
|
335
|
+
e.g. ``["A", "Ctrl"]`` -> ``"<A><Ctrl>"``."""
|
|
336
|
+
return "".join(f"<{t}>" for t in tokens)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def record_pressed_keys(sequence: Optional[str]) -> None:
|
|
340
|
+
"""Record the keys a ``Keyboard Press``/``Keyboard Type`` is about to hold,
|
|
341
|
+
and mirror the held set to the per-PID state file so a hard kill is
|
|
342
|
+
recoverable. Never raises. change: harden-platynui-stuck-key-release."""
|
|
343
|
+
tokens = _normalize_key_tokens(sequence)
|
|
344
|
+
if not tokens:
|
|
345
|
+
return
|
|
346
|
+
with _HELD_KEYS_LOCK:
|
|
347
|
+
_HELD_KEYS.update(tokens)
|
|
348
|
+
_write_state_locked()
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def record_released_keys(sequence: Optional[str]) -> None:
|
|
352
|
+
"""Remove keys released by ``Keyboard Release`` (or the paired UP inside
|
|
353
|
+
``Keyboard Type``) from the registry, updating the state file. Never
|
|
354
|
+
raises. change: harden-platynui-stuck-key-release."""
|
|
355
|
+
tokens = _normalize_key_tokens(sequence)
|
|
356
|
+
if not tokens:
|
|
357
|
+
return
|
|
358
|
+
with _HELD_KEYS_LOCK:
|
|
359
|
+
for t in tokens:
|
|
360
|
+
_HELD_KEYS.discard(t)
|
|
361
|
+
_write_state_locked()
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def release_tracked_keys() -> bool:
|
|
365
|
+
"""Release EXACTLY the keys the registry records as held (including
|
|
366
|
+
non-modifiers), then defensively blast all modifiers, and clear the
|
|
367
|
+
registry + state file.
|
|
368
|
+
|
|
369
|
+
Best-effort and non-raising; a no-op observationally when nothing is held
|
|
370
|
+
(releasing a not-held key has no effect). Never starts the runtime solely
|
|
371
|
+
to release — acts only when the runtime is already open (mirrors
|
|
372
|
+
``release_all_modifiers``). change: harden-platynui-stuck-key-release.
|
|
373
|
+
"""
|
|
374
|
+
if _RUNTIME is None or _RUNTIME_STATE != "open":
|
|
375
|
+
return False
|
|
376
|
+
with _HELD_KEYS_LOCK:
|
|
377
|
+
held = sorted(_HELD_KEYS)
|
|
378
|
+
dispatched = False
|
|
379
|
+
try:
|
|
380
|
+
if held:
|
|
381
|
+
_RUNTIME.keyboard_release(_release_sequence_for(held))
|
|
382
|
+
dispatched = True
|
|
383
|
+
# Fallback blast: covers any modifier that slipped registry tracking.
|
|
384
|
+
_RUNTIME.keyboard_release(_RELEASE_ALL_SEQUENCE)
|
|
385
|
+
dispatched = True
|
|
386
|
+
except Exception as exc: # pragma: no cover - env dependent
|
|
387
|
+
logger.debug("release_tracked_keys dispatch failed: %s", exc)
|
|
388
|
+
_HELD_KEYS.clear()
|
|
389
|
+
_clear_state_locked()
|
|
390
|
+
return dispatched
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
# --- Per-PID state file (hard-kill recovery) --------------------------------
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def _held_keys_dir() -> Optional[str]:
|
|
397
|
+
"""Return (creating if needed) the directory holding per-PID held-key state
|
|
398
|
+
files, or None on failure. Never raises."""
|
|
399
|
+
try:
|
|
400
|
+
import tempfile
|
|
401
|
+
|
|
402
|
+
d = os.path.join(tempfile.gettempdir(), _HELD_KEYS_DIRNAME)
|
|
403
|
+
os.makedirs(d, exist_ok=True)
|
|
404
|
+
return d
|
|
405
|
+
except Exception as exc: # pragma: no cover - defensive
|
|
406
|
+
logger.debug("_held_keys_dir failed: %s", exc)
|
|
407
|
+
return None
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def _state_file_path(pid: Optional[int] = None) -> Optional[str]:
|
|
411
|
+
d = _held_keys_dir()
|
|
412
|
+
if d is None:
|
|
413
|
+
return None
|
|
414
|
+
return os.path.join(d, f"held_{pid if pid is not None else os.getpid()}.json")
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def _write_state_locked() -> None:
|
|
418
|
+
"""Atomically write the current held set (with owning PID) to this process's
|
|
419
|
+
state file, or delete the file when the set is empty. Caller holds
|
|
420
|
+
``_HELD_KEYS_LOCK``. Never raises."""
|
|
421
|
+
path = _state_file_path()
|
|
422
|
+
if path is None:
|
|
423
|
+
return
|
|
424
|
+
try:
|
|
425
|
+
if not _HELD_KEYS:
|
|
426
|
+
_clear_state_locked()
|
|
427
|
+
return
|
|
428
|
+
import json
|
|
429
|
+
|
|
430
|
+
payload = json.dumps({"pid": os.getpid(), "keys": sorted(_HELD_KEYS)})
|
|
431
|
+
tmp = f"{path}.{os.getpid()}.tmp"
|
|
432
|
+
with open(tmp, "w", encoding="utf-8") as fh:
|
|
433
|
+
fh.write(payload)
|
|
434
|
+
os.replace(tmp, path)
|
|
435
|
+
except Exception as exc: # pragma: no cover - defensive
|
|
436
|
+
logger.debug("_write_state_locked failed: %s", exc)
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def _clear_state_locked() -> None:
|
|
440
|
+
"""Delete this process's state file. Caller holds ``_HELD_KEYS_LOCK``.
|
|
441
|
+
Never raises."""
|
|
442
|
+
path = _state_file_path()
|
|
443
|
+
if path is None:
|
|
444
|
+
return
|
|
445
|
+
try:
|
|
446
|
+
if os.path.exists(path):
|
|
447
|
+
os.remove(path)
|
|
448
|
+
except Exception as exc: # pragma: no cover - defensive
|
|
449
|
+
logger.debug("_clear_state_locked failed: %s", exc)
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def _pid_alive(pid: int) -> bool:
|
|
453
|
+
"""Best-effort liveness check for ``pid``. On any uncertainty returns True
|
|
454
|
+
(fail-safe: do not replay a file that might belong to a live process)."""
|
|
455
|
+
if pid <= 0:
|
|
456
|
+
return False
|
|
457
|
+
if pid == os.getpid():
|
|
458
|
+
return True
|
|
459
|
+
try:
|
|
460
|
+
if sys.platform == "win32":
|
|
461
|
+
import ctypes
|
|
462
|
+
|
|
463
|
+
PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
|
|
464
|
+
STILL_ACTIVE = 259
|
|
465
|
+
kernel32 = ctypes.windll.kernel32
|
|
466
|
+
handle = kernel32.OpenProcess(
|
|
467
|
+
PROCESS_QUERY_LIMITED_INFORMATION, False, pid
|
|
468
|
+
)
|
|
469
|
+
if not handle:
|
|
470
|
+
return False # cannot open -> gone (or access denied ~ rare)
|
|
471
|
+
try:
|
|
472
|
+
code = ctypes.c_ulong()
|
|
473
|
+
if kernel32.GetExitCodeProcess(handle, ctypes.byref(code)):
|
|
474
|
+
return code.value == STILL_ACTIVE
|
|
475
|
+
return True
|
|
476
|
+
finally:
|
|
477
|
+
kernel32.CloseHandle(handle)
|
|
478
|
+
else:
|
|
479
|
+
try:
|
|
480
|
+
os.kill(pid, 0)
|
|
481
|
+
return True
|
|
482
|
+
except ProcessLookupError:
|
|
483
|
+
return False
|
|
484
|
+
except PermissionError:
|
|
485
|
+
return True # exists but not ours
|
|
486
|
+
except Exception as exc: # pragma: no cover - defensive
|
|
487
|
+
logger.debug("_pid_alive(%s) failed: %s", pid, exc)
|
|
488
|
+
return True
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def recover_orphaned_held_keys() -> int:
|
|
492
|
+
"""Replay key-UPs for any state file owned by a DEAD pid (a prior run
|
|
493
|
+
hard-killed with keys held), then delete the file. Files owned by a live
|
|
494
|
+
pid are left untouched. Returns the number of files recovered. Best-effort;
|
|
495
|
+
never raises; never starts the runtime. change:
|
|
496
|
+
harden-platynui-stuck-key-release."""
|
|
497
|
+
if _RUNTIME is None or _RUNTIME_STATE != "open":
|
|
498
|
+
return 0
|
|
499
|
+
d = _held_keys_dir()
|
|
500
|
+
if d is None:
|
|
501
|
+
return 0
|
|
502
|
+
recovered = 0
|
|
503
|
+
try:
|
|
504
|
+
import json
|
|
505
|
+
|
|
506
|
+
for name in os.listdir(d):
|
|
507
|
+
if not (name.startswith("held_") and name.endswith(".json")):
|
|
508
|
+
continue
|
|
509
|
+
path = os.path.join(d, name)
|
|
510
|
+
try:
|
|
511
|
+
with open(path, "r", encoding="utf-8") as fh:
|
|
512
|
+
data = json.loads(fh.read() or "{}")
|
|
513
|
+
except Exception:
|
|
514
|
+
# Unreadable/partial file: remove it defensively.
|
|
515
|
+
try:
|
|
516
|
+
os.remove(path)
|
|
517
|
+
except Exception:
|
|
518
|
+
pass
|
|
519
|
+
continue
|
|
520
|
+
pid = int(data.get("pid", -1) or -1)
|
|
521
|
+
keys = data.get("keys") or []
|
|
522
|
+
if pid == os.getpid() or _pid_alive(pid):
|
|
523
|
+
continue # ours or a live session's — do not touch
|
|
524
|
+
try:
|
|
525
|
+
if keys:
|
|
526
|
+
_RUNTIME.keyboard_release(_release_sequence_for(keys))
|
|
527
|
+
# Defensive modifier blast for the orphaned session too.
|
|
528
|
+
_RUNTIME.keyboard_release(_RELEASE_ALL_SEQUENCE)
|
|
529
|
+
recovered += 1
|
|
530
|
+
except Exception as exc: # pragma: no cover - env dependent
|
|
531
|
+
logger.debug("orphan replay dispatch failed: %s", exc)
|
|
532
|
+
try:
|
|
533
|
+
os.remove(path)
|
|
534
|
+
except Exception:
|
|
535
|
+
pass
|
|
536
|
+
except Exception as exc: # pragma: no cover - defensive
|
|
537
|
+
logger.debug("recover_orphaned_held_keys failed: %s", exc)
|
|
538
|
+
return recovered
|
|
539
|
+
|
|
540
|
+
|
|
273
541
|
def _register_release_handlers_once() -> None:
|
|
274
542
|
"""Arm atexit + SIGTERM handlers that release held modifiers on process
|
|
275
543
|
exit (F16). Called once from ``get_runtime`` under ``_RUNTIME_LOCK`` after
|
|
@@ -283,7 +551,7 @@ def _register_release_handlers_once() -> None:
|
|
|
283
551
|
try:
|
|
284
552
|
import atexit
|
|
285
553
|
|
|
286
|
-
atexit.register(
|
|
554
|
+
atexit.register(release_tracked_keys)
|
|
287
555
|
except Exception as exc: # pragma: no cover - defensive
|
|
288
556
|
logger.debug("atexit release registration failed: %s", exc)
|
|
289
557
|
try:
|
|
@@ -293,7 +561,7 @@ def _register_release_handlers_once() -> None:
|
|
|
293
561
|
_prior = signal.getsignal(signal.SIGTERM)
|
|
294
562
|
|
|
295
563
|
def _release_on_sigterm(signum, frame): # pragma: no cover - signal path
|
|
296
|
-
|
|
564
|
+
release_tracked_keys()
|
|
297
565
|
if callable(_prior) and _prior not in (
|
|
298
566
|
signal.SIG_DFL,
|
|
299
567
|
signal.SIG_IGN,
|
|
@@ -644,6 +912,11 @@ class PlatynUILibraryPlugin(StaticLibraryPlugin):
|
|
|
644
912
|
# process-kill safety net. Registration is idempotent and the
|
|
645
913
|
# release itself no-ops until the runtime is open. F16.
|
|
646
914
|
_register_release_handlers_once()
|
|
915
|
+
# Recover keys left HELD by a prior process that was hard-killed
|
|
916
|
+
# (SIGKILL/TerminateProcess ran no atexit/SIGTERM handler): replay
|
|
917
|
+
# key-UPs from any state file owned by a dead pid, then clear it.
|
|
918
|
+
# change: harden-platynui-stuck-key-release.
|
|
919
|
+
recover_orphaned_held_keys()
|
|
647
920
|
# Defensively clear any modifier a PRIOR crashed run left held
|
|
648
921
|
# (no-op unless the runtime is already open) — F16.
|
|
649
922
|
release_all_modifiers()
|
|
@@ -651,14 +924,15 @@ class PlatynUILibraryPlugin(StaticLibraryPlugin):
|
|
|
651
924
|
logger.debug("PlatynUI session-start hook failed: %s", exc)
|
|
652
925
|
|
|
653
926
|
def on_session_end(self, session: "ExecutionSession") -> None:
|
|
654
|
-
"""Release any held keyboard
|
|
655
|
-
|
|
927
|
+
"""Release any held keyboard keys when a PlatynUI desktop session closes
|
|
928
|
+
(change: fix-platynui-windows-runtime, F16; harden-platynui-stuck-key-release).
|
|
656
929
|
|
|
657
930
|
``session_manager.on_session_end`` invokes this for every plugin; the
|
|
658
931
|
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.
|
|
932
|
+
it is safe for non-desktop sessions too. Never raises. Releases the
|
|
933
|
+
exact tracked held-key set (incl. non-modifiers), not just modifiers."""
|
|
660
934
|
try:
|
|
661
|
-
|
|
935
|
+
release_tracked_keys()
|
|
662
936
|
except Exception as exc: # pragma: no cover - defensive
|
|
663
937
|
logger.debug("PlatynUI session-end release failed: %s", exc)
|
|
664
938
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rf_mcp-0.34.0.dev4 → rf_mcp-0.34.0.dev5}/.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
|