Pipeline Option 工作流

SkillProductivity

Add runtime UI options (select/checkbox/switch/input) to MaaFramework option surfaces such as `assets/interface.json` or `assets/resource/tasks/**/*.json`. Use when adding a user-facing toggle, selector, checkbox, or input; wiring options to `pipeline_override`; aligning option paths with Python `context.get_node_data()` or CustomAction params; or reviewing option behavior across Pipeline JSON and Python.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Pipeline Option 工作流 skill

What this skill tells your AI

The instructions your AI receives, as published by khazixw2/maagumballs in .claude/skills/pipeline-option/SKILL.md and read by ahel’s review.

TL;DR:先识别 option surface

新增一个 UI 选项需要先识别本项目使用的 option surface。MaaGumballs 主要是 assets/interface.json,M9A 同时使用 assets/interface.jsonassets/resource/tasks/**/*.json。不要假设所有 Maa 项目只有一种入口。

常见联动点如下,按项目实际协议取用,缺关键点会导致 UI 看不到选项或运行时读不到值:

#位置内容
1option 定义处assets/interface.jsonoption 字典,或 assets/resource/tasks/**/*.json 里的 task option 定义
2task 注册处interface.json task 的 option: [],或 tasks JSON 的任务/预设引用
3assets/resource/base/pipeline/*.json预定义目标节点(pipeline_override 不会创建节点)
4Python 代码(仅 Python 需要读取或执行 Custom 时必需)context.get_node_data()argv.custom_action_paramargv.custom_recognition_param 与 option 路径保持一致

⚠️ pipeline_override 只做属性合并,不会凭空创建节点。 少了第 3 步,context.get_node_data() 会返回 None,运行时静默失败。

完整协议参考(嵌套 option、global_option、controller/resource 限制、占位符注入):references/protocol.md

历史校正

  • 不要把 pure override 当成唯一最佳解:只改已有节点字段时 pure override 最小;但涉及运行时事件库、计数、动态目标、识别后处理、失败策略、跨节点状态时,CustomAction/CustomRecognition 更合适。
  • 不要把 Flag 节点当成唯一配置入口:M9A 的 v5 object-form 里,参数经常通过 action.param.custom_action_paramcustom_action_param_coderecognition.param.custom_recognition_param 进入 Custom;这和 context.get_node_data("Flag") 是不同通道。
  • 字段路径必须闭环:UI 写哪条路径,Python 就读哪条路径;pure override 则 Python 不读,直接观察运行时行为。
  • enabled/enable 不是审美选择:MaaFramework 原生启停用 enabled;历史项目若已有 enable helper,可沿用并兼容,否则优先 enabled

4 种 type 速查

type选择override 字段节点预定义形态
select单选互斥expectedrecognition: "OCR" + expected: [...]
switch二元 Yes/Noenabled(或项目已有的 enable{"enabled": bool} / {"enable": bool}
input自由文本{name} 占位符可注入目标字段按最终读取方预定义 expectedaction.param.custom_action_param
checkbox多选enabled{"enabled": false}

选哪个模式?

你的需求推荐模式
启用/禁用一个 Python 业务函数A(switch + Flag 节点 + Python 读 flag)
从多个互斥选项里选一个值B(select + OCR 节点)
同时启用多个独立的功能模块C(checkbox + 多个 Flag 节点)
用户输入自定义文本D(input + 占位符注入)
切换行为(点哪个按钮 / 走哪条 next 链)但不想改 PythonE(pure override 现有节点字段)

经验法则:行为只等于“覆盖已有节点字段”时优先 pure override;一旦需要运行时数据、计数、动态识别、失败策略或跨节点状态,改用 Flag + Python / CustomAction / CustomRecognition。目标是让改动面和逻辑复杂度匹配。

先确认代码读取路径

加选项前先 rg "get_node_data|_node_enabled|Flag_" agent assets,确认这次配置到底由谁读取。

常见对应关系:

UI override 写什么Python 应该读什么备注
{ "Flag_X": { "enabled": true } }node.get("enabled")MaaFramework 标准启停字段
{ "Flag_X": { "enable": true } }node.get("enable") 或兼容 helper仅用于已有项目约定/历史字段
{ "SomeOCR": { "expected": ["A"] } }node["recognition"]["param"]["expected"][0]节点必须预定义为 OCR
{ "SomeNode": { "next": ["A"] } }不读,直接由 pipeline 行为生效pure override 模式

不要字段错位:例如 UI 写 AutoSky_CloneConfig.enable,Python 却调用读取 expected 的函数;或 UI 写 expected,Python 只看 enabled。这种错误不会报 JSON 语法错,但运行时会表现为"选项没生效"。

启停节点必须短路调用

如果同一个节点既保存 enable/enabled 开关,又是可执行的 Recognition/Action 节点,先读取开关,只有开启时才调用它。关闭节点后继续检查同级候选分支,不能调用已关闭节点,也不能因为开关值直接 continue 整个事件扫描。

if _node_enabled(context, "OptionalNode"):
    result = context.run_recognition("OptionalNode", image)
    if result and result.hit:
        context.run_task("OptionalNode")
        continue

# OptionalNode 关闭或未命中,继续识别 CombatNode、ExploreNode 等同级分支。

模式 A:开关(switch + Flag 节点)— 最常用

适用:开启/关闭某个功能。

interface.json

"开启5月城堡相亲": {
    "type": "switch",
    "description": "是否开启5月自动相亲",
    "default_case": "Yes",
    "cases": [
        {
            "name": "Yes",
            "pipeline_override": { "Flag_EnableMarryTask": { "enabled": true } }
        },
        {
            "name": "No",
            "pipeline_override": { "Flag_EnableMarryTask": { "enabled": false } }
        }
    ]
}

配套 pipeline 节点(必须预定义!)

"Flag_EnableMarryTask": { "enabled": true }

若项目历史节点使用 enable 而非 enabled,必须同时保证 Python 侧有兼容读取,例如 node.get("enable", node.get("enabled", True))。否则优先使用协议字段 enabled

注册到 task

"task": [{
    "name": "推年计划",
    "entry": "Auto_YearlyTask",
    "option": ["开启5月城堡相亲", /* 其他选项 */]
}]

