Skip to main content

zagens_core/engine/
runtime.rs

1//! Core `Engine` struct (M7 strangler step).
2//!
3//! Holds lean config, host trait objects, channels, and session state.
4//! Platform-specific op dispatch and tool wiring live in the sidecar via
5//! [`super::platform_ext::EnginePlatformExt`].
6
7use std::sync::{Arc, Mutex as StdMutex};
8
9use tokio::sync::{RwLock, mpsc};
10use tokio_util::sync::CancellationToken;
11
12use crate::capacity::CapacityController;
13use crate::chat::LlmClient;
14use crate::coherence::CoherenceState;
15use crate::engine::approval::{ApprovalDecision, UserInputDecision};
16use crate::engine::config::EngineConfig;
17use crate::engine::hosts::{
18    LspHost, SandboxHost, SeamHost, ShellHost, TopicMemoryHost, WorkshopHost,
19};
20use crate::engine::op::Op;
21use crate::engine::scratchpad_state::ScratchpadStepState;
22use crate::events::Event;
23use crate::lsp::DiagnosticBlock;
24use crate::session::Session;
25
26/// The core engine that processes operations and emits events.
27pub struct Engine<P, R> {
28    pub config: EngineConfig,
29    /// Tui-only extension config + concrete subsystem handles (type-erased).
30    /// Wrapped in `Option` so the op loop can [`Option::take`] it during dispatch
31    /// without aliasing `&mut self`.
32    pub ext: Option<Box<dyn crate::engine::platform_ext::EnginePlatformExt<P, R>>>,
33    pub deepseek_client: Option<Arc<dyn LlmClient>>,
34    pub deepseek_client_error: Option<String>,
35    pub api_key_env_only_recovery: Option<String>,
36    pub session: Session,
37    pub shell: Box<dyn ShellHost>,
38    pub rx_op: mpsc::Receiver<Op>,
39    pub tx_approval: mpsc::Sender<ApprovalDecision<P>>,
40    pub rx_approval: mpsc::Receiver<ApprovalDecision<P>>,
41    pub rx_user_input: mpsc::Receiver<UserInputDecision<R>>,
42    pub rx_steer: mpsc::Receiver<String>,
43    pub tx_event: mpsc::Sender<Event>,
44    pub cancel_token: CancellationToken,
45    pub shared_cancel_token: Arc<StdMutex<CancellationToken>>,
46    pub tool_exec_lock: Arc<RwLock<()>>,
47    pub capacity_controller: CapacityController,
48    pub seam: Option<Box<dyn SeamHost>>,
49    pub coherence_state: CoherenceState,
50    pub turn_counter: u64,
51    pub lsp: Arc<dyn LspHost>,
52    pub workshop: Option<Box<dyn WorkshopHost>>,
53    pub sandbox: Box<dyn SandboxHost>,
54    pub pending_lsp_blocks: Vec<DiagnosticBlock>,
55    pub scratchpad_step: ScratchpadStepState,
56    pub scratchpad_run_id: Option<String>,
57    pub scratchpad_summary_injected_this_turn: bool,
58    /// One-shot guard: inject incomplete-audit continue nudge before prose-only turn break.
59    pub scratchpad_audit_continue_injected_this_turn: bool,
60    /// One-shot guard: inject LHT continue nudge before prose-only turn break.
61    pub long_horizon_continue_injected_this_turn: bool,
62    /// "一推到底" (C2): auto-continue rounds consumed this turn — bounds the
63    /// give-up override (`long_horizon.max_auto_continue_rounds`). Reset per
64    /// user message alongside the other per-turn LHT guards.
65    pub long_horizon_auto_continue_rounds: u32,
66    pub topic_memory: Box<dyn TopicMemoryHost>,
67    /// **P2-D overflow recovery**: source-only token budget cap set by
68    /// `try_budget_recompile` when the compiler budget solver successfully
69    /// evicts Volatile / shrinks SemiStatic sources to fit within the context
70    /// window.  On the next `compiler_request_context` call the compiler runs
71    /// `compile_with_budget_override(cap)` instead of `compile()`, applying
72    /// the eviction.  Consumed (set back to `None`) immediately after use so
73    /// it only applies for one request retry.  Reset to `None` at turn start.
74    pub overflow_source_budget_cap: Option<u32>,
75}