Skip to main content

cosh_tools/computer/
types.rs

1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3
4/// What `computer_apps` should report.
5#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, JsonSchema)]
6#[serde(rename_all = "lowercase")]
7pub enum AppsTarget {
8    /// Every running application (default).
9    #[default]
10    All,
11    /// The application that currently holds the system foreground, plus the
12    /// element inside it holding keyboard focus — the cheap "where do my
13    /// keystrokes go?" check before `computer_keyboard`.
14    Focused,
15}
16
17/// Input for `computer_apps`.
18///
19/// `target: "all"` (default) lists every running application;
20/// `target: "focused"` resolves the foreground application and its
21/// keyboard-focused element instead.
22#[derive(Debug, Clone, Default, Deserialize, JsonSchema)]
23pub struct ComputerApps {
24    /// What to report: `all` (default) = every running app with PIDs and the
25    /// foreground flag; `focused` = the foreground app plus the element
26    /// holding keyboard focus inside it.
27    pub target: Option<AppsTarget>,
28    /// How long to wait for a foreground application to exist, in
29    /// milliseconds (`target: "focused"` only). Default 3000.
30    pub timeout_ms: Option<u64>,
31}
32
33/// Output format for `computer_snapshot`.
34#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, JsonSchema)]
35#[serde(rename_all = "lowercase")]
36pub enum SnapshotFormat {
37    /// Compact indented outline (one line per element). Default.
38    #[default]
39    Tree,
40    /// Structured JSON (`role`/`name`/`value`/`children` per node).
41    Json,
42}
43
44/// Input for `computer_snapshot`.
45#[derive(Debug, Clone, Default, Deserialize, JsonSchema)]
46pub struct ComputerSnapshot {
47    /// Application name (exact match, e.g. `"Safari"`). Provide `name` or
48    /// `pid` — exactly one.
49    pub name: Option<String>,
50    /// Application process ID. Provide `name` or `pid` — exactly one.
51    pub pid: Option<u32>,
52    /// Optional CSS-like selector to snapshot only the matching subtree
53    /// instead of the whole application, e.g. `"window[name='Main'] > group"`.
54    pub selector: Option<String>,
55    /// 1-based match index when `selector` matches multiple elements
56    /// (default 1).
57    pub nth: Option<usize>,
58    /// Maximum snapshot depth. Default 12; hard cap 40.
59    pub max_depth: Option<u32>,
60    /// Output format. Default `tree`.
61    pub format: Option<SnapshotFormat>,
62    /// How long to wait for the application to surface in the accessibility
63    /// tree, in milliseconds. Default 3000.
64    pub timeout_ms: Option<u64>,
65    /// Target an OS SHELL SURFACE instead of an application (mutually
66    /// exclusive with `name`/`pid`): the menu bar, Dock/taskbar/panel, tray
67    /// status items, the desktop, or a flyout open right now. `selector`
68    /// then narrows within that surface's tree.
69    pub surface: Option<SurfaceKind>,
70}
71
72/// One running application, as reported by `computer_apps`.
73#[derive(Debug, Clone, Serialize, JsonSchema)]
74pub struct AppInfo {
75    /// Application name.
76    pub name: String,
77    /// Process ID, when the platform reports one.
78    pub pid: Option<u32>,
79    /// Whether this app currently holds the system foreground.
80    pub foreground: bool,
81}
82
83/// Output of `computer_apps`.
84#[derive(Debug, Clone, Serialize, JsonSchema)]
85pub struct AppsOutput {
86    /// Running applications (unsorted, platform enumeration order).
87    pub apps: Vec<AppInfo>,
88    /// Convenience count of [`AppsOutput::apps`].
89    pub count: usize,
90}
91
92/// The element holding keyboard focus inside the foreground application,
93/// reported by `computer_apps` with `target: "focused"`.
94#[derive(Debug, Clone, Serialize, JsonSchema)]
95pub struct FocusedElement {
96    /// Element role, snake_case (same vocabulary as selectors).
97    pub role: String,
98    /// Accessible name, when the element has one.
99    pub name: Option<String>,
100    /// Current value, when the element has one (e.g. the text in a focused
101    /// field).
102    pub value: Option<String>,
103    /// Path of roles from the application root down to the focused element
104    /// (inclusive), e.g. `["window", "group", "text_field"]` — context for
105    /// locating the field without another snapshot.
106    pub path: Vec<String>,
107}
108
109/// Output of `computer_apps` with `target: "focused"`.
110#[derive(Debug, Clone, Serialize, JsonSchema)]
111pub struct FocusedOutput {
112    /// Name of the foreground application.
113    pub app: String,
114    /// Process ID of the foreground application.
115    pub pid: Option<u32>,
116    /// The element inside the app holding keyboard focus, when one does.
117    /// `None` when the app has no focused element (e.g. focus is on the
118    /// window itself or the platform does not report element focus).
119    pub focused_element: Option<FocusedElement>,
120}
121
122/// Input for `computer_screenshot`.
123#[derive(Debug, Clone, Default, Deserialize, JsonSchema)]
124pub struct ComputerScreenshot {
125    /// Capture only this region of the display, as `[x, y, width, height]`
126    /// in display pixels (origin top-left). Mutually exclusive with element
127    /// capture (`app`/`pid` + `selector`).
128    pub region: Option<Vec<i32>>,
129    /// Application name (exact match) for element capture: shoot only the
130    /// pixels under the element matched by `selector`. Requires `selector`.
131    pub app: Option<String>,
132    /// Application process ID for element capture. Requires `selector`.
133    pub pid: Option<u32>,
134    /// Optional CSS-like selector for element capture, e.g.
135    /// `"window[name='Main'] > group"`. Requires `app` or `pid`.
136    ///
137    /// With `annotate: true` this changes meaning: it selects which elements
138    /// get annotated boxes (default `"*"` — every element of the app), and
139    /// the legend maps each tag to a round-trippable selector. Comma
140    /// alternations (`"button, link"`) are rejected in both modes.
141    pub selector: Option<String>,
142    /// 1-based match index when `selector` matches multiple elements
143    /// (default 1). Not applicable to annotated captures — the legend
144    /// covers every match; narrow `selector` instead.
145    pub nth: Option<usize>,
146    /// Draw a labeled box on every matching element and return a legend
147    /// mapping each tag (`B7`) back to a selector (`*:nth(7)`) that
148    /// `computer_act` accepts directly — visual grounding without pixel
149    /// coordinates. Requires `app` or `pid` (annotation groups must be
150    /// application-scoped) and is mutually exclusive with `region`.
151    /// Default `false`.
152    #[serde(default)]
153    pub annotate: bool,
154    /// Maximum delivered image width in pixels (default 1568). Larger
155    /// captures are downscaled preserving aspect ratio; coordinates always
156    /// refer to the delivered image.
157    pub max_width: Option<u32>,
158    /// How long to wait for the app to appear (element capture only), in
159    /// milliseconds. Default 3000.
160    pub timeout_ms: Option<u64>,
161    /// Target an OS SHELL SURFACE instead of an application (mutually
162    /// exclusive with `app`/`pid`; requires `selector`): shoot the pixels
163    /// under the matched element of the menu bar, Dock/taskbar/panel, tray
164    /// status items, the desktop, or a flyout open right now. With
165    /// `annotate: true` the legend covers the surface's elements the same
166    /// way it covers an app's.
167    pub surface: Option<SurfaceKind>,
168}
169
170/// Output of `computer_screenshot`.
171///
172/// The PNG travels inline via [`ScreenshotOutput::images`] (base64
173/// `ImageBlock`s) — no file references.
174#[derive(Debug, Clone, Serialize, JsonSchema)]
175pub struct ScreenshotOutput {
176    /// Width of the DELIVERED image, in pixels (after any downscale).
177    /// Coordinates for touch tools are expressed in this space.
178    pub width: u32,
179    /// Height of the DELIVERED image, in pixels.
180    pub height: u32,
181    /// Desktop-coordinate origin of the image: the desktop point under the
182    /// delivered image's pixel (0, 0) — `(x, y)`.
183    pub desktop_origin: (i32, i32),
184    /// Desktop pixels per delivered image pixel — `(dx, dy)`. Map an image
185    /// coordinate back to the screen as
186    /// `desktop = desktop_origin + image_coord * desktop_scale`.
187    pub desktop_scale: (f64, f64),
188    /// Size of the encoded PNG in bytes.
189    pub bytes: usize,
190    /// Legend of the annotated capture: one entry per drawn box, mapping the
191    /// tag drawn on the image (`B7`) to a selector that resolves the
192    /// element — pass it to `computer_act` as-is. Empty for plain
193    /// captures.
194    pub legend: Vec<LegendEntryOutput>,
195    /// Elements that matched the annotation selector but could not be
196    /// drawn, each with the reason — the picture and the legend never
197    /// disagree silently. Empty for plain captures.
198    pub omitted: Vec<OmissionOutput>,
199    /// How many matched elements were not described at all because the
200    /// annotation cap (100) was reached. `0` when the cap did not bite;
201    /// narrow the `selector` when it does.
202    pub truncated: usize,
203    /// Inline image payload (base64 PNG) for the multimodal channel.
204    #[serde(skip)]
205    pub images: Vec<cosh_sdk::connector::ImageBlock>,
206}
207
208/// One legend entry of an annotated capture: a box on the image plus the
209/// selector that reaches the element it labels.
210#[derive(Debug, Clone, Serialize, JsonSchema)]
211pub struct LegendEntryOutput {
212    /// What is drawn in the box — `"B7"`.
213    pub tag: String,
214    /// Selector usable as-is against the annotation scope —
215    /// `"button[name='Export']:nth(7)"`. This round-trip is the point: read
216    /// the tag off the image, act on the selector.
217    pub selector: String,
218    /// The element's role, snake_case as everywhere else.
219    pub role: String,
220    /// The element's accessible name, when it has one.
221    pub name: Option<String>,
222    /// The box colour, RGB, for correlating a box with its entry by eye.
223    pub color: [u8; 3],
224}
225
226/// An element the annotation selector matched but that could not be drawn.
227#[derive(Debug, Clone, Serialize, JsonSchema)]
228pub struct OmissionOutput {
229    /// The selector that would reach this element.
230    pub selector: String,
231    /// The element's role, snake_case.
232    pub role: String,
233    /// The element's accessible name, when it has one.
234    pub name: Option<String>,
235    /// Why it could not be drawn: `no_bounds`, `zero_area` or
236    /// `outside_capture`.
237    pub reason: String,
238}
239
240/// Output of `computer_snapshot`.
241#[derive(Debug, Clone, Serialize, JsonSchema)]
242pub struct SnapshotOutput {
243    /// Name of the application the snapshot was taken from.
244    pub app: String,
245    /// Process ID of the application, when known.
246    pub pid: Option<u32>,
247    /// Rendered snapshot: an indented outline (`tree`) or pretty-printed
248    /// JSON (`json`), one element per node with role, name, value and states.
249    pub snapshot: String,
250    /// Number of elements in the snapshot.
251    pub elements: usize,
252}
253
254/// Tri-state toggle value for a checkable control (`checked` in
255/// [`ElementStates`]).
256#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
257#[serde(rename_all = "lowercase")]
258pub enum ToggleState {
259    /// Checkable and currently off.
260    Off,
261    /// Checkable and currently on.
262    On,
263    /// Tri-state checkbox in the indeterminate state.
264    Mixed,
265}
266
267impl From<xa11y::Toggled> for ToggleState {
268    fn from(t: xa11y::Toggled) -> Self {
269        match t {
270            xa11y::Toggled::Off => Self::Off,
271            xa11y::Toggled::On => Self::On,
272            xa11y::Toggled::Mixed => Self::Mixed,
273        }
274    }
275}
276
277/// The element states a snapshot reports, normalized from the platform's
278/// [`xa11y::StateSet`].
279///
280/// The subset is chosen for action planning: these are the flags that change
281/// whether an interaction will succeed (a `disabled` button fails
282/// `computer_act`'s press; a `hidden` row needs `scroll_into_view` first).
283/// Static attributes (focusable, modal, required) and window-level `active`
284/// are left out to keep per-node cost low.
285///
286/// Default (`enabled` + `visible`, everything else off/absent) matches
287/// [`xa11y::StateSet`]'s default, so "all defaults" renders as nothing in the
288/// outline and as a predictable flat object in JSON.
289#[derive(Debug, Clone, PartialEq, Serialize)]
290pub struct ElementStates {
291    /// Whether the element accepts interaction (`false` = aria `disabled`).
292    pub enabled: bool,
293    /// Whether the element is on-screen (`false` = aria `hidden`).
294    pub visible: bool,
295    /// Whether the element currently holds keyboard focus.
296    pub focused: bool,
297    /// `None` = not checkable. `Some(Off)` is meaningful (an unchecked
298    /// checkbox), hence `Option` rather than a plain bool.
299    pub checked: Option<ToggleState>,
300    /// Whether the element is the selected item of its container.
301    pub selected: bool,
302    /// `None` = not expandable. `Some(false)` = a collapsed disclosure.
303    pub expanded: Option<bool>,
304    /// Whether the element accepts text input.
305    pub editable: bool,
306    /// Whether an async operation is in progress on the element.
307    pub busy: bool,
308}
309
310impl Default for ElementStates {
311    fn default() -> Self {
312        Self {
313            enabled: true,
314            visible: true,
315            focused: false,
316            checked: None,
317            selected: false,
318            expanded: None,
319            editable: false,
320            busy: false,
321        }
322    }
323}
324
325impl ElementStates {
326    /// Normalize a platform [`xa11y::StateSet`] into the reported subset.
327    #[must_use]
328    pub fn from_state_set(states: &xa11y::StateSet) -> Self {
329        Self {
330            enabled: states.enabled,
331            visible: states.visible,
332            focused: states.focused,
333            checked: states.checked.map(ToggleState::from),
334            selected: states.selected,
335            expanded: states.expanded,
336            editable: states.editable,
337            busy: states.busy,
338        }
339    }
340
341    /// Whether every flag matches the default (enabled + visible, nothing
342    /// else). The outline renders nothing for such elements.
343    #[must_use]
344    pub fn is_default(&self) -> bool {
345        *self == Self::default()
346    }
347
348    /// aria-vocabulary tokens for the non-default flags, in fixed order:
349    /// `disabled`, `hidden`, `focused`, `checked`/`unchecked`/`mixed`,
350    /// `selected`, `expanded`/`collapsed`, `editable`, `busy`. Fixed order
351    /// keeps the outline predictable to parse.
352    #[must_use]
353    pub fn outline_tokens(&self) -> Vec<&'static str> {
354        let mut tokens = Vec::new();
355        if !self.enabled {
356            tokens.push("disabled");
357        }
358        if !self.visible {
359            tokens.push("hidden");
360        }
361        if self.focused {
362            tokens.push("focused");
363        }
364        match self.checked {
365            Some(ToggleState::On) => tokens.push("checked"),
366            Some(ToggleState::Off) => tokens.push("unchecked"),
367            Some(ToggleState::Mixed) => tokens.push("mixed"),
368            None => {}
369        }
370        if self.selected {
371            tokens.push("selected");
372        }
373        match self.expanded {
374            Some(true) => tokens.push("expanded"),
375            Some(false) => tokens.push("collapsed"),
376            None => {}
377        }
378        if self.editable {
379            tokens.push("editable");
380        }
381        if self.busy {
382            tokens.push("busy");
383        }
384        tokens
385    }
386}
387
388/// One element of the rendered snapshot: role, name, value, current states
389/// and children.
390///
391/// This is the tool's own tree — xa11y's `TreeNode` carries no states, and
392/// the builder below reads them from the same `ElementData` the node's
393/// identity comes from, so building here adds zero extra provider round
394/// trips per node.
395#[derive(Debug, Clone, Serialize)]
396pub struct StateNode {
397    /// Element role, snake_case (same vocabulary as selectors).
398    pub role: String,
399    /// Accessible name, when the element has one.
400    pub name: Option<String>,
401    /// Current value, when the element has one.
402    pub value: Option<String>,
403    /// Current element states.
404    pub states: ElementStates,
405    /// Child elements.
406    pub children: Vec<StateNode>,
407}
408/// Action to perform on the element matched by `selector`.
409#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
410#[serde(rename_all = "snake_case")]
411pub enum ActAction {
412    /// Click / invoke the element via the accessibility action layer
413    /// (buttons, links, menu items).
414    #[default]
415    Press,
416    /// Set keyboard focus.
417    Focus,
418    /// Remove keyboard focus.
419    Blur,
420    /// Toggle a two- or three-state control (checkbox, switch).
421    Toggle,
422    /// Select the element (list item, tab, row).
423    Select,
424    /// Expand a disclosure, menu, combo box or tree item.
425    Expand,
426    /// Collapse an expanded element.
427    Collapse,
428    /// Open the element's context menu or dropdown.
429    ShowMenu,
430    /// Increment a stepper/slider.
431    Increment,
432    /// Decrement a stepper/slider.
433    Decrement,
434    /// Scroll the element into view.
435    ScrollIntoView,
436    /// Replace the element's text value entirely (text fields). Requires
437    /// `value`.
438    SetValue,
439    /// Set a numeric value (slider, spinner). Requires `numeric_value`.
440    SetNumericValue,
441    /// Select a text range `start..end` inside the element. Requires `range`.
442    SelectText,
443    /// Type `value` at the element's caret (focuses it first, inserts rather
444    /// than replacing). Requires `value`.
445    TypeText,
446    /// Platform-specific action by name (e.g. `"raise"`). Requires `value`.
447    PerformAction,
448}
449
450/// Input for `computer_act` — ONE step of a semantic pipeline: either an
451/// accessibility action on the element matched by `selector`, a keyboard
452/// action (`key`/`text`), or a `wait` pause — chained via `then` with
453/// shell `&&` semantics.
454#[derive(Debug, Clone, Default, Deserialize, JsonSchema)]
455pub struct ComputerAct {
456    /// Application name (exact match, e.g. 'Safari'). Provide `name` or `pid`.
457    /// Semantic-action steps only.
458    pub name: Option<String>,
459    /// Application process ID. Provide `name` or `pid`. Semantic-action
460    /// steps only.
461    pub pid: Option<u32>,
462    /// CSS-like selector for the target element, e.g. `button[name='OK']`.
463    /// Required on a semantic-action step; absent on keyboard/wait steps.
464    pub selector: Option<String>,
465    /// 1-based match index when `selector` matches multiple elements
466    /// (default 1).
467    pub nth: Option<usize>,
468    /// Action to perform (default `press`). Semantic-action steps only.
469    pub action: Option<ActAction>,
470    /// Text for `set_value`, `type_text` and `perform_action`.
471    pub value: Option<String>,
472    /// Number for `set_numeric_value`.
473    pub numeric_value: Option<f64>,
474    /// `[start, end]` (0-based; `end` EXCLUSIVE — `[0, 3]` selects three
475    /// characters, matching the AT-SPI convention) for `select_text`.
476    pub range: Option<Vec<u32>>,
477    /// How long to wait for the app AND a visible+enabled element match, in
478    /// milliseconds (default 3000). Unlike computer_snapshot/computer_screenshot,
479    /// actions auto-wait for actionability.
480    pub timeout_ms: Option<u64>,
481    /// Target an OS SHELL SURFACE instead of an application (mutually
482    /// exclusive with `name`/`pid`): the menu bar, Dock/taskbar/panel, tray
483    /// status items, the desktop, or a flyout open right now. Semantic
484    /// steps only — a surface has no process to type into; keyboard steps
485    /// in the same chain still need an element step to have set focus.
486    pub surface: Option<SurfaceKind>,
487    // ── Keyboard fields (shared engine with computer_control) ──
488    /// Key to tap, by name: single characters (lowercase — for uppercase
489    /// hold `shift`) or named keys (`enter`, `escape`, `tab`, `space`,
490    /// `backspace`, `delete`, `insert`, `up`, `down`, `left`, `right`,
491    /// `home`, `end`, `pageup`, `pagedown`, `f1`..`f12`). Types into
492    /// whatever holds keyboard focus NOW.
493    pub key: Option<String>,
494    /// Literal text to type into the focused element. Mutually exclusive
495    /// with `key`.
496    pub text: Option<String>,
497    /// Modifier keys held while tapping `key` (`shift`, `ctrl`, `alt`,
498    /// `meta`) — e.g. key `a` + `held: ["ctrl"]` = select all. `text`
499    /// rejects `held`.
500    pub held: Option<Vec<String>>,
501    /// Milliseconds to WAIT as a STANDALONE step (no other fields): gives
502    /// the app time to settle before the next step acts. Capped at 10 s.
503    pub wait: Option<u64>,
504    /// Optional NEXT step of the pipeline, executed only if this step
505    /// succeeded. Same shape as this step, recursively.
506    pub then: Option<Box<ComputerAct>>,
507}
508
509/// Output of `computer_act`.
510#[derive(Debug, Clone, Serialize, JsonSchema)]
511pub struct ActOutput {
512    /// What was executed, per step: semantic actions as
513    /// `<action> \`<selector>\` (app)`, keyboard steps as the tapped key +
514    /// held modifiers or the typed text, waits as `waited N ms`.
515    /// Pipeline steps join with ` → `.
516    pub sent: String,
517}
518
519/// The condition `computer_wait` watches, mirroring xa11y's
520/// [`xa11y::ElementState`] (the same vocabulary `computer_snapshot` reports
521/// as state tokens).
522#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, JsonSchema)]
523#[serde(rename_all = "lowercase")]
524pub enum WaitState {
525    /// A matching element exists in the tree.
526    Attached,
527    /// No element matches the selector (a dialog closed, a spinner went
528    /// away). Tolerates the element never having existed.
529    Detached,
530    /// A matching element exists and is visible (default).
531    #[default]
532    Visible,
533    /// A matching element is hidden or no longer exists.
534    Hidden,
535    /// A matching element exists and is enabled.
536    Enabled,
537    /// A matching element exists but is disabled.
538    Disabled,
539    /// A matching element holds keyboard focus.
540    Focused,
541    /// No matching element holds keyboard focus.
542    Unfocused,
543}
544
545/// Input for `computer_wait` — block until a selector in a target app
546/// reaches a state, or time out with a diagnosis of the last observed
547/// state.
548#[derive(Debug, Clone, Default, Deserialize, JsonSchema)]
549pub struct ComputerWait {
550    /// Application name (exact match, e.g. `"Safari"`). Provide `name` or
551    /// `pid` — exactly one.
552    pub name: Option<String>,
553    /// Application process ID. Provide `name` or `pid` — exactly one.
554    pub pid: Option<u32>,
555    /// CSS-like selector for the element to watch, e.g.
556    /// `progress_bar[name='Exporting…']`.
557    pub selector: Option<String>,
558    /// 1-based match index when `selector` matches multiple elements
559    /// (default 1).
560    pub nth: Option<usize>,
561    /// The condition to wait for (default `visible`).
562    pub state: Option<WaitState>,
563    /// How long to wait for the condition, in milliseconds. Default 10 000;
564    /// capped at 60 000 so a stuck call cannot pin the session for minutes.
565    pub timeout_ms: Option<u64>,
566}
567
568/// Output of `computer_wait` when the condition was met in time.
569#[derive(Debug, Clone, Serialize, JsonSchema)]
570pub struct WaitOutput {
571    /// Whether the condition was met before the timeout (always `true` in
572    /// the success response — a timeout is an error carrying the diagnosis).
573    pub met: bool,
574    /// How long the call actually waited, in milliseconds.
575    pub elapsed_ms: u64,
576    /// The state the element was in when the condition was met — a
577    /// convenience sanity check for the model (e.g. a `detached` wait's
578    /// element is gone; an `enabled` wait's element reports enabled).
579    pub observed: WaitObservation,
580}
581
582/// What the watched element looked like when the wait resolved.
583#[derive(Debug, Clone, Serialize, JsonSchema)]
584pub struct WaitObservation {
585    /// Whether an element matched the selector at resolution time.
586    pub attached: bool,
587    /// Whether the matched element was visible (absent when detached).
588    pub visible: Option<bool>,
589    /// Whether the matched element was enabled (absent when detached).
590    pub enabled: Option<bool>,
591    /// Whether the matched element held keyboard focus (absent when
592    /// detached).
593    pub focused: Option<bool>,
594}
595
596/// Which OS shell surface to target instead of an application — the parts
597/// of the desktop OUTSIDE any app: menu bar, Dock/taskbar/panel, tray
598/// status items, the desktop itself, transient flyouts (Notification
599/// Center, Quick Settings, shell context menus). Mirrors xa11y's
600/// [`xa11y::ShellSurfaceKind`] exactly (same snake_case wire spelling).
601#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, JsonSchema)]
602#[serde(rename_all = "snake_case")]
603pub enum SurfaceKind {
604    /// The frontmost application's menu bar (macOS: File → Save As, the
605    /// Apple menu). Windows/Linux: per-window menu bars live in the app's
606    /// own tree, not here.
607    #[default]
608    MenuBar,
609    /// System status items / tray icons (macOS menu-bar extras, Windows
610    /// tray overflow needs a press first — content only exists while the
611    /// overflow is open).
612    StatusItems,
613    /// The Windows taskbar.
614    Taskbar,
615    /// Linux desktop panels (GNOME top bar, KDE panel, …).
616    Panel,
617    /// The macOS Dock.
618    Dock,
619    /// The desktop itself (icons, wallpaper-level widgets).
620    Desktop,
621    /// Transient shell flyouts open RIGHT NOW: Notification Center, Quick
622    /// Settings, shell context menus. They exist only while open — the
623    /// caller performs the press that opens them on a real element first,
624    /// then re-enumerates.
625    Flyout,
626    /// A shell surface the platform reported without a known kind.
627    Unknown,
628}
629
630/// Pointer action to perform at desktop coordinates.
631#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
632#[serde(rename_all = "snake_case")]
633pub enum PointerAction {
634    /// Left-click at (`x`, `y`).
635    #[default]
636    Click,
637    /// Double left-click at (`x`, `y`).
638    DoubleClick,
639    /// Right-click at (`x`, `y`).
640    RightClick,
641    /// Move the cursor to (`x`, `y`) without clicking.
642    Move,
643    /// Press a mouse button down at the current position (`button`, default
644    /// left) — pair with `up` for custom drags.
645    Down,
646    /// Release a previously pressed mouse button.
647    Up,
648    /// Scroll by (`dx`, `dy`) wheel steps at (`x`, `y`). Requires `dx`/`dy`.
649    Scroll,
650    /// Press at the start point, interpolate movement to the end point
651    /// (~60 Hz), release. Requires both endpoints — coordinates (`x`/`y` +
652    /// `x2`/`y2`) or elements (`selector` + `to_selector`), not a mix.
653    /// `duration_ms` paces the movement (default 150, min 50).
654    Drag,
655}
656
657/// Where a pointer action lands inside the element's bounds.
658///
659/// The element form is tree-grounded and therefore allowed in Build mode;
660/// the coordinate form is position-dependent and stays Yolo/Command only.
661#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, JsonSchema)]
662#[serde(rename_all = "snake_case")]
663pub enum PointerAnchor {
664    /// Center of the bounds (default).
665    #[default]
666    Center,
667    /// Top-left corner (half-open bounds: the last point inside, not the
668    /// exclusive edge).
669    TopLeft,
670    /// Top-right corner.
671    TopRight,
672    /// Bottom-left corner.
673    BottomLeft,
674    /// Bottom-right corner.
675    BottomRight,
676}
677
678/// Input for `computer_control` — one step of a desktop-control pipeline:
679/// EITHER a pointer action (click/scroll/move/drag — `key`/`text` absent)
680/// OR a keyboard action (`key` or `text`), at raw coordinates or on an
681/// accessibility-tree element.
682///
683/// A step may chain the NEXT step via `then`: the steps run sequentially in
684/// a single call, each only if the previous succeeded (shell `&&` semantics)
685/// — the first failing step aborts the chain with the point of failure.
686/// Targeting is PER STEP: the canonical pattern is `click` an element (the
687/// real click moves OS keyboard focus — the one mechanism that works on
688/// every toolkit) and `then` type into the focused field. `up` needs no
689/// target.
690#[derive(Debug, Clone, Default, Deserialize, JsonSchema)]
691pub struct ComputerControl {
692    // ── Pointer fields ──
693    /// X coordinate in desktop pixels (coordinate form; also the drag START).
694    pub x: Option<i32>,
695    /// Y coordinate in desktop pixels (coordinate form; also the drag START).
696    pub y: Option<i32>,
697    /// Pointer action (default `click`). Ignored — and rejected together
698    /// with any other pointer field — when the step is a keyboard step
699    /// (`key`/`text`).
700    pub action: Option<PointerAction>,
701    /// Button for `down`/`up` (`left` default, `right`, `middle`).
702    pub button: Option<String>,
703    /// Horizontal scroll delta (wheel steps) for `scroll`.
704    pub dx: Option<i32>,
705    /// Vertical scroll delta (wheel steps) for `scroll`; positive scrolls down.
706    pub dy: Option<i32>,
707    /// Targeting PER STEP: application name (exact match) scoping
708    /// `selector`. Provide `app` or `pid` — exactly one, only together with
709    /// `selector`.
710    pub app: Option<String>,
711    /// Application process ID for the element form. Per step.
712    pub pid: Option<u32>,
713    /// Target an OS SHELL SURFACE instead of an application (mutually
714    /// exclusive with `app`/`pid`; only together with `selector`): the
715    /// menu bar, Dock/taskbar/panel, tray status items, the desktop, or a
716    /// flyout open right now. Pointer steps act on the element matched
717    /// under the surface's root the same way they do under an app-scoped
718    /// step.
719    pub surface: Option<SurfaceKind>,
720    /// CSS-like selector of the element this step acts on, e.g.
721    /// `"button[name='OK']"` — the point resolves from the element's CURRENT
722    /// bounds when the step runs, never stale. A `click` on an element moves
723    /// OS keyboard focus to it, which is how the next step's typing finds
724    /// the right field. Per step.
725    pub selector: Option<String>,
726    /// 1-based match index when `selector` matches multiple elements
727    /// (default 1). Requires `selector`. Per step.
728    pub nth: Option<usize>,
729    /// Where inside the element's bounds the point lands (default `center`).
730    pub anchor: Option<PointerAnchor>,
731    /// Number of consecutive clicks for click actions (1 default, 2 =
732    /// double-click, 3 = triple). Independent of `action`: `click` +
733    /// `count: 2` is a double-click.
734    pub count: Option<u32>,
735    /// Modifier keys for THIS step: held during a click or drag, or held
736    /// while tapping `key` (`shift`, `ctrl`, `alt`, `meta`) — e.g. `click` +
737    /// `held: ["ctrl"]` = ctrl+click; key `a` + `held: ["ctrl"]` = select
738    /// all. `text` rejects `held`.
739    pub held: Option<Vec<String>>,
740    /// End X for `drag` (with `y2`). Mutually exclusive with `to_selector`.
741    pub x2: Option<i32>,
742    /// End Y for `drag`.
743    pub y2: Option<i32>,
744    /// End element selector for `drag` (CSS-like selector on the same app) —
745    /// the element form for the drag's end point. Mutually exclusive with
746    /// `x2`/`y2`.
747    pub to_selector: Option<String>,
748    /// Total duration of a `drag`'s movement, in milliseconds (default 150,
749    /// min 50). Backends interpolate the pointer path across this time.
750    pub duration_ms: Option<u64>,
751    // ── Keyboard fields ──
752    /// Key to tap, by name: single characters (`a`, `5`, `.` — lowercase;
753    /// for uppercase hold `shift`), or `enter`, `escape`, `tab`, `space`,
754    /// `backspace`, `delete`, `insert`, `up`, `down`, `left`, `right`,
755    /// `home`, `end`, `pageup`, `pagedown`, `f1`..`f12`. Typing goes into
756    /// the element focused by an earlier step's element click (or whatever
757    /// holds focus).
758    pub key: Option<String>,
759    /// Literal text to type into the focused element (any characters,
760    /// including uppercase). Mutually exclusive with `key`.
761    pub text: Option<String>,
762    /// Milliseconds to WAIT as a STANDALONE step (no other fields): gives
763    /// the app time to open a dialog or create an ephemeral field before
764    /// the next step acts — e.g. click the menu item that opens a rename
765    /// box, then `{"wait": 600}`, then type into it. Capped at 10 s.
766    pub wait: Option<u64>,
767    /// Optional NEXT step of the pipeline, executed only if this step
768    /// succeeded (e.g. click `text_field` → then text `"olá"` → then key
769    /// `enter`). Same shape as this step, recursively.
770    pub then: Option<Box<ComputerControl>>,
771}
772
773/// Output of `computer_control`.
774#[derive(Debug, Clone, Serialize, JsonSchema)]
775pub struct ControlOutput {
776    /// What was executed, per step: pointer actions as
777    /// `click <what>@x,y`, keyboard steps as the tapped key + held modifiers
778    /// or the typed text. Pipeline steps join with ` → `.
779    pub sent: String,
780}