Skip to content

feat(plugins): 插件注入的工具链框架与 macOS 只读环境设置页 - #1581

Open
FxRayHughes wants to merge 9 commits into
1lck:previewfrom
FxRayHughes:feat/plugin-toolchain-sources
Open

FxRayHughes wants to merge 9 commits into
1lck:previewfrom
FxRayHughes:feat/plugin-toolchain-sources

Conversation

@FxRayHughes

@FxRayHughes FxRayHughes commented Oct 29, 2026 •

Copy link
Copy Markdown

关联

原则

宿主不内置任何工具:Java、Maven、Node 等工具由插件定义,mise、nvm、SDKMAN 这类版本管理器由插件作为提供方接入。Core 只负责校验和按四层解析,不解释任何版本管理器的私有格式。

改动

插件契约(shared/contracts/plugins/v1/)

  • 新增能力 toolDefinitions: 1(工具定义)和 toolchainProviders: 1(提供方声明,需要权限 toolchains.provide)。
  • executableResolution 增加 v2,只用于工具定义的候选,不改变现有 Build 的候选集合。
  • 同步更新 schema、README 和 rust-core-api.md。新增 toolchains.md,以及两个 fixture(tool-definition.plugin.json、toolchain-provider.plugin.json)。
  • proposals/toolchains/ 存放完整方案草案,包括查询协议、local.json、清单贡献和解析结果的 schema 与用例。这些只用于评审,不是可以安装的清单。
  • 工具 ID 规则:
    • 不带前缀的短 ID(java、node 等)只能由宿主验证过的官方插件定义;
    • 社区插件使用 <pluginID>:<tool>;
    • 重复的 ID 拒绝后注册的一方。
  • 已安装版本可以通过两条途径获得,可以只用其一,也可以两条都用(toolchains.md 新增「Two installation routes」一节):
    • 声明式扫描:工具定义里的 discovery、homeLayouts、version,由宿主展开扫描,不运行插件代码。适合目录结构固定的安装位置。可用的写法固定为三种根(用户目录、环境变量、绝对路径)加单层 * 通配,以后不再扩充;新的安装形态改由提供方回答,宿主不加新的解析器。
    • 提供方查询:toolchainProviders 通过 toolchains/query 返回安装和版本要求,适合注册表、管理器数据库、命令输出这类声明式写法表达不了的位置。
  • 提供方的 watchFiles 改为可选。不声明时这个提供方只报告已安装的版本,答复里带版本要求会被拒绝。定义工具的插件也可以同时作为这个工具的提供方,但提供方后端不能依赖它要发现的工具运行。

Rust Core(rust/lithe-core/src/toolchain/)

工具链是一个独立的领域。插件清单模块只在准入时调用它做校验。

文件 内容
definitions.rs 校验工具定义和提供方声明,构建注册表:核对来源身份、保护短 ID、拒绝重复
query.rs 校验提供方的答复是否匹配其身份和代次、是否只涉及声明过的工具,并核对宿主提供的文件证据
pins.rs 读取 .lithe/toolchains/local.json 的 pins,找出最近祖先目录上的固定值
resolution.rs 按固定 → 项目文件 → 全局 → 自动四层解析:固定值无效时直接失败,不往下回落;项目要求的版本没装时保留「未满足」状态;冲突也保留下来。自动层把声明式扫描和提供方查询合成一个候选集合
paths.rs 规范化工作区相对路径,处理 Windows 路径大小写
  • 两条途径的合并规则:
    • 同一个规范化目录只算一次安装。来源标注优先显示提供方,其次是宿主扫描(host:system);多个提供方之间取限定 ID 较小的。这个标注只用于显示,不参与选择。
    • 不同目录的最高版本相同时仍然报自动层冲突,并列出所有来源,需要用户选择。
    • toolchains.discoveryPlan 现在对每个工具同时列出 scans(要展开的扫描)和 providers(声明了该工具的提供方)。
  • 新增七个命令:toolchains.registry、toolchains.decodeReply、toolchains.resolvePins、toolchains.resolve、toolchains.checkVersion、toolchains.discoveryPlan、toolchains.candidates。它们只处理宿主传入的快照,不启动进程、不扫描目录,也不写文件。
  • toolchains.candidates(第三个提交新增):给设置页用,列出一个工具的全局层和自动层,不解析具体目录。
    • 输入和 resolve 相同(注册表、平台、工作区根、文件事实、probes、systemInstallations、environment 等),校验也共用同一段代码(prepare)。
    • 输出 {tool, shell, installations, skipped, selected, conflicts}:shell 是登录 Shell 里声明过的 home 变量及其版本,installations 是通过检查的扫描结果(按版本从高到低、目录已规范化),skipped 带稳定的原因码(unsafePath、probeFailed、versionUnsatisfied 等),selected 是自动层的选择,同版本不同目录时为空并报冲突。
    • 两轮调用:第一次不带 probes,可执行文件都报 probeFailed;宿主只探测这些可执行文件,再带上 {executable, output, exitCode} 调第二次。工作区内的目录在探测前就被拒绝,项目里的程序不会被运行。
  • toolchains.discoveryPlan 还会带上定义的 pathPrepend 模板,以及各平台适用的登录 Shell home 变量(environment: [{variable, path}]),宿主不用自己解析可执行文件候选。
  • backend.rs:backend 2 的插件可以由已准入的工具链查询触发启动。
  • 修复机器人评审的两个 P2:
    • 提供方返回的安装目录是符号链接时,解码阶段已规范化为真实路径,解析阶段却按原始路径查文件证据,误判为 unsafePath。现在一条非 missing 的文件证据同时证明它的规范化目标;同一目标的证据互相矛盾时拒绝整个快照。版本探测按解析结果中的规范化可执行文件路径对应。
    • 环境变量候选(如 JAVA_HOME)声明的 path 布局之前被忽略,总是取第一个布局。现在只按声明的布局查找,和 macOS 设置页的行为一致。
  • declarative.rs:清单中的 toolDefinitions(最多 32 个)和 toolchainProviders(最多 8 个)必须非空,能力声明要精确对应。

