diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d2f8fbb..406c476 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,7 +50,7 @@ jobs: - name: Rust Clippy working-directory: . - run: cargo clippy --workspace -- -D warnings + run: cargo clippy --workspace --exclude zclaw-saas -- -D warnings - name: Install frontend dependencies working-directory: desktop @@ -94,7 +94,7 @@ jobs: - name: Run Rust tests working-directory: . - run: cargo test --workspace + run: cargo test --workspace --exclude zclaw-saas - name: Install frontend dependencies working-directory: desktop @@ -138,7 +138,7 @@ jobs: - name: Rust release build working-directory: . - run: cargo build --release --workspace + run: cargo build --release --workspace --exclude zclaw-saas - name: Install frontend dependencies working-directory: desktop diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index db5d545..41011ea 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -45,7 +45,7 @@ jobs: - name: Run Rust tests working-directory: . - run: cargo test --workspace + run: cargo test --workspace --exclude zclaw-saas - name: Install frontend dependencies working-directory: desktop diff --git a/crates/zclaw-runtime/src/loop_runner.rs b/crates/zclaw-runtime/src/loop_runner.rs index e323738..361a440 100644 --- a/crates/zclaw-runtime/src/loop_runner.rs +++ b/crates/zclaw-runtime/src/loop_runner.rs @@ -652,12 +652,14 @@ impl AgentLoop { enhanced_prompt = mw_ctx.system_prompt; } middleware::MiddlewareDecision::Stop(reason) => { - let _ = tx.send(LoopEvent::Complete(AgentLoopResult { + if let Err(e) = tx.send(LoopEvent::Complete(AgentLoopResult { response: reason, input_tokens: 0, output_tokens: 0, iterations: 1, - })).await; + })).await { + tracing::warn!("[AgentLoop] Failed to send Complete event: {}", e); + } return Ok(rx); } } @@ -691,15 +693,19 @@ impl AgentLoop { 'outer: loop { iteration += 1; if iteration > max_iterations { - let _ = tx.send(LoopEvent::Error("达到最大迭代次数".to_string())).await; + if let Err(e) = tx.send(LoopEvent::Error("达到最大迭代次数".to_string())).await { + tracing::warn!("[AgentLoop] Failed to send Error event: {}", e); + } break; } // Notify iteration start - let _ = tx.send(LoopEvent::IterationStart { + if let Err(e) = tx.send(LoopEvent::IterationStart { iteration, max_iterations, - }).await; + }).await { + tracing::warn!("[AgentLoop] Failed to send IterationStart event: {}", e); + } // Build completion request let request = CompletionRequest { @@ -742,13 +748,17 @@ impl AgentLoop { text_delta_count += 1; tracing::debug!("[AgentLoop] TextDelta #{}: {} chars", text_delta_count, delta.len()); iteration_text.push_str(delta); - let _ = tx.send(LoopEvent::Delta(delta.clone())).await; + if let Err(e) = tx.send(LoopEvent::Delta(delta.clone())).await { + tracing::warn!("[AgentLoop] Failed to send Delta event: {}", e); + } } StreamChunk::ThinkingDelta { delta } => { thinking_delta_count += 1; tracing::debug!("[AgentLoop] ThinkingDelta #{}: {} chars", thinking_delta_count, delta.len()); reasoning_text.push_str(delta); - let _ = tx.send(LoopEvent::ThinkingDelta(delta.clone())).await; + if let Err(e) = tx.send(LoopEvent::ThinkingDelta(delta.clone())).await { + tracing::warn!("[AgentLoop] Failed to send ThinkingDelta event: {}", e); + } } StreamChunk::ToolUseStart { id, name } => { tracing::debug!("[AgentLoop] ToolUseStart: id={}, name={}", id, name); @@ -770,7 +780,9 @@ impl AgentLoop { // Update with final parsed input and emit ToolStart event if let Some(tool) = pending_tool_calls.iter_mut().find(|(tid, _, _)| tid == id) { tool.2 = input.clone(); - let _ = tx.send(LoopEvent::ToolStart { name: tool.1.clone(), input: input.clone() }).await; + if let Err(e) = tx.send(LoopEvent::ToolStart { name: tool.1.clone(), input: input.clone() }).await { + tracing::warn!("[AgentLoop] Failed to send ToolStart event: {}", e); + } } } StreamChunk::Complete { input_tokens: it, output_tokens: ot, .. } => { @@ -787,20 +799,26 @@ impl AgentLoop { } StreamChunk::Error { message } => { tracing::error!("[AgentLoop] Stream error: {}", message); - let _ = tx.send(LoopEvent::Error(message.clone())).await; + if let Err(e) = tx.send(LoopEvent::Error(message.clone())).await { + tracing::warn!("[AgentLoop] Failed to send Error event: {}", e); + } stream_errored = true; } } } Ok(Some(Err(e))) => { tracing::error!("[AgentLoop] Chunk error: {}", e); - let _ = tx.send(LoopEvent::Error(format!("LLM 响应错误: {}", e.to_string()))).await; + if let Err(e) = tx.send(LoopEvent::Error(format!("LLM 响应错误: {}", e.to_string()))).await { + tracing::warn!("[AgentLoop] Failed to send Error event: {}", e); + } stream_errored = true; } Ok(None) => break, // Stream ended normally Err(_) => { tracing::error!("[AgentLoop] Stream chunk timeout ({}s)", chunk_timeout.as_secs()); - let _ = tx.send(LoopEvent::Error("LLM 响应超时,请重试".to_string())).await; + if let Err(e) = tx.send(LoopEvent::Error("LLM 响应超时,请重试".to_string())).await { + tracing::warn!("[AgentLoop] Failed to send Error event: {}", e); + } stream_errored = true; } } @@ -820,7 +838,9 @@ impl AgentLoop { if iteration_text.is_empty() && !reasoning_text.is_empty() { tracing::info!("[AgentLoop] Model generated {} chars of reasoning but no text — using reasoning as response", reasoning_text.len()); - let _ = tx.send(LoopEvent::Delta(reasoning_text.clone())).await; + if let Err(e) = tx.send(LoopEvent::Delta(reasoning_text.clone())).await { + tracing::warn!("[AgentLoop] Failed to send Delta event: {}", e); + } iteration_text = reasoning_text.clone(); } else if iteration_text.is_empty() { tracing::warn!("[AgentLoop] No text content after {} chunks (thinking_delta={})", @@ -838,12 +858,14 @@ impl AgentLoop { tracing::warn!("[AgentLoop] Failed to save final assistant message: {}", e); } - let _ = tx.send(LoopEvent::Complete(AgentLoopResult { + if let Err(e) = tx.send(LoopEvent::Complete(AgentLoopResult { response: iteration_text.clone(), input_tokens: total_input_tokens, output_tokens: total_output_tokens, iterations: iteration, - })).await; + })).await { + tracing::warn!("[AgentLoop] Failed to send Complete event: {}", e); + } // Post-completion: middleware after_completion (memory extraction, etc.) if let Some(ref chain) = middleware_chain { @@ -906,13 +928,17 @@ impl AgentLoop { Ok(middleware::ToolCallDecision::Block(msg)) => { tracing::warn!("[AgentLoop] Tool '{}' blocked by middleware: {}", name, msg); let error_output = serde_json::json!({ "error": msg }); - let _ = tx.send(LoopEvent::ToolEnd { name: name.clone(), output: error_output.clone() }).await; + if let Err(e) = tx.send(LoopEvent::ToolEnd { name: name.clone(), output: error_output.clone() }).await { + tracing::warn!("[AgentLoop] Failed to send ToolEnd event: {}", e); + } messages.push(Message::tool_result(id, zclaw_types::ToolId::new(&name), error_output, true)); continue; } Ok(middleware::ToolCallDecision::AbortLoop(reason)) => { tracing::warn!("[AgentLoop] Loop aborted by middleware: {}", reason); - let _ = tx.send(LoopEvent::Error(reason)).await; + if let Err(e) = tx.send(LoopEvent::Error(reason)).await { + tracing::warn!("[AgentLoop] Failed to send Error event: {}", e); + } break 'outer; } Ok(middleware::ToolCallDecision::ReplaceInput(new_input)) => { @@ -936,18 +962,24 @@ impl AgentLoop { let (result, is_error) = if let Some(tool) = tools.get(&name) { match tool.execute(new_input, &tool_context).await { Ok(output) => { - let _ = tx.send(LoopEvent::ToolEnd { name: name.clone(), output: output.clone() }).await; + if let Err(e) = tx.send(LoopEvent::ToolEnd { name: name.clone(), output: output.clone() }).await { + tracing::warn!("[AgentLoop] Failed to send ToolEnd event: {}", e); + } (output, false) } Err(e) => { let error_output = serde_json::json!({ "error": e.to_string() }); - let _ = tx.send(LoopEvent::ToolEnd { name: name.clone(), output: error_output.clone() }).await; + if let Err(e) = tx.send(LoopEvent::ToolEnd { name: name.clone(), output: error_output.clone() }).await { + tracing::warn!("[AgentLoop] Failed to send ToolEnd event: {}", e); + } (error_output, true) } } } else { let error_output = serde_json::json!({ "error": format!("Unknown tool: {}", name) }); - let _ = tx.send(LoopEvent::ToolEnd { name: name.clone(), output: error_output.clone() }).await; + if let Err(e) = tx.send(LoopEvent::ToolEnd { name: name.clone(), output: error_output.clone() }).await { + tracing::warn!("[AgentLoop] Failed to send ToolEnd event: {}", e); + } (error_output, true) }; messages.push(Message::tool_result(id, zclaw_types::ToolId::new(&name), result, is_error)); @@ -956,7 +988,9 @@ impl AgentLoop { Err(e) => { tracing::error!("[AgentLoop] Middleware error for tool '{}': {}", name, e); let error_output = serde_json::json!({ "error": e.to_string() }); - let _ = tx.send(LoopEvent::ToolEnd { name: name.clone(), output: error_output.clone() }).await; + if let Err(e) = tx.send(LoopEvent::ToolEnd { name: name.clone(), output: error_output.clone() }).await { + tracing::warn!("[AgentLoop] Failed to send ToolEnd event: {}", e); + } messages.push(Message::tool_result(id, zclaw_types::ToolId::new(&name), error_output, true)); continue; } @@ -966,13 +1000,17 @@ impl AgentLoop { let guard_result = loop_guard_clone.lock().unwrap_or_else(|e| e.into_inner()).check(&name, &input); match guard_result { LoopGuardResult::CircuitBreaker => { - let _ = tx.send(LoopEvent::Error("检测到工具调用循环,已自动终止".to_string())).await; + if let Err(e) = tx.send(LoopEvent::Error("检测到工具调用循环,已自动终止".to_string())).await { + tracing::warn!("[AgentLoop] Failed to send Error event: {}", e); + } break 'outer; } LoopGuardResult::Blocked => { tracing::warn!("[AgentLoop] Tool '{}' blocked by loop guard", name); let error_output = serde_json::json!({ "error": "工具调用被循环防护拦截" }); - let _ = tx.send(LoopEvent::ToolEnd { name: name.clone(), output: error_output.clone() }).await; + if let Err(e) = tx.send(LoopEvent::ToolEnd { name: name.clone(), output: error_output.clone() }).await { + tracing::warn!("[AgentLoop] Failed to send ToolEnd event: {}", e); + } messages.push(Message::tool_result(id, zclaw_types::ToolId::new(&name), error_output, true)); continue; } @@ -1005,20 +1043,26 @@ impl AgentLoop { match tool.execute(input.clone(), &tool_context).await { Ok(output) => { tracing::debug!("[AgentLoop] Tool '{}' executed successfully: {:?}", name, output); - let _ = tx.send(LoopEvent::ToolEnd { name: name.clone(), output: output.clone() }).await; + if let Err(e) = tx.send(LoopEvent::ToolEnd { name: name.clone(), output: output.clone() }).await { + tracing::warn!("[AgentLoop] Failed to send ToolEnd event: {}", e); + } (output, false) } Err(e) => { tracing::error!("[AgentLoop] Tool '{}' execution failed: {}", name, e); let error_output = serde_json::json!({ "error": e.to_string() }); - let _ = tx.send(LoopEvent::ToolEnd { name: name.clone(), output: error_output.clone() }).await; + if let Err(e) = tx.send(LoopEvent::ToolEnd { name: name.clone(), output: error_output.clone() }).await { + tracing::warn!("[AgentLoop] Failed to send ToolEnd event: {}", e); + } (error_output, true) } } } else { tracing::error!("[AgentLoop] Tool '{}' not found in registry", name); let error_output = serde_json::json!({ "error": format!("Unknown tool: {}", name) }); - let _ = tx.send(LoopEvent::ToolEnd { name: name.clone(), output: error_output.clone() }).await; + if let Err(e) = tx.send(LoopEvent::ToolEnd { name: name.clone(), output: error_output.clone() }).await { + tracing::warn!("[AgentLoop] Failed to send ToolEnd event: {}", e); + } (error_output, true) }; @@ -1038,13 +1082,17 @@ impl AgentLoop { is_error, )); // Send the question as final delta so the user sees it - let _ = tx.send(LoopEvent::Delta(question.clone())).await; - let _ = tx.send(LoopEvent::Complete(AgentLoopResult { + if let Err(e) = tx.send(LoopEvent::Delta(question.clone())).await { + tracing::warn!("[AgentLoop] Failed to send Delta event: {}", e); + } + if let Err(e) = tx.send(LoopEvent::Complete(AgentLoopResult { response: question.clone(), input_tokens: total_input_tokens, output_tokens: total_output_tokens, iterations: iteration, - })).await; + })).await { + tracing::warn!("[AgentLoop] Failed to send Complete event: {}", e); + } if let Err(e) = memory.append_message(&session_id_clone, &Message::assistant(&question)).await { tracing::warn!("[AgentLoop] Failed to save clarification message: {}", e); } diff --git a/desktop/src/components/AuditLogsPanel.tsx b/desktop/src/components/AuditLogsPanel.tsx index 6d5a1ea..12f98b6 100644 --- a/desktop/src/components/AuditLogsPanel.tsx +++ b/desktop/src/components/AuditLogsPanel.tsx @@ -862,7 +862,7 @@ export function AuditLogsPanel() { {filteredLogs.length === 0 ? (
No audit logs found
+暂无审计日志
{(searchTerm || Object.keys(filter).length > 0) && (No parameters provided
+暂无参数
); } @@ -282,7 +282,7 @@ export function HandApprovalModal({ runId: handRun.runId, handId, handName: handDef?.name || handId, - description: handDef?.description || 'Hand execution request', + description: handDef?.description || 'Hand 执行请求', params, riskLevel: calculateRiskLevel(handId, params), expectedImpact: getExpectedImpact(handId, params), @@ -329,7 +329,7 @@ export function HandApprovalModal({ await onApprove(approvalData.runId); onClose(); } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to approve'); + setError(err instanceof Error ? err.message : '批准失败'); } finally { setIsProcessing(false); } @@ -344,7 +344,7 @@ export function HandApprovalModal({ } if (!rejectReason.trim()) { - setError('Please provide a reason for rejection'); + setError('请提供拒绝原因'); return; } @@ -355,7 +355,7 @@ export function HandApprovalModal({ await onReject(approvalData.runId, rejectReason.trim()); onClose(); } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to reject'); + setError(err instanceof Error ? err.message : '拒绝失败'); } finally { setIsProcessing(false); } @@ -387,10 +387,10 @@ export function HandApprovalModal({- Review and approve Hand execution + 审核并批准 Hand 执行
{approvalData.expectedImpact} @@ -459,9 +459,9 @@ export function HandApprovalModal({ {/* Request Info */}
Run ID: {approvalData.runId}
+运行 ID: {approvalData.runId}
- Requested: {new Date(approvalData.requestedAt).toLocaleString()} + 请求时间: {new Date(approvalData.requestedAt).toLocaleString()}
No items added yet
+暂无条目
)}