feat: ship agent-first mission control v0.2.57
This commit is contained in:
+110
-56
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user