macOS

  • 在「设置 › 构建」下新增「运行环境」和「环境来源」两页,排在「项目」「运行配置」之前,设置搜索词同步更新。旧的项目 JDK/Maven 页面保留,等实际的使用方迁移过来后再删除。
  • 运行环境:
    • 左侧是项目的目录树,右侧是当前目录下的工具。
    • 每个工具占一行,收起时显示当前生效的来源;展开后显示固定、项目文件、全局、自动四张来源卡片。第一个命中的卡片带边框,没有值的卡片变暗显示,不会被隐藏。
    • 卡片按窗口宽度在 4、2、1 列之间切换,同一行的卡片高度一致。路径只占一行、从前面截断,鼠标悬停显示完整路径。
  • 环境来源:
    • 登录 Shell 部分显示 Shell 路径、读取状态、变量个数(只显示数量,不显示值)、读取耗时,以及 PATH 里每个目录来自登录 Shell 还是 Lithe 的启动环境,并提供「重新读取」。
    • 版本管理器部分显示插件声明的版本管理器在当前项目里找到了哪些版本文件。没有声明 watchFiles 的提供方显示「只报告已安装的版本」,不检查项目文件。
  • 页面上的所有内容都来自已启用插件的声明和磁盘上的真实文件。添加、切换、保存、查询这些还没实现的入口直接不显示,也不放禁用状态的占位按钮。
  • MacLoginShellEnvironment 新增只读的 inspect,供来源页使用。重新读取只更新缓存和页面显示,不会改变当前进程的 PATH。
  • 插件启用、停用后,两页会重新加载数据。
  • 声明式扫描(第三个提交,MacToolchainDiscovery):
    • 宿主按 toolchains.discoveryPlan 展开三种根(用户目录、环境变量、绝对路径)和单层 * 通配,跳过隐藏条目,按 homeLayouts 取第一个匹配的 home,收集文件事实,然后交给 toolchains.candidates 检查。
    • 只探测 Core 返回的可执行文件:用定义里的版本参数直接启动(不经过 Shell),单次 5 秒超时;结果按可执行文件身份和参数缓存。
    • 上限:扫描根 256、条目 4096、文件事实 8192、每个工具的安装数 512;探测总数 64、并发最多 4、总时长 20 秒。超过上限时在页面上给出诊断,不会当作「没装」。
    • 被 Core 拒绝的声明不扫描也不探测,只报诊断。
    • 加载器(MacToolchainSettingsLoader)注入扫描器;只有登记在官方插件目录、并通过内置或插件市场安装的插件才标记为官方来源,Core 只对这类插件开放 java 这样的短 ID。
  • 运行环境页的全局卡片和自动卡片改为显示真实结果:
    • 全局:登录 Shell 里声明过的 home 变量(如 $JAVA_HOME)和探测到的版本;PATH 上找到的命令显示路径,但标为「版本未验证」,不会被运行。卡片太窄时变量和版本自动换成两行。
    • 自动:通过检查的安装按版本从高到低排列,自动层选中的一项加勾选标记,其余用「另外 N 个」收起;路径显示扫描到的条目(如 jdk-21.jdk,而不是每个都一样的 Contents/Home),悬停显示完整 home。另外还会显示同版本冲突和跳过的数量。
  • 新增 Debug 预览入口 --toolchain-settings-demo 和 --toolchain-sources-demo。预览里只有插件清单是样例(java、maven、node、python 的完整清单,以及 nvm、uv、mise 提供方),工作区文件、固定值、登录 Shell 和本机安装的扫描结果都是真实的。可以用以下参数指定初始状态:
    • --toolchain-demo-directory <目录>
    • --toolchain-demo-expand <工具,…>
    • --toolchain-demo-size <宽>x<高>
    • --toolchain-demo-workspace <路径>

文档与其他

  • 新增 Agent Note(提议中)2026-10-31-plugin-injected-toolchains.md;在设置层级 Note 里补充这两页属于哪个分组。
  • 功能矩阵新增 plugin-injected-toolchains,macOS 和 Windows 都标为 partial / pending;macOS 的证据里加上了声明式扫描和对应的测试。
  • toolchains.md 更新状态说明,新增「Settings candidates」一节;rust-core-api.md 增加 toolchains.candidates 一行。Agent Note 补充扫描器的工作方式、上限和设置页的显示方式。
  • 补充中英文词条。

尚未完成

以下内容这次都没做,页面上也不显示:

  • 提供方查询进程(查询 mise、nvm、uv 实际装了哪些版本)和文件监听。声明式扫描已在 macOS 设置页接入;提供方途径目前只在 Core 里校验和合并
  • 全局默认值、写入固定值、手动添加安装
  • 把现有的 Java、Maven、Node 选择链迁移到这里
  • Windows 原生接入
  • 合并后需要在 lithe-plugin-docs 同步契约目录

验证

  • Rust Core:verify-rust-core-comments.sh、verify-rust-core.sh、cargo fmt -p lithe-core --check 都通过。toolchain 相关 71 个测试通过,其中新增:
    • 不声明 watchFiles 的提供方只能报告安装;定义插件可以作为自己工具的提供方;
    • 声明式扫描和提供方报告同一目录时只算一次,不同目录同版本时报冲突,可以按目录选择;
    • 符号链接安装从解码到解析的完整链路,以及证据矛盾时拒绝;
    • 环境变量候选按声明的布局选择可执行文件。
    • 两个评审修复的测试在去掉修复后都会失败。
    • toolchains.candidates:第一轮没有探测结果时返回要探测的可执行文件;带上探测结果后列出版本,选择结果和 resolve 一致;同版本不同目录时报冲突。
    • discoveryPlan 带上 pathPrepend 和各平台的 home 变量。
  • macOS 单测:以下测试全部通过,test-stability-macos 共 67 个测试,0 个失败,没有超过 1 秒的。
    • MacToolchainDiscoveryTests(新增):用真实的临时目录和真实的 Core,只替换用户目录和探测函数,不运行本机装的任何工具。覆盖:homeLayouts 取第一个匹配、隐藏和不匹配的条目不扫描、链接到工作区的条目不探测、只探测 Core 返回的可执行文件、显示扫描到的条目名、同版本冲突、被拒的声明不扫描不探测、通配规则和 Core 一致,以及预览用的样例清单能通过 Core 准入。
    • ToolchainSettingsLoaderTests、ToolchainSettingsFeatureTests、ToolchainSettingsLayoutTests
    • MacLoginShellEnvironmentTests(13 个)
    • SettingsSearchVocabulary、SettingsSidebar
  • 其中布局测试会:
    • 用 900/1180/1400/900/1180 几种宽度反复缩放窗口,并拖动真实的分隔条;
    • 把来源页按 500/666/946/500 缩放,检查控件边界、卡片不重叠、选择状态和快照不变。
  • ToolchainSettingsLoaderTests 新增:只报告安装的提供方会列出、不检查版本文件;watchFiles 不是数组时仍然报声明无效。
  • 静态检查:
    • verify-runtime-bundle-immutability.sh、verify-service-boundaries.sh、verify-shared-contracts.sh
    • verify-platform-feature-matrix.sh、verify-agent-notes.sh、verify-test-stability.sh
    • i18n 三项检查
  • 实际运行:
    • 在 Debug 预览里分别用 900、1180、1500 宽查看了两页,目录切换、展开和窄宽度换行都正常。
    • 声明式扫描在本机找到的结果和磁盘一致:JDK 27.0.1.0.3(Homebrew,被选中)、21.0.11(/Library/Java)、17.0.19 和 11.0.31(SDKMAN);Node 26.12.1(Homebrew,被选中)和 24.14.0(nvm);全局层 $JAVA_HOME 为 17.0.19。指向不存在目录的 openjdk-*.jdk 链接被正确跳过。1180×820 和 1000×760 两种窗口下卡片都不溢出、不折断版本号。
  • 未执行:Windows 原生构建,以及 macOS 全量测试。

@xiaoyumuxi

Copy link
Copy Markdown
Collaborator

技术方案评审:toolchainSources 的边界

先说明:PR 要解决的问题(Dock 启动看不到 mise 装的运行时、项目版本文件不生效)是真实的。安全边界(path: 落在工作区内不选版本)、按 inputs 缓存、只读、免重启、功能矩阵这些周边做得扎实,下面的意见不针对它们,针对的是方案本身的架构边界。

验收标准

新增一个版本管理器(例如 SDKMAN、nvm、asdf)只改插件,不改 Rust Core;宿主适配层只保留通用机制。这才是“声明式”的意义。

现方案没达到这个标准的地方

1. Core 在重新实现版本管理器的语义。
lts 别名、latest、prefix:、path:、全局文件的覆盖顺序都是 mise 自己的规则,PR 把它们写进了 rust/lithe-core/src/plugins/toolchain_sources.rs。“已知限制”那一节(conf.d、mise.<env>.toml、模板、system、ref:)其实就是与 mise 的分歧清单。develop-lithe 第二节写明“上游拥有的事实仍由上游负责,Core 里不再维护第二份真源”。

2. “新增版本管理器只需要一份清单”实际不成立。
versionFiles[].format 是封闭枚举 miseToml | toolVersions,Core 里已经有带 mise 名字的解析器。nvm(.nvmrc 是单工具纯文本,目录名带 v 前缀)、pyenv(.python-version)、SDKMAN(.sdkmanrc 是 properties 格式)都需要新增格式,也就是改 Core。

