🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

插件测试环境 cocofhu/dsh-testsuite

DeepSeek Harness
cocofhu7 天前有提交已提供中文文档扫描:含敏感能力

给 DeepSeek Harness 插件准备可复用的在线测试环境。按版本预构建 runtime…

31.5/100
综合分
2
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/28 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

自动兼容检查未通过:仓库根目录及浅层子目录均无 dsh 插件清单,无法用 dsh 插件安装命令安装。以下命令仍可尝试,但可能失败。
无法用 dsh 插件命令安装(仓库内无 dsh 插件清单)
git clone https://github.com/cocofhu/dsh-testsuite.git
按该仓库 README 的原生方式安装;若作者另行发布了 DSH 插件包,请以其文档为准

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:需留意
是什么
dsh 原生插件 · other
装得上吗
实装验证未通过(unknown),装前请到仓库确认最近更新与 issue
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 7 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
⚠︎ 实装验证未通过(unknown · 2026/10/30) ——可能是验证环境差异,装前建议到 GitHub 仓库确认最近更新与 issue。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查需源码安装

仓库根目录及浅层子目录均无 dsh 插件清单,无法用 dsh 插件安装命令安装

展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✗npm 包dsh-testsuite(未发布到 npm,仅可源码安装)
✓Node 引擎未声明 engines.node
✓dsh CLI 依赖未声明 dsh 版本约束
✗入口文件缺少入口声明

源码安装时若 pnpm 拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑。

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/11 22:13:44

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 106 处,展示前 106 条)
  • 读取本地文件
    image/0.0.1-rc.5/patch-frontend.mjs:46
    const src = readFileSync(path, "utf8");
  • 写入本地文件
    image/0.0.1-rc.5/patch-frontend.mjs:54
    writeFileSync(path, src.replaceAll(find, replace));
  • 执行外部命令固定命令
    image/0.0.1-rc.5/patch-frontend.mjs:58
    const root = execSync("npm root -g", { encoding: "utf8" }).trim();
  • 读取本地文件
    image/0.0.1-rc.5/patch-frontend.mjs:79
    const html = readFileSync(index, "utf8");
  • 写入本地文件
    image/0.0.1-rc.5/patch-frontend.mjs:85
    writeFileSync(index, patched);
  • 读取本地文件
    image/0.1.0-rc.2/patch-frontend.mjs:46
    const src = readFileSync(path, "utf8");
  • 写入本地文件
    image/0.1.0-rc.2/patch-frontend.mjs:54
    writeFileSync(path, src.replaceAll(find, replace));
  • 执行外部命令固定命令
    image/0.1.0-rc.2/patch-frontend.mjs:58
    const root = execSync("npm root -g", { encoding: "utf8" }).trim();
  • 读取本地文件
    image/0.1.0-rc.2/patch-frontend.mjs:79
    const html = readFileSync(index, "utf8");
  • 写入本地文件
    image/0.1.0-rc.2/patch-frontend.mjs:85
    writeFileSync(index, patched);
  • 读取本地文件
    image/0.1.0-rc.3/patch-frontend.mjs:46
    const src = readFileSync(path, "utf8");
  • 写入本地文件
    image/0.1.0-rc.3/patch-frontend.mjs:54
    writeFileSync(path, src.replaceAll(find, replace));
  • 执行外部命令固定命令
    image/0.1.0-rc.3/patch-frontend.mjs:58
    const root = execSync("npm root -g", { encoding: "utf8" }).trim();
  • 读取本地文件
    image/0.1.0-rc.3/patch-frontend.mjs:79
    const html = readFileSync(index, "utf8");
  • 写入本地文件
    image/0.1.0-rc.3/patch-frontend.mjs:85
    writeFileSync(index, patched);
  • 读取本地文件
    image/0.1.0-rc.6/patch-frontend.mjs:43
    const src = readFileSync(path, "utf8");
  • 写入本地文件
    image/0.1.0-rc.6/patch-frontend.mjs:51
    writeFileSync(path, src.replaceAll(find, replace));
  • 执行外部命令固定命令
    image/0.1.0-rc.6/patch-frontend.mjs:55
    const root = execSync("npm root -g", { encoding: "utf8" }).trim();
  • 读取本地文件
    image/0.1.0-rc.6/patch-frontend.mjs:59
    const html = readFileSync(index, "utf8");
  • 写入本地文件
    image/0.1.0-rc.6/patch-frontend.mjs:65
    writeFileSync(index, patched);
  • 读取本地文件
    image/0.1.0-rc.7/patch-frontend.mjs:43
    const src = readFileSync(path, "utf8");
  • 写入本地文件
    image/0.1.0-rc.7/patch-frontend.mjs:51
    writeFileSync(path, src.replaceAll(find, replace));
  • 执行外部命令固定命令
    image/0.1.0-rc.7/patch-frontend.mjs:55
    const root = execSync("npm root -g", { encoding: "utf8" }).trim();
  • 读取本地文件
    image/0.1.0-rc.7/patch-frontend.mjs:59
    const html = readFileSync(index, "utf8");
  • 写入本地文件
    image/0.1.0-rc.7/patch-frontend.mjs:65
    writeFileSync(index, patched);
  • 读取本地文件
    image/0.1.0-rc.8/patch-frontend.mjs:43
    const src = readFileSync(path, "utf8");
  • 写入本地文件
    image/0.1.0-rc.8/patch-frontend.mjs:51
    writeFileSync(path, src.replaceAll(find, replace));
  • 执行外部命令固定命令
    image/0.1.0-rc.8/patch-frontend.mjs:55
    const root = execSync("npm root -g", { encoding: "utf8" }).trim();
  • 读取本地文件
    image/0.1.0-rc.8/patch-frontend.mjs:59
    const html = readFileSync(index, "utf8");
  • 写入本地文件
    image/0.1.0-rc.8/patch-frontend.mjs:65
    writeFileSync(index, patched);
  • 读取本地文件
    image/0.1.1-rc.1/patch-frontend.mjs:43
    const src = readFileSync(path, "utf8");
  • 写入本地文件
    image/0.1.1-rc.1/patch-frontend.mjs:51
    writeFileSync(path, src.replaceAll(find, replace));
  • 执行外部命令固定命令
    image/0.1.1-rc.1/patch-frontend.mjs:55
    const root = execSync("npm root -g", { encoding: "utf8" }).trim();
  • 读取本地文件
    image/0.1.1-rc.1/patch-frontend.mjs:59
    const html = readFileSync(index, "utf8");
  • 写入本地文件
    image/0.1.1-rc.1/patch-frontend.mjs:65
    writeFileSync(index, patched);
  • 读取本地文件
    image/0.1.1-rc.2/patch-frontend.mjs:43
    const src = readFileSync(path, "utf8");
  • 写入本地文件
    image/0.1.1-rc.2/patch-frontend.mjs:51
    writeFileSync(path, src.replaceAll(find, replace));
  • 执行外部命令固定命令
    image/0.1.1-rc.2/patch-frontend.mjs:55
    const root = execSync("npm root -g", { encoding: "utf8" }).trim();
  • 读取本地文件
    image/0.1.1-rc.2/patch-frontend.mjs:59
    const html = readFileSync(index, "utf8");
  • 写入本地文件
    image/0.1.1-rc.2/patch-frontend.mjs:65
    writeFileSync(index, patched);
  • 读取本地文件
    image/0.1.2-alpha.2/patch-frontend.mjs:43
    const src = readFileSync(path, "utf8");
  • 写入本地文件
    image/0.1.2-alpha.2/patch-frontend.mjs:51
    writeFileSync(path, src.replaceAll(find, replace));
  • 执行外部命令固定命令
    image/0.1.2-alpha.2/patch-frontend.mjs:55
    const root = execSync("npm root -g", { encoding: "utf8" }).trim();
  • 读取本地文件
    image/0.1.2-alpha.2/patch-frontend.mjs:59
    const html = readFileSync(index, "utf8");
  • 写入本地文件
    image/0.1.2-alpha.2/patch-frontend.mjs:65
    writeFileSync(index, patched);
  • 读取本地文件
    image/0.1.2-alpha.3/patch-frontend.mjs:43
    const src = readFileSync(path, "utf8");
  • 写入本地文件
    image/0.1.2-alpha.3/patch-frontend.mjs:51
    writeFileSync(path, src.replaceAll(find, replace));
  • 执行外部命令固定命令
    image/0.1.2-alpha.3/patch-frontend.mjs:55
    const root = execSync("npm root -g", { encoding: "utf8" }).trim();
  • 读取本地文件
    image/0.1.2-alpha.3/patch-frontend.mjs:59
    const html = readFileSync(index, "utf8");
  • 写入本地文件
    image/0.1.2-alpha.3/patch-frontend.mjs:65
    writeFileSync(index, patched);
  • 读取本地文件
    image/0.1.2-alpha.4/patch-frontend.mjs:43
    const src = readFileSync(path, "utf8");
  • 写入本地文件
    image/0.1.2-alpha.4/patch-frontend.mjs:51
    writeFileSync(path, src.replaceAll(find, replace));
  • 执行外部命令固定命令
    image/0.1.2-alpha.4/patch-frontend.mjs:55
    const root = execSync("npm root -g", { encoding: "utf8" }).trim();
  • 读取本地文件
    image/0.1.2-alpha.4/patch-frontend.mjs:59
    const html = readFileSync(index, "utf8");
  • 写入本地文件
    image/0.1.2-alpha.4/patch-frontend.mjs:65
    writeFileSync(index, patched);
  • 读取本地文件
    image/0.1.2-alpha.5/patch-frontend.mjs:43
    const src = readFileSync(path, "utf8");
  • 写入本地文件
    image/0.1.2-alpha.5/patch-frontend.mjs:51
    writeFileSync(path, src.replaceAll(find, replace));
  • 执行外部命令固定命令
    image/0.1.2-alpha.5/patch-frontend.mjs:55
    const root = execSync("npm root -g", { encoding: "utf8" }).trim();
  • 读取本地文件
    image/0.1.2-alpha.5/patch-frontend.mjs:59
    const html = readFileSync(index, "utf8");
  • 写入本地文件
    image/0.1.2-alpha.5/patch-frontend.mjs:65
    writeFileSync(index, patched);
  • 读取本地文件
    image/0.1.2-rc.1/patch-frontend.mjs:43
    const src = readFileSync(path, "utf8");
  • 写入本地文件
    image/0.1.2-rc.1/patch-frontend.mjs:51
    writeFileSync(path, src.replaceAll(find, replace));
  • 执行外部命令固定命令
    image/0.1.2-rc.1/patch-frontend.mjs:55
    const root = execSync("npm root -g", { encoding: "utf8" }).trim();
  • 读取本地文件
    image/0.1.2-rc.1/patch-frontend.mjs:59
    const html = readFileSync(index, "utf8");
  • 写入本地文件
    image/0.1.2-rc.1/patch-frontend.mjs:65
    writeFileSync(index, patched);
  • 读取本地文件
    image/0.1.3-alpha.2/patch-frontend.mjs:43
    const src = readFileSync(path, "utf8");
  • 写入本地文件
    image/0.1.3-alpha.2/patch-frontend.mjs:51
    writeFileSync(path, src.replaceAll(find, replace));
  • 执行外部命令固定命令
    image/0.1.3-alpha.2/patch-frontend.mjs:55
    const root = execSync("npm root -g", { encoding: "utf8" }).trim();
  • 读取本地文件
    image/0.1.3-alpha.2/patch-frontend.mjs:59
    const html = readFileSync(index, "utf8");
  • 写入本地文件
    image/0.1.3-alpha.2/patch-frontend.mjs:65
    writeFileSync(index, patched);
  • 读取本地文件
    image/0.1.5-alpha.1/patch-frontend.mjs:43
    const src = readFileSync(path, "utf8");
  • 写入本地文件
    image/0.1.5-alpha.1/patch-frontend.mjs:51
    writeFileSync(path, src.replaceAll(find, replace));
  • 执行外部命令固定命令
    image/0.1.5-alpha.1/patch-frontend.mjs:55
    const root = execSync("npm root -g", { encoding: "utf8" }).trim();
  • 读取本地文件
    image/0.1.5-alpha.1/patch-frontend.mjs:59
    const html = readFileSync(index, "utf8");
  • 写入本地文件
    image/0.1.5-alpha.1/patch-frontend.mjs:65
    writeFileSync(index, patched);
  • 读取本地文件
    image/0.1.5-alpha.2/patch-frontend.mjs:43
    const src = readFileSync(path, "utf8");
  • 写入本地文件
    image/0.1.5-alpha.2/patch-frontend.mjs:51
    writeFileSync(path, src.replaceAll(find, replace));
  • 执行外部命令固定命令
    image/0.1.5-alpha.2/patch-frontend.mjs:55
    const root = execSync("npm root -g", { encoding: "utf8" }).trim();
  • 读取本地文件
    image/0.1.5-alpha.2/patch-frontend.mjs:59
    const html = readFileSync(index, "utf8");
  • 写入本地文件
    image/0.1.5-alpha.2/patch-frontend.mjs:65
    writeFileSync(index, patched);
  • 读取本地文件
    image/0.1.5-rc.1/patch-frontend.mjs:43
    const src = readFileSync(path, "utf8");
  • 写入本地文件
    image/0.1.5-rc.1/patch-frontend.mjs:51
    writeFileSync(path, src.replaceAll(find, replace));
  • 执行外部命令固定命令
    image/0.1.5-rc.1/patch-frontend.mjs:55
    const root = execSync("npm root -g", { encoding: "utf8" }).trim();
  • 读取本地文件
    image/0.1.5-rc.1/patch-frontend.mjs:59
    const html = readFileSync(index, "utf8");
  • 写入本地文件
    image/0.1.5-rc.1/patch-frontend.mjs:65
    writeFileSync(index, patched);
  • 读取本地文件
    image/0.1.5-rc.2/patch-frontend.mjs:43
    const src = readFileSync(path, "utf8");
  • 写入本地文件
    image/0.1.5-rc.2/patch-frontend.mjs:51
    writeFileSync(path, src.replaceAll(find, replace));
  • 执行外部命令固定命令
    image/0.1.5-rc.2/patch-frontend.mjs:55
    const root = execSync("npm root -g", { encoding: "utf8" }).trim();
  • 读取本地文件
    image/0.1.5-rc.2/patch-frontend.mjs:59
    const html = readFileSync(index, "utf8");
  • 写入本地文件
    image/0.1.5-rc.2/patch-frontend.mjs:65
    writeFileSync(index, patched);
  • 读取本地文件
    image/0.1.6-alpha.1/patch-frontend.mjs:43
    const src = readFileSync(path, "utf8");
  • 写入本地文件
    image/0.1.6-alpha.1/patch-frontend.mjs:51
    writeFileSync(path, src.replaceAll(find, replace));
  • 执行外部命令固定命令
    image/0.1.6-alpha.1/patch-frontend.mjs:55
    const root = execSync("npm root -g", { encoding: "utf8" }).trim();
  • 读取本地文件
    image/0.1.6-alpha.1/patch-frontend.mjs:59
    const html = readFileSync(index, "utf8");
  • 写入本地文件
    image/0.1.6-alpha.1/patch-frontend.mjs:65
    writeFileSync(index, patched);
  • 读取本地文件
    image/0.2.1-rc.4/patch-frontend.mjs:19
    for (const name of readdirSync(dir)) {
  • 读取本地文件
    image/0.2.1-rc.4/patch-frontend.mjs:22
    const st = statSync(path);
  • 读取本地文件
    image/0.2.1-rc.4/patch-frontend.mjs:55
    const src = readFileSync(path, "utf8");
  • 写入本地文件
    image/0.2.1-rc.4/patch-frontend.mjs:63
    writeFileSync(path, src.replaceAll(find, replace));
  • 执行外部命令固定命令
    image/0.2.1-rc.4/patch-frontend.mjs:67
    const root = execSync("npm root -g", { encoding: "utf8" }).trim();
  • 读取本地文件
    image/0.2.1-rc.4/patch-frontend.mjs:71
    const html = readFileSync(index, "utf8");
  • 写入本地文件
    image/0.2.1-rc.4/patch-frontend.mjs:77
    writeFileSync(index, patched);
  • 读取本地文件
    image/0.2.1-rc.4/patch-frontend.mjs:82
    const hostSrc = readFileSync(hostConn, "utf8");
  • 读取本地文件
    image/0.2.1-rc.4/patch-frontend.mjs:108
    const src = readFileSync(file, "utf8");
  • 写入本地文件
    image/0.2.1-rc.4/patch-frontend.mjs:110
    writeFileSync(file, src.replaceAll(loopbackFind, "true"));
  • 发起网络请求
    web/app.js:71
    const res = await fetch(path, {
扫描于 2026/11/2(1 天前) · 范围 全包扫描 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 一键创建在线测试容器
  • 内置密钥与插件
  • 插件测试套件

dsh 插件的测试套件

README

由 DeepSeek 最新模型翻译生成
dsh-testsuite 给 DeepSeek Harness 插件准备可复用的在线测试环境。按版本预构建 runtime 镜像,在管理台登记后创建 Docker 容器,注入 API Key、提供方、模型和预装插件,然后打开 dsh web。 本项目是非官方工具,不是 DeepSeek Harness 的官方产品。 怎么工作 - 控制面(本仓库 Go 服务 + Web UI):登记镜像,创建 / 启停 / 重启 / 销毁环境,看日志。 - Runtime 镜像(每版本一份 image/<ver>/Dockerfile):在平台外用 make image 构建,或从 GitHub Release 推到 GHCR。已发布 tag 默认冻结。说明见 docs/images.md。 每个环境把容器内 3080 映射到宿主机随机端口。容器进入 running 后还会探测端口,Health 变为 Healthy 才允许「打开」(dsh Web 是根路径 SPA,启动还要几秒)。 镜像预先构建好,创建环境只选已登记镜像并注入配置。默认用 Docker;也可把 runtime 设为 kubernetes,由控制面在集群里创建 Deployment / Service / Ingress(见下方)。控制面不执行 docker build。
展开完整文档(README 剩余 1 节)
dsh-testsuite

给 DeepSeek Harness 插件准备可复用的在线测试环境。按版本预构建 runtime 镜像,在管理台登记后创建 Docker 容器,注入 API Key、提供方、模型和预装插件,然后打开 dsh web。

本项目是非官方工具,不是 DeepSeek Harness 的官方产品。

怎么工作

- 控制面(本仓库 Go 服务 + Web UI):登记镜像,创建 / 启停 / 重启 / 销毁环境,看日志。
- Runtime 镜像(每版本一份 image/<ver>/Dockerfile):在平台外用 make image 构建,或从 GitHub Release 推到 GHCR。已发布 tag 默认冻结。说明见 docs/images.md。

每个环境把容器内 3080 映射到宿主机随机端口。容器进入 running 后还会探测端口,Health 变为 Healthy 才允许「打开」(dsh Web 是根路径 SPA,启动还要几秒)。

镜像预先构建好,创建环境只选已登记镜像并注入配置。默认用 Docker;也可把 runtime 设为 kubernetes,由控制面在集群里创建 Deployment / Service / Ingress(见下方)。控制面不执行 docker build。 Docker 模式下,登记镜像时若本机没有该 tag,会从 GHCR 自动 pull。

快速开始

需要 Docker 和 Go 1.25+。

make image DSH_VERSION=0.2.1-rc.4 # 在平台外打 runtime;细节见 docs/images.md
cp config.example.yaml config.yaml # 本地配置,不要提交
make run # 控制面 http://127.0.0.1:8090

1. 打开「镜像版本」,选版本登记(本机没有会从 GHCR 自动 pull)。也可只 pull、稍后登记:docker pull ghcr.io/cocofhu/dsh-testsuite-runtime:0.2.1-rc.4。
2. 在「模型预设」填提供方、模型和 API 密钥(密钥只存在本机 data/)。
3. 回到「环境」,创建并等待 Health 变为 Healthy,再点「打开」。

首次推到 GHCR 的包默认是 private,需要在 GitHub Packages 设为 public。

用 Compose 把控制面也跑进容器(需挂 docker.sock;runtime 仍在宿主机构建或 pull):

make image DSH_VERSION=0.2.1-rc.4
docker compose up --build

创建环境

| 字段 | 说明 |
| dsh 版本 | 只能选「镜像版本」里已登记的条目 |
| 模型预设 | 在「模型预设」页在线填写(含 API 密钥)。创建环境时选预设即带上密钥,或选「手动填写」 |
| API 密钥 | 预设里保存;手动创建时现填。注入为 DSH_API_KEY;官方 provider 同时设 DEEPSEEK_API_KEY。列表只显示末 4 位 |
| Provider / Model | 写在预设里,或「手动填写」时现选。仅「自定义…」才填 Provider ID / API 地址 |
| 预装插件 | 每行一个 pnpm 源,如 github:owner/plugin#main。git 源的 prepare 会由 runtime 写入 profile 的 allowBuilds |

空闲 TTL 默认 2 小时(limits.idleTTL),超时会销毁容器和该环境的 $DSH_HOME。列表显示剩余 TTL,点「续期 6h」会从当前到期时间再延 6 小时。

控制面默认无认证,不要把 :8090 裸暴露到公网。config.yaml 和 data/ 含密钥,已 gitignore。

Kubernetes

把 runtime 设为 kubernetes 后,每个环境是同命名空间里的 Deployment + Service + Ingress,不再映射宿主机随机端口。

- kubernetes.envHostTemplate 必填,且必须包含 {id}。控制面用它生成「打开」链接和 DSH_TRUSTED_HOST。dsh Web 是根路径 SPA,需要每个环境一个主机名(自备通配 DNS),例如 env-{id}.example.com。
- namespace 留空则使用 in-cluster 当前命名空间;集群外可填 kubeconfig。
- ingressClass 可选(例如 nginx)。未填则用集群默认 IngressClass。
- 登记镜像只写目录;kubelet 拉镜像。填写 kubernetes.storageClass 后,/data/dsh 与 /workspace 挂在同一块 PVC 的 home / workspace 子目录;销毁环境时删除 PVC(不保留、不按名复用卷)。未填 storageClass 时退回 emptyDir。
- docker.cpuCores / docker.memoryMB 是容器的 limit。可选的 kubernetes.cpuRequest / kubernetes.memoryRequest 只影响调度预留;不填则 request 等于 limit。
- 控制面 ServiceAccount 需要对本命名空间的 deployments、pods、pods/log、services、secrets、ingresses 有读写权限。

runtime: kubernetes
server:
publicHost: testsuite.example.com
docker:
imageRepository: ghcr.io/cocofhu/dsh-testsuite-runtime
kubernetes:
envHostTemplate: "env-{id}.example.com"
ingressClass: nginx

远程访问

从浏览器访问这台机器的 IP 时:

1. docker.bindIP 设为 0.0.0.0,否则端口只绑在 127.0.0.1,外网会 ERR_EMPTY_RESPONSE / 连不上。
2. server.publicHost 填这台机器的 IP 或域名(也可用 DSHTS_PUBLIC_HOST)。「打开」链接和容器内 dsh --trusted-host 都用它。
3. 上游 dsh 拒绝 --host 0.0.0.0。runtime 让 dsh 听 127.0.0.1:3081,entrypoint 再用 TCP 代理转到容器 3080 给 docker -p。
4. 用 IP + HTTP 打开时浏览器不是 secure context,没有 crypto.randomUUID。镜像会注入 polyfill(与社区 dsh-web-lan-access 同一思路),并把设置 / 凭证 API 放到 --trusted-host 上,否则模型页会报 settings are unavailable in this browser。
5. 0.2.1 起,dsh 会校验启动 URL。进程打印 dsh web: …?token=…,浏览器必须打开这条带 token 的地址才会换到会话 cookie。控制面从容器日志取出 token,把「打开」指到 http://<publicHost>:<端口>/?token=…。镜像还会把该地址作为 --public-url 传给 dsh。

cp config.example.yaml config.yaml
编辑 server.publicHost
export PATH="/usr/local/go/bin:$PATH"
go run ./cmd/dsh-testsuite -config config.yaml

API

GET /healthz
GET /api/providers
GET /api/presets
POST /api/presets { name, provider, model, apiKey, baseURL?, api? }
PUT /api/presets/:id apiKey 留空则不改密钥 DELETE /api/presets/:id GET /api/images GET /api/images/remote 内置可选版本列表 POST /api/images { "version": "0.2.1-rc.4", "ref"?: "...", "pull"?: true } 默认 pull=true;短名会 pull GHCR 再 tag 成本地 imageRepository:version DELETE /api/images/:version POST /api/environments { name, dshVersion, presetId? } 或手动 { apiKey, provider, model, ... } GET /api/environments GET /api/environments/:id POST /api/environments/:id/start POST /api/environments/:id/stop POST /api/environments/:id/restart POST /api/environments/:id/renew 到期时间再延 6 小时 DELETE /api/environments/:id GET /api/environments/:id/logs

MCP

控制面同进程同端口内嵌 MCP(Model Context Protocol)Streamable HTTP 端点,把上表全部能力以 MCP 工具暴露给 AI 智能体:

http://<host>:8090/mcp

支持 initialize / ping / tools/list / tools/call(Streamable HTTP,协议版本 2025-03-26 及以上)。工具面与 REST 完全等价、共用同一份存储——智能体经 MCP 创建的环境立即可在 Web UI / REST 中看到,反之亦然。

| 分类 | 工具 |
| 环境(9) | list_environments create_environment get_environment start_environment stop_environment restart_environment renew_environment(+6h)destroy_environment get_environment_logs(tail 可选) |
| 镜像(4) | list_images list_remote_images register_image(pull 默认 true)delete_image |
| 预设(4) | list_presets create_preset update_preset(apiKey 留空不改)delete_preset |
| 服务商(1) | list_providers |

参数带 JSON Schema 描述,智能体零文档即可发现;not found / conflict / 参数校验等错误都映射为 MCP tool error(isError: true),并保留原始错误文本。密钥规则与 REST 一致:任何工具响应只回显末 4 位。

MCP 客户端配置示例(Claude Code 等 Streamable HTTP 客户端通用):

{
"mcpServers": {
"dsh-testsuite": {
"type": "http",
"url": "http://<host>:8090/mcp"
}
}
}

⚠️ 安全提示:与 REST 控制面一致,/mcp 没有任何认证。绝不要把该端口暴露到公网;请通过内网 / 端口转发访问,否则任何能连到该端口的人都可以读取容器日志、用已存的密钥创建环境,甚至销毁全部环境。

配置

见 config.example.yaml。docker.imageRepository 只在登记时没填 ref 时用来拼默认 仓库:版本。

参与贡献

见 CONTRIBUTING.md。安全问题请按 SECURITY.md 私下报告。

许可证

MIT
预览分享卡片长什么样 ↓cocofhu/dsh-testsuite
分享 · DeepSeek Harness Hub
cocofhu/dsh-testsuite

给 DeepSeek Harness 插件准备可复用的在线测试环境。按版本预构建 runtime…

回到顶部