# RFC:dsh-external-stent — 仓库目的、架构与决策记录 [English](README.md) | 中文 - 状态:**活文档**(每节记录决策及其历史) - 范围:本独立 Stent 扩展仓库 - 上游锚点:deepseek-harness 快照 `7b9644f2`(0812)/ `9f9e2782a4`(0813)、fork tip `65bcaf9902`(`feat-stent`) 本文解释这个仓库**为什么**长成现在的样子。下面每一处非常规安排都来自提交历史中一次具体的事故;各节按仓库演化顺序组织,而不是按文件布局。 --- ## 1. 目的:外部化的 Stent 扩展,而非 fork deepseek-harness 是私有 monorepo。Stent/Mixin 扩展层在其中以三个实现包 存在,但消费者无法从 registry 安装它们。本仓库外部化这三个包,并发布 `@oh-my-dsh/stent-pack` carrier,使消费者能通过官方插件通道安装完整 bundle: ``` dsh plugin --profile
add @oh-my-dsh/stent-pack
```
**边界(硬规则):** 工作区包含恰好三个完整实现包——`stent`(纯转换服务)、
`stent-api`(纯 compat facade)、`stent-dsh`(DSH 面 facade、invariant、
profile bootstrap)。根包 `@oh-my-dsh/stent-pack` 是单独发布的 carrier,不是第四个实现包。
官方 `@deepseek-ai/dsh-tool-cordis` 保持为上游依赖,不在此重新发布。
## 2. Host 集成:由 launcher 提供接线
三个包通过编译后的 launcher 安装 hooks 并挂载 facade。`src/stent-dsh.ts`
编译为 `lib/stent-dsh.js`,`src/stent-dsh-preload.ts` 编译为
`lib/stent-dsh-preload.js`;launcher 在官方 CLI 加载前注入这个编译后的
preload,不需要 host patch checkout。
官方通道已经覆盖的内容被刻意排除:安装 trio(`dsh plugin add`)、bundle 行名册与依赖、catalog 生成、trio-in-workspace 的 invariant/gate 豁免、以及全部文档(`README*`、`docs/`、`.agents/`)。剩下的是任何通道都提供不了的:launcher bootstrap(`apps/cli/src/profile-boot.ts` 在任何目标导入之前调用 `installStentBootstrap`、boot 后调用 `checkStentRequiredPatches`)、`clientBundle` 源码 transform 构建接缝(`packages/client/tsdown.client.ts`)、编译进官方 `tool-cordis` 包的 catalog 条目、它们的测试、以及 pnpm 策略接缝。
### 2.1 disabled opt-in 行
web-app bundle 层把 `stent` / `stent-dsh` 行插入为 **disabled opt-in**:纯 `stent` 包是没有插件 `apply` 的库,enabled 的行每次 boot 都失败("invalid plugin")。profile 通过启用这些行 opt-in;bundle 层每次 boot 都应用,因此既有的 profile 无需编辑即被覆盖。
### 2.2 TSX 死胡同(已记录并撤销)
`dsh` 的 source 启动(`node --import tsx/esm apps/cli/src/bin.ts`)一度看起来需要 `TSX_TSCONFIG_PATH` 或 register preload:`FiberState`(const enum,只在 `vendor/cordis/src` 存在)解析失败。两个 workaround 都曾发布,后来**全部撤销**——真正原因是 shell 里一个指向旧 staging checkout 的过期 `TSX_TSCONFIG_PATH`。干净环境下 tsx 自动发现入口的 tsconfig(继承 base)并把别名解析到 `src`。官方脚本原样运行;patch 中不存在相关接缝。
## 3. 安装模型:npm bundle
可发布的根 bundle `@oh-my-dsh/stent-pack` 声明三个已发布的 npm 实现包:
```
@oh-my-dsh/stent@^0.1.1
@oh-my-dsh/stent-api@^0.1.1
@oh-my-dsh/stent-dsh@^0.1.1
```
同一个 tag workflow 会在这三个包之后发布根 carrier,确保它的 semver 依赖已经存在于 npm。
这样安装只需一个 npm 包:
```sh
dsh plugin --profile web add @oh-my-dsh/stent-pack
```
安装时由 pnpm 解析这些 npm semver 依赖;启动时 `stent-dsh` 调用 DSH 的 module-fallback healer,把 bundle 的依赖闭包映射到 `$DSH_HOME/profiles/node_modules`,使 Profile 和 preload 解析到同一套 trio 副本。
- host 源码安装在 `apps/cli/package.json` 中声明 bundle;先执行 harness workspace 的 `pnpm install` 和 `pnpm run build`,再通过插件通道安装已发布的 npm bundle(把 `@oh-my-dsh/stent-pack` 并入 `dsh.profile.bundles`)、启用 `stent-dsh` 行——启动一律走编译后的 `lib/stent-dsh.js`。
- 消费侧构建使用根目录显式的 `build` 脚本;trio 与 launcher 在打包前由 tsdown 构建,不需要安装期 `prepare`。
### 3.1 pnpm 11 供应链接缝
npm bundle 不需要在 Profile 中设置 `blockExoticSubdeps: false`,也不需要 Git prepare allowlist 或 `dangerouslyAllowAllBuilds`。workspace 仍允许原生 `esbuild` 构建,并排除快速发布的 DSH rc 序列的 minimum-release-age 检查:
- 本 workspace 的 `allowBuilds: esbuild`;
- `minimumReleaseAgeExclude: ['@deepseek-ai/dsh-*']`——dsh-* rc 序列总在 24h 窗口内发布,仅写包名豁免所有版本。
## 4. Registry 依赖策略
dsh-* host 包以快速 rc 序列发布;本仓库通过 registry 范围跟踪它们,每条教训都来自一次真实事故。
### 4.1 dsh-compact 陷阱
`@deepseek-ai/dsh-client-runtime@0.0.1-rc.1` 依赖 `@deepseek-ai/dsh-compact`,后者**从未发布**(上游发布该 runtime 之后删除了这个包)。`0.1.0-rc.x` 系列去掉了该依赖;已端到端验证可安装。
### 4.2 缺失的 rc.5
上游代码版本是 `0.1.0-rc.5`,但 registry 从 `rc.3` 直接跳到 `rc.6`——rc.5 从未发布。因此范围写作 `^0.1.0-rc.0`(解析最新发布的 rc,且 `rc.0` 使未来的稳定版也在范围内)。peer 使用相同范围,host workspace 的 rc.5 满足它——host 安装复用 workspace 包而非 registry 副本。
### 4.3 真实 host 类型,而非本地契约
trio 一度声明 `host-contracts.ts` facade 加一个全局 `@deepseek-ai/cordis` Events 注入。它破坏了 host 各包的类型检查,已删除,改为直接导入真实 `@deepseek-ai/dsh-*` 类型(声明为 peer + devDeps)——与上游形状一致。`ctx.slots` 的类型来自 `dsh-client-runtime` 的声明,与上游相同。
### 4.4 已发布 lib 的运行时 peer
在 `autoInstallPeers: false` 下,已发布 `dsh-*` lib 的加载期导入(`dsh-scope`、`dsh-llm`、`dsh-timeout`、`dsh-typert-protocol`)必须显式列入 devDependencies——每一个都是在测试加载时报 "Cannot find package" 后补上的。
## 5. 浏览器 client 格式:closure factory
web shell 以 classic script 加载 `/plugins/