3. 宿主侧的工具知识是写死的,且注入面过宽。

  • PluginToolchainSources.swift 里 tool.tool == "java" 是魔法字符串,其余工具按文件名 executables(named:) 匹配。maven 能进 Maven 选择链,只是因为可执行文件恰好叫 mvn,宿主里没有 maven → mvn 的映射。
  • 任何来源都可以为任意同名命令注入候选(比如声明 {tool: "git", executables: ["git"]}),而版本文件点名的那个版本排在 PATH 前面。权限 toolchains.read 的描述是“查找运行时”,实际效果是“影响宿主启动哪个程序”。

4. 优先级和作用域。

  • JDK、Maven、其他命令是三条顺序各不相同的链,硬编码在 ProjectRuntimeService。
  • 没有“项目级”和“全局”的统一概念,只有各条链里各自的位置。
  • 版本文件要求的版本没装时静默回落,而这正是 PR 描述里“选错版本不易察觉”的那个问题。

建议的方案

三种贡献,宿主不认识任何工具

贡献 回答什么 形式 谁提供
工具定义 工具是什么:怎么识别、探测版本、导出环境、启动 清单里的静态声明 任何插件
提供方 装了什么、项目要什么、全局要什么 插件通过 API 返回事实 版本管理器插件
使用方 谁需要这个工具 已有(通过 tool ID 引用) 语言服务器、构建系统、运行配置

1. 工具定义(示意)

"toolDefinitions": [
  { "id": "java", "displayName": "JDK",
    "executables": ["bin/java"], "commands": ["java"],
    "version": { "arguments": ["-version"], "pattern": "(?:1\\.)?(\\d+(?:\\.\\d+)*)" },
    "exports": [{"name": "JAVA_HOME", "value": "${home}"},
                {"name": "LITHE_JAVA_PATH", "value": "${executable}"}],
    "pathPrepend": ["${home}/bin"],
    "launch": ["${executable}", "${runtimeArguments}", "-jar", "${entry}", "${arguments}"] },
  { "id": "maven", "displayName": "Maven",
    "executables": ["bin/mvn"], "commands": ["mvn"],
    "projectExecutables": ["mvnw"],
    "version": { "arguments": ["-v"] },
    "pathPrepend": ["${home}/bin"] }
]

这张“工具 ID 到宿主用途”的映射表就是适配层里本来要写死的东西,改成插件贡献。它取代的是:java 当 JDK home 的特判、maven → mvn 的文件名巧合、契约里写死的版本探测参数和 LITHE_<TOOL>_PATH 导出规则、launch.kind: runtime 里 tool 只能是 java 或 node 的限制,以及选择链里专门给 mvnw、gradlew 写的分支。

  • ID 归属。 插件自己的工具用 插件id:工具;不带前缀的短名(java、node、go、maven)是保留名,只有宿主内置或官方签名插件能定义。同一 ID 被重复定义时拒绝后来者,不允许覆盖。
  • 宿主内置的那几个用同一种格式的数据文件表达,放在 shared/ 下,各平台随应用打包,作为第一个贡献者注册。适配层里不出现任何工具名,以后各插件可以逐个接管自己的定义。
  • 安全校验由 Core 统一做,与具体工具无关。 环境变量值只能是 ${home}、${executable} 模板;禁止 PATH、DYLD_*、LD_*;pathPrepend 只能在 ${home} 之内;探测参数必须是 -x/--xx 形状。安装审查里列出每个定义会运行的程序和参数。

2. 提供方 API

清单只声明“我能回答哪些工具”,不描述目录结构和文件格式:

"toolchainProviders": [{ "id": "mise", "displayName": "mise",
                         "tools": ["java", "node", "maven", "pnpm", "go"] }],
"permissions": ["toolchains.provide"]

宿主向插件发 toolchains/query({operationID, workspace, tools},workspace 为空表示只问全局),插件返回:

{ "installations": [
    {"tool": "java", "version": "21.0.2", "home": "/…/installs/java/21.0.2"} ],
  "statements": [
    {"tool": "java", "scope": "project", "source": "/proj/mise.toml",
     "request": "21", "installation": "/…/java/21.0.2"},
    {"tool": "java", "scope": "global", "source": "~/.config/mise/config.toml",
     "request": "17", "installation": "/…/java/17.0.9"},
    {"tool": "node", "scope": "project", "source": "/proj/.tool-versions",
     "request": "lts", "installation": null} ],
  "watch": ["/proj/mise.toml", "/proj/.tool-versions", "…/installs/java"] }
  • 插件只能回答自己在 tools 里声明过的、且有已启用插件定义的 ID,其余丢弃。这样“任意同名文件被注入”的口子就堵上了。
  • 版本语义(哪个文件优先、lts 对应哪个目录)归插件,宿主不解释 request。
  • installation: null 表示项目要求了但没装。
  • watch 沿用 PR 里“缓存到输入变化”的思路:宿主只做 stat 指纹比较,没变就不再调用插件。
  • 沿用现有的 operationID、取消、超时和过期结果规则。
  • 返回的路径由 Core 统一校验:绝对路径、存在、可执行、不落在工作区内(保留 PR 现有的这条安全规则)、数量有上限。
  • .tool-versions、mise.toml 这类通用解析函数放进 lithe-plugin-docs 的 Node/Java SDK,不放进宿主。

3. 统一的选择规则

所有工具共用一条规则,按层从上到下取第一个有值的:

层 内容 范围
1 固定 用户在 Lithe 里为这个项目选的 项目
2 项目要求 提供方上报的 scope: project,以及 mvnw、gradlew 等项目自带的包装脚本 项目
3 全局默认 Lithe 里的全局默认 → 提供方上报的 scope: global → JAVA_HOME/PATH 全局
4 自动检测 满足最低版本的已安装最新版(现有的 Core 规则) —
  • 没装任何插件时,第 2 层为空,行为与现在完全一致。
  • Lithe 里固定的值失效:报错,不回落(与调试适配器那篇 Note 的做法一致)。
  • 项目要求了但没装:回落到下一层继续工作,但状态标成“未满足”,界面明确提示。
  • 界面上每个工具一行,显示“JDK 21 · 来自项目要求(mise.toml)”,展开后列出每层的值,被盖住的划掉。

对这个 PR 的建议

可以保留: 工作区内路径拒绝、按输入缓存、只读原则、i18n 词条、功能矩阵条目、RuntimeChoiceSource 的“来自 %@”展示。

需要重做: Core 里的 toolchain_sources.rs(管理器专属解析与选择语义)、清单 schema 里的目录与格式描述、ProjectRuntimeService 里三条各自的链。

建议拆分:

  1. 契约(工具定义、提供方、校验)加 Agent Note,先定契约再写代码;
  2. 适配层的查询客户端(缓存、取消、过期);
  3. 统一选择规则加运行时设置界面;
  4. mise 插件加 SDK 帮手,再写一个 SDKMAN 插件当验收,并把 MacRuntimeDiscovery 里写死的 SDKMAN 位置迁出去。

契约在 shared/contracts/plugins/v1/,合并后要在 lithe-plugin-docs 运行 node scripts/contract.mjs sync 同步。

待讨论

  1. API 方向。 上面是“宿主请求、插件返回事实”,不是“插件主动推送”。推送需要插件进程为每个打开的项目常驻;请求式有明确的超时和取消。需要时可以在同一协议上加 toolchains/changed 通知。
  2. 规则细节。 “固定 > 项目要求 > 全局默认”,以及“要求没装就回落并警告”,是建议值,欢迎提出不同意见。
  3. 保留名。 只允许官方签名的插件或宿主内置定义不带前缀的短名;若允许社区插件定义,冲突处理会复杂很多。
  4. 自举和免重启。
    • 提供运行时的插件,它自己的后端不能依赖它要发现的运行时,否则循环依赖。后端必须自带运行时或是单文件可执行程序(契约已有 packagedRuntime)。
    • PR 的 canActivateWithoutRestart 只放行“无后端”的插件,这个条件需要放宽。
    • 这两点我们还没有原型验证过。
  5. Core 的一次性改动。 加定义的校验,以及把 checkRuntimeVersion 里 Java 8 的 1.x 特例改成定义里的正则。之后新增工具或管理器都不再改 Core。

@xiaoyumuxi

Copy link
Copy Markdown
Collaborator

这个具体的先后顺序啥的我记得构建工具里面我之前就提过一个方案,按照优先级去维护一个顺序,你可以去看看,具体细节我记不清楚了哎

@FxRayHughes

Copy link
Copy Markdown
Author

感谢这么细的评审。结论先说:同意按「工具定义 / 提供方 / 使用方」重做,本 PR 不再往下推。 我对照代码核对了几处问题,都成立:

  • toolchain_sources.rs 里的 lts、latest、prefix: 和覆盖顺序,等于在 Core 里维护了第二份 mise 真源。「已知限制」那一节列的正是两边的分歧。
  • versionFiles[].format 是封闭枚举,nvm、pyenv、SDKMAN 都要改 Core。
  • 宿主里有 tool == "java" 特判,maven 能进选择链只是因为文件名恰好叫 mvn。任何来源都能给同名命令注入候选,还排在 PATH 前面。toolchains.read 的描述低估了实际影响:它决定的是 Lithe 启动哪个程序。
  • 要求的版本没装时静默回落,这正是 [Bug] macOS 下自动检测不到 mise/SDKMAN 等版本管理器安装的 Maven 和 JDK,必须手动指定路径 #1514 抱怨的「选错版本不易察觉」。

你说的「按优先级维护顺序」,我找到的是你在 #1420 的评审(#1420 (comment)):

  • 单一 executables 入口,数组顺序就是优先级,可选 priority / insertBefore / insertAfter;
  • ExecutableCandidateResolver 区分 skip / resolved / terminalFailure;
  • 明确要复用到 Language Server、Runtime Dependencies 等。

如果是这条,我打算把新方案的选择规则直接建在它上面,不再写第二套分层逻辑。下面是调整方案,请看方向对不对。

1. 选择规则:写成每个工具定义的默认候选数组

四层规则不再写成 ProjectRuntimeService 里的三条链,而是写成工具定义里 executables 的默认顺序,由同一个 resolver 解析。plugins::executables 只需要两处扩展:

  • 新增一种来源 kind: "toolchain",向提供方查询;
  • setting 可以只取项目层或全局层。

示意(Maven;Java 结构相同,把 MAVEN_HOME 换成 JAVA_HOME,并且没有 Wrapper):

{ "id": "maven", "displayName": "Maven", "commands": ["mvn"],
  "version": { "arguments": ["-v"] },
  "executables": [
    { "id": "pinned",         "kind": "setting",     "settingID": "executable", "layer": "project" },
    { "id": "wrapper",        "kind": "projectFile", "path": "mvnw" },
    { "id": "projectRequest", "kind": "toolchain",   "scope": "project" },
    { "id": "default",        "kind": "setting",     "settingID": "executable", "layer": "global" },
    { "id": "globalRequest",  "kind": "toolchain",   "scope": "global" },
    { "id": "home",           "kind": "environment", "variable": "MAVEN_HOME", "path": "bin/mvn" },
    { "id": "path",           "kind": "command",     "command": "mvn" },
    { "id": "detected",       "kind": "toolchain",   "scope": "installed" }
  ] }

各层在 resolver 里的语义:

  • 固定值失效:非空 setting 指向的程序不存在或不可用时返回 terminalFailure,报错,不回落。这是 feat(plugins): 统一可执行文件候选与宿主注入解析 #1420 已有的行为。
  • 项目要求没装:toolchain 候选返回 skip,继续往下找,但带上原因,例如「mise.toml 要求 node 99,未安装」。目前的 skip 不带原因,需要扩展;界面据此标出「未满足」。
  • 自动检测(scope: "installed"):在所有提供方报告的安装里取满足最低版本的最新版。宿主现有的系统发现(java_home、Homebrew)改成第一个内置提供方,SDKMAN 迁到插件。
  • 使用方调整顺序:构建系统等使用方只能在自己的集合里 insertBefore / insertAfter,与 feat(plugins): 统一可执行文件候选与宿主注入解析 #1420 的限制一致。不新增 projectExecutables 这类旁路字段。

这样三条链变成一条。Provider、模型加载和实际执行用同一个结果,#1420 已经保证了这一点。没装版本管理器插件时,所有 toolchain 候选都是 skip,除「自动检测」层外,行为与现在一致。

2. 提供方:mise 的语义交还给 mise

同意「宿主请求,插件返回事实」。mise 插件改成带后端,后端直接调用 mise 自己的机器可读输出,任何一方都不再重写 mise 的规则:

  • mise ls --current --json:每个工具的 requested_version、install_path、source.path、installed,直接映射为 statements;
  • mise ls --installed --json:映射为 installations。

本机验证(mise 2026.9.8):用 env -i HOME=… MISE_OFFLINE=1 模拟从 Dock 启动的环境,一次调用约 20 ms。

  • 父目录的 mise.toml、子目录的 .tool-versions、全局配置都带着来源文件路径返回。
  • 要求了但没装的版本返回 installed: false。
  • conf.d、MISE_ENV、别名由 mise 自己处理,feat(plugins): 插件注入的工具链框架与 macOS 只读环境设置页 #1581「已知限制」那一节整个消失。
  • mise 会拒绝解析未信任、且含 [env] 等内容的配置。插件照实上报「配置未信任」,不替用户执行 mise trust,也不执行 mise install;设置 MISE_OFFLINE=1,不联网。

自举:后端做成单文件原生程序(Rust,按 darwin-arm64 / x64 分包,Store 已支持按架构下载),不依赖它要发现的 node 或 java。mise 本身的位置用 #1420 的候选声明:设置 → ~/.local/bin/mise → Homebrew → PATH。安装审查里列出它会运行的 mise ls --json。

SDKMAN 适合做第二个验收插件:sdk 是 shell 函数,没有可调用的 CLI,插件只能自己读 ~/.sdkman/candidates 和 .sdkmanrc。这正好检验「上游没有现成答案时,不改 Core 也能接入」。.tool-versions / .sdkmanrc 的解析帮手按你的建议放进 lithe-plugin-docs 的 SDK。用 Node / Java SDK 写的版本管理器插件必须用 packagedRuntime 自带运行时,这一点会写进 Note。

