perspt-core 0.5.8

Core types and LLM provider abstraction for Perspt
Documentation
//! Agent Events
//!
//! Event types for communication between the SRBN Orchestrator and TUI.
//! Enables async, decoupled control flow for interactive agent sessions.

use serde::{Deserialize, Serialize};

/// PSP-5 Phase 4: Per-plugin readiness summary for session-start reporting
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginReadiness {
    /// Plugin name (e.g. "rust", "python")
    pub plugin_name: String,
    /// Verifier stages that have at least one available tool
    pub available_stages: Vec<String>,
    /// Verifier stages where only a fallback or no tool is available
    pub degraded_stages: Vec<String>,
    /// LSP status description
    pub lsp_status: String,
}

/// Events emitted by the Orchestrator for TUI consumption
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AgentEvent {
    /// Task status changed
    TaskStatusChanged { node_id: String, status: NodeStatus },

    /// Plan generated by Architect
    PlanGenerated(crate::types::TaskPlan),

    /// Lyapunov energy updated
    EnergyUpdated { node_id: String, energy: f32 },

    /// Log message for display
    Log(String),

    /// Node completed successfully
    NodeCompleted { node_id: String, goal: String },

    /// Approval required before proceeding
    ApprovalRequest {
        request_id: String,
        node_id: String,
        action_type: ActionType,
        description: String,
        diff: Option<String>,
    },

    /// Orchestration finished
    Complete { success: bool, message: String },

    /// Error occurred
    Error(String),

    // =========================================================================
    // PSP-5: Lifecycle Events
    // =========================================================================
    /// PSP-5: Plan ready after sheafification with detected plugins and execution mode
    PlanReady {
        nodes: usize,
        plugins: Vec<String>,
        execution_mode: String,
    },

    /// PSP-5: Node selected for execution
    NodeSelected {
        node_id: String,
        goal: String,
        node_class: String,
    },

    /// PSP-5: Deterministic fallback planner activated
    FallbackPlanner { reason: String },

    /// PSP-5: Verification completed for a node
    VerificationComplete {
        node_id: String,
        syntax_ok: bool,
        build_ok: bool,
        tests_ok: bool,
        lint_ok: bool,
        diagnostics_count: usize,
        tests_passed: usize,
        tests_failed: usize,
        energy: f32,
        /// PSP-5 Phase 7: Full energy component breakdown
        energy_components: crate::types::EnergyComponents,
        /// PSP-5 Phase 7: Per-stage verification outcomes with sensor status
        stage_outcomes: Vec<crate::types::StageOutcome>,
        /// PSP-5 Phase 7: Whether verification ran in degraded mode
        degraded: bool,
        /// PSP-5 Phase 7: Human-readable reasons for each degraded stage
        degraded_reasons: Vec<String>,
        /// PSP-5 Phase 7: Summary suitable for display
        summary: String,
        /// PSP-5 Phase 7: Node class for display context
        node_class: String,
    },

    /// PSP-5: Artifact bundle applied to workspace
    BundleApplied {
        node_id: String,
        files_created: Vec<String>,
        files_modified: Vec<String>,
        /// PSP-5 Phase 7: Number of write (new file) operations
        writes_count: usize,
        /// PSP-5 Phase 7: Number of diff (patch) operations
        diffs_count: usize,
        /// PSP-5 Phase 7: Node class for display context
        node_class: String,
    },

    /// PSP-5 Phase 4: A sensor fell back to an alternative tool
    SensorFallback {
        node_id: String,
        stage: String,
        primary: String,
        actual: String,
        reason: String,
    },

    /// PSP-5 Phase 4: Verification completed with degraded stages
    DegradedVerification {
        node_id: String,
        degraded_stages: Vec<String>,
        stability_blocked: bool,
    },

    /// PSP-5 Phase 5: Non-convergence classified with a repair action
    EscalationClassified {
        node_id: String,
        category: String,
        action: String,
    },

    /// PSP-5 Phase 5: Sheaf validation completed for a node
    SheafValidationComplete {
        node_id: String,
        validators_run: usize,
        failures: usize,
        v_sheaf: f32,
    },

    /// PSP-5 Phase 5: Graph rewrite applied (split, interface insertion, replan)
    GraphRewriteApplied {
        trigger_node: String,
        action: String,
        nodes_affected: usize,
    },

    /// PSP-5 Phase 6: Provisional branch created for speculative child work
    BranchCreated {
        branch_id: String,
        node_id: String,
        parent_node_id: String,
    },

    /// PSP-5 Phase 6: Interface sealed for a node (dependents may proceed)
    InterfaceSealed {
        node_id: String,
        sealed_paths: Vec<String>,
        seal_hash: String,
    },

    /// PSP-5 Phase 6: Provisional branches flushed due to parent failure
    BranchFlushed {
        parent_node_id: String,
        flushed_branch_ids: Vec<String>,
        reason: String,
    },

    /// PSP-5 Phase 6: Blocked dependent unblocked after parent seal
    DependentUnblocked {
        child_node_id: String,
        parent_node_id: String,
    },

    /// PSP-5 Phase 6: Provisional branch merged into committed state
    BranchMerged { branch_id: String, node_id: String },

    /// PSP-5 Phase 3: Context assembly degraded (budget exceeded or missing artifacts)
    ContextDegraded {
        node_id: String,
        budget_exceeded: bool,
        missing_owned_files: Vec<String>,
        included_file_count: usize,
        total_bytes: usize,
        reason: String,
    },

    /// PSP-5 Phase 3: Context blocked — required structural context is untrustworthy.
    /// The node SHALL NOT proceed silently (PSP-5 §3 requirement).
    ContextBlocked {
        node_id: String,
        missing_owned_files: Vec<String>,
        reason: String,
    },

    /// PSP-5 Phase 3: Structural dependency pre-check failed — a required
    /// dependency only has prose summaries, no machine-verifiable digests.
    StructuralDependencyMissing {
        node_id: String,
        dependency_node_id: String,
        reason: String,
    },

    /// PSP-5 Phase 1/4: Model fallback triggered for a tier after structured-output failure
    ModelFallback {
        node_id: String,
        tier: String,
        primary_model: String,
        fallback_model: String,
        reason: String,
    },

    /// PSP-5 Phase 3: Context provenance drift detected on resume
    ProvenanceDrift {
        node_id: String,
        missing_files: Vec<String>,
        reason: String,
    },

    /// PSP-5 Phase 4: Tool readiness snapshot captured at session start
    ToolReadiness {
        /// Per-plugin readiness: (plugin_name, available_stages, degraded_stages, lsp_status)
        plugins: Vec<PluginReadiness>,
        /// Verifier strictness mode in effect
        strictness: String,
    },

    /// PSP-5 Phase 8: Budget envelope updated (step/cost consumed)
    BudgetUpdated {
        steps_used: u32,
        max_steps: Option<u32>,
        cost_used_usd: f64,
        max_cost_usd: Option<f64>,
        revisions_used: u32,
        max_revisions: Option<u32>,
    },

    /// PSP-5 Phase 8: Plan revision superseded by a new revision
    PlanRevised {
        revision_id: String,
        sequence: u32,
        reason: String,
        node_count: usize,
    },

    /// PSP-5 Phase 10: File deleted via safe delete operation
    FileDeleted { node_id: String, path: String },

    /// PSP-5 Phase 10: File moved/renamed via safe move operation
    FileMoved {
        node_id: String,
        from: String,
        to: String,
    },
}

