diff --git a/libs/agent/chat/message_builder.rs b/libs/agent/chat/message_builder.rs index 7ffe2f9..d93e278 100644 --- a/libs/agent/chat/message_builder.rs +++ b/libs/agent/chat/message_builder.rs @@ -7,7 +7,7 @@ use crate::client::types::ChatRequestMessage; use crate::compact::CompactService; use crate::embed::EmbedService; use crate::error::Result; -use crate::perception::{PerceptionService, SkillEntry}; +use crate::perception::{PerceptionService, SkillContext, SkillEntry}; #[derive(Clone)] pub struct MessageBuilder { @@ -393,21 +393,25 @@ impl MessageBuilder { .await; } } - let mut seen = std::collections::HashSet::new(); let mut result = Vec::new(); - for ctx in vector_skills { - if seen.insert(ctx.label.clone()) { - result.push(ctx); - } - } for ctx in keyword_skills { - if seen.insert(ctx.label.clone()) { - result.push(ctx); - } + Self::push_unique_skill_context(&mut result, ctx); + } + for ctx in vector_skills { + Self::push_unique_skill_context(&mut result, ctx); } result } + pub fn push_unique_skill_context(result: &mut Vec, ctx: SkillContext) { + let key = ctx.dedupe_key(); + if result.iter().any(|existing| existing.dedupe_key() == key) { + return; + } + result.push(ctx); + result.sort_by_key(|ctx| ctx.activation.rank()); + } + async fn build_memory_context( &self, request: &AiRequest, diff --git a/libs/agent/chat/nonstreaming_execution.rs b/libs/agent/chat/nonstreaming_execution.rs index 8756bdc..1537a7b 100644 --- a/libs/agent/chat/nonstreaming_execution.rs +++ b/libs/agent/chat/nonstreaming_execution.rs @@ -94,22 +94,10 @@ pub async fn execute_process( .collect(); let tool_names: Vec = calls.iter().map(|call| call.name.clone()).collect(); - let tool_messages = execute_tools( - &request, - &calls, - session_id, - tool_registry, - message_builder, - ) - .await; + let tool_messages = + execute_tools(&request, &calls, session_id, tool_registry, message_builder).await; messages.extend(tool_messages); - inject_passive_skills( - &request, - message_builder, - &tool_names, - &mut messages, - ) - .await; + inject_passive_skills(&request, message_builder, &tool_names, &mut messages).await; tool_depth += 1; if tool_depth >= max_tool_depth { @@ -261,7 +249,7 @@ async fn inject_passive_skills( .all(&request.db) .await { - let skill_entries: Vec = skills + let mut skill_entries: Vec = skills .into_iter() .map(|s| SkillEntry { slug: s.slug, @@ -270,6 +258,16 @@ async fn inject_passive_skills( content: s.content, }) .collect(); + for built_in in crate::skills::all_skills() { + if !skill_entries.iter().any(|s| s.slug == built_in.slug) { + skill_entries.push(SkillEntry { + slug: built_in.slug.to_string(), + name: built_in.name.to_string(), + description: Some(built_in.description.to_string()), + content: built_in.content.clone(), + }); + } + } let tool_events: Vec = tool_names .iter() .map(|name| ToolCallEvent { @@ -277,14 +275,18 @@ async fn inject_passive_skills( arguments: String::new(), }) .collect(); + let mut contexts = Vec::new(); for event in &tool_events { if let Some(ctx) = message_builder .perception_service .passive .detect(event, &skill_entries) { - messages.push(ctx.to_system_message()); + MessageBuilder::push_unique_skill_context(&mut contexts, ctx); } } + for ctx in contexts { + messages.push(ctx.to_system_message()); + } } } diff --git a/libs/agent/chat/streaming_execution.rs b/libs/agent/chat/streaming_execution.rs index dfc9954..b051697 100644 --- a/libs/agent/chat/streaming_execution.rs +++ b/libs/agent/chat/streaming_execution.rs @@ -9,9 +9,9 @@ use super::message_builder::MessageBuilder; use super::service::StreamResult; use super::session_recording::record_ai_session; use super::{AiChunkType, AiRequest, AiStreamChunk, StreamCallback}; -use crate::client::types::{ChatRequestMessage, ToolCall}; use crate::client::AiClientConfig; -use crate::client::{call_stream, StreamChunk, StreamChunkType, StreamedToolCall}; +use crate::client::types::{ChatRequestMessage, ToolCall}; +use crate::client::{StreamChunk, StreamChunkType, StreamedToolCall, call_stream}; use crate::error::Result; use crate::perception::{SkillEntry, ToolCallEvent}; use crate::tool::{ToolCall as AgentToolCall, ToolContext, ToolExecutor}; @@ -266,7 +266,8 @@ async fn drain_tool_call_notifications( chunk_type: AiChunkType::ToolCall, metadata: Some(metadata), children_id: None, - }).await; + }) + .await; all_chunks.push(StreamChunk { chunk_type: StreamChunkType::ToolCall, content: tool_display, @@ -467,7 +468,7 @@ async fn inject_passive_skills_stream( .all(&request.db) .await { - let skill_entries: Vec = skills + let mut skill_entries: Vec = skills .into_iter() .map(|s| SkillEntry { slug: s.slug, @@ -476,6 +477,16 @@ async fn inject_passive_skills_stream( content: s.content, }) .collect(); + for built_in in crate::skills::all_skills() { + if !skill_entries.iter().any(|s| s.slug == built_in.slug) { + skill_entries.push(SkillEntry { + slug: built_in.slug.to_string(), + name: built_in.name.to_string(), + description: Some(built_in.description.to_string()), + content: built_in.content.clone(), + }); + } + } let tool_events: Vec = tool_calls .iter() .map(|tc| ToolCallEvent { @@ -483,14 +494,18 @@ async fn inject_passive_skills_stream( arguments: tc.arguments.clone(), }) .collect(); + let mut contexts = Vec::new(); for event in &tool_events { if let Some(ctx) = message_builder .perception_service .passive .detect(event, &skill_entries) { - messages.push(ctx.to_system_message()); + MessageBuilder::push_unique_skill_context(&mut contexts, ctx); } } + for ctx in contexts { + messages.push(ctx.to_system_message()); + } } }