diff --git a/backend/Controllers/DashboardController.cs b/backend/Controllers/DashboardController.cs index 38b0085..418fefe 100644 --- a/backend/Controllers/DashboardController.cs +++ b/backend/Controllers/DashboardController.cs @@ -343,6 +343,23 @@ public class DashboardController( } } + /// + /// Returns the most recent activity entries (assistant messages) for a specific agent. + /// + [HttpGet("agents/{id}/activity")] + public async Task> GetAgentActivity(string id, [FromQuery] int limit = 5) + { + try + { + return await gateway.GetAgentActivityAsync(id, Math.Clamp(limit, 1, 20)); + } + catch (Exception ex) + { + logger.LogWarning(ex, "GetAgentActivity failed for {AgentId}", id); + return new List(); + } + } + /// /// Returns the list of available models that can be assigned to agents. /// Reads from OpenClaw config dynamically, falls back to hardcoded list. diff --git a/backend/Models/Dashboard.cs b/backend/Models/Dashboard.cs index 09f7c69..863ff4a 100644 --- a/backend/Models/Dashboard.cs +++ b/backend/Models/Dashboard.cs @@ -104,3 +104,8 @@ public sealed record UpdateDashboardTaskRequest( public sealed record UpdateDashboardTaskStatusRequest( string Status ); + +public sealed record AgentActivityEntry( + string Time, + string Text +); diff --git a/backend/Services/OpenClawGatewayClient.cs b/backend/Services/OpenClawGatewayClient.cs index cc02760..d3e7a25 100644 --- a/backend/Services/OpenClawGatewayClient.cs +++ b/backend/Services/OpenClawGatewayClient.cs @@ -953,6 +953,44 @@ public sealed class OpenClawGatewayClient(HttpClient httpClient, IConfiguration return Math.Clamp(totalQueuePressure + agentPressure, 0, 100); } + /// + /// Fetches the most recent assistant activity (last N messages) for a specific agent. + /// Returns entries with timestamp and truncated content text. + /// Falls back to an empty list if the session is unreachable. + /// + public async Task> GetAgentActivityAsync(string agentId, int limit = 5) + { + var entries = new List(); + try + { + var sessionKey = $"agent:{agentId}:main"; + var messages = await GetSessionHistoryAsync(sessionKey, Math.Clamp(limit * 2, 1, 100)); + foreach (var msg in messages) + { + if (!string.Equals(msg.Role, "assistant", StringComparison.OrdinalIgnoreCase)) + continue; + if (string.IsNullOrWhiteSpace(msg.Content)) + continue; + if (msg.Content == "REPLY_SKIP" || msg.Content == "ANNOUNCE_SKIP") + continue; + + // Truncate content to first 200 chars for compact display + var text = msg.Content.Length > 200 + ? msg.Content[..200] + "…" + : msg.Content; + var ts = ParseTimestamp(msg.Timestamp); + var timeAgo = FormatTimeAgo(ts); + + entries.Add(new AgentActivityEntry(timeAgo, text)); + } + } + catch + { + // Return empty list if gateway is unreachable + } + return entries.Take(Math.Clamp(limit, 1, 20)).ToList(); + } + /// /// Returns the list of available models by reading from the OpenClaw config, /// with fallback to hardcoded list. diff --git a/frontend/src/components/dashboard/AgentModal.vue b/frontend/src/components/dashboard/AgentModal.vue index 8856ff6..350377a 100644 --- a/frontend/src/components/dashboard/AgentModal.vue +++ b/frontend/src/components/dashboard/AgentModal.vue @@ -24,11 +24,19 @@ interface ModelOption { provider: string } +interface ActivityEntry { + time: string + text: string +} + const availableModels = ref([]) const selectedModel = ref('') const currentModel = ref('') const saving = ref(false) +const activityEntries = ref([]) +const activityLoaded = ref(false) + async function loadModels() { try { const res = await fetch('/api/dashboard/models', { credentials: 'include' }) @@ -75,9 +83,23 @@ async function saveModel() { } } +async function loadActivity() { + try { + const res = await fetch(`/api/dashboard/agents/${props.agent.id}/activity?limit=5`, { credentials: 'include' }) + if (res.ok) { + activityEntries.value = await res.json() + } + } catch { + // silent — fallback to empty + } finally { + activityLoaded.value = true + } +} + onMounted(async () => { await loadModels() await loadCurrentModel() + await loadActivity() }) @@ -139,16 +161,35 @@ onMounted(async () => { + + +