diff --git a/src-tauri/src/openclaw.rs b/src-tauri/src/openclaw.rs index d062d8bb..1b665867 100644 --- a/src-tauri/src/openclaw.rs +++ b/src-tauri/src/openclaw.rs @@ -2949,6 +2949,25 @@ fn finalize_thread_run( let _ = db.checkpoint_thread_run(run_id, checkpoint_payload_json); } let _ = db.finish_thread_run(run_id, final_status, error_payload_json); + // Failed runs must leave a trace in the work log (issue #64): during the + // 2026-08-24 CUJ test a fatal send failure left the Activity tab with no + // record at all, so the log could neither confirm nor deny what the agent + // claimed to be doing. Success already logs "chatted"; this is its + // counterpart, at the single chokepoint every failure path goes through. + if final_status == "failed" { + let summary = error_payload_json + .and_then(|p| serde_json::from_str::(p).ok()) + .and_then(|v| v["error"].as_str().map(|e| e.to_string())) + .unwrap_or_else(|| "Run failed with no recorded error detail.".to_string()); + let detail: String = summary.chars().take(300).collect(); + let _ = db.log_audit( + agent_id, + "run_failed", + Some("openclaw"), + &format!("Attempted a task and failed: {}", detail), + None, + ); + } let _ = refresh_thread_context_files(db, agent_id, conversation_id); clear_thread_cancellation_requested(agent_id, conversation_id); } diff --git a/src/pages/ArchitectView/ActivityTab.tsx b/src/pages/ArchitectView/ActivityTab.tsx index 10ce32b2..2e80ec14 100644 --- a/src/pages/ArchitectView/ActivityTab.tsx +++ b/src/pages/ArchitectView/ActivityTab.tsx @@ -33,6 +33,27 @@ function ActivityTab({ agent, onNavigate }: { agent: AgentData; onNavigate?: (ta // Map tier to a 0–2 position on the green→amber→red gradient track. const tierPosition = currentTier?.id === "unrestricted" ? 2 : currentTier?.id === "balanced" ? 1 : 0; + // Work-log entries speak infrastructure ("FILES_BRIDGE_SYNCED VIA FILES"); + // the log is for the person deciding whether to trust the agent, so render a + // human action narrative and keep the machine code in the hover title + // (issue #64, 2026-08-24 CUJ test). + const humanizeAuditAction = (action: string, bridgeType?: string | null): string => { + const known: Record = { + chatted: "Chat", + created: "Created", + updated: "Updated", + delete: "Deleted", + run_failed: "Task failed", + files_bridge_synced: "Files synced", + bridge_access: "Used a connected service", + bridge_enabled: "Service connected", + bridge_disabled: "Service disconnected", + }; + const base = known[action.toLowerCase()] || action.replace(/_/g, " ").toLowerCase(); + const via = bridgeType && !["openclaw", "files"].includes(bridgeType.toLowerCase()) ? ` via ${bridgeType}` : ""; + return `${base}${via}`; + }; + const groupedLogs = useMemo(() => { if (!recentLogs || recentLogs.length === 0) return []; @@ -256,8 +277,11 @@ function ActivityTab({ agent, onNavigate }: { agent: AgentData; onNavigate?: (ta return (
- - {log.action} {log.bridge_type ? `via ${log.bridge_type}` : ""} + + {humanizeAuditAction(log.action, log.bridge_type)} {new Date(log.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}