mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-31 08:39:24 +00:00
Implement tool approval, fix tool definition refresh, and wire embeddings
This commit addresses three critical issues from code review: 1. Tool approval enforcement: Tools declaring requires_approval() (shell, http, file write/patch, build_software) now gate execution. Adds PendingApproval struct, session-scoped auto-approved tools set, and approval flow with yes/no/always commands. 2. Tool definition refresh: Tool definitions now refresh each iteration in both chat and job loops, so newly built tools become visible immediately within the same session. 3. Worker tool call handling: Changed respond() to respond_with_tools() when select_tools returns empty, properly executing tool calls instead of formatting them as text. Also includes prior work from the plan: - Wire embeddings provider (OpenAI + NEAR AI) to workspace - Load workspace system prompt (identity files) into LLM context - Route heartbeat notifications through channel manager - Enable auto-context compaction when threshold exceeded - Refactor to config structs (AgentDeps, WorkerDeps, LlmCallRecord) - Fix clippy warnings (saturating_sub, too_many_arguments) Co-Authored-By: Claude Opus 4.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
8af48390a9
commit
2cc9aed364
@@ -146,6 +146,20 @@ pub trait Channel: Send + Sync {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send a proactive message without a prior incoming message.
|
||||
///
|
||||
/// Used for alerts, heartbeat notifications, and other agent-initiated communication.
|
||||
/// The user_id helps target a specific user within the channel.
|
||||
///
|
||||
/// Default implementation does nothing (for channels that don't support broadcast).
|
||||
async fn broadcast(
|
||||
&self,
|
||||
_user_id: &str,
|
||||
_response: OutgoingResponse,
|
||||
) -> Result<(), ChannelError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if the channel is healthy.
|
||||
async fn health_check(&self) -> Result<(), ChannelError>;
|
||||
|
||||
|
||||
@@ -128,6 +128,22 @@ impl Channel for TuiChannel {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn broadcast(
|
||||
&self,
|
||||
_user_id: &str,
|
||||
response: OutgoingResponse,
|
||||
) -> Result<(), ChannelError> {
|
||||
// For TUI, broadcasts appear as regular agent responses with a notification indicator
|
||||
self.event_tx
|
||||
.send(AppEvent::Response(response.content))
|
||||
.await
|
||||
.map_err(|e| ChannelError::SendFailed {
|
||||
name: "tui".to_string(),
|
||||
reason: e.to_string(),
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn health_check(&self) -> Result<(), ChannelError> {
|
||||
// Channel is healthy if we haven't been closed
|
||||
if self.event_tx.is_closed() {
|
||||
|
||||
@@ -89,11 +89,7 @@ fn render_messages(frame: &mut Frame, app: &AppState, area: Rect) {
|
||||
// Calculate scroll - show most recent messages
|
||||
let visible_height = area.height.saturating_sub(2) as usize; // Account for borders
|
||||
let total_lines = lines.len();
|
||||
let scroll_offset = if total_lines > visible_height {
|
||||
total_lines - visible_height
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let scroll_offset = total_lines.saturating_sub(visible_height);
|
||||
|
||||
let text = Text::from(lines);
|
||||
let messages = Paragraph::new(text)
|
||||
|
||||
@@ -97,6 +97,45 @@ impl ChannelManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// Broadcast a message to a specific user on a specific channel.
|
||||
///
|
||||
/// Used for proactive notifications like heartbeat alerts.
|
||||
pub async fn broadcast(
|
||||
&self,
|
||||
channel_name: &str,
|
||||
user_id: &str,
|
||||
response: OutgoingResponse,
|
||||
) -> Result<(), ChannelError> {
|
||||
let channels = self.channels.read().await;
|
||||
if let Some(channel) = channels.get(channel_name) {
|
||||
channel.broadcast(user_id, response).await
|
||||
} else {
|
||||
Err(ChannelError::SendFailed {
|
||||
name: channel_name.to_string(),
|
||||
reason: "Channel not found".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Broadcast a message to all channels.
|
||||
///
|
||||
/// Sends to the specified user on every registered channel.
|
||||
pub async fn broadcast_all(
|
||||
&self,
|
||||
user_id: &str,
|
||||
response: OutgoingResponse,
|
||||
) -> Vec<(String, Result<(), ChannelError>)> {
|
||||
let channels = self.channels.read().await;
|
||||
let mut results = Vec::new();
|
||||
|
||||
for (name, channel) in channels.iter() {
|
||||
let result = channel.broadcast(user_id, response.clone()).await;
|
||||
results.push((name.clone(), result));
|
||||
}
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
/// Check health of all channels.
|
||||
pub async fn health_check_all(&self) -> HashMap<String, Result<(), ChannelError>> {
|
||||
let channels = self.channels.read().await;
|
||||
|
||||
Reference in New Issue
Block a user