MaaAssistantArknights / MaaAssistantArknights/maa-cli

RFC: Config v2 (欢迎有兴趣的朋友提出意见)

Open
#529 24 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
117
Forks
39
PR merge metrics
No merged PRs in 30d

Description

# Config V2 设计规范

## 概述

- **Profile**(`profiles/*.toml`):定义*如何*连接——连接方式、客户端与可选的行为/进阶配置
- **Task**(`tasks/*.yaml`):定义*做什么*——带条件和覆盖的任务列表

在下面的例子里面,由于 Profile 更扁平,适合 TOML;Task 嵌套很深,更适合 YAML。但是,新版本依然支持 TOML,YAML,JSON 三种格式混合使用,你可以选择你喜欢的格式。

### 设计目标

V2 的目标有两个:

- **对用户更直观**:Profile 负责描述“如何连接”,Task 负责描述“做什么”,同时不再直接对接 MaaCore API。`client_type` 等运行上下文不再散落在任务参数中;任务文件也不再依赖多种隐式变换和不一致的规则,尤其体现在生命周期处理、参数传播,以及 variants 的激活逻辑上。
- **对实现更直接**:配置边界更清晰,`client_type` 的来源唯一,自动生命周期与手写生命周期的语义分离,单 Session 与多 Session 也分别建模。这样可以避免在运行时反复提取、合并、回填和兜底,减少隐式规则和特殊分支。

### 兼容性

Profile 和 Task 都有一个顶层字段 `version`

```toml
version = 2
```

- `version = 2` → 按 V2 解析
- `version = 1` → 按 V1 解析,不警告 (如果你暂时不想迁移的话,但是后续 V1 会删除,所以最终还是要迁移)
- 无 `version` → 按 V1 解析 + 弃用警告

Profile 和 Task 必须同时升级,不保证交叉兼容,必须是 Profile V1 + Task V1 或者 Profile V2 + Task V2.

---

## Profile 配置

### 顶层字段

```toml
version = 2 # 必填,标识配置格式版本
inherits = "default" # 可选,继承另一个 Profile,深度合并(子字段覆盖父字段)
client_type = "Official" # 可选
```

#### 继承示例

`inherits` 字段用来继承另一个 Profile,可以方便的实现配置复用:

```toml
# profiles/default.toml
version = 2

[connection]
type = "General"
address = "emulator-5554"
touch_mode = "MaaTouch"
```

```toml
# profiles/yostar-en.toml
version = 2
inherits = "default"
client_type = "YoStarEN" # 只改这一个字段,其余全部继承
```

#### 游戏客户端类型

`client_type` 是一个横跨多个作用域的配置,决定:

- 加载哪个全局资源包(如 YoStarEN → EN 资源)
- 部分 Preset 使用的游戏包名 / Bundle ID(如 PlayCover)
- 任务条件判断需要的 client_type
- 部分任务需要注入的 client_type

### `[connection]`

`type` 选择连接类型,除了 General 以外提供了一系列默认值,且决定哪些字段合法。此外,对于部分类型 (类如 Waydroid) 也负责确定如何管理外部环境。

```toml
[connection]
type = "General" # General / PlayCover / Waydroid / AVD / MuMuPro
```

#### `General`(通用模式,直接对标原始的 MaaCore API)

类似于旧版本,但是部分选项的位置和名字发生了变化。

```toml
[connection]
type = "General"
address = "emulator-5554" # 可选,缺省时从 `adb devices` 自动检测
adb_path = "adb" # 可选
touch_mode = "MaaTouch" # 可选:MaaTouch / MiniTouch / Adb
adb_lite = false # 可选
kill_adb_on_exit = false # 可选
config = "General" # 可选,连接时使用的配置,可能影响部分行为
```

#### `PlayCover`(macOS 通过 PlayCover 原生运行 iOS App)

`touch_mode` 内部固定为 `MacPlayTools`,平台差异资源 `iOS` 自动加载。

```toml
[connection]
type = "PlayCover"
address = "127.0.0.1:1717" # 可选,默认 127.0.0.1:1717
screencap_mod = "Default"
```

Playcover 支持多种截图模式:

- `Default`:默认兼容模式
- `BGR`:速度更快,使用目前没发现兼容性问题
- `SCK`:使用 macOS ScreenCaptureKit 速度最快,但是需要 cli 的宿主 (终端),被授予了截图权限

#### `Waydroid`(Linux Continer)

支持自动启动,连接地址在运行时从 `waydroid status` 获取:

```toml
[connection]
type = "Waydroid"
adb_path = "adb" # 可选
touch_mode = "MaaTouch" # 可选
adb_lite = false # 可选
```

