Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6d0dab4889 | |||
| dd509a75be | |||
| e0fc305832 | |||
| c120155170 | |||
| 0241130c2f | |||
| 889af65ae7 |
@@ -50,7 +50,11 @@ public sealed class OpenClawGatewayClient(HttpClient httpClient, IConfiguration
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
return null;
|
||||
|
||||
return JsonNode.Parse(json);
|
||||
var node = JsonNode.Parse(json);
|
||||
// Unwrap the { ok: true, result: ... } envelope
|
||||
if (node?["ok"]?.GetValue<bool>() == true && node["result"] is not null)
|
||||
return node["result"];
|
||||
return node;
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -58,81 +62,91 @@ public sealed class OpenClawGatewayClient(HttpClient httpClient, IConfiguration
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts useful data from a tool response that may embed JSON in content[0].text.
|
||||
/// Returns the unwrapped "details" object if present, otherwise the raw result.
|
||||
/// </summary>
|
||||
private static JsonNode? ExtractToolData(JsonNode? result)
|
||||
{
|
||||
if (result is null) return null;
|
||||
// Some tools return { details: {...}, content: [...] }
|
||||
if (result["details"] is JsonNode details && details is not JsonValue)
|
||||
return details;
|
||||
// Some tools wrap in content[0].text as JSON string
|
||||
if (result["content"] is JsonArray content && content.Count > 0)
|
||||
{
|
||||
var text = content[0]?["text"]?.GetValue<string>();
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
try { return JsonNode.Parse(text); }
|
||||
catch { /* fall through */ }
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<List<DashboardAgentInfo>> GetAgentsAsync()
|
||||
{
|
||||
// Hardcoded agent list (known from OpenClaw config).
|
||||
// Tools/invoke visibility is restricted to agent:main scope,
|
||||
// so we can't enumerate Iris sessions programmatically.
|
||||
var agentDefs = new[]
|
||||
{
|
||||
new { Id = "iris", Name = "Iris", Model = "GPT-5.4" },
|
||||
new { Id = "programmer", Name = "Programmer", Model = "DeepSeek V4 Flash" },
|
||||
new { Id = "reviewer", Name = "Reviewer", Model = "DeepSeek V4 Pro" },
|
||||
new { Id = "architekt", Name = "DevOps", Model = "DeepSeek V4 Pro" },
|
||||
new { Id = "executor", Name = "Executor", Model = "DeepSeek V4 Flash" },
|
||||
new { Id = "researcher", Name = "Researcher", Model = "DeepSeek V4 Pro" },
|
||||
};
|
||||
|
||||
var agents = new List<DashboardAgentInfo>();
|
||||
foreach (var def in agentDefs)
|
||||
{
|
||||
var isActive = false;
|
||||
string? currentTask = null;
|
||||
|
||||
// Get sessions list (active agents/sessions)
|
||||
var sessionsResult = await InvokeToolAsync("sessions_list");
|
||||
if (sessionsResult is not null)
|
||||
// Check workspace activity via file timestamps
|
||||
var memDir = $"/mnt/workspace-{def.Id}/memory";
|
||||
try
|
||||
{
|
||||
var sessions = sessionsResult.AsArray();
|
||||
if (sessions is not null)
|
||||
if (Directory.Exists(memDir))
|
||||
{
|
||||
foreach (var session in sessions)
|
||||
var latestFile = Directory.GetFiles(memDir, "*", SearchOption.AllDirectories)
|
||||
.Select(f => new FileInfo(f))
|
||||
.OrderByDescending(f => f.LastWriteTimeUtc)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (latestFile is not null)
|
||||
{
|
||||
if (session is null) continue;
|
||||
var id = session["sessionKey"]?.GetValue<string>() ?? session["id"]?.GetValue<string>() ?? "";
|
||||
var name = session["name"]?.GetValue<string>() ?? id;
|
||||
var model = session["model"]?.GetValue<string>() ?? "";
|
||||
var status = session["status"]?.GetValue<string>() ?? "";
|
||||
var isActive = status.Equals("active", StringComparison.OrdinalIgnoreCase);
|
||||
var age = DateTime.UtcNow - latestFile.LastWriteTimeUtc;
|
||||
isActive = age.TotalMinutes < 15;
|
||||
if (isActive)
|
||||
currentTask = $"Modified: {latestFile.Name}";
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { /* workspace not mounted or inaccessible */ }
|
||||
|
||||
agents.Add(new DashboardAgentInfo(
|
||||
Id: id,
|
||||
Name: name,
|
||||
Role: DeriveRole(id),
|
||||
Model: model,
|
||||
Id: def.Id,
|
||||
Name: def.Name,
|
||||
Role: DeriveRole(def.Id),
|
||||
Model: def.Model,
|
||||
IsActive: isActive,
|
||||
CurrentTask: session["currentTask"]?.GetValue<string>()
|
||||
CurrentTask: currentTask
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also get subagents list
|
||||
var subagentsResult = await InvokeToolAsync("sub_agents_list");
|
||||
if (subagentsResult is not null && subagentsResult is JsonArray subArray)
|
||||
{
|
||||
foreach (var sub in subArray)
|
||||
{
|
||||
if (sub is null) continue;
|
||||
var id = sub["id"]?.GetValue<string>() ?? "";
|
||||
if (agents.Any(a => a.Id == id)) continue;
|
||||
|
||||
var name = sub["name"]?.GetValue<string>() ?? id;
|
||||
var model = sub["model"]?.GetValue<string>() ?? "";
|
||||
var status = sub["status"]?.GetValue<string>() ?? "";
|
||||
var isActive = status.Equals("active", StringComparison.OrdinalIgnoreCase) ||
|
||||
status.Equals("running", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
agents.Add(new DashboardAgentInfo(
|
||||
Id: id,
|
||||
Name: name,
|
||||
Role: DeriveRole(id),
|
||||
Model: model,
|
||||
IsActive: isActive,
|
||||
CurrentTask: sub["currentTask"]?.GetValue<string>()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return agents.Count > 0 ? agents : new List<DashboardAgentInfo>();
|
||||
return agents;
|
||||
}
|
||||
|
||||
public async Task<List<MessageEntry>> GetSessionHistoryAsync(string sessionKey, int limit = 50, int offset = 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await InvokeToolAsync("sessions_history", new
|
||||
{
|
||||
sessionKey,
|
||||
limit,
|
||||
offset
|
||||
});
|
||||
|
||||
if (result is null)
|
||||
return new List<MessageEntry>();
|
||||
var result = await InvokeToolAsync("sessions_history", new { sessionKey, limit, offset });
|
||||
if (result is null) return new List<MessageEntry>();
|
||||
|
||||
var messages = new List<MessageEntry>();
|
||||
var array = result as JsonArray ?? result.AsArray();
|
||||
@@ -163,19 +177,13 @@ public sealed class OpenClawGatewayClient(HttpClient httpClient, IConfiguration
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await InvokeToolAsync("sessions_send", new
|
||||
{
|
||||
sessionKey,
|
||||
message
|
||||
});
|
||||
var result = await InvokeToolAsync("sessions_send", new { sessionKey, message });
|
||||
if (result is null) return new ChatResponse(false, null, "Gateway nicht erreichbar");
|
||||
|
||||
if (result is null)
|
||||
return new ChatResponse(false, null, "Gateway nicht erreichbar");
|
||||
|
||||
var ok = result["ok"]?.GetValue<bool>() ?? result["success"]?.GetValue<bool>() ?? false;
|
||||
var ok = result["ok"]?.GetValue<bool>() ?? false;
|
||||
var reply = result["reply"]?.GetValue<string>()
|
||||
?? result["response"]?.GetValue<string>()
|
||||
?? result["content"]?.GetValue<string>();
|
||||
?? result["content"]?[0]?["text"]?.GetValue<string>();
|
||||
var error = result["error"]?.GetValue<string>();
|
||||
|
||||
return new ChatResponse(ok, reply, error);
|
||||
@@ -190,24 +198,25 @@ public sealed class OpenClawGatewayClient(HttpClient httpClient, IConfiguration
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await InvokeToolAsync("cron_list");
|
||||
if (result is null)
|
||||
return new List<QueueItem>();
|
||||
var result = await InvokeToolAsync("cron", new { action = "list" });
|
||||
var data = ExtractToolData(result);
|
||||
if (data is null) return new List<QueueItem>();
|
||||
|
||||
var items = new List<QueueItem>();
|
||||
var array = result as JsonArray ?? result.AsArray();
|
||||
if (array is null) return items;
|
||||
var jobs = data["jobs"] as JsonArray ?? data.AsArray();
|
||||
if (jobs is null) return items;
|
||||
|
||||
foreach (var entry in array)
|
||||
foreach (var j in jobs)
|
||||
{
|
||||
if (entry is null) continue;
|
||||
var id = entry["id"]?.GetValue<string>() ?? "";
|
||||
var name = entry["name"]?.GetValue<string>() ?? id;
|
||||
var status = entry["status"]?.GetValue<string>() ?? "unknown";
|
||||
if (j is null) continue;
|
||||
var id = j["id"]?.GetValue<string>() ?? "";
|
||||
var name = j["name"]?.GetValue<string>() ?? id;
|
||||
var status = j["state"]?["lastStatus"]?.GetValue<string>()
|
||||
?? j["status"]?.GetValue<string>()
|
||||
?? "unknown";
|
||||
|
||||
items.Add(new QueueItem(id, name, status));
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
catch
|
||||
@@ -223,35 +232,48 @@ public sealed class OpenClawGatewayClient(HttpClient httpClient, IConfiguration
|
||||
var activeAgents = 0;
|
||||
var pendingTasks = 0;
|
||||
|
||||
// Step 1: Health check (no auth needed)
|
||||
try
|
||||
{
|
||||
// Check gateway health
|
||||
using var pingRequest = new HttpRequestMessage(HttpMethod.Get, "/");
|
||||
ApplyAuth(pingRequest);
|
||||
using var pingRequest = new HttpRequestMessage(HttpMethod.Get, "/health");
|
||||
using var pingResponse = await httpClient.SendAsync(pingRequest);
|
||||
gatewayOk = pingResponse.IsSuccessStatusCode;
|
||||
|
||||
if (gatewayOk)
|
||||
{
|
||||
// Get session status
|
||||
var sessionResult = await InvokeToolAsync("session_status");
|
||||
if (sessionResult is not null)
|
||||
{
|
||||
irisStatus = sessionResult["status"]?.GetValue<string>() ?? "Active";
|
||||
}
|
||||
|
||||
// Get agents for active count
|
||||
var agents = await GetAgentsAsync();
|
||||
activeAgents = agents.Count(a => a.IsActive);
|
||||
|
||||
// Get queue/cron for pending tasks
|
||||
var queue = await GetQueueAsync();
|
||||
pendingTasks = queue.Count;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
gatewayOk = false;
|
||||
// gatewayOk stays false
|
||||
}
|
||||
|
||||
if (gatewayOk)
|
||||
{
|
||||
// Step 2: Session status
|
||||
try
|
||||
{
|
||||
var sessionResult = await InvokeToolAsync("session_status");
|
||||
if (sessionResult is not null)
|
||||
{
|
||||
irisStatus = sessionResult["status"]?.GetValue<string>()
|
||||
?? sessionResult["sessionKey"]?.GetValue<string>()
|
||||
?? "Active";
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
// Step 3: Active agents
|
||||
try
|
||||
{
|
||||
var agents = await GetAgentsAsync();
|
||||
activeAgents = agents.Count(a => a.IsActive);
|
||||
}
|
||||
catch { }
|
||||
|
||||
// Step 4: Queue items
|
||||
try
|
||||
{
|
||||
var queue = await GetQueueAsync();
|
||||
pendingTasks = queue.Count;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
return new DashboardStatus(gatewayOk, irisStatus, activeAgents, pendingTasks);
|
||||
@@ -263,6 +285,8 @@ public sealed class OpenClawGatewayClient(HttpClient httpClient, IConfiguration
|
||||
"programmer" => "Developer",
|
||||
"reviewer" => "Reviewer",
|
||||
"architekt" => "Architect",
|
||||
"executor" => "Executor",
|
||||
"researcher" => "Researcher",
|
||||
"main" => "Assistant",
|
||||
_ => "Custom"
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user