Provider 发现模块

让 daemon 知道这台机器上装了哪些 agent CLI、在哪、什么版本、配置在哪 —— 一块独立的探测器:只看不改,能随时重探。
第一版:只探测、不改东西 独立通用模块 · 跟 adapter 两件事 落点 apps/daemon · agent/discovery/ 2026-06-13
一句话:把「provider 发现」做成一块独立、通用的探测器 —— 它自己知道怎么探每个 CLI,探到了就把对应 provider 标 available=true,对外只报事实,不碰界面、不管存数据库、不改公共代码。这是个独立任务,跟各 provider adapter 互不依赖;这一版只做探测,改配置(往文件里写)是确认要做的,但放后面。

这份是咱俩一起对的,主要把分工说清:哪块你来、哪块地基来、不够用了一起调。

0背景 · 现状

没跟过前面讨论的人(协作方 / LD)先看这段,补齐上下文。

要解决什么

Eyrie 要统一接入多个 agent CLI(claude、codex…),每个都能在 UI 里被选来开会话。但有个基础问题一直空着:这台机器上到底装没装某个 CLI、装在哪、什么版本、配置在哪? 现在系统答不上来 —— 一个 provider「可不可用」目前只是占位:只要它被启用、且有对应 adapter 就当可用,并不代表机器上真的装了;版本字段有,但没人往里写。于是 UI 没法诚实告诉用户「claude 在这台机器上装了没、是哪个版本」。这个模块就是来补这块真实探测。

