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.
- {ok_script-2.0.20 → ok_script-2.0.21}/PKG-INFO +1 -1
- {ok_script-2.0.20 → ok_script-2.0.21}/docs/mcp.md +10 -1
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/code_runner.py +1 -1
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/mcp_runner.py +66 -1
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/mcp_server.py +4 -2
- {ok_script-2.0.20 → ok_script-2.0.21}/ok_script.egg-info/PKG-INFO +1 -1
- {ok_script-2.0.20 → ok_script-2.0.21}/ok_script.egg-info/SOURCES.txt +1 -0
- ok_script-2.0.21/tests/test_mcp_cleanup.py +166 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_mcp_server.py +35 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/.agents/skills/develop/SKILL.md +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/.agents/skills/develop/agents/openai.yaml +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/.agents/skills/develop/references/windows-input.md +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/.agents/skills/generate-templates/SKILL.md +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/.agents/skills/reload-tasks/SKILL.md +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/.agents/skills/reload-tasks/agents/openai.yaml +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/.agents/skills/setup-env/SKILL.md +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/.agents/skills/setup-env/agents/openai.yaml +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/.agents/skills/setup-env/assets/ok-script-develop/SKILL.md +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/.agents/skills/setup-project/SKILL.md +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/.agents/skills/setup-project/agents/openai.yaml +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/.agents/skills/use_venv/SKILL.md +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/.agents/skills/write-test/SKILL.md +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/.agents/skills/write-test/agents/openai.yaml +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/LICENSE.txt +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/MANIFEST.in +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/README.md +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/diagnostics/qq_trace.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/docs/agent-setup.md +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/docs/feature-crops.md +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/examples/mcp_observe.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/__main__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/alas/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/alas/emulator_base.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/alas/emulator_windows.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/alas/platform_windows.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/alas/utils.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/assets/default-icon.png +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/capture/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/capture/adb/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/capture/adb/deep.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/capture/adb/minitouch.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/capture/adb/nemu_ipc.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/capture/adb/nemu_utils.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/capture/adb/pool.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/capture/adb/timer.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/capture/adb/util.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/capture/windows/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/capture/windows/d3d11.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/capture/windows/dump.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/cli.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/capture_labels.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/config_schema.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/downloads.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/events.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/icons.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/notifications.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/plugin_staging.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/plugins.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/screenshot.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/script_editing.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/script_packager.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/script_templates.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/skills.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/start_controller.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/task_manager.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/template_store.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/translation.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/core/ui_config.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/DeviceManager.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/capture.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/capture_methods/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/capture_methods/adb.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/capture_methods/base.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/capture_methods/bitblt.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/capture_methods/bitblt_utils.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/capture_methods/browser.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/capture_methods/desktop_duplication.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/capture_methods/hwnd_window.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/capture_methods/image.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/capture_methods/nemu_ipc.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/capture_methods/types.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/capture_methods/update.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/capture_methods/windows_graphics.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/capture_methods/windows_graphics_hdr.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/interaction.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/interaction_methods/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/interaction_methods/adb.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/interaction_methods/base.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/interaction_methods/browser.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/interaction_methods/do_nothing.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/interaction_methods/foreground_post_message.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/interaction_methods/genshin.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/interaction_methods/keys.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/interaction_methods/post_message.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/interaction_methods/pydirect.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/interaction_methods/pynput.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/interaction_methods/swipe.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/device/intercation.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/feature/Box.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/feature/Feature.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/feature/FeatureSet.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/feature/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/gui/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/gui/util/pyappify_startup.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/notification/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/notification/manager.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/notification/messenger_images.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/notification/pipeline.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/notification/ppocr.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/notification/providers.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/notification/system.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/notification/windows_messenger.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ocr/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ocr/download_paddle_model.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/Foundation/Collections.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/Foundation/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/Globalization/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/Graphics/Capture/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/Graphics/DirectX/Direct3D11/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/Graphics/DirectX/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/Graphics/Imaging/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/Graphics/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/Media/Ocr/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/Media/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/Security/Cryptography/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/Security/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/Storage/Streams/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/Storage/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/UI/ViewManagement/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/UI/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/Windows/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/delegate.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/export.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/idldsl.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/inspectable.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/roapi.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/types.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/rotypes/winstring.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/task/DiagnosisTask.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/task/TaskExecutor.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/task/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/task/exceptions.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/task/task.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/test/OKTestRunner.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/test/RunTests.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/test/TaskTestCase.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/test/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/third_party/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/third_party/paperclip.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/overlay/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/overlay/win32_gdi.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/Communicate.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/MainWindow.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/MessageWindow.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/StartController.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/TabTitles.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/about/AboutTab.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/about/HttpTextEdit.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/about/LinksBar.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/about/ProjectCard.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/about/UpdateCard.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/about/VersionCard.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/about/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/common/OKIcon.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/common/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/common/accent_color.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/common/config.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/common/design_system.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/common/icons.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/common/style_sheet.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/debug/DebugTab.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/debug/JobControls.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/debug/OverlayWidget.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/debug/RunCodeTab.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/debug/Screenshot.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/debug/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/events.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/i18n/GettextTranslator.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/i18n/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/i18n/en_US.qm +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/i18n/en_US.ts +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/i18n/es_ES.qm +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/i18n/es_ES.ts +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/i18n/ja_JP.qm +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/i18n/ja_JP.ts +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/i18n/ko_KR.qm +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/i18n/ko_KR.ts +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/i18n/path.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/i18n/update.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/i18n/zh_CN.qm +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/i18n/zh_CN.ts +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/i18n/zh_TW.qm +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/i18n/zh_TW.ts +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/overlay/OverlayWindow.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/overlay/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/plugins/PluginTab.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/plugins/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/plugins/imported_tabs.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/dark/card.qss +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/dark/discord.svg +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/dark/heart.svg +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/dark/message_window.qss +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/dark/setting_interface.qss +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/dark/status_bar.qss +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/dark/stop.svg +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/dark/tab.qss +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/light/card.qss +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/light/discord.svg +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/light/heart.svg +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/light/message_window.qss +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/light/setting_interface.qss +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/light/status_bar.qss +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/light/stop.svg +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qss/light/tab.qss +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/qt.qrc +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/resources.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/settings/GlobalConfigCard.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/settings/GlobalConfigTab.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/settings/SettingTab.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/settings/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/start/LogWindow.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/start/SelectCaptureListView.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/start/SelectInteractionListView.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/start/StartCard.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/start/StartTab.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/start/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/ConfigCard.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/ConfigItemFactory.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/ConfigLabelAndWidget.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/EditTaskTab.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/LabelAndButtons.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/LabelAndDoubleSpinBox.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/LabelAndDropDown.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/LabelAndFileSelector.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/LabelAndGlobal.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/LabelAndLineEdit.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/LabelAndList.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/LabelAndMcpServer.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/LabelAndMultiSelection.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/LabelAndSpinBox.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/LabelAndSwitchButton.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/LabelAndTextEdit.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/LabelAndWidget.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/MarkUpWindow.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/ModifyListDialog.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/ModifyListItem.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/OneTimeTaskTab.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/PythonHighlighter.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/RecordScript.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/ScheduleTaskTab.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/ScriptPackager.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/StartButton.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/TaskCard.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/TaskManger.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/TaskOpButton.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/TaskTab.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/TemplateFactory.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/TemplateTab.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/TooltipTableWidget.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/TriggerTaskTab.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/tasks/schedule_index_sync.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/util/Alert.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/util/InitWorker.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/util/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/util/app.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/util/download.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/util/pip_util.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/util/pyappify_startup.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/util/touch_scroll.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/util/windows_thumbnail.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/BaseLoading.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/BaseWindow.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/Card.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/CustomTab.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/EmptyCard.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/ExpandCardLayout.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/FlowLayout.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/FramelessDialog.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/ImageWidget.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/ImportWarningDialog.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/ListTableWidgetItem.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/NumericTableWidgetItem.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/OkCompactSpinBox.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/SortingTableWidgetItem.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/StartLoadingDialog.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/StatusBar.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/Tab.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/TabWidget.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/UpdateConfigWidgetItem.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/WidgetWithVLabel.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/YesNonWidgetItem.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/ui/qt/widget/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/update/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/update/copy_ok_folder.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/update/inline_ok_requirements.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/Analytics.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/GlobalConfig.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/__init__.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/blur.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/clazz.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/collection.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/color.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/config.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/explorer.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/file.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/gpu_driver_settings.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/handler.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/i18n.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/logger.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/partial_sync_repo.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/print_hwnd.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/process.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/version.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/window.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok/util/windows_schedule.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok_script.egg-info/dependency_links.txt +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok_script.egg-info/entry_points.txt +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok_script.egg-info/not-zip-safe +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok_script.egg-info/requires.txt +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/ok_script.egg-info/top_level.txt +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/pyproject.toml +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/setup.cfg +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/setup.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_accent_color.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_app.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_base_interaction.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_blur.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_box.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_capture_update.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_cli.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_config_card.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_core_events.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_core_ui_services.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_debug_job_controls.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_device_manager.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_executor_jobs.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_expand_card_layout.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_explorer.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_global_config.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_gpu_driver_settings.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_handler.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_headless_imports.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_inline_ok_requirements.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_logger.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_main_window_notifications.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_main_window_startup_tab.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_main_window_system_theme.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_mcp_runner.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_mcp_setting.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_missing_frame.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_mouse_buttons.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_navigation_state.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_nemu_ipc_capture.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_notifications.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_ok_mutex.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_overlay_custom_draw.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_overlay_lifecycle.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_package_metadata.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_plugin_staging.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_plugin_tabs.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_plugins.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_process.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_process_mutex.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_pyappify_startup_update.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_qt_events.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_qt_legacy_imports.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_qt_page_layout.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_runtime_startup.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_schedule_startup.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_schedule_task_index_sync.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_screenshot_naming.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_screenshot_rendering.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_skills.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_start_controller.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_start_tab_overlay.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_task_executor_next_frame.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_task_executor_queue.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_task_reloading.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_task_ui.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_template_compression.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_template_generation.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_template_tab.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_touch_scroll.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_translation_fallback.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_ui_config.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_version.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_windows_graphics_capture.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_windows_graphics_hdr.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_windows_schedule_target_parse.py +0 -0
- {ok_script-2.0.20 → ok_script-2.0.21}/tests/test_windows_schedule_xml.py +0 -0
|
@@ -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:
|
|
@@ -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
|
{ok_script-2.0.20 → ok_script-2.0.21}/.agents/skills/setup-env/assets/ok-script-develop/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|