docs: 为 CLI、网页编排器和插件协议补充学习向注释

在核心类型、图执行器、stdio 插件和前端入口写清职责与对照路径,不改运行行为。
This commit is contained in:
2026-08-28 11:51:48 +08:00
parent a3cca78592
commit 950d09ddb0
38 changed files with 206 additions and 3 deletions
+8
View File
@@ -2,6 +2,7 @@ using MAF1.PluginContract;
namespace MAF1.Web;
/// <summary>画布上一个端口的元数据,前端用来渲染圆点和检查器文案。</summary>
public sealed class PortInfo
{
public string Name { get; init; } = "";
@@ -10,6 +11,9 @@ public sealed class PortInfo
public bool Required { get; init; }
}
/// <summary>
/// 一种可放到画布上的节点类型。Origin=system 走进程内 HandlerOrigin=plugin 走独立进程。
/// </summary>
public sealed class AgentTypeInfo
{
public string Type { get; init; } = "";
@@ -30,6 +34,7 @@ public sealed class AgentTypeInfo
public IReadOnlyList<PluginCredentialNeed> Credentials { get; init; } = [];
}
/// <summary>系统节点绑定到哪个 C# 实现。插件节点 Handler 保持 None。</summary>
public enum SystemHandler
{
None = 0,
@@ -37,6 +42,9 @@ public enum SystemHandler
Weather,
}
/// <summary>
/// 写死在宿主里的两种系统节点。插件 id 若与 Type 冲突,运行时优先插件进程。
/// </summary>
public static class AgentCatalog
{
public static AgentTypeInfo? FindSystem(string type)
+4
View File
@@ -8,6 +8,10 @@ using Microsoft.Extensions.Configuration;
namespace MAF1.Web;
/// <summary>
/// 网页进程的组合根:把 LLM、两个系统 Agent、插件扫描/启动子进程、图执行器绑在一起。
/// ASP.NET 把它注册成 Singleton,一次请求里不要 new 第二份。
/// </summary>
public sealed class AgentRuntime
{
public AgentRuntime(IConfiguration config)
@@ -9,6 +9,11 @@ using Microsoft.Agents.AI;
namespace MAF1.Web;
/// <summary>
/// 网页自己的图执行器(不是 Microsoft Agents AI Workflow)。
/// 流程:拓扑排序 → 按边接线组装输入 → 跑系统节点或插件进程 → 若抽城市失败则暂停等人。
/// 和 CLI 对照:这里用 DAG + when 条件,CLI 用 WorkflowBuilder 的 Executor 图。
/// </summary>
public sealed class ConfigurableWorkflowRunner(
AIAgent fileCityAgent,
AIAgent weatherAgent,
@@ -17,6 +22,7 @@ public sealed class ConfigurableWorkflowRunner(
WorkflowRunStore runs,
int decisionTimeoutSeconds)
{
/// <summary>这些 config 键不是业务输入端口,接线时不要当 filePath 那样传给 Agent。</summary>
private static readonly HashSet<string> ReservedConfigKeys = new(StringComparer.OrdinalIgnoreCase)
{
"credentialId",
@@ -24,6 +30,7 @@ public sealed class ConfigurableWorkflowRunner(
"decisionTimeoutSeconds",
};
/// <summary>开始一次运行。后面若暂停,用同一 runId 继续 DecideAsync。</summary>
public async Task<WorkflowRunResult> RunAsync(WorkflowGraph graph, CancellationToken cancellationToken)
{
try
@@ -62,6 +69,7 @@ public sealed class ConfigurableWorkflowRunner(
return await ResolveAsync(session, answer, cancellationToken, session.Pending?.Id);
}
/// <summary>从 session.NextIndex 接着跑。条件不满足的节点记 Skipped,不调用 LLM。</summary>
private async Task<WorkflowRunResult> ContinueAsync(WorkflowSession session, CancellationToken cancellationToken)
{
try
@@ -107,6 +115,7 @@ public sealed class ConfigurableWorkflowRunner(
}
}
/// <summary>返回 needsDecision,并在后台倒计时到点后按默认方案 ResolveAsync。</summary>
private WorkflowRunResult PauseForEmptyCities(WorkflowSession session, WorkflowNode node, NodeRunLog log)
{
string? reason = ReadString(log.Outputs, "reason");
@@ -167,6 +176,7 @@ public sealed class ConfigurableWorkflowRunner(
});
}
/// <summary>把确认结果写回该节点的 outputs(例如改写 cities),然后继续后续节点。</summary>
private async Task<WorkflowRunResult> ResolveAsync(
WorkflowSession session,
DecisionAnswer answer,
@@ -319,6 +329,7 @@ public sealed class ConfigurableWorkflowRunner(
Steps = steps ?? [],
};
/// <summary>节点输出了 hasValidCities=false,且后面还有节点时,默认要弹确认。config 可关。</summary>
private static bool ShouldAskEmptyCities(WorkflowNode node, NodeRunLog log)
{
if (log.Skipped)
@@ -340,6 +351,7 @@ public sealed class ConfigurableWorkflowRunner(
return !ReadBool(log.Outputs, "hasValidCities");
}
/// <summary>插件 id 优先于系统节点。系统节点按 Handler 调 FileCityAgent / WeatherAgent。</summary>
private async Task<NodeRunLog> RunNodeAsync(
NodeCatalogSnapshot snapshot,
WorkflowNode node,
@@ -439,6 +451,7 @@ public sealed class ConfigurableWorkflowRunner(
}
}
/// <summary>Kahn 算法拓扑排序。有环则无法确定执行顺序。</summary>
private static List<string> TopologicalOrder(WorkflowGraph graph)
{
Dictionary<string, int> indegree = graph.Nodes.ToDictionary(n => n.Id, _ => 0);
@@ -473,6 +486,7 @@ public sealed class ConfigurableWorkflowRunner(
return order;
}
/// <summary>先填节点 Config,再用入边把上游输出端口覆盖到下游输入端口(连线优先)。</summary>
private static Dictionary<string, object?> ResolveInputs(
WorkflowGraph graph,
WorkflowNode node,
@@ -505,6 +519,7 @@ public sealed class ConfigurableWorkflowRunner(
return inputs;
}
/// <summary>入边 When 不满足则跳过本节点。当前只实现 hasValidCities 这一类条件。</summary>
private static bool PassEdgeConditions(
WorkflowGraph graph,
WorkflowNode node,
+8
View File
@@ -2,12 +2,14 @@ using MAF1.Decisions;
namespace MAF1.Web;
/// <summary>浏览器 POST /api/run 的图:节点 + 连线。和前端 state.nodes / state.edges 一一对应。</summary>
public sealed class WorkflowGraph
{
public List<WorkflowNode> Nodes { get; set; } = [];
public List<WorkflowEdge> Edges { get; set; } = [];
}
/// <summary>画布节点。Config 里可写 filePath、credentialId、decisionTimeoutSeconds 等固定输入。</summary>
public sealed class WorkflowNode
{
public string Id { get; set; } = "";
@@ -18,6 +20,9 @@ public sealed class WorkflowNode
public Dictionary<string, string> Config { get; set; } = new(StringComparer.OrdinalIgnoreCase);
}
/// <summary>
/// 一条边:FromPort → ToPort 传数据。When 是运行条件(hasValidCities / !hasValidCities / 空=始终)。
/// </summary>
public sealed class WorkflowEdge
{
public string Id { get; set; } = "";
@@ -28,6 +33,7 @@ public sealed class WorkflowEdge
public string? When { get; set; }
}
/// <summary>一次运行的状态机:completed / needsDecision / failed。</summary>
public static class WorkflowRunStatus
{
public const string Completed = "completed";
@@ -35,6 +41,7 @@ public static class WorkflowRunStatus
public const string Failed = "failed";
}
/// <summary>返回给前端的运行快照。暂停时带 Decision,方便弹出确认框。</summary>
public sealed class WorkflowRunResult
{
public bool Ok { get; set; }
@@ -46,6 +53,7 @@ public sealed class WorkflowRunResult
public List<NodeRunLog> Steps { get; set; } = [];
}
/// <summary>单个节点的执行日志,显示在页面底部「运行结果」。</summary>
public sealed class NodeRunLog
{
public string NodeId { get; set; } = "";
+5
View File
@@ -4,6 +4,10 @@ using MAF1.Plugins;
namespace MAF1.Web;
/// <summary>
/// 一次可暂停的运行。NextIndex 记住停在第几个节点;Pending 是当前确认请求。
/// Mutex 防止「用户点击」和「超时自动确认」同时 Continue。
/// </summary>
internal sealed class WorkflowSession
{
public required string RunId { get; init; }
@@ -22,6 +26,7 @@ internal sealed class WorkflowSession
public object Sync { get; } = new();
}
/// <summary>进程内运行字典。服务重启后 runId 会失效,学习项目不做持久化。</summary>
public sealed class WorkflowRunStore
{
private readonly ConcurrentDictionary<string, WorkflowSession> _sessions = new(StringComparer.OrdinalIgnoreCase);