1. 项目概述构建带审批机制的终端Agent在需要执行敏感系统操作的场景中直接让AI代理自动运行命令存在显著风险。去年微软开源的Agent Framework框架恰好提供了人在环(Human-in-the-loop)的解决方案这正是我选择用它来构建终端助手的原因。这个WPF应用的核心价值在于当代理试图执行任何终端命令时都会强制弹出审批窗口只有获得人工确认后才会真正执行操作。典型应用场景包括生产环境中需要谨慎执行的系统维护命令涉及敏感数据处理的自动化流程需要审计留痕的关键操作新手学习系统命令时的安全沙箱环境2. 技术栈深度解析2.1 Microsoft Agent Framework架构剖析这个框架本质上是对Semantic Kernel和AutoGen的整合升级主要包含三个关键层代理核心层基于聊天补全模型(如GPT-4)的推理引擎工具调用(Tool Calling)的运行时支持对话线程(Thread)的会话状态管理扩展功能层多代理协作的工作流引擎人工审批中间件记忆管理(Context Providers)基础设施层多模型支持(Azure/OpenAI)函数调用监控异常处理管道特别值得注意的是其审批机制实现方式当代理检测到函数标记了[ApprovalRequired]特性时会自动暂停执行并将控制权交还给前端应用。2.2 WPF与框架的集成方案在MVVM架构下关键组件这样划分职责// View层 TerminalView DataContext{Binding TerminalVM} // ViewModel层 public class TerminalAgentViewModel : INotifyPropertyChanged { private readonly IAIAgent _agent; private readonly IWindowManager _windowManager; public ICommand ExecuteCommand { get; } private async Task ExecuteAsync() { // 处理审批流程的核心逻辑 } } // Model层 public static class CommandExecutor { [Description(执行cmd命令)] public static string Execute(string script) { // 实际执行命令的逻辑 } }3. 核心实现细节3.1 命令执行函数的安全封装命令执行器需要特别注意以下几点防御措施[Description(Execute cmd script)] public static string ExecuteCmd( [Description(要执行的脚本内容)] string script) { // 参数安全检查 if (script.Contains(format) || script.Contains(rmdir)) { return 危险命令已被拦截; } var psi new ProcessStartInfo { FileName cmd.exe, Arguments $/c {script}, UseShellExecute false, // 禁止Shell执行防止注入 RedirectStandardOutput true, CreateNoWindow true, // 不显示黑窗口 StandardOutputEncoding Encoding.UTF8 }; // 超时设置 using var process new Process { StartInfo psi }; var outputBuilder new StringBuilder(); process.OutputDataReceived (_, e) outputBuilder.AppendLine(e.Data); process.Start(); process.BeginOutputReadLine(); if (!process.WaitForExit(5000)) // 5秒超时 { process.Kill(); return 命令执行超时; } return outputBuilder.ToString(); }3.2 审批流程的完整实现审批流程的状态机如下用户输入命令请求Agent生成函数调用意图框架检测到需要审批 → 触发UserInputRequest事件WPF弹出审批对话框用户选择批准/拒绝结果回传给Agent继续执行关键代码实现var response await _agent.RunAsync(input, thread); while (response.UserInputRequests.Any()) { var responses new ListChatMessage(); foreach (var request in response.UserInputRequests.OfTypeFunctionApprovalRequestContent()) { var dialog new ApprovalDialog { Title 命令审批, Command request.FunctionCall.Arguments[script].ToString() }; bool? result _dialogService.Show(dialog); responses.Add(request.CreateResponse(result true)); } response await _agent.RunAsync(responses, thread); }4. 进阶功能实现4.1 流式输出优化原始方案的输出是整体返回的对于长命令不友好。改进方案private async Task ProcessStreamingResponseAsync(AgentThread thread) { var streamingResponse _agent.RunStreamingAsync(, thread); var outputBuffer new StringBuilder(); await foreach (var update in streamingResponse) { if (update.Type StreamingUpdateType.ContentUpdate) { outputBuffer.Append(update.Text); OutputText outputBuffer.ToString(); } else if (update.Type StreamingUpdateType.FunctionCallUpdate) { // 实时显示函数调用状态 StatusText $正在执行: {update.FunctionName}; } } }4.2 命令历史记录添加SQLite支持命令审计public class CommandHistoryService { private readonly string _dbPath history.db; public async Task AddRecordAsync(string command, bool approved) { using var conn new SqliteConnection($Data Source{_dbPath}); await conn.OpenAsync(); var cmd conn.CreateCommand(); cmd.CommandText INSERT INTO History VALUES (time, cmd, approved); cmd.Parameters.AddWithValue(time, DateTime.Now); cmd.Parameters.AddWithValue(cmd, command); cmd.Parameters.AddWithValue(approved, approved); await cmd.ExecuteNonQueryAsync(); } }5. 安全增强方案5.1 命令白名单机制在App.xaml.cs中初始化时加载允许的命令列表public partial class App : Application { public static HashSetstring AllowedCommands { get; } new() { dir, time, echo, type, copy }; protected override void OnStartup(StartupEventArgs e) { // 加载更完整的白名单 var lines File.ReadAllLines(commands-whitelist.txt); foreach (var cmd in lines.Where(l !l.StartsWith(#))) { AllowedCommands.Add(cmd.Trim()); } } }5.2 敏感词过滤中间件创建自定义中间件public class SafetyMiddleware : IAgentMiddleware { public async Task InvokeAsync(AgentContext context, NextMiddleware next) { if (context.FunctionCall ! null) { var args context.FunctionCall.Arguments; if (args.ContainsKey(script)) { var script args[script].ToString(); if (ContainsDangerousCommands(script)) { context.Result new AgentResult { Status AgentResultStatus.Failure, ErrorMessage 包含危险命令 }; return; } } } await next(context); } private bool ContainsDangerousCommands(string script) { var dangerous new[] { del, format, reg }; return dangerous.Any(c script.Contains(c)); } }注册中间件agent.UseMiddlewareSafetyMiddleware();6. 性能优化技巧6.1 响应缓存实现对于常见命令结果进行缓存private static readonly ConcurrentDictionarystring, string _commandCache new(); [Description(执行命令带缓存)] public static string ExecuteWithCache(string script) { if (_commandCache.TryGetValue(script, out var cached)) return $(缓存结果)\n{cached}; var result ExecuteCmd(script); _commandCache.TryAdd(script, result); return result; }6.2 异步并行处理优化审批流程的并行处理var approvalTasks userInputRequests .OfTypeFunctionApprovalRequestContent() .Select(async request { var dialog CreateDialog(request); return new { Request request, Result await ShowDialogAsync(dialog) }; }); var approvals await Task.WhenAll(approvalTasks); var responses approvals.Select(a a.Request.CreateResponse(a.Result true));7. 调试与问题排查7.1 常见错误处理错误现象可能原因解决方案命令无响应进程死锁增加WaitForExit超时中文乱码编码问题设置StandardOutputEncoding审批不生效未标记特性检查[ApprovalRequired]流式中断线程冲突配置Dispatcher.BeginInvoke7.2 诊断日志配置使用Serilog记录详细日志Log.Logger new LoggerConfiguration() .WriteTo.File(agent.log, outputTemplate: {Timestamp:HH:mm:ss} [{Level}] {Message}{NewLine}{Exception}) .WriteTo.Debug() .CreateLogger(); // 在关键位置添加日志 Log.Information(执行命令: {Command}, script); try { // ... } catch (Exception ex) { Log.Error(ex, 命令执行失败); }8. 项目扩展方向8.1 多代理协作方案构建检查-执行双代理系统var checker agentFactory.CreateAgent(安全检查员, 你负责检查命令安全性需要 1. 分析命令潜在风险 2. 标记需要特别审批的命令 3. 对高危命令直接拒绝 ); var executor agentFactory.CreateAgent(命令执行员, 你负责实际执行通过安全检查的命令 ); var workflow new AgentWorkflow() .AddNode(checker) .AddNode(executor) .AddEdge(checker, executor, ctx ctx.Result.Status AgentResultStatus.Success);8.2 界面美化建议使用ModernWPF库改进UIWindow ... xmlns:uihttp://schemas.modernwpf.com/2021 ui:SimpleStackPanel Spacing8 ui:ToggleSwitch Header安全模式 IsOn{Binding IsSafeMode}/ ui:AutoSuggestBox QueryIconFind Text{Binding CommandText}/ ui:ScrollViewer TextBlock Text{Binding OutputText} FontFamilyCascadia Mono/ /ui:ScrollViewer /ui:SimpleStackPanel /Window实际开发中发现几个关键点审批对话框的模态处理需要特别注意线程亲和性建议使用Dispatcher.Invoke长时间运行的命令会导致UI冻结需要改为后台线程执行OpenAI的响应速度不稳定需要添加超时重试机制。对于需要更高安全级别的场景可以考虑整合Windows Hello进行生物特征验证后再执行敏感命令。