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    /// PSP-5 Phase 8: Budget envelope updated (step/cost consumed)
231    BudgetUpdated {
232        steps_used: u32,
233        max_steps: Option<u32>,
234        cost_used_usd: f64,
235        max_cost_usd: Option<f64>,
236        revisions_used: u32,
237        max_revisions: Option<u32>,
238    },
239
240    /// PSP-5 Phase 8: Plan revision superseded by a new revision
241    PlanRevised {
242        revision_id: String,
243        sequence: u32,
244        reason: String,
245        node_count: usize,
246    },
247
248    /// PSP-5 Phase 10: File deleted via safe delete operation
249    FileDeleted { node_id: String, path: String },
250
251    /// PSP-5 Phase 10: File moved/renamed via safe move operation
252    FileMoved {
253        node_id: String,
254        from: String,
255        to: String,
256    },
257}
258
259/// Node status for TUI display (mirrors NodeState but simplified)
260#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
261pub enum NodeStatus {
262    /// Queued, waiting for dependencies
263    Queued,
264    /// Planning phase (Architect)
265    Planning,
266    Pending,
267    /// Active coding / implementation phase
268    Coding,
269    Running,
270    Verifying,
271    /// PSP-5: Node is retrying after a failed verification
272    Retrying,
273    /// PSP-5 Phase 7: Sheaf consistency check underway
274    SheafCheck,
275    /// PSP-5 Phase 7: Committing stable state to ledger
276    Committing,
277    Completed,
278    Failed,
279    Escalated,
280    Aborted,
281}
282
283impl From<crate::types::NodeState> for NodeStatus {
284    fn from(state: crate::types::NodeState) -> Self {
285        use crate::types::NodeState;
286        match state {
287            NodeState::TaskQueued => NodeStatus::Queued,
288            NodeState::Planning => NodeStatus::Planning,
289            NodeState::Coding => NodeStatus::Coding,
290            NodeState::Verifying => NodeStatus::Verifying,
291            NodeState::Retry => NodeStatus::Retrying,
292            NodeState::SheafCheck => NodeStatus::SheafCheck,
293            NodeState::Committing => NodeStatus::Committing,
294            NodeState::Escalated => NodeStatus::Escalated,
295            NodeState::Completed => NodeStatus::Completed,
296            NodeState::Failed => NodeStatus::Failed,
297            NodeState::Aborted => NodeStatus::Aborted,
298            NodeState::Superseded => NodeStatus::Completed,
299        }
300    }
301}
302
303/// Type of action requiring approval
304#[derive(Debug, Clone, Serialize, Deserialize)]
305pub enum ActionType {
306    /// File creation or modification
307    FileWrite { path: String },
308    /// Shell command execution
309    Command { command: String },
310    /// Multiple files in a sub-graph
311    SubGraph { node_count: usize },
312    /// Project initialization (with editable name)
313    ProjectInit {
314        command: String,
315        suggested_name: String,
316    },
317    /// PSP-5: Multi-file artifact bundle write
318    BundleWrite {
319        /// Node that produced the bundle
320        node_id: String,
321        /// Files being written or modified
322        files: Vec<String>,
323    },
324}
325
326/// Actions sent from TUI to Orchestrator
327#[derive(Debug, Clone, Serialize, Deserialize)]
328pub enum AgentAction {
329    /// Approve a pending request
330    Approve { request_id: String },
331    /// Approve with an edited value (e.g., project name)
332    ApproveWithEdit {
333        request_id: String,
334        edited_value: String,
335    },
336    /// Reject a pending request
337    Reject {
338        request_id: String,
339        reason: Option<String>,
340    },
341    /// Pause orchestration
342    Pause,
343    /// Resume orchestration
344    Resume,
345    /// Abort the entire session
346    Abort,
347    /// PSP-5 Phase 7: Request correction with structured user feedback
348    RequestCorrection {
349        request_id: String,
350        feedback: String,
351    },
352}
353
354/// Channel types for agent communication
355pub mod channel {
356    use super::{AgentAction, AgentEvent};
357    use tokio::sync::mpsc;
358
359    /// Sender for AgentEvents (Orchestrator → TUI)
360    pub type EventSender = mpsc::UnboundedSender<AgentEvent>;
361    /// Receiver for AgentEvents (Orchestrator → TUI)
362    pub type EventReceiver = mpsc::UnboundedReceiver<AgentEvent>;
363
364    /// Sender for AgentActions (TUI → Orchestrator)
365    pub type ActionSender = mpsc::UnboundedSender<AgentAction>;
366    /// Receiver for AgentActions (TUI → Orchestrator)
367    pub type ActionReceiver = mpsc::UnboundedReceiver<AgentAction>;
368
369    /// Create event channel (Orchestrator → TUI)
370    pub fn event_channel() -> (EventSender, EventReceiver) {
371        mpsc::unbounded_channel()
372    }
373
374    /// Create action channel (TUI → Orchestrator)
375    pub fn action_channel() -> (ActionSender, ActionReceiver) {
376        mpsc::unbounded_channel()
377    }
378}