The custom Node.js and Python choices could appear unavailable until a candidate was already discovered, and selecting the source implicitly adopted the first candidate. The chooser now stays operable, waits for an explicit validated path, keeps cancellation quiet, and shows validation errors beside the affected runtime. Candidate discovery now prioritizes active virtual environments and Conda before bounded user/system PATH and common Node/Python locations, while preserving canonical validation and deduplication. Release note: 修复自定义 Node.js/Python 环境入口不可用的问题,并增加对 PATH、venv、Conda 和常见安装位置的解释器检测。
461 lines
18 KiB
Markdown
461 lines
18 KiB
Markdown
# 工具执行环境功能逻辑设计
|
||
|
||
## 文档信息
|
||
|
||
| 项目 | 内容 |
|
||
| --- | --- |
|
||
| 状态 | 已实施,验收中 |
|
||
| 版本 | 0.2 |
|
||
| 日期 | 2026-09-02 |
|
||
| 关联 PRD | [工具执行环境 PRD](./prd.md) |
|
||
| 关联场景 | [工具执行环境 User Stories](./user-stories.md) |
|
||
|
||
本文是本功能业务规则、状态转换、决策表和跨场景一致性的权威来源。它不定义组件视觉,也
|
||
不规定代码模块如何实现。
|
||
|
||
## 1. 逻辑目标
|
||
|
||
1. 同一用户操作在不同本机 Runtime 和 stdio MCP 中得到一致的解释器选择。
|
||
2. 系统不通过隐式回退掩盖配置错误。
|
||
3. 工具可执行、配套命令可执行和具体能力依赖可用是三个可区分事实。
|
||
4. 已运行进程使用稳定环境,不被设置变化中途改写。
|
||
5. 安装、更新、取消、失败和删除都有唯一、可恢复的结果。
|
||
6. 本机环境和远程 Host 环境没有隐式传播。
|
||
|
||
## 2. 核心不变量
|
||
|
||
### INV-1 单一明确来源
|
||
|
||
Node.js 和 Python 各自恰好保存一个来源:`managed` 或 `custom`。不存在 `auto`、候选优先级
|
||
列表或运行时动态选择。
|
||
|
||
### INV-2 禁止静默回退
|
||
|
||
当前来源不可用时,该工具明确不可用。即使发现另一套系统解释器,也不会自动切换。
|
||
|
||
### INV-3 配套命令同源
|
||
|
||
- `node`、`npm`、`npx` 必须属于同一 Node 来源。
|
||
- `python`、`python3`、`pip` 必须属于同一 Python 来源。
|
||
- `pip` 等价于当前 Python 的 `-m pip`,不能解析到其他解释器。
|
||
|
||
### INV-4 三层状态独立
|
||
|
||
1. **解释器状态**:Node 或 Python 本身能否执行。
|
||
2. **工具组合状态**:npm/npx 或 pip/venv/SSL 是否完整。
|
||
3. **能力依赖状态**:具体 SKILL/MCP 的入口和第三方依赖是否通过验证。
|
||
|
||
任一上层成功都不能推导下一层成功。
|
||
|
||
### INV-5 进程环境不可变
|
||
|
||
每个 Runtime/MCP 进程在启动时取得一份环境快照。设置变化只影响之后启动的进程,不改写
|
||
已运行进程的 PATH 或解释器。
|
||
|
||
### INV-6 绝对命令优先
|
||
|
||
stdio MCP 配置的绝对 command 表达用户对该 MCP 的明确覆盖,原样执行,不经过全局工具
|
||
命令替换。
|
||
|
||
### INV-7 本机与远程隔离
|
||
|
||
本机工具选择、shim、Python 包、SKILL 包和 stdio MCP 配置不上传到远程 Host。
|
||
|
||
### INV-8 不推断能力依赖已就绪
|
||
|
||
能力没有专属实际诊断时,状态只能是“未验证”,不能根据解释器版本或文件存在推断“已就绪”。
|
||
|
||
### INV-9 受管工具工件来源明确且冻结
|
||
|
||
每个受管工具外部工件同时声明固定原生地址和 OSS Target。用户选择一个下载源后,任务
|
||
启动时冻结该来源;失败不回退。OSS 必须是原生工件的字节级副本。
|
||
|
||
### INV-10 设置导航不改变业务状态
|
||
|
||
“能力与工具”的三个水平 Tab 只组织界面,不改变 Skill、MCP 或工具环境设置。切换 Tab
|
||
不得提交、丢弃或重置当前设置面板会话中的编辑状态;关闭后不持久化外层 Tab。
|
||
|
||
分阶段交付期间,未实现的“工具执行环境”不进入 Tab 状态集合。第一阶段状态集合只有
|
||
`skills | mcp`,后续实现完成后才扩展为 `skills | mcp | tool-environment`。
|
||
|
||
### INV-11 内置 Skill 默认值不覆盖用户状态
|
||
|
||
只有状态存储中没有对应 Skill 记录时才使用产品默认值。通用文档工具
|
||
`deai-writing`、`longdoc-docx` 默认启用;产品营销类 `product-evidence`、
|
||
`product-marketing`、`product-presentation` 默认停用。用户显式保存的启停状态和
|
||
Runtime 分配优先,升级不得重置。
|
||
|
||
九种产品营销文档配方属于 `product-marketing` 包内工作流,不是独立能力状态维度,
|
||
不进入顶层 Skill 目录,也不单独注入 Runtime。
|
||
|
||
## 3. 状态维度
|
||
|
||
### 3.1 来源选择
|
||
|
||
```text
|
||
managed
|
||
custom(executablePath)
|
||
```
|
||
|
||
这是唯一持久状态。
|
||
|
||
候选选择还具有不持久化的界面状态:
|
||
|
||
```text
|
||
closed
|
||
choosing
|
||
validating
|
||
failed
|
||
```
|
||
|
||
- 从 `closed` 进入 `choosing` 不改变当前持久来源。
|
||
- `choosing` 中不得自动采用首个候选;只有用户明确选择候选或文件后才进入 `validating`。
|
||
- 验证成功后一次性保存 `custom(executablePath)`;验证失败回到 `choosing` 并就地显示错误。
|
||
- 文件选择取消回到 `choosing`,不显示错误且不改变当前来源。
|
||
- 没有候选不是禁用“自定义环境”的理由,用户仍可刷新或选择其他可执行文件。
|
||
|
||
### 3.2 托管 Python 安装状态
|
||
|
||
```text
|
||
not-installed
|
||
downloading
|
||
verifying
|
||
installing
|
||
testing
|
||
ready
|
||
update-available
|
||
error
|
||
```
|
||
|
||
`error` 同时携带“是否仍有旧版可用”。若旧版可用,实际工具状态仍基于旧版诊断。
|
||
|
||
托管 Node 随 GoodBuddy 存在,不使用下载安装状态,但仍可能因打包损坏进入诊断失败。
|
||
|
||
### 3.3 工具诊断状态
|
||
|
||
```text
|
||
unknown
|
||
testing
|
||
ready
|
||
partial
|
||
unavailable
|
||
```
|
||
|
||
- `ready`:解释器和本功能承诺的配套命令全部通过。
|
||
- `partial`:解释器通过,至少一个配套命令失败。
|
||
- `unavailable`:解释器本身不能使用。
|
||
|
||
### 3.4 能力依赖状态
|
||
|
||
```text
|
||
unknown
|
||
checking
|
||
ready
|
||
missing
|
||
incompatible
|
||
error
|
||
```
|
||
|
||
- `unknown` 在 UI 中显示“依赖未验证”。
|
||
- `missing` 表示明确缺少包或入口。
|
||
- `incompatible` 表示解释器版本、架构或 ABI 不满足已声明要求。
|
||
- `error` 表示诊断本身没有得出有效结论。
|
||
|
||
### 3.5 工具下载源
|
||
|
||
```text
|
||
native
|
||
oss
|
||
```
|
||
|
||
这是独立于 Node/Python 环境来源的持久设置。全新用户默认 `native`。
|
||
|
||
## 4. 来源解析决策表
|
||
|
||
| 保存来源 | 当前事实 | 解析结果 | 是否回退 |
|
||
| --- | --- | --- | --- |
|
||
| 托管 Node | 完整诊断通过 | 使用 Electron Node 与锁定 npm/npx | 否 |
|
||
| 托管 Node | Node 通过,npm/npx 失败 | Node 部分可用,显示失败项 | 否 |
|
||
| 托管 Node | Node 失败 | Node 不可用,提示修复/重装 GoodBuddy | 否 |
|
||
| 托管 Python | 未安装 | Python 不可用,提供安装 | 否 |
|
||
| 托管 Python | 已安装且诊断通过 | 使用托管 Python | 否 |
|
||
| 托管 Python | 更新失败但旧版健康 | 继续使用旧版并显示更新失败 | 否 |
|
||
| 托管 Python | 安装损坏 | Python 不可用,提供重新安装/删除 | 否 |
|
||
| 自定义 | 路径及诊断通过 | 使用该绝对路径 | 否 |
|
||
| 自定义 | 路径失效 | 保持选择并显示路径不可用 | 否 |
|
||
| 自定义 | 解释器通过、配套命令失败 | 部分可用并显示失败项 | 否 |
|
||
| 任意 | 发现其他有效解释器 | 只作为候选展示 | 不自动使用 |
|
||
|
||
## 5. 命令解析决策表
|
||
|
||
| 调用形式 | 结果 |
|
||
| --- | --- |
|
||
| `node ...` | 当前 Node |
|
||
| `npm ...` | 当前 Node 配套 npm |
|
||
| `npx ...` | 当前 Node 配套 npx |
|
||
| `python ...` | 当前 Python |
|
||
| `python3 ...` | 当前 Python |
|
||
| `pip ...` | 当前 Python `-m pip` |
|
||
| `#!/usr/bin/env node` | 从进程工具 PATH 解析当前 Node |
|
||
| `#!/usr/bin/env python3` | 从进程工具 PATH 解析当前 Python |
|
||
| stdio MCP 绝对 command | 原样执行 |
|
||
| 未托管裸命令,例如 `uvx` | 按原筛选 PATH 处理;找不到则明确失败 |
|
||
| HTTP/SSE MCP | 不参与本机命令解析 |
|
||
|
||
绝对 command 的子进程若自行调用裸 `node` 或 `python`,仍会看到该 MCP 进程启动时的工具
|
||
PATH。这不改变绝对 command 本身。
|
||
|
||
## 6. 首次使用和迁移
|
||
|
||
### 6.1 全新安装
|
||
|
||
| 工具 | 默认选择 | 首次状态 |
|
||
| --- | --- | --- |
|
||
| Node.js | GoodBuddy 托管 | 立即真实诊断 |
|
||
| Python | GoodBuddy 托管 | 未安装,展示安装入口 |
|
||
|
||
系统候选可以展示,但不会改变默认选择。
|
||
|
||
### 6.2 旧版本升级
|
||
|
||
1. 设置中没有工具环境字段时执行一次迁移。
|
||
2. 分别按旧版本实际 PATH 解析 Node 和 Python 3。
|
||
3. 有效解释器固化为 `custom + 绝对真实路径`。
|
||
4. 无有效解释器时选择 `managed`。
|
||
5. 持久化后不再因 PATH 变化迁移。
|
||
|
||
迁移中 Node 和 Python 独立决策,不要求二者使用同一种来源。
|
||
|
||
## 7. 托管 Python 生命周期
|
||
|
||
### 7.1 首次安装
|
||
|
||
```text
|
||
not-installed
|
||
→ downloading
|
||
→ verifying
|
||
→ installing
|
||
→ testing
|
||
→ ready
|
||
```
|
||
|
||
任一步失败:
|
||
|
||
```text
|
||
→ 删除本次暂存
|
||
→ error(没有活动版本)
|
||
→ 用户可重试
|
||
```
|
||
|
||
### 7.2 取消
|
||
|
||
| 取消阶段 | 结果 |
|
||
| --- | --- |
|
||
| 下载 | 终止下载、删除暂存、回到安装前状态 |
|
||
| 校验 | 终止校验、删除暂存、回到安装前状态 |
|
||
| 解压/安装 | 完成本次有界清理、回到安装前状态 |
|
||
| 测试 | 停止诊断、删除未发布候选、回到安装前状态 |
|
||
|
||
取消不发布候选版本,不把取消显示为安装失败。
|
||
|
||
### 7.3 更新
|
||
|
||
1. 下载和验证候选时,旧版继续服务现有进程。
|
||
2. 应用候选前,页面说明需要停止依赖托管 Python 的本机 Runtime/MCP。
|
||
3. 用户确认后通过现有生命周期取消/停止相关进程。
|
||
4. 无法确认停止时不替换当前版本。
|
||
5. 候选完整诊断通过后才发布。
|
||
6. 失败继续使用旧版,并显示更新失败。
|
||
|
||
首期不让两个托管 Python 版本长期并存。候选暂存只服务本次更新。
|
||
|
||
### 7.4 删除
|
||
|
||
| 条件 | 行为 |
|
||
| --- | --- |
|
||
| 没有使用中进程 | 确认后删除托管目录 |
|
||
| 有使用中进程 | 说明影响,用户确认后停止相关进程再删除 |
|
||
| 进程停止失败 | 不删除,保留当前环境 |
|
||
| 当前选择为自定义 | 删除托管包不改变自定义来源 |
|
||
| 当前选择为托管 | 删除后保持托管选择,状态变为未安装 |
|
||
|
||
## 8. 设置保存与进程生效
|
||
|
||
### 8.1 保存时
|
||
|
||
- 验证待保存来源。
|
||
- 自定义路径必须在保存前通过解释器诊断;配套命令可以形成 `partial`。
|
||
- 保存成功后标记本机 Runtime 环境配置已变化。
|
||
- 不直接修改任何运行中进程。
|
||
|
||
### 8.2 后续进程
|
||
|
||
- 没有活动请求的缓存 Runtime 在下次使用前按现有重建入口重建。
|
||
- 有活动请求的 Runtime 完成或被用户停止前保持原环境。
|
||
- 已启动 stdio MCP 保持原环境,正常关闭、重连或显式测试后使用新环境。
|
||
- 新启动的 Runtime/MCP 立即使用新环境。
|
||
|
||
每个工具调用的活动记录应归属于实际承载它的进程,不根据最新设置追改历史来源。
|
||
|
||
### 8.3 设置导航
|
||
|
||
| 入口或操作 | 打开的外层 Tab | 数据行为 |
|
||
| --- | --- | --- |
|
||
| 普通“能力与工具”入口 | Skills | 不修改业务数据 |
|
||
| 用户点击 Skills | Skills | 不修改业务数据 |
|
||
| 用户点击 MCP | MCP | 不修改业务数据 |
|
||
| 未来的定向入口 | 指定 Tab | 不修改业务数据 |
|
||
| 同一设置面板会话中切换 Tab | 用户选择的 Tab | 保留三个面板的会话内状态 |
|
||
| 关闭后重新打开普通入口 | Skills | 不恢复上次外层 Tab |
|
||
|
||
MCP 内层页签状态属于 MCP 面板,不与外层 Tab 合并。外层 Tab 的默认值或定向入口不能改写
|
||
MCP 的连接、凭据、启用或 Runtime 分配状态。
|
||
|
||
## 9. 能力依赖逻辑
|
||
|
||
### 9.1 诊断输入
|
||
|
||
能力可以声明:
|
||
|
||
- 需要 Node 或 Python。
|
||
- 最低/最高解释器版本。
|
||
- 可选依赖文件位置。
|
||
- 可选的无副作用诊断入口。
|
||
|
||
首期声明只用于显示、版本比较和显式诊断,不触发自动安装。
|
||
|
||
### 9.2 状态推导
|
||
|
||
| 工具状态 | 能力诊断 | 能力状态 |
|
||
| --- | --- | --- |
|
||
| unavailable | 不执行 | missing 或 incompatible,附工具原因 |
|
||
| partial | 能力不依赖失败配套项且实际诊断成功 | ready |
|
||
| partial | 能力依赖失败配套项 | missing |
|
||
| ready | 没有能力诊断 | unknown |
|
||
| ready | 实际诊断成功 | ready |
|
||
| ready | 明确缺包 | missing |
|
||
| ready | 版本/ABI 不满足 | incompatible |
|
||
| 任意 | 诊断自身异常 | error |
|
||
|
||
“依赖未验证”不是错误,也不能显示为“已就绪”。
|
||
|
||
## 10. Ask、Execute 与 MCP
|
||
|
||
| 场景 | 解释器是否可见 | 是否允许执行 |
|
||
| --- | --- | --- |
|
||
| 本机 Execute Runtime | 是 | 按现有 Execute 权限 |
|
||
| 本机 Ask Runtime | 可以存在于进程环境 | 仍由 Runtime Ask 边界决定,不能因此获得命令权限 |
|
||
| Main 启动的已启用 stdio MCP | 是 | 按当前 MCP 分配和模式规则 |
|
||
| HTTP/SSE MCP | 不适用 | 按当前网络 MCP 规则 |
|
||
| 远程 Runtime | 否 | 使用远程 Host 自身环境 |
|
||
|
||
工具环境不创建新的工具,不扩大 Runtime 当前允许的工具集合。
|
||
|
||
## 11. 网络受限与离线
|
||
|
||
### 11.1 托管 Node
|
||
|
||
随 GoodBuddy 提供,安装后可离线使用。
|
||
|
||
### 11.2 托管 Python
|
||
|
||
- Windows 原生 Target 固定到官方 NuGet 包;macOS/Linux 原生 Target 固定到 Astral
|
||
`python-build-standalone`。
|
||
- OSS Target 是对应原生工件的字节级副本。
|
||
- 下载源不可达时保持未安装或旧版可用状态,并显示准确原因。
|
||
- 首期不提供 Python 离线包导入,避免引入未经选型的包格式和导入流程。
|
||
- 完全离线用户可以选择已经离线安装的自定义 Python。
|
||
|
||
未来若加入离线导入,必须在 PRD、User Stories、逻辑状态和技术包校验中同时定义,不能只
|
||
增加一个文件按钮。
|
||
|
||
### 11.3 工具下载源决策
|
||
|
||
| 保存选择 | 目录事实 | 行为 |
|
||
| --- | --- | --- |
|
||
| 原生地址 | 原生 Target 完整 | 请求固定上游 URL |
|
||
| 原生地址 | 原生 Target 缺失 | 工件不可下载,不请求 OSS |
|
||
| OSS 镜像 | OSS Target 完整 | 只请求固定 OSS URL |
|
||
| OSS 镜像 | OSS Target 缺失 | 工件不可下载,不请求原生地址 |
|
||
| 任意 | 下载后字节数/SHA-256 不符 | 删除暂存并失败 |
|
||
| 任意 | 设置在任务期间变化 | 当前任务不变,后续任务使用新选择 |
|
||
|
||
该选择只覆盖受管工具目录中的外部工件,不影响模型、应用更新、Agent、pip/npm 注册表或
|
||
用户 URL。
|
||
|
||
## 12. 冲突与一致性检查
|
||
|
||
| 可能冲突 | 唯一规则 |
|
||
| --- | --- |
|
||
| 全局工具选择 vs MCP 绝对 command | 绝对 command 优先 |
|
||
| 新设置 vs 运行中进程 | 运行中进程保留启动快照 |
|
||
| 新托管版本 vs 健康旧版本 | 候选完整通过后才发布 |
|
||
| 系统发现新版本 vs 已保存自定义路径 | 不自动切换 |
|
||
| 解释器成功 vs 缺少第三方包 | 工具 ready,能力 missing/unknown |
|
||
| Ask 环境包含解释器 vs Ask 只读 | Ask 权限边界优先 |
|
||
| 本机设置 vs 远程项目 | 远程隔离优先 |
|
||
| 镜像不可达 vs 其他来源可用 | 不在同一次操作中静默切换来源 |
|
||
| 工具下载源 vs 模型/更新下载源 | 各自独立,工具执行环境设置不覆盖其他功能 |
|
||
| OSS 对象 vs 原生工件 | 必须字节、大小和 SHA-256 相同 |
|
||
| 合并设置入口 vs 原有 Skills/MCP 数据 | 只重组导航,不迁移或重写业务数据 |
|
||
| 外层 Tab vs MCP 内层 Tab | 保持两个独立 tablist、标识和键盘焦点范围 |
|
||
|
||
## 13. 逻辑评估结论
|
||
|
||
### 13.1 完整性评估
|
||
|
||
| 评估维度 | 结论 | 依据 |
|
||
| --- | --- | --- |
|
||
| 首次使用 | 通过 | 托管 Node 立即诊断,托管 Python 明确未安装 |
|
||
| 正常执行 | 通过 | SKILL 与 stdio MCP 共用同一命令解析 |
|
||
| 自定义环境 | 通过 | 明确路径、真实诊断、禁止静默回退 |
|
||
| 部分失败 | 通过 | 解释器、工具组合、能力依赖三层状态独立 |
|
||
| 安装生命周期 | 通过 | 安装、取消、失败、重试有唯一结果 |
|
||
| 更新生命周期 | 通过 | 候选通过后发布,失败保留健康旧版 |
|
||
| 删除生命周期 | 通过 | 使用中进程先停止,失败不删除 |
|
||
| 设置生效 | 通过 | 进程启动快照不可变,新进程使用新设置 |
|
||
| 升级兼容 | 通过 | 旧系统解释器一次性固化为自定义路径 |
|
||
| Ask/Execute | 通过 | 工具环境不扩大工具集合或权限 |
|
||
| 远程边界 | 通过 | 本机配置不进入 Host |
|
||
| 受限网络 | 通过 | 来源失败可见,不在同次操作静默切换 |
|
||
| 完全离线 | 有明确限制 | Node 可用;Python 首期要求已有自定义环境 |
|
||
| 受管工具工件来源 | 通过 | 原生/OSS 独立选择、任务冻结、禁止回退 |
|
||
| 设置导航 | 通过 | 单一一级入口、分阶段水平 Tab、业务数据不变 |
|
||
|
||
### 13.2 一致性评估
|
||
|
||
- “两种来源”与自定义候选探测不冲突:候选只是自定义来源的选值,不是第三种来源。
|
||
- “禁止回退”与升级迁移不冲突:迁移只发生一次,完成后按普通保存选择处理。
|
||
- “进程环境不可变”与 Runtime 重建不冲突:只有空闲或后续启动的进程使用新环境。
|
||
- “工具环境就绪”与“能力依赖就绪”不冲突:两者属于独立状态维度。
|
||
- “绝对 MCP command 优先”与 PATH 注入不冲突:command 不替换,但其子进程继承启动快照。
|
||
- “更新保留旧版”不要求长期多版本管理:旧版只在候选发布前保持当前,候选失败即删除。
|
||
|
||
### 13.3 当前未决项
|
||
|
||
没有未决的产品逻辑分支。以下属于实施前必须解决的技术选型,不改变业务规则:
|
||
|
||
1. 六平台 CPython 独立发行包、固定版本、许可证和摘要。
|
||
2. SKILL/MCP 运行要求声明的最小 schema 和无副作用诊断入口。
|
||
3. 各 Runtime 当前缓存进程的精确空闲判定与既有重建入口接线。
|
||
|
||
若技术选型无法满足当前逻辑,例如目标 Python 工件不包含可用 pip/venv,必须回到 PRD 和
|
||
逻辑设计调整承诺,不能在实现中静默降级。
|
||
|
||
## 14. 需求追踪
|
||
|
||
| 逻辑范围 | PRD | User Stories |
|
||
| --- | --- | --- |
|
||
| 首次使用默认状态 | FR-1、FR-2、FR-6 | US-A0 |
|
||
| 明确来源与禁止回退 | FR-3、FR-8 | US-C1、US-C2、US-D3 |
|
||
| 托管 Node | FR-1 | US-A1、US-B2 |
|
||
| 托管 Python 生命周期 | FR-2、FR-9 | US-A2、US-D2、US-D4、US-D5 |
|
||
| 三层状态 | FR-6 | US-A3、US-A4 |
|
||
| SKILL 命令解析 | FR-4 | US-A1、US-A3 |
|
||
| MCP 命令解析 | FR-5 | US-B1、US-B2、US-B3 |
|
||
| 生效时机 | FR-7 | US-C3、US-C4 |
|
||
| Ask/Execute | FR-10 | US-D6 |
|
||
| 远程隔离 | FR-7、FR-9 | US-D1 |
|
||
| 受限网络与离线 | FR-2、FR-11 | US-E1、US-E2、US-E3 |
|
||
| 统一设置入口 | FR-12 | US-F1、US-F2 |
|