Python 读取(建议放在业务函数入口)

def handle_marry_festival(context: Context) -> bool:
    """处理春林节相亲(5月)"""
    EnableMarryTask = context.get_node_data("Flag_EnableMarryTask").get("enabled")
    if not EnableMarryTask:
        logger.info("自动相亲已关闭,跳过")
        return True
    # ... 正常逻辑

模式 B:单选(select + OCR 节点)

适用:选择城市、关卡、模式等互斥选项。

interface.json

"选择刷取任务国家": {
    "type": "select",
    "description": "选择要刷取任务的目标城市",
    "default_case": "雄月城",
    "cases": [
        { "name": "王座堡", "pipeline_override": { "EnterCity": { "expected": ["王座堡"] } } },
        { "name": "雄月城", "pipeline_override": { "EnterCity": { "expected": ["雄月城"] } } }
    ]
}

配套 OCR 节点

"EnterCity": {
    "recognition": "OCR",          // ⚠️ 必须是 OCR,否则 expected 不生效
    "expected": ["王座堡", "圣盾堡", "雄月城", "翠庭"],
    "roi": [58, 320, 600, 682],
    "action": "Click"
}

Python 读取

data = context.get_node_data("EnterCity")
city = data.get("recognition", {}).get("param", {}).get("expected", ["王座堡"])[0]

模式 C:多选(checkbox + 多个 Flag 节点)

适用:多条件检测(好苗子条件)、可叠加的功能模块。

interface.json

"开启好娃提醒": {
    "type": "checkbox",
    "default_case": ["科内塔之怒"],
    "cases": [
        { "name": "科内塔之怒",   "pipeline_override": { "检测_科内塔之怒":     { "enabled": true } } },
        { "name": "太阳+科内塔之怒", "pipeline_override": { "检测_太阳+科内塔之怒": { "enabled": true } } }
    ]
}

配套节点(每个 case 一个,默认全 false)

"检测_科内塔之怒":      { "expected": ["koneita"],            "enabled": false },
"检测_太阳+科内塔之怒": { "expected": ["sun_and_koneita"],    "enabled": false }

Python 读取(遍历收集)

def _get_enabled_checks(context) -> list:
    enabled = []
    for key in ["检测_科内塔之怒", "检测_太阳+科内塔之怒"]:
        node = context.get_node_data(key)
        if node and node.get("enabled", False):
            expected = node.get("recognition", {}).get("param", {}).get("expected", [])
            if expected:
                enabled.append(expected[0])
    return enabled

模式 D:自由输入(input + 占位符注入)

适用:用户输入自定义关卡号、自定义黑名单任务等。input 只是 UI surface;占位符可以注入 expectedaction.param.custom_action_param,最终路径由消费方决定。

