mcpywrap 0.4.1__tar.gz → 0.4.2__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.
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/CHANGELOG.md +9 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/MANIFEST.in +2 -0
- {mcpywrap-0.4.1/mcpywrap.egg-info → mcpywrap-0.4.2}/PKG-INFO +5 -4
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/README.md +4 -3
- mcpywrap-0.4.2/docs/runtime-input.md +15 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/docs/runtime-player.md +8 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/__init__.py +1 -1
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/cli.py +1 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/commands/runtime_cmd.py +5 -0
- mcpywrap-0.4.2/mcpywrap/commands/runtime_input_cmd.py +86 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/commands/runtime_player_cmd.py +14 -1
- mcpywrap-0.4.2/mcpywrap/input_executor.py +246 -0
- mcpywrap-0.4.2/mcpywrap/input_plan.py +257 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/bridge_payload/Injector.exe +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/bridge_payload/Loader.dll +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/bridge_payload/McpyMcsAuth.dll +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/bridge_payload/manifest.json +3 -3
- mcpywrap-0.4.2/mcpywrap/mcstudio/host_input.py +252 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/runtime_debug.py +9 -1
- mcpywrap-0.4.2/mcpywrap/mcstudio/runtime_input_payload.py +277 -0
- mcpywrap-0.4.2/mcpywrap/mcstudio/runtime_key_timeline_payload.py +229 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/runtime_player_payload.py +36 -6
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/runtime_ui.py +81 -13
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/runtime_ui_payload.py +29 -2
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/session_worker.py +2 -1
- mcpywrap-0.4.2/mcpywrap/mcstudio/unified_input.py +39 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/window.py +22 -1
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/window_capture/manifest.json +1 -1
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/window_capture/mcpy-window-capture.exe +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/remote/http_server.py +2 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/remote/service.py +7 -1
- {mcpywrap-0.4.1 → mcpywrap-0.4.2/mcpywrap.egg-info}/PKG-INFO +5 -4
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap.egg-info/SOURCES.txt +22 -0
- mcpywrap-0.4.2/native/runtime_input/README.md +11 -0
- mcpywrap-0.4.2/native/runtime_input/launcher-monotonic.patch +59 -0
- mcpywrap-0.4.2/native/runtime_input/steady_clock.h +11 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/pyproject.toml +1 -1
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/scripts/check_release.py +8 -1
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/skills/mcpywrap/SKILL.md +23 -59
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/skills/mcpywrap/references/input-sequence.md +5 -0
- mcpywrap-0.4.2/skills/mcpywrap/references/input-writing.md +213 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/skills/mcpywrap/references/local-development.md +1 -1
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/skills/mcpywrap/references/remote-testing.md +7 -6
- mcpywrap-0.4.2/skills/mcpywrap/references/runtime-input.md +134 -0
- mcpywrap-0.4.2/skills/mcpywrap/references/runtime-key-timeline.md +78 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/skills/mcpywrap/references/runtime-player.md +4 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/skills/mcpywrap/references/runtime-ui.md +2 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/skills/mcpywrap/references/troubleshooting.md +2 -2
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/skills/mcpywrap/scripts/bootstrap.py +11 -2
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/manual/runtime_controls/README.md +4 -0
- mcpywrap-0.4.2/tests/manual/runtime_controls/input_probe.py +66 -0
- mcpywrap-0.4.2/tests/manual/runtime_controls/key_timeline_probe.py +104 -0
- mcpywrap-0.4.2/tests/manual_runtime_input_unified.py +198 -0
- mcpywrap-0.4.2/tests/manual_runtime_key_timeline.py +271 -0
- mcpywrap-0.4.2/tests/test_host_input.py +199 -0
- mcpywrap-0.4.2/tests/test_runtime_input.py +350 -0
- mcpywrap-0.4.2/tests/test_runtime_key_timeline.py +416 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_runtime_ui.py +35 -2
- mcpywrap-0.4.2/tests/test_unified_skill_contract.py +52 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/LICENSE +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/THIRD_PARTY_NOTICES.md +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/docs/code-libraries.md +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/docs/development.md +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/docs/engine-discovery.md +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/docs/game-backends.md +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/docs/git-dependencies.md +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/docs/input-sequence.md +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/docs/launcher-client-python.md +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/docs/local-dependencies.md +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/docs/macos-resource-reload.md +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/docs/mcs-auth.md +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/docs/qt6-development.md +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/docs/qumod.md +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/docs/release-0.4.0.md +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/docs/release-readiness-0.4.0.md +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/docs/releasing.md +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/docs/runtime-debug.md +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/docs/runtime-ui.md +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/docs/shared-log-ui.md +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/__main__.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/builders/AddonsPack.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/builders/MapPack.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/builders/__init__.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/builders/assembly.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/builders/dependency_manager.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/builders/file_merge.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/builders/project_builder.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/builders/watcher.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/code_libraries.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/command_context.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/commands/__init__.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/commands/add_cmd.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/commands/build_cmd.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/commands/connect_cmd.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/commands/default_cmd.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/commands/dependency_prompt.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/commands/desktop_cmd.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/commands/dev_cmd.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/commands/doctor_cmd.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/commands/edit_cmd.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/commands/engine_cmd.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/commands/init_cmd.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/commands/mod_cmd.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/commands/modsdk_cmd.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/commands/package_cmd.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/commands/publish_cmd.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/commands/record_cmd.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/commands/remove_cmd.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/commands/run_cmd.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/commands/runtime_ui_cmd.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/commands/serve_cmd.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/commands/session_cmd.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/commands/sync_cmd.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/commands/ui_cmd.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/config.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/custom_packaging.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/dependencies.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/engines/__init__.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/engines/apk_profile.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/engines/backend.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/engines/host.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/engines/install.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/engines/macos.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/engines/official.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/engines/preferences.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/engines/tui.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/engines/windows.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/framework_presets.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/frameworks.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/git_cache.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/git_projects.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/SimpleMonitor.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/__init__.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/auth_interaction.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/bridge_assets.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/bridge_payload/publisher.cer +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/diagnostics.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/discovery.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/editor.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/file_logs.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/game.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/hot_reload.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/input_sequence.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/launcher_python.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/log_colorizer.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/log_protocol.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/mcs.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/mcs_auth.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/network.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/private_logs.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/processes.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/recording_worker.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/recordings.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/runtime_cppconfig.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/runtime_ui_outline.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/session_capabilities.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/sessions.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/studio_server.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/studio_server_ui.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/symlink_helper_global.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/symlink_helper_map.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/mcstudio/symlinks.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/minecraft/__init__.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/minecraft/addons.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/minecraft/level_dat.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/minecraft/map.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/minecraft/netease_modsdk.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/minecraft/template/generate_mod_files.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/minecraft/template/mod_template.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/project_dependencies.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/project_init.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/recording_io.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/remote/__init__.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/remote/client.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/source_files.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/timeline.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/ui/__init__.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/ui/engine_setup.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/ui/log_view.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/ui/log_window.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/ui/project_ui.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/ui/run_session.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/ui/session_controller.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/ui/session_tools.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/ui/session_views.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/ui/world_config.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/utils/__init__.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/utils/pip_error_parser.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/utils/print_guide.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/utils/project_setup.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap/utils/utils.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap.egg-info/dependency_links.txt +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap.egg-info/entry_points.txt +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap.egg-info/requires.txt +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/mcpywrap.egg-info/top_level.txt +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/native/mcs_auth/Bridge.cs +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/native/mcs_auth/Injector.cs +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/native/mcs_auth/Loader.c +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/native/mcs_auth/publisher.cer +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/native/mcs_auth/publisher.json +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/native/window_capture/video_capture.h +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/native/window_capture/window_capture.cpp +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/scripts/build_all_native.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/scripts/build_mcs_auth_bridge.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/scripts/build_window_capture.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/scripts/check_mcs_auth_bridge.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/scripts/check_window_capture.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/setup.cfg +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/skills/mcpywrap/agents/openai.yaml +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/skills/mcpywrap/references/instance-world-settings.md +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/skills/mcpywrap/references/macos-local.md +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/skills/mcpywrap/references/project-dependencies.md +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/skills/mcpywrap/scripts/bootstrap.ps1 +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/skills/mcpywrap/scripts/game_window.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/skills/mcpywrap/scripts/smoke.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/manual/runtime_controls/client_probe.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/manual/runtime_controls/movement.json +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/manual/runtime_controls/run.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/manual/runtime_controls/server_fixture.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/manual/runtime_controls/support.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/manual_dependency_boundaries.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/manual_input_window.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/manual_integration.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/manual_native_recording.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/manual_remote_recording.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/manual_runtime_input.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/manual_runtime_player.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/manual_runtime_ui.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_auth_interaction.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_automation.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_build_replace_retry.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_code_libraries.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_dependency_boundaries.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_engine_backends.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_engine_compatibility.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_engine_discovery.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_engine_install.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_game_window_skill.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_git_projects.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_input_sequence.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_instance_cppconfig.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_launch_encoding.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_launcher_python.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_local_auth_session.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_local_dependencies.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_manual_runtime_controls.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_mcs_auth.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_mouse.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_native_input_sequence.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_native_preferences.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_native_recording.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_network.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_network_sessions.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_pack_uuid_conflicts.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_package.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_qt_sessions.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_qumod_workflow.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_recordings.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_remote.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_remote_scripts.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_resource_reload.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_runtime_debug.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_runtime_player.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_runtime_ui_outline.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_session_capabilities.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_shared_logs.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_skill_workflows.py +0 -0
- {mcpywrap-0.4.1 → mcpywrap-0.4.2}/tests/test_window_focus.py +0 -0
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# 更新记录
|
|
2
2
|
|
|
3
|
+
## 0.4.2
|
|
4
|
+
|
|
5
|
+
- 新增统一 `runtime input capabilities/observe/run/status/cancel/stop` 与 `mcpy.input`。键盘组合、独立边沿、玩家与单节点 UI 操作共用 JSON steps、动作注册表、排时和清理;单次 key 快捷命令只生成同一份计划。
|
|
6
|
+
- 保留自动/显式时间规划:delay_ms 相对前一步计划结束,wait 从实际执行时等待;完成型动作保持实际持续时间,边沿可独立重叠,提交顺序按列表,不提供模拟 tick 对齐。
|
|
7
|
+
- 显式 windows-sendinput 特殊后端复用同一状态机,操作由会话 worker 持有,支持幂等查询、取消及失败释放。新旧游戏内输入共享互斥;旧入口隐藏但保留原参数、起点、排序和后端行为。
|
|
8
|
+
- 新增后台按键时间线与可选实机脚本;修复 Python 2 会话升级时事件方法绑定,安装源码分段传输而不扩大原请求上限。
|
|
9
|
+
- Skill 和参考文档以“观察 → 提交 → 查询”为唯一日常入口,明确快照、输入/传输 ID、迟到、实际效果与兼容写法边界;bootstrap 按实际帮助/能力发现隐藏兼容入口。
|
|
10
|
+
- Windows 3.9.0.401155、3.10.0.420447 验证独立按键、持键攻击、菜单、UI 修饰键及特殊设备输入。macOS 旧 Python 2 运行包缺可靠单调时钟时明确不支持新计划;原生适配源码不代表 Mac 二进制绑定和实机验收已完成。
|
|
11
|
+
|
|
3
12
|
## 0.4.1
|
|
4
13
|
|
|
5
14
|
- 修复主动停止游戏后,将 Windows 终止退出码误报为异常退出的问题;Qt 调试窗、终端、本地前台运行及网络会话共用停止请求判定。
|
|
@@ -3,6 +3,7 @@ include docs/releasing.md
|
|
|
3
3
|
include docs/runtime-debug.md
|
|
4
4
|
include docs/runtime-ui.md docs/runtime-player.md
|
|
5
5
|
include docs/input-sequence.md
|
|
6
|
+
include docs/runtime-input.md
|
|
6
7
|
include docs/local-dependencies.md docs/engine-discovery.md docs/development.md
|
|
7
8
|
include docs/mcs-auth.md
|
|
8
9
|
include docs/code-libraries.md docs/git-dependencies.md docs/qumod.md
|
|
@@ -18,6 +19,7 @@ recursive-include native/mcs_auth *.cs *.c *.cer *.json
|
|
|
18
19
|
recursive-include mcpywrap/mcstudio/bridge_payload *.exe *.dll *.cer *.json
|
|
19
20
|
recursive-include mcpywrap/mcstudio/window_capture *.exe *.json
|
|
20
21
|
recursive-include native/window_capture *.cpp *.h
|
|
22
|
+
recursive-include native/runtime_input *.h *.md *.patch
|
|
21
23
|
prune test
|
|
22
24
|
prune .git
|
|
23
25
|
global-exclude __pycache__ *.py[cod] *.cppconfig *.log
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcpywrap
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: A wrapper for Minecraft China Edition Addons management
|
|
5
5
|
Author-email: boybook <boybook@easecation.net>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -215,11 +215,12 @@ mcpy --project D:\tests\server stop --session <id> --json
|
|
|
215
215
|
- “启动这个项目,不弹日志界面,检查客户端和服务端的加载日志。”
|
|
216
216
|
- “启动游戏并截图,模拟组合按键移动,检查 F11 输入模式和 F3 调试信息层。”
|
|
217
217
|
|
|
218
|
-
Agent
|
|
218
|
+
Agent通过CLI管理项目和操作游戏。0.4.2起只有一条日常输入流程:runtime install → runtime input capabilities/observe → run --file → status/cancel。键盘组合、独立边沿、玩家与单节点UI操作共用JSON steps与生命周期,实际支持以当前会话能力为准,见[统一输入](docs/runtime-input.md)。需要补图且不能占用前台时用screenshot --background-only;Qt管理界面用于人工操作。
|
|
219
219
|
|
|
220
|
-
严禁业务代码依赖
|
|
220
|
+
严禁业务代码依赖mcpy.*:这些方法只存在于临时注入的调试会话。默认backend=game;原始设备测试显式设置windows-sendinput。旧player/ui、key/mouse/input-sequence入口仍兼容,但从默认帮助隐藏。
|
|
221
|
+
|
|
222
|
+
时间规划中at_ms可省略,按前一步计划结束加delay_ms编译;真正的实际停顿用wait。Mac旧运行包缺单调时钟时新计划明确不支持,不自动切后端或回退CPU时间。
|
|
221
223
|
|
|
222
|
-
连续执行统一先生成时间计划,`at_ms` 可省略并按动作时长与 `delay_ms` 自动计算。允许占用游戏前台的 Windows 键鼠快切测试使用 `input-sequence --file <计划.json>`,一次请求返回输入提交时间与实际偏差;后台游戏语义动作继续使用 `runtime player sequence`。详见 [连续动作编排与输入计时](docs/input-sequence.md)。
|
|
223
224
|
常用入口:`mcpy --project <目录> --non-interactive <命令> --json`。
|
|
224
225
|
|
|
225
226
|
## 视频录制与提帧
|
|
@@ -187,11 +187,12 @@ mcpy --project D:\tests\server stop --session <id> --json
|
|
|
187
187
|
- “启动这个项目,不弹日志界面,检查客户端和服务端的加载日志。”
|
|
188
188
|
- “启动游戏并截图,模拟组合按键移动,检查 F11 输入模式和 F3 调试信息层。”
|
|
189
189
|
|
|
190
|
-
Agent
|
|
190
|
+
Agent通过CLI管理项目和操作游戏。0.4.2起只有一条日常输入流程:runtime install → runtime input capabilities/observe → run --file → status/cancel。键盘组合、独立边沿、玩家与单节点UI操作共用JSON steps与生命周期,实际支持以当前会话能力为准,见[统一输入](docs/runtime-input.md)。需要补图且不能占用前台时用screenshot --background-only;Qt管理界面用于人工操作。
|
|
191
191
|
|
|
192
|
-
严禁业务代码依赖
|
|
192
|
+
严禁业务代码依赖mcpy.*:这些方法只存在于临时注入的调试会话。默认backend=game;原始设备测试显式设置windows-sendinput。旧player/ui、key/mouse/input-sequence入口仍兼容,但从默认帮助隐藏。
|
|
193
|
+
|
|
194
|
+
时间规划中at_ms可省略,按前一步计划结束加delay_ms编译;真正的实际停顿用wait。Mac旧运行包缺单调时钟时新计划明确不支持,不自动切后端或回退CPU时间。
|
|
193
195
|
|
|
194
|
-
连续执行统一先生成时间计划,`at_ms` 可省略并按动作时长与 `delay_ms` 自动计算。允许占用游戏前台的 Windows 键鼠快切测试使用 `input-sequence --file <计划.json>`,一次请求返回输入提交时间与实际偏差;后台游戏语义动作继续使用 `runtime player sequence`。详见 [连续动作编排与输入计时](docs/input-sequence.md)。
|
|
195
196
|
常用入口:`mcpy --project <目录> --non-interactive <命令> --json`。
|
|
196
197
|
|
|
197
198
|
## 视频录制与提帧
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# 统一输入实现
|
|
2
|
+
|
|
3
|
+
Agent 的推荐入口是 `runtime input observe → run → status/cancel`;公开协议与示例见 [统一输入](../skills/mcpywrap/references/runtime-input.md)。单键快捷命令生成同一JSON计划,默认后端game,设备测试显式选择windows-sendinput。
|
|
4
|
+
|
|
5
|
+
`input_plan.py` 保存动作注册表、集中键名映射、字段验证、规范化与限制。能力查询、CLI帮助与schema均从注册表生成;不在客户端、远程和游戏中维护不同参数表。输入源码按原有32KiB上限分段暂存,全部确认后才安装。
|
|
6
|
+
|
|
7
|
+
`input_executor.py` 是两后端共用的有界状态机:先编译绝对计划时刻,完成型动作等待自身结束,瞬时边沿保留计划持有集合,wait保留实际等待。调度器不改变旧sequence的计划语义,也不把动态目标或完成依赖平铺成静态边沿。
|
|
8
|
+
|
|
9
|
+
游戏适配复用既有玩家/UI客户端路径,增加新旧入口互斥。UI单节点操作保持快照校验,修饰键在动作前按下、动作结束后释放;菜单键盘不再受player的HUD限制,界面切换仍停止后续计划。玩家语义输入与原始设备输入路径分别记录。
|
|
10
|
+
|
|
11
|
+
Windows适配在会话worker中保留状态与幂等历史,单个调度线程推进操作;远程input端点只传参数。统一游戏控制层预留防止新旧游戏写请求插入设备计划;已持有的系统键或修饰键拒绝接管。未知释放保持busy,支持stop重试,不扩大为多请求共享输入。
|
|
12
|
+
|
|
13
|
+
原生Mac时钟primitive和启动器源码补丁在 `native/runtime_input`。旧运行包缺少经过确认的Python单调时钟时,能力明确关闭;实际Apple Silicon的二进制结构规则、编译和实机验收必须单独完成,不以Windows测试代替。原始pointer/text尚未验证的游戏后端能力保持不支持。
|
|
14
|
+
|
|
15
|
+
验证采用假时钟检查时序、调用顺序、部分失败、取消、幂等和快照;可选实机脚本覆盖真实客户端与服务端读回。旧入口不改变其修饰键排序、时间原点或输入后端;仅从默认发现入口隐藏,兼容参考保留。
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
日常 API、默认时长、坐标/槽位约定、连续计划示例及失败恢复统一维护在 [Skill 玩家操作说明](../skills/mcpywrap/references/runtime-player.md),主 Skill 直接列出了常用方法,不要求 Agent 自行检索 ModSDK。
|
|
8
8
|
|
|
9
|
+
全部输入入口的键名、别名、组合/边沿写法与平台兼容见 [按键写法与兼容对照](../skills/mcpywrap/references/input-writing.md)。
|
|
10
|
+
|
|
9
11
|
## 实现边界
|
|
10
12
|
|
|
11
13
|
- 公共 ModSDK 用于状态读取、朝向、移动向量锁定、跳跃。内部 `localplayermodule` 用于真实客户端攻击、选槽、使用物品、挖掘和释放,按实际函数是否存在报告能力,不按引擎版本号限制。接口缺失或调用异常会返回引擎版本与兼容性提示。
|
|
@@ -28,6 +30,12 @@
|
|
|
28
30
|
|
|
29
31
|
## 验证
|
|
30
32
|
|
|
33
|
+
独立按键重叠使用新增 `runtime player timeline` / `mcpy.player.timeline(plan)`,公开契约见 [后台按键时间线](../skills/mcpywrap/references/runtime-key-timeline.md)。同一时刻严格遵循列表顺序,不采用旧 key 的修饰键排序;默认迟到继续并记录,可显式设置 max_lateness 中止。每个计划独占已有写操作槽位,取消/异常只释放本计划持有的键,失败释放可用 stop 重试。接口通过同一客户端 Python 通道,原 sequence 保持串行。
|
|
34
|
+
|
|
35
|
+
统一安装源码超过单条 32 KiB 限额时分段暂存,确认各段后才执行安装;暂存不注入部分控制器。查询及业务包不依赖暂存模块,不扩大原传输上限。输入消费阶段、暂停期间释放和人工输入归属均不作额外保证,不把脚本回调计数包装为模拟 tick。
|
|
36
|
+
|
|
37
|
+
`tests/test_runtime_key_timeline.py` 用假时钟验证边沿顺序、独立释放、迟到策略、幂等、互斥及失败清理。`tests/manual_runtime_key_timeline.py` 是显式可选的 Windows 实机验收,三次重复重叠配方,并检查两种修饰键顺序、取消和菜单释放;保存真实跳跃、持续移动及后台观测证据,不进入默认测试或正常启动。
|
|
38
|
+
|
|
31
39
|
`tests/test_runtime_player.py` 使用有时序的引擎适配器验证:按键逆序释放、移动/疾跑恢复、目标变化与越距拒绝、食物 False 返回但已开始使用、弓蓄力松开、队列延迟、预检失败、步骤断言、取消、重复请求和调用者修改原计划后的隔离。
|
|
32
40
|
|
|
33
41
|
实机验收脚本为 `tests/manual_runtime_player.py`。先准备独立生存世界、未满的饥饿值、快捷栏食物/普通弓及箭,再运行 `--project <项目> --session <sid> --food-slot 3 --bow-slot 4 --output <新JSON> --require-background`。脚本通过公开 CLI 提交并验证队列,消耗一份食物和一支箭,恢复原朝向和选槽;不会创建物品、改变游戏模式或停止传入会话。
|
|
@@ -73,6 +73,7 @@ for old_command in (py_cmd, reload_cmd):
|
|
|
73
73
|
cli.add_command(alias)
|
|
74
74
|
|
|
75
75
|
from .commands.desktop_cmd import screenshot_cmd, key_cmd, mouse_cmd, input_sequence_cmd
|
|
76
|
+
key_cmd.hidden = mouse_cmd.hidden = input_sequence_cmd.hidden = True
|
|
76
77
|
from .commands.serve_cmd import serve_cmd
|
|
77
78
|
cli.add_command(screenshot_cmd, 'screenshot')
|
|
78
79
|
cli.add_command(key_cmd, 'key')
|
|
@@ -121,8 +121,13 @@ runtime_cmd.add_command(reload_cmd)
|
|
|
121
121
|
runtime_cmd.add_command(watch_cmd)
|
|
122
122
|
|
|
123
123
|
from .runtime_ui_cmd import ui_cmd
|
|
124
|
+
ui_cmd.hidden = True
|
|
124
125
|
runtime_cmd.add_command(ui_cmd)
|
|
125
126
|
|
|
126
127
|
from .runtime_player_cmd import player_cmd, install_cmd as install_runtime_cmd
|
|
128
|
+
player_cmd.hidden = True
|
|
127
129
|
runtime_cmd.add_command(player_cmd)
|
|
128
130
|
runtime_cmd.add_command(install_runtime_cmd, 'install')
|
|
131
|
+
|
|
132
|
+
from .runtime_input_cmd import input_cmd
|
|
133
|
+
runtime_cmd.add_command(input_cmd)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""观察、统一 JSON 计划与操作生命周期;快捷键只生成同一协议。"""
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import click
|
|
5
|
+
from .runtime_ui_cmd import UICommand, session_option
|
|
6
|
+
from ..command_context import project_dir
|
|
7
|
+
from ..input_plan import INPUT_ACTIONS, input_key_plan, normalize_input_plan
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def perform(session, action, **parameters):
|
|
11
|
+
from ..mcstudio.unified_input import execute
|
|
12
|
+
return execute(project_dir(), session, action, {k: v for k, v in parameters.items() if v is not None})
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.group(name='input', epilog='动作由同一注册表定义:'+', '.join(INPUT_ACTIONS))
|
|
16
|
+
def input_cmd():
|
|
17
|
+
"""观察当前状态 → run 提交统一计划 → status/cancel 查询或停止;默认游戏内执行。"""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@input_cmd.command(cls=UICommand, name='capabilities')
|
|
21
|
+
@session_option
|
|
22
|
+
def capabilities_cmd(session):
|
|
23
|
+
"""只读查询动作参数、时钟、后端和限制,不自动安装。"""
|
|
24
|
+
return perform(session, 'capabilities')
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@input_cmd.command(cls=UICommand, name='observe')
|
|
28
|
+
@session_option
|
|
29
|
+
@click.option('--view', type=click.Choice(['auto', 'player', 'ui']), default='auto')
|
|
30
|
+
@click.option('--query', help='UI 观察按名称或路径筛选')
|
|
31
|
+
@click.option('--limit', type=click.IntRange(1, 160), default=80)
|
|
32
|
+
@click.option('--offset', type=click.IntRange(0), default=0)
|
|
33
|
+
@click.option('--details', is_flag=True)
|
|
34
|
+
def observe_cmd(session, **parameters):
|
|
35
|
+
"""按当前画面返回快照、目标和可执行动作;页面改变后重新观察。"""
|
|
36
|
+
return perform(session, 'observe', **parameters)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@input_cmd.command(cls=UICommand, name='run')
|
|
40
|
+
@session_option
|
|
41
|
+
@click.option('--file', 'filename', required=True, type=click.Path(exists=True, dir_okay=False))
|
|
42
|
+
@click.option('--request-id', help='32 位小写十六进制幂等 ID;未知结果查原 ID')
|
|
43
|
+
def run_cmd(session, filename, request_id):
|
|
44
|
+
"""一次提交 JSON steps;自动/显式排时共用协议,不选择 sequence/timeline。"""
|
|
45
|
+
raw = Path(filename).read_bytes()
|
|
46
|
+
if len(raw) > 65536:
|
|
47
|
+
raise click.UsageError('输入文件超过 64 KiB;规范化计划上限为 16 KiB')
|
|
48
|
+
try:
|
|
49
|
+
plan = normalize_input_plan(json.loads(raw.decode('utf-8-sig')))
|
|
50
|
+
except (ValueError, UnicodeError) as error:
|
|
51
|
+
raise click.UsageError(str(error)) from None
|
|
52
|
+
return perform(session, 'run', plan=plan, request_id=request_id)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@input_cmd.command(cls=UICommand, name='key')
|
|
56
|
+
@session_option
|
|
57
|
+
@click.argument('keys')
|
|
58
|
+
@click.option('--duration-ms', type=click.IntRange(20, 10000), default=80)
|
|
59
|
+
@click.option('--request-id')
|
|
60
|
+
def key_cmd(session, keys, duration_ms, request_id):
|
|
61
|
+
"""人工快捷命令,按声明顺序生成 key 计划,再调用同一个 run。"""
|
|
62
|
+
return perform(session, 'run', plan=input_key_plan(keys, duration_ms), request_id=request_id)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@input_cmd.command(cls=UICommand, name='status')
|
|
66
|
+
@session_option
|
|
67
|
+
@click.option('--operation')
|
|
68
|
+
@click.option('--details', is_flag=True)
|
|
69
|
+
def status_cmd(session, operation, details):
|
|
70
|
+
"""查询 operation_id;details 返回规范化计划和实际/清理日志。"""
|
|
71
|
+
return perform(session, 'status', operation=operation, details=details)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@input_cmd.command(cls=UICommand, name='cancel')
|
|
75
|
+
@session_option
|
|
76
|
+
@click.option('--operation', required=True)
|
|
77
|
+
def cancel_cmd(session, operation):
|
|
78
|
+
"""取消原操作并释放其输入;不能以重跑计划代替清理。"""
|
|
79
|
+
return perform(session, 'cancel', operation=operation)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@input_cmd.command(cls=UICommand, name='stop')
|
|
83
|
+
@session_option
|
|
84
|
+
def stop_cmd(session):
|
|
85
|
+
"""停止本控制层输入,不退出游戏。"""
|
|
86
|
+
return perform(session, 'stop')
|
|
@@ -24,7 +24,7 @@ def player_cmd():
|
|
|
24
24
|
@player_cmd.command(cls=UICommand, name='install')
|
|
25
25
|
@session_option
|
|
26
26
|
def install_cmd(session):
|
|
27
|
-
"""
|
|
27
|
+
"""注入统一 mcpy.input 及兼容的 UI、玩家和客户端 API。"""
|
|
28
28
|
return perform(session, 'install')
|
|
29
29
|
|
|
30
30
|
|
|
@@ -155,6 +155,19 @@ def sequence_cmd(session, request_id, steps, filename):
|
|
|
155
155
|
return perform(session,'sequence',steps=plan,request_id=request_id)
|
|
156
156
|
|
|
157
157
|
|
|
158
|
+
@player_cmd.command(cls=UICommand, name='timeline')
|
|
159
|
+
@action_options
|
|
160
|
+
@click.option('--file', 'filename', required=True, type=click.Path(exists=True, dir_okay=False),
|
|
161
|
+
help='调用端 UTF-8 JSON 按键边沿计划;同一时刻严格按列表顺序')
|
|
162
|
+
def timeline_cmd(session, filename, request_id):
|
|
163
|
+
"""一次提交后台按键时间线;迟到默认继续并记录,不提供 tick 对齐。"""
|
|
164
|
+
try:
|
|
165
|
+
plan = json.loads(Path(filename).read_text(encoding='utf-8-sig'))
|
|
166
|
+
except ValueError:
|
|
167
|
+
raise click.UsageError('--file 必须是合法的 UTF-8 JSON 计划') from None
|
|
168
|
+
return perform(session, 'timeline', plan=plan, request_id=request_id)
|
|
169
|
+
|
|
170
|
+
|
|
158
171
|
@player_cmd.command(cls=UICommand, name='status')
|
|
159
172
|
@session_option
|
|
160
173
|
@click.option('--operation')
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
"""统一计划状态机;后端负责时钟、调度和提交,状态机不直接依赖引擎。"""
|
|
3
|
+
from __future__ import unicode_literals
|
|
4
|
+
import json
|
|
5
|
+
import threading
|
|
6
|
+
import uuid
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
normalize_input_plan
|
|
10
|
+
except NameError:
|
|
11
|
+
from .input_plan import (normalize_input_plan, input_signature, input_require, InputPlanError,
|
|
12
|
+
INPUT_LIMITS, INPUT_ACTIONS, input_key_plan)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class InputExecutor(object):
|
|
16
|
+
def __init__(self, adapter):
|
|
17
|
+
self.adapter = adapter
|
|
18
|
+
self.active = None
|
|
19
|
+
self.operations, self.order = {}, []
|
|
20
|
+
self.lock = threading.RLock()
|
|
21
|
+
|
|
22
|
+
def _view(self, op, details=False):
|
|
23
|
+
if op is None: return None
|
|
24
|
+
value = {k: v for k, v in op.items() if not k.startswith('_')}
|
|
25
|
+
if not details:
|
|
26
|
+
for key in ('plan', 'steps', 'events', 'cleanup_events'): value.pop(key, None)
|
|
27
|
+
return json.loads(json.dumps(value, ensure_ascii=True, allow_nan=False))
|
|
28
|
+
|
|
29
|
+
def _error(self, op, error):
|
|
30
|
+
try: message = unicode(error)
|
|
31
|
+
except NameError: message = str(error)
|
|
32
|
+
if len(message) > 512: op['logs_truncated'] = True
|
|
33
|
+
return message[:512]
|
|
34
|
+
|
|
35
|
+
def _log(self, op, family, row):
|
|
36
|
+
size = len(input_signature(row))
|
|
37
|
+
op[family].append(row); op['_log_bytes'] += size
|
|
38
|
+
while op['_log_bytes'] > INPUT_LIMITS['log_bytes']:
|
|
39
|
+
target = next(key for key in ('cleanup_events', 'events', 'steps') if op[key])
|
|
40
|
+
old = op[target].pop(0)
|
|
41
|
+
op['_log_bytes'] -= len(input_signature(old))
|
|
42
|
+
op[target+'_dropped'] += 1
|
|
43
|
+
op['logs_truncated'] = True
|
|
44
|
+
|
|
45
|
+
def run(self, plan, request_id=None):
|
|
46
|
+
normalized = normalize_input_plan(plan)
|
|
47
|
+
request_id = request_id or uuid.uuid4().hex
|
|
48
|
+
input_require(isinstance(request_id, str) or isinstance(request_id, type(u'')), 'invalid_request_id', 'request_id 必须是字符串')
|
|
49
|
+
input_require(len(request_id) == 32 and all(c in '0123456789abcdef' for c in request_id),
|
|
50
|
+
'invalid_request_id', 'request_id 必须是 32 位小写十六进制')
|
|
51
|
+
signature = input_signature(normalized)
|
|
52
|
+
with self.lock:
|
|
53
|
+
previous = self.operations.get(request_id)
|
|
54
|
+
if previous is not None:
|
|
55
|
+
input_require(previous['_signature'] == signature, 'request_conflict', 'request_id 已用于不同计划')
|
|
56
|
+
return self._view(previous, True)
|
|
57
|
+
input_require(self.active is None, 'busy', '当前输入未结束或释放未确认,查询 status 或 stop')
|
|
58
|
+
self.adapter.ready()
|
|
59
|
+
bindings = self.adapter.validate(normalized)
|
|
60
|
+
op = {'ok': True, 'operation_id': request_id, 'request_id': request_id, 'state': 'pending',
|
|
61
|
+
'backend': normalized['backend'], 'clock': 'monotonic_ms', 'clock_source': self.adapter.clock_source,
|
|
62
|
+
'effect_verified': False, 'released': False, 'started': False, 'index': 0, 'count': len(normalized['steps']),
|
|
63
|
+
'plan': normalized, 'steps': [], 'events': [], 'cleanup_events': [], 'held_keys': [],
|
|
64
|
+
'unconfirmed_release_keys': [], 'logs_truncated': False,
|
|
65
|
+
'steps_dropped': 0, 'events_dropped': 0, 'cleanup_events_dropped': 0,
|
|
66
|
+
'_signature': signature, '_bindings': bindings, '_held': [], '_stage': None,
|
|
67
|
+
'_started': None, '_deadline': self.adapter.clock()+INPUT_LIMITS['execution_ms']/1000.,
|
|
68
|
+
'_log_bytes': 0, '_finishing': False}
|
|
69
|
+
self.operations[request_id] = op; self.order.append(request_id)
|
|
70
|
+
while len(self.order) > INPUT_LIMITS['history']:
|
|
71
|
+
self.operations.pop(self.order.pop(0), None)
|
|
72
|
+
self.active = op
|
|
73
|
+
self._schedule(op, 0.)
|
|
74
|
+
return self._view(op, True)
|
|
75
|
+
|
|
76
|
+
def key(self, keys, duration_ms=80, request_id=None):
|
|
77
|
+
return self.run(input_key_plan(keys, duration_ms), request_id)
|
|
78
|
+
|
|
79
|
+
def status(self, operation=None, details=False):
|
|
80
|
+
with self.lock:
|
|
81
|
+
if operation is None:
|
|
82
|
+
return {'ok': True, 'active': self._view(self.active, details),
|
|
83
|
+
'last_operation': self._view(self.operations.get(self.order[-1]) if self.order else None, details)}
|
|
84
|
+
input_require(operation in self.operations, 'operation_not_found', '记录不存在或已淘汰,不代表从未执行')
|
|
85
|
+
return self._view(self.operations[operation], details)
|
|
86
|
+
|
|
87
|
+
def cancel(self, operation):
|
|
88
|
+
with self.lock:
|
|
89
|
+
input_require(operation in self.operations, 'operation_not_found', '操作记录不存在')
|
|
90
|
+
op = self.operations[operation]
|
|
91
|
+
if self.active is op: self._finish(op, 'cancelled', 'requested_cancel')
|
|
92
|
+
return self._view(op)
|
|
93
|
+
|
|
94
|
+
def stop(self):
|
|
95
|
+
with self.lock:
|
|
96
|
+
if self.active is not None: self._finish(self.active, 'cancelled', 'requested_stop')
|
|
97
|
+
return dict(self.status(), ok=self.active is None)
|
|
98
|
+
|
|
99
|
+
def relative_ms(self, op):
|
|
100
|
+
return None if op['_started'] is None else (self.adapter.clock()-op['_started'])*1000.
|
|
101
|
+
|
|
102
|
+
def _schedule(self, op, seconds):
|
|
103
|
+
try:
|
|
104
|
+
self.adapter.schedule(seconds, lambda: self._tick(op))
|
|
105
|
+
except Exception as error:
|
|
106
|
+
self._fail(op, error)
|
|
107
|
+
|
|
108
|
+
def _fail(self, op, error):
|
|
109
|
+
op['error'] = self._error(op, error)
|
|
110
|
+
stage = op.get('_stage')
|
|
111
|
+
if stage is not None and not stage.get('failure_logged'):
|
|
112
|
+
self._log(op, 'steps', dict(index=op['index'], action=stage['step']['action'],
|
|
113
|
+
at_ms=stage['step']['at_ms'], started_ms=stage['started_ms'], finished_ms=self.relative_ms(op),
|
|
114
|
+
result={'state': 'failed', 'code': getattr(error, 'code', 'input_error'), 'error': op['error']},
|
|
115
|
+
input_path=stage.get('input_path', self.adapter.input_path)))
|
|
116
|
+
stage['failure_logged'] = True
|
|
117
|
+
self._finish(op, 'failed', getattr(error, 'code', 'input_error'))
|
|
118
|
+
|
|
119
|
+
def _guard(self, op):
|
|
120
|
+
input_require(self.adapter.clock() < op['_deadline'], 'input_timeout', '输入超过 125 秒执行上限')
|
|
121
|
+
self.adapter.guard(op)
|
|
122
|
+
|
|
123
|
+
def _tick(self, op):
|
|
124
|
+
with self.lock:
|
|
125
|
+
if self.active is not op or op['state'] != 'pending': return
|
|
126
|
+
try:
|
|
127
|
+
if not op['started']:
|
|
128
|
+
self.adapter.prepare(op)
|
|
129
|
+
self._guard(op)
|
|
130
|
+
op['_started'] = self.adapter.clock()
|
|
131
|
+
op.update(started=True, started_at=op['_started'])
|
|
132
|
+
while self.active is op and op['state'] == 'pending':
|
|
133
|
+
self._guard(op)
|
|
134
|
+
if op['_stage'] is not None:
|
|
135
|
+
stage = op['_stage']
|
|
136
|
+
if not self._poll(op, stage):
|
|
137
|
+
self._schedule(op, .01)
|
|
138
|
+
return
|
|
139
|
+
op['_stage'] = None
|
|
140
|
+
if self.active is not op or op['state'] != 'pending': return
|
|
141
|
+
self._log(op, 'steps', dict(index=op['index'], action=stage['step']['action'],
|
|
142
|
+
at_ms=stage['step']['at_ms'], started_ms=stage['started_ms'],
|
|
143
|
+
finished_ms=self.relative_ms(op), result=stage.get('result', {}),
|
|
144
|
+
input_path=stage.get('input_path', self.adapter.input_path)))
|
|
145
|
+
op['index'] += 1
|
|
146
|
+
if op['index'] >= op['count']:
|
|
147
|
+
self._finish(op, 'completed')
|
|
148
|
+
return
|
|
149
|
+
step = op['plan']['steps'][op['index']]
|
|
150
|
+
actual = self.relative_ms(op)
|
|
151
|
+
if actual < step['at_ms']:
|
|
152
|
+
self._schedule(op, max(.001, min(.05, (step['at_ms']-actual)/1000.)))
|
|
153
|
+
return
|
|
154
|
+
maximum = op['plan']['max_lateness_ms']
|
|
155
|
+
input_require(maximum is None or actual-step['at_ms'] <= maximum,
|
|
156
|
+
'deadline_missed', '步骤迟到超过 max_lateness_ms')
|
|
157
|
+
stage = {'step': step, 'started_ms': actual, 'started_at': self.adapter.clock(), 'kind': 'instant'}
|
|
158
|
+
op['_stage'] = stage
|
|
159
|
+
self._begin(op, stage)
|
|
160
|
+
except Exception as error:
|
|
161
|
+
self._fail(op, error)
|
|
162
|
+
|
|
163
|
+
def _begin(self, op, stage):
|
|
164
|
+
step = stage['step']; name = step['action']
|
|
165
|
+
if name == 'wait':
|
|
166
|
+
stage.update(kind='duration', until=stage['started_at']+step['duration_ms']/1000., input_path='scheduler')
|
|
167
|
+
elif name == 'key':
|
|
168
|
+
stage['kind'] = 'key'
|
|
169
|
+
stage['keys'] = list(step['keys'])
|
|
170
|
+
for key in step['keys']:
|
|
171
|
+
self.edge(op, 'key', key, True)
|
|
172
|
+
if self.active is not op or op['state'] != 'pending': return
|
|
173
|
+
stage['until'] = stage['started_at']+step['duration_ms']/1000.
|
|
174
|
+
elif name in ('key_down', 'key_up'):
|
|
175
|
+
self.edge(op, 'key', step['key'], name == 'key_down')
|
|
176
|
+
elif name in ('pointer.down', 'pointer.up'):
|
|
177
|
+
self.edge(op, 'pointer', step['button'], name == 'pointer.down')
|
|
178
|
+
else:
|
|
179
|
+
self.adapter.begin(op, stage)
|
|
180
|
+
|
|
181
|
+
def _poll(self, op, stage):
|
|
182
|
+
if stage['kind'] in ('duration', 'key'):
|
|
183
|
+
if self.adapter.clock() < stage['until']: return False
|
|
184
|
+
if stage['kind'] == 'key':
|
|
185
|
+
for key in reversed(stage['keys']): self.edge(op, 'key', key, False)
|
|
186
|
+
return True
|
|
187
|
+
if stage['kind'] == 'instant': return True
|
|
188
|
+
return self.adapter.poll(op, stage)
|
|
189
|
+
|
|
190
|
+
def edge(self, op, kind, key, down, cleanup=False):
|
|
191
|
+
binding = self.adapter.binding(op, kind, key)
|
|
192
|
+
identity = self.adapter.identity(kind, binding)
|
|
193
|
+
owned = next((row for row in op['_held'] if row['identity'] == identity), None)
|
|
194
|
+
input_require(owned is None if down else owned is not None, 'input_conflict', '输入边沿与本计划持有状态冲突')
|
|
195
|
+
if down:
|
|
196
|
+
owned = {'kind': kind, 'key': key, 'binding': binding, 'identity': identity, 'index': op['index']}
|
|
197
|
+
op['_held'].append(owned)
|
|
198
|
+
self._held(op)
|
|
199
|
+
row = {'index': op['index'], 'action': kind+('_down' if down else '_up'), 'key': key,
|
|
200
|
+
'started_ms': self.relative_ms(op), 'accepted': None, 'input_path': self.adapter.input_path}
|
|
201
|
+
if cleanup: row['release_of_index'] = owned['index']
|
|
202
|
+
else:
|
|
203
|
+
row['at_ms'] = op['plan']['steps'][op['index']]['at_ms']
|
|
204
|
+
row['lateness_ms'] = max(0., row['started_ms']-row['at_ms'])
|
|
205
|
+
try:
|
|
206
|
+
accepted = self.adapter.send(kind, binding, down)
|
|
207
|
+
row['accepted'] = accepted
|
|
208
|
+
row.update(getattr(self.adapter, 'last_send', {}))
|
|
209
|
+
input_require(accepted is True, 'release_failed' if not down else 'input_rejected', '输入接口未确认提交')
|
|
210
|
+
if not down and owned in op['_held']:
|
|
211
|
+
op['_held'].remove(owned)
|
|
212
|
+
self._held(op)
|
|
213
|
+
except Exception as error:
|
|
214
|
+
row['error'] = self._error(op, error)
|
|
215
|
+
raise
|
|
216
|
+
finally:
|
|
217
|
+
row.update(finished_ms=self.relative_ms(op), held_keys=list(op['held_keys']))
|
|
218
|
+
self._log(op, 'cleanup_events' if cleanup else 'events', row)
|
|
219
|
+
|
|
220
|
+
@staticmethod
|
|
221
|
+
def _held(op):
|
|
222
|
+
op['held_keys'] = [row['key'] if row['kind'] == 'key' else 'pointer:'+row['key'] for row in op['_held']]
|
|
223
|
+
|
|
224
|
+
def _finish(self, op, state, code=None):
|
|
225
|
+
if op['_finishing']: return
|
|
226
|
+
op['_finishing'] = True
|
|
227
|
+
if op['state'] == 'pending':
|
|
228
|
+
op['_terminal'] = state
|
|
229
|
+
if code: op['code'] = code
|
|
230
|
+
op['state'] = op.get('_terminal', state)
|
|
231
|
+
failures = []
|
|
232
|
+
try:
|
|
233
|
+
self.adapter.cleanup(op)
|
|
234
|
+
except Exception as error:
|
|
235
|
+
failures.append('action')
|
|
236
|
+
op['cleanup_error'] = self._error(op, error)
|
|
237
|
+
for row in reversed(list(op['_held'])):
|
|
238
|
+
try: self.edge(op, row['kind'], row['key'], False, cleanup=True)
|
|
239
|
+
except Exception as error:
|
|
240
|
+
op['cleanup_error'] = self._error(op, error)
|
|
241
|
+
op['unconfirmed_release_keys'] = list(op['held_keys'])+failures
|
|
242
|
+
op['released'] = not op['unconfirmed_release_keys']
|
|
243
|
+
op.update(ok=op['_terminal'] in ('completed', 'cancelled') and op['released'],
|
|
244
|
+
state=op['_terminal'] if op['released'] else 'unknown', finished_ms=self.relative_ms(op), _finishing=False)
|
|
245
|
+
if op['released'] and self.active is op: self.active = None
|
|
246
|
+
self.adapter.finished(op)
|