maafw-custom

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

MaaFW Custom 开发

MaaFW Custom Development

Pipeline 表达不了的逻辑用 Custom 补齐:读外部状态、跨节点数据传递、动态计数、复杂判定、自定义识别算法。binding 类签名与 context API 不凭记忆写,一律路由 MaaLLMWiki 的 bindings / native-api 索引(版本锁定、引用回源;已安装
maallmwiki
skill 时由其承载完整路由流程)。
Use Custom to supplement logic that cannot be expressed by Pipeline: reading external state, cross-node data transfer, dynamic counting, complex judgments, and custom recognition algorithms. Do not write binding class signatures and context API from memory — always route to the bindings / native-api index of MaaLLMWiki (version locked, source-referenced; when the
maallmwiki
skill is installed, it will handle the complete routing process).

硬护栏

Hard Guardrails

  1. 能 pipeline 不 custom(默认方向,由成本非对称决定而非偏好):
    • 为什么偏 pipeline:custom 节点的外层仍受引擎调度与观测——next 候选顺序(先命中先执行)、
      max_hit
      、
      on_error
      、focus、option 的 pipeline_override 照常生效,
      run_task
      起的子流程也由引擎执行。失去自动约束的是回调内部——内部循环 / 等待 / 网络 IO 不展开为节点图,节点
      timeout
      打不断阻塞中的回调,耗时、取消、重试、内部状态全靠自己管,对工具链只剩日志。
    • 一律 pipeline:视觉可判定的等待 / 点击 / 滑动 / 找字 / 循环 / 分支——循环 = next 环 + 出口 + max_hit(固定次数重复用
      repeat
      /
      repeat_delay
      /
      repeat_wait_freezes
      ,v5.3+),分支 = next 顺序 + 遮蔽。
    • 才上 Custom:依赖画面外运行时数据(文件、网络、进程状态、时间)的读取或以其为判据的控制流;识别 / 动作字段表达不了的判定;外部调用。
    • 字段表达力的两条硬边界(实证项目 custom 存量的大头就在这):
      • And / Or 是布尔组合——无数值比较、无跨节点算术(数值比较、多节点数值表达式是 custom 的地盘);组合期唯一数据流是 box:
        box_index
        选输出、
        sub_name
        传 filtered box 作 ROI、Or 取命中者 box;
      • max_hit
        只会递增,pipeline 字段读不到也清不掉命中计数——custom 内可用
        get_hit_count
        /
        clear_hit_count
        (作用域:当前任务与其
        run_task
        子任务共享);跨节点"数据"仍无字段通道——
        anchor
        是节点名变量(写 / 清 / 引用三态),不是数据存储。
    • 该转 custom 的反信号:为表达一段逻辑开始堆克隆节点、节点图膨胀难读、开始需要运行时改参数或计数(override / 状态类标准件频繁登场)——继续拆不如转。
    • 判据拿不准先回
      maafw-pipeline
      试拆节点。
  2. 失败要显式,且分清三种语义:
    • CustomRecognition 返回未命中 = 本候选不命中——轮询继续试后面的候选,整轮全未命中等宿主节点
      timeout
      后走宿主的 on_error(不是立即触发);
    • CustomAction 返回失败 = 走动作所在节点的 on_error;实现异常不吞、显式失败返回——静默假成功是排障噩梦;
    • binding 侧空返回值语义要查再写(样例:Python 动作
      run
      返回 None 等价成功、识别
      analyze
      返回 None 等价未命中——漏写 return 在动作侧就是静默假成功;以 binding 路由为准);
    • 直调
      context.run_recognition()
      只返回识别结果,错误分支由调用方自理。
  3. 注册名逐字符一致:注册名(装饰器或注册表中的字符串)与 pipeline 节点引用完全一致,区分大小写。改名时同步所有注册点——装饰器/注册表、pipeline 引用(先改 pipeline 用法再改注册)、有 Schema 基建则含 enum;custom 代码内的节点名引用(
    run_task
    /
    run_recognition
    /
    run_action
    的 entry、
    override_next
    的目标)也在同步面——调用即失败(返回值要判),不是静默失联;素材路径同样在同步面,但框架不替你报错——自定义代码读图必须自查加载结果(空图即失败),漏改多表现为静默空转(引用点全表见 maafw-pipeline 的 naming.md)。
  4. 签名路由:
    analyze
    /
    run
    的参数结构、context API 随 binding 版本演进,以路由结果为准,本文样例仅作形态示意;不依赖框架未承诺的钩子(如 stop / 中断回调——有没有、叫什么以路由结果为准)。框架不会主动打断回调——长循环自查停止条件、外部 IO 设超时、资源清理用 finally / RAII 等该语言机制。
    • Context 能力面(上了 custom 之后有哪些杠杆;名字以 binding 路由为准):
      run_task
      /
      run_recognition
      /
      run_action
      (按节点跑,识别与动作配对);
      run_recognition_direct
      /
      run_action_direct
      (不建节点直接用引擎算法);
      wait_freezes
      (引擎等画面静止,内部自截图);
      override_pipeline
      /
      override_next
      /
      override_image
      (补丁三件);
      get_node_data
      (读当前有效节点定义,含补丁);
      set_anchor
      /
      get_anchor
      (锚点读写);
      get_hit_count
      /
      clear_hit_count
      (计数读清);
      get_tasker
      (取 controller / resource——截图链的前提);
      clone
      (分叉 context)。
  5. 状态最小化:custom 内尽量无状态;确需跨调用记忆(如"仅首次执行"哨兵)要注明生命周期——agent 进程存活期间类状态跨任务共享,任务重入会带着上次的值。
  1. Prefer pipeline over custom (default direction, determined by cost asymmetry rather than preference):
    • Why prefer pipeline: The outer layer of custom nodes is still scheduled and observed by the engine — next candidate order (first hit first executed),
      max_hit
      ,
      on_error
      , focus, and option's pipeline_override still take effect as usual, and sub-processes started by
      run_task
      are also executed by the engine. What loses automatic constraints is the inside of the callback — internal loops / waits / network IO are not expanded into a node graph, node
      timeout
      cannot interrupt a blocked callback, and time consumption, cancellation, retries, and internal state are all managed by yourself, leaving only logs for the toolchain.
    • Always use pipeline for visually determinable waits / clicks / swipes / text finding / loops / branches — loop = next ring + exit + max_hit (use
      repeat
      /
      repeat_delay
      /
      repeat_wait_freezes
      for fixed number of repetitions, v5.3+), branch = next order + masking.
    • Only use Custom for: reading runtime data outside the screen (files, network, process status, time) or control flow based on such data; judgments that cannot be expressed by recognition / action fields; external calls.
    • Two hard boundaries of field expressiveness (these account for the majority of custom usage in empirical projects):
      • And / Or are boolean combinations — no numerical comparison, no cross-node arithmetic (numerical comparison and multi-node numerical expressions are the territory of custom); the only data flow during combination is box:
        box_index
        selects output,
        sub_name
        passes filtered box as ROI, Or takes the hit box;
      • max_hit
        only increments, and pipeline fields cannot read or clear the hit count — you can use
        get_hit_count
        /
        clear_hit_count
        in custom (scope: shared by the current task and its
        run_task
        subtasks); there is still no field channel for cross-node "data" —
        anchor
        is a node name variable (three states: write / clear / reference), not data storage.
    • Reverse signals that it's time to switch to custom: when you start stacking cloned nodes to express a piece of logic, the node graph becomes bloated and hard to read, or you start needing to modify parameters or counts at runtime (override / state-type standard components appear frequently) — it's better to switch than to keep splitting.
    • If you are unsure about the criteria, go back to
      maafw-pipeline
      and try splitting the nodes first.
  2. Fail explicitly, and distinguish three semantics:
    • CustomRecognition returns miss = this candidate is not hit — polling continues to try subsequent candidates, and if all candidates miss in the entire round, it will trigger the host's on_error after the host node's
      timeout
      (not triggered immediately);
    • CustomAction returns failure = triggers the on_error of the node where the action is located; do not swallow implementation exceptions, return failure explicitly — silent false success is a troubleshooting nightmare;
    • Empty return value semantics on the binding side must be checked before writing (example: Python action
      run
      returning None is equivalent to success, recognition
      analyze
      returning None is equivalent to miss — forgetting to write return on the action side is silent false success; subject to binding routing);
    • Directly calling
      context.run_recognition()
      only returns the recognition result, and the error branch is handled by the caller.
  3. Registration names are character-by-character consistent: The registration name (the string in the decorator or registry) is exactly the same as the pipeline node reference, case-sensitive. Synchronize all registration points when renaming — decorator/registry, pipeline references (modify pipeline usage first, then registration), and include enum if there is Schema infrastructure; node name references in custom code (entries of
    run_task
    /
    run_recognition
    /
    run_action
    , targets of
    override_next
    ) are also in the synchronization scope — calls will fail directly (you must check the return value), not silent disconnection; asset paths are also in the synchronization scope, but the framework will not report errors for you — custom code reading images must self-check the loading result (empty image means failure), and missing modifications mostly manifest as silent idling (see naming.md of maafw-pipeline for the full list of reference points).
  4. Signature Routing: The parameter structure of
    analyze
    /
    run
    and context API evolve with the binding version, subject to the routing result. The examples in this article are only for illustration; do not rely on hooks that the framework does not promise (such as stop / interrupt callback — whether they exist and what they are called are subject to the routing result). The framework will not actively interrupt callbacks — for long loops, self-check stop conditions, set timeouts for external IO, and use language mechanisms such as finally / RAII for resource cleanup.
    • Context Capabilities (what levers you have after using custom; names are subject to binding routing):
      run_task
      /
      run_recognition
      /
      run_action
      (run by node, recognition and action are paired);
      run_recognition_direct
      /
      run_action_direct
      (use engine algorithms directly without creating nodes);
      wait_freezes
      (engine waits for the screen to freeze, takes screenshots internally);
      override_pipeline
      /
      override_next
      /
      override_image
      (three patch tools);
      get_node_data
      (read current valid node definition, including patches);
      set_anchor
      /
      get_anchor
      (anchor read/write);
      get_hit_count
      /
      clear_hit_count
      (count read/clear);
      get_tasker
      (get controller / resource — prerequisite for screenshot chain);
      clone
      (fork context).
  5. State Minimization: Try to be stateless in custom; if cross-call memory is really needed (such as a "first execution only" sentinel), indicate the lifecycle — class state is shared across tasks during the lifetime of the agent process, and task re-entry will carry the last value.

