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(托管环境根目录,默认)

决策

  1. uv 是唯一的主管理器。创建、安装、同步、运行全部优先走 uv;pip 只在 uv 缺失时作为回退适配器出现,conda 永远只读。
  2. 环境根目录封闭。插件只创建/删除 <root>/<name> 内的环境;pyvenv.cfghome 等原始字段只存在于 Host 进程内,不下发客户端。
  3. Client 永远只访问 DSH 同源 API;uv/pip/conda 的路径只在 Host 侧探测。API 契约 JSON-only,不传输任何运行对象。
  4. 无 shell 派生。全部子进程用参数数组 spawn,包规格允许字符集为 [A-Za-z0-9._~^!+*=<>,-],明确拒绝空白、;&、反引号、$()、重定向等。
  5. 借鉴 AgentWorkOS 哲学:环境是被管理的工作区资产——先体检(doctor/bootstrap)、显式动作、可见结果;M2 起引入 dry-run 优先的差异预览。
  6. 插件不托管 uv 自身。不下载 uv、不改用户 PATH、不碰 conda 与全局 pip;缺失工具链时优雅降级为提示而非报错崩溃。
  7. 项目同步 dry-run 优先。预览只做静态解析(pyproject.toml[project].dependencies 字符串数组 + uv.lock[[package]] name/version),与环境 freeze 按 PEP 503 归一化名比对;不访问网络、不做完整依赖求解。版本漂移只在 == 钉死规格或 uv.lock 锁定时精确判定,>= 等有界规格只展示已装版本、由 uv 在应用时求解。应用把缺失/漂移规格经 stdin 交给 uv pip install -r -;全部满足时跳过 spawn。
  8. 项目文件只进不出。pyproject.toml / uv.lock 仅在 Host 内解析,原始内容与项目内其它文件一律不下发客户端;单文件读取上限 256 KB,依赖声明上限 200 条。

兼容边界

  • DSH 验证基线:0.1.0-rc.6@deepseek-ai/dshdsh-host-webserverdsh-client-runtimedsh-client-ui-settingsdsh-client-ui-slots)。
  • uv 实测基线:0.11.19uv venvuv pipuv 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,插件停用时移除路由,不残留全局状态。