1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//! Write-side command vocabulary.
//!
//! HTTP shims (`uzor-agent-api`) decode JSON into [`Command`] and pass
//! it to the platform window manager which forwards to either
//! [`super::LmAgent`] or its own override (e.g. screenshot).
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum Command {
// ── synthetic input (pixel mode) ────────────────────────────────
/// Pointer-move at `(x, y)` in the named window.
InjectHover { window: String, x: f64, y: f64 },
/// Full pointer-down + pointer-up at `(x, y)`.
InjectClick { window: String, x: f64, y: f64, button: MouseButton },
/// Wheel scroll (`dx`, `dy` in logical pixels).
InjectScroll { window: String, dx: f64, dy: f64 },
// ── semantic / direct LM ops ────────────────────────────────────
/// Resolve `widget_id` to its rect via the branch's layout tree
/// and synthesise a click at its centre. Detect-friendly: agents
/// don't track pixel positions.
ClickWidget { window: String, widget_id: String },
/// Hover over a widget by id.
HoverWidget { window: String, widget_id: String },
/// Open / close composite-state slots by id.
OpenModal { window: String, modal_id: String },
CloseModal { window: String, modal_id: String },
OpenPopup { window: String, popup_id: String },
ClosePopup { window: String, popup_id: String },
OpenDropdown { window: String, dropdown_id: String },
CloseDropdown { window: String, dropdown_id: String },
ToggleSidebar { window: String, sidebar_id: String },
// ── window lifecycle ────────────────────────────────────────────
SpawnWindow {
key: String,
title: String,
width: u32,
height: u32,
background: Option<u32>,
decorations: Option<bool>,
},
CloseWindow { key: String },
// ── LM-root ops ─────────────────────────────────────────────────
SetSyncMode {
node_id: String,
mode: String, // "synced" / "sometimes_alone" / "sometimes_group" / "standalone"
group_id: Option<u64>,
},
ApplyStylePreset { name: String },
// ── blackbox routing ────────────────────────────────────────────
/// Synthetic click on a mini-widget published by a blackbox.
BlackboxClickWidget { window: String, slot_id: String, sub_id: String },
/// Free-form agent-log push. Used by the HTTP shim to record
/// `<slot>.<action>` entries after a blackbox action returns
/// with `log_payload`, and by external tooling that wants to
/// drop breadcrumbs into the merged feed.
LogPush {
category: String,
#[serde(default)]
payload: serde_json::Value,
#[serde(default)]
window: Option<String>,
},
/// Change the baseline tick rate of a window. `mode` is
/// `"dirty"`, `"capped"`, or `"uncapped"`. When `mode == "capped"`,
/// `fps` is required.
SetTickRate {
window: String,
mode: String,
#[serde(default)]
fps: Option<u32>,
},
// ── Resize / move ────────────────────────────────────────────────
/// Drag a single edge of a panel. The LM resolves
/// `(panel_id, edge)` onto the dock separator that owns that edge
/// and applies `delta_px` along the separator's axis. No-op for
/// edges that touch the window / a chrome strip rather than a
/// sibling panel. `edge` is one of `"n" / "s" / "e" / "w"`.
ResizePanelEdge {
window: String,
panel_id: String,
edge: String,
delta_px: f64,
},
/// Drag a dock separator by index. `sep_idx` matches
/// `LayoutManager::panels().separators()[sep_idx]`; `delta_px` is
/// pixel movement along the separator's axis (positive = right /
/// down). Equivalent to a low-level scripted version of grabbing
/// the divider visual.
DragDockSeparator {
window: String,
sep_idx: usize,
delta_px: f64,
},
/// Set an explicit rect on a panel. For free-floating panels
/// only — panels managed by the dock tree / edge slots ignore the
/// call and return `ok: false`. Hooked up alongside the
/// free-floating panel API in a follow-up step.
SetPanelRect {
window: String,
panel_id: String,
x: f64,
y: f64,
width: f64,
height: f64,
},
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum MouseButton {
Left,
Right,
Middle,
}
#[derive(Debug, Clone, Serialize)]
pub struct CommandReply {
pub ok: bool,
pub message: Option<String>,
}
impl CommandReply {
pub fn ok() -> Self {
Self { ok: true, message: None }
}
pub fn err(msg: impl Into<String>) -> Self {
Self { ok: false, message: Some(msg.into()) }
}
}