dsh-uvm 架构
August 19, 2026 · View on GitHub
DSH Web / uv 环境 tab(Client,settings.plugins.tab 插槽)
│ same-origin fetch /dsh-uvm/api/*
▼
dsh-uvm Host 插件(inject: webServer)
├─ loopback + 同源守卫
├─ 输入白名单(环境名 / 包规格 / 运行参数 / 体积上限)
├─ spawn 参数数组,无 shell
└─ uvm-adapter 工具链抽象
├─ UvAdapter uv venv · uv pip install|uninstall|freeze · uv python list
├─ PipAdapter 回退:python -m venv · python -m pip
├─ CondaAdapter 只读:conda env list --json
└─ 项目同步(M2) 静态解析 pyproject.toml + uv.lock → 差异预览 → 应用
│
▼
~/.dsh-uvm/envs(托管环境根目录,默认)
决策
- uv 是唯一的主管理器。创建、安装、同步、运行全部优先走 uv;pip 只在 uv 缺失时作为回退适配器出现,conda 永远只读。
- 环境根目录封闭。插件只创建/删除
<root>/<name>内的环境;pyvenv.cfg的home等原始字段只存在于 Host 进程内,不下发客户端。 - Client 永远只访问 DSH 同源 API;uv/pip/conda 的路径只在 Host 侧探测。API 契约 JSON-only,不传输任何运行对象。
- 无 shell 派生。全部子进程用参数数组
spawn,包规格允许字符集为[A-Za-z0-9._~^!+*=<>,-],明确拒绝空白、;、&、反引号、$()、重定向等。 - 借鉴 AgentWorkOS 哲学:环境是被管理的工作区资产——先体检(doctor/bootstrap)、显式动作、可见结果;M2 起引入 dry-run 优先的差异预览。
- 插件不托管 uv 自身。不下载 uv、不改用户 PATH、不碰 conda 与全局 pip;缺失工具链时优雅降级为提示而非报错崩溃。
- 项目同步 dry-run 优先。预览只做静态解析(
pyproject.toml的[project].dependencies字符串数组 +uv.lock的[[package]]name/version),与环境freeze按 PEP 503 归一化名比对;不访问网络、不做完整依赖求解。版本漂移只在==钉死规格或 uv.lock 锁定时精确判定,>=等有界规格只展示已装版本、由 uv 在应用时求解。应用把缺失/漂移规格经 stdin 交给uv pip install -r -;全部满足时跳过 spawn。 - 项目文件只进不出。pyproject.toml / uv.lock 仅在 Host 内解析,原始内容与项目内其它文件一律不下发客户端;单文件读取上限 256 KB,依赖声明上限 200 条。
兼容边界
- DSH 验证基线:
0.1.0-rc.6(@deepseek-ai/dsh、dsh-host-webserver、dsh-client-runtime、dsh-client-ui-settings、dsh-client-ui-slots)。 - uv 实测基线:
0.11.19(uv venv、uv pip、uv python list --only-installed文本输出解析;--json暂不可用,路线图跟随上游)。 - pip 回退基线:
python3 -m venv+python3 -m pip(ensurepip)。 - conda:
conda env list --json,兼容字符串数组与对象数组两种输出形态。 - 平台:macOS 已实测;Windows venv 布局(
Scripts/python.exe)已按process.platform分支实现,待实测回归。
失败边界
- 工具链缺失:bootstrap 返回可用性为 false 的清单,UI 提示,不抛异常。
- 单条路由失败:仅该请求返回 4xx/5xx JSON;插件注册本身不因一次 spawn 失败而卸载。
- 超时:创建 180s、安装/同步 120s、运行 60s、查询 30s;SIGTERM 后 3s 宽限 SIGKILL。
- 项目路径:必须为绝对路径、无控制字符、≤512 字符;解析失败(缺 pyproject、非字符串数组、依赖为空/超 200 条)返回明确的 4xx,不影响其它路由。
- 输出:stdout/stderr 各自截断在 512 KB,防止内存膨胀。
- 卸载:
ctx.effect持有webServer.register的 disposer,插件停用时移除路由,不残留全局状态。