返回 DeepSeek-TUI-2026
events.rs
根目录 / crates / tui / src / core / events.rs
1 //! Events emitted by the core engine to the UI.
2 //!
3 //! These events flow from the engine to the TUI via a channel,
4 //! enabling non-blocking, real-time updates.
5
6 use std::path::PathBuf;
7
8 use serde_json::Value;
9
10 use crate::core::coherence::CoherenceState;
11 use crate::error_taxonomy::ErrorEnvelope;
12 use crate::models::{Message, SystemPrompt, Usage};
13 use crate::tools::spec::{ToolError, ToolResult};
14 use crate::tools::subagent::SubAgentResult;
15 use crate::tools::user_input::UserInputRequest;
16
17 /// Final status for a turn.
18 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
19 pub enum TurnOutcomeStatus {
20 Completed,
21 Interrupted,
22 Failed,
23 }
24
25 /// Events emitted by the engine to update the UI.
26 #[derive(Debug, Clone)]
27 pub enum Event {
28 // === Streaming Events ===
29 /// A new message block has started
30 MessageStarted {
31 #[allow(dead_code)]
32 index: usize,
33 },
34
35 /// Incremental text content delta
36 MessageDelta {
37 #[allow(dead_code)]
38 index: usize,
39 content: String,
40 },
41
42 /// Message block completed
43 MessageComplete {
44 #[allow(dead_code)]
45 index: usize,
46 },
47
48 /// Thinking block started
49 ThinkingStarted {
50 #[allow(dead_code)]
51 index: usize,
52 },
53
54 /// Incremental thinking content delta
55 ThinkingDelta {
56 #[allow(dead_code)]
57 index: usize,
58 content: String,
59 },
60
61 /// Thinking block completed
62 ThinkingComplete {
63 #[allow(dead_code)]
64 index: usize,
65 },
66
67 // === Tool Events ===
68 /// Tool call initiated
69 ToolCallStarted {
70 id: String,
71 name: String,
72 input: Value,
73 },
74
75 /// Tool execution progress (for long-running tools)
76 #[allow(dead_code)]
77 ToolCallProgress { id: String, output: String },
78
79 /// Tool call completed
80 ToolCallComplete {
81 id: String,
82 name: String,
83 result: Result<ToolResult, ToolError>,
84 },
85
86 // === Turn Lifecycle ===
87 /// A new turn has started (user sent a message)
88 TurnStarted { turn_id: String },
89
90 /// The turn is complete (no more tool calls)
91 TurnComplete {
92 usage: Usage,
93 status: TurnOutcomeStatus,
94 error: Option<String>,
95 },
96
97 /// Context compaction started.
98 CompactionStarted {
99 id: String,
100 auto: bool,
101 message: String,
102 },
103
104 /// Context compaction completed.
105 CompactionCompleted {
106 id: String,
107 auto: bool,
108 message: String,
109 /// Number of messages before compaction.
110 #[allow(dead_code)]
111 messages_before: Option<usize>,
112 /// Number of messages after compaction.
113 #[allow(dead_code)]
114 messages_after: Option<usize>,
115 },
116
117 /// Context compaction failed.
118 CompactionFailed {
119 id: String,
120 auto: bool,
121 message: String,
122 },
123
124 /// Checkpoint-restart cycle boundary advanced (issue #124). The previous
125 /// cycle has already been archived to disk; the engine has swapped its
126 /// in-memory message buffer for the seed messages of cycle `to`.
127 /// Carries the full briefing record so the UI can populate
128 /// `app.cycle_briefings` for `/cycle <n>`.
129 CycleAdvanced {
130 from: u32,
131 to: u32,
132 briefing: crate::cycle_manager::CycleBriefing,
133 },
134
135 /// Capacity decision telemetry.
136 #[allow(dead_code)]
137 CapacityDecision {
138 session_id: String,
139 turn_id: String,
140 h_hat: f64,
141 c_hat: f64,
142 slack: f64,
143 min_slack: f64,
144 violation_ratio: f64,
145 p_fail: f64,
146 risk_band: String,
147 action: String,
148 cooldown_blocked: bool,
149 reason: String,
150 },
151
152 /// Capacity intervention telemetry.
153 #[allow(dead_code)]
154 CapacityIntervention {
155 session_id: String,
156 turn_id: String,
157 action: String,
158 before_prompt_tokens: usize,
159 after_prompt_tokens: usize,
160 compaction_size_reduction: usize,
161 replay_outcome: Option<String>,
162 replan_performed: bool,
163 },
164
165 /// Capacity memory persistence failure telemetry.
166 #[allow(dead_code)]
167 CapacityMemoryPersistFailed {
168 session_id: String,
169 turn_id: String,
170 action: String,
171 error: String,
172 },
173
174 /// Plain-language session coherence state.
175 CoherenceState {
176 state: CoherenceState,
177 label: String,
178 description: String,
179 reason: String,
180 },
181
182 // === Sub-Agent Events ===
183 /// A sub-agent has been spawned
184 AgentSpawned { id: String, prompt: String },
185
186 /// Sub-agent progress update
187 AgentProgress { id: String, status: String },
188
189 /// Sub-agent completed
190 AgentComplete { id: String, result: String },
191
192 /// Sub-agent listing
193 AgentList { agents: Vec<SubAgentResult> },
194
195 /// Structured sub-agent mailbox envelope (issue #128). Carries the
196 /// monotonic seq + the typed `MailboxMessage` so the UI can route each
197 /// envelope to the correct in-transcript card.
198 SubAgentMailbox {
199 seq: u64,
200 message: crate::tools::subagent::MailboxMessage,
201 },
202
203 // === System Events ===
204 /// An error occurred
205 Error {
206 envelope: ErrorEnvelope,
207 #[allow(dead_code)]
208 recoverable: bool,
209 },
210
211 /// Status message for UI display
212 Status { message: String },
213
214 /// Pause terminal input events (for interactive subprocesses)
215 PauseEvents,
216
217 /// Resume terminal input events after subprocess completion
218 ResumeEvents,
219
220 /// Request user approval for a tool call
221 ApprovalRequired {
222 id: String,
223 tool_name: String,
224 description: String,
225 /// Fingerprint key for per‑call approval caching (§5.A).
226 approval_key: String,
227 },
228
229 /// Request user input for a tool call
230 UserInputRequired {
231 id: String,
232 request: UserInputRequest,
233 },
234
235 /// Authoritative API conversation state from the engine session.
236 ///
237 /// The UI receives granular display events, but those are not always a
238 /// lossless representation of the API transcript. DeepSeek can emit
239 /// reasoning directly followed by tool calls without a visible assistant
240 /// text block, and that assistant message still has to be persisted for
241 /// later `reasoning_content` replay.
242 SessionUpdated {
243 messages: Vec<Message>,
244 system_prompt: Option<SystemPrompt>,
245 model: String,
246 workspace: PathBuf,
247 },
248
249 /// Request user decision after sandbox denial
250 #[allow(dead_code)]
251 ElevationRequired {
252 tool_id: String,
253 tool_name: String,
254 command: Option<String>,
255 denial_reason: String,
256 blocked_network: bool,
257 blocked_write: bool,
258 },
259 }
260
261 impl Event {
262 /// Create an error event from a categorized envelope. The envelope's own
263 /// `recoverable` flag controls whether the UI flips into offline mode.
264 pub fn error(envelope: ErrorEnvelope) -> Self {
265 let recoverable = envelope.recoverable;
266 Event::Error {
267 envelope,
268 recoverable,
269 }
270 }
271
272 /// Create a new status event
273 pub fn status(message: impl Into<String>) -> Self {
274 Event::Status {
275 message: message.into(),
276 }
277 }
278 }
279
279 lines RUST