返回 CodeWhale
ops.rs
根目录 / crates / tui / src / core / ops.rs
1 //! Operations submitted by the UI to the core engine.
2 //!
3 //! These operations flow from the TUI to the engine via a channel,
4 //! allowing the UI to remain responsive while the engine processes requests.
5
6 use crate::compaction::CompactionConfig;
7 use crate::config::ApiProvider;
8 use crate::models::{Message, SystemPrompt};
9 use crate::route_runtime::ResolvedRuntimeRoute;
10 use crate::tools::goal::GoalStatus;
11 use crate::tui::app::AppMode;
12 use crate::tui::approval::ApprovalMode;
13 use codewhale_protocol::runtime::DynamicToolSpec;
14 use std::path::PathBuf;
15
16 /// Prefix used for tool-call ids created by local composer shell shortcuts.
17 pub const USER_SHELL_TOOL_ID_PREFIX: &str = "user_shell_";
18
19 /// Snapshot of session state for saving to disk.
20 /// Returned by `Op::GetSessionSnapshot` via a oneshot channel.
21 #[derive(Debug, Clone)]
22 pub struct SessionSnapshot {
23 pub messages: Vec<Message>,
24 pub total_tokens: u64,
25 pub model: String,
26 /// Generic provider kind retained for serialized compatibility.
27 pub model_provider: String,
28 /// Exact non-secret configured provider key.
29 pub model_provider_id: Option<String>,
30 pub workspace: PathBuf,
31 pub system_prompt: Option<SystemPrompt>,
32 pub mode: String,
33 }
34
35 /// Provider request runtime state surfaced by `/provider`.
36 /// Returned by `Op::GetProviderRuntimeStatus` via a oneshot channel.
37 #[derive(Debug, Clone, PartialEq, Eq)]
38 pub struct ProviderRuntimeStatus {
39 pub provider: ApiProvider,
40 pub request_concurrency_limit: Option<usize>,
41 pub active_provider_requests: usize,
42 }
43
44 /// Result of rebuilding the engine-owned MCP pool in process.
45 pub type McpReloadResult = Result<crate::mcp::McpManagerSnapshot, String>;
46
47 /// Origin of text being introduced as a user-role turn.
48 ///
49 /// Chat providers force several runtime/control-plane signals through
50 /// `role = "user"` for compatibility, so role alone is not authority.
51 #[allow(dead_code)] // Some origins are reserved for ingestion sites landing after the first gate.
52 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
53 pub enum UserInputProvenance {
54 /// Text typed or submitted through the active UI/API input boundary.
55 ExternalUser,
56 /// Runtime-generated continuation, diagnostic, or tool feedback.
57 Runtime,
58 /// Completion/event text from a child worker or sub-agent handoff.
59 SubAgentHandoff,
60 /// Text restored from a saved/imported transcript.
61 ImportedTranscript,
62 /// Text recalled from memory or another persisted source.
63 MemoryRecall,
64 /// Assistant-authored text that is shaped like a user response.
65 AssistantGenerated,
66 }
67
68 impl UserInputProvenance {
69 pub fn as_str(self) -> &'static str {
70 match self {
71 Self::ExternalUser => "external_user",
72 Self::Runtime => "runtime",
73 Self::SubAgentHandoff => "subagent_handoff",
74 Self::ImportedTranscript => "imported_transcript",
75 Self::MemoryRecall => "memory_recall",
76 Self::AssistantGenerated => "assistant_generated",
77 }
78 }
79
80 pub fn can_authorize_work(self) -> bool {
81 matches!(self, Self::ExternalUser)
82 }
83 }
84
85 /// Operations that can be submitted to the engine.
86 #[derive(Debug)]
87 pub enum Op {
88 /// Send a message to the AI
89 SendMessage {
90 content: String,
91 mode: AppMode,
92 /// Exact, structurally resolved route authority for this turn. The
93 /// engine activates its client before mutating turn state; injected
94 /// engines may use their already-supplied client with the same receipt.
95 route: Box<ResolvedRuntimeRoute>,
96 /// Compaction policy derived from the same provider route. Carrying it
97 /// atomically avoids a model/limit mismatch before `SendMessage`.
98 compaction: Box<CompactionConfig>,
99 goal_objective: Option<String>,
100 goal_token_budget: Option<u32>,
101 goal_status: GoalStatus,
102 /// Reasoning-effort tier: `"off" | "low" | "medium" | "high" | "max"`.
103 /// `None` lets the provider apply its default.
104 reasoning_effort: Option<String>,
105 /// True when the user selected auto thinking, even though the UI sends
106 /// a concrete per-turn value to the model API.
107 reasoning_effort_auto: bool,
108 /// True when the user selected auto model routing.
109 auto_model: bool,
110 allow_shell: bool,
111 trust_mode: bool,
112 auto_approve: bool,
113 approval_mode: ApprovalMode,
114 translation_enabled: bool,
115 /// Tool restriction from custom slash command frontmatter.
116 /// `None` means the current turn may use the normal tool set.
117 allowed_tools: Option<Vec<String>>,
118 /// Runtime-supplied tools available only for this turn.
119 dynamic_tools: Vec<DynamicToolSpec>,
120 /// Hook executor for control-plane hooks.
121 /// `ToolCallBefore` hooks may deny a tool call with exit code 2.
122 hook_executor: Option<std::sync::Arc<crate::hooks::HookExecutor>>,
123 verbosity: Option<String>,
124 /// Structural input origin. This gates whether the turn may inherit
125 /// YOLO/auto-approval authority; user-shaped text is not enough.
126 provenance: UserInputProvenance,
127 },
128
129 /// Re-check and dispatch an interactive goal continuation when this
130 /// operation reaches the front of the engine queue. Keeping this distinct
131 /// from `SendMessage` prevents a queued `/goal pause` or `/goal clear`
132 /// from being overwritten by a stale synthetic Active snapshot.
133 ContinueGoal {
134 /// Runtime-supplied tools remain available across the synthetic turn
135 /// that continues the same logical goal run.
136 dynamic_tools: Vec<DynamicToolSpec>,
137 /// Opaque identity for an engine-owned synthetic continuation. Direct
138 /// callers use `None`; the engine uses `Some` to coalesce one token
139 /// across capacity-waiting, enqueued, and running-adjacent states.
140 engine_schedule_id: Option<u64>,
141 },
142
143 /// Execute a user-submitted composer shell command (`! <command>`) without
144 /// sending a model turn. This still routes through `exec_shell`, approval,
145 /// sandbox, and command-safety handling.
146 RunShellCommand {
147 command: String,
148 mode: AppMode,
149 allow_shell: bool,
150 trust_mode: bool,
151 auto_approve: bool,
152 approval_mode: ApprovalMode,
153 },
154
155 /// Set the runtime goal status without dispatching a model turn. Used by
156 /// `/goal pause`, `/goal resume`, `/goal clear`, etc. so the engine's
157 /// `SharedGoalState` learns the new status immediately and a queued
158 /// continuation doesn't overwrite it back to Active.
159 SetGoalStatus {
160 status: GoalStatus,
161 /// When `true`, clear the objective entirely (`/goal clear`).
162 clear: bool,
163 },
164
165 /// Cancel the current request
166 #[allow(dead_code)]
167 CancelRequest,
168
169 /// Approve a tool call that requires permission
170 #[allow(dead_code)]
171 ApproveToolCall { id: String },
172
173 /// Deny a tool call that requires permission
174 #[allow(dead_code)]
175 DenyToolCall { id: String },
176
177 /// Spawn a sub-agent
178 #[allow(dead_code)]
179 SpawnSubAgent { prompt: String },
180
181 /// Describe the exact request the next turn would send, without
182 /// sending it (`/preview-request`, #1004).
183 ///
184 /// Handled by the engine because only the engine can rebuild the current
185 /// tool catalog, MCP state, mode, gates, permission posture, and resolved
186 /// route. Pure inspection: it adds no message, no turn, and no tool call.
187 PreviewOutboundRequest {
188 inputs: Box<crate::core::engine::preview::PreviewRequestInputs>,
189 /// Render the manifest as JSON instead of the human-readable table.
190 json: bool,
191 /// Explicit disclosure of the base prompt only; effective system text
192 /// remains protected behind hashes.
193 base_prompt_only: bool,
194 },
195
196 /// List current sub-agents and their status
197 ListSubAgents,
198
199 /// Cancel a running sub-agent by id or session name.
200 CancelSubAgent { agent_id: String },
201
202 /// Change the operating mode
203 #[allow(dead_code)]
204 ChangeMode {
205 mode: AppMode,
206 allow_shell: bool,
207 trust_mode: bool,
208 auto_approve: bool,
209 approval_mode: ApprovalMode,
210 configured_sandbox_mode: Option<String>,
211 },
212
213 /// Update the model being used and refresh stable prompt context.
214 #[allow(dead_code)]
215 SetModel {
216 model: String,
217 mode: AppMode,
218 route_limits: Option<codewhale_config::route::RouteLimits>,
219 },
220
221 /// Update auto-compaction settings
222 SetCompaction { config: CompactionConfig },
223
224 /// Replace the live user permission rules without clearing session-only
225 /// approvals.
226 SetPermissionRuleset {
227 ruleset: codewhale_execpolicy::Ruleset,
228 },
229
230 /// Update the SSE idle timeout used for subsequent streamed turns.
231 SetStreamChunkTimeout { timeout_secs: u64 },
232
233 /// Update sub-agent runtime controls for subsequent turns.
234 SetSubagentRuntimeConfig {
235 enabled: bool,
236 max_subagents: usize,
237 launch_concurrency: usize,
238 max_spawn_depth: u32,
239 api_timeout_secs: u64,
240 heartbeat_timeout_secs: u64,
241 },
242
243 /// Replace the engine's merged Fleet roster after the setup wizard saves a
244 /// project or personal profile. Subsequent turns can use the new role
245 /// immediately instead of requiring an application restart.
246 SetFleetRoster {
247 roster: std::sync::Arc<crate::fleet::roster::FleetRoster>,
248 },
249
250 /// Sync engine session state (used for resume/load)
251 SyncSession {
252 session_id: Option<String>,
253 messages: Vec<Message>,
254 system_prompt: Option<SystemPrompt>,
255 system_prompt_override: bool,
256 model: String,
257 workspace: PathBuf,
258 mode: AppMode,
259 },
260
261 /// Run context compaction on one exact, structurally resolved provider
262 /// route with policy derived from that same descriptor.
263 CompactContext {
264 route: Box<ResolvedRuntimeRoute>,
265 compaction: Box<CompactionConfig>,
266 },
267
268 /// Get a snapshot of the current session state (messages, tokens, etc.)
269 /// for saving to disk. Returns the result via the oneshot sender so
270 /// the caller doesn't have to compete with the SSE event stream.
271 GetSessionSnapshot {
272 tx: std::sync::Arc<std::sync::Mutex<Option<tokio::sync::oneshot::Sender<SessionSnapshot>>>>,
273 },
274
275 /// Get active provider request concurrency state for readiness surfaces.
276 GetProviderRuntimeStatus {
277 tx: std::sync::Arc<
278 std::sync::Mutex<Option<tokio::sync::oneshot::Sender<ProviderRuntimeStatus>>>,
279 >,
280 },
281
282 /// Force the engine-owned MCP config/catalog to reload and reconnect.
283 /// The returned snapshot is taken from that same live pool.
284 ReloadMcp {
285 config_path: PathBuf,
286 tx: std::sync::Arc<std::sync::Mutex<Option<tokio::sync::oneshot::Sender<McpReloadResult>>>>,
287 },
288
289 /// Run agent-driven context purging.
290 PurgeContext,
291
292 /// Edit the last user message: remove the last user+assistant exchange
293 /// from the session, then re-send with the new content.
294 #[allow(dead_code)]
295 EditLastTurn { new_message: String },
296
297 /// Enable or disable the background advisor watcher for this session.
298 /// When enabled, a fire-and-forget background task runs after each turn
299 /// that contained tool calls and emits an `Event::AdvisoryNote` with
300 /// concise observations. (#3982)
301 SetAdvisorEnabled { enabled: bool },
302
303 /// Shutdown the engine
304 Shutdown,
305 }
306
306 lines RUST