#### `MuMuPro`(macOS)

类似于通用 ADB,但是有已知的 adb_path 和 address

```toml
[connection]
type = "MuMuPro"
address = "127.0.0.1:16384" # 可选
touch_mode = "MaaTouch" # 可选
adb_lite = false # 可选
kill_adb_on_exit = false # 可选
```

后续可以扩展更多类型,比如 AVD (自动启动),实体设备 (自动调节分辨率),以及 Windows 上面的 Mumu / 雷电(截图增强):

#### `AVD`(Android 虚拟设备)

`sdk_path` 必填,`adb_path` 和模拟器命令从中推导。

理论上支持截图增强,见 MaaAssistantArknights/MaaAssistantArknights#15608,有待实际实现

```toml
[connection]
type = "AVD"
sdk_path = "/home/user/Android/Sdk" # 必填
avd_name = "Pixel_6_API_33" # 可选,用于启动指定模拟器
touch_mode = "MaaTouch" # 可选
adb_lite = false # 可选
kill_adb_on_exit = false # 可选
```

### `[behavior]`

运行时行为偏好,所有字段可选。

```toml
[behavior]
auto_reconnect = true # 掉线后自动重连,默认 true
deployment_with_pause = false # 部署时暂停,默认 false
```

### `[advanced]`

进阶技术配置,大多数用户无需关注。所有字段可选。

```toml
[advanced]
inference_engine = "cpu" # cpu / gpu:0 / gpu:1 / ...,默认 cpu
user_resource = false # 从配置目录加载自定义资源,默认 false
```

### 完整示例

**最简配置(PlayCover):**

```toml
version = 2

[connection]
type = "PlayCover"
```

**完整配置(General):**

```toml
version = 2
client_type = "YoStarEN"

[connection]
type = "General"
address = "127.0.0.1:5555"
adb_path = "/usr/bin/adb"
touch_mode = "MaaTouch"
adb_lite = false
kill_adb_on_exit = false

[behavior]
auto_reconnect = true

[advanced]
inference_engine = "gpu:0"
user_resource = true
```

---

## Task 配置(`tasks/*.yaml`)

### 结构

### 生命周期管理

两个标志控制自动启停:

- `manage_environment_lifecycle`(默认 `true`):是否自动管理外部环境(如 Waydroid session、AVD)。由 Profile 的 `connection.type` 决定是否实际生效——General / PlayCover 等无外部环境的类型忽略此标志。
- `manage_game_lifecycle`(默认 `true`):是否自动管理游戏启停流程。
- 启用时,运行时会自动执行 StartUp / CloseDown,`client_type` 和 `account_name` 会自动注入给 StartUp。
- 禁用时,用户可以自己在 `tasks` 中手写 StartUp / CloseDown task。Runtime 仍会将 Profile 的 `client_type` 注入到这些 task 的 params 中,但不会从中提取 `client_type`, `client_type` 的唯一来源始终是 Profile,同时不再保证中间任务出错后依然执行 CloseDown。

### 任务

Task 文件支持两种模式,两者不能同时存在:

- `tasks`:任务列表模式,通常对于单账号任务;
- `sessions`:编排模式,主要用于多个账号按顺序轮跑;

#### 任务列表模式

```yaml
version = 2
manage_environment_lifecycle: true # 可选,默认 true
manage_game_lifecycle: true # 可选,默认 true
account_name: main # 可选,注入给 StartUp

tasks:
- type: Fight
# ...
```

#### 编排模式

所有 Session 共享 Profile 中的 `client_type`,只有 `account_name` 不同。实际按顺序串行执行。

`manage_environment_lifecycle` 只执行一次,(环境只启停一次)。
`manage_game_lifecycle` 每个 session 执行一次:

- 为 `true` 时,运行时会在各 Session 之间自动执行 CloseDown → StartUp
- 为 `false` 时,运行时仅按书写顺序执行,不做额外检查或兜底;如需切账号,需用户自己手写 StartUp / CloseDown

在 `sessions` 模式下:

- 顶层不能再出现 `account_name`
- 每个 Session 只描述该账号自己的 `tasks`
- Session 按顺序执行;当前 Session 未完成时,不会进入下一个 Session
- `manage_game_lifecycle = false` 时,Session 内允许手写 StartUp / CloseDown,但运行时不额外保证切换正确性

编排模式下的失败处理规则:

- 用户主动中断时,立即停止整个运行,不进入后续 Session
- 当前 Session 运行报错或掉线时,将该 Session 标记为失败,但继续执行后续 Session
- 如果启用了自动游戏生命周期,当前 Session 在报错或掉线后仍会先自动执行 CloseDown,再进入下一个 Session
- 最终结果需要反映所有失败的 Session,不能因为后续 Session 成功而吞掉前面的错误

