1 前言上一篇博客介绍了 Agent 层的整体架构、LLM 客户端设计和工具注册系统。这篇记录剩余几个模块的实现细节Agent Runtime 的执行循环、安全策略的设计思路、配置持久化的问题修复以及 GlobalSettingsPanel 从占位符到完整功能的改造。2 Agent Runtime支持 Tool Call 的多轮执行循环agentRuntime.ts是整个 Agent 层的执行核心。它的职责是接收任务描述驱动 LLM 完成推理处理 Tool Call 的多轮迭代解析最终输出并在任何环节出错时触发降级。核心函数签名如下export async function runAgentWithFallbackT( config: LlmConfig | undefined, task: AgentTask, parseResult?: (rawOutput: string) T | null ): PromiseAgentResultT泛型参数T是调用方期望的输出类型parseResult由调用方传入负责把 LLM 的原始文本转换成结构化数据。这样设计的好处是 Runtime 本身不感知具体的输出格式每个 P1 工作流用自己的 Schema 来解析互不干扰。执行流程分五步第一步前置检查。如果config为空或apiKey未填写直接走降级路径返回模板结果不尝试调用 LLM。这保证了在用户未配置 AI 的情况下基础 Git 功能完全不受影响。第二步组装消息。将task.systemPrompt和task.userMessage组装为标准的 messages 数组连同task.tools里声明的工具定义一起发给 LLM。第三步Tool Call 循环。LLM 可能在一次对话里多次调用工具每次调用都需要执行对应工具、把结果追加到消息记录、再继续推理。循环在finish_reason stop时终止。while (true) { const response await client.chat(messages, toolDefs) if (response.finishReason tool_calls) { for (const call of response.toolCalls) { const toolResult await toolRegistry.execute(call.name, call.arguments) messages.push({ role: tool, content: toolResult, toolCallId: call.id }) } continue } // finish_reason stop退出循环 rawOutput response.content break }第四步解析输出。调用传入的parseResult函数处理 LLM 的原始文本。如果解析失败返回null自动进入降级。第五步异常兜底。整个执行过程包在 try-catch 里任何未预期的异常都触发降级而不是向上抛出导致界面报错。3 三套 Prompt 模板prompts/目录下针对三个 P1 工作流各维护一套 Prompt 模板均支持参数渲染。提交工作流 Prompt接收 unified diff 作为输入要求 LLM 输出符合 Conventional Commits 规范的结构化提交信息输入: unified diff 输出: { type, scope, subject, body, breaking }System Prompt 里明确了输出格式要求并给出了feat/fix/refactor/chore等类型的定义和使用场景减少模型在类型判断上的歧义。冲突管控 Prompt接收三方内容ancestor / ours / theirs作为输入要求 LLM 给出合并策略和建议代码输入: ancestor / ours / theirs 三方内容 输出: { strategy: take_ours | take_theirs | merge_both | manual, resolvedContent }strategy字段限定了四个枚举值防止模型输出无法处理的策略描述。manual表示模型认为无法自动解决需要用户介入。自然语言助手 Prompt接收用户的自然语言输入输出结构化的操作计划输入: 用户自然语言 输出: { intent, operations[{ command, args, riskLevel }], requiresWorkflow }riskLevel字段要求模型对每条 Git 命令做风险标注这个结果会直接传入安全策略模块做二次验证。4 安全策略三级风险分级safety.ts实现了基于正则规则的命令风险分级分三档级别行为典型示例safe直接执行git status、git log、git diffhigh需用户二次确认git push --force、git reset --hard、git rebaseextreme默认阻止push --force到main/master、git clean -fexport function checkCommandRisk(command: string): SafetyCheckResult { for (const rule of EXTREME_RISK_RULES) { if (rule.pattern.test(command)) { return { riskLevel: extreme, reason: rule.reason, blocked: true } } } for (const rule of HIGH_RISK_RULES) { if (rule.pattern.test(command)) { return { riskLevel: high, reason: rule.reason, blocked: false } } } return { riskLevel: safe, blocked: false } }extreme级别的判断优先于high避免规则覆盖顺序导致的漏判。blocked: true的命令在 NLP 助手页面以已阻止状态记录用户需要在设置中显式解锁才能执行。安全策略在两个位置被调用NLP 工作流在执行转译后的命令前调用Agent Runtime 在处理 Tool Call 里的 Git 操作前调用。两处校验互相独立不存在可以绕过其中一处的路径。5 降级处理LLM 不可用时的保底策略fallback.ts的设计原则是AI 功能不可用时不影响基础 Git 操作同时给用户一个可用的默认结果而不是显示错误状态。按taskType分派三种降级结果commit.generateMessage根据已暂存的文件数量生成格式化的占位提交信息例如chore: update 3 filesconflict.suggestResolution返回strategy: manual提示用户手动解决不给出具体建议nl_assistant对用户输入做关键词匹配尝试识别基础意图push / pull / commit / status匹配失败时提示无法解析请直接使用 Git 命令降级结果在格式上和正常 AI 输出完全一致上层代码不需要区分处理。6 配置持久化的 Bug 修复在接入 LLM 配置时发现了一个已有的持久化逻辑问题persistConfig函数在写入时直接构造了一个新的AppConfig对象没有读取当前存储的完整配置导致每次保存仓库列表时会把llmConfig字段覆盖为空。// 修改前直接构造丢失其他字段 const config: AppConfig { repos, currentRepoPath } // 修改后先读取当前配置再合并写入 const current await loadConfig() const config: AppConfig { ...current, repos, currentRepoPath }这类问题在功能模块相互独立开发时容易出现——各自写入同一份配置文件但没有协调好合并逻辑。修复方式是在写入前先读取全量配置用展开运算符合并确保只更新自己负责的字段。7 GlobalSettingsPanel 与 StatusBar 改造GlobalSettingsPanel 从占位 UI 改为完整的配置界面包含以下交互元素Provider 选择OpenAI 兼容 / Anthropic切换时界面联动显示/隐藏 Base URL 输入项API Key 输入框密码模式隐藏内容Base URL 输入项OpenAI 兼容模式下显示用于接入 DeepSeek 等兼容接口模型名称输入框Temperature 滑块0.0 ~ 2.0和 Max Tokens 滑块连接测试按钮点击后调用ping()验证 API Key 有效性结果实时显示StatusBar 的 AI 状态指示灯从硬编码的API 已连接改为动态状态对应四种状态// unconfigured用户未填写 API Key灰点 // checking正在验证连接黄点 // ready连接正常绿点 // error连接失败红点状态数据来源于llmConfigStoreping()的结果写入 storeStatusBar 订阅变化自动更新不需要额外的通信逻辑。8 阶段小结P0 Agent 框架完成后项目的 AI 基础层具备了可用状态LLM 多 Provider 支持、Tool Call 执行循环、三套工作流 Prompt、安全分级策略、结构化输出解析、完整降级兜底以及配置界面和状态可视化。下一步是推进 P1 工作流的接入——在这套框架上注册 Git 工具实现把智能提交、冲突管控、自然语言助手三个功能从占位符变成真正可用的交互。