diff --git a/src-tauri/src/chat_channel/command_dispatcher.rs b/src-tauri/src/chat_channel/command_dispatcher.rs index dd1f841..a1a5a29 100644 --- a/src-tauri/src/chat_channel/command_dispatcher.rs +++ b/src-tauri/src/chat_channel/command_dispatcher.rs @@ -156,7 +156,7 @@ async fn dispatch_command( }; if has_session { return session_commands::handle_followup( - db, text, channel_id, sender_id, conn_mgr, bridge, lang, + db, text, channel_id, sender_id, conn_mgr, bridge, lang, prefix, ) .await; } @@ -193,23 +193,23 @@ async fn dispatch_command( // Session commands "folder" => { - session_commands::handle_folder(db, args, channel_id, sender_id, lang).await + session_commands::handle_folder(db, args, channel_id, sender_id, lang, prefix).await } "agent" => { - session_commands::handle_agent(db, args, channel_id, sender_id, lang).await + session_commands::handle_agent(db, args, channel_id, sender_id, lang, prefix).await } "task" | "do" => { session_commands::handle_task( - db, args, channel_id, sender_id, conn_mgr, emitter, bridge, lang, + db, args, channel_id, sender_id, conn_mgr, emitter, bridge, lang, prefix, ) .await } "sessions" => { - session_commands::handle_sessions(db, channel_id, sender_id, lang).await + session_commands::handle_sessions(db, channel_id, sender_id, lang, prefix).await } "resume" => { session_commands::handle_resume( - db, args, channel_id, sender_id, conn_mgr, emitter, bridge, lang, + db, args, channel_id, sender_id, conn_mgr, emitter, bridge, lang, prefix, ) .await } diff --git a/src-tauri/src/chat_channel/i18n.rs b/src-tauri/src/chat_channel/i18n.rs index f944e20..ac3008f 100644 --- a/src-tauri/src/chat_channel/i18n.rs +++ b/src-tauri/src/chat_channel/i18n.rs @@ -610,14 +610,14 @@ pub fn help_title(lang: Lang) -> &'static str { pub fn help_body(lang: Lang, prefix: &str) -> String { match lang { Lang::ZhCn => format!( - "📂 {prefix}folder - 选择工作目录\n\ - 🤖 {prefix}agent - 选择 Agent\n\ - 🚀 {prefix}task <描述> - 创建会话并执行任务\n\ - 📋 {prefix}sessions - 当前目录的活跃会话\n\ - ▶️ {prefix}resume - 恢复已有会话\n\ - ⏹️ {prefix}cancel - 取消当前任务\n\ - ✅ {prefix}approve [always] - 批准权限请求\n\ - ❌ {prefix}deny - 拒绝权限请求\n\ + "{prefix}folder - 选择工作目录\n\ + {prefix}agent - 选择 Agent\n\ + {prefix}task <描述> - 创建会话并执行任务\n\ + {prefix}sessions - 当前目录的活跃会话\n\ + {prefix}resume - 恢复已有会话\n\ + {prefix}cancel - 取消当前任务\n\ + {prefix}approve [always] - 批准权限请求\n\ + {prefix}deny - 拒绝权限请求\n\ \n\ {prefix}recent - 最近 5 条会话\n\ {prefix}search <关键词> - 搜索会话\n\ @@ -626,81 +626,169 @@ pub fn help_body(lang: Lang, prefix: &str) -> String { {prefix}status - 渠道连接状态\n\ {prefix}help - 显示帮助\n\ \n\ - 💡 有活跃会话时,直接发文本即可继续对话" + 有活跃会话时,直接发文本即可继续对话" ), Lang::ZhTw => format!( - "{prefix}recent - 最近 5 條對話\n\ + "{prefix}folder - 選擇工作目錄\n\ + {prefix}agent - 選擇 Agent\n\ + {prefix}task <描述> - 建立對話並執行任務\n\ + {prefix}sessions - 當前目錄的活躍對話\n\ + {prefix}resume - 恢復已有對話\n\ + {prefix}cancel - 取消當前任務\n\ + {prefix}approve [always] - 批准權限請求\n\ + {prefix}deny - 拒絕權限請求\n\ + \n\ + {prefix}recent - 最近 5 條對話\n\ {prefix}search <關鍵字> - 搜尋對話\n\ {prefix}detail - 對話詳情\n\ {prefix}today - 今日活動匯總\n\ {prefix}status - 頻道連線狀態\n\ - {prefix}help - 顯示幫助" + {prefix}help - 顯示幫助\n\ + \n\ + 有活躍對話時,直接發文字即可繼續對話" ), Lang::Ja => format!( - "{prefix}recent - 最新5件のセッション\n\ + "{prefix}folder - 作業フォルダを選択\n\ + {prefix}agent - エージェントを選択\n\ + {prefix}task <説明> - セッションを作成してタスクを実行\n\ + {prefix}sessions - フォルダ内のアクティブセッション\n\ + {prefix}resume - セッションを再開\n\ + {prefix}cancel - 現在のタスクをキャンセル\n\ + {prefix}approve [always] - 権限を承認\n\ + {prefix}deny - 権限を拒否\n\ + \n\ + {prefix}recent - 最新5件のセッション\n\ {prefix}search <キーワード> - セッション検索\n\ {prefix}detail - セッション詳細\n\ {prefix}today - 本日の活動まとめ\n\ {prefix}status - チャンネル接続状況\n\ - {prefix}help - ヘルプを表示" + {prefix}help - ヘルプを表示\n\ + \n\ + セッションがアクティブな場合、テキストを送信するだけで会話を続けられます" ), Lang::Ko => format!( - "{prefix}recent - 최근 5개 대화\n\ + "{prefix}folder - 작업 폴더 선택\n\ + {prefix}agent - 에이전트 선택\n\ + {prefix}task <설명> - 세션 생성 및 작업 실행\n\ + {prefix}sessions - 폴더 내 활성 세션\n\ + {prefix}resume - 세션 재개\n\ + {prefix}cancel - 현재 작업 취소\n\ + {prefix}approve [always] - 권한 승인\n\ + {prefix}deny - 권한 거부\n\ + \n\ + {prefix}recent - 최근 5개 대화\n\ {prefix}search <키워드> - 대화 검색\n\ {prefix}detail - 대화 상세\n\ {prefix}today - 오늘의 활동 요약\n\ {prefix}status - 채널 연결 상태\n\ - {prefix}help - 도움말 표시" + {prefix}help - 도움말 표시\n\ + \n\ + 세션이 활성화된 경우 텍스트를 보내면 대화를 계속할 수 있습니다" ), Lang::Es => format!( - "{prefix}recent - 5 conversaciones más recientes\n\ + "{prefix}folder - Seleccionar carpeta de trabajo\n\ + {prefix}agent - Seleccionar agente\n\ + {prefix}task - Crear sesion y ejecutar tarea\n\ + {prefix}sessions - Sesiones activas en la carpeta\n\ + {prefix}resume - Reanudar una sesion\n\ + {prefix}cancel - Cancelar tarea actual\n\ + {prefix}approve [always] - Aprobar permiso\n\ + {prefix}deny - Denegar permiso\n\ + \n\ + {prefix}recent - 5 conversaciones mas recientes\n\ {prefix}search - Buscar conversaciones\n\ - {prefix}detail - Detalles de conversación\n\ + {prefix}detail - Detalles de conversacion\n\ {prefix}today - Resumen de hoy\n\ {prefix}status - Estado de canales\n\ - {prefix}help - Mostrar ayuda" + {prefix}help - Mostrar ayuda\n\ + \n\ + Cuando hay una sesion activa, simplemente escriba texto para continuar" ), Lang::De => format!( - "{prefix}recent - 5 neueste Sitzungen\n\ + "{prefix}folder - Arbeitsordner auswahlen\n\ + {prefix}agent - Agent auswahlen\n\ + {prefix}task - Sitzung erstellen und Aufgabe ausfuhren\n\ + {prefix}sessions - Aktive Sitzungen im Ordner\n\ + {prefix}resume - Sitzung fortsetzen\n\ + {prefix}cancel - Aktuelle Aufgabe abbrechen\n\ + {prefix}approve [always] - Berechtigung genehmigen\n\ + {prefix}deny - Berechtigung verweigern\n\ + \n\ + {prefix}recent - 5 neueste Sitzungen\n\ {prefix}search - Sitzungen suchen\n\ {prefix}detail - Sitzungsdetails\n\ {prefix}today - Heutige Zusammenfassung\n\ {prefix}status - Kanalstatus\n\ - {prefix}help - Hilfe anzeigen" + {prefix}help - Hilfe anzeigen\n\ + \n\ + Bei aktiver Sitzung einfach Text eingeben, um das Gesprach fortzusetzen" ), Lang::Fr => format!( - "{prefix}recent - 5 dernières sessions\n\ - {prefix}search - Rechercher des sessions\n\ - {prefix}detail - Détails de la session\n\ - {prefix}today - Résumé du jour\n\ + "{prefix}folder - Selectionner le dossier de travail\n\ + {prefix}agent - Selectionner l'agent\n\ + {prefix}task - Creer une session et executer une tache\n\ + {prefix}sessions - Sessions actives dans le dossier\n\ + {prefix}resume - Reprendre une session\n\ + {prefix}cancel - Annuler la tache en cours\n\ + {prefix}approve [always] - Approuver la permission\n\ + {prefix}deny - Refuser la permission\n\ + \n\ + {prefix}recent - 5 dernieres sessions\n\ + {prefix}search - Rechercher des sessions\n\ + {prefix}detail - Details de la session\n\ + {prefix}today - Resume du jour\n\ {prefix}status - Statut des canaux\n\ - {prefix}help - Afficher l'aide" + {prefix}help - Afficher l'aide\n\ + \n\ + Lorsqu'une session est active, envoyez du texte pour continuer la conversation" ), Lang::Pt => format!( - "{prefix}recent - 5 sessões mais recentes\n\ - {prefix}search - Buscar sessões\n\ - {prefix}detail - Detalhes da sessão\n\ + "{prefix}folder - Selecionar pasta de trabalho\n\ + {prefix}agent - Selecionar agente\n\ + {prefix}task - Criar sessao e executar tarefa\n\ + {prefix}sessions - Sessoes ativas na pasta\n\ + {prefix}resume - Retomar uma sessao\n\ + {prefix}cancel - Cancelar tarefa atual\n\ + {prefix}approve [always] - Aprovar permissao\n\ + {prefix}deny - Negar permissao\n\ + \n\ + {prefix}recent - 5 sessoes mais recentes\n\ + {prefix}search - Buscar sessoes\n\ + {prefix}detail - Detalhes da sessao\n\ {prefix}today - Resumo de hoje\n\ {prefix}status - Status dos canais\n\ - {prefix}help - Mostrar ajuda" + {prefix}help - Mostrar ajuda\n\ + \n\ + Quando uma sessao esta ativa, basta digitar texto para continuar a conversa" ), Lang::Ar => format!( - "{prefix}recent - أحدث 5 جلسات\n\ + "{prefix}folder - اختيار مجلد العمل\n\ + {prefix}agent - اختيار الوكيل\n\ + {prefix}task <وصف> - انشاء جلسة وتنفيذ مهمة\n\ + {prefix}sessions - الجلسات النشطة في المجلد\n\ + {prefix}resume - استئناف جلسة\n\ + {prefix}cancel - الغاء المهمة الحالية\n\ + {prefix}approve [always] - الموافقة على الاذن\n\ + {prefix}deny - رفض الاذن\n\ + \n\ + {prefix}recent - احدث 5 جلسات\n\ {prefix}search <كلمة> - البحث في الجلسات\n\ {prefix}detail - تفاصيل الجلسة\n\ {prefix}today - ملخص اليوم\n\ {prefix}status - حالة القنوات\n\ - {prefix}help - عرض المساعدة" + {prefix}help - عرض المساعدة\n\ + \n\ + عندما تكون الجلسة نشطة، ارسل نصا لمتابعة المحادثة" ), Lang::En => format!( - "📂 {prefix}folder - Select working folder\n\ - 🤖 {prefix}agent - Select agent\n\ - 🚀 {prefix}task - Create session & run task\n\ - 📋 {prefix}sessions - Active sessions in folder\n\ - ▶️ {prefix}resume - Resume a session\n\ - ⏹️ {prefix}cancel - Cancel current task\n\ - ✅ {prefix}approve [always] - Approve permission\n\ - ❌ {prefix}deny - Deny permission\n\ + "{prefix}folder - Select working folder\n\ + {prefix}agent - Select agent\n\ + {prefix}task - Create session & run task\n\ + {prefix}sessions - Active sessions in folder\n\ + {prefix}resume - Resume a session\n\ + {prefix}cancel - Cancel current task\n\ + {prefix}approve [always] - Approve permission\n\ + {prefix}deny - Deny permission\n\ \n\ {prefix}recent - 5 most recent conversations\n\ {prefix}search - Search conversations\n\ @@ -709,7 +797,7 @@ pub fn help_body(lang: Lang, prefix: &str) -> String { {prefix}status - Channel connection status\n\ {prefix}help - Show help\n\ \n\ - 💡 When a session is active, just type text to continue the conversation" + When a session is active, just type text to continue the conversation" ), } } @@ -810,3 +898,20 @@ pub fn unknown_command_title(lang: Lang) -> &'static str { Lang::En => "Unknown Command", } } + +// ── Session progress messages ── + +pub fn agent_responding(lang: Lang) -> &'static str { + match lang { + Lang::ZhCn => "Claude Code 正在响应中...", + Lang::ZhTw => "Claude Code 正在回應中...", + Lang::Ja => "Claude Code が応答中...", + Lang::Ko => "Claude Code 응답 중...", + Lang::Es => "Claude Code respondiendo...", + Lang::De => "Claude Code antwortet...", + Lang::Fr => "Claude Code en cours de reponse...", + Lang::Pt => "Claude Code respondendo...", + Lang::Ar => "...Claude Code يستجيب", + Lang::En => "Claude Code is responding...", + } +} diff --git a/src-tauri/src/chat_channel/session_commands.rs b/src-tauri/src/chat_channel/session_commands.rs index b73ba02..11822af 100644 --- a/src-tauri/src/chat_channel/session_commands.rs +++ b/src-tauri/src/chat_channel/session_commands.rs @@ -24,14 +24,15 @@ pub async fn handle_folder( channel_id: i32, sender_id: &str, lang: Lang, + prefix: &str, ) -> RichMessage { if args.is_empty() { - return list_folders(db, channel_id, sender_id, lang).await; + return list_folders(db, channel_id, sender_id, lang, prefix).await; } // Try parse as index (1-based) if let Ok(idx) = args.parse::() { - return select_folder_by_index(db, idx, channel_id, sender_id, lang).await; + return select_folder_by_index(db, idx, channel_id, sender_id, lang, prefix).await; } // Treat as path @@ -43,6 +44,7 @@ async fn list_folders( channel_id: i32, sender_id: &str, lang: Lang, + prefix: &str, ) -> RichMessage { let folders = match folder_service::list_folders(db).await { Ok(f) => f, @@ -77,10 +79,11 @@ async fn list_folders( body.push_str(&format!( "\n{}", - t( + tp( lang, - "Reply /folder to select.", - "回复 /folder <数字> 选择目录。" + prefix, + "Reply {prefix}folder to select.", + "回复 {prefix}folder <数字> 选择目录。" ) )); @@ -94,6 +97,7 @@ async fn select_folder_by_index( channel_id: i32, sender_id: &str, lang: Lang, + prefix: &str, ) -> RichMessage { if idx == 0 { return RichMessage::info(t(lang, "Index starts from 1.", "序号从 1 开始。")); @@ -105,10 +109,11 @@ async fn select_folder_by_index( }; let Some(folder) = folders.get(idx - 1) else { - return RichMessage::info(t( + return RichMessage::info(tp( lang, - "Index out of range. Use /folder to list.", - "序号超出范围,请使用 /folder 查看列表。", + prefix, + "Index out of range. Use {prefix}folder to list.", + "序号超出范围,请使用 {prefix}folder 查看列表。", )); }; @@ -146,14 +151,15 @@ pub async fn handle_agent( channel_id: i32, sender_id: &str, lang: Lang, + prefix: &str, ) -> RichMessage { if args.is_empty() { - return list_agents(db, channel_id, sender_id, lang).await; + return list_agents(db, channel_id, sender_id, lang, prefix).await; } // Try parse as index if let Ok(idx) = args.parse::() { - return select_agent_by_index(db, idx, channel_id, sender_id, lang).await; + return select_agent_by_index(db, idx, channel_id, sender_id, lang, prefix).await; } // Try parse as agent type name @@ -165,6 +171,7 @@ async fn list_agents( channel_id: i32, sender_id: &str, lang: Lang, + prefix: &str, ) -> RichMessage { let agents = all_acp_agents(); let ctx = sender_context_service::get_or_create(db, channel_id, sender_id) @@ -185,10 +192,11 @@ async fn list_agents( body.push_str(&format!( "\n{}", - t( + tp( lang, - "Reply /agent or /agent to select.", - "回复 /agent <数字> 或 /agent <名称> 选择。" + prefix, + "Reply {prefix}agent or {prefix}agent to select.", + "回复 {prefix}agent <数字> 或 {prefix}agent <名称> 选择。" ) )); @@ -202,13 +210,15 @@ async fn select_agent_by_index( channel_id: i32, sender_id: &str, lang: Lang, + prefix: &str, ) -> RichMessage { let agents = all_acp_agents(); if idx == 0 || idx > agents.len() { - return RichMessage::info(t( + return RichMessage::info(tp( lang, - "Index out of range. Use /agent to list.", - "序号超出范围,请使用 /agent 查看列表。", + prefix, + "Index out of range. Use {prefix}agent to list.", + "序号超出范围,请使用 {prefix}agent 查看列表。", )); } @@ -257,12 +267,14 @@ pub async fn handle_task( emitter: &EventEmitter, bridge: &Arc>, lang: Lang, + prefix: &str, ) -> RichMessage { if task_description.is_empty() { - return RichMessage::info(t( + return RichMessage::info(tp( lang, - "Usage: /task ", - "用法: /task <任务描述>", + prefix, + "Usage: {prefix}task ", + "用法: {prefix}task <任务描述>", )); } @@ -275,10 +287,11 @@ pub async fn handle_task( let folder_id = match ctx.current_folder_id { Some(id) => id, None => { - return RichMessage::info(t( + return RichMessage::info(tp( lang, - "No folder selected. Use /folder first.", - "未选择工作目录,请先使用 /folder 选择。", + prefix, + "No folder selected. Use {prefix}folder first.", + "未选择工作目录,请先使用 {prefix}folder 选择。", )); } }; @@ -287,10 +300,11 @@ pub async fn handle_task( let folder = match folder_service::get_folder_by_id(db, folder_id).await { Ok(Some(f)) => f, _ => { - return RichMessage::info(t( + return RichMessage::info(tp( lang, - "Folder not found. Use /folder to select.", - "目录不存在,请使用 /folder 重新选择。", + prefix, + "Folder not found. Use {prefix}folder to select.", + "目录不存在,请使用 {prefix}folder 重新选择。", )); } }; @@ -381,6 +395,7 @@ pub async fn handle_sessions( channel_id: i32, sender_id: &str, lang: Lang, + prefix: &str, ) -> RichMessage { let ctx = match sender_context_service::get_or_create(db, channel_id, sender_id).await { Ok(c) => c, @@ -390,10 +405,11 @@ pub async fn handle_sessions( let folder_id = match ctx.current_folder_id { Some(id) => id, None => { - return RichMessage::info(t( + return RichMessage::info(tp( lang, - "No folder selected. Use /folder first.", - "未选择工作目录,请先使用 /folder 选择。", + prefix, + "No folder selected. Use {prefix}folder first.", + "未选择工作目录,请先使用 {prefix}folder 选择。", )); } }; @@ -456,10 +472,11 @@ pub async fn handle_sessions( body.push_str(&format!( "\n{}", - t( + tp( lang, - "Reply /resume to continue.", - "回复 /resume 继续会话。" + prefix, + "Reply {prefix}resume to continue.", + "回复 {prefix}resume 继续会话。" ) )); @@ -482,14 +499,16 @@ pub async fn handle_resume( emitter: &EventEmitter, bridge: &Arc>, lang: Lang, + prefix: &str, ) -> RichMessage { let conversation_id: i32 = match args.parse() { Ok(id) => id, Err(_) => { - return RichMessage::info(t( + return RichMessage::info(tp( lang, - "Usage: /resume ", - "用法: /resume <会话ID>", + prefix, + "Usage: {prefix}resume ", + "用法: {prefix}resume <会话ID>", )); } }; @@ -753,6 +772,7 @@ pub async fn handle_followup( conn_mgr: &ConnectionManager, bridge: &Arc>, lang: Lang, + prefix: &str, ) -> RichMessage { let ctx = match sender_context_service::get_or_create(db, channel_id, sender_id).await { Ok(c) => c, @@ -762,10 +782,11 @@ pub async fn handle_followup( let connection_id = match &ctx.current_connection_id { Some(id) => id.clone(), None => { - return RichMessage::info(t( + return RichMessage::info(tp( lang, - "No active session. Use /task to start one.", - "没有活跃的会话,请使用 /task 开始新任务。", + prefix, + "No active session. Use {prefix}task to start one.", + "没有活跃的会话,请使用 {prefix}task 开始新任务。", )); } }; @@ -777,10 +798,11 @@ pub async fn handle_followup( // Connection lost, clear context drop(bridge_guard); let _ = sender_context_service::clear_session(db, channel_id, sender_id).await; - return RichMessage::info(t( + return RichMessage::info(tp( lang, - "Session connection lost. Use /task to start a new one.", - "会话连接已断开,请使用 /task 开始新任务。", + prefix, + "Session connection lost. Use {prefix}task to start a new one.", + "会话连接已断开,请使用 {prefix}task 开始新任务。", )); } } @@ -812,6 +834,11 @@ fn t(lang: Lang, en: &str, zh: &str) -> String { } } +/// Like `t()` but replaces `{prefix}` placeholders with the actual command prefix. +fn tp(lang: Lang, prefix: &str, en: &str, zh: &str) -> String { + t(lang, en, zh).replace("{prefix}", prefix) +} + fn agent_type_to_string(at: AgentType) -> String { serde_json::to_value(at) .ok() diff --git a/src-tauri/src/chat_channel/session_event_subscriber.rs b/src-tauri/src/chat_channel/session_event_subscriber.rs index 3343629..ac8cf1e 100644 --- a/src-tauri/src/chat_channel/session_event_subscriber.rs +++ b/src-tauri/src/chat_channel/session_event_subscriber.rs @@ -17,10 +17,12 @@ use crate::web::event_bridge::WebEventBroadcaster; use super::manager::ChatChannelManager; -const FLUSH_INTERVAL_SECS: u64 = 5; +const FLUSH_INTERVAL_SECS: u64 = 10; const BUFFER_FLUSH_THRESHOLD: usize = 500; const MAX_MESSAGE_LEN: usize = 2000; const MESSAGE_LANGUAGE_KEY: &str = "chat_message_language"; +const COMMAND_PREFIX_KEY: &str = "chat_command_prefix"; +const DEFAULT_COMMAND_PREFIX: &str = "/"; pub fn spawn_session_event_subscriber( broadcaster: Arc, @@ -59,7 +61,7 @@ pub fn spawn_session_event_subscriber( } _ = tokio::time::sleep(Duration::from_secs(FLUSH_INTERVAL_SECS)) => { if last_heartbeat.elapsed() >= Duration::from_secs(FLUSH_INTERVAL_SECS) { - flush_progress(&bridge, &manager).await; + flush_progress(&bridge, &manager, &db_conn).await; last_heartbeat = Instant::now(); } } @@ -77,6 +79,14 @@ async fn get_lang(db: &DatabaseConnection) -> Lang { .unwrap_or_default() } +async fn get_prefix(db: &DatabaseConnection) -> String { + app_metadata_service::get_value(db, COMMAND_PREFIX_KEY) + .await + .ok() + .flatten() + .unwrap_or_else(|| DEFAULT_COMMAND_PREFIX.to_string()) +} + async fn handle_acp_event_payload( payload: &serde_json::Value, bridge: &Arc>, @@ -135,11 +145,11 @@ async fn handle_acp_event_payload( && session.last_flushed.elapsed() >= Duration::from_secs(2) { let channel_id = session.channel_id; - let buf_len = session.content_buffer.len(); let last_tool = session.tool_calls.last().cloned(); session.last_flushed = Instant::now(); - let mut status = format!("... ({buf_len} chars)"); + let lang = get_lang(db).await; + let mut status = super::i18n::agent_responding(lang).to_string(); if let Some(tool) = last_tool { status.push_str(&format!(" | {tool}")); } @@ -252,12 +262,13 @@ async fn handle_acp_event_payload( drop(guard); let lang = get_lang(db).await; + let prefix = get_prefix(db).await; let body = match lang { Lang::ZhCn | Lang::ZhTw => { - format!("Agent 请求权限: {tool_desc}\n\n/approve 批准 | /deny 拒绝 | /approve always 自动批准") + format!("Agent 请求权限: {tool_desc}\n\n{prefix}approve 批准 | {prefix}deny 拒绝 | {prefix}approve always 自动批准") } _ => { - format!("Agent requests permission: {tool_desc}\n\n/approve | /deny | /approve always") + format!("Agent requests permission: {tool_desc}\n\n{prefix}approve | {prefix}deny | {prefix}approve always") } }; @@ -387,7 +398,11 @@ async fn handle_acp_event_payload( } } -async fn flush_progress(bridge: &Arc>, manager: &ChatChannelManager) { +async fn flush_progress( + bridge: &Arc>, + manager: &ChatChannelManager, + db: &DatabaseConnection, +) { let updates: Vec<(i32, String)> = { let mut guard = bridge.lock().await; let mut out = Vec::new(); @@ -395,13 +410,14 @@ async fn flush_progress(bridge: &Arc>, manager: &ChatChanne if !session.content_buffer.is_empty() && session.last_flushed.elapsed() >= Duration::from_secs(FLUSH_INTERVAL_SECS) { - let buf_len = session.content_buffer.len(); - let tool_count = session.tool_calls.len(); session.last_flushed = Instant::now(); - out.push(( - session.channel_id, - format!("... ({buf_len} chars, {tool_count} tools)"), - )); + let last_tool = session.tool_calls.last().cloned(); + let lang = get_lang(db).await; + let mut status = super::i18n::agent_responding(lang).to_string(); + if let Some(tool) = last_tool { + status.push_str(&format!(" | {tool}")); + } + out.push((session.channel_id, status)); } } out