Files
AzuTear f5552218bc
CI - Build & Test / Backend (.NET) (push) Successful in 42s
CI - Build & Test / Frontend (Vue/TS) (push) Successful in 2m46s
CI - Build & Test / Security Check (push) Successful in 3s
CI - Build & Test / Deploy Nexus (push) Successful in 56s
feat: ship agent-first mission control v0.2.57
2026-07-31 22:39:47 +02:00

141 lines
4.8 KiB
C#

using System.Text.RegularExpressions;
using Nexus.Api.Models;
namespace Nexus.Api.Services;
public sealed partial class IncidentService(
IOpenClawAgentConfigurationService configuration) : IIncidentService
{
private const string IncidentDirectory = "memory/incidents";
public async Task<IReadOnlyList<IncidentSummary>> GetAllAsync(
string agentId,
CancellationToken cancellationToken = default)
{
var normalizedAgentId = NormalizeAgentId(agentId);
OpenClawWorkspaceCollectionDto listing;
try
{
listing = await configuration.GetWorkspaceAsync(
normalizedAgentId,
IncidentDirectory,
0,
OpenClawContentReadHelpers.MaxFiles,
cancellationToken);
}
catch (OpenClawGatewayRpcException exception)
when (OpenClawContentReadHelpers.IsNotFound(exception))
{
return [];
}
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,
string agentId,
CancellationToken cancellationToken = default)
{
if (!OpenClawContentReadHelpers.IsSafeFileName(name))
return null;
var normalizedAgentId = NormalizeAgentId(agentId);
var fileName = name.EndsWith(".md", StringComparison.OrdinalIgnoreCase)
? name
: name + ".md";
try
{
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;
}
}
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);
return match.Success ? match.Groups[1].Value.Trim() : name;
}
private static string? ExtractDate(string fileName)
{
var match = DateRegex().Match(fileName);
return match.Success ? match.Groups[1].Value : null;
}
private static string ExtractSeverity(string content)
{
var match = SeverityRegex().Match(content);
return match.Success ? match.Groups[1].Value.Trim() : "unknown";
}
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();
return excerpt.Length > 200 ? excerpt[..200] + "…" : excerpt;
}
[GeneratedRegex(@"^#\s+(.+)$", RegexOptions.Multiline)]
private static partial Regex TitleRegex();
[GeneratedRegex(@"^(\d{4}-\d{2}-\d{2})")]
private static partial Regex DateRegex();
[GeneratedRegex(@"\*\*Severity:\*\*\s*(.+)$", RegexOptions.Multiline)]
private static partial Regex SeverityRegex();
}