Skip to main content

beam_core/
ipc.rs

1use serde::{Deserialize, Serialize};
2
3use crate::{backend_kind::BackendKind, config::ScreenAnalyzerConfig, session::AdoptedFrom};
4
5#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
6#[serde(rename_all = "snake_case")]
7pub enum DisplayMode {
8    Hidden,
9    Screenshot,
10}
11
12#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
13#[serde(rename_all = "snake_case")]
14pub enum ScreenStatus {
15    Starting,
16    Working,
17    Idle,
18    Analyzing,
19    Limited,
20}
21
22#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
23#[serde(rename_all = "snake_case")]
24pub enum CliUsageLimitKind {
25    Usage,
26    Rate,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
30#[serde(rename_all = "camelCase")]
31pub struct CliUsageLimitState {
32    pub limited: bool,
33    pub kind: CliUsageLimitKind,
34    pub retry_at_ms: u64,
35    pub retry_label: String,
36    pub retry_ready: bool,
37}
38
39#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
40#[serde(rename_all = "snake_case")]
41pub enum TermActionKey {
42    Esc,
43    CtrlC,
44    Tab,
45    Enter,
46    Space,
47    Up,
48    Down,
49    Left,
50    Right,
51    HalfPageUp,
52    HalfPageDown,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
56pub struct TuiPromptOption {
57    #[serde(default)]
58    pub label: Option<String>,
59    pub text: String,
60    pub selected: bool,
61    #[serde(rename = "type", default)]
62    pub option_type: Option<String>,
63    #[serde(default)]
64    pub keys: Vec<String>,
65}
66
67#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
68#[serde(rename_all = "snake_case")]
69pub enum FinalOutputKind {
70    Bridge,
71    LocalTurn,
72    LocalTurnHeadless,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
76pub struct InitConfig {
77    pub session_id: String,
78    pub title: String,
79    pub chat_id: String,
80    pub root_message_id: String,
81    pub working_dir: String,
82    pub cli_id: String,
83    pub cli_bin: String,
84    #[serde(default, skip_serializing_if = "Option::is_none")]
85    pub cgroup_slice: Option<String>,
86    #[serde(default)]
87    pub cli_args: Vec<String>,
88    pub prompt: String,
89    #[serde(default)]
90    pub resume: bool,
91    #[serde(default)]
92    pub cli_session_id: Option<String>,
93    pub lark_app_id: String,
94    pub lark_app_secret: String,
95    #[serde(default)]
96    pub prompt_turn_id: Option<String>,
97    #[serde(default)]
98    pub owner_open_id: Option<String>,
99    #[serde(default)]
100    pub adopted_from: Option<AdoptedFrom>,
101    #[serde(default)]
102    pub adopt_restored_from_metadata: bool,
103    #[serde(default)]
104    pub screen_analyzer: ScreenAnalyzerConfig,
105    #[serde(default)]
106    pub initial_prompt: Option<String>,
107    #[serde(default)]
108    pub model: Option<String>,
109    #[serde(default)]
110    pub locale: Option<String>,
111    #[serde(default)]
112    pub bot_name: Option<String>,
113    #[serde(default)]
114    pub bot_open_id: Option<String>,
115    #[serde(default)]
116    pub resume_session_id: Option<String>,
117    #[serde(default)]
118    pub disable_cli_bypass: bool,
119    /// Terminal backend selected for this session (daemon default, bot
120    /// override, or the adopted candidate's backend).
121    #[serde(default)]
122    pub backend_kind: BackendKind,
123    #[serde(default)]
124    pub herdr_session: Option<String>,
125    #[serde(default)]
126    pub herdr_workspace_id: Option<String>,
127    #[serde(default)]
128    pub herdr_pane_id: Option<String>,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
132#[serde(tag = "type", rename_all = "snake_case")]
133pub enum DaemonToWorker {
134    Init(Box<InitConfig>),
135    Message { content: String, turn_id: String },
136    RawInput { content: String, turn_id: String },
137    Close,
138    Restart,
139    SetDisplayMode { mode: DisplayMode },
140    TermAction { key: TermActionKey },
141    SpecialKeys { keys: Vec<String> },
142    TuiKeys { keys: Vec<String>, is_final: bool },
143    TuiTextInput { keys: Vec<String>, text: String },
144    RefreshScreen,
145    SetTranscriptSource { cli_session_id: String },
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
149#[serde(tag = "type", rename_all = "snake_case")]
150pub enum WorkerToDaemon {
151    Ready {
152        zellij_session: String,
153        #[serde(default)]
154        backend_kind: BackendKind,
155        #[serde(default)]
156        herdr_workspace_id: Option<String>,
157        #[serde(default)]
158        herdr_pane_id: Option<String>,
159    },
160    PromptReady,
161    ScreenUpdate {
162        content: String,
163        status: ScreenStatus,
164        #[serde(default)]
165        usage_limit: Option<CliUsageLimitState>,
166    },
167    ScreenshotUploaded {
168        image_key: String,
169        status: ScreenStatus,
170        #[serde(default)]
171        usage_limit: Option<CliUsageLimitState>,
172        #[serde(default)]
173        turn_id: Option<String>,
174    },
175    CliSessionId {
176        cli_session_id: String,
177    },
178    CliExit {
179        code: Option<i32>,
180        signal: Option<String>,
181    },
182    TuiPrompt {
183        description: String,
184        options: Vec<TuiPromptOption>,
185        #[serde(default)]
186        multi_select: bool,
187    },
188    TuiPromptResolved {
189        #[serde(default)]
190        selected_text: Option<String>,
191    },
192    FinalOutput {
193        content: String,
194        turn_id: String,
195        #[serde(default)]
196        kind: Option<FinalOutputKind>,
197        #[serde(default)]
198        user_text: Option<String>,
199    },
200    AdoptPreamble {
201        user_text: String,
202        assistant_text: String,
203    },
204    UserNotify {
205        message: String,
206    },
207    /// Periodic liveness signal from the worker (independent of the message
208    /// loop). `processing_since_ms` is `Some(start_ms)` while the message
209    /// loop is busy handling a daemon message, so the daemon can distinguish
210    /// "worker dead" (no heartbeat) from "worker stuck on a message".
211    Heartbeat {
212        #[serde(default)]
213        processing_since_ms: Option<u64>,
214    },
215    TranscriptChoices {
216        candidates: Vec<TranscriptChoice>,
217        turn_id: String,
218    },
219    Error {
220        message: String,
221    },
222    /// Mux-pushed agent state (Herdr `events.subscribe` or `agent get`
223    /// polling). Only `state == "blocked"` produces an attention side effect
224    /// in the daemon; every other state is log/metric only. Deliberately a
225    /// separate variant: `ScreenUpdate` only fires on hash/status/usage_limit
226    /// changes and would drop `blocked` when the screen is unchanged.
227    MuxAgentState {
228        state: String,
229        #[serde(default)]
230        agent_name: Option<String>,
231        pane_id: String,
232        #[serde(default)]
233        message: Option<String>,
234    },
235}
236
237/// A single transcript source candidate for disambiguation.
238#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
239pub struct TranscriptChoice {
240    pub session_id: String,
241    pub label: String,
242}