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.
Files changed (196) hide show
  1. {extra_desktop-0.3.3/src/extra_desktop.egg-info → extra_desktop-0.3.4}/PKG-INFO +8 -4
  2. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/README.md +7 -3
  3. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/pyproject.toml +1 -1
  4. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/__init__.py +1 -1
  5. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/cli.py +58 -2
  6. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/__init__.py +13 -0
  7. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/distiller.py +53 -5
  8. extra_desktop-0.3.4/src/extra/core/mode.py +153 -0
  9. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/base.py +2 -0
  10. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/linux/shell.py +12 -0
  11. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/macos/shell.py +4 -0
  12. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/windows/focus.py +42 -8
  13. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/windows/shell.py +16 -1
  14. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/windows/uia_plane.py +61 -7
  15. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/stall_breaker.py +105 -2
  16. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/subtask.py +416 -2
  17. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/fastpath/__init__.py +8 -0
  18. extra_desktop-0.3.4/src/extra/fastpath/comms.py +233 -0
  19. extra_desktop-0.3.4/src/extra/mcp/server.py +6265 -0
  20. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/relay/client.py +127 -19
  21. {extra_desktop-0.3.3 → extra_desktop-0.3.4/src/extra_desktop.egg-info}/PKG-INFO +8 -4
  22. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra_desktop.egg-info/SOURCES.txt +7 -0
  23. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_app_registry_dynamic.py +26 -0
  24. extra_desktop-0.3.4/tests/test_comms_and_modal.py +137 -0
  25. extra_desktop-0.3.4/tests/test_execution_modes.py +205 -0
  26. extra_desktop-0.3.4/tests/test_full_nifty_speedrun_benchmark.py +249 -0
  27. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_stall_breaker_cognitive.py +114 -0
  28. extra_desktop-0.3.4/tests/test_sub_30s_speedrun_benchmark.py +133 -0
  29. extra_desktop-0.3.4/tests/test_subtask_compiler_expansion.py +186 -0
  30. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_ui_distiller.py +171 -1
  31. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_verification_mcp.py +86 -0
  32. extra_desktop-0.3.3/src/extra/mcp/server.py +0 -2859
  33. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/LICENSE +0 -0
  34. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/NOTICE +0 -0
  35. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/setup.cfg +0 -0
  36. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/capture.py +0 -0
  37. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/composer/__init__.py +0 -0
  38. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/composer/blueprint.py +0 -0
  39. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/composer/folding.py +0 -0
  40. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/composer/lineage.py +0 -0
  41. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/composer/verifier.py +0 -0
  42. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/evolution/__init__.py +0 -0
  43. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/evolution/analyzer.py +0 -0
  44. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/evolution/api_synthesizer.py +0 -0
  45. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/evolution/crystallizer.py +0 -0
  46. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/evolution/curator.py +0 -0
  47. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/evolution/harness_generator.py +0 -0
  48. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/focus.py +0 -0
  49. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/geometry.py +0 -0
  50. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/indicators.py +0 -0
  51. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/input_engine.py +0 -0
  52. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/memory/__init__.py +0 -0
  53. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/memory/db.py +0 -0
  54. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/memory/embeddings.py +0 -0
  55. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/memory/ingest.py +0 -0
  56. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/memory/recall.py +0 -0
  57. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/metrics/__init__.py +0 -0
  58. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/metrics/auditor.py +0 -0
  59. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/metrics/config.py +0 -0
  60. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/metrics/dashboard.py +0 -0
  61. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/metrics/ledger.py +0 -0
  62. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/metrics/sanitizer.py +0 -0
  63. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/metrics/telemetry.py +0 -0
  64. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/motion/__init__.py +0 -0
  65. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/motion/curves.py +0 -0
  66. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/motion/profiler.py +0 -0
  67. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/motion/stroke.py +0 -0
  68. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/motion/windmouse.py +0 -0
  69. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/__init__.py +0 -0
  70. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/linux/__init__.py +0 -0
  71. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/linux/atspi_plane.py +0 -0
  72. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/linux/capture.py +0 -0
  73. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/linux/focus.py +0 -0
  74. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/linux/geometry.py +0 -0
  75. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/linux/indicators.py +0 -0
  76. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/linux/input_engine.py +0 -0
  77. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/linux/permissions.py +0 -0
  78. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/macos/__init__.py +0 -0
  79. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/macos/ax_plane.py +0 -0
  80. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/macos/capture.py +0 -0
  81. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/macos/focus.py +0 -0
  82. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/macos/geometry.py +0 -0
  83. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/macos/indicators.py +0 -0
  84. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/macos/input_engine.py +0 -0
  85. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/macos/permissions.py +0 -0
  86. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/windows/__init__.py +0 -0
  87. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/windows/capture.py +0 -0
  88. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/windows/child_session.py +0 -0
  89. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/windows/geometry.py +0 -0
  90. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/windows/indicators.py +0 -0
  91. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/windows/input_engine.py +0 -0
  92. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/platform/windows/pip_window.py +0 -0
  93. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/scout/__init__.py +0 -0
  94. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/scout/cdp_sniffer.py +0 -0
  95. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/scout/detector.py +0 -0
  96. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/scout/scraper.py +0 -0
  97. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/scout/session_vault.py +0 -0
  98. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/scout/synthesizer.py +0 -0
  99. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/soul/__init__.py +0 -0
  100. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/soul/compressor.py +0 -0
  101. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/soul/critic.py +0 -0
  102. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/soul/decider.py +0 -0
  103. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/soul/eyes.py +0 -0
  104. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/soul/gateman.py +0 -0
  105. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/soul/grammar.py +0 -0
  106. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/soul/runtime.py +0 -0
  107. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/soul/schemas.py +0 -0
  108. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/soul/state_critic.py +0 -0
  109. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/soul/vision_parser.py +0 -0
  110. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/uia_plane.py +0 -0
  111. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/core/uninstall.py +0 -0
  112. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/fastpath/bridge.py +0 -0
  113. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/fastpath/browser.py +0 -0
  114. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/fastpath/cli_harness.py +0 -0
  115. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/fastpath/cli_hub.py +0 -0
  116. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/fastpath/clipboard_html.py +0 -0
  117. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/fastpath/fs.py +0 -0
  118. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/fastpath/relay.py +0 -0
  119. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/fastpath/shell.py +0 -0
  120. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/fastpath/tunnel.py +0 -0
  121. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/fastpath/vectorizer.py +0 -0
  122. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/fastpath/visualizer.py +0 -0
  123. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/fastpath/web/__init__.py +0 -0
  124. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/fastpath/web_workspace.py +0 -0
  125. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/mcp/__init__.py +0 -0
  126. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/relay/__init__.py +0 -0
  127. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra/relay/tunnel.py +0 -0
  128. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra_desktop.egg-info/dependency_links.txt +0 -0
  129. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra_desktop.egg-info/entry_points.txt +0 -0
  130. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra_desktop.egg-info/requires.txt +0 -0
  131. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/src/extra_desktop.egg-info/top_level.txt +0 -0
  132. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_acceleration_suite.py +0 -0
  133. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_bridge_sandbox.py +0 -0
  134. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_canvas_stall_breaker.py +0 -0
  135. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_child_session.py +0 -0
  136. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_cli_harness.py +0 -0
  137. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_composer_director_soul.py +0 -0
  138. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_core_engine.py +0 -0
  139. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_dynamic_batching.py +0 -0
  140. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_evolution.py +0 -0
  141. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_evolution_harness.py +0 -0
  142. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_geometry_exhaustive.py +0 -0
  143. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_hierarchical_benchmarks.py +0 -0
  144. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_human_mouse.py +0 -0
  145. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_input_engine_exhaustive.py +0 -0
  146. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_issue4_bottlenecks.py +0 -0
  147. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_launch_maximize.py +0 -0
  148. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_lineage.py +0 -0
  149. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_linux_atspi.py +0 -0
  150. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_linux_capture.py +0 -0
  151. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_linux_focus.py +0 -0
  152. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_linux_geometry.py +0 -0
  153. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_linux_indicators.py +0 -0
  154. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_linux_input.py +0 -0
  155. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_linux_shell.py +0 -0
  156. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_macos_ax.py +0 -0
  157. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_macos_core.py +0 -0
  158. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_macos_focus_shell.py +0 -0
  159. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_macos_indicators.py +0 -0
  160. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_macos_input.py +0 -0
  161. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_macos_perception.py +0 -0
  162. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_macos_safety.py +0 -0
  163. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_mcp_schemas_exhaustive.py +0 -0
  164. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_mcp_sse_transport.py +0 -0
  165. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_memory_kuzu.py +0 -0
  166. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_memory_subsystem_exhaustive.py +0 -0
  167. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_metrics.py +0 -0
  168. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_milestone_budgeting.py +0 -0
  169. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_paint_remediation.py +0 -0
  170. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_perception_vision_exhaustive.py +0 -0
  171. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_pillar1_funnel.py +0 -0
  172. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_pillar2_installer.py +0 -0
  173. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_pillar3_tcc_playbook.py +0 -0
  174. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_pillar4_web_edge.py +0 -0
  175. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_pillar5_package_ecosystem.py +0 -0
  176. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_pre_release_fixes.py +0 -0
  177. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_process_shell_exhaustive.py +0 -0
  178. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_scout.py +0 -0
  179. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_scout_evolution_exhaustive.py +0 -0
  180. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_security_hardening.py +0 -0
  181. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_soul_benchmarks.py +0 -0
  182. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_soul_evolution.py +0 -0
  183. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_soul_runtime.py +0 -0
  184. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_stall_breaker_exhaustive.py +0 -0
  185. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_state_critic.py +0 -0
  186. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_subtask_runner.py +0 -0
  187. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_telemetry.py +0 -0
  188. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_tiered_verification.py +0 -0
  189. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_tiered_verification_benchmarks.py +0 -0
  190. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_uninstall.py +0 -0
  191. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_universal_tasksuite.py +0 -0
  192. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_vision_parser.py +0 -0
  193. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_visual_grounding.py +0 -0
  194. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_web_to_api.py +0 -0
  195. {extra_desktop-0.3.3 → extra_desktop-0.3.4}/tests/test_web_workspace.py +0 -0
  196. {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
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
- ## ✨ Why Extra?
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
- ## 💡 What You Can Ask Your AI To Do
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
- ## 🎯 How It Works Under The Hood
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
- ## ✨ Why Extra?
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
- ## 💡 What You Can Ask Your AI To Do
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
- ## 🎯 How It Works Under The Hood
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.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"
@@ -2,4 +2,4 @@
2
2
  Project Extra — Flashless Linux, Windows & macOS Computer-Use Engine & MCP Server.
3
3
  """
4
4
 
5
- __version__ = "0.3.3"
5
+ __version__ = "0.3.4"
@@ -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.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 == "stats":
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
- filtered_elements = self._prune_and_filter(raw_elements, interactive_only=interactive_only)
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
- return (
203
- t.replace("\u200b", "")
204
- .replace("\u200c", "")
205
- .replace("\u200d", "")
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
- (expected_proc and win.process_name.lower() == expected_proc.lower())
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