```yaml
version = 2
manage_environment_lifecycle: true # 可选,默认 true,全局一次

sessions:
- account_name: main
tasks:
- type: Fight
params:
stage: "1-7"

- account_name: alt
tasks:
- type: Fight
params:
stage: CE-6
```

一个可能的痛点是,多 session 模式可能会存在大量的重复,所以后续可能会加上 template,允许复用。
现阶段主要使用可以利用 YAML 的锚点,一定程度进行复用。

### Task 定义

每个 Task 包含:

- `type`(必填):MaaCore 任务类型
- `name`(可选):控制最终显示在 Summary 里面的名字
- `if`(可选):控制该任务是否执行的条件;缺省表示始终执行
- `params`(可选):任务执行时始终传给 MaaCore 的基础参数
- `override_strategy`(可选):多个 override 同时命中时的处理策略,`first` 或 `merge`,默认 `first`
- `overrides`(可选):条件性参数覆盖列表,叠加在 `params` 之上

除非显式说明,V2 中的 `type` 都直接对应 MaaCore task 类型。V2 不额外引入新的“生命周期 task 类型”;自动启停由 `manage_*_lifecycle` 控制。

```yaml
tasks:
- type: Fight
name: Fight Daily
override_strategy: first
if:
weekdays: [Mon, Wed, Fri]
params:
stage: "1-7"
report_to_penguin: true
overrides:
- if: OnSideStory
params:
stage: ""
- if:
weekdays: [Tue, Thu, Sat]
params:
stage: CE-6
```

`overrides` 的应用策略由 `override_strategy` 决定:

- `first`(默认):按顺序找到第一个命中的 override,应用后停止
- `merge`:按顺序应用所有命中的 override,后者覆盖前者

无匹配时使用原始 `params`,任务照常执行 (注意,V1 版本则会静默跳过)。

### 执行语义

#### 自动游戏生命周期

当 `manage_game_lifecycle = true` 时:

- 单 Session 模式会在任务列表前后自动执行 StartUp / CloseDown
- `account_name` 会自动注入给 StartUp
- `client_type` 会自动注入给需要的任务参数

自动执行的 CloseDown 只在以下情况执行:

- 正常结束
- 运行报错
- 掉线停止

用户主动中断 (Ctrl-C) 时,不自动执行 CloseDown。后续可能添加配置/命令行参数,在报错的情况下,依然不关闭。

#### 手写生命周期

当 `manage_game_lifecycle = false` 时:

- `tasks` 中可以手写 StartUp / CloseDown,并允许任意穿插
- 运行时严格按书写顺序执行
- 不再根据 CloseDown 做任何额外兜底
- 失败或中断后的行为与普通任务一致

---

## 条件系统

条件出现在两处:

- `task.if`——决定任务是否执行
- `override.if`——决定该 override 是否应用

### 字符串形式(无参数条件)

```yaml
if: Always # 始终激活(缺省 if 时的默认行为)
if: OnSideStory # SideStory 期间激活(使用 Profile 中的 client_type)
```

其中:

- `OnSideStory`(字符串)表示检查当前 Profile 的 `client_type`
- `on_side_story: Xxx`(对象)表示显式检查指定客户端

### `Weekday`(星期)

唯一字段:`weekdays`。

```yaml
if:
weekdays: [Mon, Wed, Fri]
timezone: Official # 可选:Local(默认)/ Official / YoStarEN / ... / UTC 偏移整数
```

`timezone` 为客户端名称时,以服务器时间 04:00 作为换天边界。

### `DayMod`(天数取模)

唯一字段:`divisor`。满足 `num_days_from_ce % divisor == remainder` 时激活。

```yaml
if:
divisor: 2
remainder: 0 # 可选,默认 0
timezone: 8 # 可选,UTC 偏移整数
```

### `Time`(每日时间段,循环)

唯一字段:`time_after` 和/或 `time_before`。

```yaml
if:
time_after: "16:00:00"
time_before: "23:59:59"
timezone: Official # 可选
```

仅设 `time_after`:从该时刻到午夜激活。仅设 `time_before`:从午夜到该时刻激活。支持跨午夜的时间段(如 23:00–01:00)。

### `DateTime`(日期范围,一次性)

唯一字段:`start_date` 和/或 `end_date`。

```yaml
if:
start_date: "2024-08-01T00:00:00"
end_date: "2024-08-21T04:00:00"
timezone: 8 # 可选,UTC 偏移整数
```