已经有的地基(本模块往上接)

  • provider 自注册已落地(PR #55):有了 agent_providers 表、ProviderModuledetectAll();但「可用」就是上面那个占位,没探真实安装。
  • Claude adapter 已落地(PR #42,providers/claude/),codex 在路上 —— 这俩是各自的「专项适配」,跟本模块是两件独立的事。
  • 数据库已留好空位等这件事:agentInfoJson(放版本等)/ capabilitiesJson / capabilitiesUpdatedAt(上次刷新时间)三列建好了,全仓没人写
  • 传输已是 tRPC over WebSocket(PR #56),所以「重探」入口将来是个 tRPC 调用,不是老式 HTTP 接口。
  • 「探测」这个位子是当初就特意留空的(先给个诚实占位,等真做探测时一次性设计好)—— 本模块就是那次设计。

这一版边界

只做只读探测(细节见 ①);改配置(自定义第三方 API 那类操作)确认要做、但放本期之后(⑧)。

1做什么 / 不做什么

先把范围对齐:这样你清楚哪些是这次要做的、哪些是故意先不做的 —— 不会多做,也不用担心漏了。

这一版做

  • 探这台机器装没装 claude / codex、装在哪、什么版本
  • 找到每个 CLI 自己的那几个目录(配置 / 历史会话 / 命令),记下位置 + 一个小标记(以后看有没有被改过)
  • 能针对某一个 CLI 点一下「重探一遍」;daemon 启动时自动探一次
  • 处理不同系统的路径差异(做到哪一档见 ⑦)
  • 把「算路径」这段写成可复用的小工具

这一版不做

  • 改配置文件(比如填第三方 API)—— 以后做,见 ⑧
  • 看懂配置文件里写了什么(那是各 CLI 作者的事)
  • 判断「版本太旧算不算还能用」(这归地基定)
  • 存数据库、推送通知、做界面
  • 盯着文件夹自动监听变化(放二阶段,见 ⑦)

2「发现」拆开是三件事

用户说的「发现」其实是三件事。它们用的是同一批底层本事(算路径、跑命令探一下、把结果记下来再判断要不要重探)—— 所以才值得单独做成一块。

① 装没装

有没有 / 在哪 / 版本

查这台机器有没有这个 CLI、在哪、什么版本。不同系统找起来差别最大;装了好几份要全列,不偷偷只挑一个。

这一版核心
② 它的那些目录

配置 / 历史会话 / 命令

只「找到位置 + 记个小标记」,不打开看里面写了啥。难点是不同系统的家目录写法、WSL 下能不能看到。

这一版(只找位置)
③ 会不会过时

装新的 / 升级 / 改了配置之后

之前探的结果不能一直是老的。这一版靠「手动点重探 + 启动探一次」;自动监听放二阶段。

重探本版 · 监听以后

3引擎长什么样(就三块)

说到底就三块:模块自己知道「怎么探每个 CLI」→ 探一遍输出「探到啥」→ 能「重探」。下面写的是大概有哪些信息,具体怎么定义你自己来。

① 怎么探(模块自己知道)

发现模块内部就知道 claude/codex 怎么探:命令叫 claude/codex、版本用 --version 问、那几个目录在哪。就是模块里的几行数据,不用谁喂、也不发给 provider 层;外加一个小函数把版本号摘出来。

② 探到啥(出)

装了就把对应 provider 标 available=true;再带上用的哪条路径、装了好几份就全列、版本号、那几个目录在不在 + 小标记、什么时候探的、没探到是哪种原因。全是事实。

③ 重探

启动探一次;针对某个 CLI 点重探(同时点好几下,合并成只跑一次,不重复开进程);要关的时候能干净地停。

结果里值得专门说的几样

  • 没探到要说清是哪种,别只给个「有/没有」:没找到命令 / 跑超时了 / 版本那行解析不出来 / 目录读不了 —— 界面以后要分清「没装」和「探的时候出错了」。
  • 带个时间戳:别人能判断这结果新不新,界面能显示「上次探测于 …」。
  • 装了好几份要全摆出来:比如 claude 用 npm 装了一个、又用安装包装了一个,两个都列,标一下用的是哪个。
  • 目录只到「在哪 + 在不在 + 小标记」:不打开、不读内容。

4四条不能松的规矩

这四条是这块东西的灵魂,也是 review 时咱最该一起守的。其余内部怎么搭,你全自由。

规矩为什么
只报事实,不下「能不能用」的结论引擎只说「命令找到了、版本是多少」;「这到底算不算能用」是地基那边定,不在引擎里。
探的时候出错,就当「不可用」,绝不让错误冒出来把整件事搞崩机器状态什么样不好说,一次探测出岔子,不能连累整个流程。
所有跟外部打交道的动作都能换成假的(读文件 / 跑命令 / 看是什么系统 / 取时间)这样测不同系统的行为时,不用真去找一台 Windows/Mac,在假环境里就能测。
不去读懂配置里写了什么里面写了啥是各 CLI 作者的知识;引擎只负责找到它、记个小标记。

5分工:你的地盘 + 我们接的地方

整块东西交给你来建,地基这边做公共接口和接线。简单分一下:

你来建

引擎本体 + 测试 + 自己用的类型,全在 agent/discovery/ 里;里面怎么分文件、定义什么类型、测试怎么写,你说了算

地基来接

公共接口(类型定义 / 各 provider 声明)和接线(把你的结果接进系统、存起来、加「重探」入口、加边界检查)—— 这些你不用动。

一起调

做着发现跟地基的接口对不上(比如探到的结果想多带个字段、registry 那边没接),喊一声,咱一起补。

claude/codex 要探哪些(命令名、目录)就是模块自己的一张小数据表,不发给谁;模块本身通用,加新 provider 就往表里加一行 —— 你不用等任何人,直接开工

6你探出来的结果,我们怎么接(你不用做,知道就行)

这几件都是地基这边的活,不用你写。摆出来是让你知道「我探出来的东西会被这么用」—— 对一下,接口才严丝合缝。其中「写回」那条是我们这边要补一下的,标出来了。

地方现在啥情况我们这边会做
探出来的结果怎么接进去已经有读「版本」的地方,但还没人往里写把你的结果接进去;「这算不算能用」由地基这边判断,不用你管
写回数据库 需调整⚠ 现在只能整行覆盖,没有「只改几列」的写法我们要加个「只改探测结果那几列」的写法,不然会把用户自己改过的命令/环境变量/密钥冲掉 —— 之前踩过
「重探」入口还没有在 service 加个重探方法 + 一个 tRPC 调用(注:现在走 tRPC,不是老的 HTTP 接口)
边界检查已经有两条再加一条:引擎不去 import provider 内部,provider 也不 import 引擎实现

7要一起定的几件事(聊的时候敲定)

这是唯一需要先定的 —— 这几条定了,你就能直接开工。下面是地基这边的倾向,咱对一下,有不同想法随时提。

问题倾向为什么
Windows(原生)这次做不做先不做,先把 Linux + Mac 弄好我们现在开发就在 WSL/Linux 上;Windows 原生那套(路径、注册表那些)是跨系统里最费劲的大头。接口先留好「换系统」的位子,以后补不费事
文件自动监听这次做不做先不做,放二阶段这次就「手动点重探 + 启动探一次」;靠监听去发现命令「装上/卸掉」不现实(要盯的目录太多)
探出来的结果存哪存数据库列已经有了、本机数据库也没有跨机器的麻烦、重启不丢 —— 但要走上面那个「只改几列」的写法

8「改配置」为什么放后面 + 将来怎么接

自定义第三方 API 渠道(不是所有人都走官方渠道)是确认要做的真需求,但这一版不做。顺手把它跟探测的关系讲清,方便将来续上 —— 也让你知道现在为什么不碰它。

先分清两种「自定义模型」 (A) 在 Eyrie 里给某次对话挑个模型/调参数 —— 存在 Eyrie 自己的库里,开任务时传给运行的进程,根本不碰 CLI 自己的配置文件;探测这边只要把「有哪些模型可选」读出来给界面就行。
(B) 真的去改 CLI 自己的配置文件(比如填第三方 API 地址)—— 只有这种才要往文件里写。这一节说的是 (B)。

为什么写不进探测引擎

  • 读和写是两码事:引擎是「只看、不改、出错就当没有」;写就会去覆盖用户手改的配置、可能写一半、还会跟 CLI 自己抢着写 —— 把写塞进探测器,等于探测里出个 bug 就可能动到用户的文件。
  • 每个 CLI 的配置格式都不一样(claude 一种、codex 另一种),这种知识该放在各自的适配器里;塞进公共引擎,等于逼引擎去学每个 CLI 的配置长啥样,正好是边界检查要拦的事。

将来怎么放(读和写是一对,不是一个人干)

探测(读)

找到配置文件 + 记小标记,顺便发现是不是被外面改过。

维护(写)

放在各 CLI 适配器里的单独本事:读出来 → 改 → 写回去,带校验、先备份再整体替换、绝不碰自己看不懂的字段。

回到探测

写完文件变了 → 顺手触发一次重探。

这两边共用探测导出的「算路径」那套小工具,串成「读 → 写 → 读」。所以这次把「算路径」那块做扎实,就是给将来的写功能铺好路。

9速查:一开始就对齐的几个点

几个一开始就对齐的点,省得做到一半想岔了。有别扭的随时提。

我能改公共接口(types / registry)那些吗?

接线是地基的活,这块你不用碰;接口不够用喊一声一起补。

「能不能用」谁说了算?

你只报「命令找到了」这个事实;合不合成「能用」是地基这边判断,不归你。

探的时候出错怎么办?

就当「不可用」,顺便说清是哪种原因,千万别让错误冒出来。

配置文件里写了啥,我要看懂吗?

不用,只要找到它、记个小标记。内容是各 CLI 作者的知识。

第三方 API 那个要我做吗?

这次不做。那是以后的「写」功能,放在各 CLI 适配器里,跟你共用「算路径」那套工具。

版本怎么拿?

去跑一下那个命令问版本(给个超时,比如两三秒);从打印出来的字里摘版本号,用你那份说明里的小函数。跑命令、超时这些引擎管,你不用碰进程。

一个 CLI 装了好几份咋办?

全列出来,标一下用的是哪个,别偷偷只挑一个。

怎么测不同系统?

把读文件、跑命令这些都换成假的,在假环境里测,不依赖跑测试的机器是什么系统。