/// Node status for TUI display (mirrors NodeState but simplified)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum NodeStatus {
    /// Queued, waiting for dependencies
    Queued,
    /// Planning phase (Architect)
    Planning,
    Pending,
    /// Active coding / implementation phase
    Coding,
    Running,
    Verifying,
    /// PSP-5: Node is retrying after a failed verification
    Retrying,
    /// PSP-5 Phase 7: Sheaf consistency check underway
    SheafCheck,
    /// PSP-5 Phase 7: Committing stable state to ledger
    Committing,
    Completed,
    Failed,
    Escalated,
    Aborted,
}

impl From<crate::types::NodeState> for NodeStatus {
    fn from(state: crate::types::NodeState) -> Self {
        use crate::types::NodeState;
        match state {
            NodeState::TaskQueued => NodeStatus::Queued,
            NodeState::Planning => NodeStatus::Planning,
            NodeState::Coding => NodeStatus::Coding,
            NodeState::Verifying => NodeStatus::Verifying,
            NodeState::Retry => NodeStatus::Retrying,
            NodeState::SheafCheck => NodeStatus::SheafCheck,
            NodeState::Committing => NodeStatus::Committing,
            NodeState::Escalated => NodeStatus::Escalated,
            NodeState::Completed => NodeStatus::Completed,
            NodeState::Failed => NodeStatus::Failed,
            NodeState::Aborted => NodeStatus::Aborted,
            NodeState::Superseded => NodeStatus::Completed,
        }
    }
}

/// Type of action requiring approval
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ActionType {
    /// File creation or modification
    FileWrite { path: String },
    /// Shell command execution
    Command { command: String },
    /// Multiple files in a sub-graph
    SubGraph { node_count: usize },
    /// Project initialization (with editable name)
    ProjectInit {
        command: String,
        suggested_name: String,
    },
    /// PSP-5: Multi-file artifact bundle write
    BundleWrite {
        /// Node that produced the bundle
        node_id: String,
        /// Files being written or modified
        files: Vec<String>,
    },
}

/// Actions sent from TUI to Orchestrator
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AgentAction {
    /// Approve a pending request
    Approve { request_id: String },
    /// Approve with an edited value (e.g., project name)
    ApproveWithEdit {
        request_id: String,
        edited_value: String,
    },
    /// Reject a pending request
    Reject {
        request_id: String,
        reason: Option<String>,
    },
    /// Pause orchestration
    Pause,
    /// Resume orchestration
    Resume,
    /// Abort the entire session
    Abort,
    /// PSP-5 Phase 7: Request correction with structured user feedback
    RequestCorrection {
        request_id: String,
        feedback: String,
    },
}

/// Channel types for agent communication
pub mod channel {
    use super::{AgentAction, AgentEvent};
    use tokio::sync::mpsc;

    /// Sender for AgentEvents (Orchestrator → TUI)
    pub type EventSender = mpsc::UnboundedSender<AgentEvent>;
    /// Receiver for AgentEvents (Orchestrator → TUI)
    pub type EventReceiver = mpsc::UnboundedReceiver<AgentEvent>;

    /// Sender for AgentActions (TUI → Orchestrator)
    pub type ActionSender = mpsc::UnboundedSender<AgentAction>;
    /// Receiver for AgentActions (TUI → Orchestrator)
    pub type ActionReceiver = mpsc::UnboundedReceiver<AgentAction>;

    /// Create event channel (Orchestrator → TUI)
    pub fn event_channel() -> (EventSender, EventReceiver) {
        mpsc::unbounded_channel()
    }

    /// Create action channel (TUI → Orchestrator)
    pub fn action_channel() -> (ActionSender, ActionReceiver) {
        mpsc::unbounded_channel()
    }
}