工作流

Workflow

  1. 定宿主与注册路径(先于一切实现决策):能不能进程内注册由谁拥有主程序决定——细则、agent 段接线与架构见 references/agent-mode.md。
  2. 判型:
    • 自定义识别算法 →
      CustomRecognition
      (
      analyze
      返回命中 box);
    • 外部调用 / 复杂动作 →
      CustomAction
      (
      run
      返回成功失败);
    • 横切钩子(全局检查、失败引导)→ sink 通道而非散进 action(细则见 patterns.md「事件监听」)。
    特例不与上列并列:非标设备控制走
    CustomController
    ——按控制器接入而非注册(子类 + 工厂构造后 bind 给 Tasker),且仅掌控宿主进程时可行,agent 子进程内框架不提供控制器创建(见 agent-mode.md 的 agent 能力面)。
  3. 最小注册闭环:先接最小节点(DoNothing + 日志)跑通"注册 → pipeline 引用 → 被调到",再填实现。
  4. 建文件与实现:
    • 按业务能力分文件,与 resource 的模块目录对齐(文件形态随语言惯用法,判据见 references/patterns.md「模块边界」);
    • 通用可复用件进共享件目录;注册进唯一的注册点(装饰器或集中注册表);
    • 参数从 pipeline 的
      custom_action_param
      /
      custom_recognition_param
      (JSON)进入,经解析工具函数做类型 / 必填 / 默认值处理与运行时校验后使用,不裸取字段;实现自带行为契约文档(docstring / 注释等该语言惯例)——写清预期行为与参数语义。
  5. 复用而非重写:
    • custom 内需要识别 / 动作时优先引用既有 pipeline 节点——
      context.run_recognition("<节点名>", image)
      直接跑某节点的识别;
    • 图像来源按用途:识别回调判定本次画面用
      argv.image
      ;动作后要新画面经既有 controller 主动截图等完成后再取图(样例形态
      context.tasker.controller.post_screencap().wait().get()
      ,名字以 binding 路由为准);
      cached_image
      属 controller、是最近一次已完成截图的缓存、不会自己刷新——时效可接受才用;
    • 识别 → 操作 → 再识别法则在 custom 内同样成立(见 maafw-pipeline 的 flow-patterns)。
  6. 失败、取消与状态作用域:失败三语义见硬护栏 2;长循环与外部 IO 的取消策略见硬护栏 4;跨调用状态的生命周期注明(硬护栏 5);override 补丁写明对象、作用域与生命周期(见 patterns.md「状态与作用域」)。
  7. 验证:日志带节点名 + 入参摘要 + 结果摘要(这是排错唯一线索);单元验证后转
    maafw-debug
    单节点流程与端到端。
  1. Determine host and registration path (prior to all implementation decisions): Whether in-process registration is possible is determined by who owns the main program — see references/agent-mode.md for details, agent segment wiring and architecture.
  2. Type Determination:
    • Custom recognition algorithm →
      CustomRecognition
      (
      analyze
      returns hit box);
    • External calls / complex actions →
      CustomAction
      (
      run
      returns success or failure);
    • Cross-cutting hooks (global checks, failure guidance) → sink channel instead of scattered into actions (see "Event Listening" in patterns.md for details).
    Special cases are not included in the above categories: Non-standard device control uses
    CustomController
    — it is accessed as a controller rather than registered (subclass + factory constructed and then bound to Tasker), and is only feasible when you control the host process; the framework does not provide controller creation in the agent child process (see agent capabilities in agent-mode.md).
  3. Minimum Registration Closed Loop: First connect the minimum node (DoNothing + logs) to get through the "registration → pipeline reference → being called" flow, then fill in the implementation.
  4. File Creation and Implementation:
    • Split files by business capability, aligned with the module directory of resource (file form follows language idioms, see "Module Boundaries" in references/patterns.md for criteria);
    • Common reusable components go into the shared component directory; register at a single registration point (decorator or centralized registry);
    • Parameters enter from pipeline's
      custom_action_param
      /
      custom_recognition_param
      (JSON), and are used after being processed for type / required / default values and runtime validation by parsing utility functions, do not take fields directly; the implementation comes with behavior contract documentation (docstring / comments and other language conventions) — clearly write expected behavior and parameter semantics.
  5. Reuse instead of Rewrite:
    • When recognition / action is needed in custom, prioritize referencing existing pipeline nodes —
      context.run_recognition("<node_name>", image)
      directly runs the recognition of a node;
    • Image source depends on usage: use
      argv.image
      for the current screen in recognition callback; for a new screen after an action, actively take a screenshot through the existing controller and get the image after it is completed (example form
      context.tasker.controller.post_screencap().wait().get()
      , name is subject to binding routing);
      cached_image
      belongs to the controller, is the cache of the latest completed screenshot, and will not refresh by itself — use only when timeliness is acceptable;
    • The recognize → operate → re-recognize rule also applies in custom (see flow-patterns of maafw-pipeline).
  6. Failure, Cancellation and State Scope: See Hard Guardrail 2 for the three failure semantics; see Hard Guardrail 4 for cancellation strategies for long loops and external IO; indicate the lifecycle of cross-call state (Hard Guardrail 5); specify the target, scope and lifecycle of override patches (see "State and Scope" in patterns.md).
  7. Validation: Logs include node name + input parameter summary + result summary (this is the only clue for troubleshooting); after unit validation, proceed to
    maafw-debug
    for single-node flow and end-to-end testing.

