Skip to main content

nomad_common/
protocol.rs

1use serde::{Deserialize, Serialize};
2
3use crate::ecs::{ChangedEntities, EcsFrame, PageMetadata};
4
5/// Structured error codes for machine-readable error handling.
6#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
7pub enum ErrorCode {
8    Unknown = 0,
9    Timeout = 1,
10    TabNotFound = 2,
11    RateLimited = 3,
12    BadRequest = 4,
13    Internal = 5,
14}
15
16/// Messages sent over Unix Domain Socket between daemon and client.
17///
18/// ⚠ DISCRIMINANT ORDER IS FIXED — never reorder, never insert between groups.
19/// Adding new variants: APPEND to the end.
20/// Reordering WILL silently break all existing clients via bincode encoding.
21///
22/// Current layout:
23///   Daemon→Client: Frame=0, FrameDelta=1, Metadata=2, Error=3, EvaluateResult=4, FoundEntities=5
24///   Client→Daemon: Navigate=6, Scroll=7, Click=8, Input=9, Resize=10,
25///                  NewTab=11, CloseTab=12, GoBack=13, GoForward=14, Evaluate=15
26///   Agent:         FindEntities=16, EntitySnapshot=17, ClickEntity=18, InputEntity=19
27///   Extensions:    ActionLog=20, ClickSelector=21, GetActionLog=22, CompareTabs=23, TabComparison=24, TransferTab=25
28///   Health:        Health=26, HealthResult=27
29///   Auth/Version:  ApiKey=28, Handshake=29
30#[derive(Serialize, Deserialize, Debug, Clone)]
31pub enum DaemonMessage {
32    // ── Daemon → Client (0-5) ──
33    Frame { tab_id: u64, frame: EcsFrame, metadata: PageMetadata },
34    FrameDelta { tab_id: u64, sequence_number: u64, scroll_offset: (f32, f32), viewport: (f32, f32), entities: ChangedEntities },
35    Metadata { tab_id: u64, title: String, url: String },
36    Error { tab_id: u64, code: ErrorCode, description: String },
37    EvaluateResult { tab_id: u64, result: String },
38    FoundEntities { tab_id: u64, entities: Vec<EntitySummary> },
39
40    // ── Client → Daemon (6-15) ──
41    Navigate { tab_id: u64, url: String, format: u8 },
42    Scroll { tab_id: u64, dx: f32, dy: f32 },
43    Click { tab_id: u64, x: f32, y: f32 },
44    Input { tab_id: u64, text: String },
45    Resize { tab_id: u64, width: u32, height: u32 },
46    NewTab { url: Option<String> },
47    CloseTab { tab_id: u64 },
48    GoBack { tab_id: u64 },
49    GoForward { tab_id: u64 },
50    Evaluate { tab_id: u64, js: String, timeout_ms: u32 },
51
52    // ── Agent Messages (16-19) ──
53    FindEntities { tab_id: u64, query: EntityQuery },
54    EntitySnapshot { tab_id: u64 },
55    ClickEntity { tab_id: u64, entity_id: u64 },
56    InputEntity { tab_id: u64, entity_id: u64, value: String },
57
58    // ── Extensions (20+) — APPEND ONLY, never insert ──
59
60    /// Daemon → Client: action history for agent reasoning.
61    ActionLog { tab_id: u64, entries: Vec<ActionEntry> },
62
63    /// Click the first entity matching a semantic selector.
64    ClickSelector { tab_id: u64, query: EntityQuery },
65
66    /// Get action history for a tab.
67    GetActionLog { tab_id: u64 },
68
69    /// Compare two tabs' entity states.
70    CompareTabs { tab_a: u64, tab_b: u64 },
71    /// Daemon → Client: result of CompareTabs.
72    TabComparison { tab_a: u64, tab_b: u64, summary: String },
73
74    /// Transfer focus / content from one tab to another.
75    TransferTab { from: u64, to: u64 },
76
77    /// Client → Daemon: request daemon health/status info.
78    Health,
79    /// Daemon → Client: health/status response.
80    HealthResult { pid: u32, uptime_secs: u64, rss_kb: u64, connections: u32, version: String },
81
82    /// Client → Daemon: authenticate with API key (must be first message).
83    ApiKey { key: String },
84    /// Bidirectional: protocol version handshake.
85    Handshake { version: u32, capabilities: Vec<String> },
86}
87
88/// Single entry in the action history for agent reasoning.
89#[derive(Serialize, Deserialize, Debug, Clone)]
90pub struct ActionEntry {
91    pub sequence: u64,
92    pub action: String,
93    pub target_id: u64,
94    pub value: String,
95    pub timestamp: u64,
96    pub entity_count: u32,
97}
98
99/// Filter for finding entities by semantic properties.
100#[derive(Serialize, Deserialize, Debug, Clone)]
101pub struct EntityQuery {
102    pub role: Option<String>,
103    pub tag: Option<String>,
104    pub intent: Option<String>,
105    pub text_contains: Option<String>,
106    pub label: Option<String>,
107    pub limit: Option<u16>,
108}
109
110/// Summary of a single entity for agent consumption.
111#[derive(Serialize, Deserialize, Debug, Clone)]
112pub struct EntitySummary {
113    pub id: u64,
114    pub tag: String,
115    pub text: String,
116    pub role: String,
117    pub href: String,
118    pub classes: String,
119    pub intent: String,
120    pub is_interactive: bool,
121    pub parent_id: u64,
122}