clippy --fix: 合并嵌套if、简化map_or、移除冗余引用等机械性优化
This commit is contained in:
parent
ceb8234a30
commit
81e9f1e7db
@ -162,7 +162,7 @@ impl LoopDetector {
|
|||||||
.count();
|
.count();
|
||||||
|
|
||||||
// Warn every warn_every times
|
// Warn every warn_every times
|
||||||
if consecutive > 0 && consecutive % self.config.warn_every == 0 {
|
if consecutive > 0 && consecutive.is_multiple_of(self.config.warn_every) {
|
||||||
LoopDetectionResult::Warning(format!(
|
LoopDetectionResult::Warning(format!(
|
||||||
"注意: 工具 '{}' 已连续执行 {} 次,参数相同。如果任务没有进展,请尝试其他方法。",
|
"注意: 工具 '{}' 已连续执行 {} 次,参数相同。如果任务没有进展,请尝试其他方法。",
|
||||||
last.name, consecutive
|
last.name, consecutive
|
||||||
@ -339,7 +339,7 @@ impl AgentLoop {
|
|||||||
tracing::debug!(history_len = messages.len(), max_iterations = self.max_iterations, "Starting agent process");
|
tracing::debug!(history_len = messages.len(), max_iterations = self.max_iterations, "Starting agent process");
|
||||||
|
|
||||||
// Build and inject system prompt if not present
|
// Build and inject system prompt if not present
|
||||||
let has_system = messages.first().map_or(false, |m| m.role == "system");
|
let has_system = messages.first().is_some_and(|m| m.role == "system");
|
||||||
if !has_system {
|
if !has_system {
|
||||||
let system_prompt = build_system_prompt(&self.workspace_dir, &self.model_name, &self.tools, None, None);
|
let system_prompt = build_system_prompt(&self.workspace_dir, &self.model_name, &self.tools, None, None);
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
|
|||||||
@ -186,14 +186,13 @@ impl PromptSection for UserProfileSection {
|
|||||||
let mut output = String::from("## 用户配置\n\n");
|
let mut output = String::from("## 用户配置\n\n");
|
||||||
|
|
||||||
// Load USER.md from ~/.picobot/USER.md
|
// Load USER.md from ~/.picobot/USER.md
|
||||||
if let Some(user_config_dir) = get_user_config_dir() {
|
if let Some(user_config_dir) = get_user_config_dir()
|
||||||
if let Some(content) =
|
&& let Some(content) =
|
||||||
load_file_from_dir(&user_config_dir, "USER.md", BOOTSTRAP_MAX_CHARS)
|
load_file_from_dir(&user_config_dir, "USER.md", BOOTSTRAP_MAX_CHARS)
|
||||||
{
|
{
|
||||||
output.push_str(&content);
|
output.push_str(&content);
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// No USER.md found, return empty
|
// No USER.md found, return empty
|
||||||
String::new()
|
String::new()
|
||||||
|
|||||||
@ -32,6 +32,12 @@ pub struct CliChatChannel {
|
|||||||
clients: Mutex<Vec<Arc<Client>>>,
|
clients: Mutex<Vec<Arc<Client>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for CliChatChannel {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl CliChatChannel {
|
impl CliChatChannel {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|||||||
@ -229,12 +229,11 @@ impl FeishuChannel {
|
|||||||
// 1. Check cache
|
// 1. Check cache
|
||||||
{
|
{
|
||||||
let cached = self.tenant_token.read().await;
|
let cached = self.tenant_token.read().await;
|
||||||
if let Some(ref token) = *cached {
|
if let Some(ref token) = *cached
|
||||||
if Instant::now() < token.refresh_after {
|
&& Instant::now() < token.refresh_after {
|
||||||
return Ok(token.value.clone());
|
return Ok(token.value.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Fetch new token
|
// 2. Fetch new token
|
||||||
let (token, ttl) = self.fetch_new_token().await?;
|
let (token, ttl) = self.fetch_new_token().await?;
|
||||||
@ -901,11 +900,10 @@ impl FeishuChannel {
|
|||||||
let (mut content, media) = self.parse_and_download_message(msg_type, &raw_content, &message_id).await?;
|
let (mut content, media) = self.parse_and_download_message(msg_type, &raw_content, &message_id).await?;
|
||||||
|
|
||||||
// Fetch and prepend quoted message content if this is a reply
|
// Fetch and prepend quoted message content if this is a reply
|
||||||
if let Some(ref pid) = parent_id {
|
if let Some(ref pid) = parent_id
|
||||||
if let Some(reply_ctx) = self.get_message_content(pid).await {
|
&& let Some(reply_ctx) = self.get_message_content(pid).await {
|
||||||
content = format!("{}\n{}", reply_ctx, content);
|
content = format!("{}\n{}", reply_ctx, content);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
if let Some(ref m) = media {
|
if let Some(ref m) = media {
|
||||||
@ -1296,8 +1294,8 @@ fn parse_post_content(content: &str) -> String {
|
|||||||
// Fall back: try any dict child
|
// Fall back: try any dict child
|
||||||
if let Some(root_obj) = root.as_object() {
|
if let Some(root_obj) = root.as_object() {
|
||||||
for (_key, val) in root_obj {
|
for (_key, val) in root_obj {
|
||||||
if let Some(obj) = val.as_object() {
|
if let Some(obj) = val.as_object()
|
||||||
if obj.get("content").and_then(|c| c.as_array()).is_some() {
|
&& obj.get("content").and_then(|c| c.as_array()).is_some() {
|
||||||
parse_block(val, &mut texts);
|
parse_block(val, &mut texts);
|
||||||
let result = texts.join("");
|
let result = texts.join("");
|
||||||
if !result.trim().is_empty() {
|
if !result.trim().is_empty() {
|
||||||
@ -1307,7 +1305,6 @@ fn parse_post_content(content: &str) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
content.to_string()
|
content.to_string()
|
||||||
}
|
}
|
||||||
@ -1329,22 +1326,19 @@ fn extract_interactive_content(content: &str) -> Result<(String, Option<MediaIte
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Extract from card object
|
// Extract from card object
|
||||||
if let Some(card) = parsed.get("card").and_then(|c| c.as_object()) {
|
if let Some(card) = parsed.get("card").and_then(|c| c.as_object())
|
||||||
if let Some(elements) = card.get("elements").and_then(|e| e.as_array()) {
|
&& let Some(elements) = card.get("elements").and_then(|e| e.as_array()) {
|
||||||
for el in elements {
|
for el in elements {
|
||||||
extract_element_content(el, &mut texts);
|
extract_element_content(el, &mut texts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Extract from header
|
// Extract from header
|
||||||
if let Some(header) = parsed.get("header").and_then(|h| h.as_object()) {
|
if let Some(header) = parsed.get("header").and_then(|h| h.as_object())
|
||||||
if let Some(title) = header.get("title").and_then(|t| t.as_object()) {
|
&& let Some(title) = header.get("title").and_then(|t| t.as_object())
|
||||||
if let Some(text) = title.get("content").and_then(|c| c.as_str()) {
|
&& let Some(text) = title.get("content").and_then(|c| c.as_str()) {
|
||||||
texts.push(format!("title: {}\n", text));
|
texts.push(format!("title: {}\n", text));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = texts.join("").trim().to_string();
|
let result = texts.join("").trim().to_string();
|
||||||
if result.is_empty() {
|
if result.is_empty() {
|
||||||
@ -1495,12 +1489,11 @@ fn collect_list_items(items: &[serde_json::Value], lines: &mut Vec<String>, dept
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}) {
|
})
|
||||||
if let Some(children) = children_arr.as_object().and_then(|o| o.get("children")).and_then(|c| c.as_array()) {
|
&& let Some(children) = children_arr.as_object().and_then(|o| o.get("children")).and_then(|c| c.as_array()) {
|
||||||
collect_list_items(children, lines, depth + 1);
|
collect_list_items(children, lines, depth + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract text from inline elements (text, link, at-mention)
|
/// Extract text from inline elements (text, link, at-mention)
|
||||||
@ -2088,7 +2081,7 @@ impl Channel for FeishuChannel {
|
|||||||
content: Self::strip_thinking_tags(&msg.content),
|
content: Self::strip_thinking_tags(&msg.content),
|
||||||
..msg
|
..msg
|
||||||
};
|
};
|
||||||
let receive_id = if msg.chat_id.starts_with("oc_") { &msg.chat_id } else { &msg.reply_to.as_ref().unwrap_or(&msg.chat_id) };
|
let receive_id = if msg.chat_id.starts_with("oc_") { &msg.chat_id } else { msg.reply_to.as_ref().unwrap_or(&msg.chat_id) };
|
||||||
let receive_id_type = if msg.chat_id.starts_with("oc_") { "chat_id" } else { "open_id" };
|
let receive_id_type = if msg.chat_id.starts_with("oc_") { "chat_id" } else { "open_id" };
|
||||||
|
|
||||||
// If no media, use smart format detection
|
// If no media, use smart format detection
|
||||||
|
|||||||
@ -32,7 +32,7 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
|||||||
let alias = cmd.aliases.first().map(|a| a.as_str()).unwrap_or(&cmd.name);
|
let alias = cmd.aliases.first().map(|a| a.as_str()).unwrap_or(&cmd.name);
|
||||||
|
|
||||||
ListItem::new(Line::from(vec![
|
ListItem::new(Line::from(vec![
|
||||||
Span::styled(alias, style.clone()),
|
Span::styled(alias, style),
|
||||||
Span::styled(" - ", Style::default().fg(Color::Gray)),
|
Span::styled(" - ", Style::default().fg(Color::Gray)),
|
||||||
Span::styled(&cmd.description, style),
|
Span::styled(&cmd.description, style),
|
||||||
]))
|
]))
|
||||||
|
|||||||
@ -13,8 +13,7 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
|||||||
.map(|session| {
|
.map(|session| {
|
||||||
let is_current = app
|
let is_current = app
|
||||||
.current_session_id
|
.current_session_id
|
||||||
.as_ref()
|
.as_ref() == Some(&session.session_id);
|
||||||
.map_or(false, |id| id == &session.session_id);
|
|
||||||
let archived = session.archived_at.is_some();
|
let archived = session.archived_at.is_some();
|
||||||
|
|
||||||
let mut content = if is_current {
|
let mut content = if is_current {
|
||||||
|
|||||||
@ -36,12 +36,11 @@ async fn handle_socket(ws: WebSocket, state: Arc<GatewayState>) {
|
|||||||
// Task: forward from receiver to WebSocket
|
// Task: forward from receiver to WebSocket
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
while let Some(msg) = receiver.recv().await {
|
while let Some(msg) = receiver.recv().await {
|
||||||
if let Ok(text) = serialize_outbound(&msg) {
|
if let Ok(text) = serialize_outbound(&msg)
|
||||||
if ws_sender.send(WsMessage::Text(text.into())).await.is_err() {
|
&& ws_sender.send(WsMessage::Text(text.into())).await.is_err() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Main loop: receive WebSocket messages and forward to CliChatChannel
|
// Main loop: receive WebSocket messages and forward to CliChatChannel
|
||||||
|
|||||||
@ -26,11 +26,10 @@ pub fn init_logging() {
|
|||||||
let log_dir = get_default_log_dir();
|
let log_dir = get_default_log_dir();
|
||||||
|
|
||||||
// Create log directory if it doesn't exist
|
// Create log directory if it doesn't exist
|
||||||
if !log_dir.exists() {
|
if !log_dir.exists()
|
||||||
if let Err(e) = std::fs::create_dir_all(&log_dir) {
|
&& let Err(e) = std::fs::create_dir_all(&log_dir) {
|
||||||
eprintln!("Warning: Failed to create log directory {}: {}", log_dir.display(), e);
|
eprintln!("Warning: Failed to create log directory {}: {}", log_dir.display(), e);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Create file appender with daily rotation
|
// Create file appender with daily rotation
|
||||||
let file_appender = RollingFileAppender::new(
|
let file_appender = RollingFileAppender::new(
|
||||||
|
|||||||
@ -11,11 +11,10 @@ use std::sync::Arc;
|
|||||||
use crate::storage::Storage;
|
use crate::storage::Storage;
|
||||||
|
|
||||||
fn convert_content_blocks(blocks: &[ContentBlock]) -> Value {
|
fn convert_content_blocks(blocks: &[ContentBlock]) -> Value {
|
||||||
if blocks.len() == 1 {
|
if blocks.len() == 1
|
||||||
if let ContentBlock::Text { text } = &blocks[0] {
|
&& let ContentBlock::Text { text } = &blocks[0] {
|
||||||
return Value::String(text.clone());
|
return Value::String(text.clone());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Value::Array(blocks.iter().map(|b| match b {
|
Value::Array(blocks.iter().map(|b| match b {
|
||||||
ContentBlock::Text { text } => json!({ "type": "text", "text": text }),
|
ContentBlock::Text { text } => json!({ "type": "text", "text": text }),
|
||||||
ContentBlock::ImageUrl { image_url } => {
|
ContentBlock::ImageUrl { image_url } => {
|
||||||
@ -77,7 +76,7 @@ impl OpenAIProvider {
|
|||||||
"tool_call_id": m.tool_call_id,
|
"tool_call_id": m.tool_call_id,
|
||||||
"name": m.name,
|
"name": m.name,
|
||||||
})
|
})
|
||||||
} else if m.role == "assistant" && m.tool_calls.as_ref().map_or(false, |c| !c.is_empty()) {
|
} else if m.role == "assistant" && m.tool_calls.as_ref().is_some_and(|c| !c.is_empty()) {
|
||||||
json!({
|
json!({
|
||||||
"role": m.role,
|
"role": m.role,
|
||||||
"content": convert_content_blocks(&m.content),
|
"content": convert_content_blocks(&m.content),
|
||||||
@ -187,8 +186,8 @@ impl LLMProvider for OpenAIProvider {
|
|||||||
for (i, msg) in msgs.iter().enumerate() {
|
for (i, msg) in msgs.iter().enumerate() {
|
||||||
if let Some(content) = msg.get("content").and_then(|c| c.as_array()) {
|
if let Some(content) = msg.get("content").and_then(|c| c.as_array()) {
|
||||||
for (j, item) in content.iter().enumerate() {
|
for (j, item) in content.iter().enumerate() {
|
||||||
if item.get("type").and_then(|t| t.as_str()) == Some("image_url") {
|
if item.get("type").and_then(|t| t.as_str()) == Some("image_url")
|
||||||
if let Some(url_str) = item.get("image_url").and_then(|u| u.get("url")).and_then(|v| v.as_str()) {
|
&& let Some(url_str) = item.get("image_url").and_then(|u| u.get("url")).and_then(|v| v.as_str()) {
|
||||||
let prefix: String = url_str.chars().take(20).collect();
|
let prefix: String = url_str.chars().take(20).collect();
|
||||||
tracing::debug!(msg_idx = i, item_idx = j, image_prefix = %prefix, image_url_len = %url_str.len(), "Image in LLM request (first 20 bytes shown)");
|
tracing::debug!(msg_idx = i, item_idx = j, image_prefix = %prefix, image_url_len = %url_str.len(), "Image in LLM request (first 20 bytes shown)");
|
||||||
}
|
}
|
||||||
@ -197,7 +196,6 @@ impl LLMProvider for OpenAIProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let mut req_builder = self
|
let mut req_builder = self
|
||||||
.client
|
.client
|
||||||
|
|||||||
@ -22,7 +22,7 @@ use crate::agent::{AgentLoop, AgentError, ContextCompressor};
|
|||||||
use crate::agent::system_prompt::build_system_prompt;
|
use crate::agent::system_prompt::build_system_prompt;
|
||||||
use crate::agent::context_compressor::ContextCompressionConfig;
|
use crate::agent::context_compressor::ContextCompressionConfig;
|
||||||
use crate::providers::{create_provider, LLMProvider};
|
use crate::providers::{create_provider, LLMProvider};
|
||||||
use crate::session::session_id::{UnifiedSessionId, DEFAULT_DIALOG_ID};
|
use crate::session::session_id::UnifiedSessionId;
|
||||||
use crate::session::events::DialogInfo;
|
use crate::session::events::DialogInfo;
|
||||||
use crate::skills::SkillsLoader;
|
use crate::skills::SkillsLoader;
|
||||||
use crate::tools::{ToolRegistry, create_default_tools};
|
use crate::tools::{ToolRegistry, create_default_tools};
|
||||||
@ -193,8 +193,8 @@ impl Session {
|
|||||||
self.seq_counter += 1;
|
self.seq_counter += 1;
|
||||||
|
|
||||||
// Persist to Storage
|
// Persist to Storage
|
||||||
if persist {
|
if persist
|
||||||
if let Some(ref storage) = self.storage {
|
&& let Some(ref storage) = self.storage {
|
||||||
let msg_meta = crate::storage::message::MessageMeta {
|
let msg_meta = crate::storage::message::MessageMeta {
|
||||||
id: message.id.clone(),
|
id: message.id.clone(),
|
||||||
session_id: self.id.to_string(),
|
session_id: self.id.to_string(),
|
||||||
@ -214,7 +214,6 @@ impl Session {
|
|||||||
};
|
};
|
||||||
storage.append_message_with_retry(&self.id.to_string(), &msg_meta).await?;
|
storage.append_message_with_retry(&self.id.to_string(), &msg_meta).await?;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Update in-memory state
|
// Update in-memory state
|
||||||
self.messages.push(message);
|
self.messages.push(message);
|
||||||
@ -411,7 +410,7 @@ impl Session {
|
|||||||
|
|
||||||
/// 将当前 session 导出为 markdown 文档并保存到文件
|
/// 将当前 session 导出为 markdown 文档并保存到文件
|
||||||
pub fn dump_to_file(&self, system_prompt: &str) -> std::io::Result<String> {
|
pub fn dump_to_file(&self, system_prompt: &str) -> std::io::Result<String> {
|
||||||
use chrono::{DateTime, Local};
|
use chrono::Local;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
@ -440,7 +439,7 @@ impl Session {
|
|||||||
let now = Local::now().format("%Y-%m-%d %H:%M:%S");
|
let now = Local::now().format("%Y-%m-%d %H:%M:%S");
|
||||||
|
|
||||||
let mut md = String::new();
|
let mut md = String::new();
|
||||||
md.push_str(&format!("# Session Dump\n\n"));
|
md.push_str(&"# Session Dump\n\n".to_string());
|
||||||
md.push_str(&format!("- **Session ID**: `{}`\n", self.id));
|
md.push_str(&format!("- **Session ID**: `{}`\n", self.id));
|
||||||
md.push_str(&format!("- **Channel**: `{}`\n", self.id.channel));
|
md.push_str(&format!("- **Channel**: `{}`\n", self.id.channel));
|
||||||
md.push_str(&format!("- **Chat ID**: `{}`\n", self.id.chat_id));
|
md.push_str(&format!("- **Chat ID**: `{}`\n", self.id.chat_id));
|
||||||
@ -473,7 +472,7 @@ impl Session {
|
|||||||
md.push_str("```\n");
|
md.push_str("```\n");
|
||||||
|
|
||||||
if let Some(ref tool_calls) = msg.tool_calls {
|
if let Some(ref tool_calls) = msg.tool_calls {
|
||||||
md.push_str(&format!("[Tool Calls]\n"));
|
md.push_str(&"[Tool Calls]\n".to_string());
|
||||||
for tc in tool_calls {
|
for tc in tool_calls {
|
||||||
md.push_str(&format!("- {}: {:?}\n", tc.name, tc.arguments));
|
md.push_str(&format!("- {}: {:?}\n", tc.name, tc.arguments));
|
||||||
}
|
}
|
||||||
@ -599,11 +598,10 @@ fn repair_tool_call_chains(messages: &mut Vec<ChatMessage>) {
|
|||||||
let mut j = i + 1;
|
let mut j = i + 1;
|
||||||
while j < messages.len() && found < expected_count {
|
while j < messages.len() && found < expected_count {
|
||||||
if messages[j].role == "tool" {
|
if messages[j].role == "tool" {
|
||||||
if let Some(ref tc_id) = messages[j].tool_call_id {
|
if let Some(ref tc_id) = messages[j].tool_call_id
|
||||||
if expected_ids.contains(tc_id.as_str()) {
|
&& expected_ids.contains(tc_id.as_str()) {
|
||||||
found += 1;
|
found += 1;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else if messages[j].role == "user" || messages[j].role == "assistant" {
|
} else if messages[j].role == "user" || messages[j].role == "assistant" {
|
||||||
// Next user/assistant message — stop scanning, chain is broken
|
// Next user/assistant message — stop scanning, chain is broken
|
||||||
break;
|
break;
|
||||||
@ -1031,7 +1029,7 @@ impl SessionManager {
|
|||||||
self.tools.clone(),
|
self.tools.clone(),
|
||||||
Some(self.storage.clone()),
|
Some(self.storage.clone()),
|
||||||
String::new(),
|
String::new(),
|
||||||
format!("新对话"),
|
"新对话".to_string(),
|
||||||
self.memory_manager.clone(),
|
self.memory_manager.clone(),
|
||||||
).await?;
|
).await?;
|
||||||
|
|
||||||
@ -1165,15 +1163,12 @@ impl SessionManager {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(ref current_id) = current_id {
|
if let Some(ref current_id) = current_id {
|
||||||
match self.storage.get_session(current_id).await {
|
if let Ok(_) = self.storage.get_session(current_id).await {
|
||||||
Ok(_) => {
|
|
||||||
let parts: Vec<&str> = current_id.split(':').collect();
|
let parts: Vec<&str> = current_id.split(':').collect();
|
||||||
if parts.len() == 3 {
|
if parts.len() == 3 {
|
||||||
return Ok(UnifiedSessionId::new(channel, chat_id, parts[2]));
|
return Ok(UnifiedSessionId::new(channel, chat_id, parts[2]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let ttl_millis = self.inner.lock().await.session_ttl.as_millis() as i64;
|
let ttl_millis = self.inner.lock().await.session_ttl.as_millis() as i64;
|
||||||
@ -1310,7 +1305,7 @@ impl SessionManager {
|
|||||||
let skills_prompt = self.skills_loader.build_skills_prompt();
|
let skills_prompt = self.skills_loader.build_skills_prompt();
|
||||||
|
|
||||||
// Fetch memory context
|
// Fetch memory context
|
||||||
let memory_context = match self.memory_manager.recall(&content, 5, Some(crate::memory::MemoryCategory::Knowledge)).await {
|
let memory_context = match self.memory_manager.recall(content, 5, Some(crate::memory::MemoryCategory::Knowledge)).await {
|
||||||
Ok(entries) if !entries.is_empty() => {
|
Ok(entries) if !entries.is_empty() => {
|
||||||
Some(entries.iter()
|
Some(entries.iter()
|
||||||
.map(|e| format!("- {}: {}", e.key, e.content))
|
.map(|e| format!("- {}: {}", e.key, e.content))
|
||||||
@ -1342,11 +1337,10 @@ impl SessionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if we need to generate a title (after 10 user messages)
|
// Check if we need to generate a title (after 10 user messages)
|
||||||
if session_guard.should_generate_title() {
|
if session_guard.should_generate_title()
|
||||||
if let Err(e) = session_guard.generate_title().await {
|
&& let Err(e) = session_guard.generate_title().await {
|
||||||
tracing::warn!("failed to generate title: {}", e);
|
tracing::warn!("failed to generate title: {}", e);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
result.final_response.content
|
result.final_response.content
|
||||||
};
|
};
|
||||||
@ -1456,11 +1450,10 @@ impl SessionManager {
|
|||||||
.map_err(|e| AgentError::Other(format!("persist error: {}", e)))?;
|
.map_err(|e| AgentError::Other(format!("persist error: {}", e)))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if session_guard.should_generate_title() {
|
if session_guard.should_generate_title()
|
||||||
if let Err(e) = session_guard.generate_title().await {
|
&& let Err(e) = session_guard.generate_title().await {
|
||||||
tracing::warn!("failed to generate title: {}", e);
|
tracing::warn!("failed to generate title: {}", e);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let raw_response = result.final_response.content;
|
let raw_response = result.final_response.content;
|
||||||
let prefix = format!(
|
let prefix = format!(
|
||||||
|
|||||||
@ -12,6 +12,7 @@ pub struct Skill {
|
|||||||
pub path: Option<PathBuf>,
|
pub path: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
struct SkillMarkdownMeta {
|
struct SkillMarkdownMeta {
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
@ -147,24 +148,21 @@ impl SkillsLoader {
|
|||||||
fn get_dir_mtime(dir: &Path) -> Option<SystemTime> {
|
fn get_dir_mtime(dir: &Path) -> Option<SystemTime> {
|
||||||
let mut max_mtime = None;
|
let mut max_mtime = None;
|
||||||
|
|
||||||
if let Ok(metadata) = std::fs::metadata(dir) {
|
if let Ok(metadata) = std::fs::metadata(dir)
|
||||||
if let Ok(mtime) = metadata.modified() {
|
&& let Ok(mtime) = metadata.modified() {
|
||||||
max_mtime = Some(mtime);
|
max_mtime = Some(mtime);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(entries) = std::fs::read_dir(dir) {
|
if let Ok(entries) = std::fs::read_dir(dir) {
|
||||||
for entry in entries.flatten() {
|
for entry in entries.flatten() {
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
if let Ok(metadata) = std::fs::metadata(&path) {
|
if let Ok(metadata) = std::fs::metadata(&path)
|
||||||
if let Ok(mtime) = metadata.modified() {
|
&& let Ok(mtime) = metadata.modified()
|
||||||
if max_mtime.map_or(true, |current| mtime > current) {
|
&& max_mtime.is_none_or(|current| mtime > current) {
|
||||||
max_mtime = Some(mtime);
|
max_mtime = Some(mtime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
max_mtime
|
max_mtime
|
||||||
}
|
}
|
||||||
@ -424,15 +422,6 @@ impl Default for SkillsLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SkillMarkdownMeta {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
name: None,
|
|
||||||
description: None,
|
|
||||||
always: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extract first non-empty, non-heading line as description
|
/// Extract first non-empty, non-heading line as description
|
||||||
fn extract_description(content: &str) -> String {
|
fn extract_description(content: &str) -> String {
|
||||||
|
|||||||
@ -96,7 +96,7 @@ impl super::Storage {
|
|||||||
.cut(query, true)
|
.cut(query, true)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|w| w.len() > 1 || w.bytes().any(|b| b > 127))
|
.filter(|w| w.len() > 1 || w.bytes().any(|b| b > 127))
|
||||||
.map(|w| w.replace('%', "").replace('_', ""))
|
.map(|w| w.replace(['%', '_'], ""))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if !terms.is_empty() {
|
if !terms.is_empty() {
|
||||||
@ -159,7 +159,7 @@ impl super::Storage {
|
|||||||
.cut(q, true)
|
.cut(q, true)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|w| w.len() > 1 || w.bytes().any(|b| b > 127))
|
.filter(|w| w.len() > 1 || w.bytes().any(|b| b > 127))
|
||||||
.map(|w| w.replace('%', "").replace('_', ""))
|
.map(|w| w.replace(['%', '_'], ""))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if terms.is_empty() {
|
if terms.is_empty() {
|
||||||
|
|||||||
@ -144,7 +144,7 @@ impl Tool for BashTool {
|
|||||||
let cwd = self
|
let cwd = self
|
||||||
.working_dir
|
.working_dir
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|d| Path::new(d))
|
.map(Path::new)
|
||||||
.unwrap_or_else(|| Path::new("."));
|
.unwrap_or_else(|| Path::new("."));
|
||||||
|
|
||||||
let result = timeout(
|
let result = timeout(
|
||||||
@ -217,7 +217,7 @@ impl BashTool {
|
|||||||
let stderr_str = String::from_utf8_lossy(&stderr);
|
let stderr_str = String::from_utf8_lossy(&stderr);
|
||||||
if !stderr_str.trim().is_empty() {
|
if !stderr_str.trim().is_empty() {
|
||||||
if !output.is_empty() {
|
if !output.is_empty() {
|
||||||
output.push_str("\n");
|
output.push('\n');
|
||||||
}
|
}
|
||||||
output.push_str("STDERR:\n");
|
output.push_str("STDERR:\n");
|
||||||
output.push_str(&stderr_str);
|
output.push_str(&stderr_str);
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
use std::io::Read;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
use crate::bus::message::ContentBlock;
|
|
||||||
use crate::tools::traits::{Tool, ToolResult};
|
use crate::tools::traits::{Tool, ToolResult};
|
||||||
|
|
||||||
const MAX_CHARS: usize = 128_000;
|
const MAX_CHARS: usize = 128_000;
|
||||||
|
|||||||
@ -113,17 +113,15 @@ impl Tool for FileWriteTool {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Create parent directories if needed
|
// Create parent directories if needed
|
||||||
if let Some(parent) = resolved.parent() {
|
if let Some(parent) = resolved.parent()
|
||||||
if !parent.exists() {
|
&& !parent.exists()
|
||||||
if let Err(e) = std::fs::create_dir_all(parent) {
|
&& let Err(e) = std::fs::create_dir_all(parent) {
|
||||||
return Ok(ToolResult {
|
return Ok(ToolResult {
|
||||||
success: false,
|
success: false,
|
||||||
output: String::new(),
|
output: String::new(),
|
||||||
error: Some(format!("Failed to create parent directory: {}", e)),
|
error: Some(format!("Failed to create parent directory: {}", e)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match std::fs::write(&resolved, content) {
|
match std::fs::write(&resolved, content) {
|
||||||
Ok(_) => Ok(ToolResult {
|
Ok(_) => Ok(ToolResult {
|
||||||
|
|||||||
@ -78,17 +78,15 @@ impl HttpRequestTool {
|
|||||||
|
|
||||||
if let Some(obj) = headers.as_object() {
|
if let Some(obj) = headers.as_object() {
|
||||||
for (key, value) in obj {
|
for (key, value) in obj {
|
||||||
if let Some(str_val) = value.as_str() {
|
if let Some(str_val) = value.as_str()
|
||||||
if let Ok(name) = reqwest::header::HeaderName::from_bytes(key.as_bytes()) {
|
&& let Ok(name) = reqwest::header::HeaderName::from_bytes(key.as_bytes())
|
||||||
if let Ok(val) =
|
&& let Ok(val) =
|
||||||
reqwest::header::HeaderValue::from_str(str_val)
|
reqwest::header::HeaderValue::from_str(str_val)
|
||||||
{
|
{
|
||||||
header_map.insert(name, val);
|
header_map.insert(name, val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
header_map
|
header_map
|
||||||
}
|
}
|
||||||
|
|||||||
@ -114,11 +114,10 @@ impl SchemaCleanr {
|
|||||||
anyhow::bail!("Schema missing required 'type' field");
|
anyhow::bail!("Schema missing required 'type' field");
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(Value::String(t)) = obj.get("type") {
|
if let Some(Value::String(t)) = obj.get("type")
|
||||||
if t == "object" && !obj.contains_key("properties") {
|
&& t == "object" && !obj.contains_key("properties") {
|
||||||
tracing::warn!("Object schema without 'properties' field may cause issues");
|
tracing::warn!("Object schema without 'properties' field may cause issues");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -173,11 +172,10 @@ impl SchemaCleanr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle anyOf/oneOf simplification
|
// Handle anyOf/oneOf simplification
|
||||||
if obj.contains_key("anyOf") || obj.contains_key("oneOf") {
|
if (obj.contains_key("anyOf") || obj.contains_key("oneOf"))
|
||||||
if let Some(simplified) = Self::try_simplify_union(&obj, defs, strategy, ref_stack) {
|
&& let Some(simplified) = Self::try_simplify_union(&obj, defs, strategy, ref_stack) {
|
||||||
return simplified;
|
return simplified;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Build cleaned object
|
// Build cleaned object
|
||||||
let mut cleaned = Map::new();
|
let mut cleaned = Map::new();
|
||||||
@ -244,14 +242,13 @@ impl SchemaCleanr {
|
|||||||
return Self::preserve_meta(obj, Value::Object(Map::new()));
|
return Self::preserve_meta(obj, Value::Object(Map::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(def_name) = Self::parse_local_ref(ref_value) {
|
if let Some(def_name) = Self::parse_local_ref(ref_value)
|
||||||
if let Some(definition) = defs.get(def_name.as_str()) {
|
&& let Some(definition) = defs.get(def_name.as_str()) {
|
||||||
ref_stack.insert(ref_value.to_string());
|
ref_stack.insert(ref_value.to_string());
|
||||||
let cleaned = Self::clean_with_defs(definition.clone(), defs, strategy, ref_stack);
|
let cleaned = Self::clean_with_defs(definition.clone(), defs, strategy, ref_stack);
|
||||||
ref_stack.remove(ref_value);
|
ref_stack.remove(ref_value);
|
||||||
return Self::preserve_meta(obj, cleaned);
|
return Self::preserve_meta(obj, cleaned);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
tracing::warn!("Cannot resolve $ref: {}", ref_value);
|
tracing::warn!("Cannot resolve $ref: {}", ref_value);
|
||||||
Self::preserve_meta(obj, Value::Object(Map::new()))
|
Self::preserve_meta(obj, Value::Object(Map::new()))
|
||||||
@ -342,17 +339,15 @@ impl SchemaCleanr {
|
|||||||
if let Some(Value::Null) = obj.get("const") {
|
if let Some(Value::Null) = obj.get("const") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if let Some(Value::Array(arr)) = obj.get("enum") {
|
if let Some(Value::Array(arr)) = obj.get("enum")
|
||||||
if arr.len() == 1 && matches!(arr[0], Value::Null) {
|
&& arr.len() == 1 && matches!(arr[0], Value::Null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
if let Some(Value::String(t)) = obj.get("type")
|
||||||
if let Some(Value::String(t)) = obj.get("type") {
|
&& t == "null" {
|
||||||
if t == "null" {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user