interface.json

"自定义任务黑名单": {
    "type": "input",
    "inputs": [
        {
            "name": "任务名称",
            "pipeline_type": "string",
            "default": "",
            "verify": "^[^,,]*$",
            "pattern_msg": "不能包含逗号"
        }
    ],
    "pipeline_override": {
        "CustomTaskBlacklist": {
            "expected": ["{任务名称}"]   // {名称} 占位符被实际输入替换
        }
    }
}

Python 读取

data = context.get_node_data("CustomTaskBlacklist")
value = data.get("recognition", {}).get("param", {}).get("expected", [""])[0]

⚠️ 常见错误:混淆 get_node_data() 和 CustomAction 参数通道

custom_action_param 可以用,但要读对地方:

  • 如果参数是给当前 CustomAction 执行时使用,写入 action.param.custom_action_param,在 Python 里读 argv.custom_action_param
  • 如果参数是给普通业务代码提前读取,写到一个预定义节点的 recognition.param.expectedenabledenable 或其他明确字段,再用 context.get_node_data("Node") 读取对应路径。
  • 不要把用户输入塞到 pipeline_override.custom_action_param 后,再用 get_node_data("X").get("custom_action_param") 读顶层字段;这两个不是同一条通道。

错误信号:UI 显示已选择,但 Python 读到默认值或 None。排查时打印完整 context.get_node_data("X"),确认字段实际落点。


模式 E:行为覆盖(pure override 现有节点字段)— 最简

适用:行为切换映射到现有 pipeline 节点的单个字段next 数组 / action 类型 / recognition 算法 / 任何可覆盖字段),且不需要 Python 判断

核心思路:用户切换 UI 选项 → 改变 pipeline 节点的字段值 → 框架自身根据新值执行。Python 代码完全不动。

典型场景:开关决定点哪个按钮

"开启自动接受佣兵": {
    "type": "switch",
    "default_case": "No",
    "cases": [
        {
            "name": "Yes",
            "description": "直接点确认",
            "pipeline_override": {
                "Event_MercenaryJoin": {
                    "next": ["Event_MercenaryJoinConfirm"]
                }
            }
        },
        {
            "name": "No",
            "description": "直接点取消",
            "pipeline_override": {
                "Event_MercenaryJoin": {
                    "next": ["Event_MercenaryJoinCancel"]
                }
            }
        }
    ]
}

Event_MercenaryJoin 节点本身在 event_utils.json 里有完整定义(recognition / expected / roi / timeout 都在),pipeline_override 只覆盖 next 字段,其他字段保持原值。

next 数组的单元素 vs 多元素语义

