using MAF1.PluginContract; using MAF1.Web; namespace MAF1.Plugins; /// 一次目录快照:系统节点 + 插件节点合并后的 Nodes 列表给画布用。 public sealed class NodeCatalogSnapshot { public string PluginsRoot { get; init; } = ""; public List System { get; init; } = []; public List Plugins { get; init; } = []; public List Nodes { get; init; } = []; public List Issues { get; init; } = []; public IReadOnlyList LoadedPlugins { get; init; } = []; } /// 把扫描结果和 AgentCatalog 拼在一起。同名时插件覆盖系统实现。 public sealed class NodeCatalogService(PluginScanner scanner) { public NodeCatalogSnapshot Load() { PluginScanResult scan = scanner.Scan(); List issues = [.. scan.Issues]; List system = AgentCatalog.SystemNodes .Select(Clone) .ToList(); foreach (AgentTypeInfo item in system) { item.Origin = "system"; } Dictionary systemByType = system.ToDictionary(n => n.Type, StringComparer.OrdinalIgnoreCase); List plugins = []; foreach (LoadedPlugin plugin in scan.Plugins) { AgentTypeInfo info = ToTypeInfo(plugin); if (systemByType.ContainsKey(info.Type)) { issues.Add(new CatalogIssue { Level = "info", Source = info.Type, Message = $"插件 `{info.Type}` 覆盖了同名系统节点,画布运行将走插件进程。", }); systemByType[info.Type].Overridden = true; } plugins.Add(info); } Dictionary merged = systemByType .ToDictionary(kv => kv.Key, kv => kv.Value, StringComparer.OrdinalIgnoreCase); foreach (AgentTypeInfo plugin in plugins) { merged[plugin.Type] = plugin; } return new NodeCatalogSnapshot { PluginsRoot = scan.Root, System = system, Plugins = plugins, Nodes = merged.Values.OrderBy(n => n.Origin).ThenBy(n => n.Name).ToList(), Issues = issues, LoadedPlugins = scan.Plugins, }; } public LoadedPlugin? FindPlugin(string type) => Load().LoadedPlugins.FirstOrDefault(p => p.Manifest.Id.Equals(type, StringComparison.OrdinalIgnoreCase)); private static AgentTypeInfo ToTypeInfo(LoadedPlugin plugin) { PluginManifest manifest = plugin.Manifest; return new AgentTypeInfo { Type = manifest.Id, Name = string.IsNullOrWhiteSpace(manifest.Name) ? manifest.Id : manifest.Name, Description = manifest.Description, Origin = "plugin", Version = manifest.Version, Folder = plugin.FolderName, Inputs = manifest.Inputs.Select(ToPort).ToList(), Outputs = manifest.Outputs.Select(ToPort).ToList(), Credentials = manifest.Credentials, }; } private static PortInfo ToPort(PluginPort port) => new() { Name = port.Name, Type = port.Type, Description = port.Description, Required = port.Required, }; private static AgentTypeInfo Clone(AgentTypeInfo source) => new() { Type = source.Type, Name = source.Name, Description = source.Description, Origin = source.Origin, Version = source.Version, Folder = source.Folder, Overridden = source.Overridden, Handler = source.Handler, Inputs = source.Inputs, Outputs = source.Outputs, Credentials = source.Credentials, }; }