Skip to main content

idlewarden_plugin_api/
action.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Two strictly separated layers (ADR-0003):
3//!
4//! * [`Intent`], what the *agent* decides ("buy_upgrade"). Game vocabulary.
5//! * [`InputCommand`], what the *Core* executes. Window-relative, never
6//!   screen-absolute, so it survives the window being moved or the display
7//!   changing.
8//!
9//! The plugin owns the translation between them. Every action reports an
10//! [`ActionOutcome`]; an action with no verifiable post-condition is a bug.
11
12use serde::{Deserialize, Serialize};
13use std::collections::BTreeMap;
14
15/// A point in window-client space, normalised to `0.0..=1.0`.
16#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
17pub struct Point {
18    pub x: f64,
19    pub y: f64,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
23#[serde(rename_all = "snake_case")]
24pub enum MouseButton {
25    Left,
26    Right,
27    Middle,
28}
29
30#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
31pub struct Key(pub String);
32
33/// What the agent decided to do, in the plugin's own vocabulary.
34#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
35pub struct Intent {
36    pub name: String,
37    #[serde(default)]
38    pub params: BTreeMap<String, crate::value::Value>,
39}
40
41impl Intent {
42    pub fn new(name: impl Into<String>) -> Self {
43        Intent {
44            name: name.into(),
45            params: BTreeMap::new(),
46        }
47    }
48}
49
50/// A primitive the Core knows how to execute.
51#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
52#[serde(tag = "op", rename_all = "snake_case")]
53pub enum InputCommand {
54    MoveTo { to: Point },
55    Click { at: Point, button: MouseButton },
56    KeyPress { key: Key },
57    KeyDown { key: Key },
58    KeyUp { key: Key },
59    Scroll { at: Point, delta: i32 },
60    Wait { ms: u64 },
61}
62
63/// The result of executing one intent. Transactional and interruptible:
64/// without this, robustness is impossible (ADR-0003).
65#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
66#[serde(tag = "outcome", rename_all = "snake_case")]
67pub enum ActionOutcome {
68    /// The post-condition was observed to hold.
69    Succeeded,
70    /// The sequence ran but the post-condition did not hold.
71    Failed {
72        reason: String,
73    },
74    /// A precondition (focus, resolution, known screen) was not met.
75    Rejected {
76        reason: String,
77    },
78    /// The Governor or the user stopped it mid-flight.
79    Aborted,
80    TimedOut {
81        after_ms: u64,
82    },
83}
84
85impl ActionOutcome {
86    pub fn is_success(&self) -> bool {
87        matches!(self, ActionOutcome::Succeeded)
88    }
89}