extra-desktop 0.3.3__tar.gz → 0.3.4__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.
- {extra_desktop-0.3.3/src/extra_desktop.egg-info → extra_desktop-0.3.4}/PKG-INFO +8 -4
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/README.md +7 -3
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/pyproject.toml +1 -1
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/__init__.py +1 -1
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/cli.py +58 -2
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/__init__.py +13 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/distiller.py +53 -5
- extra_desktop-0.3.4/src/extra/core/mode.py +153 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/base.py +2 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/linux/shell.py +12 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/macos/shell.py +4 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/windows/focus.py +42 -8
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/windows/shell.py +16 -1
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/windows/uia_plane.py +61 -7
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/stall_breaker.py +105 -2
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/subtask.py +416 -2
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/fastpath/__init__.py +8 -0
- extra_desktop-0.3.4/src/extra/fastpath/comms.py +233 -0
- extra_desktop-0.3.4/src/extra/mcp/server.py +6265 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/relay/client.py +127 -19
- {extra_desktop-0.3.3 → extra_desktop-0.3.4/src/extra_desktop.egg-info}/PKG-INFO +8 -4
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra_desktop.egg-info/SOURCES.txt +7 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_app_registry_dynamic.py +26 -0
- extra_desktop-0.3.4/tests/test_comms_and_modal.py +137 -0
- extra_desktop-0.3.4/tests/test_execution_modes.py +205 -0
- extra_desktop-0.3.4/tests/test_full_nifty_speedrun_benchmark.py +249 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_stall_breaker_cognitive.py +114 -0
- extra_desktop-0.3.4/tests/test_sub_30s_speedrun_benchmark.py +133 -0
- extra_desktop-0.3.4/tests/test_subtask_compiler_expansion.py +186 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_ui_distiller.py +171 -1
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_verification_mcp.py +86 -0
- extra_desktop-0.3.3/src/extra/mcp/server.py +0 -2859
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/LICENSE +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/NOTICE +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/setup.cfg +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/capture.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/composer/__init__.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/composer/blueprint.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/composer/folding.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/composer/lineage.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/composer/verifier.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/evolution/__init__.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/evolution/analyzer.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/evolution/api_synthesizer.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/evolution/crystallizer.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/evolution/curator.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/evolution/harness_generator.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/focus.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/geometry.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/indicators.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/input_engine.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/memory/__init__.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/memory/db.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/memory/embeddings.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/memory/ingest.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/memory/recall.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/metrics/__init__.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/metrics/auditor.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/metrics/config.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/metrics/dashboard.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/metrics/ledger.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/metrics/sanitizer.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/metrics/telemetry.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/motion/__init__.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/motion/curves.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/motion/profiler.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/motion/stroke.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/motion/windmouse.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/__init__.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/linux/__init__.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/linux/atspi_plane.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/linux/capture.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/linux/focus.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/linux/geometry.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/linux/indicators.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/linux/input_engine.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/linux/permissions.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/macos/__init__.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/macos/ax_plane.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/macos/capture.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/macos/focus.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/macos/geometry.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/macos/indicators.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/macos/input_engine.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/macos/permissions.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/windows/__init__.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/windows/capture.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/windows/child_session.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/windows/geometry.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/windows/indicators.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/windows/input_engine.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/windows/pip_window.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/scout/__init__.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/scout/cdp_sniffer.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/scout/detector.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/scout/scraper.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/scout/session_vault.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/scout/synthesizer.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/soul/__init__.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/soul/compressor.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/soul/critic.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/soul/decider.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/soul/eyes.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/soul/gateman.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/soul/grammar.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/soul/runtime.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/soul/schemas.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/soul/state_critic.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/soul/vision_parser.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/uia_plane.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/uninstall.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/fastpath/bridge.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/fastpath/browser.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/fastpath/cli_harness.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/fastpath/cli_hub.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/fastpath/clipboard_html.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/fastpath/fs.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/fastpath/relay.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/fastpath/shell.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/fastpath/tunnel.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/fastpath/vectorizer.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/fastpath/visualizer.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/fastpath/web/__init__.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/fastpath/web_workspace.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/mcp/__init__.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/relay/__init__.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/relay/tunnel.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra_desktop.egg-info/dependency_links.txt +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra_desktop.egg-info/entry_points.txt +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra_desktop.egg-info/requires.txt +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra_desktop.egg-info/top_level.txt +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_acceleration_suite.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_bridge_sandbox.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_canvas_stall_breaker.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_child_session.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_cli_harness.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_composer_director_soul.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_core_engine.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_dynamic_batching.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_evolution.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_evolution_harness.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_geometry_exhaustive.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_hierarchical_benchmarks.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_human_mouse.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_input_engine_exhaustive.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_issue4_bottlenecks.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_launch_maximize.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_lineage.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_linux_atspi.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_linux_capture.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_linux_focus.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_linux_geometry.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_linux_indicators.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_linux_input.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_linux_shell.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_macos_ax.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_macos_core.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_macos_focus_shell.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_macos_indicators.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_macos_input.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_macos_perception.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_macos_safety.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_mcp_schemas_exhaustive.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_mcp_sse_transport.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_memory_kuzu.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_memory_subsystem_exhaustive.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_metrics.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_milestone_budgeting.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_paint_remediation.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_perception_vision_exhaustive.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_pillar1_funnel.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_pillar2_installer.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_pillar3_tcc_playbook.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_pillar4_web_edge.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_pillar5_package_ecosystem.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_pre_release_fixes.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_process_shell_exhaustive.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_scout.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_scout_evolution_exhaustive.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_security_hardening.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_soul_benchmarks.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_soul_evolution.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_soul_runtime.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_stall_breaker_exhaustive.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_state_critic.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_subtask_runner.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_telemetry.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_tiered_verification.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_tiered_verification_benchmarks.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_uninstall.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_universal_tasksuite.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_vision_parser.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_visual_grounding.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_web_to_api.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_web_workspace.py +0 -0
- {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_windows_indicators.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: extra-desktop
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: Flashless Linux, Windows & macOS Computer-Use Engine & Model Context Protocol (MCP) Server
|
|
5
5
|
Author-email: AIYantra Engineering Team <engineering@yantraos.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -75,7 +75,9 @@ Dynamic: license-file
|
|
|
75
75
|
|
|
76
76
|
---
|
|
77
77
|
|
|
78
|
-
##
|
|
78
|
+
## What is Extra and Why Does It Exist?
|
|
79
|
+
|
|
80
|
+
Extra is an open-source Computer Use protocol and Model Context Protocol (MCP) server developed by yantraOS. It bridges LLMs (including ChatGPT, Claude, Cursor, and Gemini) directly to physical desktop operating systems across Linux, macOS, and Windows. Extra replaces fragile vision-only screenshot loops with native OS accessibility trees and sub-3ms GPU frame streaming, delivering pixel-perfect clicking, instant typing, and up to 225× lower token costs.
|
|
79
81
|
|
|
80
82
|
Today's AI assistants can write brilliant essays and code, but they are trapped inside a chat window. When you need them to click a button, open an app, organize your files, or input data into a spreadsheet, they can only give you text instructions.
|
|
81
83
|
|
|
@@ -89,7 +91,7 @@ It connects **Claude, Google Antigravity, Cursor, AGY**, or any autonomous agent
|
|
|
89
91
|
|
|
90
92
|
---
|
|
91
93
|
|
|
92
|
-
##
|
|
94
|
+
## What Can an AI Assistant Do With Extra?
|
|
93
95
|
|
|
94
96
|
Once Extra is running, you can talk to your AI like a real human assistant sitting at your desk:
|
|
95
97
|
|
|
@@ -198,7 +200,9 @@ Or using native Python:
|
|
|
198
200
|
|
|
199
201
|
---
|
|
200
202
|
|
|
201
|
-
##
|
|
203
|
+
## How Does Extra Work Under the Hood?
|
|
204
|
+
|
|
205
|
+
Extra operates via a sovereign Dual-Plane Architecture that decouples visual perception from hardware actuation. For perception, Extra combines sub-3ms GPU frame capture (DirectX DXGI, Metal, Spectacle) with OS accessibility hierarchy inspection (COM UI Automation, AT-SPI2, Quartz). For actuation, Extra dispatches lossless kernel and Win32 hardware events with zero coordinate drift, guaranteeing deterministic control across all resolutions and display scaling factors.
|
|
202
206
|
|
|
203
207
|
Extra is engineered from the ground up for speed, reliability, and token efficiency:
|
|
204
208
|
|
|
@@ -31,7 +31,9 @@
|
|
|
31
31
|
|
|
32
32
|
---
|
|
33
33
|
|
|
34
|
-
##
|
|
34
|
+
## What is Extra and Why Does It Exist?
|
|
35
|
+
|
|
36
|
+
Extra is an open-source Computer Use protocol and Model Context Protocol (MCP) server developed by yantraOS. It bridges LLMs (including ChatGPT, Claude, Cursor, and Gemini) directly to physical desktop operating systems across Linux, macOS, and Windows. Extra replaces fragile vision-only screenshot loops with native OS accessibility trees and sub-3ms GPU frame streaming, delivering pixel-perfect clicking, instant typing, and up to 225× lower token costs.
|
|
35
37
|
|
|
36
38
|
Today's AI assistants can write brilliant essays and code, but they are trapped inside a chat window. When you need them to click a button, open an app, organize your files, or input data into a spreadsheet, they can only give you text instructions.
|
|
37
39
|
|
|
@@ -45,7 +47,7 @@ It connects **Claude, Google Antigravity, Cursor, AGY**, or any autonomous agent
|
|
|
45
47
|
|
|
46
48
|
---
|
|
47
49
|
|
|
48
|
-
##
|
|
50
|
+
## What Can an AI Assistant Do With Extra?
|
|
49
51
|
|
|
50
52
|
Once Extra is running, you can talk to your AI like a real human assistant sitting at your desk:
|
|
51
53
|
|
|
@@ -154,7 +156,9 @@ Or using native Python:
|
|
|
154
156
|
|
|
155
157
|
---
|
|
156
158
|
|
|
157
|
-
##
|
|
159
|
+
## How Does Extra Work Under the Hood?
|
|
160
|
+
|
|
161
|
+
Extra operates via a sovereign Dual-Plane Architecture that decouples visual perception from hardware actuation. For perception, Extra combines sub-3ms GPU frame capture (DirectX DXGI, Metal, Spectacle) with OS accessibility hierarchy inspection (COM UI Automation, AT-SPI2, Quartz). For actuation, Extra dispatches lossless kernel and Win32 hardware events with zero coordinate drift, guaranteeing deterministic control across all resolutions and display scaling factors.
|
|
158
162
|
|
|
159
163
|
Extra is engineered from the ground up for speed, reliability, and token efficiency:
|
|
160
164
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "extra-desktop"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.4"
|
|
8
8
|
description = "Flashless Linux, Windows & macOS Computer-Use Engine & Model Context Protocol (MCP) Server"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -22,7 +22,7 @@ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
|
22
22
|
try:
|
|
23
23
|
from extra import __version__
|
|
24
24
|
except Exception:
|
|
25
|
-
__version__ = "0.3.
|
|
25
|
+
__version__ = "0.3.4"
|
|
26
26
|
|
|
27
27
|
from extra.core.capture import capture_screen
|
|
28
28
|
from extra.core.focus import (
|
|
@@ -1058,6 +1058,53 @@ def cmd_audit(args) -> int:
|
|
|
1058
1058
|
return 1
|
|
1059
1059
|
|
|
1060
1060
|
|
|
1061
|
+
def cmd_mode(args: argparse.Namespace) -> int:
|
|
1062
|
+
"""View or change the Extra automation execution speed mode."""
|
|
1063
|
+
from extra.core.mode import (
|
|
1064
|
+
ExecutionMode,
|
|
1065
|
+
get_execution_mode,
|
|
1066
|
+
set_execution_mode,
|
|
1067
|
+
get_mode_config,
|
|
1068
|
+
)
|
|
1069
|
+
|
|
1070
|
+
target_mode = getattr(args, "mode", None)
|
|
1071
|
+
if not target_mode:
|
|
1072
|
+
curr = get_execution_mode()
|
|
1073
|
+
cfg = get_mode_config(curr)
|
|
1074
|
+
print("\n" + "=" * 60)
|
|
1075
|
+
print(f" [Extra] EXECUTION MODE: {curr.value.upper()}")
|
|
1076
|
+
print("=" * 60)
|
|
1077
|
+
print(f" * Active Profile : {cfg.mode.value}")
|
|
1078
|
+
print(f" * Settle Delay Timeout : {cfg.settle_timeout_sec}s")
|
|
1079
|
+
print(f" * Max Inspected Elements : {cfg.max_inspect_elements}")
|
|
1080
|
+
print(f" * Compact UI Serialization: {cfg.compact_ui}")
|
|
1081
|
+
print(f" * Sleep Scale Factor : {cfg.sleep_scale}x (max {cfg.max_sleep_ms}ms)")
|
|
1082
|
+
print(f" * Proactive Memory Recall : {cfg.proactive_memory_recall}")
|
|
1083
|
+
print(f" * Dual-Plane Verification : {cfg.dual_plane_verification}")
|
|
1084
|
+
print("-" * 60)
|
|
1085
|
+
print(" Available Modes:")
|
|
1086
|
+
print(" - turbo (low, fast) : Sub-50ms dispatch, 0ms settle, <800B UI")
|
|
1087
|
+
print(" - balanced (medium, default): ~15ms settle, balanced UI & safety gates")
|
|
1088
|
+
print(" - smart (high, verify) : Full SOUL visual critic & memory recall")
|
|
1089
|
+
print("-" * 60)
|
|
1090
|
+
print(" Usage: extra mode <turbo|balanced|smart> or extra modeset <mode>\n")
|
|
1091
|
+
return 0
|
|
1092
|
+
|
|
1093
|
+
try:
|
|
1094
|
+
resolved = ExecutionMode.from_string(target_mode)
|
|
1095
|
+
except Exception as e:
|
|
1096
|
+
print(f"Error: Invalid mode '{target_mode}': {e}", file=sys.stderr)
|
|
1097
|
+
return 1
|
|
1098
|
+
|
|
1099
|
+
cfg = set_execution_mode(resolved, persist=True)
|
|
1100
|
+
print(f"\n[Extra] Execution mode set to: {resolved.value.upper()} (saved to ~/.extra/config.json)")
|
|
1101
|
+
print(f" * Settle Timeout : {cfg.settle_timeout_sec}s")
|
|
1102
|
+
print(f" * Sleep Scale : {cfg.sleep_scale}x")
|
|
1103
|
+
print(f" * Compact UI : {cfg.compact_ui}")
|
|
1104
|
+
print(f" * Proactive Recall : {cfg.proactive_memory_recall}\n")
|
|
1105
|
+
return 0
|
|
1106
|
+
|
|
1107
|
+
|
|
1061
1108
|
def check_first_run_consent() -> None:
|
|
1062
1109
|
"""Checks and prompts for first-run interactive telemetry consent."""
|
|
1063
1110
|
from extra.core.metrics.config import (
|
|
@@ -1109,6 +1156,13 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1109
1156
|
)
|
|
1110
1157
|
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
1111
1158
|
|
|
1159
|
+
# mode / modeset
|
|
1160
|
+
mode_parser = subparsers.add_parser("mode", help="View or set the Extra automation speed mode (turbo, balanced, smart)")
|
|
1161
|
+
mode_parser.add_argument("mode", nargs="?", default=None, help="Speed mode to activate: turbo (low/fast), balanced (medium/default), smart (high/deliberate)")
|
|
1162
|
+
|
|
1163
|
+
modeset_parser = subparsers.add_parser("modeset", help="Set the Extra automation speed mode (alias for 'extra mode')")
|
|
1164
|
+
modeset_parser.add_argument("mode", nargs="?", default=None, help="Speed mode to activate: turbo (low/fast), balanced (medium/default), smart (high/deliberate)")
|
|
1165
|
+
|
|
1112
1166
|
# doctor
|
|
1113
1167
|
doctor_parser = subparsers.add_parser("doctor", help="Run system health and capability diagnostics")
|
|
1114
1168
|
doctor_parser.add_argument("--open", "-o", action="store_true", help="Open System Settings privacy panes if permissions missing")
|
|
@@ -1261,7 +1315,9 @@ def main() -> None:
|
|
|
1261
1315
|
except Exception:
|
|
1262
1316
|
pass
|
|
1263
1317
|
|
|
1264
|
-
if args.command
|
|
1318
|
+
if args.command in ("mode", "modeset"):
|
|
1319
|
+
sys.exit(cmd_mode(args))
|
|
1320
|
+
elif args.command == "stats":
|
|
1265
1321
|
sys.exit(cmd_stats(args))
|
|
1266
1322
|
elif args.command == "dashboard":
|
|
1267
1323
|
sys.exit(cmd_dashboard(args))
|
|
@@ -74,6 +74,13 @@ from extra.core.subtask import (
|
|
|
74
74
|
SubtaskRunner,
|
|
75
75
|
run_subtask,
|
|
76
76
|
)
|
|
77
|
+
from extra.core.mode import (
|
|
78
|
+
ExecutionMode,
|
|
79
|
+
ModeConfig,
|
|
80
|
+
get_execution_mode,
|
|
81
|
+
set_execution_mode,
|
|
82
|
+
get_mode_config,
|
|
83
|
+
)
|
|
77
84
|
from extra.core.soul import (
|
|
78
85
|
DecisionType,
|
|
79
86
|
SoulDecision,
|
|
@@ -156,4 +163,10 @@ __all__ = [
|
|
|
156
163
|
"SubtaskResult",
|
|
157
164
|
"SubtaskRunner",
|
|
158
165
|
"run_subtask",
|
|
166
|
+
# Sovereign Execution Mode Controller
|
|
167
|
+
"ExecutionMode",
|
|
168
|
+
"ModeConfig",
|
|
169
|
+
"get_execution_mode",
|
|
170
|
+
"set_execution_mode",
|
|
171
|
+
"get_mode_config",
|
|
159
172
|
]
|
|
@@ -212,8 +212,9 @@ class UIDistiller:
|
|
|
212
212
|
max_elements=max(max_elements * 4, 150),
|
|
213
213
|
)
|
|
214
214
|
|
|
215
|
-
# 3. Filter, Prune, and Classify
|
|
216
|
-
|
|
215
|
+
# 3. Filter, Prune, and Classify (with strict window viewport boundary enforcement)
|
|
216
|
+
win_rect = win_info.rect if (win_info and win_info.rect and win_info.width > 10 and win_info.height > 10) else None
|
|
217
|
+
filtered_elements = self._prune_and_filter(raw_elements, interactive_only=interactive_only, window_rect=win_rect)
|
|
217
218
|
|
|
218
219
|
# Determine default source
|
|
219
220
|
plat_src = "uia_tier0"
|
|
@@ -357,12 +358,25 @@ class UIDistiller:
|
|
|
357
358
|
self,
|
|
358
359
|
elements: List[UIElement],
|
|
359
360
|
interactive_only: bool = False,
|
|
361
|
+
window_rect: Optional[Tuple[int, int, int, int]] = None,
|
|
360
362
|
) -> List[UIElement]:
|
|
361
363
|
"""Prunes noisy container nodes, deduplicates overlapping elements, and drops offscreen items."""
|
|
362
364
|
pruned: List[UIElement] = []
|
|
363
365
|
seen_keys: Set[Tuple[int, int, str]] = set()
|
|
364
366
|
|
|
365
367
|
for el in elements:
|
|
368
|
+
# Universal Viewport Boundary Check:
|
|
369
|
+
# Discard any element whose bounding box is strictly outside the target window viewport
|
|
370
|
+
if window_rect:
|
|
371
|
+
w_left, w_top, w_right, w_bottom = window_rect
|
|
372
|
+
b_left, b_top, b_right, b_bottom = el.bounding_box
|
|
373
|
+
if b_bottom <= w_top or b_top >= w_bottom or b_right <= w_left or b_left >= w_right:
|
|
374
|
+
continue
|
|
375
|
+
|
|
376
|
+
# Fallback: Reject extreme negative-Y/X coordinates from virtualized Qt/Chromium containers
|
|
377
|
+
if el.bounding_box[1] < -8000 or el.bounding_box[0] < -8000:
|
|
378
|
+
continue
|
|
379
|
+
|
|
366
380
|
c_type = el.control_type or "Unknown"
|
|
367
381
|
name = (el.name or "").strip()
|
|
368
382
|
auto_id = (el.automation_id or "").strip()
|
|
@@ -376,8 +390,9 @@ class UIDistiller:
|
|
|
376
390
|
is_action = c_type in ACTION_CONTROL_TYPES
|
|
377
391
|
is_info = c_type in INFO_CONTROL_TYPES
|
|
378
392
|
is_container = c_type in CONTAINER_CONTROL_TYPES
|
|
393
|
+
has_stream = " | Buffer:" in name or getattr(el, "source", "") in ("text_pattern", "ocr")
|
|
379
394
|
|
|
380
|
-
if interactive_only and not (is_input or is_action):
|
|
395
|
+
if interactive_only and not (is_input or is_action or has_stream):
|
|
381
396
|
continue
|
|
382
397
|
|
|
383
398
|
# Discard unnamed, non-interactive containers with no ID
|
|
@@ -490,14 +505,26 @@ class UIDistiller:
|
|
|
490
505
|
|
|
491
506
|
return "\n".join(lines)
|
|
492
507
|
|
|
508
|
+
def _get_provenance_tag(self, el: UIElement) -> str:
|
|
509
|
+
"""Determines the semantic provenance tag for the given element."""
|
|
510
|
+
src = str(getattr(el, "source", "uia") or "uia").lower()
|
|
511
|
+
if "ocr" in src:
|
|
512
|
+
return "[OCR]"
|
|
513
|
+
elif "txt" in src or "text_pattern" in src or " | Buffer:" in el.name:
|
|
514
|
+
return "[TXT]"
|
|
515
|
+
elif "soul" in src:
|
|
516
|
+
return "[SOUL]"
|
|
517
|
+
return "[UIA]"
|
|
518
|
+
|
|
493
519
|
def _format_element_line(self, el: UIElement, include_coordinates: bool = True) -> str:
|
|
494
|
-
"""Formats a single UI element bullet."""
|
|
520
|
+
"""Formats a single UI element bullet with provenance."""
|
|
495
521
|
name = f"'{el.name}'" if el.name else "(unnamed)"
|
|
496
522
|
coord_str = f" [Center: {el.center[0]}, {el.center[1]}]" if include_coordinates else ""
|
|
497
523
|
auto_id_str = f" (id: {el.automation_id})" if el.automation_id and el.automation_id != el.name else ""
|
|
498
524
|
enabled_str = " (disabled)" if not el.is_enabled else ""
|
|
525
|
+
tag = self._get_provenance_tag(el)
|
|
499
526
|
|
|
500
|
-
return f"- [ID: {el.element_id}] {el.control_type}: {name}{auto_id_str}{enabled_str}{coord_str}"
|
|
527
|
+
return f"- {tag} [ID: {el.element_id}] {el.control_type}: {name}{auto_id_str}{enabled_str}{coord_str}"
|
|
501
528
|
|
|
502
529
|
def _format_compact_action_rows(
|
|
503
530
|
self, actions: List[UIElement], include_coordinates: bool = True, row_tolerance: int = 14
|
|
@@ -548,6 +575,25 @@ class UIDistiller:
|
|
|
548
575
|
f"Size: {work_surface['width']}x{work_surface['height']} | Center: ({work_surface['center'][0]},{work_surface['center'][1]})]"
|
|
549
576
|
)
|
|
550
577
|
|
|
578
|
+
# Streams & Buffers (Terminals, Console stdout, Rich Documents, OCR Text)
|
|
579
|
+
terminal_buffers = []
|
|
580
|
+
stream_items = []
|
|
581
|
+
for el in elements:
|
|
582
|
+
src_tag = self._get_provenance_tag(el)
|
|
583
|
+
if src_tag in ("[TXT]", "[OCR]") or " | Buffer:" in el.name:
|
|
584
|
+
coords = f"({el.center[0]},{el.center[1]})" if include_coordinates else ""
|
|
585
|
+
stream_items.append(f"{src_tag} [{el.element_id}] {el.name}{coords}")
|
|
586
|
+
if " | Buffer:" in el.name:
|
|
587
|
+
clean_buf = el.name.split(" | Buffer:", 1)[1].strip()
|
|
588
|
+
lines = [l.strip() for l in clean_buf.splitlines() if l.strip()]
|
|
589
|
+
clean_lines = " \n ".join(lines)
|
|
590
|
+
terminal_buffers.append(f"[{el.element_id}] {clean_lines}")
|
|
591
|
+
|
|
592
|
+
if terminal_buffers:
|
|
593
|
+
parts.append("TERMINAL_BUFFER: " + " | ".join(terminal_buffers[:4]))
|
|
594
|
+
if stream_items:
|
|
595
|
+
parts.append("STREAMS: " + " | ".join(stream_items[:4]))
|
|
596
|
+
|
|
551
597
|
info_items = [
|
|
552
598
|
f"{el.name}" for el in elements
|
|
553
599
|
if el.control_type in INFO_CONTROL_TYPES and el.name
|
|
@@ -596,6 +642,8 @@ class UIDistiller:
|
|
|
596
642
|
"id": el.element_id,
|
|
597
643
|
"type": el.control_type,
|
|
598
644
|
"name": el.name,
|
|
645
|
+
"provenance": self._get_provenance_tag(el),
|
|
646
|
+
"source": getattr(el, "source", "uia"),
|
|
599
647
|
"center": list(el.center) if include_coordinates else None,
|
|
600
648
|
"enabled": el.is_enabled,
|
|
601
649
|
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Project Extra — Sovereign Execution Mode Controller
|
|
3
|
+
Manages runtime acceleration profiles: 'turbo' (low latency), 'balanced' (medium), and 'smart' (high verification).
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import asdict, dataclass
|
|
8
|
+
from enum import Enum
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Dict, Optional, Union
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ExecutionMode(str, Enum):
|
|
16
|
+
TURBO = "turbo" # Fast / Fire-and-forget / Minimal delays (< 15ms)
|
|
17
|
+
BALANCED = "balanced" # Default / Adaptive SOUL settle / Compact UI (~100ms)
|
|
18
|
+
SMART = "smart" # Full closed-loop verification / UIA + Pixel diffing / Memory recall (~400ms)
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def from_string(cls, val: Union[str, ExecutionMode]) -> ExecutionMode:
|
|
22
|
+
if isinstance(val, ExecutionMode):
|
|
23
|
+
return val
|
|
24
|
+
s = str(val).strip().lower()
|
|
25
|
+
if s in ("turbo", "fast", "low", "lightning", "speed", "l"):
|
|
26
|
+
return cls.TURBO
|
|
27
|
+
if s in ("smart", "high", "deep", "full", "verify", "safe", "h"):
|
|
28
|
+
return cls.SMART
|
|
29
|
+
return cls.BALANCED
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class ModeConfig:
|
|
34
|
+
mode: ExecutionMode
|
|
35
|
+
auto_settle: bool
|
|
36
|
+
settle_timeout_sec: float
|
|
37
|
+
max_inspect_elements: int
|
|
38
|
+
compact_ui: bool
|
|
39
|
+
strip_heavy_attributes: bool
|
|
40
|
+
sleep_scale: float
|
|
41
|
+
max_sleep_ms: float
|
|
42
|
+
proactive_memory_recall: bool
|
|
43
|
+
dual_plane_verification: bool
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def name(self) -> ExecutionMode:
|
|
47
|
+
return self.mode
|
|
48
|
+
|
|
49
|
+
def scale_sleep(self, duration_ms: float) -> float:
|
|
50
|
+
"""Scales requested sleep duration (in milliseconds) according to mode profile."""
|
|
51
|
+
scaled = float(duration_ms) * self.sleep_scale
|
|
52
|
+
return min(scaled, self.max_sleep_ms)
|
|
53
|
+
|
|
54
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
55
|
+
d = asdict(self)
|
|
56
|
+
d["mode"] = self.mode.value
|
|
57
|
+
return d
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
MODE_PROFILES: Dict[ExecutionMode, ModeConfig] = {
|
|
61
|
+
ExecutionMode.TURBO: ModeConfig(
|
|
62
|
+
mode=ExecutionMode.TURBO,
|
|
63
|
+
auto_settle=False,
|
|
64
|
+
settle_timeout_sec=0.5,
|
|
65
|
+
max_inspect_elements=10,
|
|
66
|
+
compact_ui=True,
|
|
67
|
+
strip_heavy_attributes=True,
|
|
68
|
+
sleep_scale=0.05,
|
|
69
|
+
max_sleep_ms=50.0,
|
|
70
|
+
proactive_memory_recall=False,
|
|
71
|
+
dual_plane_verification=False,
|
|
72
|
+
),
|
|
73
|
+
ExecutionMode.BALANCED: ModeConfig(
|
|
74
|
+
mode=ExecutionMode.BALANCED,
|
|
75
|
+
auto_settle=True,
|
|
76
|
+
settle_timeout_sec=1.5,
|
|
77
|
+
max_inspect_elements=15,
|
|
78
|
+
compact_ui=True,
|
|
79
|
+
strip_heavy_attributes=True,
|
|
80
|
+
sleep_scale=0.5,
|
|
81
|
+
max_sleep_ms=500.0,
|
|
82
|
+
proactive_memory_recall=False,
|
|
83
|
+
dual_plane_verification=False,
|
|
84
|
+
),
|
|
85
|
+
ExecutionMode.SMART: ModeConfig(
|
|
86
|
+
mode=ExecutionMode.SMART,
|
|
87
|
+
auto_settle=True,
|
|
88
|
+
settle_timeout_sec=4.0,
|
|
89
|
+
max_inspect_elements=30,
|
|
90
|
+
compact_ui=False,
|
|
91
|
+
strip_heavy_attributes=False,
|
|
92
|
+
sleep_scale=1.0,
|
|
93
|
+
max_sleep_ms=10000.0,
|
|
94
|
+
proactive_memory_recall=True,
|
|
95
|
+
dual_plane_verification=True,
|
|
96
|
+
),
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
def _load_initial_mode() -> ExecutionMode:
|
|
100
|
+
"""Loads default mode from EXTRA_SPEED_MODE env var or persistent ~/.extra/config.json."""
|
|
101
|
+
env_val = os.environ.get("EXTRA_SPEED_MODE")
|
|
102
|
+
if env_val:
|
|
103
|
+
return ExecutionMode.from_string(env_val)
|
|
104
|
+
try:
|
|
105
|
+
cfg_path = Path.home() / ".extra" / "config.json"
|
|
106
|
+
if cfg_path.exists():
|
|
107
|
+
with open(cfg_path, "r", encoding="utf-8") as f:
|
|
108
|
+
data = json.load(f)
|
|
109
|
+
if isinstance(data, dict) and "speed_mode" in data:
|
|
110
|
+
return ExecutionMode.from_string(data["speed_mode"])
|
|
111
|
+
except Exception:
|
|
112
|
+
pass
|
|
113
|
+
return ExecutionMode.BALANCED
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
_active_mode: ExecutionMode = _load_initial_mode()
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def get_execution_mode() -> ExecutionMode:
|
|
120
|
+
"""Returns currently active global ExecutionMode."""
|
|
121
|
+
return _active_mode
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def set_execution_mode(mode: Union[str, ExecutionMode], persist: bool = True) -> ModeConfig:
|
|
125
|
+
"""Sets active ExecutionMode and persists to ~/.extra/config.json by default."""
|
|
126
|
+
global _active_mode
|
|
127
|
+
_active_mode = ExecutionMode.from_string(mode)
|
|
128
|
+
os.environ["EXTRA_SPEED_MODE"] = _active_mode.value
|
|
129
|
+
if persist:
|
|
130
|
+
try:
|
|
131
|
+
cfg_path = Path.home() / ".extra" / "config.json"
|
|
132
|
+
cfg_path.parent.mkdir(parents=True, exist_ok=True)
|
|
133
|
+
data: Dict[str, Any] = {}
|
|
134
|
+
if cfg_path.exists():
|
|
135
|
+
try:
|
|
136
|
+
with open(cfg_path, "r", encoding="utf-8") as f:
|
|
137
|
+
data = json.load(f)
|
|
138
|
+
except Exception:
|
|
139
|
+
data = {}
|
|
140
|
+
data["speed_mode"] = _active_mode.value
|
|
141
|
+
with open(cfg_path, "w", encoding="utf-8") as f:
|
|
142
|
+
json.dump(data, f, indent=2)
|
|
143
|
+
except Exception:
|
|
144
|
+
pass
|
|
145
|
+
return MODE_PROFILES[_active_mode]
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def get_mode_config(mode: Optional[Union[str, ExecutionMode]] = None) -> ModeConfig:
|
|
149
|
+
"""Returns ModeConfig for given mode, or active mode if None."""
|
|
150
|
+
if mode is not None:
|
|
151
|
+
resolved = ExecutionMode.from_string(mode)
|
|
152
|
+
return MODE_PROFILES[resolved]
|
|
153
|
+
return MODE_PROFILES[_active_mode]
|
|
@@ -85,6 +85,7 @@ class UIElement:
|
|
|
85
85
|
is_enabled: bool = True
|
|
86
86
|
is_offscreen: bool = False
|
|
87
87
|
raw_element: Any = None
|
|
88
|
+
source: str = "uia"
|
|
88
89
|
|
|
89
90
|
@property
|
|
90
91
|
def width(self) -> int:
|
|
@@ -106,6 +107,7 @@ class UIElement:
|
|
|
106
107
|
"center": list(self.center),
|
|
107
108
|
"is_enabled": self.is_enabled,
|
|
108
109
|
"is_offscreen": self.is_offscreen,
|
|
110
|
+
"source": self.source,
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
|
|
@@ -312,6 +312,18 @@ def get_desktop_indexer() -> XDGDesktopIndexer:
|
|
|
312
312
|
# ── Built-in Application Fallbacks & Semantic Registry ────────────────────────
|
|
313
313
|
|
|
314
314
|
BUILTIN_APP_FALLBACKS: Dict[str, Dict[str, Any]] = {
|
|
315
|
+
# Clock & Timer
|
|
316
|
+
"clock": {
|
|
317
|
+
"candidates": ["gnome-clocks", "kclock", "alarm-clock-applet"],
|
|
318
|
+
"type": "bin",
|
|
319
|
+
"proc": "clocks",
|
|
320
|
+
},
|
|
321
|
+
"timer": {
|
|
322
|
+
"candidates": ["gnome-clocks", "kclock", "alarm-clock-applet"],
|
|
323
|
+
"type": "bin",
|
|
324
|
+
"proc": "clocks",
|
|
325
|
+
},
|
|
326
|
+
|
|
315
327
|
# Calculator
|
|
316
328
|
"calc": {
|
|
317
329
|
"candidates": ["kcalc", "gnome-calculator", "galculator", "xcalc", "speedcrunch", "qalculate-gtk", "bc"],
|
|
@@ -44,6 +44,10 @@ BUILTIN_MAC_APP_REGISTRY: Dict[str, Dict[str, str]] = {
|
|
|
44
44
|
"vlc": {"target": "VLC", "bundle": "/Applications/VLC.app", "proc": "VLC", "type": "app"},
|
|
45
45
|
"blender": {"target": "Blender", "bundle": "/Applications/Blender.app", "proc": "Blender", "type": "app"},
|
|
46
46
|
"canva": {"target": "Canva", "bundle": "/Applications/Canva.app", "proc": "Canva", "type": "app"},
|
|
47
|
+
"clock": {"target": "Clock", "bundle": "/System/Applications/Clock.app", "proc": "Clock", "type": "app"},
|
|
48
|
+
"timer": {"target": "Clock", "bundle": "/System/Applications/Clock.app", "proc": "Clock", "type": "app"},
|
|
49
|
+
"calendar": {"target": "Calendar", "bundle": "/System/Applications/Calendar.app", "proc": "Calendar", "type": "app"},
|
|
50
|
+
"mail": {"target": "Mail", "bundle": "/System/Applications/Mail.app", "proc": "Mail", "type": "app"},
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
MAC_APP_REGISTRY: Dict[str, Dict[str, str]] = dict(BUILTIN_MAC_APP_REGISTRY)
|
|
@@ -196,17 +196,18 @@ ALIAS_PROCESS_MAP: Dict[str, str] = _DynamicProcessMap()
|
|
|
196
196
|
|
|
197
197
|
|
|
198
198
|
def _normalize_title(t: str) -> str:
|
|
199
|
-
"""Normalizes title string by stripping zero-width and invisible control unicode chars."""
|
|
199
|
+
"""Normalizes title string by stripping zero-width, bidirectional, and invisible control unicode chars."""
|
|
200
200
|
if not t:
|
|
201
201
|
return ""
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
.replace("\ufeff", "")
|
|
207
|
-
.strip()
|
|
208
|
-
.lower()
|
|
202
|
+
bad_chars = (
|
|
203
|
+
"\u200b", "\u200c", "\u200d", "\u200e", "\u200f",
|
|
204
|
+
"\u202a", "\u202b", "\u202c", "\u202d", "\u202e",
|
|
205
|
+
"\ufeff", "\ufffc", "\u00a0",
|
|
209
206
|
)
|
|
207
|
+
res = t
|
|
208
|
+
for ch in bad_chars:
|
|
209
|
+
res = res.replace(ch, "")
|
|
210
|
+
return res.strip().lower()
|
|
210
211
|
|
|
211
212
|
|
|
212
213
|
def find_window_by_title(
|
|
@@ -286,6 +287,39 @@ def find_window_by_title(
|
|
|
286
287
|
break
|
|
287
288
|
time.sleep(0.1)
|
|
288
289
|
|
|
290
|
+
# Tier 5: Minimized or Tray Window Fallback
|
|
291
|
+
# If standard visible lookup fails, check non-visible windows for process match.
|
|
292
|
+
# If a window exists (e.g. Telegram or WhatsApp docked to tray or minimized console),
|
|
293
|
+
# restore it into foreground visibility and return it.
|
|
294
|
+
if visible_only:
|
|
295
|
+
proc_target = None
|
|
296
|
+
try:
|
|
297
|
+
from extra.core.platform.windows.shell import APP_REGISTRY
|
|
298
|
+
reg_entry = APP_REGISTRY.get(q)
|
|
299
|
+
if reg_entry and "proc" in reg_entry:
|
|
300
|
+
proc_target = reg_entry["proc"].lower().rstrip(".exe")
|
|
301
|
+
except Exception:
|
|
302
|
+
pass
|
|
303
|
+
if not proc_target:
|
|
304
|
+
proc_target = q.rstrip(".exe")
|
|
305
|
+
|
|
306
|
+
all_wins = list_windows(visible_only=False)
|
|
307
|
+
for win in all_wins:
|
|
308
|
+
w_proc = win.process_name.lower().rstrip(".exe")
|
|
309
|
+
is_proc_match = (
|
|
310
|
+
w_proc == proc_target
|
|
311
|
+
or proc_target in w_proc
|
|
312
|
+
or (len(proc_target) > 3 and w_proc in proc_target)
|
|
313
|
+
or (proc_target in ("powershell", "pwsh", "cmd") and w_proc in ("windowsterminal", "openconsole"))
|
|
314
|
+
)
|
|
315
|
+
if is_proc_match and win.title:
|
|
316
|
+
try:
|
|
317
|
+
win32gui.ShowWindow(win.hwnd, SW_RESTORE)
|
|
318
|
+
time.sleep(0.1)
|
|
319
|
+
except Exception:
|
|
320
|
+
pass
|
|
321
|
+
return win
|
|
322
|
+
|
|
289
323
|
return None
|
|
290
324
|
|
|
291
325
|
|
|
@@ -63,9 +63,19 @@ BUILTIN_APP_REGISTRY: Dict[str, Dict[str, str]] = {
|
|
|
63
63
|
"mspaint": {"target": "mspaint.exe", "type": "exe", "proc": "mspaint.exe"},
|
|
64
64
|
"photos": {"target": "ms-photos:", "type": "uri", "proc": "Photos.exe"},
|
|
65
65
|
"store": {"target": "ms-windows-store:", "type": "uri", "proc": "WinStore.App.exe"},
|
|
66
|
+
"clock": {"target": "ms-clock:", "type": "uri", "proc": "ApplicationFrameHost.exe"},
|
|
67
|
+
"alarms": {"target": "ms-clock:", "type": "uri", "proc": "ApplicationFrameHost.exe"},
|
|
68
|
+
"timer": {"target": "ms-clock:", "type": "uri", "proc": "ApplicationFrameHost.exe"},
|
|
69
|
+
"stopwatch": {"target": "ms-clock:", "type": "uri", "proc": "ApplicationFrameHost.exe"},
|
|
70
|
+
"mail": {"target": "outlookmail:", "type": "uri", "proc": "HxOutlook.exe"},
|
|
71
|
+
"calendar": {"target": "outlookcal:", "type": "uri", "proc": "HxCalendar.exe"},
|
|
72
|
+
"camera": {"target": "microsoft.windows.camera:", "type": "uri", "proc": "WindowsCamera.exe"},
|
|
73
|
+
"weather": {"target": "msnweather:", "type": "uri", "proc": "Weather.exe"},
|
|
66
74
|
"vlc": {"target": "vlc.exe", "type": "exe", "proc": "vlc.exe"},
|
|
67
75
|
"blender": {"target": "blender.exe", "type": "exe", "proc": "blender.exe"},
|
|
68
76
|
"canva": {"target": "canva.exe", "type": "exe", "proc": "canva.exe"},
|
|
77
|
+
"telegram": {"target": "Telegram.exe", "type": "exe", "proc": "Telegram.exe"},
|
|
78
|
+
"whatsapp": {"target": "WhatsApp.exe", "type": "exe", "proc": "WhatsApp.exe"},
|
|
69
79
|
}
|
|
70
80
|
|
|
71
81
|
APP_REGISTRY: Dict[str, Dict[str, str]] = dict(BUILTIN_APP_REGISTRY)
|
|
@@ -538,9 +548,14 @@ def launch_app(
|
|
|
538
548
|
matched_pid = win.process_id
|
|
539
549
|
break
|
|
540
550
|
# Or matches process name (including WindowsTerminal hosting console apps in Win11)
|
|
551
|
+
is_uwp_host = bool(expected_proc and expected_proc.lower() == "applicationframehost.exe")
|
|
552
|
+
proc_matches = bool(expected_proc and win.process_name.lower() == expected_proc.lower())
|
|
553
|
+
title_matches = bool(clean_app in win_title or (clean_app in ("clock", "timer", "alarms", "stopwatch") and "clock" in win_title))
|
|
554
|
+
|
|
541
555
|
if (
|
|
542
|
-
(
|
|
556
|
+
(proc_matches and (not is_uwp_host or title_matches))
|
|
543
557
|
or (clean_app in ("powershell", "cmd", "terminal") and "terminal" in win.process_name.lower())
|
|
558
|
+
or (title_matches and win_title not in EXCLUDED_SHELL_TITLES)
|
|
544
559
|
):
|
|
545
560
|
matched_hwnd = win.hwnd
|
|
546
561
|
matched_title = win.title
|