### `OnSideStory`(指定客户端)

```yaml
if:
on_side_story: Txwy # 检查指定客户端的限时活动状态
```

这里的 `Txwy` 是显式目标客户端,与 Profile 当前使用的 `client_type` 无关。

### 组合条件

```yaml
# AND——所有子条件满足
if:
all:
- weekdays: [Mon, Wed, Fri]
- time_after: "16:00:00"

# OR——任一子条件满足
if:
any:
- weekdays: [Sat]
- weekdays: [Sun]

# NOT——取反
if:
not:
weekdays: [Sun]
```

---

## 完整 Task 示例

```yaml
manage_game_lifecycle: true

tasks:
# Fight:始终执行,但根据条件覆盖关卡
- type: Fight
params:
stage: "1-7"
report_to_penguin: true
penguin_id: "00000000"
overrides:
- if: OnSideStory
params:
stage: ""
- if:
weekdays: [Tue, Thu, Sat]
params:
stage: CE-6

# Mall:仅在 16:00 后执行
- type: Mall
if:
time_after: "16:00:00"
params:
shopping: true
credit_fight: true
buy_first: [招聘许可, 龙门币]
blacklist: [碳, 家具, 加急许可]

# Recruit:始终执行
- type: Recruit
params:
refresh: true
select: [4, 5]
confirm: [3, 4, 5]
```

---

## V1 → V2 迁移

### Profile 迁移

#### V1

```toml
[connection]
type = "ADB"
adb_path = "adb"
device = "emulator-5554"
config = "CompatMac"

[resource]
global_resource = "YoStarEN"
user_resource = true

[static_options]
cpu_ocr = true

[instance_options]
touch_mode = "MaaTouch"
deployment_with_pause = false
adb_lite_enabled = false
kill_adb_on_exit = false
```

#### V2

```toml
version = 2
client_type = "YoStarEN" # resource.global_resource 不再需要手动设,由 client_type 自动推导

[connection]
type = "General" # "ADB" → "General"
address = "emulator-5554" # "device" → "address"
adb_path = "adb"
touch_mode = "MaaTouch" # 原 instance_options.touch_mode
adb_lite = false # 原 instance_options.adb_lite_enabled
kill_adb_on_exit = false
config = "CompatMac"

[behavior]
deployment_with_pause = false # 原 instance_options.deployment_with_pause

[advanced]
inference_engine = "cpu" # 原 static_options.cpu_ocr = true → "cpu"
user_resource = true # 原 resource.user_resource
```

主要变化:

- 添加 `version = 2`
- `connection.type`:`ADB` → `General`
- `connection.device` → `connection.address`
- `resource.global_resource` / `platform_diff_resource` → 由 `client_type` 和 `connection.type` 自动推导,不再需要手动设
- `static_options` / `instance_options` → 拆分到 `[connection]`、`[behavior]`、`[advanced]`

### Task 迁移

#### V1

```yaml
tasks:
- type: StartUp
params:
start_game_enabled: true
client_type: Official

- type: Fight
params:
stage: "1-7"
variants:
- condition:
type: Weekday
weekdays: [Tue, Thu, Sat]
params:
stage: CE-6
- condition:
type: Always
params: {}

- type: CloseDown
```

#### V2

```yaml
manage_game_lifecycle: true # 替代手写 StartUp / CloseDown task

tasks:
- type: Fight
params:
stage: "1-7" # base params,始终使用
overrides: # variants → overrides
- if:
weekdays: [Tue, Thu, Sat] # condition → if,无需 type 标签
params:
stage: CE-6 # 不再需要 Always variant 保底
```

主要变化:

- `StartUp` / `CloseDown` task → `manage_game_lifecycle: true`(默认值),自动管理游戏启停
- `client_type` 不再在 task params 中设置,由 Profile 注入
- 用户仍可手写 StartUp / CloseDown(设 `manage_game_lifecycle: false`),但 `client_type` 只注入不提取,且运行时完全按书写顺序执行
- `variants` → `overrides`,仅覆盖参数,不影响任务是否执行
- `condition` → `if`,通过唯一字段推断类型,无需 `type` 标签
- 不再需要 `Always` variant 作为保底

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

The proposal names profiles/*.toml and tasks/*.yaml as the configuration surfaces; start by reviewing the existing V1 profile and task handling in the CLI. Compare those paths with the V1→V2 examples, and treat completion as an agreed implementation of the V2 schema, compatibility behavior, and corresponding tests.

Written by the indexing model from the issue text.

Assessment

Tech stack
json, rust, yaml
Domain
cli, tooling
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.