automas-maafw-runner 0.1.1__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.
- automas_maafw_runner-0.1.1/PKG-INFO +21 -0
- automas_maafw_runner-0.1.1/README.md +10 -0
- automas_maafw_runner-0.1.1/pyproject.toml +25 -0
- automas_maafw_runner-0.1.1/setup.cfg +4 -0
- automas_maafw_runner-0.1.1/src/automas_maafw_runner/__init__.py +30 -0
- automas_maafw_runner-0.1.1/src/automas_maafw_runner/environment.py +353 -0
- automas_maafw_runner-0.1.1/src/automas_maafw_runner/models.py +94 -0
- automas_maafw_runner-0.1.1/src/automas_maafw_runner/pipeline_override.py +335 -0
- automas_maafw_runner-0.1.1/src/automas_maafw_runner/plugin.py +40 -0
- automas_maafw_runner-0.1.1/src/automas_maafw_runner/py.typed +1 -0
- automas_maafw_runner-0.1.1/src/automas_maafw_runner/run_plan.py +650 -0
- automas_maafw_runner-0.1.1/src/automas_maafw_runner/runner.py +2139 -0
- automas_maafw_runner-0.1.1/src/automas_maafw_runner/service.py +151 -0
- automas_maafw_runner-0.1.1/src/automas_maafw_runner/worker.py +91 -0
- automas_maafw_runner-0.1.1/src/automas_maafw_runner.egg-info/PKG-INFO +21 -0
- automas_maafw_runner-0.1.1/src/automas_maafw_runner.egg-info/SOURCES.txt +18 -0
- automas_maafw_runner-0.1.1/src/automas_maafw_runner.egg-info/dependency_links.txt +1 -0
- automas_maafw_runner-0.1.1/src/automas_maafw_runner.egg-info/entry_points.txt +2 -0
- automas_maafw_runner-0.1.1/src/automas_maafw_runner.egg-info/requires.txt +4 -0
- automas_maafw_runner-0.1.1/src/automas_maafw_runner.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: automas-maafw-runner
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Isolated MaaFW runner service plugin for AUTO-MAS
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: automas-maafw-agent-env>=0.1.0
|
|
8
|
+
Requires-Dist: automas-maafw-interface>=0.1.1
|
|
9
|
+
Requires-Dist: json5>=0.9
|
|
10
|
+
Requires-Dist: pydantic>=2
|
|
11
|
+
|
|
12
|
+
# automas-maafw-runner
|
|
13
|
+
|
|
14
|
+
Isolated MaaFW runner service for AUTO-MAS.
|
|
15
|
+
|
|
16
|
+
It provides `maafw.runner.v1`. The service builds run plans in the host process
|
|
17
|
+
and runs MaaFW through a worker subprocess so importing the service does not load
|
|
18
|
+
`maa` into the AUTO-MAS main process.
|
|
19
|
+
|
|
20
|
+
The wheel includes the MaaFW runtime worker code. `maa` is imported only by the
|
|
21
|
+
worker subprocess entrypoint, not by `MaaFWRunnerService`.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# automas-maafw-runner
|
|
2
|
+
|
|
3
|
+
Isolated MaaFW runner service for AUTO-MAS.
|
|
4
|
+
|
|
5
|
+
It provides `maafw.runner.v1`. The service builds run plans in the host process
|
|
6
|
+
and runs MaaFW through a worker subprocess so importing the service does not load
|
|
7
|
+
`maa` into the AUTO-MAS main process.
|
|
8
|
+
|
|
9
|
+
The wheel includes the MaaFW runtime worker code. `maa` is imported only by the
|
|
10
|
+
worker subprocess entrypoint, not by `MaaFWRunnerService`.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "automas-maafw-runner"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "Isolated MaaFW runner service plugin for AUTO-MAS"
|
|
9
|
+
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"automas-maafw-agent-env>=0.1.0",
|
|
13
|
+
"automas-maafw-interface>=0.1.1",
|
|
14
|
+
"json5>=0.9",
|
|
15
|
+
"pydantic>=2",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.entry-points."auto_mas.plugins"]
|
|
19
|
+
automas_maafw_runner = "automas_maafw_runner.plugin:Plugin"
|
|
20
|
+
|
|
21
|
+
[tool.setuptools.packages.find]
|
|
22
|
+
where = ["src"]
|
|
23
|
+
|
|
24
|
+
[tool.setuptools.package-data]
|
|
25
|
+
automas_maafw_runner = ["py.typed"]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .environment import MaaFWRunnerEnvironment
|
|
4
|
+
from .models import (
|
|
5
|
+
MaaFWDeviceConfig,
|
|
6
|
+
MaaFWResolvedPath,
|
|
7
|
+
MaaFWResourceBundlePlan,
|
|
8
|
+
MaaFWRunnerJobPayload,
|
|
9
|
+
MaaFWRunPlan,
|
|
10
|
+
MaaFWRunResult,
|
|
11
|
+
MaaFWSkippedTaskPlan,
|
|
12
|
+
MaaFWTaskRunPlan,
|
|
13
|
+
)
|
|
14
|
+
from .run_plan import MaaFWRunPlanError, build_maafw_run_plan
|
|
15
|
+
from .service import MaaFWRunnerService
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"MaaFWDeviceConfig",
|
|
19
|
+
"MaaFWResolvedPath",
|
|
20
|
+
"MaaFWResourceBundlePlan",
|
|
21
|
+
"MaaFWRunPlan",
|
|
22
|
+
"MaaFWRunPlanError",
|
|
23
|
+
"MaaFWRunResult",
|
|
24
|
+
"MaaFWRunnerEnvironment",
|
|
25
|
+
"MaaFWRunnerJobPayload",
|
|
26
|
+
"MaaFWRunnerService",
|
|
27
|
+
"MaaFWSkippedTaskPlan",
|
|
28
|
+
"MaaFWTaskRunPlan",
|
|
29
|
+
"build_maafw_run_plan",
|
|
30
|
+
]
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import shutil
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
import sysconfig
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Callable, Iterable
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
RUNNER_ENV_MANIFEST_NAME = ".auto_mas_maafw_runner_env.json"
|
|
17
|
+
RUNNER_DEFAULT_PACKAGES = (
|
|
18
|
+
"maafw",
|
|
19
|
+
"pydantic==2.11.7",
|
|
20
|
+
"json5==0.14.0",
|
|
21
|
+
)
|
|
22
|
+
RUNNER_ENV_TIMEOUT = 300
|
|
23
|
+
REQUIREMENT_NAME_RE = re.compile(
|
|
24
|
+
r"^\s*([A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?)"
|
|
25
|
+
r"\s*(?:\[[^\]]+\])?\s*(?:===|[<>=!~]=?|@|;|\s|$)"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class MaaFWRunnerEnvironment:
|
|
31
|
+
python_executable: Path
|
|
32
|
+
venv_path: Path
|
|
33
|
+
env: dict[str, str]
|
|
34
|
+
packages: tuple[str, ...]
|
|
35
|
+
maafw_version: str | None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def prepare_runner_environment(
|
|
39
|
+
project_path: str | Path,
|
|
40
|
+
*,
|
|
41
|
+
managed_env_root: str | Path | None = None,
|
|
42
|
+
import_paths: Iterable[str | Path] = (),
|
|
43
|
+
send_log: Callable[[str], None] | None = None,
|
|
44
|
+
) -> MaaFWRunnerEnvironment:
|
|
45
|
+
"""Prepare a project-scoped runner whose dependencies follow the project."""
|
|
46
|
+
|
|
47
|
+
project = Path(project_path).resolve()
|
|
48
|
+
root = (
|
|
49
|
+
Path(managed_env_root).resolve()
|
|
50
|
+
if managed_env_root is not None
|
|
51
|
+
else (Path.cwd() / "config" / "maafw_runner_venvs").resolve()
|
|
52
|
+
)
|
|
53
|
+
venv_path = root / _runner_env_name(project)
|
|
54
|
+
if venv_path.exists() and not _is_valid_venv(venv_path):
|
|
55
|
+
_reset_managed_venv(venv_path, root)
|
|
56
|
+
|
|
57
|
+
if not _is_valid_venv(venv_path):
|
|
58
|
+
venv_path.parent.mkdir(parents=True, exist_ok=True)
|
|
59
|
+
bootstrap_python = _venv_bootstrap_python()
|
|
60
|
+
_send_log(
|
|
61
|
+
send_log,
|
|
62
|
+
f"[MaaFW Runner] 创建项目隔离 venv: {venv_path} "
|
|
63
|
+
f"(引导 Python: {bootstrap_python})",
|
|
64
|
+
)
|
|
65
|
+
_run_setup_command(
|
|
66
|
+
[bootstrap_python, "-m", "venv", str(venv_path)],
|
|
67
|
+
cwd=Path.cwd(),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
python_executable = _venv_python(venv_path)
|
|
71
|
+
packages = tuple(build_runner_packages(project))
|
|
72
|
+
manifest = _build_manifest(project, packages)
|
|
73
|
+
manifest_path = venv_path / RUNNER_ENV_MANIFEST_NAME
|
|
74
|
+
env = build_runner_environment(venv_path, import_paths=import_paths)
|
|
75
|
+
if not _manifest_matches(manifest_path, manifest):
|
|
76
|
+
_send_log(
|
|
77
|
+
send_log,
|
|
78
|
+
f"[MaaFW Runner] 安装项目运行依赖: {', '.join(packages)}",
|
|
79
|
+
)
|
|
80
|
+
_run_setup_command(
|
|
81
|
+
[
|
|
82
|
+
str(python_executable),
|
|
83
|
+
"-m",
|
|
84
|
+
"pip",
|
|
85
|
+
"install",
|
|
86
|
+
"--upgrade",
|
|
87
|
+
"--disable-pip-version-check",
|
|
88
|
+
"--quiet",
|
|
89
|
+
*packages,
|
|
90
|
+
],
|
|
91
|
+
cwd=project,
|
|
92
|
+
env=env,
|
|
93
|
+
)
|
|
94
|
+
_write_manifest(manifest_path, manifest)
|
|
95
|
+
_send_log(send_log, f"[MaaFW Runner] 项目运行依赖已准备: {venv_path}")
|
|
96
|
+
else:
|
|
97
|
+
_send_log(send_log, f"[MaaFW Runner] 项目隔离 venv 已就绪: {venv_path}")
|
|
98
|
+
|
|
99
|
+
maafw_version = _installed_maafw_version(python_executable, env)
|
|
100
|
+
if maafw_version:
|
|
101
|
+
_send_log(send_log, f"[MaaFW Runner] 使用项目 MaaFW: v{maafw_version}")
|
|
102
|
+
|
|
103
|
+
return MaaFWRunnerEnvironment(
|
|
104
|
+
python_executable=python_executable,
|
|
105
|
+
venv_path=venv_path,
|
|
106
|
+
env=env,
|
|
107
|
+
packages=packages,
|
|
108
|
+
maafw_version=maafw_version,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def build_runner_packages(project_path: str | Path) -> list[str]:
|
|
113
|
+
project_packages = _load_requirements(Path(project_path).resolve())
|
|
114
|
+
project_distribution_names = {
|
|
115
|
+
name
|
|
116
|
+
for requirement in project_packages
|
|
117
|
+
if (name := requirement_distribution_name(requirement)) is not None
|
|
118
|
+
}
|
|
119
|
+
packages = [
|
|
120
|
+
package
|
|
121
|
+
for package in RUNNER_DEFAULT_PACKAGES
|
|
122
|
+
if requirement_distribution_name(package) not in project_distribution_names
|
|
123
|
+
]
|
|
124
|
+
packages.extend(project_packages)
|
|
125
|
+
return packages
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def requirement_distribution_name(requirement: str) -> str | None:
|
|
129
|
+
match = REQUIREMENT_NAME_RE.match(requirement)
|
|
130
|
+
if match is None:
|
|
131
|
+
return None
|
|
132
|
+
return re.sub(r"[-_.]+", "-", match.group(1)).lower()
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def build_runner_environment(
|
|
136
|
+
venv_path: str | Path,
|
|
137
|
+
*,
|
|
138
|
+
import_paths: Iterable[str | Path] = (),
|
|
139
|
+
) -> dict[str, str]:
|
|
140
|
+
env = os.environ.copy()
|
|
141
|
+
for name in (
|
|
142
|
+
"PYTHONHOME",
|
|
143
|
+
"PYTHONUSERBASE",
|
|
144
|
+
"PIP_TARGET",
|
|
145
|
+
"PIP_PREFIX",
|
|
146
|
+
"PIP_USER",
|
|
147
|
+
):
|
|
148
|
+
env.pop(name, None)
|
|
149
|
+
|
|
150
|
+
venv = Path(venv_path).resolve()
|
|
151
|
+
scripts_dir = venv / ("Scripts" if os.name == "nt" else "bin")
|
|
152
|
+
resolved_import_paths = [
|
|
153
|
+
str(Path(path).resolve())
|
|
154
|
+
for path in import_paths
|
|
155
|
+
if Path(path).exists()
|
|
156
|
+
]
|
|
157
|
+
existing_python_path = env.get("PYTHONPATH", "")
|
|
158
|
+
if existing_python_path:
|
|
159
|
+
resolved_import_paths.append(existing_python_path)
|
|
160
|
+
|
|
161
|
+
env["VIRTUAL_ENV"] = str(venv)
|
|
162
|
+
env["PYTHONNOUSERSITE"] = "1"
|
|
163
|
+
env["PYTHONIOENCODING"] = "utf-8"
|
|
164
|
+
env["PATH"] = f"{scripts_dir}{os.pathsep}{env.get('PATH', '')}"
|
|
165
|
+
if resolved_import_paths:
|
|
166
|
+
env["PYTHONPATH"] = os.pathsep.join(resolved_import_paths)
|
|
167
|
+
else:
|
|
168
|
+
env.pop("PYTHONPATH", None)
|
|
169
|
+
return env
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def prefer_active_venv_site_packages(
|
|
173
|
+
site_packages: str | Path | None = None,
|
|
174
|
+
) -> Path | None:
|
|
175
|
+
"""Keep the project Runner packages ahead of shared plugin dependencies."""
|
|
176
|
+
|
|
177
|
+
raw_path = site_packages or sysconfig.get_path("purelib")
|
|
178
|
+
if not raw_path:
|
|
179
|
+
return None
|
|
180
|
+
|
|
181
|
+
active_site_packages = Path(raw_path).resolve()
|
|
182
|
+
normalized_path = str(active_site_packages)
|
|
183
|
+
sys.path[:] = [
|
|
184
|
+
item
|
|
185
|
+
for item in sys.path
|
|
186
|
+
if _normalized_sys_path(item) != normalized_path
|
|
187
|
+
]
|
|
188
|
+
sys.path.insert(0, normalized_path)
|
|
189
|
+
return active_site_packages
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _load_requirements(project_path: Path) -> list[str]:
|
|
193
|
+
requirements_path = project_path / "requirements.txt"
|
|
194
|
+
packages: list[str] = []
|
|
195
|
+
try:
|
|
196
|
+
for raw_line in requirements_path.read_text(encoding="utf-8").splitlines():
|
|
197
|
+
line = raw_line.strip()
|
|
198
|
+
if not line or line.startswith("#"):
|
|
199
|
+
continue
|
|
200
|
+
packages.append(line)
|
|
201
|
+
except FileNotFoundError:
|
|
202
|
+
pass
|
|
203
|
+
return packages
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _runner_env_name(project_path: Path) -> str:
|
|
207
|
+
key = str(project_path)
|
|
208
|
+
if os.name == "nt":
|
|
209
|
+
key = key.casefold()
|
|
210
|
+
digest = hashlib.sha256(key.encode("utf-8")).hexdigest()[:16]
|
|
211
|
+
return f"maafw_runner_{digest}"
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _build_manifest(project_path: Path, packages: tuple[str, ...]) -> dict[str, object]:
|
|
215
|
+
requirements_path = project_path / "requirements.txt"
|
|
216
|
+
interface_path = next(
|
|
217
|
+
(
|
|
218
|
+
project_path / file_name
|
|
219
|
+
for file_name in ("interface.json", "interface.jsonc")
|
|
220
|
+
if (project_path / file_name).is_file()
|
|
221
|
+
),
|
|
222
|
+
None,
|
|
223
|
+
)
|
|
224
|
+
requirements_hash = (
|
|
225
|
+
hashlib.sha256(requirements_path.read_bytes()).hexdigest()
|
|
226
|
+
if requirements_path.is_file()
|
|
227
|
+
else ""
|
|
228
|
+
)
|
|
229
|
+
interface_hash = (
|
|
230
|
+
hashlib.sha256(interface_path.read_bytes()).hexdigest()
|
|
231
|
+
if interface_path is not None
|
|
232
|
+
else ""
|
|
233
|
+
)
|
|
234
|
+
return {
|
|
235
|
+
"schemaVersion": 4,
|
|
236
|
+
"projectPath": str(project_path),
|
|
237
|
+
"requirementsHash": requirements_hash,
|
|
238
|
+
"interfaceHash": interface_hash,
|
|
239
|
+
"packages": list(packages),
|
|
240
|
+
"pythonVersion": f"{sys.version_info.major}.{sys.version_info.minor}",
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _manifest_matches(manifest_path: Path, expected: dict[str, object]) -> bool:
|
|
245
|
+
try:
|
|
246
|
+
current = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
247
|
+
except (FileNotFoundError, json.JSONDecodeError, OSError):
|
|
248
|
+
return False
|
|
249
|
+
return current == expected
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _write_manifest(manifest_path: Path, manifest: dict[str, object]) -> None:
|
|
253
|
+
temporary_path = manifest_path.with_suffix(f"{manifest_path.suffix}.tmp")
|
|
254
|
+
temporary_path.write_text(
|
|
255
|
+
json.dumps(manifest, ensure_ascii=False, indent=2),
|
|
256
|
+
encoding="utf-8",
|
|
257
|
+
)
|
|
258
|
+
temporary_path.replace(manifest_path)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _run_setup_command(
|
|
262
|
+
command: list[str],
|
|
263
|
+
*,
|
|
264
|
+
cwd: Path,
|
|
265
|
+
env: dict[str, str] | None = None,
|
|
266
|
+
) -> None:
|
|
267
|
+
try:
|
|
268
|
+
result = subprocess.run(
|
|
269
|
+
command,
|
|
270
|
+
capture_output=True,
|
|
271
|
+
timeout=RUNNER_ENV_TIMEOUT,
|
|
272
|
+
text=True,
|
|
273
|
+
encoding="utf-8",
|
|
274
|
+
errors="replace",
|
|
275
|
+
cwd=cwd,
|
|
276
|
+
env=env,
|
|
277
|
+
)
|
|
278
|
+
except subprocess.TimeoutExpired as exc:
|
|
279
|
+
raise RuntimeError(f"MaaFW Runner 环境准备超时: {command[:3]}") from exc
|
|
280
|
+
|
|
281
|
+
if result.returncode == 0:
|
|
282
|
+
return
|
|
283
|
+
detail = (result.stderr or result.stdout or "").strip()
|
|
284
|
+
raise RuntimeError(
|
|
285
|
+
f"MaaFW Runner 环境准备失败 (exit={result.returncode}): {detail[:800]}"
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _installed_maafw_version(
|
|
290
|
+
python_executable: Path,
|
|
291
|
+
env: dict[str, str],
|
|
292
|
+
) -> str | None:
|
|
293
|
+
probe_env = env.copy()
|
|
294
|
+
probe_env.pop("PYTHONPATH", None)
|
|
295
|
+
try:
|
|
296
|
+
result = subprocess.run(
|
|
297
|
+
[
|
|
298
|
+
str(python_executable),
|
|
299
|
+
"-c",
|
|
300
|
+
"import importlib.metadata as m; print(m.version('maafw'))",
|
|
301
|
+
],
|
|
302
|
+
capture_output=True,
|
|
303
|
+
timeout=15,
|
|
304
|
+
text=True,
|
|
305
|
+
encoding="utf-8",
|
|
306
|
+
errors="replace",
|
|
307
|
+
env=probe_env,
|
|
308
|
+
)
|
|
309
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
310
|
+
return None
|
|
311
|
+
if result.returncode != 0:
|
|
312
|
+
return None
|
|
313
|
+
version = result.stdout.strip()
|
|
314
|
+
return version or None
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _normalized_sys_path(path: str) -> str:
|
|
318
|
+
try:
|
|
319
|
+
return str(Path(path).resolve())
|
|
320
|
+
except (OSError, RuntimeError):
|
|
321
|
+
return path
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _reset_managed_venv(venv_path: Path, managed_root: Path) -> None:
|
|
325
|
+
resolved_venv = venv_path.resolve()
|
|
326
|
+
if (
|
|
327
|
+
resolved_venv.parent != managed_root.resolve()
|
|
328
|
+
or not resolved_venv.name.startswith("maafw_runner_")
|
|
329
|
+
):
|
|
330
|
+
raise RuntimeError(f"拒绝重建非托管 MaaFW Runner venv: {venv_path}")
|
|
331
|
+
shutil.rmtree(resolved_venv, ignore_errors=True)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _venv_python(venv_path: Path) -> Path:
|
|
335
|
+
if os.name == "nt":
|
|
336
|
+
return venv_path / "Scripts" / "python.exe"
|
|
337
|
+
return venv_path / "bin" / "python"
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def _is_valid_venv(venv_path: Path) -> bool:
|
|
341
|
+
return _venv_python(venv_path).is_file() and (venv_path / "pyvenv.cfg").is_file()
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def _venv_bootstrap_python() -> str:
|
|
345
|
+
portable_python = Path.cwd() / "environment" / "python" / "python.exe"
|
|
346
|
+
if portable_python.is_file():
|
|
347
|
+
return str(portable_python)
|
|
348
|
+
return sys.executable
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def _send_log(send_log: Callable[[str], None] | None, message: str) -> None:
|
|
352
|
+
if send_log is not None:
|
|
353
|
+
send_log(message)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Literal
|
|
4
|
+
|
|
5
|
+
from automas_maafw_agent_env.models import MaaFWAgentCommandPlan
|
|
6
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
MaaFWControllerType = Literal["Adb", "Win32"]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MaaFWResolvedPath(BaseModel):
|
|
13
|
+
raw: str
|
|
14
|
+
resolved: str
|
|
15
|
+
exists: bool
|
|
16
|
+
isFile: bool = False
|
|
17
|
+
isDir: bool = False
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class MaaFWResourceBundlePlan(BaseModel):
|
|
21
|
+
name: str
|
|
22
|
+
label: str | None = None
|
|
23
|
+
paths: list[MaaFWResolvedPath] = Field(default_factory=list)
|
|
24
|
+
attachedPaths: list[MaaFWResolvedPath] = Field(default_factory=list)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class MaaFWTaskRunPlan(BaseModel):
|
|
28
|
+
name: str
|
|
29
|
+
label: str | None = None
|
|
30
|
+
entry: str
|
|
31
|
+
options: dict[str, Any] = Field(default_factory=dict)
|
|
32
|
+
pipelineOverride: dict[str, Any] = Field(default_factory=dict)
|
|
33
|
+
logOptions: dict[str, Any] = Field(default_factory=dict)
|
|
34
|
+
overrideNodes: list[str] = Field(default_factory=list)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class MaaFWSkippedTaskPlan(BaseModel):
|
|
38
|
+
name: str
|
|
39
|
+
label: str | None = None
|
|
40
|
+
entry: str | None = None
|
|
41
|
+
reason: str
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class MaaFWPretaskRunPlan(BaseModel):
|
|
45
|
+
name: str
|
|
46
|
+
label: str | None = None
|
|
47
|
+
executable: str
|
|
48
|
+
args: list[str] = Field(default_factory=list)
|
|
49
|
+
options: dict[str, Any] = Field(default_factory=dict)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class MaaFWRunPlan(BaseModel):
|
|
53
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
54
|
+
|
|
55
|
+
path: str
|
|
56
|
+
projectName: str
|
|
57
|
+
projectLabel: str | None = None
|
|
58
|
+
controllerName: str
|
|
59
|
+
controllerType: str
|
|
60
|
+
resourceName: str
|
|
61
|
+
resource: MaaFWResourceBundlePlan
|
|
62
|
+
agents: list[MaaFWAgentCommandPlan] = Field(default_factory=list)
|
|
63
|
+
pretasks: list[MaaFWPretaskRunPlan] = Field(default_factory=list)
|
|
64
|
+
piEnv: dict[str, str] = Field(default_factory=dict)
|
|
65
|
+
tasks: list[MaaFWTaskRunPlan] = Field(default_factory=list)
|
|
66
|
+
skippedTasks: list[MaaFWSkippedTaskPlan] = Field(default_factory=list)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class MaaFWDeviceConfig(BaseModel):
|
|
70
|
+
type: MaaFWControllerType
|
|
71
|
+
adbPath: str | None = None
|
|
72
|
+
address: str | None = None
|
|
73
|
+
hWnd: int | None = None
|
|
74
|
+
screencapMethods: int = 0
|
|
75
|
+
inputMethods: int = 0
|
|
76
|
+
screencapMethod: int = 0
|
|
77
|
+
mouseMethod: int = 0
|
|
78
|
+
keyboardMethod: int = 0
|
|
79
|
+
config: dict[str, Any] = Field(default_factory=dict)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class MaaFWRunResult(BaseModel):
|
|
83
|
+
success: bool
|
|
84
|
+
projectName: str
|
|
85
|
+
controllerName: str
|
|
86
|
+
resourceName: str
|
|
87
|
+
completedTasks: list[str] = Field(default_factory=list)
|
|
88
|
+
failedTask: str | None = None
|
|
89
|
+
errorMessage: str | None = None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class MaaFWRunnerJobPayload(BaseModel):
|
|
93
|
+
plan: MaaFWRunPlan
|
|
94
|
+
deviceConfig: MaaFWDeviceConfig
|