审查清单

Review Checklist

  • 判据:这个逻辑真的 pipeline 表达不了吗?有没有把流程控制藏进 custom(回调内部不展开为节点图)?
  • 命名能自解释:注册名与 pipeline 节点命名风格对齐(惯例 PascalCase),项目内统一。
  • 参数底线:入口运行时校验——类型 / 必填 / 默认值 / 错误行为明确;有 Schema 基建的项目同步 enum 与注册名,并确认校验入口真的覆盖(建文件本身不产生校验)。
  • 失败路径:三种失败语义分清;没有裸
    except: pass
    ;空返回值的 binding 语义核对过。
  • 阻塞与取消:长循环有停止检查、外部 IO 有超时、清理有 finally / RAII 等机制。
  • 注册点:改名改删时,pipeline 用法 → 注册点 → (有 Schema 则含 enum)→ custom 代码内节点名引用与素材路径,全部同步。
  • Criteria: Can this logic really not be expressed by pipeline? Is process control hidden in custom (the inside of the callback is not expanded into a node graph)?
  • Naming is self-explanatory: registration names are aligned with pipeline node naming style (conventionally PascalCase), unified within the project.
  • Parameter baseline: entry runtime validation — clear type / required / default value / error behavior; projects with Schema infrastructure synchronize enum and registration names, and confirm that the validation entry is really covered (creating a file itself does not generate validation).
  • Failure path: distinguish the three failure semantics; no bare
    except: pass
    ; binding semantics of empty return values have been checked.
  • Blocking and cancellation: long loops have stop checks, external IO has timeouts, cleanup has mechanisms such as finally / RAII.
  • Registration points: when renaming, modifying or deleting, synchronize all: pipeline usage → registration points → (include enum if there is Schema) → node name references and asset paths in custom code.

深入阅读(按需)

Further Reading (On Demand)

  • references/patterns.md — Custom 实现决策指南:注册与加载、模块边界、参数契约、Pipeline 复用、状态作用域、失败取消、事件监听、验证
  • references/agent-mode.md — Agent 子进程模式:启动到退出的阶段职责、连接标识符、PI_* 容错、双日志、宿主与隔离决策
  • references/patterns.md — Custom implementation decision guide: registration and loading, module boundaries, parameter contracts, Pipeline reuse, state scope, failure and cancellation, event listening, validation
  • references/agent-mode.md — Agent child process mode: phase responsibilities from startup to exit, connection identifiers, PI_* fault tolerance, dual logging, host and isolation decisions