mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-27 08:00:17 +00:00
Add Codex-inspired patterns for improved agent architecture: **Task Scheduler (Phase 1 & 4)** - New Task enum with Job, ToolExec, Background variants - TaskHandler trait for custom background tasks - Scheduler.spawn_subtask() and spawn_batch() for parallel execution - Worker executes multiple tools in parallel via futures::join_all **Tool Approval System (Phase 6)** - Tool.requires_approval() method (default false for sandboxed tools) - HttpTool marked as requiring approval (external network) - MCP protocol annotations: destructive_hint, side_effects_hint **Ratatui TUI CLI (Phase 2)** - Replace blocking stdin with event-driven TUI - ChatComposer with history navigation and tab completion - ApprovalOverlay modal with y/n/a keyboard shortcuts - Raw mode with proper terminal cleanup **Session/Turn Model (Phase 3)** - Session, Thread, Turn structs for conversation tracking - Submission enum for user input, approvals, undo, interrupt - UndoManager with checkpoint-based undo/redo **Context Compaction (Phase 5)** - ContextMonitor with token estimation and threshold checks - CompactionStrategy: Summarize, Truncate, MoveToWorkspace - ContextCompactor writes summaries to workspace daily logs Co-Authored-By: Claude Opus 4.5 <[email protected]>
146 lines
3.8 KiB
Rust
146 lines
3.8 KiB
Rust
//! Approval overlay modal.
|
|
|
|
use uuid::Uuid;
|
|
|
|
/// A request for user approval before executing a tool.
|
|
#[derive(Debug, Clone)]
|
|
pub struct ApprovalRequest {
|
|
/// Unique ID for this request.
|
|
pub id: Uuid,
|
|
/// Name of the tool requesting approval.
|
|
pub tool_name: String,
|
|
/// Description of what the tool will do.
|
|
pub description: String,
|
|
/// Parameters being passed to the tool.
|
|
pub parameters: serde_json::Value,
|
|
/// Whether this is a destructive operation.
|
|
pub destructive: bool,
|
|
}
|
|
|
|
impl ApprovalRequest {
|
|
/// Create a new approval request.
|
|
pub fn new(
|
|
tool_name: impl Into<String>,
|
|
description: impl Into<String>,
|
|
parameters: serde_json::Value,
|
|
) -> Self {
|
|
Self {
|
|
id: Uuid::new_v4(),
|
|
tool_name: tool_name.into(),
|
|
description: description.into(),
|
|
parameters,
|
|
destructive: false,
|
|
}
|
|
}
|
|
|
|
/// Mark as destructive operation.
|
|
pub fn destructive(mut self) -> Self {
|
|
self.destructive = true;
|
|
self
|
|
}
|
|
}
|
|
|
|
/// Current selection in the approval overlay.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum ApprovalSelection {
|
|
/// Yes, approve this action.
|
|
Yes,
|
|
/// No, deny this action.
|
|
No,
|
|
/// Always approve this tool (for this session).
|
|
Always,
|
|
}
|
|
|
|
impl ApprovalSelection {
|
|
/// Get the next selection (cycling).
|
|
pub fn next(self) -> Self {
|
|
match self {
|
|
Self::Yes => Self::No,
|
|
Self::No => Self::Always,
|
|
Self::Always => Self::Yes,
|
|
}
|
|
}
|
|
|
|
/// Get the previous selection (cycling).
|
|
pub fn prev(self) -> Self {
|
|
match self {
|
|
Self::Yes => Self::Always,
|
|
Self::No => Self::Yes,
|
|
Self::Always => Self::No,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Approval overlay state.
|
|
pub struct ApprovalOverlay {
|
|
/// The request being shown.
|
|
pub request: ApprovalRequest,
|
|
/// Current selection.
|
|
pub selection: ApprovalSelection,
|
|
}
|
|
|
|
impl ApprovalOverlay {
|
|
/// Create a new approval overlay.
|
|
pub fn new(request: ApprovalRequest) -> Self {
|
|
Self {
|
|
request,
|
|
selection: ApprovalSelection::Yes,
|
|
}
|
|
}
|
|
|
|
/// Move selection left.
|
|
pub fn select_prev(&mut self) {
|
|
self.selection = self.selection.prev();
|
|
}
|
|
|
|
/// Move selection right.
|
|
pub fn select_next(&mut self) {
|
|
self.selection = self.selection.next();
|
|
}
|
|
|
|
/// Handle keyboard shortcut.
|
|
pub fn handle_shortcut(&mut self, c: char) -> Option<bool> {
|
|
match c.to_ascii_lowercase() {
|
|
'y' => Some(true),
|
|
'n' => Some(false),
|
|
'a' => {
|
|
self.selection = ApprovalSelection::Always;
|
|
Some(true)
|
|
}
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// Confirm the current selection.
|
|
pub fn confirm(&self) -> (bool, bool) {
|
|
match self.selection {
|
|
ApprovalSelection::Yes => (true, false),
|
|
ApprovalSelection::No => (false, false),
|
|
ApprovalSelection::Always => (true, true),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_approval_selection_cycle() {
|
|
let sel = ApprovalSelection::Yes;
|
|
assert_eq!(sel.next(), ApprovalSelection::No);
|
|
assert_eq!(sel.next().next(), ApprovalSelection::Always);
|
|
assert_eq!(sel.next().next().next(), ApprovalSelection::Yes);
|
|
}
|
|
|
|
#[test]
|
|
fn test_approval_shortcuts() {
|
|
let request = ApprovalRequest::new("test", "Test operation", serde_json::json!({}));
|
|
let mut overlay = ApprovalOverlay::new(request);
|
|
|
|
assert_eq!(overlay.handle_shortcut('y'), Some(true));
|
|
assert_eq!(overlay.handle_shortcut('n'), Some(false));
|
|
assert_eq!(overlay.handle_shortcut('x'), None);
|
|
}
|
|
}
|