feat: ship agent-first mission control v0.2.57
This commit is contained in:
@@ -1,58 +1,107 @@
|
||||
using Nexus.Api.Helpers;
|
||||
using System.Text.RegularExpressions;
|
||||
using Nexus.Api.Models;
|
||||
|
||||
namespace Nexus.Api.Services;
|
||||
|
||||
public sealed partial class IncidentService : IIncidentService
|
||||
public sealed partial class IncidentService(
|
||||
IOpenClawAgentConfigurationService configuration) : IIncidentService
|
||||
{
|
||||
private const string BasePath = "/mnt/workspace-iris/memory/incidents";
|
||||
private const string IncidentDirectory = "memory/incidents";
|
||||
|
||||
public async Task<IReadOnlyList<IncidentSummary>> GetAllAsync()
|
||||
public async Task<IReadOnlyList<IncidentSummary>> GetAllAsync(
|
||||
string agentId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!Directory.Exists(BasePath))
|
||||
return Array.Empty<IncidentSummary>();
|
||||
|
||||
var incidents = new List<IncidentSummary>();
|
||||
foreach (var file in Directory.GetFiles(BasePath, "*.md").OrderByDescending(f => f).Take(50))
|
||||
var normalizedAgentId = NormalizeAgentId(agentId);
|
||||
OpenClawWorkspaceCollectionDto listing;
|
||||
try
|
||||
{
|
||||
var fi = new FileInfo(file);
|
||||
if (fi.Length > 1_000_000) continue;
|
||||
|
||||
var name = Path.GetFileNameWithoutExtension(file);
|
||||
var content = await File.ReadAllTextAsync(file);
|
||||
var title = ExtractTitle(name, content);
|
||||
var date = ExtractDate(name);
|
||||
var severity = ExtractSeverity(content);
|
||||
var excerpt = ExtractExcerpt(content);
|
||||
|
||||
incidents.Add(new IncidentSummary(Path.GetFileName(file), title, date, severity, excerpt, fi.Length));
|
||||
listing = await configuration.GetWorkspaceAsync(
|
||||
normalizedAgentId,
|
||||
IncidentDirectory,
|
||||
0,
|
||||
OpenClawContentReadHelpers.MaxFiles,
|
||||
cancellationToken);
|
||||
}
|
||||
catch (OpenClawGatewayRpcException exception)
|
||||
when (OpenClawContentReadHelpers.IsNotFound(exception))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return incidents;
|
||||
var entries = listing.Entries
|
||||
.Where(OpenClawContentReadHelpers.IsMarkdownFile)
|
||||
.OrderByDescending(entry => entry.Name, StringComparer.OrdinalIgnoreCase);
|
||||
return await OpenClawContentReadHelpers.SelectBoundedAsync<
|
||||
OpenClawWorkspaceEntryDto,
|
||||
IncidentSummary>(
|
||||
entries,
|
||||
async (entry, token) =>
|
||||
{
|
||||
var file = await configuration.GetWorkspaceFileAsync(
|
||||
normalizedAgentId,
|
||||
entry.Path,
|
||||
token);
|
||||
var content = OpenClawContentReadHelpers.ReadText(file);
|
||||
if (content is null)
|
||||
return null;
|
||||
var baseName = Path.GetFileNameWithoutExtension(file.Name);
|
||||
return new IncidentSummary(
|
||||
file.Name,
|
||||
ExtractTitle(baseName, content),
|
||||
ExtractDate(file.Name),
|
||||
ExtractSeverity(content),
|
||||
ExtractExcerpt(content),
|
||||
file.Size,
|
||||
normalizedAgentId,
|
||||
file.Path);
|
||||
},
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<IncidentDetail?> GetByNameAsync(string name)
|
||||
public async Task<IncidentDetail?> GetByNameAsync(
|
||||
string name,
|
||||
string agentId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!PathSecurityHelper.TryResolveSafePath(BasePath, name, out var filePath))
|
||||
if (!OpenClawContentReadHelpers.IsSafeFileName(name))
|
||||
return null;
|
||||
|
||||
if (!File.Exists(filePath!))
|
||||
var normalizedAgentId = NormalizeAgentId(agentId);
|
||||
var fileName = name.EndsWith(".md", StringComparison.OrdinalIgnoreCase)
|
||||
? name
|
||||
: name + ".md";
|
||||
try
|
||||
{
|
||||
if (!name.EndsWith(".md", StringComparison.OrdinalIgnoreCase))
|
||||
filePath = Path.Combine(BasePath, name + ".md");
|
||||
if (!File.Exists(filePath!))
|
||||
var file = await configuration.GetWorkspaceFileAsync(
|
||||
normalizedAgentId,
|
||||
$"{IncidentDirectory}/{fileName}",
|
||||
cancellationToken);
|
||||
var content = OpenClawContentReadHelpers.ReadText(file);
|
||||
if (content is null)
|
||||
return null;
|
||||
return new IncidentDetail(
|
||||
file.Name,
|
||||
ExtractTitle(
|
||||
Path.GetFileNameWithoutExtension(file.Name),
|
||||
content),
|
||||
ExtractDate(file.Name),
|
||||
content,
|
||||
file.Size,
|
||||
normalizedAgentId,
|
||||
file.Path);
|
||||
}
|
||||
catch (OpenClawGatewayRpcException exception)
|
||||
when (OpenClawContentReadHelpers.IsNotFound(exception))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var content = await File.ReadAllTextAsync(filePath!);
|
||||
var fi = new FileInfo(filePath!);
|
||||
var fileName = Path.GetFileName(filePath!);
|
||||
var title = ExtractTitle(Path.GetFileNameWithoutExtension(filePath!), content);
|
||||
var date = ExtractDate(fileName);
|
||||
|
||||
return new IncidentDetail(fileName, title, date, content, fi.Length);
|
||||
}
|
||||
|
||||
private static string NormalizeAgentId(string? agentId)
|
||||
=> string.IsNullOrWhiteSpace(agentId)
|
||||
? "iris"
|
||||
: agentId.Trim().ToLowerInvariant();
|
||||
|
||||
private static string ExtractTitle(string name, string content)
|
||||
{
|
||||
var match = TitleRegex().Match(content);
|
||||
@@ -74,7 +123,9 @@ public sealed partial class IncidentService : IIncidentService
|
||||
private static string ExtractExcerpt(string content)
|
||||
{
|
||||
var excerptEnd = content.IndexOf("\n## ", StringComparison.Ordinal);
|
||||
var excerpt = excerptEnd > 0 ? content[..excerptEnd].Trim() : content[..Math.Min(300, content.Length)].Trim();
|
||||
var excerpt = excerptEnd > 0
|
||||
? content[..excerptEnd].Trim()
|
||||
: content[..Math.Min(300, content.Length)].Trim();
|
||||
return excerpt.Length > 200 ? excerpt[..200] + "…" : excerpt;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user