首页
/ DeepSeek Harness 修复解析:`dsh web` 的插件客户端 Bundle 从何而来,以及 Web 演示为何必须先完整构建

DeepSeek Harness 修复解析:`dsh web` 的插件客户端 Bundle 从何而来,以及 Web 演示为何必须先完整构建

2026-09-04 20:40:47作者:董斯意

本篇技术指南围绕 DeepSeek Harness(dsh)仓库中一篇已归档的 bug 修复记录展开:Web UI 的插件客户端 bundle(lib/client.js)只在根级完整构建时产出,仅执行 Vite 前端构建会导致 dsh web 下所有插件端点 404 并显示 "Failed to load plugins"。读完本文,你将掌握 dsh Web 的两层构建模型(前端 shell 与插件 bundle)、各构建脚本的产物边界,以及"演示入口必须先完整构建"这一修复背后的权衡与验证方法。

背景:dsh Web 的两层构建模型

dsh web 启动的 Web UI 实际上由两类独立产物组成,二者由不同的构建链路生成,这是理解本次 bug 修复的前提:

  1. 前端 shell(编译壳)apps/web(包名 @deepseek-ai/dsh-web-frontend)用 Vite 构建出 dist/,由 dsh web 直接静态服务。其 package.json 的自述为 "Web application entry: vite build over the @deepseek-ai/dsh-client-web shell library; dist/ served by apps/cli's dsh web"。
  2. 插件客户端 bundledsh web 通过 GET /plugins/<id>/client.js 逐个服务每个 web-client 插件的 bundle,路径从各插件包的 exports["./client"](即 lib/client.js)解析。这些 bundle 只能由根级 pnpm run build 产出——先 tsc -b 类型编译,再由各包的 tsdown.client.ts 配置打包。

package.json 中的构建脚本清晰体现了这两层分工:

脚本 命令 产物
build tsx scripts/build.ts 完整仓库构建(lib 产物 + 前端 shell)
build:lib:host node --max-old-space-size=4096 tsc -b tsconfig.host.json && tsdown --env.DSH_BUILD_FACE host Host 面(Node 侧)lib 产物
build:lib:client tsc -b tsconfig.client.json && tsdown --env.DSH_BUILD_FACE client Client 面(浏览器侧)lib 产物,含各插件 lib/client.js
build:web pnpm --filter @deepseek-ai/dsh-web-frontend run build Vite 前端 shell 的 apps/web/dist

完整构建的编排逻辑见 scripts/build.ts:它先 runScript('build:lib')runScript('build:web'),随后通过 writeClientBuildRecord 记录客户端产物文件数与公开环境值("build: recorded N client artifact(s) with M public value(s)")。

Client 面打包的关键配置在根 tsdown.config.ts:配置通过环境变量 DSH_BUILD_FACE 区分两个面——host 面以 lib/types/{index,invariant,startup}.js 为入口(tsc -b 的产物)并挂载 Typert 插件;client 面则改为空入口 entry: '',"selects packages that declare a browser bundle and lets their package-local configs emit both their Node loader entry and browser artifact"(选择声明了浏览器 bundle 的包,让包级配置同时产出 Node loader 入口与浏览器产物)。UI 插件 bundle 的共享打包预设见 packages/client/tsdown.client.ts:它产出闭包工厂(closure-factory)产物——bundle 执行时调用 window.__ModuleLoader__.load({id, factory}),外部依赖通过注入的 require(loader 模块表)解析,CSS 由 lightningcss 在 bundle 内编译并注入。这也解释了为什么浏览器侧加载器能独立工作:每个插件 bundle 是自包含的工厂函数,不依赖 import map 或全局变量。

问题:干净检出下的 "Failed to load plugins"

