Skip to content

feat(update): 支持 MAA、MaaEnd 与 MFW 家族选择全量或增量更新 - #1949

Merged
youngslin merged 5 commits into
AUTO-MAS-Project:devfrom
youngslin:feat/incremental-update-mode-local
Nov 13, 2026
Merged

youngslin merged 5 commits into
AUTO-MAS-Project:devfrom
youngslin:feat/incremental-update-mode-local

Conversation

@youngslin

@youngslin youngslin commented Nov 12, 2026 •

Copy link
Copy Markdown

摘要

  • MAA、MaaEnd、MFW、M9A、MSS 在接管更新旁新增“全量 / 增量”,默认全量;选择增量时提示风险,必要时回退全量。
  • MAA 复用官方 OTA 安装入口,支持 GitHub 匹配 OTA 和 Mirror酱增量;资源增量先合并为校验后的完整共享缓存。MFW 家族按选择控制 Mirror酱差量与 GitHub 区间下载,在安全边界内回退全量。
  • MaaEnd 专用更新器接入自建站字节增量,由 MAS 重建归档并核对独立从 GitHub 获取的官方整包大小、SHA256,再走现有可恢复安装器;无需 CDK。首次无可信旧包、版本对不匹配、站点不可用或校验失败时回退原有全量源。仅选择增量时保存下一次可复用的归档。
  • 增量生成与上传 Action 在配套数据仓库 AUTO-MAS-Copilot-Data #4,不在本 PR。MaaEnd 每架构/通道使用独立站点文件;源未部署时仍可回退全量。前端 API 类型通过生成器更新,只保留一个更新日志碎片。

审查修复

  • 合法资源基线带根目录或子目录 .gitignore 时,重建增量缓存采用与分发相同的忽略规则;保留未改文件及摘要校验,不再误触发全量。
  • MaaFW 识别真实 UpdateApplyError → PayloadError → MaaFWProjectUpdateError 链;登记前仅回退一次全量。真实坏指纹、取消、两种磁盘错误、登记后回调失败及全量请求仍返回坏增量均有回归;旧载荷内容全程保持不变。只隔离 HTTP 和磁盘故障,不替换构建、校验或登记逻辑。
  • MAA 成功入缓存后,持同架构/通道锁清理旧基线,保留最近三份和 24 小时内缓存,避免同日下载额度重复消耗;不动完整包、其他通道、安装目录待更新包或链接指向的目录。测试覆盖最近三份及当天多实例缓存保护。
  • 以上故障先在原逻辑复现再修复;更新保存与执行结果继续复用已有遥测入口,不新增逐包事件。

测试与验证

  • Windows,项目锁定 Python 环境、Yarn 4、Edge;审查修复验证提交 c62ecffd557f5c3597eb5e53d7e79fc93fafa39f,已合并上游 dev 的 5dc79aebd 并解决三种语言与统计覆盖文档冲突。
  • 审查专项回归:uv run --no-sync python -m pytest tests/task/test_maa_fluxdown_update.py tests/task/test_maafw_update_fallback.py -k 'delta_fallback_uses_real or resource_delta_rebuilds or prunes_old_baselines' -q -p no:cacheprovider --tb=short:9 passed。
  • 前端 corepack yarn test --run:358 passed;corepack yarn typecheck、lint、format:check 通过,lint 仅有原有 QueueDeleteButton 属性默认值警告。ruff format --check .、相关文件 Ruff 检查、uv lock --check、更新日志碎片与 diff 检查通过。check_reverts 仅提示增量接口签名与夹具的对应调整;同日新版突破旧额度限制的原回归仍保留并通过。
  • corepack yarn e2e --grep 'package mode persists':2 passed,隔离配置、不填写 CDK,覆盖 MAA/MaaEnd 禁用状态、选择增量、保存、风险提示、刷新保留。产出截图、HTML 报告和 trace;corepack yarn e2e:check 通过。
  • 此前真实官方 x64 ZIP v3.1.0-beta.4 → v3.1.0-beta.5:两包核对官方 SHA256 后,用数据仓库生成器制包,再用本 PR 的客户端解码。全量 316,003,772 字节,增量 11,172,712 字节,节省约 96.5%;重建的官方整包 SHA256 为 05b516305872c8b8a41fa0235d5ecfedef0157d1822dbe7147cc3112c00477a4。
  • 上述重建包经 MAS 真实 apply_update 入口安装到独立临时目录,安装回执、目标版本/架构及用户配置保留均通过;没有启动 MaaEnd 或游戏,没有修改用户实际安装。
  • 新增回归随实现提交:MaaEnd 站点有效增量、损坏包/基线不匹配回退、取消不触发全量,及前一提交的 MAA OTA、软件缓存、资源合并等场景。

