SuperAPI AI Directory

maafw-custom

Agents & models

View repository

Updated 10-16 09:34

About this skill

MaaFW Custom Development

Use Custom to fill in logic that Pipeline cannot express: reading external state, cross-node data passing, dynamic counting, complex decisions, custom recognition algorithms. binding class signatures and context API Do not write from memory, always route to the MaaLLMWiki bindings / native-api index (version-locked, references trace back to source; when the maallmwiki skill is installed, it carries the full routing process).

Hard Guardrails

  1. If pipeline can do it, don't use custom(the default direction, determined by cost asymmetry rather than preference):
    • Why prefer pipeline: the custom node'souter layeris still subject to engine scheduling and observation—next candidate order (first hit executes first),max_hit、on_error, focus, and option's pipeline_override still take effect as usual, andrun_task -initiated subflows are also executed by the engine. What loses automatic constraints isinside the callback—internal loops / waits / network IO are not expanded into a node graph, and node timeout cannot interrupt a blocking callback; duration, cancellation, retries, and internal state are all up to you, leaving only logs for the toolchain.
    • Always pipeline: visually determinable waits / clicks / swipes / text finding / loops / branches—loops = next cycle + exit + max_hit (for fixed-count repetition use repeat / repeat_delay / repeat_wait_freezes, MaaFW v5.3+), branches = next order + masking.
    • Only then use Custom: reading runtime data outside the screen (files, network, process state, time) or control flow based on it; decisions that recognition / action fields cannot express; external calls.
    • Two hard boundaries of field expressiveness(in real projects, the bulk of custom usage is here):
      • And / Or areboolean combinations—no numeric comparison, no cross-node arithmetic (numeric comparison and multi-node numeric expressions are custom territory); the only data flow during combination is box:box_index selects the output,sub_name passes the filtered box as ROI, Or takes the box of the hit;
      • max_hit only increments, and pipeline fieldscannot read or clearhit counts—within custom you can use get_hit_count / clear_hit_count(scope: the current task and its run_task subtasks share it); there is still no field channel for cross-node "data"—anchor is a node-name variable (write / clear / reference three states), not a data store.
    • Counter-signals that it should be converted to custom: when you start piling up clone nodes to express a piece of logic, the node graph bloats and becomes hard to read, and you start needing runtime parameter changes or counting (override / state-type standard components frequently appear)—continuing to split is worse than converting.
    • If unsure about the criteria, first go back to maafw-pipeline and try splitting nodes.
  2. Failures must be explicit, and distinguish three semantics:
    • CustomRecognition returning no match = this candidate does not match—polling continues to try subsequent candidates; if the entire round has no match, after the host node timeout timeout, it proceeds tothe host's on_error (not triggered immediately);
    • CustomAction returning failure = proceeds tothe node where the action resides's on_error; implementation exceptions are not swallowed, explicit failure is returned—silent false success is a debugging nightmare;
    • on the binding sideempty return value semanticscheck before writing (example: Python action run returning None is equivalent to success, recognition analyze returning None is equivalent to no match—omitting return on the action side is silent false success; the binding routing is authoritative);
    • direct call context.run_recognition() only returns the recognition result; error branches are handled by the caller.
  3. Registration names must match character by character: the registration name (the string in the decorator or registry) must be exactly identical to the pipeline node reference, case-sensitive.When renaming, synchronize all registration points(for the full table of reference points, see maafw-pipeline's naming.md):
    • registry and pipeline references: decorator/registry, pipeline's custom_recognition / custom_action(change pipeline usage first, then change the registration); if there is Schema infrastructure, include the enum;
    • node name references within Custom code:run_task / run_recognition / run_action 's entry,override_next 's target are also within the synchronization scope—the call fails immediately(the return value must be checked), not a silent disconnection;
    • asset paths: also within the synchronization scope, but the framework will not report errors for you—custom code reading images must self-check the loading result (an empty image means failure); a missed change mostly manifests as silent idling.
  4. Signature routing:analyze / run 's parameter structure and context API evolve with the binding version; the routing result is authoritative. The examples in this document are only for shape illustration; do not rely on hooks not promised by the framework (such as stop / interrupt callbacks—whether they exist and what they are called is subject to the routing result). The frameworkwill not actively interrupt callbacks—long loops must self-check stop conditions, external IO must set timeouts, and resource cleanup should use finally / RAII or other mechanisms of the language.
    • Context capability surface(what levers are available after enabling custom; names are subject to binding routing):
      • Execution and waiting:run_task / run_recognition / run_action(run by node, recognition and action paired);run_recognition_direct / run_action_direct(use engine algorithms directly without creating nodes);wait_freezes(the engine waits for the screen to become static, taking screenshots internally);
      • Patches and node definitions:override_pipeline / override_next / override_image(the three patch items);get_node_data(read the current effective node definition, including patches);
      • State and instances:set_anchor / get_anchor(anchor read/write);get_hit_count / clear_hit_count(counter read/clear);get_tasker(get controller / resource—the prerequisite for the screenshot chain);clone(fork context).
  5. State minimization: keep custom as stateless as possible; if cross-call memory is truly needed (such as a "first execution only" sentinel), specify its lifecycle—class state is shared across tasks while the agent process is alive, and task re-entry will carry the previous value.

Workflow

  1. Determine the host and registration path(Before any implementation decision): Can in-process registration be done bywho owns the main programto decide—for details, agent segment wiring and architecture see references/agent-mode.md。

  2. Type determination:

    • Custom recognition algorithm → CustomRecognition(analyze returns a hit box);
    • External calls / complex actions → CustomAction(run returns success/failure);
    • Cross-cutting hooks (global checks, failure guidance) → sink channel rather than scattered into actions (for details see references/patterns.md"Event Listening").

    Special cases are not listed alongside the above: non-standard device control goes through CustomController—according tocontrolleraccess rather than registration (subclass + factory construction then bind to Tasker), and is only feasible when controlling the host process; the framework within the agent subprocess does not provide controller creation (see references/agent-mode.md agent capability surface).

  3. Minimal registration closed loop: first connect the minimal node (DoNothing + logging) and get "registration → pipeline reference → being called" working, then fill in the implementation.

  4. File creation and implementation:

    • Split files by business capability, aligned with the resource module directory (file form follows language idioms, for criteria see references/patterns.md"Module Boundaries");
    • Common reusable pieces go into the shared components directory; register into the single registration point (decorator or centralized registry);
    • Parameters enter from the pipeline's custom_action_param / custom_recognition_param(JSON), and are used after type/required/default handling and runtime validation through parsing utility functions, without directly accessing fields; the implementation includes its own behavioral contract documentation (docstring / comments per language convention)—clearly state expected behavior and parameter semantics (for node wiring see references/patterns.md"Parameter Contract").
  5. Reuse rather than rewrite:

    • When recognition/actions are needed within custom, prefer referencing existing pipeline nodes—context.run_recognition("<节点名>", image) directly runs the recognition of a certain node;
    • Image source by purpose: the recognition callback determines the current frame using argv.image; if a new frame is needed after an action, actively take a screenshot via the existing controllerand wait for completion before fetching the image(sample form context.tasker.controller.post_screencap().wait().get(), names are subject to binding routing);cached_image belongs to controller, is the cache of the most recent completed screenshot,does not refresh itself—only use if timeliness is acceptable;
    • The recognition → operation → re-recognition rule also holds within custom (see maafw-pipeline's flow-patterns.md)。
  6. Failure, cancellation and state scope: for the three semantics of failure see hard guardrail 2; for cancellation strategy of long loops and external IO see hard guardrail 4; note the lifecycle of state across calls (hard guardrail 5); override patches must specify object, scope and lifecycle (see references/patterns.md"State and Scope").

  7. Verification: logs include node name + input parameter summary + result summary (this is the only clue for troubleshooting); after unit verification, move to maafw-debug single-node flow and end-to-end.

Review checklist

  • Criterion: can this logic really not be expressed by pipeline? Has flow control been hidden inside custom (callbacks not expanded internally into a node graph)?
  • Naming is self-explanatory: registration names align with pipeline node naming style (convention PascalCase), unified within the project.
  • Parameter baseline: entry-point runtime validation—types / required / default values / error behavior must be explicit; for projects with Schema infrastructure, keep enum and registration names in sync, and confirm that the validation entry point actually covers them (creating the file itself does not produce validation).
  • Failure paths: clearly distinguish the three failure semantics; no bare except: pass; the binding semantics of empty return values have been checked.
  • Blocking and cancellation: long loops have stop checks, external IO has timeouts, cleanup has finally / RAII and other mechanisms.
  • Registration points: when renaming, modifying, or deleting, pipeline usage → registration points → (if there is a Schema, include enum) → node name references and asset paths in custom code, all must be synchronized.

In-depth reading (as needed)

  • references/patterns.md — Custom implementation decision guide: registration and loading, module boundaries, parameter contracts and node wiring examples, Pipeline reuse, state scope, failure cancellation, event listening, validation
  • references/agent-mode.md — Agent subprocess mode: phase responsibilities from startup to exit, connection identifiers, PI_* fault tolerance, dual logging, host and isolation decisions