Skip to content

feat(runtime): 接入 Runtime 初始化、监督与更新链路 - #528

Closed
qiyinxi wants to merge 57 commits into
AUTO-MAS-Project:devfrom
qiyinxi:integ/runtime-20260901
Closed

feat(runtime): 接入 Runtime 初始化、监督与更新链路#528
qiyinxi wants to merge 57 commits into
AUTO-MAS-Project:devfrom
qiyinxi:integ/runtime-20260901

Conversation

@qiyinxi

@qiyinxi qiyinxi commented Sep 2, 2026

Copy link
Copy Markdown
Contributor
  • Runtime 灰度模式接管首次初始化、后端监督和后端更新,并保留旧启动链路。
  • Electron 消费 Runtime 的进度、错误处置与取消事件;安装包下载并校验固定版本可执行文件。
  • 补齐受监督模式、受管路径与锁文件,以及 MaaFW Runtime 基础设施适配。

qiyinxi and others added 30 commits September 1, 2026 21:13
新增 app.utils.is_supervised()(精确匹配 AUTO_MAS_SUPERVISED=1),main.py 与
app/api/core.py 共用同一判据,让后端遵守 AUTO-MAS-Runtime 的监督约定:

- main() 的提权判断抽成纯函数 should_restart_as_admin() 单独测试;受监督时
  永远不 restart_as_admin()(会逃出 Job Object),非管理员时改为记 warning
  并继续运行,不非零退出。
- resolve_http_port() 受监督时固定返回 36163,忽略 AUTO_MAS_HTTP_PORT 与开发
  环境判据(监督器健康检查/关闭请求都硬编码打这个端口)。
- is_backend_dev_mode() 受监督时恒为 False,优先级高于 AUTO_MAS_DEV 与
  AUTO_MAS_ENV,_shutdown_backend() 才会走完整 teardown 并真实置退出标志。

.env.example 补充说明受监督时上述开发环境行为不生效。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- BackendHealthOut 新增 protocol/version/commit:受监督(AUTO_MAS_SUPERVISED=1)
  且监督器注入了对应期望值时原样回显,否则回退本地 Config.VERSION 与空字符串;
  protocol 固定返回后端自身支持的版本,不回显注入值。
- 修正 pyproject.toml 版本号滞后(5.5.0b2 -> 5.5.0b3),并把它纳入
  check-version-json.yml 的版本一致性校验(PEP 440 规范化比较)。
