Tolaria 可选择的 CLI AI Agent 架构:ADR-0062 与共享面板的源码级实现
2026/9/13 7:44:23 网站建设 项目流程

Tolaria 可选择的 CLI AI Agent 架构:ADR-0062 与共享面板的源码级实现

【免费下载链接】tolariaDesktop app to manage markdown knowledge bases项目地址: https://gitcode.com/GitHub_Trending/to/tolaria

Tolaria 的 AI 面板最初只为 Claude Code 一个 CLI 服务,这使每一处 UI 与后端接缝都带有单 Agent 的假设。ADR-0062 决定了引入一套「共享 CLI-Agent 抽象」:前端把 Agent 视为小型注册表,后端由ai_agents.rs统一负责探测与流式分发。读完本文,你将理解这套抽象的前后端模型、统一的流事件协议、default_ai_agent设置为何存放在应用设置而非 vault,以及如何在 Tolaria 中低成本地新增一个 CLI Agent。

背景:从单 CLI 依赖走向多 Agent

ADR-0062(docs/adr/0062-selectable-cli-ai-agents.md,2026-04-13,状态 active)给出的背景是:Tolaria 的 AI 面板、引导流程(onboarding)和状态界面都围绕单个 CLI 依赖 Claude Code 构建。这在首个版本可行,但让所有 UI 与后端接缝都变得 Agent 特化——若要加入 Codex 作为第二个受支持的 CLI Agent,需要复制大量代码:独立的可用性检查、第二条 onboarding 路径、另一个状态徽章、又一个流式 hook。

产品方向比单一供应商更宽:Tolaria 需要一个能面向多个本地 CLI Agent 的 AI 面板,同时保持相同的 MCP-backed vault 工具集、相同的笔记上下文组装(note-context assembly),以及一个本地持久化的「默认 Agent」偏好。仓库中的 MCP 服务由 mcp-server/ 目录承载(如 mcp-server/index.js 与 mcp-server/tool-service.js),这是所有 Agent 共用的 vault 工具层。

决策:前端注册表 + 后端适配器层

ADR 的核心决策是:为 Tolaria 的所有 AI 表面引入共享的 CLI-Agent 抽象。前端把 Agent 视为一个小型注册表,携带标签(label)、安装链接(install URL)、可用性状态,以及持久化的default_ai_agent设置;AI 面板、onboarding 门禁、命令面板和状态栏都从这个共享模型读取。后端则由 src-tauri/src/ai_agents.rs 拥有 Agent 探测与流式处理,向每个 Agent 的适配器分发:Claude 仍走claude_cli.rs,Codex 则通过codex exec --json启动,并用瞬态配置标志(transient config flags)注入 Tolaria 的 MCP 服务。

前端:Agent 注册表与默认偏好

前端注册表位于 src/lib/aiAgents.ts。ADR 决策时注册表只有claude_codecodex两项,当前代码中的AI_AGENT_DEFINITIONS已扩展到 8 个 Agent,每个条目包含idlabelshortLabelinstallUrl

export type AiAgentId = | 'claude_code' | 'codex' | 'copilot' | 'opencode' | 'pi' | 'antigravity' | 'kiro' | 'hermes' export interface AiAgentDefinition { id: AiAgentId label: string shortLabel: string installUrl: string } export const DEFAULT_AI_AGENT: AiAgentId = 'claude_code'

这正体现了 ADR 后果部分的第一条正面结论:「新的 CLI Agent 可以通过实现一个后端适配器并注册一个前端定义来添加」。可用性模型是三态的:checking/installed/missingnormalizeAiAgentsStatus()把后端 IPC 载荷归一化为该模型,缺省字段一律回退为missing

注册表还承担了一层历史兼容:normalizeStoredAiAgent()把旧值'gemini'重映射为'antigravity'。后端 Rust 侧的 src-tauri/src/settings.rs 中normalize_default_ai_agent()做了同样的归一(geminiantigravity,其余值必须在SUPPORTED_DEFAULT_AI_AGENTS白名单内,否则丢弃),而AiAgentId枚举也通过#[serde(alias = "gemini")]保持了旧数据反序列化兼容。

设置写入路径见 src/hooks/useAiAgentPreferences.ts:setDefaultAiAgent()会把default_ai_agentdefault_ai_target同时写入应用设置并弹出 Toast;cycleDefaultAiAgent()则按注册表顺序循环切换,配合 src/lib/aiAgents.ts 中的getNextAiAgentId()实现键盘式快速轮换。

后端:探测与流式分发

src-tauri/src/ai_agents.rs 是后端的中枢。get_ai_agents_status()并行探测所有受支持的 CLI:

pub async fn get_ai_agents_status() -> AiAgentsStatus { let claude = tokio::task::spawn_blocking(availability_from_claude); let codex = tokio::task::spawn_blocking(crate::codex_cli::check_cli); let copilot = tokio::task::spawn_blocking(crate::copilot_cli::check_cli); // ... opencode、pi、antigravity、kiro、hermes 同理 let (claude, codex, copilot, opencode, pi, antigravity, kiro, hermes) = tokio::join!( availability_or_missing(claude, AI_AGENT_STATUS_PROBE_TIMEOUT), // ... ); }

这里有两个值得注意的工程细节:

  • 并行探测而非串行:源码注释说明,每个check_cli()在二进制缺失时会回退到 login-shell 查找(如/bin/zsh -lc 'command -v <agent>'),单个探测最长约 1 秒;串行探测在没有任何 Agent 安装时会为冷启动累加数秒,因此改为在 Tokio 阻塞线程池上展开,用户感知的耗时变为「最慢的单个探测」。
  • 超时兜底:每个探测都被availability_or_missing()包上AI_AGENT_STATUS_PROBE_TIMEOUT(5 秒)超时,超时或 panic 统一映射为installed: false,保证 IPC 永远返回完整填充的AiAgentsStatus,前端可以持续渲染。文件内的测试availability_probe_timeout_returns_missing_status直接验证了该兜底行为。

流式分发入口是run_ai_agent_stream()dispatch_ai_agent_stream()。分发逻辑按 Agent 选择 runner:

fn shared_agent_runner<F>(agent: AiAgentId) -> Option<SharedAgentRunner<F>> { match agent { AiAgentId::ClaudeCode => None, // 走专属路径 claude_cli.rs AiAgentId::Codex => Some(crate::codex_cli::run_agent_stream), AiAgentId::Copilot => Some(crate::copilot_cli::run_agent_stream), // ... opencode、pi、antigravity、kiro、hermes } }

非 Claude 的 Agent 统一映射到crate::cli_agent_runtime::AgentStreamRequest后交给各自的共享运行时适配器;Claude 则单独流经claude_cli::run_agent_stream,其事件再由map_claude_event()归一到统一事件模型。从源码结构看,这条「共享 CLI 运行时」的抽取与 ADR-0093(docs/adr/0093-shared-cli-agent-runtime-adapters.md)一脉相承,是 ADR-0062 抽象在后续演进中的落地形态。

统一流事件模型:Tolaria 自有的事件归一

ADR 后果部分列出的第一条负面结论是:事件归一化从此由 Tolaria 负责,各后端适配器必须把每个 CLI 的流格式翻译成统一事件模型。该模型即AiAgentStreamEvent(定义于 src-tauri/src/ai_agents.rs):

事件载荷语义
Initsession_id会话建立
TextDeltatext增量可见文本
ThinkingDeltatext增量思考内容
ToolStarttool_name,tool_id,input?工具调用开始
ToolDonetool_id,output?工具调用结束
Errormessage错误
Done流结束

map_claude_event()展示了归一的具体规则:Claude 的Result事件若携带非空文本会被拆成一条TextDelta,空Result则直接丢弃(返回None)。该映射有多组单元测试覆盖,如map_claude_done_event_preserves_completion_signalmap_claude_tool_events_preserve_stream_datamap_claude_empty_result_event_is_ignored,保证每种事件在归一后不丢失数据。

AiAgentStreamRequest则定义了统一请求面:agent、可选modelmessagesystem_promptvault_path与多 vault 的vault_pathspermission_mode。权限模式AiAgentPermissionModeSafe默认 /PowerUser)缺省取Safe,由测试stream_request_uses_default_or_explicit_permission_mode验证——对应 ADR 中「同一笔记上下文组装、同一面板」的产品约束在请求层的体现。

三个候选方案及其取舍

ADR 完整记录了三个候选方案:

  • 方案 A(选中):共享 Agent 注册表 + 后端适配器层——一个面板、一个偏好设置、一条 onboarding 路径,并为未来 CLI Agent 留出清晰的接入点。
  • 方案 B:UI 保持 Claude 特化,把 Codex 作为第二个特例「打补丁」式加入——短期成本最低,但每新增一个 Agent 都会成倍增加定制的检查、提示词和命令处理器。
  • 方案 C:按 Agent 拆分为各自独立的产品面板——单集成的所有权更清晰,但会碎片化用户体验,并使命令面板与状态栏的交互不一致。

选 A 的实质是:把 Agent 差异收敛到「注册表条目 + 后端适配器」两个边界,其余所有表面(面板、onboarding、命令面板、状态栏,如 src/components/status-bar/AiAgentsBadge.tsx 与 src/components/AiAgentsOnboardingPrompt.tsx)只消费共享模型。

默认 Agent:安装本地偏好,而非 vault 配置

default_ai_agent存放在应用设置(app settings)而非 vault 中,ADR 明确指出这是为了匹配 ADR-0004 的规则——机器相关的工具偏好属于应用设置,不属于 vault(参见 docs/adr/0004-vault-vs-app-settings-storage.md)。

从源码看,该偏好是「设置层」的字符串而非「vault 层」的文件内容:Rust 侧 src-tauri/src/settings.rs 的Settings结构体持有default_ai_agent: Option<String>default_ai_target: Option<String>,保存前经normalize_settings()白名单校验。

值得一提的是,仓库在当前版本中又引入了default_ai_target与 Agent 平级的 API 模型目标:src/lib/aiTargets.ts 用agent:/model:前缀统一了「CLI Agent」与「API 模型」两类目标(对应 ADR-0108 直接模型目标),resolveAiTarget()在读取存储值时会把旧的纯 Agent 值(如claude_code)当作 legacy 目标回退解析。这说明 ADR-0062 建立的「单一偏好位」在后续演进中被扩展成了「单一目标位」,但读取、归一、回退的分层结构保持不变。

Agent 列表的增长:抽象经受住了扩展

ADR-0062 决策时的注册表只有两个 Agent。当前AiAgentId枚举已包含 8 个变体:ClaudeCodeCodexCopilotOpencodePiAntigravity(含gemini别名)、KiroHermes;前端AI_AGENT_DEFINITIONS与之一一对应,各自携带安装链接(如https://docs.anthropic.com/en/docs/claude-codehttps://developers.openai.com/codex/cli等,见 src/lib/aiAgents.ts)。新增 Agent 对应的独立 ADR 包括 docs/adr/0090-pi-cli-agent-adapter.md、docs/adr/0097-gemini-cli-agent-adapter.md、docs/adr/0147-antigravity-cli-agent-adapter.md、docs/adr/0150-github-copilot-cli-agent-adapter.md——每个新 Agent 都遵循 ADR-0062 定义的「一个后端适配器 + 一个前端注册表条目」的接入路径,正面印证了该抽象的可扩展性。

此外 src-tauri/src/ai_agents.rs 还暴露了get_ai_agent_model_catalog():Claude 的模型目录使用稳定的文档化别名(sonnet/opus/haiku),Codex 则带 5 秒超时地探测其本地可用模型,探测失败不影响整体目录返回。这同样是「同一面板面向多 Agent」的延伸:模型选择也被纳入了共享目录。

边界上的 Agent 特化与再评估条件

ADR 也诚实地列出了负面后果:部分用户引导在边缘处重新变得 Agent 特化,例如各 Agent 的安装链接(即注册表中的installUrl字段)与认证错误文案(claudelogin 对codexlogin)。这在实现中确实存在——安装提示与登录引导必须按 Agent 给出,无法被共享模型完全吞并。

ADR 最后给出了再评估触发条件:如果某个 Agent 需要共享面板无法清晰表达的能力,或 Tolaria 从 CLI 子进程转向专用本地 SDK/运行时,则应重新审视该架构。

小结:新增一个 CLI Agent 的实际路径

综合 ADR-0062 与当前源码,在 Tolaria 中新增一个 CLI Agent 的完整路径是:

  1. 后端:新建<agent>_cli.rs适配器,实现check_cli()(可用性探测)与run_agent_stream()(把该 CLI 的流格式翻译成AiAgentStreamEvent),并在 src-tauri/src/ai_agents.rs 的shared_agent_runner()get_ai_agents_status()中注册;
  2. 设置层:把新 Agent 加入 src-tauri/src/settings.rs 的SUPPORTED_DEFAULT_AI_AGENTS白名单,使default_ai_agent可以持久化该值;
  3. 前端:在 src/lib/aiAgents.ts 的AI_AGENT_DEFINITIONS增加一条{ id, label, shortLabel, installUrl },面板、状态栏、onboarding、命令面板即自动获得对新 Agent 的支持。

ADR-0062 的价值正在于把「支持新 Agent」从一个跨全应用的改造压缩成两个注册点加一个适配器模块,而统一事件模型、并行探测与安装本地偏好这三块基础设施,则让所有后续 Agent 共享同一套面板体验。

【免费下载链接】tolariaDesktop app to manage markdown knowledge bases项目地址: https://gitcode.com/GitHub_Trending/to/tolaria

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询