写法语义何时用
["A"]强约束:只走 A行为已确定,单路径足够(模式 E 的典型形态
["A", "B"]回退链:优先 A,A 失败走 B兜底机制("优先点确认,找不到才点取消")
["A", "B", "[JumpBack]C"]失败后跳回 C 节点重试复杂回退

可被 override 的字段

字段override 效果典型用途
next改变后续节点列表切换行为路径(模式 E 主力)
action改变点击/滑动/输入动作切换操作类型
recognition改变识别算法切换识别方式(OCR ↔ Template)
expected改变识别期望值配合 select 选值
roi改变识别区域适配不同界面尺寸
timeout改变超时时间适配不同网络/性能

关键认识:上面这些字段都是普通 JSON 值,pipeline_override 一视同仁做深合并。模式 A 用的 enabled 字段只是最常见的入口,不是唯一可 override 的字段。

模式 A vs 模式 E 对比

场景模式 A(Flag + Python)模式 E(pure override)
行为由 Python if 控制✅ 必须❌ 绕远路
行为由 pipeline 字段决定❌ 多此一举✅ 最简
需要运行时根据 flag 走不同代码分支✅ 唯一选择❌ 不行
改动 Python 代码✅ 需要❌ 不需要
需要新加 Flag 节点✅ 需要❌ 不需要

实战决策流程

要加新选项
│
├─ 行为切换对应到一个 pipeline 节点的某个字段?
│   └─ ✅ 用模式 E(pure override)
│       示例:佣兵加入时点"确认"还是"取消"
│
└─ ❌ 行为在 Python 业务逻辑里
    └─ 用模式 A(Flag 节点 + Python 读 flag)
        示例:跳过整个 handle_sailing_festival 函数

补充:能用状态机就别写 Python orchestration

MaaFramework 的 next + [JumpBack] 是为跨页面状态推进设计的原语。如果一个流程的步骤可以列举为有限个页面状态(入口 → A → B → C → 战斗),优先用 JSON 状态机;不要写 Python 把 context.run_task 串起来。

详见 .claude/skills/pipeline-guide/SKILL.md 的「跨页面状态机」典型模式。

状态机 vs Python orchestration 对比

场景状态机(推荐)Python orchestration(次选)
有限页面状态推进(如活动流程)✅ 链 next + [JumpBack]❌ 自己写 for/while 调度
按 flag 跳过整段函数❌ 不适合✅ 读 flag + 早返回
复杂的运行时分支逻辑❌ 难表达✅ Python 灵活

跨文件节点引用的测试陷阱

MaaFramework 全局加载时,所有 assets/resource/base/pipeline/*.json 会合并到同一命名空间,所以 [JumpBack]OtherFileNode 能解析。但 run_pipeline 测试工具只加载单文件,跨文件引用会报"加载 Pipeline 失败"。

应对

  • 集成测试必须用 MaaFramework GUI / CLI 触发,不能依赖 run_pipeline
  • 单元测试每个节点用 run_pipeline 是 OK 的(无跨文件依赖)
  • 若某个流程有跨文件引用,本地调试时考虑用 MaaCli 跑全 bundle

补充:状态机驱动的「流程型选项」

如果一个 UI 选项代表的是进入某个跨页面流程(如"开启成长试炼"→ 大地图 → 难度选择 → 队伍 → 战斗),把选项的 pipeline_override 用于:

  1. 切换"是否启用流程"的 Flag 节点
  2. 注入该流程入口节点所需参数(如难度 expected

不要用 Python orchestration 串联流程中的每个节点。完整流程示例:

// 选项定义
"开启3月成长试炼": {
    "type": "switch",
    "default_case": "No",
    "cases": [
        {
            "name": "Yes",
            "pipeline_override": {
                "Flag_GrowthTrialMode": { "enabled": true },
                "GrowthTrial_Difficulty_Select": { "expected": ["噩梦"] }
            }
        },
        {
            "name": "No",
            "pipeline_override": {
                "Flag_GrowthTrialMode": { "enabled": false }
            }
        }
    ]
}
// 入口节点(路由)
"GrowthTrial_Start": {
    "next": [
        "GrowthTrial_TeamReady",                  // 已在队伍配置页
        "[JumpBack]GrowthTrial_Difficulty_Select", // 在难度选择页
        "[JumpBack]GrowthTrial_Enter"             // 在大地图
    ]
}

战斗入口自动接力:

"GrowthTrial_EnterBattle": {
    "action": "Click",
    "next": [
        "GrowthTrial_FightStart",                   // 战斗开始
        "[JumpBack]GrowthTrial_TravelSelect_Boat",  // 弹出旅行框
        "[JumpBack]GrowthTrial_TravelSelect_Walk"   // fallback
    ]
}

命名与默认值

命名约定

角色风格示例
option 名(用户可见)中文动词起头开启5月城堡相亲选择刷取任务国家
节点名(pipeline)英文Flag_EnableMarryTaskEnterCity检测_科内塔之怒
switch case 名严格 Yes / No不要用 true/false是/否(Client 解析跨平台不一致)

默认值策略

保持现有行为是底线。 老用户不该因新选项而行为改变。

场景推荐 default
新开关让功能默认关闭No(明确告知用户"关了")
新开关让功能默认开启Yes(保留旧行为)
旧代码无条件开启Yes(兼容)
旧代码无条件关闭No(兼容)

读取位置

决策类型放哪读理由
是否执行某段流程业务函数入口 handle_xxx与现有同名函数风格一致,子函数自治
用哪个值做主逻辑任务入口 runYearlyTaskProcessor一次读取、多次复用

反例:不要把"是否开启 X"的判断堆在通用 dispatch 函数(如 handle_festival_by_month)里。每加一个开关 dispatch 就多一个 if-elif,越来越臃肿。


✅ 推荐做法

  1. 先复用现有模式:参考同项目里现成的同类选项(开关 → 开启5月城堡相亲;选择 → 选择刷取任务国家
  2. 3 处同步改完再跑:不要中途停下来"先编译试试"
  3. JSON 改完跑资源加载检查:本仓库与 M9A 的真实路径是 python tools/ci/check_resource.py assets/resource/base;如果目标项目路径不同,先用 Get-ChildItem tools -Recurse -Filter check_resource.py 发现真实脚本。pipeline 加载错误(如重复 key)会立刻报
  4. 默认值遵循现状:选项是"开"还是"关"取决于旧代码行为,不是你的偏好
  5. 在 task 的 doc 数组里加一行说明:用户能看懂每个选项的作用
  6. 让 option 通道匹配实际复杂度:行为只动已有 pipeline 字段时用 pure override;Python 需要做真实判断、计数、动态识别或安全策略时,用 Flag + Python / CustomAction / CustomRecognition,不要为了少写 Python 把复杂逻辑硬塞进 JSON。

❌ 不要做

1. 不要只通过 pipeline_override 定义节点

// ❌ 错:节点没在 pipeline JSON 中预定义 → 不会被加载 → get_node_data() 返回 None

// ✅ 对:在 pipeline JSON 里预定义
"Flag_EnableSailingFestivalPurchase": { "enabled": true }

验证方法:加完后跑 python tools/ci/check_resource.py assets/resource/base(或目标项目真实资源检查命令),并在 Python 里加个 None 兜底日志。

2. 不要忘了注册到 task 的 option 数组

// ❌ 错:option 定义了但 task 不引用 → UI 上看不到
"option": []

// ✅ 对:同步注册
"option": ["开启3月启航节购买"]

3. 不要把判断塞到 dispatch 函数

# ❌ 错:dispatch 越来越臃肿
def handle_festival_by_month(month):
    if month == 3 and not context.get_node_data("Flag_X").get("enabled"):
        return True
    if month == 3:
        return handle_sailing_festival(context)
    # ... 每加一个开关都得多一个 if

# ✅ 对:业务函数自治
def handle_sailing_festival(context):
    if not context.get_node_data("Flag_X").get("enabled"):
        return True
    # ... 正常逻辑

4. 不要混淆字段路径

用途字段路径备注
selectdata["recognition"]["param"]["expected"][0]节点必须 recognition: "OCR"
input 注入 CustomActiondata["action"]["param"]["custom_action_param"][key] 或执行时的 argv.custom_action_param取决于参数消费位置
input 注入普通节点字段读取被占位符覆盖的真实路径,如 recognition.param.expectedinput 不限定注入目标
switch / checkboxdata["enabled"]最简单
历史 enable 开关data.get("enable", data.get("enabled", default))仅在项目已有该字段时使用
模式 E 不读(不读,直接看 override 后节点的运行时行为)pure override 模式,Python 拿不到也不需要 flag

5. 不要用非 Yes/No 的 switch case 名

// ❌ 错:Client 解析可能不一致
{ "name": "true" } / { "name": "是" } / { "name": "ON" }

// ✅ 对:跨 Client 一致
{ "name": "Yes" } / { "name": "No" }

6. 不要让 input 注入路径和读取路径错位

input{name} 占位符既可以注入 OCR expected,也可以注入 action.param.custom_action_param。选择哪条路径取决于最终消费方:普通 OCR/配置读取用实际节点字段,CustomAction 执行参数用 argv.custom_action_param。不要写入一条路径却从另一条路径读取。

7. 不要用中文做 pipeline 节点名

// ❌ 错:中文节点名 + 英文字段访问
"开启5月": { "enabled": true }

// ✅ 对:英文 Flag_ 命名
"Flag_EnableMarryTask": { "enabled": true }

中文做 option 名(用户可见),英文做 pipeline 节点名(代码访问)。混了会让代码和配置都对不上。

8. 不要在多文件 pipeline 里重复定义同名节点

parse_and_override_once 合并所有 pipeline JSON 时严格拒绝重复顶层 key。检查方法:

grep -rn "^\s*\"YourNodeName\":" assets/resource/base/pipeline/

两个文件都定义同一个顶层节点会直接让整个 check_resource.py 失败,且 Python json.load() 检测不出来(Python 会静默覆盖),必须用 C++ 解析器或 C++ 模拟检测。

9. 不要为了"配置统一"硬塞 Flag 节点

// ❌ 错:行为切换只动 pipeline 字段,但你硬加了 Flag 节点 + Python 分支
"Flag_AcceptMercenary": { "enabled": true },   // ← 不必要
def handle_mercenary_join(context):
    if not context.get_node_data("Flag_AcceptMercenary").get("enabled"):
        return True
    context.run_task("Event_MercenaryJoin")      # 实际行为由 Event_MercenaryJoin.next 决定

// ✅ 对:直接 override `next`,零 Python 改动
"开启自动接受佣兵": {
    "type": "switch",
    "pipeline_override": {
        "Event_MercenaryJoin": { "next": ["Event_MercenaryJoinConfirm"] }
    }
}

判断口诀:如果你的 Python 分支里只做了一件事(调用 run_task 让 pipeline 接手),那这个分支完全可以由 pipeline_override 替代。Flag 节点 + Python 分支只在你需要在 Python 侧做真正的条件逻辑(不只是转发)时才必要。

10. 不要用 Python orchestration 替代状态机

如果一个跨页面流程可以列举为有限个页面状态(A → B → C → D),优先用 MaaFramework 的 next + [JumpBack] 串起来。不要写 Python for 循环 + context.run_task() 调度。

// ✅ 对:纯 JSON 状态机(推荐)
"GrowthTrial_Start": {
    "next": [
        "GrowthTrial_TeamReady",                  // 已在队伍配置页
        "[JumpBack]GrowthTrial_Difficulty_Select", // 在难度选择页
        "[JumpBack]GrowthTrial_Enter"             // 在大地图
    ]
}

"GrowthTrial_Enter": {
    "next": [
        "GrowthTrial_Enter_Click",                  // 找到图标
        "[JumpBack]BigMap_Activity_Resident",       // 切"常驻"tab
        "[JumpBack]BigMap_Activity"                 // 打开活动页
    ]
}

"GrowthTrial_EnterBattle": {
    "action": "Click",
    "next": [
        "GrowthTrial_FightStart",                    // 战斗开始
        "[JumpBack]GrowthTrial_TravelSelect_Boat",   // 弹出旅行框
        "[JumpBack]GrowthTrial_TravelSelect_Walk"    // fallback
    ]
}
# ❌ 错:自己重新发明状态机
def enter_growth_trial(context):
    found = False
    for attempt in range(5):
        if context.run_recognition("BigMap_GrowthTrial_OCR", ...).hit:
            found = True
            break
        context.run_task("Map_SwipeUp_OnBigMap")
    if not found:
        return False
    context.run_task("GrowthTrial_Enter")
    # ... 又是 for/if 链
    return True

自检问题

  • 我的 Python 代码里是否在run_task 把控制权交给 pipeline?是 → 考虑改用 next
  • 我的"流程推进"是否依赖显式的状态变量(如 found)?是 → 改用 [JumpBack] 让框架自动回退
  • 我的"流程"是否可以画成状态机图?是 → 用 JSON next

注意:MaaFramework 全局加载时跨文件节点引用会解析(main_ui.json 里的 BigMap_Activity* 能在 growth_trial.json 引用),但**run_pipeline 测试工具只加载单文件**——集成测试必须用 MaaFramework GUI/CLI 触发。

11. 不要让 UI override 字段和 Python 读取字段不一致

// ❌ 错:UI 写 enable
"开启克隆体": {
    "type": "switch",
    "cases": [
        { "name": "Yes", "pipeline_override": { "AutoSky_CloneConfig": { "enable": true } } }
    ]
}
# ❌ 错:代码却读 expected,永远读不到用户开关
self._clone_enabled = _read_expected_value(context, "AutoSky_CloneConfig")
# ✅ 对:读同一个字段,或提供 enable/enabled 兼容
self._clone_enabled = _node_enabled(context, "AutoSky_CloneConfig")

自检口诀:UI 写哪条路径,Python 就读哪条路径;pure override 则 Python 不读。


验证流程

改完一次完整流程,按顺序做这 4 步:

  1. JSON 语法检查

    python -c "import json; json.load(open('assets/interface.json', encoding='utf-8'))"
    python -c "import json; json.load(open('assets/resource/base/pipeline/auto_task.json', encoding='utf-8'))"
    
  2. 资源加载检查

    python tools/ci/check_resource.py assets/resource/base
    

    期望输出 All directories checked.

  3. Pipeline 节点测试(可选)

    data = context.get_node_data("Flag_EnableSailingFestivalPurchase")
    assert data is not None, "节点未预定义"
    assert "enabled" in data
    
  4. 端到端验证:用 Pipeline Testing Skill 跑一次实际流程


完整协议

更多 type 字段、嵌套 option、global_option、controller/resource 限制、{占位符} 注入机制等高级特性见 references/protocol.md

Signals

GitHub stars
62
Forks
29
Last commit
Aug 2026
Advanced
Catalog kind
skill
Gateway key
pipeline-option
Source
github.com/khazixw2/maagumballs