已知验证边界

  • 本次完整后端回归(c62ecffd5,Windows):uv run --no-sync python -m pytest tests -q -p no:cacheprovider --tb=short:2076 passed、1 skipped、1 failed,17 subtests passed。唯一失败仍为 tests/utils/test_fluxdown_detect_managed_priority.py::test_incomplete_managed_fluxdown_falls_back:本机完整 res/fluxdown-cli 优先于夹具的外部目录。此前未修改上游 5aa609284 独立工作树在相同工作目录已复现同一失败;该检测实现及测试从 5aa609284 到本次合并的 5dc79aebd 均无变化,本 PR 也未修改。没有新增测试失败。
  • 此前原生 Electron 开发版真实 Computer Use 验证通过:使用 Windows 桌面工具实际操作 MaaEnd 页面,确认接管关闭时更新方式禁用、开启后可切换全量/增量、增量风险与回退提示正确显示、切回全量时提示消失;刷新页面及隔离后端重启后,选择仍保留。只操作 36174 端口的隔离测试数据,正式 MAS 队列始终为空;未启动游戏或真实账号任务。本地已保存全量/增量两张桌面截图。
  • 尚无 arm64 真实包和正式下载站发布/回读证据。数据仓库 README 已列出独立文件、Access Key 和四个文件 ID 变量的启用要求;本次不合并、不触发生产发布。

选项名称已统一为“全量更新 / 增量更新”,默认仍为全量;文案调整后类型检查、格式检查与两个更新设置组件测试通过。

@github-actions github-actions Bot added 模块: 更新 按更新日志碎片自动设置 类型: 新增 按更新日志碎片自动设置 labels Nov 12, 2026
Comment on lines +490 to 500
raise _DownloadError("资源增量基线已失效,必须重新下载全量包")
complete = stage_tmp / "complete"
shutil.copytree(_STAGE_DIR / "resource", complete)
merge_package_tree(root, complete)
root = complete
expected = len(resource_tree_hashes(root))
else:
expected = count_zip_entries(zip_path, prefix=prefix)
file_hashes = resource_tree_hashes(root)
_MANIFEST_FILE.unlink(missing_ok=True)
if _STAGE_DIR.exists():

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: Discrepancy in counting .gitignore files during incremental stage validation causes _DownloadError and forces unexpected fallbacks to full downloads.
Severity: MEDIUM

Suggested Fix

Align file counting between expected and actual. Either ignore .gitignore files when calculating actual via rglob, or exclude .gitignore during copytree when building the incremental stage tree.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: app/task/MAA/tools/resource_update.py#L484-L500

Potential issue: In `_build_stage`, the expected file count for incremental packages is
calculated via `len(resource_tree_hashes(root))`, which ignores `.gitignore` files.
However, `actual` is calculated by counting all files in `_STAGE_DIR / 'resource'`,
including `.gitignore` files copied from the baseline tree during `copytree`. If
`.gitignore` files exist in the baseline directory, `actual` exceeds `expected`, raising
`_DownloadError` and causing incremental updates to fail and fall back to full package
downloads every time.

Did we get this right? 👍 / 👎 to inform future reviews.

Comment on lines +81 to 91
) -> Path:
"""本体更新共享缓存目录:按系统、架构、通道隔离,互不覆盖。"""
return Path.cwd() / "data" / "maa_update" / "software" / os_name / arch / channel
root = Path.cwd() / "data" / "maa_update" / "software" / os_name / arch / channel
return (
root / ("base-" + hashlib.sha256(base_version.encode()).hexdigest()[:16])
if base_version
else root
)


def load_cache_manifest(cache_dir: Path) -> dict[str, str] | None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: Versioned base-* cache directories for incremental software updates are never cleaned up, resulting in unbounded disk usage.
Severity: LOW

Suggested Fix

Implement a cleanup routine to remove obsolete base-* directories within {channel} when a new software package is cached or after an update completes successfully.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: app/task/MAA/tools/software_package.py#L76-L91

Potential issue: `software_cache_dir` creates versioned cache directories prefixed with
`base-<hash>` when a base version is specified. Because there is no pruning mechanism or
cleanup routine for old `base-*` directories, each incremental update leaves past
package caches behind, causing cumulative unbounded disk space consumption over multiple
updates.

Also affects:

  • app/task/MAA/tools/software_update.py:466~491
  • app/task/MAA/tools/software_package.py:287~298

Did we get this right? 👍 / 👎 to inform future reviews.

Copy link
Copy Markdown
Contributor