原始缺陷记录(.agents/notes/archived/bug-fix/2026-07-23-demo-web-builds-client-bundles.md,状态 implemented,归档于 2026-07-26)描述的问题链条如下:

  • 插件 bundle(lib/client.js由根 pnpm run build 产出;Vite 的 build:web 只构建前端 shell。
  • 当时的 demo:web 演示入口以及 README 的 Web UI 说明只运行了 build:web
  • 因此在一个没有先执行过完整构建的检出目录中,dsh web 服务的每个 GET /plugins/<id>/client.js 全部 404,客户端 loader 将每个插件标记为 failed,启动屏显示 "Failed to load plugins"。
  • 更具迷惑性的是:前端 shell 本身构建完全正常,缺失的产物被掩盖成一个运行时浏览器错误,而不是构建失败——问题不会在构建阶段暴露,只会出现在用户打开浏览器的那一刻。

修复:演示入口先执行完整构建

修复决策同样记录在该 note 中,核心是保证服务前产物存在

  1. demo:web 在运行 npm run build:web 之前先运行 npm run build,使各插件的 lib/client.js bundle 在 dsh web 服务它们之前已经存在。
  2. README 的 Web UI 章节对安装器落盘到 ~/.dsh/source 的检出(安装过程从不构建它)给出 pnpm run build && pnpm run build:web 的说明。

当前仓库的 README.md "Run from source" 一节即体现了修复后的最终形态:

git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
pnpm install
pnpm run build
pnpm dsh web

README 明确指出 "pnpm run build prepares the repository artifacts. pnpm dsh web uses those built artifacts without rebuilding."(dsh web 直接使用已构建产物,不再重新构建)——这正对应了 note 中"服务方不拥有构建步骤"的原则。

验证方式(见 note 的 Verification 一节):完整构建后,全部 8 个 /plugins/<id>/client.js 端点均返回 200;用无头 Chromium 加载 http://127.0.0.1:3080,shell 正常渲染且不再出现 "Failed to load plugins" 状态。

备选方案及其被否决的原因

该 note 记录了两个被否决的替代方案,其否决理由体现了项目的架构约束,值得展开:

方案一:在 dsh web 启动时构建 bundle。 被否。应用本身通过 tsx 从源码运行,不拥有任何构建步骤;把产物构建塞进服务器启动流程会跨越源码/产物(source/artifact)的分界线,并且拖慢每一次启动。这也是 README 中 "dsh web uses those built artifacts without rebuilding" 一句的由来——服务方只消费产物,不生产产物。

方案二:扩展 tsdown 根配置,让 build:web 顺带产出客户端 bundle。 被否。build:web 的语义是 Vite 前端构建;客户端 bundle 是对 lib/typestsc -b tsconfig.client.json 的产物)的一次独立的 tsdown 打包,见 tsdown.config.ts 中 client 面的入口定义与 scripts/build.tsbuild:lib 先于 build:web 的执行顺序。合并两者会把 shell 构建与包构建混为一谈,而且根级 build 仍然是唯一的产物生产者,扩展 build:web 并不能消除这条依赖链,只会让脚本语义更模糊。

代价与后续影响:全量构建的成本由演示入口承担

note 的 Consequences 一节给出了明确的取舍:demo:web 现在每次调用都要支付完整 tsc -b && tsdown 的成本,而不是只跑 Vite 构建。这是"从干净目录即可运行 Web 演示"必须付出的代价;已经构建过的调用方可以直接调用 dsh web,无需重复付费。从当前根 package.json 的脚本清单看,演示相关入口为 demo:ptcdemo:inspectorweb --patch ... 形式)与 dev:web;该 note 已归档,README 面向读者的标准流程则是 pnpm run buildpnpm dsh web

对日常开发场景,仓库提供了增量替代路径 scripts/dev-web.ts。其文件头注释完整描述了三层增量重建循环:"rebuilds every artifact the browser reads from a source edit"——tsc -b tsconfig.client.json 产出 lib/types,tsdown 打包 lib/index.jslib/client.jsvite build 重写 apps/web/dist,而 dsh web 服务的宿主 web server 会 stat 轮询这些 bundle 并自行广播 rebuilt 帧。注释同时给出两条重要约束:该脚本必须先执行过一次 pnpm run build(每层都是对上一层输出的增量编译,没有任何一层能自建缺失的目录树),且不能与 pnpm run build 并发运行(两者写同一棵 lib/apps/web/dist/ 树);另外在 inotify 事件不可达的网络挂载上需使用 --poll 轮询模式。

小结与核对清单

这篇归档修复的核心经验可以归纳为三点:

  1. 识别产物边界:dsh Web 的"shell 正常、插件全 404"症状,本质是 Vite shell 构建与 tsdown 插件 bundle 构建两条链路被单一入口(只跑 build:web)错误地裁剪。核对插件端点应直接请求 GET /plugins/<id>/client.js 并确认返回 200。
  2. 服务方不构建dsh web 从源码(tsx)运行但不拥有构建步骤,修复方向是让构建发生在演示入口/文档指引中,而不是塞进服务启动流程,保持源码与产物的分界。
  3. 验证要落到运行态:修复验证不止于端点 200,还包括无头浏览器加载 http://127.0.0.1:3080 后 shell 渲染且无 "Failed to load plugins" 状态。

关键文件索引:

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
980
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384