Skip to main content

perspt_core/
events.rs

1//! Agent Events
2//!
3//! Event types for communication between the SRBN Orchestrator and TUI.
4//! Enables async, decoupled control flow for interactive agent sessions.
5
6use serde::{Deserialize, Serialize};
7
8/// PSP-5 Phase 4: Per-plugin readiness summary for session-start reporting
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct PluginReadiness {
11    /// Plugin name (e.g. "rust", "python")
12    pub plugin_name: String,
13    /// Verifier stages that have at least one available tool
14    pub available_stages: Vec<String>,
15    /// Verifier stages where only a fallback or no tool is available
16    pub degraded_stages: Vec<String>,
17    /// LSP status description
18    pub lsp_status: String,
19}
20
21/// Events emitted by the Orchestrator for TUI consumption
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub enum AgentEvent {
24    /// Task status changed
25    TaskStatusChanged { node_id: String, status: NodeStatus },
26
27    /// Plan generated by Architect
28    PlanGenerated(crate::types::TaskPlan),
29
30    /// Lyapunov energy updated
31    EnergyUpdated { node_id: String, energy: f32 },
32
33    /// Log message for display
34    Log(String),
35
36    /// Node completed successfully
37    NodeCompleted { node_id: String, goal: String },
38
39    /// Approval required before proceeding
40    ApprovalRequest {
41        request_id: String,
42        node_id: String,
43        action_type: ActionType,
44        description: String,
45        diff: Option<String>,
46    },
47
48    /// Orchestration finished
49    Complete { success: bool, message: String },
50
51    /// Error occurred
52    Error(String),
53
54    // =========================================================================
55    // PSP-5: Lifecycle Events
56    // =========================================================================
57    /// PSP-5: Plan ready after sheafification with detected plugins and execution mode
58    PlanReady {
59        nodes: usize,
60        plugins: Vec<String>,
61        execution_mode: String,
62    },
63
64    /// PSP-5: Node selected for execution
65    NodeSelected {
66        node_id: String,
67        goal: String,
68        node_class: String,
69    },
70
71    /// PSP-5: Deterministic fallback planner activated
72    FallbackPlanner { reason: String },
73
74    /// PSP-5: Verification completed for a node
75    VerificationComplete {
76        node_id: String,
77        syntax_ok: bool,
78        build_ok: bool,
79        tests_ok: bool,
80        lint_ok: bool,
81        diagnostics_count: usize,
82        tests_passed: usize,
83        tests_failed: usize,
84        energy: f32,
85        /// PSP-5 Phase 7: Full energy component breakdown
86        energy_components: crate::types::EnergyComponents,
87        /// PSP-5 Phase 7: Per-stage verification outcomes with sensor status
88        stage_outcomes: Vec<crate::types::StageOutcome>,
89        /// PSP-5 Phase 7: Whether verification ran in degraded mode
90        degraded: bool,
91        /// PSP-5 Phase 7: Human-readable reasons for each degraded stage
92        degraded_reasons: Vec<String>,
93        /// PSP-5 Phase 7: Summary suitable for display
94        summary: String,
95        /// PSP-5 Phase 7: Node class for display context
96        node_class: String,
97    },
98
99    /// PSP-5: Artifact bundle applied to workspace
100    BundleApplied {
101        node_id: String,
102        files_created: Vec<String>,
103        files_modified: Vec<String>,
104        /// PSP-5 Phase 7: Number of write (new file) operations
105        writes_count: usize,
106        /// PSP-5 Phase 7: Number of diff (patch) operations
107        diffs_count: usize,
108        /// PSP-5 Phase 7: Node class for display context
109        node_class: String,
110    },
111
112    /// PSP-5 Phase 4: A sensor fell back to an alternative tool
113    SensorFallback {
114        node_id: String,
115        stage: String,
116        primary: String,
117        actual: String,
118        reason: String,
119    },
120
121    /// PSP-5 Phase 4: Verification completed with degraded stages
122    DegradedVerification {
123        node_id: String,
124        degraded_stages: Vec<String>,
125        stability_blocked: bool,
126    },
127
128    /// PSP-5 Phase 5: Non-convergence classified with a repair action
129    EscalationClassified {
130        node_id: String,
131        category: String,
132        action: String,
133    },
134
135    /// PSP-5 Phase 5: Sheaf validation completed for a node
136    SheafValidationComplete {
137        node_id: String,
138        validators_run: usize,
139        failures: usize,
140        v_sheaf: f32,
141    },
142
143    /// PSP-5 Phase 5: Graph rewrite applied (split, interface insertion, replan)
144    GraphRewriteApplied {
145        trigger_node: String,
146        action: String,
147        nodes_affected: usize,
148    },
149
150    /// PSP-5 Phase 6: Provisional branch created for speculative child work
151    BranchCreated {
152        branch_id: String,
153        node_id: String,
154        parent_node_id: String,
155    },
156
157    /// PSP-5 Phase 6: Interface sealed for a node (dependents may proceed)
158    InterfaceSealed {
159        node_id: String,
160        sealed_paths: Vec<String>,
161        seal_hash: String,
162    },
163
164    /// PSP-5 Phase 6: Provisional branches flushed due to parent failure
165    BranchFlushed {
166        parent_node_id: String,
167        flushed_branch_ids: Vec<String>,
168        reason: String,
169    },
170
171    /// PSP-5 Phase 6: Blocked dependent unblocked after parent seal
172    DependentUnblocked {
173        child_node_id: String,
174        parent_node_id: String,
175    },
176
177    /// PSP-5 Phase 6: Provisional branch merged into committed state
178    BranchMerged { branch_id: String, node_id: String },
179
180    /// PSP-5 Phase 3: Context assembly degraded (budget exceeded or missing artifacts)
181    ContextDegraded {
182        node_id: String,
183        budget_exceeded: bool,
184        missing_owned_files: Vec<String>,
185        included_file_count: usize,
186        total_bytes: usize,
187        reason: String,
188    },
189
190    /// PSP-5 Phase 3: Context blocked — required structural context is untrustworthy.
191    /// The node SHALL NOT proceed silently (PSP-5 §3 requirement).
192    ContextBlocked {
193        node_id: String,
194        missing_owned_files: Vec<String>,
195        reason: String,
196    },
197
198    /// PSP-5 Phase 3: Structural dependency pre-check failed — a required
199    /// dependency only has prose summaries, no machine-verifiable digests.
200    StructuralDependencyMissing {
201        node_id: String,
202        dependency_node_id: String,
203        reason: String,
204    },
205
206    /// PSP-5 Phase 1/4: Model fallback triggered for a tier after structured-output failure
207    ModelFallback {
208        node_id: String,
209        tier: String,
210        primary_model: String,
211        fallback_model: String,
212        reason: String,
213    },
214
215    /// PSP-5 Phase 3: Context provenance drift detected on resume
216    ProvenanceDrift {
217        node_id: String,
218        missing_files: Vec<String>,
219        reason: String,
220    },
221
222    /// PSP-5 Phase 4: Tool readiness snapshot captured at session start
223    ToolReadiness {
224        /// Per-plugin readiness: (plugin_name, available_stages, degraded_stages, lsp_status)
225        plugins: Vec<PluginReadiness>,
226        /// Verifier strictness mode in effect
227        strictness: String,
228    },
229}
230
231/// Node status for TUI display (mirrors NodeState but simplified)
232#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
233pub enum NodeStatus {
234    /// Queued, waiting for dependencies
235    Queued,
236    /// Planning phase (Architect)
237    Planning,
238    Pending,
239    /// Active coding / implementation phase
240    Coding,
241    Running,
242    Verifying,
243    /// PSP-5: Node is retrying after a failed verification
244    Retrying,
245    /// PSP-5 Phase 7: Sheaf consistency check underway
246    SheafCheck,
247    /// PSP-5 Phase 7: Committing stable state to ledger
248    Committing,
249    Completed,
250    Failed,
251    Escalated,
252}
253
254impl From<crate::types::NodeState> for NodeStatus {
255    fn from(state: crate::types::NodeState) -> Self {
256        use crate::types::NodeState;
257        match state {
258            NodeState::TaskQueued => NodeStatus::Queued,
259            NodeState::Planning => NodeStatus::Planning,
260            NodeState::Coding => NodeStatus::Coding,
261            NodeState::Verifying => NodeStatus::Verifying,
262            NodeState::Retry => NodeStatus::Retrying,
263            NodeState::SheafCheck => NodeStatus::SheafCheck,
264            NodeState::Committing => NodeStatus::Committing,
265            NodeState::Escalated => NodeStatus::Escalated,
266            NodeState::Completed => NodeStatus::Completed,
267            NodeState::Failed => NodeStatus::Failed,
268            NodeState::Aborted => NodeStatus::Failed,
269        }
270    }
271}
272
273/// Type of action requiring approval
274#[derive(Debug, Clone, Serialize, Deserialize)]
275pub enum ActionType {
276    /// File creation or modification
277    FileWrite { path: String },
278    /// Shell command execution
279    Command { command: String },
280    /// Multiple files in a sub-graph
281    SubGraph { node_count: usize },
282    /// Project initialization (with editable name)
283    ProjectInit {
284        command: String,
285        suggested_name: String,
286    },
287    /// PSP-5: Multi-file artifact bundle write
288    BundleWrite {
289        /// Node that produced the bundle
290        node_id: String,
291        /// Files being written or modified
292        files: Vec<String>,
293    },
294}
295
296/// Actions sent from TUI to Orchestrator
297#[derive(Debug, Clone, Serialize, Deserialize)]
298pub enum AgentAction {
299    /// Approve a pending request
300    Approve { request_id: String },
301    /// Approve with an edited value (e.g., project name)
302    ApproveWithEdit {
303        request_id: String,
304        edited_value: String,
305    },
306    /// Reject a pending request
307    Reject {
308        request_id: String,
309        reason: Option<String>,
310    },
311    /// Pause orchestration
312    Pause,
313    /// Resume orchestration
314    Resume,
315    /// Abort the entire session
316    Abort,
317    /// PSP-5 Phase 7: Request correction with structured user feedback
318    RequestCorrection {
319        request_id: String,
320        feedback: String,
321    },
322}
323
324/// Channel types for agent communication
325pub mod channel {
326    use super::{AgentAction, AgentEvent};
327    use tokio::sync::mpsc;
328
329    /// Sender for AgentEvents (Orchestrator → TUI)
330    pub type EventSender = mpsc::UnboundedSender<AgentEvent>;
331    /// Receiver for AgentEvents (Orchestrator → TUI)
332    pub type EventReceiver = mpsc::UnboundedReceiver<AgentEvent>;
333
334    /// Sender for AgentActions (TUI → Orchestrator)
335    pub type ActionSender = mpsc::UnboundedSender<AgentAction>;
336    /// Receiver for AgentActions (TUI → Orchestrator)
337    pub type ActionReceiver = mpsc::UnboundedReceiver<AgentAction>;
338
339    /// Create event channel (Orchestrator → TUI)
340    pub fn event_channel() -> (EventSender, EventReceiver) {
341        mpsc::unbounded_channel()
342    }
343
344    /// Create action channel (TUI → Orchestrator)
345    pub fn action_channel() -> (ActionSender, ActionReceiver) {
346        mpsc::unbounded_channel()
347    }
348}