总体结论:修改后合并

AUTO-MAS-Project/AUTO-MAS #1949

宝宝,这次没有另起一套安装器,MAA 继续交给官方更新入口,MFW 家族也沿用了现有的新版本构建与登记流程,这个边界保留得好喵。不过增量路径还漏了两处:一处把合法资源缓存误判成损坏,另一处没接到真正需要回退全量的异常,先补好再合啦。

配置和前端负责保存全量/增量偏好;MAA 软件模块准备更新包,资源模块先拼出完整缓存再分发;MaaFW 更新模块则在临时目录构建、校验新版本,通过后才登记并切换。下面两项都是静态源码推演,未执行更新或复现。

MAA 资源更新

[逻辑 · P2] 基线含 .gitignore 时,合法增量会被误判损坏

位置:resource_update.py:487–514

启动/预取进入资源更新后,_build_stage 用 copytree 复制完整基线,再覆盖增量。这里会保留基线中的 .gitignore,但新增的 expected = len(resource_tree_hashes(root)) 不统计它,后面的 actual 却统计所有文件,所以两者必然不相等。

这不是假设缓存里随便多放了文件:官方 MaaResource 的资源目录确实包含 .gitignore。先使用 GitHub 全量资源包,再切换 Mirror 酱并选择增量,就能复用这样的合法基线。现有基线校验同样忽略 .gitignore,不会提前拦住它;原全量流程按 ZIP 条目计数,也能正常通过。

结果是先下载增量,再报“暂存树不完整”并重新下载全量;全量回退也失败时,本轮资源更新失败。旧安装目录的保护仍在,不是安装内容被破坏,但这条路径省流量的目的就落空啦。

请在资源暂存模块统一文件集合:复制基线时同样排除 .gitignore,或让预期数和实际数采用一致规则,同时保留内容摘要校验。现有增量重建测试加一个含 .gitignore 的有效基线,确认增量成功、未改文件保留且没有请求全量,就能抓住这只漏网 bug。

MaaFW / M9A / MSS 更新

[逻辑 · P2] 新增全量回退只看一层异常,接不到差量校验失败

位置:updater.py:860–864

选择优先增量、通过 Mirror 酱取得差量包后,若包的基线版本、指纹或文件哈希校验失败,手动和自动更新都会走到 apply_maafw_project_update → build_from_package。底层抛出的 UpdateApplyError 先被 payloads.py:761–768 包成 PayloadError,再被 updater.py:1797–1814 包成 MaaFWProjectUpdateError。

因此,新增判断看到的直接 __cause__ 是 PayloadError,不是 UpdateApplyError,不会进入全量回退,而是直接结束更新。当前版本仍受临时目录和登记前保护,不会因此损坏;但用户得手动切全量重试。这里指出的是本次新增回退分支未接通,原有校验拒绝坏差量本身没有错,别为了回退放宽校验喵。

请由构建/应用边界保留明确的可回退错误类别,或准确识别现有异常链,继续排除取消、登记后错误及无关磁盘错误,不要一律重试。补一个从真实 build_from_package 校验失败开始的测试,确认只回退一次全量且旧版本保持不变;只模拟一层异常包装会漏掉实际调用链。

已读取现有 CI 日志:Ubuntu 为 1973 passed、39 skipped,Windows 为 2011 passed、2 skipped,两者另有 17 subtests。它们不能替代上述缺失场景;dot酱未独立运行项目代码、测试或真实安装验证。

—— dot酱拍拍爪敬上 🐾

@youngslin youngslin changed the title feat(update): 支持 MAA 与 MFW 家族选择全量或增量更新 feat(update): 支持 MAA、MaaEnd 与 MFW 家族选择全量或增量更新 Nov 13, 2026

Copy link
Copy Markdown
Contributor

总体结论:可以合并

宝宝,上一轮两处问题都补好了喵:资源增量复制已统一排除 .gitignore;MFW 全量回退也接到了真实异常链,而且保留了取消、磁盘故障和登记后的保护,没有为了重试放宽校验。

新增缓存清理也核对过了:与取包、复制共用通道锁,保留近期和当前缓存,不会顺手删掉安装目录里的待更新包。合入 dev 后的配置衔接未发现新的必须修改项,这次可以放行啦。

dot酱只做了静态复核,未执行测试或真实安装更新。

—— 把增量回退这条线检查完的 dot酱敬上 🐾

@youngslin
youngslin merged commit ad3de27 into AUTO-MAS-Project:dev Nov 13, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

模块: 更新 按更新日志碎片自动设置 类型: 新增 按更新日志碎片自动设置

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants