feat: ship agent-first mission control v0.2.57
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

This commit is contained in:
AzuTear
2026-07-31 22:39:47 +02:00
parent 3bc7622977
commit f5552218bc
535 changed files with 95242 additions and 8791 deletions
+110 -56
View File
@@ -1,75 +1,129 @@
using Nexus.Api.Helpers;
using Nexus.Api.Models;
namespace Nexus.Api.Services;
public sealed class DocService : IDocService
public sealed class DocService(
IOpenClawAgentConfigurationService configuration) : IDocService
{
private static readonly string[] AllowedExtensions = [".md", ".json", ".txt", ".yaml", ".yml", ".html", ".css"];
private static readonly string[] SearchRoots =
private static readonly HashSet<string> AllowedExtensions =
new(
[".md", ".json", ".txt", ".yaml", ".yml", ".html", ".css"],
StringComparer.OrdinalIgnoreCase);
private static readonly (string Path, string Category)[] ScanDirectories =
[
"/mnt/workspace-iris",
"/home/node/.openclaw/workspace/nexus"
("", "workspace"),
("nexus-phases", "phases"),
("skills", "skills"),
("nexus", "nexus"),
("nexus/phases", "nexus-phases")
];
private static readonly (string Dir, string Category)[] ScanDirectories =
[
("/mnt/workspace-iris/nexus-phases", "phases"),
("/mnt/workspace-iris/skills", "skills"),
("/mnt/workspace-iris", "workspace"),
("/home/node/.openclaw/workspace/nexus", "nexus"),
("/home/node/.openclaw/workspace/nexus/phases", "nexus-phases")
];
public IReadOnlyList<DocFileInfo> GetAll()
public async Task<IReadOnlyList<DocFileInfo>> GetAllAsync(
string agentId,
CancellationToken cancellationToken = default)
{
var results = new List<DocFileInfo>();
foreach (var (dir, category) in ScanDirectories)
{
if (!Directory.Exists(dir)) continue;
foreach (var file in Directory.GetFiles(dir, "*.*"))
var normalizedAgentId = NormalizeAgentId(agentId);
var directories = await OpenClawContentReadHelpers.SelectBoundedAsync<
(string Path, string Category),
DirectoryResult>(
ScanDirectories,
async (directory, token) =>
{
var ext = Path.GetExtension(file).ToLowerInvariant();
if (!AllowedExtensions.Contains(ext)) continue;
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);
var fi = new FileInfo(file);
results.Add(new DocFileInfo(
fi.Name,
file.Replace("/mnt/workspace-iris", "").TrimStart('/'),
category,
ext.Replace(".", ""),
fi.Length,
fi.LastWriteTimeUtc));
}
}
return results.OrderByDescending(x => x.ModifiedAt).Take(100).ToList();
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)
public async Task<DocFileContent?> GetFileAsync(
string path,
string agentId,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(path))
return null;
string? resolvedPath = null;
foreach (var root in SearchRoots)
if (string.IsNullOrWhiteSpace(path)
|| !AllowedExtensions.Contains(Path.GetExtension(path)))
{
if (PathSecurityHelper.TryResolveSafePath(root, path, out var candidate) && File.Exists(candidate))
{
resolvedPath = candidate;
break;
}
return null;
}
if (resolvedPath is 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;
var content = await File.ReadAllTextAsync(resolvedPath);
var fi = new FileInfo(resolvedPath);
var relativePath = resolvedPath
.Replace("/mnt/workspace-iris/", "")
.Replace("/home/node/.openclaw/workspace/nexus/", "");
return new DocFileContent(fi.Name, relativePath, content, fi.Length, fi.LastWriteTimeUtc);
}
}
private static string NormalizeAgentId(string? agentId)
=> string.IsNullOrWhiteSpace(agentId)
? "iris"
: agentId.Trim().ToLowerInvariant();
private sealed record DirectoryResult(
string Category,
OpenClawWorkspaceCollectionDto Listing);
}