chore(lint): clean up frontend and Rust lint issues
This commit is contained in:
@@ -202,9 +202,7 @@ impl LarkBackend {
|
||||
|
||||
let token = result
|
||||
.tenant_access_token
|
||||
.ok_or_else(|| {
|
||||
ChatChannelError::AuthenticationFailed("No token in response".into())
|
||||
})?;
|
||||
.ok_or_else(|| ChatChannelError::AuthenticationFailed("No token in response".into()))?;
|
||||
let expire_secs = result.expire.unwrap_or(7200);
|
||||
|
||||
let expires_at = Instant::now()
|
||||
@@ -252,10 +250,7 @@ impl LarkBackend {
|
||||
)));
|
||||
}
|
||||
|
||||
let message_id = result
|
||||
.data
|
||||
.and_then(|d| d.message_id)
|
||||
.unwrap_or_default();
|
||||
let message_id = result.data.and_then(|d| d.message_id).unwrap_or_default();
|
||||
Ok(SentMessageId(message_id))
|
||||
}
|
||||
|
||||
|
||||
@@ -34,10 +34,7 @@ impl TelegramBackend {
|
||||
}
|
||||
|
||||
fn api_url(&self, method: &str) -> String {
|
||||
format!(
|
||||
"https://api.telegram.org/bot{}/{}",
|
||||
self.bot_token, method
|
||||
)
|
||||
format!("https://api.telegram.org/bot{}/{}", self.bot_token, method)
|
||||
}
|
||||
|
||||
async fn send_text(
|
||||
@@ -171,9 +168,8 @@ impl ChatChannelBackend for TelegramBackend {
|
||||
{
|
||||
offset = uid + 1;
|
||||
}
|
||||
if let Some(text) = update
|
||||
.pointer("/message/text")
|
||||
.and_then(|t| t.as_str())
|
||||
if let Some(text) =
|
||||
update.pointer("/message/text").and_then(|t| t.as_str())
|
||||
{
|
||||
// Group chat filtering: only process if @bot is mentioned
|
||||
let chat_type = update
|
||||
@@ -184,8 +180,7 @@ impl ChatChannelBackend for TelegramBackend {
|
||||
if (chat_type == "group" || chat_type == "supergroup")
|
||||
&& !bot_username.is_empty()
|
||||
{
|
||||
let at_bot =
|
||||
format!("@{}", bot_username);
|
||||
let at_bot = format!("@{}", bot_username);
|
||||
if !text.to_lowercase().contains(&at_bot) {
|
||||
eprintln!("[Telegram] skipped group msg without @bot: {text}");
|
||||
continue;
|
||||
|
||||
@@ -57,6 +57,18 @@ pub struct WeixinQrcodeStatusPublic {
|
||||
pub status: String,
|
||||
}
|
||||
|
||||
struct SendRequest<'a> {
|
||||
client: &'a reqwest::Client,
|
||||
base_url: &'a str,
|
||||
bot_token: &'a str,
|
||||
wechat_uin: &'a str,
|
||||
to_user_id: &'a str,
|
||||
context_token: &'a str,
|
||||
text: &'a str,
|
||||
reply_context: &'a Mutex<Option<WeixinReplyContext>>,
|
||||
pending_messages: &'a Mutex<Vec<String>>,
|
||||
}
|
||||
|
||||
// ── QR code auth functions (called before backend exists) ──
|
||||
|
||||
pub async fn weixin_get_qrcode() -> Result<WeixinQrcodeInfo, ChatChannelError> {
|
||||
@@ -186,16 +198,16 @@ fn generate_qrcode_data_uri(content: &str) -> Result<String, ChatChannelError> {
|
||||
Ok(format!("data:image/png;base64,{b64}"))
|
||||
}
|
||||
|
||||
pub async fn weixin_check_qrcode(
|
||||
qrcode: &str,
|
||||
) -> Result<WeixinQrcodeStatus, ChatChannelError> {
|
||||
pub async fn weixin_check_qrcode(qrcode: &str) -> Result<WeixinQrcodeStatus, ChatChannelError> {
|
||||
let client = qr_client();
|
||||
let resp = client
|
||||
.get(format!("{ILINK_BASE_URL}/ilink/bot/get_qrcode_status"))
|
||||
.query(&[("qrcode", qrcode)])
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| ChatChannelError::ConnectionFailed(format!("QR status request failed: {e}")))?;
|
||||
.map_err(|e| {
|
||||
ChatChannelError::ConnectionFailed(format!("QR status request failed: {e}"))
|
||||
})?;
|
||||
|
||||
let body: serde_json::Value = resp
|
||||
.json()
|
||||
@@ -289,11 +301,7 @@ impl WeixinBackend {
|
||||
}
|
||||
|
||||
/// Build the JSON body for the iLink sendmessage API.
|
||||
fn build_send_body(
|
||||
to_user_id: &str,
|
||||
context_token: &str,
|
||||
text: &str,
|
||||
) -> serde_json::Value {
|
||||
fn build_send_body(to_user_id: &str, context_token: &str, text: &str) -> serde_json::Value {
|
||||
serde_json::json!({
|
||||
"msg": {
|
||||
"from_user_id": "",
|
||||
@@ -313,23 +321,14 @@ impl WeixinBackend {
|
||||
|
||||
/// Send a message via the iLink API and handle the response.
|
||||
/// Returns `Ok(true)` if sent, `Ok(false)` if buffered due to expired context.
|
||||
async fn do_send(
|
||||
client: &reqwest::Client,
|
||||
base_url: &str,
|
||||
bot_token: &str,
|
||||
wechat_uin: &str,
|
||||
to_user_id: &str,
|
||||
context_token: &str,
|
||||
text: &str,
|
||||
reply_context: &Mutex<Option<WeixinReplyContext>>,
|
||||
pending_messages: &Mutex<Vec<String>>,
|
||||
) -> Result<bool, ChatChannelError> {
|
||||
let body = Self::build_send_body(to_user_id, context_token, text);
|
||||
let url = format!("{base_url}/ilink/bot/sendmessage");
|
||||
async fn do_send(req: SendRequest<'_>) -> Result<bool, ChatChannelError> {
|
||||
let body = Self::build_send_body(req.to_user_id, req.context_token, req.text);
|
||||
let url = format!("{}/ilink/bot/sendmessage", req.base_url);
|
||||
|
||||
let resp = client
|
||||
let resp = req
|
||||
.client
|
||||
.post(&url)
|
||||
.headers(Self::build_headers(bot_token, wechat_uin))
|
||||
.headers(Self::build_headers(req.bot_token, req.wechat_uin))
|
||||
.json(&body)
|
||||
.send()
|
||||
.await
|
||||
@@ -356,20 +355,18 @@ impl WeixinBackend {
|
||||
|
||||
if ret == -2 {
|
||||
// Context token expired — mark stale and buffer
|
||||
if let Some(ref mut c) = *reply_context.lock().await {
|
||||
if let Some(ref mut c) = *req.reply_context.lock().await {
|
||||
c.expired = true;
|
||||
}
|
||||
let mut buf = pending_messages.lock().await;
|
||||
let mut buf = req.pending_messages.lock().await;
|
||||
if buf.len() < MAX_PENDING_MESSAGES {
|
||||
buf.push(text.to_string());
|
||||
buf.push(req.text.to_string());
|
||||
}
|
||||
eprintln!("[Weixin] context_token expired (ret=-2), buffered message");
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
return Err(ChatChannelError::SendFailed(format!(
|
||||
"ret={ret}: {errmsg}"
|
||||
)));
|
||||
return Err(ChatChannelError::SendFailed(format!("ret={ret}: {errmsg}")));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -377,10 +374,7 @@ impl WeixinBackend {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
async fn send_text(
|
||||
&self,
|
||||
text: &str,
|
||||
) -> Result<SentMessageId, ChatChannelError> {
|
||||
async fn send_text(&self, text: &str) -> Result<SentMessageId, ChatChannelError> {
|
||||
// Extract context data under lock, then release
|
||||
let (to_user_id, context_token, expired) = {
|
||||
let guard = self.reply_context.lock().await;
|
||||
@@ -418,17 +412,17 @@ impl WeixinBackend {
|
||||
text.len()
|
||||
);
|
||||
|
||||
Self::do_send(
|
||||
&self.client,
|
||||
&self.base_url,
|
||||
&self.bot_token,
|
||||
&self.wechat_uin,
|
||||
&to_user_id,
|
||||
&context_token,
|
||||
Self::do_send(SendRequest {
|
||||
client: &self.client,
|
||||
base_url: &self.base_url,
|
||||
bot_token: &self.bot_token,
|
||||
wechat_uin: &self.wechat_uin,
|
||||
to_user_id: &to_user_id,
|
||||
context_token: &context_token,
|
||||
text,
|
||||
&self.reply_context,
|
||||
&self.pending_messages,
|
||||
)
|
||||
reply_context: &self.reply_context,
|
||||
pending_messages: &self.pending_messages,
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(SentMessageId(String::new()))
|
||||
@@ -478,10 +472,8 @@ impl ChatChannelBackend for WeixinBackend {
|
||||
|
||||
eprintln!("[Weixin] verify response status={status_code}, body={resp_text}");
|
||||
|
||||
let verify_result: serde_json::Value =
|
||||
serde_json::from_str(&resp_text).map_err(|e| {
|
||||
ChatChannelError::ConnectionFailed(format!("JSON parse failed: {e}"))
|
||||
})?;
|
||||
let verify_result: serde_json::Value = serde_json::from_str(&resp_text)
|
||||
.map_err(|e| ChatChannelError::ConnectionFailed(format!("JSON parse failed: {e}")))?;
|
||||
|
||||
// iLink API auth failures come back as `{"errcode":-14,"errmsg":"session timeout"}`
|
||||
// (no `ret` field). Treat any non-zero errcode as authentication failure.
|
||||
@@ -606,8 +598,7 @@ impl ChatChannelBackend for WeixinBackend {
|
||||
for msg in msgs {
|
||||
// Only handle user messages (message_type=1),
|
||||
// skip bot echo (message_type=2)
|
||||
let msg_type =
|
||||
msg.get("message_type").and_then(|v| v.as_i64());
|
||||
let msg_type = msg.get("message_type").and_then(|v| v.as_i64());
|
||||
if msg_type != Some(1) {
|
||||
continue;
|
||||
}
|
||||
@@ -654,10 +645,8 @@ impl ChatChannelBackend for WeixinBackend {
|
||||
if !from_user_id.is_empty() && !context_token.is_empty() {
|
||||
let was_expired = {
|
||||
let mut guard = reply_context.lock().await;
|
||||
let was = guard
|
||||
.as_ref()
|
||||
.map(|c| c.expired)
|
||||
.unwrap_or(false);
|
||||
let was =
|
||||
guard.as_ref().map(|c| c.expired).unwrap_or(false);
|
||||
*guard = Some(WeixinReplyContext {
|
||||
to_user_id: from_user_id.to_string(),
|
||||
context_token: context_token.to_string(),
|
||||
@@ -676,23 +665,22 @@ impl ChatChannelBackend for WeixinBackend {
|
||||
buffered.len()
|
||||
);
|
||||
for pending_text in &buffered {
|
||||
let ok = WeixinBackend::do_send(
|
||||
&client,
|
||||
&base_url,
|
||||
&bot_token,
|
||||
&wechat_uin,
|
||||
from_user_id,
|
||||
let ok = WeixinBackend::do_send(SendRequest {
|
||||
client: &client,
|
||||
base_url: &base_url,
|
||||
bot_token: &bot_token,
|
||||
wechat_uin: &wechat_uin,
|
||||
to_user_id: from_user_id,
|
||||
context_token,
|
||||
pending_text,
|
||||
&reply_context,
|
||||
&pending_messages,
|
||||
)
|
||||
text: pending_text,
|
||||
reply_context: &reply_context,
|
||||
pending_messages: &pending_messages,
|
||||
})
|
||||
.await;
|
||||
if let Err(e) = ok {
|
||||
eprintln!("[Weixin] resend error: {e}");
|
||||
// Re-buffer remaining on hard error
|
||||
let mut buf =
|
||||
pending_messages.lock().await;
|
||||
let mut buf = pending_messages.lock().await;
|
||||
if buf.len() < MAX_PENDING_MESSAGES {
|
||||
buf.push(pending_text.clone());
|
||||
}
|
||||
@@ -724,15 +712,11 @@ impl ChatChannelBackend for WeixinBackend {
|
||||
}
|
||||
Err(e) => {
|
||||
consecutive_errors += 1;
|
||||
eprintln!(
|
||||
"[Weixin] polling error ({consecutive_errors}): {e}"
|
||||
);
|
||||
eprintln!("[Weixin] polling error ({consecutive_errors}): {e}");
|
||||
*status.lock().await = ChannelConnectionStatus::Error;
|
||||
// Exponential backoff: 5s, 10s, 20s, capped at 30s
|
||||
let delay = std::cmp::min(
|
||||
5 * 2u64.saturating_pow(consecutive_errors - 1),
|
||||
30,
|
||||
);
|
||||
let delay =
|
||||
std::cmp::min(5 * 2u64.saturating_pow(consecutive_errors - 1), 30);
|
||||
tokio::time::sleep(Duration::from_secs(delay)).await;
|
||||
}
|
||||
}
|
||||
@@ -791,9 +775,8 @@ impl ChatChannelBackend for WeixinBackend {
|
||||
|
||||
eprintln!("[Weixin] test_connection: status={status_code}, body={resp_text}");
|
||||
|
||||
let resp_json: serde_json::Value = serde_json::from_str(&resp_text).map_err(|e| {
|
||||
ChatChannelError::ConnectionFailed(format!("Not valid JSON: {e}"))
|
||||
})?;
|
||||
let resp_json: serde_json::Value = serde_json::from_str(&resp_text)
|
||||
.map_err(|e| ChatChannelError::ConnectionFailed(format!("Not valid JSON: {e}")))?;
|
||||
|
||||
if !status_code.is_success() {
|
||||
return Err(ChatChannelError::AuthenticationFailed(format!(
|
||||
|
||||
@@ -155,9 +155,16 @@ async fn dispatch_command(
|
||||
guard.find_by_sender(channel_id, sender_id).is_some()
|
||||
};
|
||||
if has_session {
|
||||
return session_commands::handle_followup(
|
||||
db, text, channel_id, sender_id, conn_mgr, bridge, lang, prefix,
|
||||
)
|
||||
return session_commands::handle_followup(session_commands::FollowupRequest {
|
||||
db,
|
||||
text,
|
||||
channel_id,
|
||||
sender_id,
|
||||
conn_mgr,
|
||||
bridge,
|
||||
lang,
|
||||
prefix,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
return command_handlers::handle_help(prefix, lang);
|
||||
@@ -205,8 +212,7 @@ async fn dispatch_command(
|
||||
.await
|
||||
}
|
||||
"cancel" => {
|
||||
session_commands::handle_cancel(db, channel_id, sender_id, conn_mgr, bridge, lang)
|
||||
.await
|
||||
session_commands::handle_cancel(db, channel_id, sender_id, conn_mgr, bridge, lang).await
|
||||
}
|
||||
"approve" => {
|
||||
let always = args.eq_ignore_ascii_case("always");
|
||||
|
||||
@@ -6,11 +6,7 @@ use super::manager::ChatChannelManager;
|
||||
use super::types::{MessageLevel, RichMessage};
|
||||
use crate::db::entities::conversation;
|
||||
|
||||
pub async fn handle_search(
|
||||
db: &DatabaseConnection,
|
||||
keyword: &str,
|
||||
lang: Lang,
|
||||
) -> RichMessage {
|
||||
pub async fn handle_search(db: &DatabaseConnection, keyword: &str, lang: Lang) -> RichMessage {
|
||||
let matched = match conversation::Entity::find()
|
||||
.filter(conversation::Column::DeletedAt.is_null())
|
||||
.filter(conversation::Column::Title.contains(keyword))
|
||||
@@ -40,10 +36,7 @@ pub async fn handle_search(
|
||||
let title = conv.title.as_deref().unwrap_or(i18n::untitled(lang));
|
||||
let agent = &conv.agent_type;
|
||||
let time = conv.created_at.format("%m-%d %H:%M");
|
||||
body.push_str(&format!(
|
||||
"#{} [{}] {} ({})\n",
|
||||
conv.id, agent, title, time,
|
||||
));
|
||||
body.push_str(&format!("#{} [{}] {} ({})\n", conv.id, agent, title, time,));
|
||||
}
|
||||
|
||||
RichMessage::info(body.trim_end()).with_title(i18n::search_results_count_title(
|
||||
@@ -55,11 +48,7 @@ pub async fn handle_search(
|
||||
|
||||
pub async fn handle_today(db: &DatabaseConnection, lang: Lang) -> RichMessage {
|
||||
let now = Utc::now();
|
||||
let today_start = now
|
||||
.date_naive()
|
||||
.and_hms_opt(0, 0, 0)
|
||||
.unwrap()
|
||||
.and_utc();
|
||||
let today_start = now.date_naive().and_hms_opt(0, 0, 0).unwrap().and_utc();
|
||||
|
||||
let rows = match conversation::Entity::find()
|
||||
.filter(conversation::Column::DeletedAt.is_null())
|
||||
@@ -99,10 +88,7 @@ pub async fn handle_today(db: &DatabaseConnection, lang: Lang) -> RichMessage {
|
||||
let mut body = i18n::total_sessions(lang, rows.len() as u32);
|
||||
body.push_str(&format!("\n\n{}", i18n::by_agent_label(lang)));
|
||||
for (agent, count) in &by_agent {
|
||||
body.push_str(&format!(
|
||||
"\n {}",
|
||||
i18n::agent_count(lang, agent, *count)
|
||||
));
|
||||
body.push_str(&format!("\n {}", i18n::agent_count(lang, agent, *count)));
|
||||
}
|
||||
|
||||
if !titles.is_empty() {
|
||||
|
||||
@@ -9,7 +9,9 @@ use super::i18n::Lang;
|
||||
use super::manager::ChatChannelManager;
|
||||
use super::message_formatter;
|
||||
use super::types::RichMessage;
|
||||
use crate::db::service::{app_metadata_service, chat_channel_message_log_service, chat_channel_service};
|
||||
use crate::db::service::{
|
||||
app_metadata_service, chat_channel_message_log_service, chat_channel_service,
|
||||
};
|
||||
use crate::web::event_bridge::WebEventBroadcaster;
|
||||
|
||||
/// Minimum interval between pushes for the same event type per channel (debounce).
|
||||
|
||||
@@ -340,7 +340,6 @@ pub fn search_results_count_title(lang: Lang, keyword: &str, count: usize) -> St
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn no_activity_today(lang: Lang) -> &'static str {
|
||||
match lang {
|
||||
Lang::ZhCn => "今日暂无编码活动",
|
||||
@@ -667,36 +666,26 @@ pub fn search_usage(lang: Lang, prefix: &str) -> String {
|
||||
|
||||
pub fn unknown_command(lang: Lang, prefix: &str, command: &str) -> String {
|
||||
match lang {
|
||||
Lang::ZhCn => format!(
|
||||
"未知命令: {prefix}{command}\n输入 {prefix}help 查看可用命令"
|
||||
),
|
||||
Lang::ZhTw => format!(
|
||||
"未知命令: {prefix}{command}\n輸入 {prefix}help 查看可用命令"
|
||||
),
|
||||
Lang::Ja => format!(
|
||||
"不明なコマンド: {prefix}{command}\n{prefix}help でヘルプを表示"
|
||||
),
|
||||
Lang::Ko => format!(
|
||||
"알 수 없는 명령: {prefix}{command}\n{prefix}help 로 도움말 보기"
|
||||
),
|
||||
Lang::ZhCn => format!("未知命令: {prefix}{command}\n输入 {prefix}help 查看可用命令"),
|
||||
Lang::ZhTw => format!("未知命令: {prefix}{command}\n輸入 {prefix}help 查看可用命令"),
|
||||
Lang::Ja => format!("不明なコマンド: {prefix}{command}\n{prefix}help でヘルプを表示"),
|
||||
Lang::Ko => format!("알 수 없는 명령: {prefix}{command}\n{prefix}help 로 도움말 보기"),
|
||||
Lang::Es => format!(
|
||||
"Comando desconocido: {prefix}{command}\nEscriba {prefix}help para ver los comandos"
|
||||
),
|
||||
Lang::De => format!(
|
||||
"Unbekannter Befehl: {prefix}{command}\n{prefix}help für Hilfe eingeben"
|
||||
),
|
||||
Lang::Fr => format!(
|
||||
"Commande inconnue : {prefix}{command}\nTapez {prefix}help pour l'aide"
|
||||
),
|
||||
Lang::Pt => format!(
|
||||
"Comando desconhecido: {prefix}{command}\nDigite {prefix}help para ajuda"
|
||||
),
|
||||
Lang::Ar => format!(
|
||||
"أمر غير معروف: {prefix}{command}\nاكتب {prefix}help لعرض المساعدة"
|
||||
),
|
||||
Lang::En => format!(
|
||||
"Unknown command: {prefix}{command}\nType {prefix}help for available commands"
|
||||
),
|
||||
Lang::De => {
|
||||
format!("Unbekannter Befehl: {prefix}{command}\n{prefix}help für Hilfe eingeben")
|
||||
}
|
||||
Lang::Fr => {
|
||||
format!("Commande inconnue : {prefix}{command}\nTapez {prefix}help pour l'aide")
|
||||
}
|
||||
Lang::Pt => {
|
||||
format!("Comando desconhecido: {prefix}{command}\nDigite {prefix}help para ajuda")
|
||||
}
|
||||
Lang::Ar => format!("أمر غير معروف: {prefix}{command}\nاكتب {prefix}help لعرض المساعدة"),
|
||||
Lang::En => {
|
||||
format!("Unknown command: {prefix}{command}\nType {prefix}help for available commands")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -782,10 +771,14 @@ pub fn folder_index_out_of_range(lang: Lang, prefix: &str) -> String {
|
||||
match lang {
|
||||
Lang::ZhCn => format!("序号超出范围,请使用 {prefix}folder 查看列表。"),
|
||||
Lang::ZhTw => format!("序號超出範圍,請使用 {prefix}folder 查看列表。"),
|
||||
Lang::Ja => format!("インデックスが範囲外です。{prefix}folder でリストを確認してください。"),
|
||||
Lang::Ja => {
|
||||
format!("インデックスが範囲外です。{prefix}folder でリストを確認してください。")
|
||||
}
|
||||
Lang::Ko => format!("인덱스가 범위를 벗어났습니다. {prefix}folder로 목록을 확인하세요."),
|
||||
Lang::Es => format!("Índice fuera de rango. Usa {prefix}folder para ver la lista."),
|
||||
Lang::De => format!("Index außerhalb des Bereichs. {prefix}folder verwenden, um aufzulisten."),
|
||||
Lang::De => {
|
||||
format!("Index außerhalb des Bereichs. {prefix}folder verwenden, um aufzulisten.")
|
||||
}
|
||||
Lang::Fr => format!("Index hors limites. Utilisez {prefix}folder pour lister."),
|
||||
Lang::Pt => format!("Índice fora de intervalo. Use {prefix}folder para listar."),
|
||||
Lang::Ar => format!("الفهرس خارج النطاق. استخدم {prefix}folder لعرض القائمة."),
|
||||
@@ -842,7 +835,9 @@ pub fn no_folder_selected(lang: Lang, prefix: &str) -> String {
|
||||
match lang {
|
||||
Lang::ZhCn => format!("未选择工作目录,请先使用 {prefix}folder 选择。"),
|
||||
Lang::ZhTw => format!("未選擇工作目錄,請先使用 {prefix}folder 選擇。"),
|
||||
Lang::Ja => format!("フォルダが選択されていません。先に {prefix}folder を使用してください。"),
|
||||
Lang::Ja => {
|
||||
format!("フォルダが選択されていません。先に {prefix}folder を使用してください。")
|
||||
}
|
||||
Lang::Ko => format!("폴더가 선택되지 않았습니다. 먼저 {prefix}folder를 사용하세요."),
|
||||
Lang::Es => format!("Ninguna carpeta seleccionada. Usa {prefix}folder primero."),
|
||||
Lang::De => format!("Kein Ordner ausgewählt. Zuerst {prefix}folder verwenden."),
|
||||
@@ -873,12 +868,22 @@ pub fn agent_select_hint(lang: Lang, prefix: &str) -> String {
|
||||
match lang {
|
||||
Lang::ZhCn => format!("回复 {prefix}agent <数字> 或 {prefix}agent <名称> 选择。"),
|
||||
Lang::ZhTw => format!("回覆 {prefix}agent <數字> 或 {prefix}agent <名稱> 選擇。"),
|
||||
Lang::Ja => format!("{prefix}agent <番号> または {prefix}agent <名前> で選択してください。"),
|
||||
Lang::Ja => {
|
||||
format!("{prefix}agent <番号> または {prefix}agent <名前> で選択してください。")
|
||||
}
|
||||
Lang::Ko => format!("{prefix}agent <번호> 또는 {prefix}agent <이름>으로 선택하세요."),
|
||||
Lang::Es => format!("Responde {prefix}agent <número> o {prefix}agent <nombre> para seleccionar."),
|
||||
Lang::De => format!("Antworte {prefix}agent <Nummer> oder {prefix}agent <Name> zur Auswahl."),
|
||||
Lang::Fr => format!("Répondez {prefix}agent <numéro> ou {prefix}agent <nom> pour sélectionner."),
|
||||
Lang::Pt => format!("Responda {prefix}agent <número> ou {prefix}agent <nome> para selecionar."),
|
||||
Lang::Es => {
|
||||
format!("Responde {prefix}agent <número> o {prefix}agent <nombre> para seleccionar.")
|
||||
}
|
||||
Lang::De => {
|
||||
format!("Antworte {prefix}agent <Nummer> oder {prefix}agent <Name> zur Auswahl.")
|
||||
}
|
||||
Lang::Fr => {
|
||||
format!("Répondez {prefix}agent <numéro> ou {prefix}agent <nom> pour sélectionner.")
|
||||
}
|
||||
Lang::Pt => {
|
||||
format!("Responda {prefix}agent <número> ou {prefix}agent <nome> para selecionar.")
|
||||
}
|
||||
Lang::Ar => format!("أجب بـ {prefix}agent <رقم> أو {prefix}agent <اسم> للاختيار."),
|
||||
Lang::En => format!("Reply {prefix}agent <number> or {prefix}agent <name> to select."),
|
||||
}
|
||||
@@ -891,7 +896,9 @@ pub fn agent_index_out_of_range(lang: Lang, prefix: &str) -> String {
|
||||
Lang::Ja => format!("インデックスが範囲外です。{prefix}agent でリストを確認してください。"),
|
||||
Lang::Ko => format!("인덱스가 범위를 벗어났습니다. {prefix}agent로 목록을 확인하세요."),
|
||||
Lang::Es => format!("Índice fuera de rango. Usa {prefix}agent para ver la lista."),
|
||||
Lang::De => format!("Index außerhalb des Bereichs. {prefix}agent verwenden, um aufzulisten."),
|
||||
Lang::De => {
|
||||
format!("Index außerhalb des Bereichs. {prefix}agent verwenden, um aufzulisten.")
|
||||
}
|
||||
Lang::Fr => format!("Index hors limites. Utilisez {prefix}agent pour lister."),
|
||||
Lang::Pt => format!("Índice fora de intervalo. Use {prefix}agent para listar."),
|
||||
Lang::Ar => format!("الفهرس خارج النطاق. استخدم {prefix}agent لعرض القائمة."),
|
||||
@@ -1299,7 +1306,9 @@ pub fn no_active_session_use_task(lang: Lang, prefix: &str) -> String {
|
||||
match lang {
|
||||
Lang::ZhCn => format!("没有活跃的会话,请使用 {prefix}task 开始新任务。"),
|
||||
Lang::ZhTw => format!("沒有活躍的對話,請使用 {prefix}task 開始新任務。"),
|
||||
Lang::Ja => format!("アクティブなセッションがありません。{prefix}task で開始してください。"),
|
||||
Lang::Ja => {
|
||||
format!("アクティブなセッションがありません。{prefix}task で開始してください。")
|
||||
}
|
||||
Lang::Ko => format!("활성 세션이 없습니다. {prefix}task로 시작하세요."),
|
||||
Lang::Es => format!("No hay sesión activa. Usa {prefix}task para iniciar una."),
|
||||
Lang::De => format!("Keine aktive Sitzung. {prefix}task zum Starten verwenden."),
|
||||
@@ -1314,11 +1323,17 @@ pub fn session_connection_lost(lang: Lang, prefix: &str) -> String {
|
||||
match lang {
|
||||
Lang::ZhCn => format!("会话连接已断开,请使用 {prefix}task 开始新任务。"),
|
||||
Lang::ZhTw => format!("對話連線已斷開,請使用 {prefix}task 開始新任務。"),
|
||||
Lang::Ja => format!("セッション接続が切断されました。{prefix}task で新しく開始してください。"),
|
||||
Lang::Ja => {
|
||||
format!("セッション接続が切断されました。{prefix}task で新しく開始してください。")
|
||||
}
|
||||
Lang::Ko => format!("세션 연결이 끊어졌습니다. {prefix}task로 새로 시작하세요."),
|
||||
Lang::Es => format!("Conexión de sesión perdida. Usa {prefix}task para iniciar una nueva."),
|
||||
Lang::De => format!("Sitzungsverbindung verloren. {prefix}task für neue Sitzung verwenden."),
|
||||
Lang::Fr => format!("Connexion de session perdue. Utilisez {prefix}task pour en démarrer une nouvelle."),
|
||||
Lang::De => {
|
||||
format!("Sitzungsverbindung verloren. {prefix}task für neue Sitzung verwenden.")
|
||||
}
|
||||
Lang::Fr => format!(
|
||||
"Connexion de session perdue. Utilisez {prefix}task pour en démarrer une nouvelle."
|
||||
),
|
||||
Lang::Pt => format!("Conexão da sessão perdida. Use {prefix}task para iniciar uma nova."),
|
||||
Lang::Ar => format!("انقطع اتصال الجلسة. استخدم {prefix}task لبدء جلسة جديدة."),
|
||||
Lang::En => format!("Session connection lost. Use {prefix}task to start a new one."),
|
||||
|
||||
@@ -268,28 +268,26 @@ impl ChatChannelManager {
|
||||
}
|
||||
|
||||
async fn auto_connect_channels(&self, db_conn: &DatabaseConnection) {
|
||||
let channels =
|
||||
match crate::db::service::chat_channel_service::list_enabled(db_conn).await {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
eprintln!("[ChatChannel] failed to load enabled channels: {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let channels = match crate::db::service::chat_channel_service::list_enabled(db_conn).await {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
eprintln!("[ChatChannel] failed to load enabled channels: {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
for ch in channels {
|
||||
let channel_type: ChannelType = match serde_json::from_value(
|
||||
serde_json::Value::String(ch.channel_type.clone()),
|
||||
) {
|
||||
Ok(t) => t,
|
||||
Err(_) => {
|
||||
eprintln!(
|
||||
"[ChatChannel] unknown channel type '{}' for '{}' (id={}), skipping",
|
||||
ch.channel_type, ch.name, ch.id
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let channel_type: ChannelType =
|
||||
match serde_json::from_value(serde_json::Value::String(ch.channel_type.clone())) {
|
||||
Ok(t) => t,
|
||||
Err(_) => {
|
||||
eprintln!(
|
||||
"[ChatChannel] unknown channel type '{}' for '{}' (id={}), skipping",
|
||||
ch.channel_type, ch.name, ch.id
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let config: serde_json::Value = match serde_json::from_str(&ch.config_json) {
|
||||
Ok(v) => v,
|
||||
@@ -313,17 +311,17 @@ impl ChatChannelManager {
|
||||
}
|
||||
};
|
||||
|
||||
let backend =
|
||||
match super::backends::create_backend(ch.id, channel_type, &config, token) {
|
||||
Ok(b) => b,
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"[ChatChannel] failed to create backend for '{}' (id={}): {e}",
|
||||
ch.name, ch.id
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let backend = match super::backends::create_backend(ch.id, channel_type, &config, token)
|
||||
{
|
||||
Ok(b) => b,
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"[ChatChannel] failed to create backend for '{}' (id={}): {e}",
|
||||
ch.name, ch.id
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = self
|
||||
.add_channel(ch.id, ch.name.clone(), channel_type, backend)
|
||||
|
||||
@@ -8,7 +8,9 @@ use super::i18n::Lang;
|
||||
use super::manager::ChatChannelManager;
|
||||
use super::message_formatter::{self, DailyReportData};
|
||||
use crate::db::entities::conversation;
|
||||
use crate::db::service::{app_metadata_service, chat_channel_message_log_service, chat_channel_service};
|
||||
use crate::db::service::{
|
||||
app_metadata_service, chat_channel_message_log_service, chat_channel_service,
|
||||
};
|
||||
|
||||
const MESSAGE_LANGUAGE_KEY: &str = "chat_message_language";
|
||||
/// Days to retain message logs before cleanup.
|
||||
@@ -60,10 +62,7 @@ pub fn spawn_daily_report_scheduler(
|
||||
continue;
|
||||
}
|
||||
|
||||
let report_time = ch
|
||||
.daily_report_time
|
||||
.as_deref()
|
||||
.unwrap_or("18:00");
|
||||
let report_time = ch.daily_report_time.as_deref().unwrap_or("18:00");
|
||||
|
||||
if current_time != report_time {
|
||||
continue;
|
||||
@@ -124,8 +123,7 @@ async fn generate_daily_report(db: &DatabaseConnection) -> DailyReportData {
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut by_agent: std::collections::HashMap<String, u32> =
|
||||
std::collections::HashMap::new();
|
||||
let mut by_agent: std::collections::HashMap<String, u32> = std::collections::HashMap::new();
|
||||
let mut folder_ids: HashSet<i32> = HashSet::new();
|
||||
let mut activities: Vec<String> = Vec::new();
|
||||
|
||||
|
||||
@@ -54,9 +54,9 @@ impl SessionBridge {
|
||||
}
|
||||
|
||||
pub fn find_by_sender(&self, channel_id: i32, sender_id: &str) -> Option<&ActiveSession> {
|
||||
self.sessions.values().find(|s| {
|
||||
s.channel_id == channel_id && s.sender_id == sender_id
|
||||
})
|
||||
self.sessions
|
||||
.values()
|
||||
.find(|s| s.channel_id == channel_id && s.sender_id == sender_id)
|
||||
}
|
||||
|
||||
pub fn find_by_sender_mut(
|
||||
@@ -64,9 +64,9 @@ impl SessionBridge {
|
||||
channel_id: i32,
|
||||
sender_id: &str,
|
||||
) -> Option<&mut ActiveSession> {
|
||||
self.sessions.values_mut().find(|s| {
|
||||
s.channel_id == channel_id && s.sender_id == sender_id
|
||||
})
|
||||
self.sessions
|
||||
.values_mut()
|
||||
.find(|s| s.channel_id == channel_id && s.sender_id == sender_id)
|
||||
}
|
||||
|
||||
pub fn all_sessions(&self) -> impl Iterator<Item = &ActiveSession> {
|
||||
|
||||
@@ -16,6 +16,17 @@ use crate::db::service::{conversation_service, folder_service, sender_context_se
|
||||
use crate::models::agent::AgentType;
|
||||
use crate::web::event_bridge::EventEmitter;
|
||||
|
||||
pub struct FollowupRequest<'a> {
|
||||
pub db: &'a DatabaseConnection,
|
||||
pub text: &'a str,
|
||||
pub channel_id: i32,
|
||||
pub sender_id: &'a str,
|
||||
pub conn_mgr: &'a ConnectionManager,
|
||||
pub bridge: &'a Arc<Mutex<SessionBridge>>,
|
||||
pub lang: Lang,
|
||||
pub prefix: &'a str,
|
||||
}
|
||||
|
||||
// ── /folder ──
|
||||
|
||||
pub async fn handle_folder(
|
||||
@@ -70,13 +81,7 @@ async fn list_folders(
|
||||
.map(|id| id == f.id)
|
||||
.unwrap_or(false);
|
||||
let marker = if current { " [*]" } else { "" };
|
||||
body.push_str(&format!(
|
||||
"{}. {}{} ({})\n",
|
||||
i + 1,
|
||||
f.name,
|
||||
marker,
|
||||
f.path
|
||||
));
|
||||
body.push_str(&format!("{}. {}{} ({})\n", i + 1, f.name, marker, f.path));
|
||||
}
|
||||
|
||||
body.push_str(&format!("\n{}", i18n::folder_select_hint(lang, prefix)));
|
||||
@@ -107,8 +112,7 @@ async fn select_folder_by_index(
|
||||
return RichMessage::info(i18n::folder_index_out_of_range(lang, prefix));
|
||||
};
|
||||
|
||||
let _ = sender_context_service::update_folder(db, channel_id, sender_id, Some(folder.id))
|
||||
.await;
|
||||
let _ = sender_context_service::update_folder(db, channel_id, sender_id, Some(folder.id)).await;
|
||||
|
||||
RichMessage::info(format!("{} ({})", folder.name, folder.path))
|
||||
.with_title(i18n::folder_selected_title(lang))
|
||||
@@ -128,8 +132,7 @@ async fn select_folder_by_path(
|
||||
}
|
||||
};
|
||||
|
||||
let _ =
|
||||
sender_context_service::update_folder(db, channel_id, sender_id, Some(entry.id)).await;
|
||||
let _ = sender_context_service::update_folder(db, channel_id, sender_id, Some(entry.id)).await;
|
||||
|
||||
RichMessage::info(format!("{} ({})", entry.name, entry.path))
|
||||
.with_title(i18n::folder_selected_title(lang))
|
||||
@@ -317,10 +320,7 @@ pub async fn handle_task(
|
||||
conversation::ConversationStatus::Cancelled,
|
||||
)
|
||||
.await;
|
||||
return RichMessage::error(format!(
|
||||
"{}{e}",
|
||||
i18n::failed_to_start_agent_label(lang)
|
||||
));
|
||||
return RichMessage::error(format!("{}{e}", i18n::failed_to_start_agent_label(lang)));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -352,11 +352,8 @@ pub async fn handle_task(
|
||||
)
|
||||
.await;
|
||||
|
||||
RichMessage::info(format!(
|
||||
"[{}] #{} @ {}",
|
||||
agent_type, conv.id, folder.name,
|
||||
))
|
||||
.with_title(i18n::task_started_title(lang))
|
||||
RichMessage::info(format!("[{}] #{} @ {}", agent_type, conv.id, folder.name,))
|
||||
.with_title(i18n::task_started_title(lang))
|
||||
}
|
||||
|
||||
// ── /sessions ──
|
||||
@@ -401,16 +398,16 @@ pub async fn handle_sessions(
|
||||
{
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
return RichMessage::error(format!(
|
||||
"{}{e}",
|
||||
i18n::failed_to_list_sessions_label(lang)
|
||||
));
|
||||
return RichMessage::error(format!("{}{e}", i18n::failed_to_list_sessions_label(lang)));
|
||||
}
|
||||
};
|
||||
|
||||
if convs.is_empty() {
|
||||
return RichMessage::info(i18n::no_active_sessions_in_folder(lang))
|
||||
.with_title(format!("{} - {}", i18n::sessions_title(lang), folder.name));
|
||||
return RichMessage::info(i18n::no_active_sessions_in_folder(lang)).with_title(format!(
|
||||
"{} - {}",
|
||||
i18n::sessions_title(lang),
|
||||
folder.name
|
||||
));
|
||||
}
|
||||
|
||||
let mut body = String::new();
|
||||
@@ -433,8 +430,11 @@ pub async fn handle_sessions(
|
||||
|
||||
body.push_str(&format!("\n{}", i18n::sessions_resume_hint(lang, prefix)));
|
||||
|
||||
RichMessage::info(body.trim_end())
|
||||
.with_title(format!("{} - {}", i18n::sessions_title(lang), folder.name))
|
||||
RichMessage::info(body.trim_end()).with_title(format!(
|
||||
"{} - {}",
|
||||
i18n::sessions_title(lang),
|
||||
folder.name
|
||||
))
|
||||
}
|
||||
|
||||
// ── /resume ──
|
||||
@@ -491,10 +491,7 @@ pub async fn handle_resume(
|
||||
{
|
||||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
return RichMessage::error(format!(
|
||||
"{}{e}",
|
||||
i18n::failed_to_start_agent_label(lang)
|
||||
));
|
||||
return RichMessage::error(format!("{}{e}", i18n::failed_to_start_agent_label(lang)));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -579,8 +576,7 @@ pub async fn handle_cancel(
|
||||
// Clear session from context
|
||||
let _ = sender_context_service::clear_session(db, channel_id, sender_id).await;
|
||||
|
||||
RichMessage::info(i18n::task_cancelled_body(lang))
|
||||
.with_title(i18n::task_cancelled_title(lang))
|
||||
RichMessage::info(i18n::task_cancelled_body(lang)).with_title(i18n::task_cancelled_title(lang))
|
||||
}
|
||||
|
||||
// ── /approve, /deny ──
|
||||
@@ -661,8 +657,7 @@ pub async fn handle_permission_response(
|
||||
|
||||
// Update auto_approve if requested
|
||||
if always && approve {
|
||||
let _ =
|
||||
sender_context_service::update_auto_approve(db, channel_id, sender_id, true).await;
|
||||
let _ = sender_context_service::update_auto_approve(db, channel_id, sender_id, true).await;
|
||||
}
|
||||
|
||||
let action = if approve {
|
||||
@@ -680,66 +675,58 @@ pub async fn handle_permission_response(
|
||||
|
||||
// ── follow-up (non-command text) ──
|
||||
|
||||
pub async fn handle_followup(
|
||||
db: &DatabaseConnection,
|
||||
text: &str,
|
||||
channel_id: i32,
|
||||
sender_id: &str,
|
||||
conn_mgr: &ConnectionManager,
|
||||
bridge: &Arc<Mutex<SessionBridge>>,
|
||||
lang: Lang,
|
||||
prefix: &str,
|
||||
) -> RichMessage {
|
||||
let ctx = match sender_context_service::get_or_create(db, channel_id, sender_id).await {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
return RichMessage::error(format!("{}{e}", i18n::failed_to_load_context_label(lang)));
|
||||
}
|
||||
};
|
||||
pub async fn handle_followup(req: FollowupRequest<'_>) -> RichMessage {
|
||||
let ctx =
|
||||
match sender_context_service::get_or_create(req.db, req.channel_id, req.sender_id).await {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
return RichMessage::error(format!(
|
||||
"{}{e}",
|
||||
i18n::failed_to_load_context_label(req.lang)
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let connection_id = match &ctx.current_connection_id {
|
||||
Some(id) => id.clone(),
|
||||
None => {
|
||||
return RichMessage::info(i18n::no_active_session_use_task(lang, prefix));
|
||||
return RichMessage::info(i18n::no_active_session_use_task(req.lang, req.prefix));
|
||||
}
|
||||
};
|
||||
|
||||
// Check connection exists in bridge
|
||||
{
|
||||
let bridge_guard = bridge.lock().await;
|
||||
let bridge_guard = req.bridge.lock().await;
|
||||
if bridge_guard.get(&connection_id).is_none() {
|
||||
// Connection lost, clear context
|
||||
drop(bridge_guard);
|
||||
let _ = sender_context_service::clear_session(db, channel_id, sender_id).await;
|
||||
return RichMessage::info(i18n::session_connection_lost(lang, prefix));
|
||||
let _ =
|
||||
sender_context_service::clear_session(req.db, req.channel_id, req.sender_id).await;
|
||||
return RichMessage::info(i18n::session_connection_lost(req.lang, req.prefix));
|
||||
}
|
||||
}
|
||||
|
||||
// Send prompt to agent
|
||||
let blocks = vec![PromptInputBlock::Text {
|
||||
text: text.to_string(),
|
||||
text: req.text.to_string(),
|
||||
}];
|
||||
|
||||
if let Err(e) = conn_mgr.send_prompt(&connection_id, blocks).await {
|
||||
if let Err(e) = req.conn_mgr.send_prompt(&connection_id, blocks).await {
|
||||
// Connection may have died
|
||||
bridge.lock().await.remove(&connection_id);
|
||||
let _ = sender_context_service::clear_session(db, channel_id, sender_id).await;
|
||||
req.bridge.lock().await.remove(&connection_id);
|
||||
let _ = sender_context_service::clear_session(req.db, req.channel_id, req.sender_id).await;
|
||||
return RichMessage::error(format!(
|
||||
"{}{e}",
|
||||
i18n::failed_to_send_message_label(lang)
|
||||
i18n::failed_to_send_message_label(req.lang)
|
||||
));
|
||||
}
|
||||
|
||||
RichMessage::info(i18n::message_sent(lang))
|
||||
RichMessage::info(i18n::message_sent(req.lang))
|
||||
}
|
||||
|
||||
// ── /resume (list recent) ──
|
||||
|
||||
async fn list_recent_sessions(
|
||||
db: &DatabaseConnection,
|
||||
lang: Lang,
|
||||
prefix: &str,
|
||||
) -> RichMessage {
|
||||
async fn list_recent_sessions(db: &DatabaseConnection, lang: Lang, prefix: &str) -> RichMessage {
|
||||
let recent = match conversation::Entity::find()
|
||||
.filter(conversation::Column::DeletedAt.is_null())
|
||||
.order_by_desc(conversation::Column::CreatedAt)
|
||||
@@ -768,10 +755,7 @@ async fn list_recent_sessions(
|
||||
let title = conv.title.as_deref().unwrap_or(i18n::untitled(lang));
|
||||
let agent = &conv.agent_type;
|
||||
let time = conv.created_at.format("%m-%d %H:%M");
|
||||
body.push_str(&format!(
|
||||
"#{} [{}] {} ({})\n",
|
||||
conv.id, agent, title, time,
|
||||
));
|
||||
body.push_str(&format!("#{} [{}] {} ({})\n", conv.id, agent, title, time,));
|
||||
}
|
||||
|
||||
body.push_str(&format!("\n{}", i18n::recent_resume_hint(lang, prefix)));
|
||||
|
||||
@@ -10,9 +10,7 @@ use super::session_bridge::{PendingPermission, SessionBridge};
|
||||
use super::types::{MessageLevel, RichMessage};
|
||||
use crate::acp::manager::ConnectionManager;
|
||||
use crate::acp::types::PromptInputBlock;
|
||||
use crate::db::service::{
|
||||
app_metadata_service, conversation_service, sender_context_service,
|
||||
};
|
||||
use crate::db::service::{app_metadata_service, conversation_service, sender_context_service};
|
||||
use crate::web::event_bridge::WebEventBroadcaster;
|
||||
|
||||
use super::manager::ChatChannelManager;
|
||||
@@ -132,10 +130,7 @@ async fn handle_acp_event_payload(
|
||||
}
|
||||
|
||||
"content_delta" => {
|
||||
let text = payload
|
||||
.get("text")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("");
|
||||
let text = payload.get("text").and_then(|v| v.as_str()).unwrap_or("");
|
||||
|
||||
// Collect flush info under the lock, then release before any IO.
|
||||
let flush_info: Option<(i32, String, Option<String>)> = {
|
||||
@@ -245,14 +240,11 @@ async fn handle_acp_event_payload(
|
||||
let channel_id = session.channel_id;
|
||||
let sender_id = session.sender_id.clone();
|
||||
|
||||
let auto_approve = sender_context_service::get_or_create(
|
||||
db,
|
||||
channel_id,
|
||||
&sender_id,
|
||||
)
|
||||
.await
|
||||
.map(|ctx| ctx.auto_approve)
|
||||
.unwrap_or(false);
|
||||
let auto_approve =
|
||||
sender_context_service::get_or_create(db, channel_id, &sender_id)
|
||||
.await
|
||||
.map(|ctx| ctx.auto_approve)
|
||||
.unwrap_or(false);
|
||||
|
||||
if auto_approve {
|
||||
let option_id = options
|
||||
@@ -286,8 +278,7 @@ async fn handle_acp_event_payload(
|
||||
serde_json::Value::Null => None,
|
||||
other => Some(other.to_string()),
|
||||
});
|
||||
let tool_desc =
|
||||
format_tool_call_detail(tool_title, raw_input_str.as_deref());
|
||||
let tool_desc = format_tool_call_detail(tool_title, raw_input_str.as_deref());
|
||||
|
||||
session.permission_pending = Some(PendingPermission {
|
||||
request_id: request_id.to_string(),
|
||||
@@ -407,16 +398,12 @@ async fn handle_acp_event_payload(
|
||||
crate::db::entities::conversation::ConversationStatus::Cancelled,
|
||||
)
|
||||
.await;
|
||||
let _ =
|
||||
sender_context_service::clear_session(db, channel_id, &sender_id).await;
|
||||
let _ = sender_context_service::clear_session(db, channel_id, &sender_id).await;
|
||||
}
|
||||
}
|
||||
|
||||
"status_changed" => {
|
||||
let status = payload
|
||||
.get("status")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("");
|
||||
let status = payload.get("status").and_then(|v| v.as_str()).unwrap_or("");
|
||||
|
||||
if status == "disconnected" || status == "error" {
|
||||
let mut guard = bridge.lock().await;
|
||||
@@ -425,8 +412,7 @@ async fn handle_acp_event_payload(
|
||||
let sender_id = session.sender_id.clone();
|
||||
drop(guard);
|
||||
|
||||
let _ =
|
||||
sender_context_service::clear_session(db, channel_id, &sender_id).await;
|
||||
let _ = sender_context_service::clear_session(db, channel_id, &sender_id).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -653,7 +639,11 @@ fn format_tool_call_detail(title: &str, raw_input: Option<&str>) -> String {
|
||||
if let Some(pattern) = obj.get("pattern").and_then(|v| v.as_str()) {
|
||||
let path = obj.get("path").and_then(|v| v.as_str());
|
||||
return if let Some(p) = path {
|
||||
format!("Grep: \"{}\" in {}", truncate_str(pattern, 40), short_path(p))
|
||||
format!(
|
||||
"Grep: \"{}\" in {}",
|
||||
truncate_str(pattern, 40),
|
||||
short_path(p)
|
||||
)
|
||||
} else {
|
||||
format!("Grep: \"{}\"", truncate_str(pattern, 60))
|
||||
};
|
||||
|
||||
@@ -147,7 +147,8 @@ impl InteractiveMessage {
|
||||
.iter()
|
||||
.map(|b| format!("[{}]", b.label))
|
||||
.collect();
|
||||
msg.body.push_str(&format!("\n\n{}", button_text.join(" ")));
|
||||
msg.body
|
||||
.push_str(&format!("\n\n{}", button_text.join(" ")));
|
||||
}
|
||||
msg
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user