后端曾往根目录 runtime/ 下写 MaaFW job 文件与 HSR SRA 临时配置,与即将接管
该目录的外部监督器 AUTO-MAS-Runtime 撞名。两处改落到受保护的 data/ 目录,
并新增 app/utils/io.migrate_legacy_dir 公共辅助函数:首次访问新路径时若旧
目录存在则整体搬迁一次,失败只记 warning 不阻塞。
按 AUTO-MAS-Runtime 的 internal/protocol/*.go 定义事件判别联合、stage/state/
capability/remediation 字面量、错误码全集与其稳定行为四元组,以及 stdin 控制命令类型。
一切按 type/code/success/stage/status 判定,不解析中文 message。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
处理 chunk 边界半行、空行与 CRLF;单行 JSON 解析失败产生 RUNTIME_PROTOCOL_ERROR
条目而不是被吞掉;未知事件类型按「忽略未知」返回,不拒绝整条协议。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
以可执行文件路径加参数数组 spawn Runtime,固定 --output ndjson --protocol 1;
run() 等 hello 后聚合事件并返回终态 result,supervise() 返回可发 status/cancel/
shutdown 的长驻句柄;六个调用侧错误码统一收敛为 RuntimeClientError;
控制命令的 commandId 按 Runtime 的校验规则生成规范 ULID;
保留与 backendService 一致的 formatStartupLogs 展示格式。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
夹具由本机构建的 auto-mas-runtime.exe 在临时 app-root 上真实跑出:
version、doctor、backend supervise 参数错误、取消并带 warning,以及
--protocol 2 与未知子命令的 stderr。共 48 条用例。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
用 dependencies check 的真实 GIT_REPOSITORY_INVALID 输出覆盖 retryable=true 的失败路径,
并确认 hello.capabilities 随命令变化。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AUTO-MAS-Runtime 用 Job Object 监督后端时不提供便携 Python 也不把 uv 加进
PATH,只通过 AUTO_MAS_UV_EXE 注入已校验过的路径;_find_uv_executable 补上
这一优先级,找不到再退回原有的便携路径与 PATH 查找。
_venv_bootstrap_python 在受管模式下会落到 sys.executable(监督器管理的
venv),监督器 repair/rebuild 把它整个删掉重建后,旧隔离 venv 的
pyvenv.cfg 里 home 会指向一个不存在的目录,venv 从此静默失效。
_is_valid_venv_path 补上这层校验,判定失效时沿用既有的重建路径。
后端被 AUTO-MAS-Runtime 用 Job Object 监督(KILL_ON_JOB_CLOSE)后,
creation_flags/detached_flags 叠加 CREATE_BREAKAWAY_FROM_JOB,让模拟器、
游戏、自更新安装程序等不该被回收的进程显式脱离,保持“关闭 AUTO-MAS 不关
游戏”的现有行为。父进程所在 Job 不允许 breakaway 时 CreateProcess 会以
ERROR_ACCESS_DENIED 失败,open_process 去掉该标志重试一次并记 warning。

同步补充 res/version.json 变更记录。
…ime-20260901

# Conflicts:
#	res/version.json
…runtime-20260901

# Conflicts:
#	res/version.json
- 新增 app/utils/paths.py 的 SOURCE_ROOT/resource_path(),替换 main.py、config.py、
  maa_manager.py、notification.py、BetterGI/MaaEnd/MaaFW/Okww 专项里全部 res/ 下
  内置资源的 Path.cwd() 引用,使其改为相对源码位置解析
- main.py 的 chdir 受 AUTO_MAS_SUPERVISED=1 门控:受监督时源码在 repo/ 子目录,
  不能 chdir 进去,工作目录由监督器决定;main() 里 is_telemetry_enabled 读取的
  Config.json 属用户数据,改回相对 Path.cwd() 解析
- wuthering_waves_updater.py 的 hpatchz 下载缓存从旧便携 Python 目录名
  environment/ 迁到受保护的用户数据目录 data/cache/

真实布局验证:T 根为工作目录、T/repo 为源码目录(app/res 用 junction 接入 w4cwd),
supervised+dev 环境变量下启动,config/data/debug/history 均创建在 T 根而非 T/repo,
health 端点就绪后 close 正常关闭。
…time-20260901

# Conflicts:
#	res/version.json
受监督时优先采用 AUTO_MAS_UV_CACHE_DIR / AUTO_MAS_UV_PYTHON_INSTALL_DIR
指定的共享目录(无效注入回退池本地目录并记 warning),包索引与 Python
分发源改为按 AUTO_MAS_MIRROR_PACKAGE_INDEX / AUTO_MAS_MIRROR_PYTHON 下发
的有序列表逐个重试,成功使用的索引写入 installer_metadata;未受监督时
行为不变。对应契约 doc/契约补充-v1-增补1.md C11。
_canonicalize_pool_paths 恢复「python_root/cache_dir 不得逃出 pool_root」的
断言(_path_is_within 一并恢复),默认对两侧都生效,与受监督前完全一致。
两个目录解析函数改为内部先经 *_with_source 变体拿到 (path, injected),
resolve_python_interpreter 及其三个下游函数(_find_pool_managed_python /
_install_pool_managed_python / _select_uv_python_version)都新增
python_injected/cache_injected 形参(默认 False),只有真正解析自
AUTO_MAS_UV_CACHE_DIR / AUTO_MAS_UV_PYTHON_INSTALL_DIR 注入值的那一侧才跳过
断言,两侧互不影响;resolve_uv_cache_dir/resolve_python_install_dir 对外签名不变。
…ime-20260901

# Conflicts:
#	res/version.json
把 bootstrap 的 stage 映射回现有初始化界面的 7 段模型:uv.* 与 python.* 归
python 段、workspace.* 归 repository 段、dependencies.* 归 dependency 段,
未知 stage 落到通用段而不是拒绝协议。桥接只向前推进,因为真实 bootstrap 的
顺序是 uv -> 仓库 -> Python -> 依赖,按事件重开段会让界面倒退。

夹具 bootstrap-success.ndjson 是本机 auto-mas-runtime.exe 对合成分支
v9.9.9-alpha.1 真实跑出来的:73 条 progress 事件没有一条带 percent,
依赖同步阶段一条 progress 都没有,所以段内进度不编造,只有段开始 10%
与段结束 100%。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
off 走原有五步安装链,一行未动;development 跳过全部安装步骤(开发检出自带
.venv,Runtime 只监督不创建),六段各发一个完成后直接起后端;managed 用一次
bootstrap --version v<应用版本> 顶掉装 Python / 装 pip / 装 Git / clone 仓库 /
pip 装依赖,成功后仍由 backendService 起 backend supervise。

进度形状保持旧的 7 段模型,只额外加一个机器可读的 status;失败结果额外带上
code / retryable / remediation / logs,渲染进程暂不消费。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
qiyinxi and others added 26 commits September 1, 2026 22:17
用真实 bootstrap 夹具回放桥接,断言事件流里除顶层 bootstrap 外的每个 stage
都有显式对应,且三段各只出现一次 started 与 completed。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
W9d 的初始化界面要按机器字段决定失败时给哪些按钮,缺三样东西,这里只加不改地补上:

- RuntimeStageOutcome / InitializationResult 补 logPath,取自 Runtime 的
  result.details.logPath,供「打开日志」直接 shell.openPath;
- 进度对象补 runtimeMode,另新增 get-runtime-init-context 通道一次给出模式、
  回退日志文件与各段可用镜像键(镜像键从 W9b 的映射表原样导出,映射表仍是唯一真相源),
  界面开局问一次即可,不必等第一条进度事件;
- 单步重试通道补 rebuild 参数:界面的「重试」与「重建环境」是两个按钮,显式传值时
  以它为准,不传仍按上一次失败的 remediation 决定,W9b 原有行为不变。

doctor 的逐项检查原样带进 check-critical-files 的返回值,供「运行诊断」展示。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
构建流程新增一步,从 AUTO-MAS-Runtime 的 GitHub Release 下载钉死版本的
auto-mas-runtime-<版本>.exe 与 SHA256SUMS.txt 并校验 SHA-256,通过后改名为
auto-mas-runtime.exe;打包产物生成后原样拷入 dist\win-unpacked\resources,
Lite 与 Full 安装包会一起带上它(两者本就是把该目录整体打包)。不经
electron-builder 的 extraResources 声明,避免本地未预先下载该文件时
yarn build 直接失败。Runtime 不自更新,随桌面安装包整体升级;后续若有
依赖 Runtime 新行为的改动,需等 Runtime 发新版后再手动提升 RUNTIME_VERSION。
新增 runtimeUpdateService:向监督进程发 shutdown 等到退出,再跑
bootstrap --version v<新版本>,成功后重新 backend supervise,替代后端
自己下整包拉安装器的旧流程。失败按 shutdown / bootstrap / restart 三类
结局分流,各自给出对应的重试入口。

进度桥接、阶段映射与单步重试全部复用初始化链路,只在首尾各加一个停机与
重启的进度态。为此给初始化编排器加了目标版本注入、显式处置强度与在途取消,
给 Runtime 客户端的一次性命令加了 onStarted 控制入口以下发 stdin cancel。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
新增 update-backend-via-runtime、retry-backend-update、cancel-backend-update
与 backend-update-progress 事件通道,另加 get-runtime-launch-mode 让渲染进程
知道当前走哪条启动链路。清理资源时一并丢弃更新会话。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
managed 模式下「检测到后端更新」改调新的 Runtime 更新 IPC,弹窗展示分段进度,
失败时按停机、同步、重启三类结局给出对应按钮;development 模式下入口置灰并说明
不支持自动更新;灰度开关关闭时行为不变。三份词表同步补齐。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
断言 stopBackend、bootstrap --version、startBackend 的严格先后;停不掉后端时
bootstrap 不跑;克隆失败与依赖同步失败各自的重试入口命令;新后端起不来时展示
整块启动日志;取消、版本号规范化与非法输入拒绝、development 模式不发命令。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
decideFailureActions 只吃机器字段(code / retryable / remediation / stage /
runtimeMode),返回有序的动作列表加上要不要展开镜像面板、要不要多说一句话,
界面照着渲染即可,不再解析任何中文文案。三条硬规则:

- retryable=false 不出任何重试类按钮,哪怕 remediation 里写了;
- INTERNAL_ERROR 一律按不可重试处理,只给日志和一句「请携带日志反馈」;
- 认不出来的 remediation 按协议要求忽略,一条都认不出来时退回现有行为
  (重试 + 失败态显示切换镜像),旧链路缺字段时同样如此。

另加 filterRuntimeMirrors:Runtime 收不下的镜像源不摆出来,键名以主进程从
W9b 映射表导出的为准,界面不自己抄一份。

electron.d.ts 补上进度与失败对象的新可选字段;顺带删掉 installDependencies
在同一接口里过时的重复声明(它遮蔽了带 rebuild 的权威签名)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
失败态不再固定给「重试 + 镜像面板」,改为渲染 decideFailureActions 算出的动作:
换镜像重试打开按 Runtime 映射表过滤后的镜像列表,重建环境走同一重试通道带
rebuild,打开日志优先用 Runtime 给的 logPath、没有就退回本程序日志,运行诊断
调 check-critical-files 并把 doctor 的逐项结果列出来;失败日志仍整块 pre 展示。
自动重试只在决策给出重试类动作时才启动。

Runtime 模式下 pip 与 git 两段标为「由运行时接管」并直接置完成(mirror 段本就
不在步骤条上),python 段标题改为「准备运行环境」,全部按主进程给的模式判断,
不看中文文案。三份词表各加 15 条 key。

六个步骤的初始状态改由工厂函数生成,免得加字段时漏掉其中一份。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- _get_repo() 改用 app.utils.paths.SOURCE_ROOT 打开仓库:受 AUTO-MAS-Runtime 监督时
  源码在 <app-root>/repo/,工作目录(app-root)下没有 .git,按 Path.cwd() 打开必然失败,
  /api/info/version 会永远返回 if_need_update=True,标题栏常驻「检测到后端更新」
- get_git_version() 受监督时一律视为最新:更新由 Runtime 整体替换 repo/ 完成、不在旧仓库
  上 fetch,比对 origin/<branch> 没有意义;managed 模式直接回显注入的
  AUTO_MAS_EXPECTED_COMMIT,不再依赖受管布局里并不存在的 git 命令行;development 模式
  无注入身份,沿用源码目录的 Git 信息用于展示
- environment/git/bin/git.exe 候选路径保持相对工作目录:它是桌面端装在安装根下的便携
  工具,与源码目录是否分离无关

验证:新增 tests/core/test_git_version.py 5 例;本树 .venv 全量 pytest 377 通过、3 跳过、
1 失败(tests/task/test_maa_config_overrides.py 在纯净 HEAD 导出上同样失败,与本次无关);
以无 .git 的临时目录为工作目录、本树为源码目录实跑 get_git_version():managed 回显注入
HEAD 且不导入 GitPython,development 与未受监督均读到本树真实 HEAD。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
仓库没有 @vue/test-utils,也没有 DOM 环境,用 vue 自带的 SSR 渲染器出一份 HTML,
把 ant-design-vue 组件换成占位实现后断言 StepPanel 在给定失败对象时渲染出的按钮
集合与顺序:依赖同步失败给「重试 / 重建环境 / 打开日志」且不带镜像面板,
INTERNAL_ERROR 只给「打开日志」加内部错误说明,旧链路缺字段时仍是原来的
「用选中的镜像源重试」加镜像面板,诊断结果按 doctor 的逐项结果展示。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
上一提交为格式化新加的词条顺手对整个 locales 目录跑了 oxfmt,把英日词表里
原本就超长的既有词条一并重排了。三份词表在本分支基线上就不符合 oxfmt,
不该由本次改动顺带修,现在只保留新增的 14 条 key。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
# Conflicts:
#	frontend/electron/ipc/initializationHandlers.ts
#	frontend/electron/services/runtimeInitializationService.ts
#	frontend/src/types/electron.d.ts
#	res/version.json
resolveRuntimeLaunchMode() 从只读 AUTO_MAS_RUNTIME_MODE 升级为三级优先级:
环境变量 > 持久化的用户设置(frontend_config.json 的 Runtime.LaunchMode,
与 main.ts 的 loadConfig()/saveConfig() 同一份文件)> 构建默认值(打包安装
且已捆绑 Runtime 时为 managed,否则 off)。任一级取值非法只 warn 后落到下
一级,不再直接判 off。新增 get/set-runtime-launch-mode IPC,设置页开发者
选项区加「后端运行方式」下拉,展示当前生效值与来源,重启后生效。函数签名
新增必填 appRoot 参数,同步更新 backendService.ts 的调用点。
新增 runtimeEnv.ts 的 buildRuntimeEnv(appRoot):读后端持久化的
GlobalConfig.Function.IfEnableTelemetry(与 Electron 主进程自身 Sentry 开关
同一份 Config.json、同一条判定规则),关闭时返回 { AUTO_MAS_TELEMETRY:
'disabled' },开启时不设该变量,不动 --offline。

新增 runtimeClientFactory.ts 的 createRuntimeClient(),统一注入上述环境
变量;runtimeInitializationService.ts 的 defaultClientFactory 与
backendService.ts 的 startBackendViaRuntime 两处构造点都改走它,不改各自
原有的控制流程。

同步修正 initializationService.test.ts 里假 RuntimeClient 的挂载点:
createRuntimeClient 从 './client' 直接拿类,只替身桶文件 './runtime' 拦
不到这次构造,需要在 './runtime/client' 上也接同一个假类。
…me-20260901

# Conflicts:
#	frontend/src/types/electron.d.ts
T13.4 起 Runtime 在 dependencies sync 时改写锁副本里的下载地址参与镜像轮换,
显式指定的 package-index 镜像排在尝试顺序最前,依赖段不再是「给了按钮只会弹空面板」。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
mapMirrorSelection 原先直接拿界面传入值当 MirrorSource.key 查表,但
MirrorRotationService.execute(..., preferredMirrorName) 按 mirror.name 匹配,
渲染进程存的选中值也是 name,几乎必然查不到,导致 Runtime 模式下「换镜像
重试」静默退化成不带 --mirror 的普通重跑。改为先用该段对应的旧镜像类型在
getMirrors() 里按 key 或 name 找到条目,再用条目的 key 查表;mapMirrorSelection
因此多了一个 mirrorService 依赖,从 initializationService 已有的实例注入,
不新建第二个。

T13.4 起 Runtime 允许显式 --mirror package-index=<键>(改写锁副本,不覆盖
uv.lock 冻结的 registry URL),顺带给 dependency 段补上 aliyun / tsinghua /
ustc 三个键名相同的映射;旧 official 对应 Runtime 的 pypi,键名对不上,不
假定是同一个源,仍不映射。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
W9b 把 mirrorService 设为必填后,W9f 的 updateBackendViaRuntime 构造点缺该字段,
electron 类型检查失败;这里复用与初始化流程同源的 MirrorService(appRoot)。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…time-20260901

# Conflicts:
#	res/version.json

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @qiyinxi, your pull request is larger than the review limit of 150,000 diff characters

@ClozyA

ClozyA commented Sep 3, 2026

Copy link
Copy Markdown
Member

@sourcery-ai

@qiyinxi

qiyinxi commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

#550 取代:同一条集成分支的更新版本,已同步到最新 dev、解掉与全仓清理 PR 的冲突、清理了历次并集解冲突残留的 changelog 副本,并补上真机联调后修掉的问题。

@qiyinxi qiyinxi closed this Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants