Skip to main content
Glama

phone_run_steps

Destructive

Run a sequence of phone actions in one call and get the ending screen back immediately, replacing one turn per tap. Validates the whole batch first and stops at the first failure.

Instructions

THE DEFAULT WAY TO ACT: run a sequence of actions in ONE call and get the screen it ended on back in the same reply (observe, on by default) — one turn instead of one per tap. The daemon validates the whole sequence first, holds one control lock, and stops at the first failure. Use it as soon as a segment is understood; keep single-action tools for exploring an unknown screen. Step kinds: launch_app, open_url, tap_locator, tap_label, tap, type, key, shortcut, scroll, swipe, drag, longpress, multi_longpress (2–5 explicit normalized points held simultaneously), back, alert (system alerts; taps on them do not act), picker, perform (toggle a switch, adjust a slider: element+snapshot from phone_elements), wait_for, pause. Drag points can dwell at an edge while keeping one finger down. Guard every screen change with wait_for; never batch an irreversible step you have not verified. These step objects are exactly a saved flow's steps — when the response carries flow_suggestion, ask the user whether to keep the task as a flow (phone_flow_draft). The result names completed/applied counts and the failed step; retry_safe=false means DO NOT replay.

Input Schema

NameRequiredDescriptionDefault
stepsYesOrdered steps. The daemon validates the whole list before sending the first action and stops immediately when any action or wait condition fails.
observeNoReturn the screen the batch ended on (`snapshot`, `elements`, `alert`) in the same reply, so the next decision needs no phone_elements call. Default true; set false only when you will not look at the result.
instanceNoInstalled local instance name, such as i13 or default. Omit to use the configured daemon. No fallback on failure.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed7 schema fields changedv0.27.34
    • addedInput schema / $defs / PhoneDragPoint / properties / dwell_ms / minimum
      Added value: +0
    • addedInput schema / $defs / PhoneElementLocator / properties / label_contains
      Added value: +{
      +  "description": "Case-sensitive literal substring of the normalized label.",
      +  "maxLength": 1000,
      +  "minLength": 1,
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • changedInput schema / $defs / PhoneStep / oneOf
      Previous value: -[
      -  {
      -    "additionalProperties": false,
      -    "description": "Tap normalized screen coordinates. Prefer `tap_locator` (durable in a\nsaved flow) whenever the target has a label or identifier.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "tap",
      -        "type": "string"
      -      },
      -      "x": {
      -        "type": "number"
      -      },
      -      "y": {
      -        "type": "number"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "x",
      -      "y"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Long-press normalized screen coordinates.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "duration_ms": {
      -        "default": 600,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "longpress",
      -        "type": "string"
      -      },
      -      "x": {
      -        "type": "number"
      -      },
      -      "y": {
      -        "type": "number"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "x",
      -      "y"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Hold 2–5 distinct normalized points simultaneously; array length is finger count.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "duration_ms": {
      -        "default": 1000,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "multi_longpress",
      -        "type": "string"
      -      },
      -      "points": {
      -        "items": {
      -          "$ref": "#/$defs/PhoneHoldPoint"
      -        },
      -        "type": "array"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "points"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Swipe once from one normalized screen point to another.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "duration_ms": {
      -        "default": 300,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "swipe",
      -        "type": "string"
      -      },
      -      "x1": {
      -        "type": "number"
      -      },
      -      "x2": {
      -        "type": "number"
      -      },
      -      "y1": {
      -        "type": "number"
      -      },
      -      "y2": {
      -        "type": "number"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "x1",
      -      "y1",
      -      "x2",
      -      "y2"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Hold, then drag continuously. Use either x1/y1/x2/y2 or 2–16\nnormalized points with optional dwell_ms at each stop. For points,\nduration_ms is travel time per segment; total hold/travel/dwell <= 10s.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "duration_ms": {
      -        "default": 300,
      -        "type": "integer"
      -      },
      -      "hold_ms": {
      -        "default": 500,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "drag",
      -        "type": "string"
      -      },
      -      "points": {
      -        "default": null,
      -        "items": {
      -          "$ref": "#/$defs/PhoneDragPoint"
      -        },
      -        "type": [
      -          "array",
      -          "null"
      -        ]
      -      },
      -      "x1": {
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      },
      -      "x2": {
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      },
      -      "y1": {
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      },
      -      "y2": {
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      }
      -    },
      -    "required": [
      -      "kind"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Tap one exact, unique accessibility label.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "tap_label",
      -        "type": "string"
      -      },
      -      "label": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "label"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Tap the one current element matching every supplied semantic locator\nfield. Zero or multiple matches send no tap.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "if_present": {
      -        "description": "Pass instead of failing when no element matches — for screens that\nappear only sometimes (\"Not now\", \"Skip\"). Nothing is sent then.",
      -        "type": "boolean"
      -      },
      -      "kind": {
      -        "const": "tap_locator",
      -        "type": "string"
      -      },
      -      "locator": {
      -        "$ref": "#/$defs/PhoneElementLocator"
      -      },
      -      "via": {
      -        "description": "`point`: tap the centre of the element's live frame instead of\nXCUIElement's click — for custom controls that ACK a click and do\nnothing. Default `element`.",
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "locator"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Type Unicode text into the focused field. `clear=true` clears that field\nimmediately before inserting the text as one compound action.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "clear": {
      -        "default": false,
      -        "type": "boolean"
      -      },
      -      "kind": {
      -        "const": "type",
      -        "type": "string"
      -      },
      -      "text": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "text"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Send one supported device-native key.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "key",
      -        "type": "string"
      -      },
      -      "name": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "name"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Trigger Home or Spotlight.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "shortcut",
      -        "type": "string"
      -      },
      -      "name": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "name"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Scroll with the same normalized anchor and deltas as `phone_scroll`.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "dx": {
      -        "type": "number"
      -      },
      -      "dy": {
      -        "type": "number"
      -      },
      -      "kind": {
      -        "const": "scroll",
      -        "type": "string"
      -      },
      -      "x": {
      -        "type": "number"
      -      },
      -      "y": {
      -        "type": "number"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "x",
      -      "y",
      -      "dx",
      -      "dy"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Launch or foreground an installed app: give exactly one of `bundle`\n(its exact bundle identifier) or `app` (its name — 微信, WeChat,\n招商银行, Settings — which the daemon resolves through phone_apps; a\nloose name that starts a word of exactly one installed app's name also\nlaunches it, and the step's `resolved` says which; an ambiguous or\nunknown name is refused with candidates, nothing sent).",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "app": {
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      },
      -      "bundle": {
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      },
      -      "fresh": {
      -        "description": "Quit the app first so it opens on its first screen, not the page\nit was left on (flows recorded on the phone start there).",
      -        "type": "boolean"
      -      },
      -      "kind": {
      -        "const": "launch_app",
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Open a link (`https://…`, or a deep link such as `weixin://`) with\nthe app iOS assigns to it. http/https always work; other schemes\n(tel, sms, mailto, app deep links) are refused unless the operator\nallowed them in IPHONE_USE_ALLOW_URL_SCHEMES. It leaves the current\napp, so guard the next screen with `wait_for`.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "open_url",
      -        "type": "string"
      -      },
      -      "url": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "url"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Navigate back inside the current application.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "back",
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Press a button on a system alert (UIAlertController) through the device runner's\nnative alert route — the only path that actually acts on one; taps on\nalert buttons are acknowledged without effect. Give the exact button\ntext, or `action: accept|dismiss` for the default/cancel button. Fails\nclosed when no alert is showing.",
      -    "properties": {
      -      "action": {
      -        "default": null,
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      },
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "button": {
      -        "default": null,
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      },
      -      "if_present": {
      -        "description": "Pass instead of failing when no alert is up — for prompts that\nappear only sometimes (\"Allow Paste\"). Flows using it need a\nclient that knows it.",
      -        "type": "boolean"
      -      },
      -      "kind": {
      -        "const": "alert",
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Select a value in a native picker wheel.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "column": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "picker",
      -        "type": "string"
      -      },
      -      "value": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "value"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Invoke a named affordance on a snapshot-bound element: toggle a\nswitch, increment/decrement or adjust a slider or stepper, and the\nother names in phone_capabilities (`perform_actions`). `element` and\n`snapshot` come from phone_elements. Snapshot-bound, so not for saved\nflows. `value` only with `adjust`; `duration_ms` only with\n`menu`/`force_press`. Verify the new `value` with wait_for.",
      -    "properties": {
      -      "action": {
      -        "type": "string"
      -      },
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "duration_ms": {
      -        "default": null,
      -        "type": [
      -          "integer",
      -          "null"
      -        ]
      -      },
      -      "element": {
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "perform",
      -        "type": "string"
      -      },
      -      "snapshot": {
      -        "type": "string"
      -      },
      -      "value": {
      -        "default": null,
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "element",
      -      "snapshot",
      -      "action"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Poll the current element tree until the semantic expectation holds.",
      -    "properties": {
      -      "expect": {
      -        "$ref": "#/$defs/PhoneUiExpectation"
      -      },
      -      "kind": {
      -        "const": "wait_for",
      -        "type": "string"
      -      },
      -      "poll_ms": {
      -        "default": 250,
      -        "type": "integer"
      -      },
      -      "timeout_ms": {
      -        "default": 15000,
      -        "type": "integer"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "expect"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "A short animation pause. Prefer `wait_for` for correctness.",
      -    "properties": {
      -      "kind": {
      -        "const": "pause",
      -        "type": "string"
      -      },
      -      "ms": {
      -        "type": "integer"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "ms"
      -    ],
      -    "type": "object"
      -  }
      -]New value: +[
      +  {
      +    "additionalProperties": false,
      +    "description": "Tap normalized screen coordinates. Prefer `tap_locator` (durable in a\nsaved flow) whenever the target has a label or identifier.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "tap",
      +        "type": "string"
      +      },
      +      "x": {
      +        "type": "number"
      +      },
      +      "y": {
      +        "type": "number"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "x",
      +      "y"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Long-press normalized screen coordinates.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "duration_ms": {
      +        "default": 600,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "longpress",
      +        "type": "string"
      +      },
      +      "x": {
      +        "type": "number"
      +      },
      +      "y": {
      +        "type": "number"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "x",
      +      "y"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Hold 2–5 distinct normalized points simultaneously; array length is finger count.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "duration_ms": {
      +        "default": 1000,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "multi_longpress",
      +        "type": "string"
      +      },
      +      "points": {
      +        "items": {
      +          "$ref": "#/$defs/PhoneHoldPoint"
      +        },
      +        "type": "array"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "points"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Swipe once from one normalized screen point to another.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "duration_ms": {
      +        "default": 300,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "swipe",
      +        "type": "string"
      +      },
      +      "x1": {
      +        "type": "number"
      +      },
      +      "x2": {
      +        "type": "number"
      +      },
      +      "y1": {
      +        "type": "number"
      +      },
      +      "y2": {
      +        "type": "number"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "x1",
      +      "y1",
      +      "x2",
      +      "y2"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Hold, then drag continuously. Use either x1/y1/x2/y2 or 2–16\nnormalized points with optional dwell_ms at each stop. For points,\nduration_ms is travel time per segment; total hold/travel/dwell <= 10s.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "duration_ms": {
      +        "default": 300,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "hold_ms": {
      +        "default": 500,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "drag",
      +        "type": "string"
      +      },
      +      "points": {
      +        "default": null,
      +        "items": {
      +          "$ref": "#/$defs/PhoneDragPoint"
      +        },
      +        "type": [
      +          "array",
      +          "null"
      +        ]
      +      },
      +      "x1": {
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "x2": {
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "y1": {
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "y2": {
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      }
      +    },
      +    "required": [
      +      "kind"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Tap one exact, unique accessibility label.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "tap_label",
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "label"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Tap the one current element matching every supplied semantic locator\nfield. Zero or multiple matches send no tap.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "if_present": {
      +        "description": "Pass instead of failing when no element matches — for screens that\nappear only sometimes (\"Not now\", \"Skip\"). Nothing is sent then.",
      +        "type": "boolean"
      +      },
      +      "kind": {
      +        "const": "tap_locator",
      +        "type": "string"
      +      },
      +      "locator": {
      +        "$ref": "#/$defs/PhoneElementLocator"
      +      },
      +      "via": {
      +        "description": "`point`: tap the centre of the element's live frame instead of\nXCUIElement's click — for custom controls that ACK a click and do\nnothing. Default `element`.",
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "locator"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Type Unicode text into the focused field. `clear=true` clears that field\nimmediately before inserting the text as one compound action.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "budget_ms": {
      +        "description": "Optional per-call typing budget (6000..15000 ms).",
      +        "maximum": 15000,
      +        "minimum": 6000,
      +        "type": [
      +          "integer",
      +          "null"
      +        ]
      +      },
      +      "clear": {
      +        "default": false,
      +        "type": "boolean"
      +      },
      +      "kind": {
      +        "const": "type",
      +        "type": "string"
      +      },
      +      "text": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "text"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Continue only the unsent suffix held under a single-use original-field token.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "continue_token": {
      +        "maxLength": 64,
      +        "minLength": 64,
      +        "pattern": "^[0-9a-f]{64}$",
      +        "type": "string"
      +      },
      +      "kind": {
      +        "const": "type_continue",
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "continue_token"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Send one supported device-native key.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "key",
      +        "type": "string"
      +      },
      +      "name": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "name"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Trigger Home or Spotlight.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "shortcut",
      +        "type": "string"
      +      },
      +      "name": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "name"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Scroll with the same normalized anchor and deltas as `phone_scroll`.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "dx": {
      +        "type": "number"
      +      },
      +      "dy": {
      +        "type": "number"
      +      },
      +      "kind": {
      +        "const": "scroll",
      +        "type": "string"
      +      },
      +      "x": {
      +        "type": "number"
      +      },
      +      "y": {
      +        "type": "number"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "x",
      +      "y",
      +      "dx",
      +      "dy"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Launch or foreground an installed app: give exactly one of `bundle`\n(its exact bundle identifier) or `app` (its name — 微信, WeChat,\n招商银行, Settings — which the daemon resolves through phone_apps; a\nloose name that starts a word of exactly one installed app's name also\nlaunches it, and the step's `resolved` says which; an ambiguous or\nunknown name is refused with candidates, nothing sent).",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "app": {
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "bundle": {
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "fresh": {
      +        "description": "Quit the app first so it opens on its first screen, not the page\nit was left on (flows recorded on the phone start there).",
      +        "type": "boolean"
      +      },
      +      "kind": {
      +        "const": "launch_app",
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Open a link (`https://…`, or a deep link such as `weixin://`) with\nthe app iOS assigns to it. http/https always work; other schemes\n(tel, sms, mailto, app deep links) are refused unless the operator\nallowed them in IPHONE_USE_ALLOW_URL_SCHEMES. It leaves the current\napp, so guard the next screen with `wait_for`.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "open_url",
      +        "type": "string"
      +      },
      +      "url": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "url"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Navigate back inside the current application.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "back",
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Press a button on a system alert (UIAlertController) through the device runner's\nnative alert route — the only path that actually acts on one; taps on\nalert buttons are acknowledged without effect. Give the exact button\ntext, or `action: accept|dismiss` for the default/cancel button. Fails\nclosed when no alert is showing.",
      +    "properties": {
      +      "action": {
      +        "default": null,
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "after_ms": {
      +        "default": 0,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "button": {
      +        "default": null,
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "if_present": {
      +        "description": "Pass instead of failing when no alert is up — for prompts that\nappear only sometimes (\"Allow Paste\"). Flows using it need a\nclient that knows it.",
      +        "type": "boolean"
      +      },
      +      "kind": {
      +        "const": "alert",
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Select a value in a native picker wheel.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "column": {
      +        "default": 0,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "picker",
      +        "type": "string"
      +      },
      +      "value": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "value"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Invoke a named affordance on a snapshot-bound element: toggle a\nswitch, increment/decrement or adjust a slider or stepper, and the\nother names in phone_capabilities (`perform_actions`). `element` and\n`snapshot` come from phone_elements. Snapshot-bound, so not for saved\nflows. `value` only with `adjust`; `duration_ms` only with\n`menu`/`force_press`. Verify the new `value` with wait_for.",
      +    "properties": {
      +      "action": {
      +        "type": "string"
      +      },
      +      "after_ms": {
      +        "default": 0,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "duration_ms": {
      +        "default": null,
      +        "minimum": 0,
      +        "type": [
      +          "integer",
      +          "null"
      +        ]
      +      },
      +      "element": {
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "perform",
      +        "type": "string"
      +      },
      +      "snapshot": {
      +        "type": "string"
      +      },
      +      "value": {
      +        "default": null,
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "element",
      +      "snapshot",
      +      "action"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Poll until the expectation holds. For tree waits, timeout_ms:0 makes\none observation attempt bounded to 500ms, without polling or read retry.\nUse observe:false to omit the separate final batch observation.",
      +    "properties": {
      +      "expect": {
      +        "$ref": "#/$defs/PhoneUiExpectation"
      +      },
      +      "kind": {
      +        "const": "wait_for",
      +        "type": "string"
      +      },
      +      "poll_ms": {
      +        "default": 250,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "timeout_ms": {
      +        "default": 15000,
      +        "minimum": 0,
      +        "type": "integer"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "expect"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "A short animation pause. Prefer `wait_for` for correctness.",
      +    "properties": {
      +      "kind": {
      +        "const": "pause",
      +        "type": "string"
      +      },
      +      "ms": {
      +        "minimum": 0,
      +        "type": "integer"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "ms"
      +    ],
      +    "type": "object"
      +  }
      +]
    • changedInput schema / $defs / PhoneUiExpectation / properties / absent / items / $ref
      Previous value: -"#/$defs/PhoneElementLocator"New value: +"#/$defs/WaitSelector"
    • changedInput schema / $defs / PhoneUiExpectation / properties / present / items / $ref
      Previous value: -"#/$defs/PhoneElementLocator"New value: +"#/$defs/WaitSelector"
    • addedInput schema / $defs / WaitIndex
      Added value: +{
      +  "description": "Zero-based ordinal among matches; index 2 requires at least three matches.",
      +  "maximum": 199,
      +  "minimum": 0,
      +  "type": "integer"
      +}
    • addedInput schema / $defs / WaitSelector
      Added value: +{
      +  "additionalProperties": false,
      +  "description": "Flat legacy locator fields plus a wait-only bounded ordinal. Custom parsing\npreserves strict unknown/duplicate-field rejection with flattened schema.",
      +  "properties": {
      +    "enabled": {
      +      "type": [
      +        "boolean",
      +        "null"
      +      ]
      +    },
      +    "focused": {
      +      "type": [
      +        "boolean",
      +        "null"
      +      ]
      +    },
      +    "identifier": {
      +      "type": [
      +        "string",
      +        "null"
      +      ]
      +    },
      +    "index": {
      +      "$ref": "#/$defs/WaitIndex"
      +    },
      +    "kind": {
      +      "type": [
      +        "string",
      +        "null"
      +      ]
      +    },
      +    "label": {
      +      "type": [
      +        "string",
      +        "null"
      +      ]
      +    },
      +    "label_contains": {
      +      "description": "Case-sensitive literal substring of the normalized label.",
      +      "maxLength": 1000,
      +      "minLength": 1,
      +      "type": [
      +        "string",
      +        "null"
      +      ]
      +    },
      +    "value": {
      +      "type": [
      +        "string",
      +        "null"
      +      ]
      +    },
      +    "visible": {
      +      "type": [
      +        "boolean",
      +        "null"
      +      ]
      +    }
      +  },
      +  "type": "object"
      +}
  2. Changed4 schema fields changedv0.27.3
    • changedInput schema / $defs / PhoneStep / oneOf
      Previous value: -[
      -  {
      -    "additionalProperties": false,
      -    "description": "Tap normalized screen coordinates. Prefer `tap_locator` (durable in a\nsaved flow) whenever the target has a label or identifier.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "tap",
      -        "type": "string"
      -      },
      -      "x": {
      -        "type": "number"
      -      },
      -      "y": {
      -        "type": "number"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "x",
      -      "y"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Long-press normalized screen coordinates.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "duration_ms": {
      -        "default": 600,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "longpress",
      -        "type": "string"
      -      },
      -      "x": {
      -        "type": "number"
      -      },
      -      "y": {
      -        "type": "number"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "x",
      -      "y"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Hold 2–5 distinct normalized points simultaneously; array length is finger count.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "duration_ms": {
      -        "default": 1000,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "multi_longpress",
      -        "type": "string"
      -      },
      -      "points": {
      -        "items": {
      -          "$ref": "#/$defs/PhoneHoldPoint"
      -        },
      -        "type": "array"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "points"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Swipe once from one normalized screen point to another.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "duration_ms": {
      -        "default": 300,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "swipe",
      -        "type": "string"
      -      },
      -      "x1": {
      -        "type": "number"
      -      },
      -      "x2": {
      -        "type": "number"
      -      },
      -      "y1": {
      -        "type": "number"
      -      },
      -      "y2": {
      -        "type": "number"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "x1",
      -      "y1",
      -      "x2",
      -      "y2"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Hold, then drag continuously. Use either x1/y1/x2/y2 or 2–16\nnormalized points with optional dwell_ms at each stop. For points,\nduration_ms is travel time per segment; total hold/travel/dwell <= 10s.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "duration_ms": {
      -        "default": 300,
      -        "type": "integer"
      -      },
      -      "hold_ms": {
      -        "default": 500,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "drag",
      -        "type": "string"
      -      },
      -      "points": {
      -        "default": null,
      -        "items": {
      -          "$ref": "#/$defs/PhoneDragPoint"
      -        },
      -        "type": [
      -          "array",
      -          "null"
      -        ]
      -      },
      -      "x1": {
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      },
      -      "x2": {
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      },
      -      "y1": {
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      },
      -      "y2": {
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      }
      -    },
      -    "required": [
      -      "kind"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Tap one exact, unique accessibility label.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "tap_label",
      -        "type": "string"
      -      },
      -      "label": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "label"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Tap the one current element matching every supplied semantic locator\nfield. Zero or multiple matches send no tap.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "if_present": {
      -        "description": "Pass instead of failing when no element matches — for screens that\nappear only sometimes (\"Not now\", \"Skip\"). Nothing is sent then.",
      -        "type": "boolean"
      -      },
      -      "kind": {
      -        "const": "tap_locator",
      -        "type": "string"
      -      },
      -      "locator": {
      -        "$ref": "#/$defs/PhoneElementLocator"
      -      },
      -      "via": {
      -        "description": "`point`: tap the centre of the element's live frame instead of\nXCUIElement's click — for custom controls that ACK a click and do\nnothing. Default `element`.",
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "locator"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Type Unicode text into the focused field. `clear=true` clears that field\nimmediately before inserting the text as one compound action.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "clear": {
      -        "default": false,
      -        "type": "boolean"
      -      },
      -      "kind": {
      -        "const": "type",
      -        "type": "string"
      -      },
      -      "text": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "text"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Send one supported device-native key.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "key",
      -        "type": "string"
      -      },
      -      "name": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "name"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Trigger Home or Spotlight.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "shortcut",
      -        "type": "string"
      -      },
      -      "name": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "name"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Scroll with the same normalized anchor and deltas as `phone_scroll`.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "dx": {
      -        "type": "number"
      -      },
      -      "dy": {
      -        "type": "number"
      -      },
      -      "kind": {
      -        "const": "scroll",
      -        "type": "string"
      -      },
      -      "x": {
      -        "type": "number"
      -      },
      -      "y": {
      -        "type": "number"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "x",
      -      "y",
      -      "dx",
      -      "dy"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Launch or foreground an installed app: give exactly one of `bundle`\n(its exact bundle identifier) or `app` (its exact name — 微信, WeChat,\n招商银行, Settings — which the daemon resolves through phone_apps; an\nambiguous or unknown name is refused with candidates, nothing sent).",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "app": {
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      },
      -      "bundle": {
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      },
      -      "fresh": {
      -        "description": "Quit the app first so it opens on its first screen, not the page\nit was left on (flows recorded on the phone start there).",
      -        "type": "boolean"
      -      },
      -      "kind": {
      -        "const": "launch_app",
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Open a link (`https://…`, or a deep link such as `weixin://`) with\nthe app iOS assigns to it. http/https always work; other schemes\n(tel, sms, mailto, app deep links) are refused unless the operator\nallowed them in IPHONE_USE_ALLOW_URL_SCHEMES. It leaves the current\napp, so guard the next screen with `wait_for`.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "open_url",
      -        "type": "string"
      -      },
      -      "url": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "url"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Navigate back inside the current application.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "back",
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Press a button on a system alert (UIAlertController) through the device runner's\nnative alert route — the only path that actually acts on one; taps on\nalert buttons are acknowledged without effect. Give the exact button\ntext, or `action: accept|dismiss` for the default/cancel button. Fails\nclosed when no alert is showing.",
      -    "properties": {
      -      "action": {
      -        "default": null,
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      },
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "button": {
      -        "default": null,
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      },
      -      "if_present": {
      -        "description": "Pass instead of failing when no alert is up — for prompts that\nappear only sometimes (\"Allow Paste\"). Flows using it need a\nclient that knows it.",
      -        "type": "boolean"
      -      },
      -      "kind": {
      -        "const": "alert",
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Select a value in a native picker wheel.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "column": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "picker",
      -        "type": "string"
      -      },
      -      "value": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "value"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Invoke a named affordance on a snapshot-bound element: toggle a\nswitch, increment/decrement or adjust a slider or stepper, and the\nother names in phone_capabilities (`perform_actions`). `element` and\n`snapshot` come from phone_elements. Snapshot-bound, so not for saved\nflows. `value` only with `adjust`; `duration_ms` only with\n`menu`/`force_press`. Verify the new `value` with wait_for.",
      -    "properties": {
      -      "action": {
      -        "type": "string"
      -      },
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "duration_ms": {
      -        "default": null,
      -        "type": [
      -          "integer",
      -          "null"
      -        ]
      -      },
      -      "element": {
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "perform",
      -        "type": "string"
      -      },
      -      "snapshot": {
      -        "type": "string"
      -      },
      -      "value": {
      -        "default": null,
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "element",
      -      "snapshot",
      -      "action"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Poll the current element tree until the semantic expectation holds.",
      -    "properties": {
      -      "expect": {
      -        "$ref": "#/$defs/PhoneUiExpectation"
      -      },
      -      "kind": {
      -        "const": "wait_for",
      -        "type": "string"
      -      },
      -      "poll_ms": {
      -        "default": 250,
      -        "type": "integer"
      -      },
      -      "timeout_ms": {
      -        "default": 15000,
      -        "type": "integer"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "expect"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "A short animation pause. Prefer `wait_for` for correctness.",
      -    "properties": {
      -      "kind": {
      -        "const": "pause",
      -        "type": "string"
      -      },
      -      "ms": {
      -        "type": "integer"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "ms"
      -    ],
      -    "type": "object"
      -  }
      -]New value: +[
      +  {
      +    "additionalProperties": false,
      +    "description": "Tap normalized screen coordinates. Prefer `tap_locator` (durable in a\nsaved flow) whenever the target has a label or identifier.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "tap",
      +        "type": "string"
      +      },
      +      "x": {
      +        "type": "number"
      +      },
      +      "y": {
      +        "type": "number"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "x",
      +      "y"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Long-press normalized screen coordinates.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "duration_ms": {
      +        "default": 600,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "longpress",
      +        "type": "string"
      +      },
      +      "x": {
      +        "type": "number"
      +      },
      +      "y": {
      +        "type": "number"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "x",
      +      "y"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Hold 2–5 distinct normalized points simultaneously; array length is finger count.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "duration_ms": {
      +        "default": 1000,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "multi_longpress",
      +        "type": "string"
      +      },
      +      "points": {
      +        "items": {
      +          "$ref": "#/$defs/PhoneHoldPoint"
      +        },
      +        "type": "array"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "points"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Swipe once from one normalized screen point to another.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "duration_ms": {
      +        "default": 300,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "swipe",
      +        "type": "string"
      +      },
      +      "x1": {
      +        "type": "number"
      +      },
      +      "x2": {
      +        "type": "number"
      +      },
      +      "y1": {
      +        "type": "number"
      +      },
      +      "y2": {
      +        "type": "number"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "x1",
      +      "y1",
      +      "x2",
      +      "y2"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Hold, then drag continuously. Use either x1/y1/x2/y2 or 2–16\nnormalized points with optional dwell_ms at each stop. For points,\nduration_ms is travel time per segment; total hold/travel/dwell <= 10s.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "duration_ms": {
      +        "default": 300,
      +        "type": "integer"
      +      },
      +      "hold_ms": {
      +        "default": 500,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "drag",
      +        "type": "string"
      +      },
      +      "points": {
      +        "default": null,
      +        "items": {
      +          "$ref": "#/$defs/PhoneDragPoint"
      +        },
      +        "type": [
      +          "array",
      +          "null"
      +        ]
      +      },
      +      "x1": {
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "x2": {
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "y1": {
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "y2": {
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      }
      +    },
      +    "required": [
      +      "kind"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Tap one exact, unique accessibility label.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "tap_label",
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "label"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Tap the one current element matching every supplied semantic locator\nfield. Zero or multiple matches send no tap.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "if_present": {
      +        "description": "Pass instead of failing when no element matches — for screens that\nappear only sometimes (\"Not now\", \"Skip\"). Nothing is sent then.",
      +        "type": "boolean"
      +      },
      +      "kind": {
      +        "const": "tap_locator",
      +        "type": "string"
      +      },
      +      "locator": {
      +        "$ref": "#/$defs/PhoneElementLocator"
      +      },
      +      "via": {
      +        "description": "`point`: tap the centre of the element's live frame instead of\nXCUIElement's click — for custom controls that ACK a click and do\nnothing. Default `element`.",
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "locator"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Type Unicode text into the focused field. `clear=true` clears that field\nimmediately before inserting the text as one compound action.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "clear": {
      +        "default": false,
      +        "type": "boolean"
      +      },
      +      "kind": {
      +        "const": "type",
      +        "type": "string"
      +      },
      +      "text": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "text"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Send one supported device-native key.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "key",
      +        "type": "string"
      +      },
      +      "name": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "name"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Trigger Home or Spotlight.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "shortcut",
      +        "type": "string"
      +      },
      +      "name": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "name"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Scroll with the same normalized anchor and deltas as `phone_scroll`.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "dx": {
      +        "type": "number"
      +      },
      +      "dy": {
      +        "type": "number"
      +      },
      +      "kind": {
      +        "const": "scroll",
      +        "type": "string"
      +      },
      +      "x": {
      +        "type": "number"
      +      },
      +      "y": {
      +        "type": "number"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "x",
      +      "y",
      +      "dx",
      +      "dy"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Launch or foreground an installed app: give exactly one of `bundle`\n(its exact bundle identifier) or `app` (its name — 微信, WeChat,\n招商银行, Settings — which the daemon resolves through phone_apps; a\nloose name that starts a word of exactly one installed app's name also\nlaunches it, and the step's `resolved` says which; an ambiguous or\nunknown name is refused with candidates, nothing sent).",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "app": {
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "bundle": {
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "fresh": {
      +        "description": "Quit the app first so it opens on its first screen, not the page\nit was left on (flows recorded on the phone start there).",
      +        "type": "boolean"
      +      },
      +      "kind": {
      +        "const": "launch_app",
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Open a link (`https://…`, or a deep link such as `weixin://`) with\nthe app iOS assigns to it. http/https always work; other schemes\n(tel, sms, mailto, app deep links) are refused unless the operator\nallowed them in IPHONE_USE_ALLOW_URL_SCHEMES. It leaves the current\napp, so guard the next screen with `wait_for`.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "open_url",
      +        "type": "string"
      +      },
      +      "url": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "url"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Navigate back inside the current application.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "back",
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Press a button on a system alert (UIAlertController) through the device runner's\nnative alert route — the only path that actually acts on one; taps on\nalert buttons are acknowledged without effect. Give the exact button\ntext, or `action: accept|dismiss` for the default/cancel button. Fails\nclosed when no alert is showing.",
      +    "properties": {
      +      "action": {
      +        "default": null,
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "button": {
      +        "default": null,
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "if_present": {
      +        "description": "Pass instead of failing when no alert is up — for prompts that\nappear only sometimes (\"Allow Paste\"). Flows using it need a\nclient that knows it.",
      +        "type": "boolean"
      +      },
      +      "kind": {
      +        "const": "alert",
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Select a value in a native picker wheel.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "column": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "picker",
      +        "type": "string"
      +      },
      +      "value": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "value"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Invoke a named affordance on a snapshot-bound element: toggle a\nswitch, increment/decrement or adjust a slider or stepper, and the\nother names in phone_capabilities (`perform_actions`). `element` and\n`snapshot` come from phone_elements. Snapshot-bound, so not for saved\nflows. `value` only with `adjust`; `duration_ms` only with\n`menu`/`force_press`. Verify the new `value` with wait_for.",
      +    "properties": {
      +      "action": {
      +        "type": "string"
      +      },
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "duration_ms": {
      +        "default": null,
      +        "type": [
      +          "integer",
      +          "null"
      +        ]
      +      },
      +      "element": {
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "perform",
      +        "type": "string"
      +      },
      +      "snapshot": {
      +        "type": "string"
      +      },
      +      "value": {
      +        "default": null,
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "element",
      +      "snapshot",
      +      "action"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Poll the current element tree until the semantic expectation holds.",
      +    "properties": {
      +      "expect": {
      +        "$ref": "#/$defs/PhoneUiExpectation"
      +      },
      +      "kind": {
      +        "const": "wait_for",
      +        "type": "string"
      +      },
      +      "poll_ms": {
      +        "default": 250,
      +        "type": "integer"
      +      },
      +      "timeout_ms": {
      +        "default": 15000,
      +        "type": "integer"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "expect"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "A short animation pause. Prefer `wait_for` for correctness.",
      +    "properties": {
      +      "kind": {
      +        "const": "pause",
      +        "type": "string"
      +      },
      +      "ms": {
      +        "type": "integer"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "ms"
      +    ],
      +    "type": "object"
      +  }
      +]
    • addedInput schema / $defs / PhoneUiExpectation / properties / ocr_absent
      Added value: +{
      +  "description": "Text that must not be visible (a loading caption), read by OCR.",
      +  "items": {
      +    "type": "string"
      +  },
      +  "type": "array"
      +}
    • addedInput schema / $defs / PhoneUiExpectation / properties / ocr_present
      Added value: +{
      +  "description": "Text that must be visible, read by OCR from screenshots — for canvas\nscreens (games, Flutter/Unity) whose element tree has no labels, e.g.\nwaiting out a game's loading screen. Case and spaces are ignored.\nCannot be combined with application/present/absent in one wait_for;\nsuch a wait may take timeout_ms up to 60000.",
      +  "items": {
      +    "type": "string"
      +  },
      +  "type": "array"
      +}
    • addedInput schema / properties / instance
      Added value: +{
      +  "description": "Installed local instance name, such as i13 or default. Omit to use the configured daemon. No fallback on failure.",
      +  "type": "string"
      +}
  3. Changed2 schema fields changedv0.23.0
    • addedInput schema / $defs / PhoneHoldPoint
      Added value: +{
      +  "additionalProperties": false,
      +  "description": "One independently placed finger in a simultaneous hold.",
      +  "properties": {
      +    "x": {
      +      "type": "number"
      +    },
      +    "y": {
      +      "type": "number"
      +    }
      +  },
      +  "required": [
      +    "x",
      +    "y"
      +  ],
      +  "type": "object"
      +}
    • changedInput schema / $defs / PhoneStep / oneOf
      Previous value: -[
      -  {
      -    "additionalProperties": false,
      -    "description": "Tap normalized screen coordinates. Prefer `tap_locator` (durable in a\nsaved flow) whenever the target has a label or identifier.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "tap",
      -        "type": "string"
      -      },
      -      "x": {
      -        "type": "number"
      -      },
      -      "y": {
      -        "type": "number"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "x",
      -      "y"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Long-press normalized screen coordinates.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "duration_ms": {
      -        "default": 600,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "longpress",
      -        "type": "string"
      -      },
      -      "x": {
      -        "type": "number"
      -      },
      -      "y": {
      -        "type": "number"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "x",
      -      "y"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Swipe once from one normalized screen point to another.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "duration_ms": {
      -        "default": 300,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "swipe",
      -        "type": "string"
      -      },
      -      "x1": {
      -        "type": "number"
      -      },
      -      "x2": {
      -        "type": "number"
      -      },
      -      "y1": {
      -        "type": "number"
      -      },
      -      "y2": {
      -        "type": "number"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "x1",
      -      "y1",
      -      "x2",
      -      "y2"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Hold, then drag continuously. Use either x1/y1/x2/y2 or 2–16\nnormalized points with optional dwell_ms at each stop. For points,\nduration_ms is travel time per segment; total hold/travel/dwell <= 10s.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "duration_ms": {
      -        "default": 300,
      -        "type": "integer"
      -      },
      -      "hold_ms": {
      -        "default": 500,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "drag",
      -        "type": "string"
      -      },
      -      "points": {
      -        "default": null,
      -        "items": {
      -          "$ref": "#/$defs/PhoneDragPoint"
      -        },
      -        "type": [
      -          "array",
      -          "null"
      -        ]
      -      },
      -      "x1": {
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      },
      -      "x2": {
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      },
      -      "y1": {
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      },
      -      "y2": {
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      }
      -    },
      -    "required": [
      -      "kind"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Tap one exact, unique accessibility label.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "tap_label",
      -        "type": "string"
      -      },
      -      "label": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "label"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Tap the one current element matching every supplied semantic locator\nfield. Zero or multiple matches send no tap.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "if_present": {
      -        "description": "Pass instead of failing when no element matches — for screens that\nappear only sometimes (\"Not now\", \"Skip\"). Nothing is sent then.",
      -        "type": "boolean"
      -      },
      -      "kind": {
      -        "const": "tap_locator",
      -        "type": "string"
      -      },
      -      "locator": {
      -        "$ref": "#/$defs/PhoneElementLocator"
      -      },
      -      "via": {
      -        "description": "`point`: tap the centre of the element's live frame instead of\nXCUIElement's click — for custom controls that ACK a click and do\nnothing. Default `element`.",
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "locator"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Type Unicode text into the focused field. `clear=true` clears that field\nimmediately before inserting the text as one compound action.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "clear": {
      -        "default": false,
      -        "type": "boolean"
      -      },
      -      "kind": {
      -        "const": "type",
      -        "type": "string"
      -      },
      -      "text": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "text"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Send one supported device-native key.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "key",
      -        "type": "string"
      -      },
      -      "name": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "name"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Trigger Home or Spotlight.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "shortcut",
      -        "type": "string"
      -      },
      -      "name": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "name"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Scroll with the same normalized anchor and deltas as `phone_scroll`.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "dx": {
      -        "type": "number"
      -      },
      -      "dy": {
      -        "type": "number"
      -      },
      -      "kind": {
      -        "const": "scroll",
      -        "type": "string"
      -      },
      -      "x": {
      -        "type": "number"
      -      },
      -      "y": {
      -        "type": "number"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "x",
      -      "y",
      -      "dx",
      -      "dy"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Launch or foreground an installed app: give exactly one of `bundle`\n(its exact bundle identifier) or `app` (its exact name — 微信, WeChat,\n招商银行, Settings — which the daemon resolves through phone_apps; an\nambiguous or unknown name is refused with candidates, nothing sent).",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "app": {
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      },
      -      "bundle": {
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      },
      -      "fresh": {
      -        "description": "Quit the app first so it opens on its first screen, not the page\nit was left on (flows recorded on the phone start there).",
      -        "type": "boolean"
      -      },
      -      "kind": {
      -        "const": "launch_app",
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Open a link (`https://…`, or a deep link such as `weixin://`) with\nthe app iOS assigns to it. http/https always work; other schemes\n(tel, sms, mailto, app deep links) are refused unless the operator\nallowed them in IPHONE_USE_ALLOW_URL_SCHEMES. It leaves the current\napp, so guard the next screen with `wait_for`.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "open_url",
      -        "type": "string"
      -      },
      -      "url": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "url"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Navigate back inside the current application.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "back",
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Press a button on a system alert (UIAlertController) through the device runner's\nnative alert route — the only path that actually acts on one; taps on\nalert buttons are acknowledged without effect. Give the exact button\ntext, or `action: accept|dismiss` for the default/cancel button. Fails\nclosed when no alert is showing.",
      -    "properties": {
      -      "action": {
      -        "default": null,
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      },
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "button": {
      -        "default": null,
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      },
      -      "if_present": {
      -        "description": "Pass instead of failing when no alert is up — for prompts that\nappear only sometimes (\"Allow Paste\"). Flows using it need a\nclient that knows it.",
      -        "type": "boolean"
      -      },
      -      "kind": {
      -        "const": "alert",
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Select a value in a native picker wheel.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "column": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "picker",
      -        "type": "string"
      -      },
      -      "value": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "value"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Invoke a named affordance on a snapshot-bound element: toggle a\nswitch, increment/decrement or adjust a slider or stepper, and the\nother names in phone_capabilities (`perform_actions`). `element` and\n`snapshot` come from phone_elements. Snapshot-bound, so not for saved\nflows. `value` only with `adjust`; `duration_ms` only with\n`menu`/`force_press`. Verify the new `value` with wait_for.",
      -    "properties": {
      -      "action": {
      -        "type": "string"
      -      },
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "duration_ms": {
      -        "default": null,
      -        "type": [
      -          "integer",
      -          "null"
      -        ]
      -      },
      -      "element": {
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "perform",
      -        "type": "string"
      -      },
      -      "snapshot": {
      -        "type": "string"
      -      },
      -      "value": {
      -        "default": null,
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "element",
      -      "snapshot",
      -      "action"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Poll the current element tree until the semantic expectation holds.",
      -    "properties": {
      -      "expect": {
      -        "$ref": "#/$defs/PhoneUiExpectation"
      -      },
      -      "kind": {
      -        "const": "wait_for",
      -        "type": "string"
      -      },
      -      "poll_ms": {
      -        "default": 250,
      -        "type": "integer"
      -      },
      -      "timeout_ms": {
      -        "default": 15000,
      -        "type": "integer"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "expect"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "A short animation pause. Prefer `wait_for` for correctness.",
      -    "properties": {
      -      "kind": {
      -        "const": "pause",
      -        "type": "string"
      -      },
      -      "ms": {
      -        "type": "integer"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "ms"
      -    ],
      -    "type": "object"
      -  }
      -]New value: +[
      +  {
      +    "additionalProperties": false,
      +    "description": "Tap normalized screen coordinates. Prefer `tap_locator` (durable in a\nsaved flow) whenever the target has a label or identifier.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "tap",
      +        "type": "string"
      +      },
      +      "x": {
      +        "type": "number"
      +      },
      +      "y": {
      +        "type": "number"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "x",
      +      "y"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Long-press normalized screen coordinates.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "duration_ms": {
      +        "default": 600,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "longpress",
      +        "type": "string"
      +      },
      +      "x": {
      +        "type": "number"
      +      },
      +      "y": {
      +        "type": "number"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "x",
      +      "y"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Hold 2–5 distinct normalized points simultaneously; array length is finger count.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "duration_ms": {
      +        "default": 1000,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "multi_longpress",
      +        "type": "string"
      +      },
      +      "points": {
      +        "items": {
      +          "$ref": "#/$defs/PhoneHoldPoint"
      +        },
      +        "type": "array"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "points"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Swipe once from one normalized screen point to another.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "duration_ms": {
      +        "default": 300,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "swipe",
      +        "type": "string"
      +      },
      +      "x1": {
      +        "type": "number"
      +      },
      +      "x2": {
      +        "type": "number"
      +      },
      +      "y1": {
      +        "type": "number"
      +      },
      +      "y2": {
      +        "type": "number"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "x1",
      +      "y1",
      +      "x2",
      +      "y2"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Hold, then drag continuously. Use either x1/y1/x2/y2 or 2–16\nnormalized points with optional dwell_ms at each stop. For points,\nduration_ms is travel time per segment; total hold/travel/dwell <= 10s.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "duration_ms": {
      +        "default": 300,
      +        "type": "integer"
      +      },
      +      "hold_ms": {
      +        "default": 500,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "drag",
      +        "type": "string"
      +      },
      +      "points": {
      +        "default": null,
      +        "items": {
      +          "$ref": "#/$defs/PhoneDragPoint"
      +        },
      +        "type": [
      +          "array",
      +          "null"
      +        ]
      +      },
      +      "x1": {
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "x2": {
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "y1": {
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "y2": {
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      }
      +    },
      +    "required": [
      +      "kind"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Tap one exact, unique accessibility label.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "tap_label",
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "label"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Tap the one current element matching every supplied semantic locator\nfield. Zero or multiple matches send no tap.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "if_present": {
      +        "description": "Pass instead of failing when no element matches — for screens that\nappear only sometimes (\"Not now\", \"Skip\"). Nothing is sent then.",
      +        "type": "boolean"
      +      },
      +      "kind": {
      +        "const": "tap_locator",
      +        "type": "string"
      +      },
      +      "locator": {
      +        "$ref": "#/$defs/PhoneElementLocator"
      +      },
      +      "via": {
      +        "description": "`point`: tap the centre of the element's live frame instead of\nXCUIElement's click — for custom controls that ACK a click and do\nnothing. Default `element`.",
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "locator"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Type Unicode text into the focused field. `clear=true` clears that field\nimmediately before inserting the text as one compound action.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "clear": {
      +        "default": false,
      +        "type": "boolean"
      +      },
      +      "kind": {
      +        "const": "type",
      +        "type": "string"
      +      },
      +      "text": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "text"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Send one supported device-native key.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "key",
      +        "type": "string"
      +      },
      +      "name": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "name"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Trigger Home or Spotlight.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "shortcut",
      +        "type": "string"
      +      },
      +      "name": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "name"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Scroll with the same normalized anchor and deltas as `phone_scroll`.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "dx": {
      +        "type": "number"
      +      },
      +      "dy": {
      +        "type": "number"
      +      },
      +      "kind": {
      +        "const": "scroll",
      +        "type": "string"
      +      },
      +      "x": {
      +        "type": "number"
      +      },
      +      "y": {
      +        "type": "number"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "x",
      +      "y",
      +      "dx",
      +      "dy"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Launch or foreground an installed app: give exactly one of `bundle`\n(its exact bundle identifier) or `app` (its exact name — 微信, WeChat,\n招商银行, Settings — which the daemon resolves through phone_apps; an\nambiguous or unknown name is refused with candidates, nothing sent).",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "app": {
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "bundle": {
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "fresh": {
      +        "description": "Quit the app first so it opens on its first screen, not the page\nit was left on (flows recorded on the phone start there).",
      +        "type": "boolean"
      +      },
      +      "kind": {
      +        "const": "launch_app",
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Open a link (`https://…`, or a deep link such as `weixin://`) with\nthe app iOS assigns to it. http/https always work; other schemes\n(tel, sms, mailto, app deep links) are refused unless the operator\nallowed them in IPHONE_USE_ALLOW_URL_SCHEMES. It leaves the current\napp, so guard the next screen with `wait_for`.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "open_url",
      +        "type": "string"
      +      },
      +      "url": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "url"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Navigate back inside the current application.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "back",
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Press a button on a system alert (UIAlertController) through the device runner's\nnative alert route — the only path that actually acts on one; taps on\nalert buttons are acknowledged without effect. Give the exact button\ntext, or `action: accept|dismiss` for the default/cancel button. Fails\nclosed when no alert is showing.",
      +    "properties": {
      +      "action": {
      +        "default": null,
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "button": {
      +        "default": null,
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "if_present": {
      +        "description": "Pass instead of failing when no alert is up — for prompts that\nappear only sometimes (\"Allow Paste\"). Flows using it need a\nclient that knows it.",
      +        "type": "boolean"
      +      },
      +      "kind": {
      +        "const": "alert",
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Select a value in a native picker wheel.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "column": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "picker",
      +        "type": "string"
      +      },
      +      "value": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "value"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Invoke a named affordance on a snapshot-bound element: toggle a\nswitch, increment/decrement or adjust a slider or stepper, and the\nother names in phone_capabilities (`perform_actions`). `element` and\n`snapshot` come from phone_elements. Snapshot-bound, so not for saved\nflows. `value` only with `adjust`; `duration_ms` only with\n`menu`/`force_press`. Verify the new `value` with wait_for.",
      +    "properties": {
      +      "action": {
      +        "type": "string"
      +      },
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "duration_ms": {
      +        "default": null,
      +        "type": [
      +          "integer",
      +          "null"
      +        ]
      +      },
      +      "element": {
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "perform",
      +        "type": "string"
      +      },
      +      "snapshot": {
      +        "type": "string"
      +      },
      +      "value": {
      +        "default": null,
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "element",
      +      "snapshot",
      +      "action"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Poll the current element tree until the semantic expectation holds.",
      +    "properties": {
      +      "expect": {
      +        "$ref": "#/$defs/PhoneUiExpectation"
      +      },
      +      "kind": {
      +        "const": "wait_for",
      +        "type": "string"
      +      },
      +      "poll_ms": {
      +        "default": 250,
      +        "type": "integer"
      +      },
      +      "timeout_ms": {
      +        "default": 15000,
      +        "type": "integer"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "expect"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "A short animation pause. Prefer `wait_for` for correctness.",
      +    "properties": {
      +      "kind": {
      +        "const": "pause",
      +        "type": "string"
      +      },
      +      "ms": {
      +        "type": "integer"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "ms"
      +    ],
      +    "type": "object"
      +  }
      +]
  4. Changed2 schema fields changedv0.22.3
    • addedInput schema / $defs / PhoneDragPoint
      Added value: +{
      +  "additionalProperties": false,
      +  "description": "One stop in a continuous drag. Dwell keeps the same finger down.",
      +  "properties": {
      +    "dwell_ms": {
      +      "default": 0,
      +      "type": "integer"
      +    },
      +    "x": {
      +      "type": "number"
      +    },
      +    "y": {
      +      "type": "number"
      +    }
      +  },
      +  "required": [
      +    "x",
      +    "y"
      +  ],
      +  "type": "object"
      +}
    • changedInput schema / $defs / PhoneStep / oneOf
      Previous value: -[
      -  {
      -    "additionalProperties": false,
      -    "description": "Tap normalized screen coordinates. Prefer `tap_locator` (durable in a\nsaved flow) whenever the target has a label or identifier.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "tap",
      -        "type": "string"
      -      },
      -      "x": {
      -        "type": "number"
      -      },
      -      "y": {
      -        "type": "number"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "x",
      -      "y"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Long-press normalized screen coordinates.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "duration_ms": {
      -        "default": 600,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "longpress",
      -        "type": "string"
      -      },
      -      "x": {
      -        "type": "number"
      -      },
      -      "y": {
      -        "type": "number"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "x",
      -      "y"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Swipe once from one normalized screen point to another.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "duration_ms": {
      -        "default": 300,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "swipe",
      -        "type": "string"
      -      },
      -      "x1": {
      -        "type": "number"
      -      },
      -      "x2": {
      -        "type": "number"
      -      },
      -      "y1": {
      -        "type": "number"
      -      },
      -      "y2": {
      -        "type": "number"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "x1",
      -      "y1",
      -      "x2",
      -      "y2"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Hold, then drag once between two normalized screen points.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "duration_ms": {
      -        "default": 300,
      -        "type": "integer"
      -      },
      -      "hold_ms": {
      -        "default": 500,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "drag",
      -        "type": "string"
      -      },
      -      "x1": {
      -        "type": "number"
      -      },
      -      "x2": {
      -        "type": "number"
      -      },
      -      "y1": {
      -        "type": "number"
      -      },
      -      "y2": {
      -        "type": "number"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "x1",
      -      "y1",
      -      "x2",
      -      "y2"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Tap one exact, unique accessibility label.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "tap_label",
      -        "type": "string"
      -      },
      -      "label": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "label"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Tap the one current element matching every supplied semantic locator\nfield. Zero or multiple matches send no tap.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "if_present": {
      -        "description": "Pass instead of failing when no element matches — for screens that\nappear only sometimes (\"Not now\", \"Skip\"). Nothing is sent then.",
      -        "type": "boolean"
      -      },
      -      "kind": {
      -        "const": "tap_locator",
      -        "type": "string"
      -      },
      -      "locator": {
      -        "$ref": "#/$defs/PhoneElementLocator"
      -      },
      -      "via": {
      -        "description": "`point`: tap the centre of the element's live frame instead of\nXCUIElement's click — for custom controls that ACK a click and do\nnothing. Default `element`.",
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "locator"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Type Unicode text into the focused field. `clear=true` clears that field\nimmediately before inserting the text as one compound action.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "clear": {
      -        "default": false,
      -        "type": "boolean"
      -      },
      -      "kind": {
      -        "const": "type",
      -        "type": "string"
      -      },
      -      "text": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "text"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Send one supported device-native key.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "key",
      -        "type": "string"
      -      },
      -      "name": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "name"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Trigger Home or Spotlight.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "shortcut",
      -        "type": "string"
      -      },
      -      "name": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "name"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Scroll with the same normalized anchor and deltas as `phone_scroll`.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "dx": {
      -        "type": "number"
      -      },
      -      "dy": {
      -        "type": "number"
      -      },
      -      "kind": {
      -        "const": "scroll",
      -        "type": "string"
      -      },
      -      "x": {
      -        "type": "number"
      -      },
      -      "y": {
      -        "type": "number"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "x",
      -      "y",
      -      "dx",
      -      "dy"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Launch or foreground an installed app: give exactly one of `bundle`\n(its exact bundle identifier) or `app` (its exact name — 微信, WeChat,\n招商银行, Settings — which the daemon resolves through phone_apps; an\nambiguous or unknown name is refused with candidates, nothing sent).",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "app": {
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      },
      -      "bundle": {
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      },
      -      "fresh": {
      -        "description": "Quit the app first so it opens on its first screen, not the page\nit was left on (flows recorded on the phone start there).",
      -        "type": "boolean"
      -      },
      -      "kind": {
      -        "const": "launch_app",
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Open a link (`https://…`, or a deep link such as `weixin://`) with\nthe app iOS assigns to it. http/https always work; other schemes\n(tel, sms, mailto, app deep links) are refused unless the operator\nallowed them in IPHONE_USE_ALLOW_URL_SCHEMES. It leaves the current\napp, so guard the next screen with `wait_for`.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "open_url",
      -        "type": "string"
      -      },
      -      "url": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "url"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Navigate back inside the current application.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "back",
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Press a button on a system alert (UIAlertController) through the device runner's\nnative alert route — the only path that actually acts on one; taps on\nalert buttons are acknowledged without effect. Give the exact button\ntext, or `action: accept|dismiss` for the default/cancel button. Fails\nclosed when no alert is showing.",
      -    "properties": {
      -      "action": {
      -        "default": null,
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      },
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "button": {
      -        "default": null,
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      },
      -      "if_present": {
      -        "description": "Pass instead of failing when no alert is up — for prompts that\nappear only sometimes (\"Allow Paste\"). Flows using it need a\nclient that knows it.",
      -        "type": "boolean"
      -      },
      -      "kind": {
      -        "const": "alert",
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Select a value in a native picker wheel.",
      -    "properties": {
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "column": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "picker",
      -        "type": "string"
      -      },
      -      "value": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "value"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Invoke a named affordance on a snapshot-bound element: toggle a\nswitch, increment/decrement or adjust a slider or stepper, and the\nother names in phone_capabilities (`perform_actions`). `element` and\n`snapshot` come from phone_elements. Snapshot-bound, so not for saved\nflows. `value` only with `adjust`; `duration_ms` only with\n`menu`/`force_press`. Verify the new `value` with wait_for.",
      -    "properties": {
      -      "action": {
      -        "type": "string"
      -      },
      -      "after_ms": {
      -        "default": 0,
      -        "type": "integer"
      -      },
      -      "duration_ms": {
      -        "default": null,
      -        "type": [
      -          "integer",
      -          "null"
      -        ]
      -      },
      -      "element": {
      -        "type": "integer"
      -      },
      -      "kind": {
      -        "const": "perform",
      -        "type": "string"
      -      },
      -      "snapshot": {
      -        "type": "string"
      -      },
      -      "value": {
      -        "default": null,
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "element",
      -      "snapshot",
      -      "action"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "Poll the current element tree until the semantic expectation holds.",
      -    "properties": {
      -      "expect": {
      -        "$ref": "#/$defs/PhoneUiExpectation"
      -      },
      -      "kind": {
      -        "const": "wait_for",
      -        "type": "string"
      -      },
      -      "poll_ms": {
      -        "default": 250,
      -        "type": "integer"
      -      },
      -      "timeout_ms": {
      -        "default": 15000,
      -        "type": "integer"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "expect"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "A short animation pause. Prefer `wait_for` for correctness.",
      -    "properties": {
      -      "kind": {
      -        "const": "pause",
      -        "type": "string"
      -      },
      -      "ms": {
      -        "type": "integer"
      -      }
      -    },
      -    "required": [
      -      "kind",
      -      "ms"
      -    ],
      -    "type": "object"
      -  }
      -]New value: +[
      +  {
      +    "additionalProperties": false,
      +    "description": "Tap normalized screen coordinates. Prefer `tap_locator` (durable in a\nsaved flow) whenever the target has a label or identifier.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "tap",
      +        "type": "string"
      +      },
      +      "x": {
      +        "type": "number"
      +      },
      +      "y": {
      +        "type": "number"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "x",
      +      "y"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Long-press normalized screen coordinates.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "duration_ms": {
      +        "default": 600,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "longpress",
      +        "type": "string"
      +      },
      +      "x": {
      +        "type": "number"
      +      },
      +      "y": {
      +        "type": "number"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "x",
      +      "y"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Swipe once from one normalized screen point to another.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "duration_ms": {
      +        "default": 300,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "swipe",
      +        "type": "string"
      +      },
      +      "x1": {
      +        "type": "number"
      +      },
      +      "x2": {
      +        "type": "number"
      +      },
      +      "y1": {
      +        "type": "number"
      +      },
      +      "y2": {
      +        "type": "number"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "x1",
      +      "y1",
      +      "x2",
      +      "y2"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Hold, then drag continuously. Use either x1/y1/x2/y2 or 2–16\nnormalized points with optional dwell_ms at each stop. For points,\nduration_ms is travel time per segment; total hold/travel/dwell <= 10s.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "duration_ms": {
      +        "default": 300,
      +        "type": "integer"
      +      },
      +      "hold_ms": {
      +        "default": 500,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "drag",
      +        "type": "string"
      +      },
      +      "points": {
      +        "default": null,
      +        "items": {
      +          "$ref": "#/$defs/PhoneDragPoint"
      +        },
      +        "type": [
      +          "array",
      +          "null"
      +        ]
      +      },
      +      "x1": {
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "x2": {
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "y1": {
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "y2": {
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      }
      +    },
      +    "required": [
      +      "kind"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Tap one exact, unique accessibility label.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "tap_label",
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "label"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Tap the one current element matching every supplied semantic locator\nfield. Zero or multiple matches send no tap.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "if_present": {
      +        "description": "Pass instead of failing when no element matches — for screens that\nappear only sometimes (\"Not now\", \"Skip\"). Nothing is sent then.",
      +        "type": "boolean"
      +      },
      +      "kind": {
      +        "const": "tap_locator",
      +        "type": "string"
      +      },
      +      "locator": {
      +        "$ref": "#/$defs/PhoneElementLocator"
      +      },
      +      "via": {
      +        "description": "`point`: tap the centre of the element's live frame instead of\nXCUIElement's click — for custom controls that ACK a click and do\nnothing. Default `element`.",
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "locator"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Type Unicode text into the focused field. `clear=true` clears that field\nimmediately before inserting the text as one compound action.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "clear": {
      +        "default": false,
      +        "type": "boolean"
      +      },
      +      "kind": {
      +        "const": "type",
      +        "type": "string"
      +      },
      +      "text": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "text"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Send one supported device-native key.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "key",
      +        "type": "string"
      +      },
      +      "name": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "name"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Trigger Home or Spotlight.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "shortcut",
      +        "type": "string"
      +      },
      +      "name": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "name"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Scroll with the same normalized anchor and deltas as `phone_scroll`.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "dx": {
      +        "type": "number"
      +      },
      +      "dy": {
      +        "type": "number"
      +      },
      +      "kind": {
      +        "const": "scroll",
      +        "type": "string"
      +      },
      +      "x": {
      +        "type": "number"
      +      },
      +      "y": {
      +        "type": "number"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "x",
      +      "y",
      +      "dx",
      +      "dy"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Launch or foreground an installed app: give exactly one of `bundle`\n(its exact bundle identifier) or `app` (its exact name — 微信, WeChat,\n招商银行, Settings — which the daemon resolves through phone_apps; an\nambiguous or unknown name is refused with candidates, nothing sent).",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "app": {
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "bundle": {
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "fresh": {
      +        "description": "Quit the app first so it opens on its first screen, not the page\nit was left on (flows recorded on the phone start there).",
      +        "type": "boolean"
      +      },
      +      "kind": {
      +        "const": "launch_app",
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Open a link (`https://…`, or a deep link such as `weixin://`) with\nthe app iOS assigns to it. http/https always work; other schemes\n(tel, sms, mailto, app deep links) are refused unless the operator\nallowed them in IPHONE_USE_ALLOW_URL_SCHEMES. It leaves the current\napp, so guard the next screen with `wait_for`.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "open_url",
      +        "type": "string"
      +      },
      +      "url": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "url"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Navigate back inside the current application.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "back",
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Press a button on a system alert (UIAlertController) through the device runner's\nnative alert route — the only path that actually acts on one; taps on\nalert buttons are acknowledged without effect. Give the exact button\ntext, or `action: accept|dismiss` for the default/cancel button. Fails\nclosed when no alert is showing.",
      +    "properties": {
      +      "action": {
      +        "default": null,
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "button": {
      +        "default": null,
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "if_present": {
      +        "description": "Pass instead of failing when no alert is up — for prompts that\nappear only sometimes (\"Allow Paste\"). Flows using it need a\nclient that knows it.",
      +        "type": "boolean"
      +      },
      +      "kind": {
      +        "const": "alert",
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Select a value in a native picker wheel.",
      +    "properties": {
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "column": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "picker",
      +        "type": "string"
      +      },
      +      "value": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "value"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Invoke a named affordance on a snapshot-bound element: toggle a\nswitch, increment/decrement or adjust a slider or stepper, and the\nother names in phone_capabilities (`perform_actions`). `element` and\n`snapshot` come from phone_elements. Snapshot-bound, so not for saved\nflows. `value` only with `adjust`; `duration_ms` only with\n`menu`/`force_press`. Verify the new `value` with wait_for.",
      +    "properties": {
      +      "action": {
      +        "type": "string"
      +      },
      +      "after_ms": {
      +        "default": 0,
      +        "type": "integer"
      +      },
      +      "duration_ms": {
      +        "default": null,
      +        "type": [
      +          "integer",
      +          "null"
      +        ]
      +      },
      +      "element": {
      +        "type": "integer"
      +      },
      +      "kind": {
      +        "const": "perform",
      +        "type": "string"
      +      },
      +      "snapshot": {
      +        "type": "string"
      +      },
      +      "value": {
      +        "default": null,
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "element",
      +      "snapshot",
      +      "action"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "Poll the current element tree until the semantic expectation holds.",
      +    "properties": {
      +      "expect": {
      +        "$ref": "#/$defs/PhoneUiExpectation"
      +      },
      +      "kind": {
      +        "const": "wait_for",
      +        "type": "string"
      +      },
      +      "poll_ms": {
      +        "default": 250,
      +        "type": "integer"
      +      },
      +      "timeout_ms": {
      +        "default": 15000,
      +        "type": "integer"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "expect"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "A short animation pause. Prefer `wait_for` for correctness.",
      +    "properties": {
      +      "kind": {
      +        "const": "pause",
      +        "type": "string"
      +      },
      +      "ms": {
      +        "type": "integer"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "ms"
      +    ],
      +    "type": "object"
      +  }
      +]
  5. First observedv0.21.0

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The annotations only declare destructive/openWorld/non-idempotent; the description goes well beyond them by describing the execution model — whole-sequence pre-validation, a single control lock, stop-at-first-failure — and the risk semantics of retry_safe and unverified irreversible steps. It also discloses that alert-button taps are acknowledged without effect, which is a non-obvious trap an agent could not infer from the schema. Nothing here contradicts the destructiveHint=true annotation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The opening sentence is front-loaded with the payoff (one call, screen returned, one turn) and the constraints follow in descending priority. It is a single dense block, though; the long comma-separated enumeration of step kinds would scan faster as a list, and a few clauses (e.g. the multi_longpress finger-count restatement) duplicate schema text.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool whose schema is a 19-variant oneOf, the description supplies exactly what the schema cannot: batching strategy, failure semantics, the safe-retry rule, and what the result reports (completed/applied counts and the failed step). With no output schema present, describing the result shape and the flow_suggestion follow-up closes the remaining gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is already 100%, so the baseline is 3, but the description adds real meaning: it enumerates the step kinds so an agent can plan a batch from the description alone, clarifies multi_longpress as 2–5 simultaneous fingers, explains drag edge-dwell, and states that these step objects are byte-identical to a saved flow's `steps`. It also names the one non-obvious workflow dependency, that `perform` needs element+snapshot from phone_elements.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource — run a sequence of actions in one call — and immediately names the differentiator from siblings: returns the ending screen in the same reply, one turn instead of one per tap. The lead 'THE DEFAULT WAY TO ACT' plus the contrast with single-action tools lets an agent place phone_run_steps relative to phone_tap, phone_type, and phone_elements without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit when-to-use ('as soon as a segment is understood') and when-not ('keep single-action tools for exploring an unknown screen'), plus hard operational rules: guard every screen change with wait_for, never batch an unverified irreversible step, and treat retry_safe=false as DO NOT replay. It also handles the follow-up branch by telling the agent to ask the user about a flow when flow_suggestion appears.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.