3. 对待讨论各点的意见

  1. API 方向:同意请求式。补充一点:项目里新建的版本文件不在上一次的 watch 里,所以提供方可以在声明里列出它关心的文件名(如 mise.toml、.tool-versions)。工作区里出现或删除这些文件时,宿主重新查询。
  2. 规则:同意「固定 > 项目要求 > 全局默认 > 自动检测」,以及「没装就回落并警告」。有两个细节想确认:
    • 同一层里 mvnw 和 mise 要求的 Maven 谁先?建议 Wrapper 先:它是项目自带的,更具体,也和现有 resolve_maven_executable「覆盖 → Wrapper → 机器安装」一致。Wrapper 属于执行仓库代码,仍按现有规则只在用户操作后启动。
    • 两个提供方对同一工具都给出项目要求时,建议不猜:按插件 ID 顺序取第一个,并在界面标出冲突。
  3. 保留名:同意不带前缀的短名只能由宿主内置或官方签名插件定义。需要确认的是回答:社区提供方(如 mise)应当可以回答 java、node 这类保留名工具,否则 mise 插件对 JDK 没有用。安全靠三层保证:Core 校验答案,安装审查列出影响,界面标明「来自 mise」。
  4. 自举和免重启:自举用单文件后端解决。免重启先不放宽:带后端的提供方在第一阶段装完需要重启,feat(plugins): 插件注入的工具链框架与 macOS 只读环境设置页 #1581 里「仅工具链来源免重启」的改动撤回。等查询客户端的撤回、取消做完,再按热激活 Note 的规则逐类放行。
  5. Core 的一次性改动:同意把 checkRuntimeVersion 里 Java 8 的 1.x 特例改成定义里的正则。toolchain 候选来源和答案校验(绝对路径、存在、可执行、不在工作区内、数量上限)也放在这次。

4. 拆分

