ok-script 2.0.20__tar.gz → 2.0.21__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 (398) hide show
  1. {ok_script-2.0.20 → ok_script-2.0.21}/PKG-INFO +1 -1
  2. {ok_script-2.0.20 → ok_script-2.0.21}/docs/mcp.md +10 -1
  3. {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/code_runner.py +1 -1
  4. {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/mcp_runner.py +66 -1
  5. {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/mcp_server.py +4 -2
  6. {ok_script-2.0.20 → ok_script-2.0.21}/ok_script.egg-info/PKG-INFO +1 -1
  7. {ok_script-2.0.20 → ok_script-2.0.21}/ok_script.egg-info/SOURCES.txt +1 -0
  8. ok_script-2.0.21/tests/test_mcp_cleanup.py +166 -0
  9. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_mcp_server.py +35 -0
  10. {ok_script-2.0.20 → ok_script-2.0.21}/.agents/skills/develop/SKILL.md +0 -0
  11. {ok_script-2.0.20 → ok_script-2.0.21}/.agents/skills/develop/agents/openai.yaml +0 -0
  12. {ok_script-2.0.20 → ok_script-2.0.21}/.agents/skills/develop/references/windows-input.md +0 -0
  13. {ok_script-2.0.20 → ok_script-2.0.21}/.agents/skills/generate-templates/SKILL.md +0 -0
  14. {ok_script-2.0.20 → ok_script-2.0.21}/.agents/skills/reload-tasks/SKILL.md +0 -0
  15. {ok_script-2.0.20 → ok_script-2.0.21}/.agents/skills/reload-tasks/agents/openai.yaml +0 -0
  16. {ok_script-2.0.20 → ok_script-2.0.21}/.agents/skills/setup-env/SKILL.md +0 -0
  17. {ok_script-2.0.20 → ok_script-2.0.21}/.agents/skills/setup-env/agents/openai.yaml +0 -0
  18. {ok_script-2.0.20 → ok_script-2.0.21}/.agents/skills/setup-env/assets/ok-script-develop/SKILL.md +0 -0
  19. {ok_script-2.0.20 → ok_script-2.0.21}/.agents/skills/setup-project/SKILL.md +0 -0
  20. {ok_script-2.0.20 → ok_script-2.0.21}/.agents/skills/setup-project/agents/openai.yaml +0 -0
  21. {ok_script-2.0.20 → ok_script-2.0.21}/.agents/skills/use_venv/SKILL.md +0 -0
  22. {ok_script-2.0.20 → ok_script-2.0.21}/.agents/skills/write-test/SKILL.md +0 -0
  23. {ok_script-2.0.20 → ok_script-2.0.21}/.agents/skills/write-test/agents/openai.yaml +0 -0
  24. {ok_script-2.0.20 → ok_script-2.0.21}/LICENSE.txt +0 -0
  25. {ok_script-2.0.20 → ok_script-2.0.21}/MANIFEST.in +0 -0
  26. {ok_script-2.0.20 → ok_script-2.0.21}/README.md +0 -0
  27. {ok_script-2.0.20 → ok_script-2.0.21}/diagnostics/qq_trace.py +0 -0
  28. {ok_script-2.0.20 → ok_script-2.0.21}/docs/agent-setup.md +0 -0
  29. {ok_script-2.0.20 → ok_script-2.0.21}/docs/feature-crops.md +0 -0
  30. {ok_script-2.0.20 → ok_script-2.0.21}/examples/mcp_observe.py +0 -0
  31. {ok_script-2.0.20 → ok_script-2.0.21}/ok/__init__.py +0 -0
  32. {ok_script-2.0.20 → ok_script-2.0.21}/ok/__main__.py +0 -0
  33. {ok_script-2.0.20 → ok_script-2.0.21}/ok/alas/__init__.py +0 -0
  34. {ok_script-2.0.20 → ok_script-2.0.21}/ok/alas/emulator_base.py +0 -0
  35. {ok_script-2.0.20 → ok_script-2.0.21}/ok/alas/emulator_windows.py +0 -0
  36. {ok_script-2.0.20 → ok_script-2.0.21}/ok/alas/platform_windows.py +0 -0
  37. {ok_script-2.0.20 → ok_script-2.0.21}/ok/alas/utils.py +0 -0
  38. {ok_script-2.0.20 → ok_script-2.0.21}/ok/assets/default-icon.png +0 -0
  39. {ok_script-2.0.20 → ok_script-2.0.21}/ok/capture/__init__.py +0 -0
  40. {ok_script-2.0.20 → ok_script-2.0.21}/ok/capture/adb/__init__.py +0 -0
  41. {ok_script-2.0.20 → ok_script-2.0.21}/ok/capture/adb/deep.py +0 -0
  42. {ok_script-2.0.20 → ok_script-2.0.21}/ok/capture/adb/minitouch.py +0 -0
  43. {ok_script-2.0.20 → ok_script-2.0.21}/ok/capture/adb/nemu_ipc.py +0 -0
  44. {ok_script-2.0.20 → ok_script-2.0.21}/ok/capture/adb/nemu_utils.py +0 -0
  45. {ok_script-2.0.20 → ok_script-2.0.21}/ok/capture/adb/pool.py +0 -0
  46. {ok_script-2.0.20 → ok_script-2.0.21}/ok/capture/adb/timer.py +0 -0
  47. {ok_script-2.0.20 → ok_script-2.0.21}/ok/capture/adb/util.py +0 -0
  48. {ok_script-2.0.20 → ok_script-2.0.21}/ok/capture/windows/__init__.py +0 -0
  49. {ok_script-2.0.20 → ok_script-2.0.21}/ok/capture/windows/d3d11.py +0 -0
  50. {ok_script-2.0.20 → ok_script-2.0.21}/ok/capture/windows/dump.py +0 -0
  51. {ok_script-2.0.20 → ok_script-2.0.21}/ok/cli.py +0 -0
  52. {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/__init__.py +0 -0
  53. {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/capture_labels.py +0 -0
  54. {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/config_schema.py +0 -0
  55. {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/downloads.py +0 -0
  56. {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/events.py +0 -0
  57. {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/icons.py +0 -0
  58. {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/notifications.py +0 -0
  59. {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/plugin_staging.py +0 -0
  60. {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/plugins.py +0 -0
  61. {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/screenshot.py +0 -0
  62. {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/script_editing.py +0 -0
  63. {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/script_packager.py +0 -0
  64. {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/script_templates.py +0 -0
  65. {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/skills.py +0 -0
  66. {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/start_controller.py +0 -0
  67. {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/task_manager.py +0 -0
  68. {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/template_store.py +0 -0
  69. {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/translation.py +0 -0
  70. {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/ui_config.py +0 -0
  71. {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/DeviceManager.py +0 -0
  72. {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/__init__.py +0 -0
  73. {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/capture.py +0 -0
  74. {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/capture_methods/__init__.py +0 -0
  75. {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/capture_methods/adb.py +0 -0
  76. {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/capture_methods/base.py +0 -0
  77. {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/capture_methods/bitblt.py +0 -0
  78. {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/capture_methods/bitblt_utils.py +0 -0
  79. {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/capture_methods/browser.py +0 -0
  80. {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/capture_methods/desktop_duplication.py +0 -0
  81. {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/capture_methods/hwnd_window.py +0 -0
  82. {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/capture_methods/image.py +0 -0
  83. {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/capture_methods/nemu_ipc.py +0 -0
  84. {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/capture_methods/types.py +0 -0
  85. {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/capture_methods/update.py +0 -0
  86. {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/capture_methods/windows_graphics.py +0 -0
  87. {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/capture_methods/windows_graphics_hdr.py +0 -0
  88. {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/interaction.py +0 -0
  89. {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/interaction_methods/__init__.py +0 -0
  90. {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/interaction_methods/adb.py +0 -0
  91. {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/interaction_methods/base.py +0 -0
  92. {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/interaction_methods/browser.py +0 -0
  93. {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/interaction_methods/do_nothing.py +0 -0
  94. {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/interaction_methods/foreground_post_message.py +0 -0
  95. {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/interaction_methods/genshin.py +0 -0
  96. {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/interaction_methods/keys.py +0 -0
  97. {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/interaction_methods/post_message.py +0 -0
  98. {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/interaction_methods/pydirect.py +0 -0
  99. {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/interaction_methods/pynput.py +0 -0
  100. {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/interaction_methods/swipe.py +0 -0
  101. {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/intercation.py +0 -0
  102. {ok_script-2.0.20 → ok_script-2.0.21}/ok/feature/Box.py +0 -0
  103. {ok_script-2.0.20 → ok_script-2.0.21}/ok/feature/Feature.py +0 -0
  104. {ok_script-2.0.20 → ok_script-2.0.21}/ok/feature/FeatureSet.py +0 -0
  105. {ok_script-2.0.20 → ok_script-2.0.21}/ok/feature/__init__.py +0 -0
  106. {ok_script-2.0.20 → ok_script-2.0.21}/ok/gui/__init__.py +0 -0
  107. {ok_script-2.0.20 → ok_script-2.0.21}/ok/gui/util/pyappify_startup.py +0 -0
  108. {ok_script-2.0.20 → ok_script-2.0.21}/ok/notification/__init__.py +0 -0
  109. {ok_script-2.0.20 → ok_script-2.0.21}/ok/notification/manager.py +0 -0
  110. {ok_script-2.0.20 → ok_script-2.0.21}/ok/notification/messenger_images.py +0 -0
  111. {ok_script-2.0.20 → ok_script-2.0.21}/ok/notification/pipeline.py +0 -0
  112. {ok_script-2.0.20 → ok_script-2.0.21}/ok/notification/ppocr.py +0 -0
  113. {ok_script-2.0.20 → ok_script-2.0.21}/ok/notification/providers.py +0 -0
  114. {ok_script-2.0.20 → ok_script-2.0.21}/ok/notification/system.py +0 -0
  115. {ok_script-2.0.20 → ok_script-2.0.21}/ok/notification/windows_messenger.py +0 -0
  116. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ocr/__init__.py +0 -0
  117. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ocr/download_paddle_model.py +0 -0
  118. {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/Foundation/Collections.py +0 -0
  119. {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/Foundation/__init__.py +0 -0
  120. {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/Globalization/__init__.py +0 -0
  121. {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/Graphics/Capture/__init__.py +0 -0
  122. {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/Graphics/DirectX/Direct3D11/__init__.py +0 -0
  123. {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/Graphics/DirectX/__init__.py +0 -0
  124. {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/Graphics/Imaging/__init__.py +0 -0
  125. {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/Graphics/__init__.py +0 -0
  126. {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/Media/Ocr/__init__.py +0 -0
  127. {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/Media/__init__.py +0 -0
  128. {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/Security/Cryptography/__init__.py +0 -0
  129. {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/Security/__init__.py +0 -0
  130. {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/Storage/Streams/__init__.py +0 -0
  131. {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/Storage/__init__.py +0 -0
  132. {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/UI/ViewManagement/__init__.py +0 -0
  133. {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/UI/__init__.py +0 -0
  134. {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/__init__.py +0 -0
  135. {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/__init__.py +0 -0
  136. {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/delegate.py +0 -0
  137. {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/export.py +0 -0
  138. {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/idldsl.py +0 -0
  139. {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/inspectable.py +0 -0
  140. {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/roapi.py +0 -0
  141. {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/types.py +0 -0
  142. {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/winstring.py +0 -0
  143. {ok_script-2.0.20 → ok_script-2.0.21}/ok/task/DiagnosisTask.py +0 -0
  144. {ok_script-2.0.20 → ok_script-2.0.21}/ok/task/TaskExecutor.py +0 -0
  145. {ok_script-2.0.20 → ok_script-2.0.21}/ok/task/__init__.py +0 -0
  146. {ok_script-2.0.20 → ok_script-2.0.21}/ok/task/exceptions.py +0 -0
  147. {ok_script-2.0.20 → ok_script-2.0.21}/ok/task/task.py +0 -0
  148. {ok_script-2.0.20 → ok_script-2.0.21}/ok/test/OKTestRunner.py +0 -0
  149. {ok_script-2.0.20 → ok_script-2.0.21}/ok/test/RunTests.py +0 -0
  150. {ok_script-2.0.20 → ok_script-2.0.21}/ok/test/TaskTestCase.py +0 -0
  151. {ok_script-2.0.20 → ok_script-2.0.21}/ok/test/__init__.py +0 -0
  152. {ok_script-2.0.20 → ok_script-2.0.21}/ok/third_party/__init__.py +0 -0
  153. {ok_script-2.0.20 → ok_script-2.0.21}/ok/third_party/paperclip.py +0 -0
  154. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/__init__.py +0 -0
  155. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/overlay/__init__.py +0 -0
  156. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/overlay/win32_gdi.py +0 -0
  157. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/Communicate.py +0 -0
  158. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/MainWindow.py +0 -0
  159. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/MessageWindow.py +0 -0
  160. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/StartController.py +0 -0
  161. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/TabTitles.py +0 -0
  162. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/__init__.py +0 -0
  163. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/about/AboutTab.py +0 -0
  164. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/about/HttpTextEdit.py +0 -0
  165. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/about/LinksBar.py +0 -0
  166. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/about/ProjectCard.py +0 -0
  167. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/about/UpdateCard.py +0 -0
  168. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/about/VersionCard.py +0 -0
  169. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/about/__init__.py +0 -0
  170. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/common/OKIcon.py +0 -0
  171. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/common/__init__.py +0 -0
  172. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/common/accent_color.py +0 -0
  173. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/common/config.py +0 -0
  174. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/common/design_system.py +0 -0
  175. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/common/icons.py +0 -0
  176. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/common/style_sheet.py +0 -0
  177. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/debug/DebugTab.py +0 -0
  178. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/debug/JobControls.py +0 -0
  179. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/debug/OverlayWidget.py +0 -0
  180. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/debug/RunCodeTab.py +0 -0
  181. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/debug/Screenshot.py +0 -0
  182. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/debug/__init__.py +0 -0
  183. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/events.py +0 -0
  184. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/i18n/GettextTranslator.py +0 -0
  185. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/i18n/__init__.py +0 -0
  186. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/i18n/en_US.qm +0 -0
  187. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/i18n/en_US.ts +0 -0
  188. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/i18n/es_ES.qm +0 -0
  189. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/i18n/es_ES.ts +0 -0
  190. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/i18n/ja_JP.qm +0 -0
  191. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/i18n/ja_JP.ts +0 -0
  192. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/i18n/ko_KR.qm +0 -0
  193. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/i18n/ko_KR.ts +0 -0
  194. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/i18n/path.py +0 -0
  195. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/i18n/update.py +0 -0
  196. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/i18n/zh_CN.qm +0 -0
  197. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/i18n/zh_CN.ts +0 -0
  198. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/i18n/zh_TW.qm +0 -0
  199. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/i18n/zh_TW.ts +0 -0
  200. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/overlay/OverlayWindow.py +0 -0
  201. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/overlay/__init__.py +0 -0
  202. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/plugins/PluginTab.py +0 -0
  203. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/plugins/__init__.py +0 -0
  204. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/plugins/imported_tabs.py +0 -0
  205. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/__init__.py +0 -0
  206. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/dark/card.qss +0 -0
  207. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/dark/discord.svg +0 -0
  208. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/dark/heart.svg +0 -0
  209. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/dark/message_window.qss +0 -0
  210. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/dark/setting_interface.qss +0 -0
  211. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/dark/status_bar.qss +0 -0
  212. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/dark/stop.svg +0 -0
  213. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/dark/tab.qss +0 -0
  214. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/light/card.qss +0 -0
  215. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/light/discord.svg +0 -0
  216. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/light/heart.svg +0 -0
  217. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/light/message_window.qss +0 -0
  218. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/light/setting_interface.qss +0 -0
  219. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/light/status_bar.qss +0 -0
  220. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/light/stop.svg +0 -0
  221. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/light/tab.qss +0 -0
  222. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qt.qrc +0 -0
  223. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/resources.py +0 -0
  224. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/settings/GlobalConfigCard.py +0 -0
  225. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/settings/GlobalConfigTab.py +0 -0
  226. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/settings/SettingTab.py +0 -0
  227. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/settings/__init__.py +0 -0
  228. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/start/LogWindow.py +0 -0
  229. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/start/SelectCaptureListView.py +0 -0
  230. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/start/SelectInteractionListView.py +0 -0
  231. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/start/StartCard.py +0 -0
  232. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/start/StartTab.py +0 -0
  233. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/start/__init__.py +0 -0
  234. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/ConfigCard.py +0 -0
  235. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/ConfigItemFactory.py +0 -0
  236. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/ConfigLabelAndWidget.py +0 -0
  237. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/EditTaskTab.py +0 -0
  238. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/LabelAndButtons.py +0 -0
  239. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/LabelAndDoubleSpinBox.py +0 -0
  240. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/LabelAndDropDown.py +0 -0
  241. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/LabelAndFileSelector.py +0 -0
  242. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/LabelAndGlobal.py +0 -0
  243. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/LabelAndLineEdit.py +0 -0
  244. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/LabelAndList.py +0 -0
  245. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/LabelAndMcpServer.py +0 -0
  246. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/LabelAndMultiSelection.py +0 -0
  247. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/LabelAndSpinBox.py +0 -0
  248. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/LabelAndSwitchButton.py +0 -0
  249. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/LabelAndTextEdit.py +0 -0
  250. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/LabelAndWidget.py +0 -0
  251. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/MarkUpWindow.py +0 -0
  252. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/ModifyListDialog.py +0 -0
  253. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/ModifyListItem.py +0 -0
  254. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/OneTimeTaskTab.py +0 -0
  255. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/PythonHighlighter.py +0 -0
  256. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/RecordScript.py +0 -0
  257. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/ScheduleTaskTab.py +0 -0
  258. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/ScriptPackager.py +0 -0
  259. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/StartButton.py +0 -0
  260. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/TaskCard.py +0 -0
  261. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/TaskManger.py +0 -0
  262. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/TaskOpButton.py +0 -0
  263. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/TaskTab.py +0 -0
  264. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/TemplateFactory.py +0 -0
  265. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/TemplateTab.py +0 -0
  266. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/TooltipTableWidget.py +0 -0
  267. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/TriggerTaskTab.py +0 -0
  268. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/__init__.py +0 -0
  269. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/schedule_index_sync.py +0 -0
  270. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/util/Alert.py +0 -0
  271. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/util/InitWorker.py +0 -0
  272. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/util/__init__.py +0 -0
  273. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/util/app.py +0 -0
  274. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/util/download.py +0 -0
  275. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/util/pip_util.py +0 -0
  276. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/util/pyappify_startup.py +0 -0
  277. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/util/touch_scroll.py +0 -0
  278. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/util/windows_thumbnail.py +0 -0
  279. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/BaseLoading.py +0 -0
  280. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/BaseWindow.py +0 -0
  281. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/Card.py +0 -0
  282. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/CustomTab.py +0 -0
  283. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/EmptyCard.py +0 -0
  284. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/ExpandCardLayout.py +0 -0
  285. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/FlowLayout.py +0 -0
  286. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/FramelessDialog.py +0 -0
  287. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/ImageWidget.py +0 -0
  288. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/ImportWarningDialog.py +0 -0
  289. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/ListTableWidgetItem.py +0 -0
  290. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/NumericTableWidgetItem.py +0 -0
  291. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/OkCompactSpinBox.py +0 -0
  292. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/SortingTableWidgetItem.py +0 -0
  293. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/StartLoadingDialog.py +0 -0
  294. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/StatusBar.py +0 -0
  295. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/Tab.py +0 -0
  296. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/TabWidget.py +0 -0
  297. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/UpdateConfigWidgetItem.py +0 -0
  298. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/WidgetWithVLabel.py +0 -0
  299. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/YesNonWidgetItem.py +0 -0
  300. {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/__init__.py +0 -0
  301. {ok_script-2.0.20 → ok_script-2.0.21}/ok/update/__init__.py +0 -0
  302. {ok_script-2.0.20 → ok_script-2.0.21}/ok/update/copy_ok_folder.py +0 -0
  303. {ok_script-2.0.20 → ok_script-2.0.21}/ok/update/inline_ok_requirements.py +0 -0
  304. {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/Analytics.py +0 -0
  305. {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/GlobalConfig.py +0 -0
  306. {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/__init__.py +0 -0
  307. {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/blur.py +0 -0
  308. {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/clazz.py +0 -0
  309. {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/collection.py +0 -0
  310. {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/color.py +0 -0
  311. {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/config.py +0 -0
  312. {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/explorer.py +0 -0
  313. {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/file.py +0 -0
  314. {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/gpu_driver_settings.py +0 -0
  315. {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/handler.py +0 -0
  316. {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/i18n.py +0 -0
  317. {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/logger.py +0 -0
  318. {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/partial_sync_repo.py +0 -0
  319. {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/print_hwnd.py +0 -0
  320. {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/process.py +0 -0
  321. {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/version.py +0 -0
  322. {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/window.py +0 -0
  323. {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/windows_schedule.py +0 -0
  324. {ok_script-2.0.20 → ok_script-2.0.21}/ok_script.egg-info/dependency_links.txt +0 -0
  325. {ok_script-2.0.20 → ok_script-2.0.21}/ok_script.egg-info/entry_points.txt +0 -0
  326. {ok_script-2.0.20 → ok_script-2.0.21}/ok_script.egg-info/not-zip-safe +0 -0
  327. {ok_script-2.0.20 → ok_script-2.0.21}/ok_script.egg-info/requires.txt +0 -0
  328. {ok_script-2.0.20 → ok_script-2.0.21}/ok_script.egg-info/top_level.txt +0 -0
  329. {ok_script-2.0.20 → ok_script-2.0.21}/pyproject.toml +0 -0
  330. {ok_script-2.0.20 → ok_script-2.0.21}/setup.cfg +0 -0
  331. {ok_script-2.0.20 → ok_script-2.0.21}/setup.py +0 -0
  332. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_accent_color.py +0 -0
  333. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_app.py +0 -0
  334. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_base_interaction.py +0 -0
  335. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_blur.py +0 -0
  336. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_box.py +0 -0
  337. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_capture_update.py +0 -0
  338. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_cli.py +0 -0
  339. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_config_card.py +0 -0
  340. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_core_events.py +0 -0
  341. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_core_ui_services.py +0 -0
  342. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_debug_job_controls.py +0 -0
  343. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_device_manager.py +0 -0
  344. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_executor_jobs.py +0 -0
  345. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_expand_card_layout.py +0 -0
  346. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_explorer.py +0 -0
  347. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_global_config.py +0 -0
  348. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_gpu_driver_settings.py +0 -0
  349. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_handler.py +0 -0
  350. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_headless_imports.py +0 -0
  351. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_inline_ok_requirements.py +0 -0
  352. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_logger.py +0 -0
  353. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_main_window_notifications.py +0 -0
  354. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_main_window_startup_tab.py +0 -0
  355. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_main_window_system_theme.py +0 -0
  356. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_mcp_runner.py +0 -0
  357. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_mcp_setting.py +0 -0
  358. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_missing_frame.py +0 -0
  359. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_mouse_buttons.py +0 -0
  360. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_navigation_state.py +0 -0
  361. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_nemu_ipc_capture.py +0 -0
  362. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_notifications.py +0 -0
  363. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_ok_mutex.py +0 -0
  364. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_overlay_custom_draw.py +0 -0
  365. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_overlay_lifecycle.py +0 -0
  366. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_package_metadata.py +0 -0
  367. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_plugin_staging.py +0 -0
  368. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_plugin_tabs.py +0 -0
  369. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_plugins.py +0 -0
  370. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_process.py +0 -0
  371. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_process_mutex.py +0 -0
  372. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_pyappify_startup_update.py +0 -0
  373. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_qt_events.py +0 -0
  374. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_qt_legacy_imports.py +0 -0
  375. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_qt_page_layout.py +0 -0
  376. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_runtime_startup.py +0 -0
  377. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_schedule_startup.py +0 -0
  378. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_schedule_task_index_sync.py +0 -0
  379. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_screenshot_naming.py +0 -0
  380. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_screenshot_rendering.py +0 -0
  381. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_skills.py +0 -0
  382. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_start_controller.py +0 -0
  383. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_start_tab_overlay.py +0 -0
  384. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_task_executor_next_frame.py +0 -0
  385. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_task_executor_queue.py +0 -0
  386. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_task_reloading.py +0 -0
  387. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_task_ui.py +0 -0
  388. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_template_compression.py +0 -0
  389. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_template_generation.py +0 -0
  390. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_template_tab.py +0 -0
  391. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_touch_scroll.py +0 -0
  392. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_translation_fallback.py +0 -0
  393. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_ui_config.py +0 -0
  394. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_version.py +0 -0
  395. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_windows_graphics_capture.py +0 -0
  396. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_windows_graphics_hdr.py +0 -0
  397. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_windows_schedule_target_parse.py +0 -0
  398. {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_windows_schedule_xml.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ok-script
3
- Version: 2.0.20
3
+ Version: 2.0.21
4
4
  Summary: Automation with Computer Vision for Python
5
5
  Author-email: ok-oldking <firedcto@gmail.com>
6
6
  License: ok-script License
@@ -72,6 +72,15 @@ Applications using `OK(config).start()` can enable MCP through their config:
72
72
  "mcp": {"enabled": True, "port": 8765, "artifact_directory": "logs/mcp"}
73
73
  ```
74
74
 
75
+ MCP artifacts use a seven-day retention period by default. Set `mcp.retention_days`
76
+ to a nonnegative number of days, or `0` to disable cleanup. Cleanup runs on startup
77
+ and at most once per hour when submitting runs/reloads or requesting screenshots.
78
+ It removes expired completed run directories (including their logs, status and
79
+ custom artifacts), their in-memory status records, and old observation images.
80
+ Run age is measured from `finished_at`; observation age uses the image file's
81
+ modification time. Active/nonterminal runs, unrecognized files and connection
82
+ records are preserved. Copy evidence elsewhere if it must outlive retention.
83
+
75
84
  The server binds only to IPv4 loopback and validates HTTP Host and Origin.
76
85
  Each instance writes `logs/mcp/connections/<pid>-<port>.json` containing its URL,
77
86
  bearer token, process ID, process start time and project directory. These records
@@ -143,7 +152,7 @@ Clients supporting MCP resources can also discover these skills through
143
152
 
144
153
  States are `queued`, `running`, `stopping`, `succeeded`, `failed`, and
145
154
  `cancelled`. Only the last three are terminal. Run IDs are scoped to the current
146
- process; saved artifacts remain available after restarting it.
155
+ process; saved artifacts remain available after restarting it until retention cleanup.
147
156
 
148
157
  ## Preferred workflow for existing tasks
149
158
 
@@ -24,7 +24,7 @@ from typing import Any
24
24
 
25
25
  from ok.task.exceptions import TaskDisabledException
26
26
 
27
- _log_formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s', datefmt='%H:%M:%S')
27
+ _log_formatter = logging.Formatter('%(asctime)s.%(msecs)03d %(levelname)s %(message)s', datefmt='%H:%M:%S')
28
28
  _capture_lock = threading.Lock()
29
29
  _run_ids = itertools.count(1)
30
30
 
@@ -4,6 +4,9 @@ import hashlib
4
4
  import importlib.util
5
5
  import inspect
6
6
  import json
7
+ import math
8
+ import re
9
+ import shutil
7
10
  import sys
8
11
  import threading
9
12
  import time
@@ -15,9 +18,11 @@ from pathlib import Path
15
18
  from ok.core.code_runner import capture_output
16
19
  from ok.task.exceptions import TaskDisabledException
17
20
  from ok.task.task import BaseTask, TriggerTask
21
+ from ok.util.logger import Logger
18
22
 
19
23
 
20
24
  TERMINAL_STATES = {'succeeded', 'failed', 'cancelled'}
25
+ logger = Logger.get_logger(__name__)
21
26
 
22
27
 
23
28
  class McpTaskRunner:
@@ -30,7 +35,10 @@ class McpTaskRunner:
30
35
  """
31
36
 
32
37
  def __init__(self, executor, app, screenshot_service, artifact_directory='logs/mcp', prepare=None,
33
- task_manager=None):
38
+ task_manager=None, retention_days=7):
39
+ if (isinstance(retention_days, bool) or not isinstance(retention_days, (int, float))
40
+ or not math.isfinite(retention_days) or retention_days < 0):
41
+ raise ValueError('mcp.retention_days must be a finite nonnegative number')
34
42
  self.executor = executor
35
43
  self.app = app
36
44
  self.screenshot_service = screenshot_service
@@ -42,8 +50,63 @@ class McpTaskRunner:
42
50
  self._active = None
43
51
  self._observation_job = None
44
52
  self._closed = False
53
+ self.retention_days = retention_days
54
+ self._next_cleanup = 0
55
+ self._cleanup_artifacts()
45
56
  self.executor.enable_frame_snapshots()
46
57
 
58
+ def _cleanup_artifacts(self):
59
+ """Expire completed runs and observations, at most hourly during activity."""
60
+ with self._lock:
61
+ now = time.monotonic()
62
+ if self.retention_days == 0 or now < self._next_cleanup:
63
+ return
64
+ self._next_cleanup = now + 3600
65
+ cutoff = time.time() - self.retention_days * 86400
66
+ try:
67
+ paths = list(self.artifact_directory.iterdir())
68
+ except FileNotFoundError:
69
+ return
70
+ except OSError as error:
71
+ logger.warning(f'Could not scan MCP artifacts: {error}')
72
+ return
73
+ for path in paths:
74
+ try:
75
+ # Only operate on framework-owned entries inside this root.
76
+ # In particular, never follow directory links or junctions.
77
+ if path.is_symlink() or path.resolve() != path:
78
+ continue
79
+ if path.name == 'observations' and path.is_dir():
80
+ for image in path.iterdir():
81
+ if (re.fullmatch(r'[0-9a-f]{32}\.png', image.name)
82
+ and not image.is_symlink() and image.is_file()
83
+ and image.resolve() == image
84
+ and image.stat().st_mtime < cutoff):
85
+ try:
86
+ image.unlink()
87
+ except OSError as error:
88
+ logger.warning(f'Could not remove MCP observation {image}: {error}')
89
+ elif (re.fullmatch(r'[0-9a-f]{32}', path.name) and path.is_dir()
90
+ and path.name != self._active):
91
+ try:
92
+ record = json.loads((path / 'status.json').read_text(encoding='utf-8'))
93
+ except (ValueError, UnicodeError):
94
+ continue
95
+ if not isinstance(record, dict):
96
+ continue
97
+ finished = record.get('finished_at')
98
+ if (record.get('run_id') == path.name
99
+ and isinstance(record.get('state'), str)
100
+ and record['state'] in TERMINAL_STATES
101
+ and isinstance(finished, (int, float)) and not isinstance(finished, bool)
102
+ and math.isfinite(finished) and finished < cutoff):
103
+ shutil.rmtree(path)
104
+ self._runs.pop(path.name, None)
105
+ except FileNotFoundError:
106
+ pass # Another server sharing this directory may have removed it.
107
+ except OSError as error:
108
+ logger.warning(f'Could not clean MCP artifacts {path}: {error}')
109
+
47
110
  def _save(self, record):
48
111
  path = Path(record['artifact_directory']) / 'status.json'
49
112
  temporary = path.with_suffix('.tmp')
@@ -135,6 +198,7 @@ class McpTaskRunner:
135
198
  raise RuntimeError('MCP task runner is shutting down')
136
199
  if self._active is not None:
137
200
  raise RuntimeError(f'Run {self._active} is still active; wait until it has stopped')
201
+ self._cleanup_artifacts()
138
202
  run_id = uuid.uuid4().hex
139
203
  owner = f'mcp:{run_id}'
140
204
  self.executor.acquire_exclusive(owner)
@@ -282,6 +346,7 @@ class McpTaskRunner:
282
346
  with self._lock:
283
347
  if self._closed:
284
348
  raise RuntimeError('MCP task runner is shutting down')
349
+ self._cleanup_artifacts()
285
350
  if (self._active is None and self._observation_job is None and self.executor.current_job is None
286
351
  and self.executor.current_task is None and self.executor.exclusive_owner is None):
287
352
  def capture():
@@ -232,7 +232,7 @@ class LocalMcpServer:
232
232
  app = runtime.headless_app if runtime.should_init_task_manager_headless() else runtime.app
233
233
  self.runner = McpTaskRunner(runtime.task_executor, app, runtime.screenshot,
234
234
  config.get('artifact_directory', 'logs/mcp'), self._prepare_device,
235
- task_manager=og.task_manager)
235
+ task_manager=og.task_manager, retention_days=config.get('retention_days', 7))
236
236
  self._server = None
237
237
  self._socket = None
238
238
  self._thread = None
@@ -289,9 +289,11 @@ class LocalMcpServer:
289
289
  self.port = self._socket.getsockname()[1]
290
290
  app = create_mcp_app(self.runner, self.token, self.port, self.exit_process)
291
291
  import uvicorn
292
+ # uvicorn's default log_config goes through dictConfig, which closes every
293
+ # existing handler in the process, including the ok-script.log file handler.
292
294
  self._server = uvicorn.Server(uvicorn.Config(
293
295
  app, host='127.0.0.1', port=self.port, log_level='warning', access_log=False,
294
- timeout_graceful_shutdown=5))
296
+ log_config=None, timeout_graceful_shutdown=5))
295
297
 
296
298
  def serve():
297
299
  try:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ok-script
3
- Version: 2.0.20
3
+ Version: 2.0.21
4
4
  Summary: Automation with Computer Vision for Python
5
5
  Author-email: ok-oldking <firedcto@gmail.com>
6
6
  License: ok-script License
@@ -349,6 +349,7 @@ tests/test_logger.py
349
349
  tests/test_main_window_notifications.py
350
350
  tests/test_main_window_startup_tab.py
351
351
  tests/test_main_window_system_theme.py
352
+ tests/test_mcp_cleanup.py
352
353
  tests/test_mcp_runner.py
353
354
  tests/test_mcp_server.py
354
355
  tests/test_mcp_setting.py
@@ -0,0 +1,166 @@
1
+ import json
2
+ import os
3
+ import time
4
+ import uuid
5
+ from pathlib import Path
6
+ from unittest.mock import Mock
7
+
8
+ import pytest
9
+
10
+ from ok.core.mcp_runner import McpTaskRunner
11
+ from test_mcp_runner import development, wait_run, write_task
12
+
13
+
14
+ def saved_run(root, *, state='succeeded', age=8, **fields):
15
+ path = root / uuid.uuid4().hex
16
+ path.mkdir(parents=True)
17
+ record = {'run_id': path.name, 'state': state, 'finished_at': time.time() - age * 86400, **fields}
18
+ (path / 'status.json').write_text(json.dumps(record), encoding='utf-8')
19
+ (path / 'run.log').write_text('evidence', encoding='utf-8')
20
+ (path / 'custom').mkdir()
21
+ (path / 'custom' / 'image.png').write_bytes(b'custom evidence')
22
+ return path
23
+
24
+
25
+ def observation(root, age):
26
+ path = root / 'observations' / f'{uuid.uuid4().hex}.png'
27
+ path.parent.mkdir(parents=True, exist_ok=True)
28
+ path.write_bytes(b'image')
29
+ modified = time.time() - age * 86400
30
+ os.utime(path, (modified, modified))
31
+ return path
32
+
33
+
34
+ def runner_at(root, **kwargs):
35
+ return McpTaskRunner(Mock(), Mock(), Mock(), root, **kwargs)
36
+
37
+
38
+ def test_startup_expires_completed_runs_and_observations_only(tmp_path):
39
+ old = [saved_run(tmp_path, state=state) for state in ('succeeded', 'failed', 'cancelled')]
40
+ recent = saved_run(tmp_path, age=1)
41
+ unfinished = [saved_run(tmp_path, state=state) for state in ('queued', 'running', 'stopping')]
42
+ old_image = observation(tmp_path, 8)
43
+ recent_image = observation(tmp_path, 1)
44
+ connections = tmp_path / 'connections'
45
+ connections.mkdir()
46
+ connection = connections / '1-8765.json'
47
+ connection.write_text('{}')
48
+ compatibility = tmp_path / 'connection.json'
49
+ compatibility.write_text('{}')
50
+ unknown = tmp_path / 'notes'
51
+ unknown.mkdir()
52
+ (unknown / 'notes.txt').write_text('keep')
53
+ unknown_image = tmp_path / 'observations' / 'manual.png'
54
+ unknown_image.write_bytes(b'keep')
55
+ os.utime(unknown_image, (0, 0))
56
+
57
+ runner_at(tmp_path)
58
+
59
+ assert all(not path.exists() for path in old)
60
+ assert not old_image.exists()
61
+ assert all(path.exists() for path in [recent, *unfinished, recent_image, connection,
62
+ compatibility, unknown / 'notes.txt', unknown_image])
63
+
64
+
65
+ @pytest.mark.parametrize('contents', ['{', '[]', '{"state": []}', None])
66
+ def test_cleanup_preserves_incomplete_or_malformed_run_records(tmp_path, contents):
67
+ path = saved_run(tmp_path)
68
+ status = path / 'status.json'
69
+ if contents is None:
70
+ status.unlink()
71
+ else:
72
+ status.write_text(contents)
73
+ runner_at(tmp_path)
74
+ assert (path / 'run.log').exists()
75
+
76
+
77
+ @pytest.mark.parametrize('fields', [{'state': []}, {'finished_at': None}, {'finished_at': '0'},
78
+ {'finished_at': float('nan')}, {'run_id': 'different'}])
79
+ def test_cleanup_preserves_unusable_status_metadata(tmp_path, fields):
80
+ path = saved_run(tmp_path, **fields)
81
+ runner_at(tmp_path)
82
+ assert (path / 'run.log').exists()
83
+
84
+
85
+ def test_retention_can_be_configured_or_disabled(tmp_path):
86
+ path = saved_run(tmp_path, age=3)
87
+ image = observation(tmp_path, 3)
88
+ runner_at(tmp_path, retention_days=0)
89
+ assert path.exists() and image.exists()
90
+ runner_at(tmp_path, retention_days=2)
91
+ assert not path.exists() and not image.exists()
92
+
93
+
94
+ @pytest.mark.parametrize('retention', [-1, None, '7', True, float('nan'), float('inf')])
95
+ def test_invalid_retention_is_rejected(tmp_path, retention):
96
+ with pytest.raises(ValueError, match='retention_days'):
97
+ runner_at(tmp_path, retention_days=retention)
98
+
99
+
100
+ def test_cleanup_during_activity_is_throttled_and_expires_memory(development):
101
+ dev = development
102
+ task = write_task(dev.root)
103
+ status = wait_run(dev.runner, dev.runner.run_task(str(task))['run_id'])
104
+ run_id = status['run_id']
105
+ record = dev.runner._runs[run_id]['record']
106
+ record['finished_at'] = time.time() - 8 * 86400
107
+ dev.runner._save(record)
108
+ path = Path(status['artifact_directory'])
109
+
110
+ # The startup sweep already ran, so requests within the hour leave it alone.
111
+ dev.runner.screenshot()
112
+ assert path.exists()
113
+ dev.runner._next_cleanup = 0
114
+ dev.runner.screenshot()
115
+ assert not path.exists()
116
+ with pytest.raises(ValueError, match='Unknown run_id'):
117
+ dev.runner.get_run_status(run_id)
118
+
119
+ old = saved_run(dev.runner.artifact_directory)
120
+ dev.runner._next_cleanup = 0
121
+ assert wait_run(dev.runner, dev.runner.run_task(str(task))['run_id'])['state'] == 'succeeded'
122
+ assert not old.exists()
123
+
124
+
125
+ def test_cleanup_preserves_active_run_even_with_old_terminal_record(development):
126
+ dev = development
127
+ task = write_task(dev.root, 'self.executor.next_frame(); self._app.started.set(); self._app.release.wait(3)')
128
+ run_id = dev.runner.run_task(str(task))['run_id']
129
+ assert dev.app.started.wait(2)
130
+ path = Path(dev.runner.get_run_status(run_id)['artifact_directory'])
131
+ (path / 'status.json').write_text(json.dumps({
132
+ 'run_id': run_id, 'state': 'succeeded', 'finished_at': 0}))
133
+ dev.runner._next_cleanup = 0
134
+ dev.runner.screenshot()
135
+ assert (path / 'run.log').exists()
136
+ dev.app.release.set()
137
+ assert wait_run(dev.runner, run_id)['state'] == 'succeeded'
138
+
139
+
140
+ def test_cleanup_failure_does_not_block_other_artifacts(tmp_path, monkeypatch):
141
+ path = saved_run(tmp_path)
142
+ image = observation(tmp_path, 8)
143
+ monkeypatch.setattr('ok.core.mcp_runner.shutil.rmtree', Mock(side_effect=PermissionError('locked')))
144
+ runner_at(tmp_path)
145
+ assert path.exists()
146
+ assert not image.exists()
147
+
148
+
149
+ def test_cleanup_does_not_follow_directory_junctions(tmp_path):
150
+ import subprocess
151
+
152
+ if os.name != 'nt':
153
+ pytest.skip('Windows directory junctions')
154
+ outside = tmp_path / 'outside'
155
+ saved = saved_run(outside)
156
+ observation(outside, 8)
157
+ root = tmp_path / 'mcp'
158
+ root.mkdir()
159
+ # A junction needs no elevated symlink privilege on Windows.
160
+ for link, target in [(root / saved.name, saved), (root / 'observations', outside / 'observations')]:
161
+ result = subprocess.run(['cmd', '/c', 'mklink', '/J', str(link), str(target)],
162
+ capture_output=True, text=True)
163
+ assert result.returncode == 0, result.stderr
164
+ runner_at(root)
165
+ assert (saved / 'run.log').exists()
166
+ assert len(list((outside / 'observations').glob('*.png'))) == 1
@@ -1,6 +1,7 @@
1
1
  import asyncio
2
2
  import json
3
3
  import errno
4
+ import logging
4
5
  import os
5
6
  import socket
6
7
  import threading
@@ -12,9 +13,20 @@ import pytest
12
13
 
13
14
  from ok.core.mcp_server import LocalMcpServer, bind_mcp_socket, create_mcp_app, discover_mcp_servers
14
15
  from ok.util.handler import ExitEvent
16
+ from ok.util.logger import SafeFileHandler
15
17
  from test_mcp_runner import development, write_task, register_task
16
18
 
17
19
 
20
+ @pytest.mark.parametrize('options,expected', [({}, 7), ({'retention_days': 0}, 0), ({'retention_days': 2}, 2)])
21
+ def test_server_passes_artifact_retention_to_runner(development, options, expected):
22
+ dev = development
23
+ runtime = SimpleNamespace(task_executor=dev.executor, screenshot=dev.screens,
24
+ headless_app=dev.app, should_init_task_manager_headless=lambda: True)
25
+ server = LocalMcpServer(runtime, {'artifact_directory': str(dev.root / 'http'), **options})
26
+ assert server.runner.retention_days == expected
27
+ server.runner.close()
28
+
29
+
18
30
  @pytest.mark.parametrize('mode', ['auto', 'legacy'])
19
31
  def test_http_connection_and_tools_end_to_end(development, mode, monkeypatch):
20
32
  pytest.importorskip('mcp')
@@ -134,6 +146,29 @@ def test_http_connection_and_tools_end_to_end(development, mode, monkeypatch):
134
146
  assert discover_mcp_servers(dev.root, artifact_directory='http') == []
135
147
 
136
148
 
149
+ def test_start_keeps_existing_file_log_open(development, tmp_path):
150
+ pytest.importorskip('mcp')
151
+ dev = development
152
+ runtime = SimpleNamespace(task_executor=dev.executor, screenshot=dev.screens,
153
+ headless_app=dev.app, exit_event=ExitEvent(),
154
+ should_init_task_manager_headless=lambda: True)
155
+ server = LocalMcpServer(runtime, {'port': 0, 'artifact_directory': str(dev.root / 'http')})
156
+ # The application configures ok-script.log before it starts the MCP server.
157
+ log_file = tmp_path / 'ok-script.log'
158
+ handler = SafeFileHandler(log_file, when='midnight', encoding='utf-8')
159
+ ok_logger = logging.getLogger('ok')
160
+ ok_logger.addHandler(handler)
161
+ try:
162
+ server.start()
163
+ ok_logger.warning('logged after MCP start')
164
+ finally:
165
+ ok_logger.removeHandler(handler)
166
+ handler.close()
167
+ server.stop()
168
+ server.join()
169
+ assert 'logged after MCP start' in log_file.read_text(encoding='utf-8')
170
+
171
+
137
172
  def test_exit_request_is_idempotent_and_does_not_wait_for_runner(development, monkeypatch):
138
173
  dev = development
139
174
  runtime = SimpleNamespace(task_executor=dev.executor, screenshot=dev.screens,
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