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

130 lines
4.3 KiB
C#

using Nexus.Api.Models;
namespace Nexus.Api.Services;
public sealed class DocService(
IOpenClawAgentConfigurationService configuration) : IDocService
{
private static readonly HashSet<string> AllowedExtensions =
new(
[".md", ".json", ".txt", ".yaml", ".yml", ".html", ".css"],
StringComparer.OrdinalIgnoreCase);
private static readonly (string Path, string Category)[] ScanDirectories =
[
("", "workspace"),
("nexus-phases", "phases"),
("skills", "skills"),
("nexus", "nexus"),
("nexus/phases", "nexus-phases")
];
public async Task<IReadOnlyList<DocFileInfo>> GetAllAsync(
string agentId,
CancellationToken cancellationToken = default)
{
var normalizedAgentId = NormalizeAgentId(agentId);
var directories = await OpenClawContentReadHelpers.SelectBoundedAsync<
(string Path, string Category),
DirectoryResult>(
ScanDirectories,
async (directory, token) =>
{
try
{
var listing = await configuration.GetWorkspaceAsync(
normalizedAgentId,
directory.Path,
0,
100,
token);
return new DirectoryResult(
directory.Category,
listing);
}
catch (OpenClawGatewayRpcException exception)
when (OpenClawContentReadHelpers.IsNotFound(exception))
{
return null;
}
},
cancellationToken);
return directories
.SelectMany(directory => directory.Listing.Entries
.Where(entry =>
string.Equals(entry.Kind, "file", StringComparison.Ordinal)
&& !(string.IsNullOrEmpty(directory.Listing.Path)
&& string.Equals(
entry.Name,
"MEMORY.md",
StringComparison.OrdinalIgnoreCase))
&& AllowedExtensions.Contains(
Path.GetExtension(entry.Name)))
.Select(entry => new DocFileInfo(
entry.Name,
entry.Path,
directory.Category,
Path.GetExtension(entry.Name).TrimStart('.')
.ToLowerInvariant(),
entry.Size ?? 0,
(entry.UpdatedAt
?? directory.Listing.CheckedAt).UtcDateTime,
normalizedAgentId,
entry.Path)))
.OrderByDescending(item => item.ModifiedAt)
.Take(100)
.ToArray();
}
public async Task<DocFileContent?> GetFileAsync(
string path,
string agentId,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(path)
|| !AllowedExtensions.Contains(Path.GetExtension(path)))
{
return null;
}
var normalizedAgentId = NormalizeAgentId(agentId);
try
{
var file = await configuration.GetWorkspaceFileAsync(
normalizedAgentId,
path,
cancellationToken);
var content = OpenClawContentReadHelpers.ReadText(file);
if (content is null
|| !AllowedExtensions.Contains(Path.GetExtension(file.Name)))
{
return null;
}
return new DocFileContent(
file.Name,
file.Path,
content,
file.Size,
(file.UpdatedAt ?? file.CheckedAt).UtcDateTime,
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 sealed record DirectoryResult(
string Category,
OpenClawWorkspaceCollectionDto Listing);
}