按你的建议,先契约后代码,拆成 4 个 PR:

  1. 提案:proposed/ 下的 Agent Note,加上 shared/contracts/plugins/v1/ 的契约草案(工具定义、提供方、toolchain 候选、答案校验)和 fixture,不改代码。参照 docs(vcs): 统一 VCS 模型与 Provider 接入提案(#827) #1428 VCS 提案的方式先评审。
  2. Core 与查询客户端:
  3. 统一选择与运行时设置界面:ProjectRuntimeService 的三条链换成按工具定义解析。每个工具一行,展开后列出各层的值,被覆盖的划掉,未满足的给出提示。
  4. 插件:mise 0.2.0(后端调用 mise),SDKMAN 插件验收,并把 MacRuntimeDiscovery 里写死的 SDKMAN 位置迁出去。

你列的可以保留的部分(工作区内路径拒绝、按输入缓存、只读原则、i18n 词条、功能矩阵、「来自 %@」展示)会分别带进第 2、3 个 PR。第 1 个提案 PR 提交后关闭本 PR,分支保留作参考;配套的 Store 收录 PR(Lithe-IDEA/Lithe-store#32)同时关闭,等新契约落地后重新提交。

如果方向没问题,我先提第 1 个提案 PR。

@xiaoyumuxi

Copy link
Copy Markdown
Collaborator
  1. 这个删除逻辑是可以的,让 core 去进行监控,但是监控文件类型还是需要声明式的。确实有道理比如修改 swift 里面的版本链文件项目级别控制的时候确实可能导致项目级别的工具链版本进行变更
  2. 查询模块麻烦进行内置,监听的文件列表只要有一个变化了整个都需要进行重载的
  3. 运行时这些东西要不就移动到插件的那个管理界面即可了吧,在那里进行修改吧,可以看看下图这种模式
image 就是说你可以弄一个界面去进行统一展示环境相关的内容,但是一定是需要插件去注入式的,这个很关键。我上面是一个例子呀,因为注入的东西不是通用的模板,这个插件采取的方式是在插件的设置页面注入的(如果你打算做插件的话,插件自己的配置都是需要在这个界面的——也就是你说的 PR4)

@xiaoyumuxi

Copy link
Copy Markdown
Collaborator

剩下的没啥问题了,你大胆去做吧

@xiaoyumuxi

Copy link
Copy Markdown
Collaborator

@FxRayHughes 这个可能需要催一下进度,因为有一个 PR 里面我要求使用你这个 PR 的版本管理方案来处理了,如果你没有空的话可以跟我说我来接手

@xiaoyumuxi

Copy link
Copy Markdown
Collaborator
image image image 相关设计的 HTML 等内容都已经附进你的 PR 了的了,这些 Java 等内容都不能写死而是让插件去进行注入的

@xiaoyumuxi

xiaoyumuxi commented Oct 31, 2026 •

Copy link
Copy Markdown
Collaborator

改写后的计划:工具全部由插件注入

上面几张截图换成了一个可交互的 HTML 原型(链接和用法在最后),以原型和这条评论为准。你 4 个 PR 的拆分和 #1420 候选数组的思路都保留;主要改动是下面这条原则。另外,#1558 的 Maven 迁移要用这里的工具定义来声明 Maven,所以第 1 个提案 PR 请优先提。

0. 原则:宿主不认识任何工具

  • 宿主里没有任何工具的定义。 原计划第 2 个 PR 里放在 shared/ 下的“内置定义数据文件”去掉,也不做兼容别名。
  • 保留名:同意你的第 3 点。不带前缀的短名(java、node……)只能由官方签名插件定义;社区提供方(mise)可以回答这些工具。
  • 宿主只做框架:
    • 四层解析(resolver);
    • 查询模块;
    • 目录树;
    • 固定文件 .lithe/toolchains/local.json;
    • 环境页和来源页的骨架。
  • 插件自己的配置放在插件自己的设置页,例如 mise 可执行文件的位置、是否离线。
  • 插件停用或卸载时:
    • 对应的工具行、使用方、导出的环境变量、插件设置页一起消失;
    • 已经写下的固定值留在 local.json 里,重新启用后恢复;
    • 项目文件要求了某个工具、但定义它的插件没启用时,提示「…要求 Maven,但定义它的插件「Maven」未启用」,并给出「在插件页启用」按钮。

1. 三类贡献:界面上每一块由谁注入

界面上的东西 谁注入 建议的契约字段
工具行的名字、图标 工具定义(插件) toolDefinitions[].id / displayName / icon
怎么确认一个目录是这个工具(版本、手动添加时的校验) 工具定义 executables、version.arguments / version.pattern、homeLayouts(例如 *.jdk → Contents/Home)
自动层扫描哪些位置 工具定义 discovery(宿主的系统扫描只执行各定义声明的位置,自己不写路径)
登录 Shell 读哪个变量、PATH 里找哪些命令、Wrapper 工具定义 homeVariable、commands、wrappers
导出的环境变量和 PATH 工具定义 exports(如 JAVA_HOME: ${home})、pathPrepend
已安装版本、项目或全局的要求(带来源文件) 提供方(mise、SDKMAN……) toolchainProviders[],后端实现 toolchains/query,返回 installations / statements;声明 watchFiles;权限 toolchains.provide
“谁在用这个工具”、切换后怎么生效 使用方(语言服务器、构建、运行配置) launch.tool、requiresTools、onToolchainChange:restart / resync / nextRun

2. 选择规则:四层,建在 #1420 的候选数组上

你在第 1 节给的 executables 示意保留。四层规则在 resolver 里的语义:

  1. 固定
    • 按目录生效,取最近的祖先目录的值。
    • 来源是 local.json 的 pins,也就是你示意里 layer: "project" 的 setting。
    • 固定的安装不存在或不可用时返回 terminalFailure,报错,不回落。
  2. 项目文件
    • 离当前目录最近的文件生效。
    • 同一目录里:Wrapper > 环境(如 venv)> 版本声明。这样 mvnw 排在 mise 要求的 Maven 前面,同意你的建议。
    • 同一个文件被多个提供方读到时:原生读取 > 兼容读取。例如 .nvmrc 归 nvm,mise 读它算兼容。
    • 要求的版本没装时返回 skip,必须带原因,例如「mise.toml 要求 node 99,未安装」;界面标「未满足」。
  3. 全局
    • Lithe 全局默认优先,其次是各提供方的全局设置。
    • 多个提供方的全局设置冲突时,不按插件 ID 猜:让用户选;选之前跟登录 Shell 的 PATH 走。
    • 最后是登录 Shell。
  4. 自动:在所有提供方报告的安装里,取满足最低版本的最新版。

3. 按目录(模块)拆分和固定

  • 环境页左侧是 IDEA 式目录树。每个目录(模块)单独解析,子目录继承最近祖先的固定值。
  • 在某个目录上固定一个版本,会写进 .lithe/toolchains/local.json,重新打开项目时自动应用。建议格式:
    {"version": 1, "toolchains": {}, "pins": {".": {"java": {"home": "/Users/me/.local/share/mise/installs/java/21.0.4", "version": "21.0.4", "provider": "mise"}}}}
    需要在契约里给 local.json 加上 pins 字段。
  • 第一次写入时,往 .lithe/.gitignore 加一行 /toolchains/local.json。打开项目时只读取,不写任何文件。
  • 使用方也按目录拆开显示:每个目录下列出用了哪些工具(Maven 构建任务、语言服务器、运行配置),以及它们实际拿到的版本和环境变量。

4. 手动添加

  • 版本下拉框里有「添加…」。选中目录后,按工具定义里的 homeLayouts 和 version 校验,例如 JDK 跑 bin/java -version。
  • 手动添加的安装只存在应用存储里,不写 app bundle,也不写项目。它们只能用来固定,不参与自动层。

5. 紧凑展示

  • 每个工具一行,展开后默认只显示正在生效的那一层。没有值的层写「没有」。
  • 有一个小开关「显示全部」,打开后列出全部层:被覆盖的划掉,未满足的写原因。

6. 查询模块和文件监听

  • 查询模块内置在宿主里:每次查询带 operationID,支持取消和超时,过期结果直接丢弃。
  • 监听哪些文件是声明式的:提供方用 watchFiles 列出文件名或 glob(mise.toml、.tool-versions、.sdkmanrc……),新建或删除这些文件也会触发。
  • 任意一个被监听的文件变化,就整体重新查询所有提供方,不做增量。可以复用 feat(plugins): 插件注入的工具链框架与 macOS 只读环境设置页 #1581 的 stat 指纹缓存。

7. 拆分(改写后)

  1. 提案:proposed/ 下的 Agent Note,加上契约草案和 fixture,不改代码。契约包括:
    • toolDefinitions[]:第 1 节表格里的字段;
    • toolchainProviders[]、toolchains/query 的请求和应答、watchFiles、权限 toolchains.provide;
    • toolchain 候选来源,以及 skip 的原因字段;
    • launch.tool 和 runtimeDependencies[].tool 改成可以填任意已定义的工具 ID(现在是写死的枚举);新增 requiresTools、onToolchainChange;
    • local.json 的 pins;
    • fixture 至少包括:一个「Java 支持」式的工具定义、一个 mise 提供方、一个引用未定义工具的反例。
    • 合并后记得在 lithe-plugin-docs 运行 node scripts/contract.mjs sync。
  2. Core 与查询客户端:
    • 定义校验和答案校验(绝对路径、存在、可执行、不在工作区内、数量上限);
    • resolver 的扩展:toolchain 候选、带原因的 skip、项目层从 pins 取最近的祖先;
    • macOS 查询客户端;
    • 系统扫描作为第一个内置提供方,扫哪些位置来自各工具定义的 discovery;
    • 不再有内置定义数据文件。
  3. 统一选择与设置界面:
    • 按原型做环境页(目录树、工具行、紧凑展示、使用方、导出的环境变量)和来源页(提供方、手动添加),文案全部走 shared/i18n;
    • ProjectRuntimeService 的三条链换成按工具定义解析;
    • 把 Java 的定义迁进「Java 支持」,Maven 的定义迁进 Maven 插件(和 feat(plugins): #1495 Maven 模型、配置、测试与服务运行迁移(阶段 D 进行中) #1558 协调),并删掉 project-jdk / project-maven 的写死;
    • Node、Go、Python 等它们的官方插件声明定义后再迁。在那之前保持现状,但不要再新增特判。
  4. 插件:
    • mise 0.2.0,后端调用 mise ls --json;
    • SDKMAN 验收插件,同时把 SDKMAN 的位置迁出 MacRuntimeDiscovery;
    • 两个插件各自的设置放在自己的插件设置页。

你第 3 节里的其余几点照原样进行:免重启第一阶段不放宽,checkRuntimeVersion 的 Java 8 特例改成定义里的正则。

8. 现有代码里要清掉的写死

  • 契约:
    • plugin-manifest.schema.json:launch.kind: "runtime" 的 tool 只允许 java / node;runtimeDependencies[].tool 只允许 go / java / node。
    • rust/lithe-core/src/debug/adapters.rs:SELECTED_RUNTIME_TOOLS = ["java", "node", "python"]。
  • Core:project-jdk、project-maven、project-node 出现在:
    • execution/configuration.rs
    • execution/environment.rs
    • execution/java_selection.rs
    • execution/detectors/{maven,gradle,npm}.rs
    • project/maven.rs
  • macOS:
    • 本 PR 里的 tool == "java";
    • ProjectRuntimeService 的三条选择链;
    • MacRuntimeDiscovery 里写死的 SDKMAN 路径。

9. 原型怎么用

原型 HTML:https://gist.github.com/xiaoyumuxi/2ae889a6592599b13998bc811c647942

下载后打开:curl -L -o environment-settings.html https://gist.githubusercontent.com/xiaoyumuxi/2ae889a6592599b13998bc811c647942/raw/environment-settings.html && open environment-settings.html;也可以在 gist 页面点 Raw,另存为 .html。

  • 下载后用浏览器直接打开,不需要联网。
  • 顶部的演示栏有「工具插件」开关。关掉「Maven」或「Java 支持」,可以看到工具行、使用方、导出的环境变量、来源页里提供方的工具胶囊和设置导航一起变化;固定值保留,重新启用后恢复。全部关掉时显示空状态。
  • 演示栏的「实现说明」会打开一个侧栏,内容包括:
    • 界面每一块对应哪个插件注入、建议的契约字段;
    • 宿主负责哪些部分;
    • 原型里定下来的行为;
    • 要清掉的写死;
    • 「Java 支持」工具定义和 mise 提供方的清单示意。
  • 每一层旁边的 ⓘ 和每个工具展开后的「定义」行,会说明这个工具的数据来自哪个插件的哪个字段。

xiaoyumuxi added a commit that referenced this pull request Oct 31, 2026
* feat(plugins): 声明式语法高亮与运行标记(API-25/27/29)

- languages 2:插件语言可带 Monarch 语法和编辑器配置,Core 校验,纯语法插件免重启
- runMarkers 1:backend 2 插件函数回答可运行行,编辑器画运行按钮,点击经 commands/didRun 带 runMarker
- 契约、fixture、Agent Note、计划文档与功能矩阵同步更新;Windows 暂不支持

Refs #570

* feat(plugins): 独立编辑器高亮与动作扩展

- languages 2 提供静态语法和独立编辑配置,插件正则在可终止 worker 中运行\n- editor/registerActions 运行期注册文本动作,点击复用普通函数调用,不扩张 Run/LSP/Build/Debug 领域契约\n- 补齐文档版本、动作租约、取消与清理、独立工具后端启用及安全模式边界\n- 更新 Skill、审查规则与决策笔记,总结 #1581/#1558 的架构边界\n- 同步契约、fixtures、中英词条、功能矩阵与资源复用说明\n\nRefs #570\nReworks #1617

* docs(plugins): 明确 API-25 JSON 贡献的支持范围与局限

区分包内静态 JSON 贡献与尚未开放的运行期 Provider 注册、替换和释放;说明独立语义 Provider、新引擎适配及平台、SDK 和验收限制。同步计划、架构笔记、正式契约与功能矩阵。

* fix(plugins): 修复编辑器扩展审查中的生命周期与高亮问题

动作刷新立即确认调度并隔离非致命失败,整批收集共享八秒期限并在撤销时清理计时任务。

Core 保留校验后的 grammar 原始文本,worker 按源顺序解析 cases 并显式默认 root;配置-only 别名借用既有 tokenizer,重映射 token 语言身份以保留独立注释配置。

模块状态观察弱引用 runtime,补齐有界、确定性的 Swift/Rust/WebKit 回归与测试文档模型清理,同步契约、Agent Note 和功能矩阵。

验证:57 项 Swift、1459 项 Rust、39 项编辑器和 119 项真实 WebKit 测试通过;静态边界、i18n、只读 bundle、资源复用与矩阵门禁通过。未执行 Windows 原生构建或打包安装验收。

Refs #1617, #570
@xiaoyumuxi
xiaoyumuxi force-pushed the feat/plugin-toolchain-sources branch from 27852b0 to 71f9f65 Compare October 31, 2026 14:07
xiaoyumuxi added a commit to FxRayHughes/Lithe-IDEA that referenced this pull request Oct 31, 2026
按 1lck#1581 评审后的改写计划重做:宿主不认识任何工具,工具定义、版本管理器
(提供方)全部由插件声明;Core 只做校验和四层解析,不解析 mise 等管理器
的私有格式。

- 契约:新增 toolDefinitions 1、toolchainProviders 1,executableResolution
  增加 v2(只用于工具定义候选);同步 schema、README、toolchains.md、
  fixture 和 rust-core-api.md;proposals/toolchains/ 放完整方案草案
- Rust Core:新领域模块 rust/lithe-core/src/toolchain/(定义注册表、
  提供方答复校验、目录固定、固定/项目文件/全局/自动四层解析、版本匹配、
  扫描计划)和六个 toolchains.* 命令,全部只处理宿主快照,不启动进程、
  不扫描、不写文件
- macOS:设置 › 构建新增「运行环境」「环境来源」两页,内容来自已启用
  插件的声明和磁盘上的真实文件;未实现的添加、切换、保存、查询入口不显示
- 登录 Shell:新增只读 inspect,来源页显示读取状态、变量数、耗时和
  PATH 各目录来源;重新读取不改变本进程 PATH
- Debug 预览入口 --toolchain-settings-demo / --toolchain-sources-demo
- Agent Note、设置层级 Note、功能矩阵和中英词条同步

Refs 1lck#1514
@xiaoyumuxi xiaoyumuxi changed the title feat(plugins): 声明式版本管理器 toolchainSources,自动发现 mise 等安装的运行时并按项目版本选择 feat(plugins): 插件注入的工具链框架与 macOS 只读环境设置页 Oct 31, 2026
@xiaoyumuxi
xiaoyumuxi marked this pull request as ready for review October 31, 2026 14:11
@lithe-code-review

lithe-code-review Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

Lithe Review

结论: ⚠️ 有建议
范围: preview ← feat/plugin-toolchain-sources · head: 71f9f65

未发现需要阻塞合并的问题。

Findings

  1. [P2] 保留规范化安装路径对应的文件证据
    rust/lithe-core/src/toolchain/resolution.rs:362

    • 实际影响:合法的符号链接安装通过提供方答复校验后,在解析阶段被误判为 unsafePath,导致项目要求回落、自动层遗漏该安装。
    • 触发条件与复现输入:提供方返回 /tools/java/latest,宿主事实以该路径及其 bin/java 为 path,以 /tools/java/21 下对应路径为 canonicalPath,并提供成功的版本探测记录;所有路径均在工作区之外。
    • 调用路径:query::decode 接受这些事实,并将安装及 statement 中的路径规范化为 /tools/java/21;这里再调用 snapshot.selection,经 validate_installation 按规范化路径重新查找事实。但事实表仅以原始 path 建索引,契约没有要求再提供一份规范化路径记录,因此同一快照会在第二次校验时失败。全局和自动分支也存在相同问题。
    • 来源:本次引入,属于新增 Core 解析命令的内部一致性问题。
  2. [P2] 环境候选应使用其声明的可执行文件路径
    rust/lithe-core/src/toolchain/resolution.rs:1071

    • 实际影响:解析结果可能选择另一个可执行文件及版本,与插件候选计划不符,也与 macOS 设置加载器按候选 path 查找的行为不一致。
    • 触发条件与复现输入:以工具定义 fixture 为基础,增加同平台布局 alt/bin/java,保留 bin/java 为第一个布局,将环境候选的 path 指向 alt/bin/java。设置 JAVA_HOME,为两个程序提供有效文件事实与不同版本的成功探测记录。
    • 调用路径:定义校验允许环境候选引用任一匹配布局;此分支却只读取 variable,未使用 candidate.path。随后 checked_installation 从布局列表首项开始选择,最终返回 bin/java。既有环境候选的路径语义因此丢失。
    • 来源:本次引入,属于新增候选解析实现。

Scope Check

  • 关联 [Bug] macOS 下自动检测不到 mise/SDKMAN 等版本管理器安装的 Maven 和 JDK,必须手动指定路径 #1514 仅交付基础部分:实际版本管理器发现和运行时选择尚未接入,Issue 仍未解决;这与 PR 明确声明的范围一致。
  • 已完成范围:Core 注册、答复校验、固定值和分层解析,以及 macOS 只读设置骨架。
  • 未完成范围:提供方查询进程、监听、持久化、使用方迁移及 Windows 原生接入均已明确延期,不作为本次缺陷。
  • 候选排序复用了现有图校验;未发现重新实现 mise/SDKMAN 私有版本语义。两项 Finding 均位于本次新增逻辑,属于本次职责范围。

Suggestions

  1. 保留已校验安装的原始查找路径或证据关联,使规范化结果能够继续使用同一份快照;补充“外部符号链接安装从 decode 到 resolve”的正向回归用例。
  2. 将环境候选指定的布局传入校验与选择流程,确保探测、导出和最终 executable 都对应其 path;补充同平台多个布局同时存在的用例。
  3. 补充可确认对应本 head 的两页实现截图,覆盖宽窄布局;已有界面受影响时提供前后对比。当前上下文中的图片是设计参考,无法据此确认最终视觉效果。

Summary

整体分层与延期边界清晰。建议在后续原生接入前修复上述两处解析不一致,避免设置展示与实际选择产生分歧。

Verification

  • 静态检查了新增 toolchain 模块、插件准入和命令分发、直接相关契约与测试,以及 macOS 加载器、登录 Shell、设置模型和视图调用链;未读取生成资源正文。
  • 作者报告 Rust 检查、71 个定向 macOS 测试及相关静态检查通过,本次未独立运行或核验这些结果。
  • 所提供 head checks 中 Notes、矩阵及 Ubuntu i18n 有成功结果;Swift/Rust 测试任务为 skipped,部分检查仍 queued,平台 gate 成功不能视为对应测试已执行。
  • 本次未运行任何代码、测试或应用,未访问网络。Windows 原生构建、macOS 全量测试及本 head 的视觉效果仍未验证。

按 1lck#1581 评审后的改写计划重做:宿主不认识任何工具,工具定义、版本管理器
(提供方)全部由插件声明;Core 只做校验和四层解析,不解析 mise 等管理器
的私有格式。

- 契约:新增 toolDefinitions 1、toolchainProviders 1,executableResolution
  增加 v2(只用于工具定义候选);同步 schema、README、toolchains.md、
  fixture 和 rust-core-api.md;proposals/toolchains/ 放完整方案草案
- Rust Core:新领域模块 rust/lithe-core/src/toolchain/(定义注册表、
  提供方答复校验、目录固定、固定/项目文件/全局/自动四层解析、版本匹配、
  扫描计划)和六个 toolchains.* 命令,全部只处理宿主快照,不启动进程、
  不扫描、不写文件
- macOS:设置 › 构建新增「运行环境」「环境来源」两页,内容来自已启用
  插件的声明和磁盘上的真实文件;未实现的添加、切换、保存、查询入口不显示
- 登录 Shell:新增只读 inspect,来源页显示读取状态、变量数、耗时和
  PATH 各目录来源;重新读取不改变本进程 PATH
- Debug 预览入口 --toolchain-settings-demo / --toolchain-sources-demo
- Agent Note、设置层级 Note、功能矩阵和中英词条同步

Refs 1lck#1514
工具的已安装版本有两条途径,可以只用其一或同时使用:
- 声明式扫描:工具定义的 discovery/homeLayouts/version 由宿主展开,不运行
  插件代码;可用的根和通配写法固定,新的安装形态交给提供方回答。
- 提供方查询:toolchainProviders 通过 toolchains/query 返回安装和要求。

- watchFiles 改为可选:不声明的提供方只报告安装,答复里带版本要求会被拒绝;
  定义工具的插件也可以作为自己工具的提供方。
- 自动层把两条途径合成一个候选集合:同一规范化目录只算一次,来源标注优先
  提供方、其次 host:system,只用于显示;不同目录同版本仍报冲突。
- discoveryPlan 对每个工具同时列出 scans 和 providers。
- macOS 环境来源页对只报告安装的提供方显示「只报告已安装的版本」。

同时修复评审的两个 P2:
- 符号链接安装在解码时已规范化,解析时却按原始路径查证据而误判 unsafePath;
  现在文件证据同时证明其规范化目标,证据互相矛盾时拒绝快照。
- 环境变量候选声明的 path 布局被忽略、总取第一个布局;现在按声明的布局选择。
- Core 新增 toolchains.candidates:列出一个工具的登录 Shell home 变量、扫描到的安装、跳过原因和自动层的选择;和 resolve 共用事实校验,宿主分两轮调用完成版本探测,工作区内的目录在探测前拒绝
- toolchains.discoveryPlan 增加 pathPrepend 模板和各平台适用的 home 变量
- 新增 MacToolchainDiscovery:按插件声明展开扫描,只探测 Core 返回的可执行文件;扫描根、条目、文件事实、安装数、探测数、并发和总时长都有上限,超出时给出诊断
- 运行环境页:全局卡片显示 home 变量和探测到的版本,PATH 命令标为版本未验证;自动卡片列出真实安装、选中项、同版本冲突和跳过数,路径显示扫描到的条目名
- 加载器只把登记在官方目录、并经内置或插件市场安装的插件标为官方来源
- 预览改用完整的样例清单,扫描本机真实安装
- 更新契约 toolchains.md、rust-core-api.md、Agent Note、功能矩阵和中英词条
@xiaoyumuxi
xiaoyumuxi force-pushed the feat/plugin-toolchain-sources branch from 78e696e to 06337dc Compare November 1, 2026 03:48
运行环境、环境来源两页插在项目页和运行配置之间,使 SettingsCategory
声明顺序(搜索结果顺序)与侧栏构建分组顺序不一致,
settingsCategoryPlacesProjectAndRunBetweenKeymapAndTerminal 在 CI 失败。
把两页的 case 移到项目页之前,测试改为断言 Keymap 与 Terminal 之间
恰好是构建分组,且分组以项目页、运行配置结尾。
test(macos): 设置页声明顺序与构建分组保持一致(修复 1lck#1581 的 macOS CI)
- Core: lsp.toolchainRequest 与 litheToolchains 协商,原样回传回复交 toolchains.decodeReply
- Core: candidates 接受 providerInstallations 并保留提供方归属
- 修复别名链接 home 只有链接路径事实时被判 unsafePath(机器人 P2)
- macOS: 启动或复用插件后端查询,全局+逐目录独立 operationID/代次,超时与取消
- macOS: watchFiles 与回复 watch 变化整份重查,短时间事件合并
- 设置页显示提供方要求、版本与查询结果;文案走 shared/i18n
- 更新契约、功能矩阵与 Agent Note
feat(plugins): macOS 设置页接入工具链提供方查询 (1lck#1581)
@FxRayHughes

Copy link
Copy Markdown
Author

按上次的分工,补齐了框架剩下的部分。每一项都在单独分支上做,经 fork 内 PR 合入本分支,当前 head 为 b87c7f60:

  • A(test(macos): 设置页声明顺序与构建分组保持一致(修复 #1581 的 macOS CI) FxRayHughes/Lithe-IDEA#1):修复 macOS CI 里 settingsCategoryPlacesProjectAndRunBetweenKeymapAndTerminal 的设置分类顺序回归。
  • B(feat(plugins): macOS 设置页接入工具链提供方查询 (#1581) FxRayHughes/Lithe-IDEA#2):macOS 查询客户端。
    • Core 新增 lsp.toolchainRequest 和 litheToolchains 协商。回复原样交给 toolchains.decodeReply,Core 不另存一份错误规则。
    • 宿主启动或复用插件后端。全局查询之外,再逐目录查询;每次查询有独立的 operationID 和代次,有超时,也能取消。
    • watchFiles 或回复里的 watch 有变化时,整份重新查询,短时间内的多次变化合并成一次。
    • 设置页显示提供方给出的要求、版本和查询结果。
    • 顺带修复了 bot 提的 P2(resolution.rs):provider 返回别名链接、宿主只有链接路径的事实时,会被判为 unsafePath。已加回归测试。
  • C(Answer Lithe toolchain queries from mise (0.2.0) FxRayHughes/lithe-plugin-mise#1,Draft,未发版):mise 插件 0.2.0。
    • 单文件 Rust 后端,基于 mise ls --json / mise bin-paths,默认离线,不执行 trust 或 install。
    • 配置未受信任时如实返回 -32073 untrusted,找不到 mise 时返回 miseNotFound。
    • 插件自己的设置页提供 mise 路径和离线开关。
    • 用这个分支的 Core decodeReply 校验了真实 mise 2026.9.8 的输出。

本地验证

  • lithe-core --lib:1498 个测试通过。
  • macOS 工具链、插件后端、LanguageServer、运行配置相关测试:306 个通过。
  • 文档校验、契约校验、矩阵(含变更闸)、i18n 校验均通过。
  • clippy 没有新增问题。

还没做到的

  • 合入后本 PR 上游的 CI 没有被触发:62786636、b87c7f60 都没有 run。需要维护者重新触发。
  • macOS 全量测试和 Windows 没在本地跑。
  • 没有在真实 App 里装上 mise 0.2.0 做端到端验证。
  • 保存、Lithe 全局默认和运行时消费者仍未接入。
  • 契约有改动,合入后需要同步 lithe-plugin-docs。

框架合入后,我再向 Store 重新提交 mise 的索引 PR。

- 每个查询过的目录、每个工具调用一次 toolchains.resolve,摘要、徽标和
  “第几层生效”都读它的结果,视图不再自己按固定 → 项目 → 全局 → 自动排序
- 解析与提供方查询共用同一份文件事实;同一轮查询的提供方共享代次,
  每次查询仍有独立 operationID
- 固定值交给 Core 校验:只有固定值的目录只解析它固定的工具(每次加载
  最多 256 个),固定的 home 经 toolchains.candidates 检查后探测,
  不会出现在自动检测列表;不可用时终止失败,不回落
- 未安装的项目要求记为 unmet 并退到下一层;Core 无法给出结果时给出诊断
- 已知限制写入提案 Note 并建 Issue:冲突选择与写入 1lck#1639、
  项目 wrapper 1lck#1640、PATH 命令 commandHomes 1lck#1641
- 新增文案走 shared/i18n 并重新生成

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review: no-score ghfind author score missing (not zero)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants