attachany
Live begin only: nav attach directive, e.g. {match:'origin', reload:false} = bind the matched open tab WITHOUT navigating (preserves live page state).
digestany
Live: survey the bound page in ONE call — every visible interactive element as {i, role, name, selector, inViewport, x/y/w/h, suggested_op (i = document scan order; viewport-first, capped 120). suggested_op is a READY-MADE op payload you can call directly (swap value only, or pass unchanged for click-like ops). Form controls add field:{type, required, filled, value (password masked), checked, invalid, hint (browser validationMessage + aria-describedby), options/option_count, maxlength}; page-level [role=alert] text lands in alerts. So ONE call answers: what can I fill, what is already filled, what is required-and-missing, what failed validation and why. Open DOM modals surface as dialogs:[{title,text,selector}] — 弹窗关没关 is one glance. Items are ready-made input targets: {role, name} works directly as target. START HERE instead of eval-probing. Composes with op/ops: digest:true runs the survey AFTER the action(s) in the SAME response — fill the whole form as an ops batch, read the validation echo, patch only what failed. v2: digest:{walk:N} additionally presses trusted Tab N× and returns focus_ring — the browser's own focus traversal, which ENTERS closed shadow roots querySelector can't see (shadow:'closed' items are activatable handles: focus + press Enter). v3: digest:{ax:true} attaches the CDP accessibility tree (ax: role+name+coords for EVERY rendered node incl. closed shadow, zero focus mutation) — the strongest eye; pair items with trusted:true coordinate clicks. op:ax is also plan-callable directly.
draftboolean
Live: preview the flow freeze WOULD produce from this session's successful ops — returns {draft, freeze_call:{site,name}} with NOTHING persisted and the session still live. Inspect, then persist via freeze (rename site/name freely). When the session contains mutating ops, draft is null and reason names the missing key. Works on ANY unfrozen session id — including past sessions surfaced as session_hits at begin/capture (already-paid work; draft instead of re-deriving).
freezeobject
Live: materialize the session's successful ops into a saved Flow {site, name, key?, description?, ops?, return?, must?, accept_candidates?, lessons?}. A post_act must is required when the selection mutates — either author it, or set accept_candidates: true to adopt the engine-derived φ shown in freeze_nudge.freeze_call.must_candidate (it lifts the session's read-only tail into a confirm phase and asserts a field that was observably true at capture, or falls back to the recorded write oracle; candidate_provenance names which). An authored must always wins. lessons: string[] — durable per-host authoring notes (what this session taught you), merged into your harness hints and surfaced at every later begin/capture on this host. Archives the session. If the response carries a capture-tab-completeness warning (the page had tab groups your session never visited — listed in groups), unvisited tabs often hold required configuration (e.g. a key was set but a permission toggle lives on another tab): revisit them and re-freeze, or ignore the warning when those tabs are genuinely irrelevant to the flow.
intentstring
Natural-language description of what the user wants extracted. Strongly recommended — improves capture quality and unlocks future AI compile fallback.
liveboolean
true + url: begin a LIVE session — bind a tab and return live_session for real-time single-op dispatch (every op recorded). Combine with attach to bind the user's open tab without reloading it.
live_sessionstring
Live-session id from the begin call. Combine with op or freeze.
namestring
Required for save (e.g. "trending")
opobject
Live: ONE Op to dispatch now on the session's tab (~1s feedback). Same lint rules as authored flows. Result returns directly; the op is appended to the session recording.
save:"x" binds the value for every LATER op in this session — reference it as {{observe.x}} (scope_bound in the response lists the exact spellings), and it resolves identically on replay. Never paste a value you just read as a literal into the next op: that freezes a capture-time constant where a reference belonged.
**Op is a DISCRIMINATED UNION — every op carries op:<string>; the 18 allowed values are: fetch nav wait input extract cookies tap if foreach parallel eval tab bookmark pdf notify ax host screenshot. **Most interactive clicks/fills/scrolls use op:input + kind:<10-subtypes— the remaining 17 ops carry direct shape. The 8 SHAPE IS IN code types.ts:OP_NAMES_V2 but the COMMON ops are listed below with example literals (copy-paste the example and swap selectors/urls). **Common ops (copy-paste ready):** - Navigate:{"op":"nav","url":"https://example.com"}`
- Click element: {"op":"input","kind":"click","target":{"selector":"button.submit"}}
- Fill input: {"op":"input","kind":"fill","target":{"selector":"#email"},"value":"me@x.com"}
- Type (sends keystrokes, triggers input events): {"op":"input","kind":"type","target":{"selector":"input"},"value":"hello"}}
- Scroll page: {"op":"input","kind":"scroll","value":"page"} ← value ∈ {"page","-page","pageup","pagedown","top","bottom","-element"} or a pixel number string.
- Wait: {"op":"wait","ms":1500} (or {"op":"wait","selector":"#ready"})
- HTTP fetch (no tab): {"op":"fetch","url":"https://api.x.com/v","format":"json","save":"resp"}
- Extract data from page: {"op":"extract","fields":{"title":{"selector":"h1","kind":"text"}}}
- Run JS on page (pure value extraction, NO side effects — evals are value-only in the engine): {"op":"eval","src":"document.title"}
- Hover: {"op":"input","kind":"hover","target":{"selector":".menu"}}
- Press key: {"op":"input","kind":"press","value":"Enter"} (Tab Escape Enter Space plus any single char or arrowKeys ArrowUp/Down/Left/Right)
**10 kind values inside op:input:** click type fill press scroll hover keytype blur upload setHtml — closed union at 10.
**Target** (target: field on element ops): either {selector:<CSS string>} OR {role:<string>,name:<string>} (accessibility role+name pair — digest returns these for every element). Use digest:{ax:true} to emit role+name for every rendered node even inside closed shadow roots.
Copy an example → swap the url/selector/value → call. NEVER invent new op names or new kind values — the engine rejects unknowns at the lint chokepoint.
opsobject[]
Live: a SHORT op sequence dispatched in one call — sequential, FAIL-FAST (stops at the first failure; tail not attempted). All ops lint up front. Each op is recorded individually. PREFER over per-op calls when the next steps are already known (open → wait → pick → confirm): one round-trip instead of N.
Values come back: the response carries results:[{seq, ok, value|error, anomalies?}] in dispatch order — the SAME per-op payload single op dispatch returns, one entry per op. So a batch may end in reads (extract/eval/fetch) and you get their data in this response; batching is NOT limited to blind actions.
Values also carry FORWARD: save:"x" on any op binds it for every later op in the session (batch or not) — reference it as {{observe.x}} (or {{act.x}} once the session has mutated; scope_bound in the response lists the exact spellings). op:extract with no from reads the previous op's value. The phase attribution is the freeze materializer's own rule, so what resolves live resolves identically on replay. Reference, never paste: writing the value you just READ as a literal into the next op freezes a capture-time constant (token, row id, date) that replays green against stale data.
Each item follows the SAME op shape described on the op field (discriminated union, 18 values, op:input with kind for interactivity).
sitestring
Required for save (e.g. "github"); also enables re-capture when url is omitted
urlstring
Source URL to probe