为 DSH 安装 Stent/Mixin 扩展层,提供 stent-dsh 启动器和两个 profile 行,让受信插件在不修改源码的前提下改写其他模块的函数行为。
- 语言
- TypeScript
- 分支
- main
安装
$ dsh plugin --profile web add github:omdsh-dev/fabric在终端中运行以上命令,通过 dsh CLI 安装此插件。可在右上角切换 Profile。 第一次用 dsh?看这篇新手教程
对话式安装
帮我安装 DeepSeek Harness 插件 omdsh-dev/fabric:先查看仓库 https://github.com/omdsh-dev/fabric 确认安全性,然后执行安装命令并验证插件加载成功。
把这段指令粘贴给 DSH Web GUI 里的助手,由它代你完成安装与验证。
一句话定位
这是 @oh-my-dsh/stent-pack 发行包在 DSH 插件市场的发布载体:把上游 deepseek-harness monorepo 里的 Stent/Mixin 扩展层(stent + stent-api + stent-dsh)打包成一行 dsh plugin 命令可装的 npm bundle,安装后为 DSH 增加一个独立的 stent-dsh 启动器和两个 profile 行(stent 与 stent-dsh),让受信插件能针对任何 npm 包的指定文件/函数注册 patch,无需 fork 也不必改上游源码。
核心能力
- 提供 stent-dsh 启动器:替代普通
dsh命令启动宿主 CLI,在官方 CLI 加载前注入 Stent 变换 hook 并写入进程级启动标记 - 在 profile 中插入
stent与stent-dsh两个可选行:默认 disabled,启用即接入 Stent/Mixin 扩展层 - 提供四种 patch 操作:
before(改参数)、after(改返回值)、around(决定是否执行原函数并可替换结果)、replace(完全接管调用) - 在目标模块首次加载前完成 AST 改写:基于 Orchestrion-JS(
@apm-js-collab/code-transformer)改写 CommonJS 与 ESM 模块,并把每次改写记录为 binding - 支持声明
required: true:boot 后检查若 patch 未命中目标函数,loud 失败并指出 patch id 与目标模块 - 同时支持 Node 宿主与浏览器 client:浏览器入口以 closure factory 形态发布,在 web Cordis 树上安装
ctx.stentClient
技术实现
- 语言: TypeScript(ESM,monorepo 三个包 + 顶层 launcher)
- 关键依赖:
@apm-js-collab/code-transformer(AST 改写)、@deepseek-ai/cordis(插件 fiber 框架)、@oh-my-dsh/stent(核心运行时,跨三个内部包共享)、@deepseek-ai/dsh-*(作为 peer 与 devDep,类型声明而非打包发布) - 架构模式: 双层 launcher:编译后的
src/stent-dsh-preload.ts→lib/stent-dsh-preload.js负责在 CLI 加载前安装 hook;src/stent-dsh/main.ts→lib/stent-dsh.js作为stent-dshbin 启动官方 CLI,并合并多 patch layer(bundles、profile、$DSH_HOME、--patch 覆盖)输出$STENT_CONFIG - 入口文件: 顶层 launcher
src/stent-dsh.ts;preloadsrc/stent-dsh-preload.ts;三个内部包主入口packages/stent/src/index.ts、packages/stent-api/src/index.ts、packages/stent-dsh/src/index.ts
适用场景
希望让第三方插件在不修改 DSH 自身或任何 npm 包源码的前提下,"在外部"扩展、改写或观察某些函数行为——典型如记录某些关键调用、统一改写参数/返回值、或为已有闭源工具临时打补丁。安装此 bundle 后,下游需要 Stent 能力的插件(如声明 inject: ['stent'] 的 Mod)即可在你的 profile 里正常工作。
前置依赖与兼容性
| 依赖 | 最低版本 | 说明 |
|---|---|---|
| DSH | 0.1.0-rc.0+ | stent-dsh 的 peerDependencies 钉住 @deepseek-ai/dsh-agent / dsh-commands / dsh-invariants / dsh-session / dsh-system-prompt / dsh-tools / dsh-client-ui-commands / dsh-client-ui-slots 全部 ^0.1.0-rc.0 |
| Node.js | ^22.19.0 或 >=24.0.0 | 顶层 engines.node;若同时启用 loader-thread hook(如 tsx)建议 ≥ 22.22.3 / ≥ 24.11.1,否则 CommonJS 模块走同步 hook 路径会触发 Node load 校验崩溃 |
| Cordis | ^4.0.1-0 | stent / stent-dsh 都以 @deepseek-ai/cordis 为 peer |
| 平台 | 跨平台 | 未声明 os / cpu 限制 |
| 原生模块 | 无 | 未引入 node-pty / node:sqlite / FFI 等原生依赖;esbuild 是 pnpm 允许的构建依赖 |
安装方式
dsh plugin --profile web add github:omdsh-dev/fabric
配置项
本插件不需要用户直接编辑配置文件,安装后会通过 cordis.patch.yml 自动向 profile 注入两行并默认禁用。要启用 Stent 扩展能力,按下面的 YAML 模板在自己的 profile 配置或 patch 层里把对应行改为启用(并写好 patches 描述符):
| 配置 | 类型 | 说明 | 默认值 |
|---|---|---|---|
id: stent(profile 行) | 行配置 | 纯 stent 包的描述符载体,必须保持 disabled: true;其 config.stent.patches 字段列出所有静态 patch 描述符(id / target / operation) | disabled: true |
id: stent-dsh(profile 行) | 行配置 | DSH 集成包(Host facade + 浏览器 client + required-patch 校验),启用后才挂载 ctx.stentAgent / ctx.stentTools / ctx.stentPrompt / ctx.stentCommands 等服务 | disabled: true |
config.stent.patches[*].id | 字符串 | patch 唯一 id,匹配 [A-Za-z0-9._:/+-]{1-120};同 id 仅属于一个属主 | 无 |
config.stent.patches[*].target.module | 字符串 | 被改写函数所属的 npm 包名 | 无 |
config.stent.patches[*].target.versionRange | 字符串 | 目标包必须满足的 semver 区间 | 无 |
config.stent.patches[*].target.filePath / filePaths | 字符串 / RegExp / 字符串数组 | 目标函数所在的包内文件路径,多形态并存时用 RegExp 或数组 | 无 |
config.stent.patches[*].target.functionQuery / astQuery | 对象 / 字符串 | 选中目标函数的查询(按函数名 / AST 选择器);二者至少给一个 | 无 |
config.stent.patches[*].operation | 枚举 | before / after / around / replace 之一,决定 handler 介入方式 | 无 |
config.stent.patches[*].required | 布尔 | true 时若 boot 后该 patch 没有任何 binding,启动直接报错而不是静默失效 | false |
第三方 plugin 内的 inject | 字符串数组 | 需要 Stent 注册能力的插件须声明 inject: ['stent'],否则在普通 dsh 下会保持 pending | 视插件而定 |
常见问题
Q: 这个插件装好后能做什么?
A: 在 profile 里同时启用 stent 和 stent-dsh 两行后,DSH 进程就具备 Stent 扩展能力:受信插件可针对 npm 包的指定文件与函数注册 patch(前置改参、结果拦截、整体替换等四种操作),无需 fork 上游包。
Q: 安装后为什么 profile 列表里看不到效果?
A: stent 与 stent-dsh 两行默认 disabled——纯 stent 包没有 apply,作为描述符载体存在;只有主动把这两行启用,且通过 stent-dsh 启动器(而非普通 dsh)启动,Stent hook 才会真正安装。
Q: 能不能用普通 dsh 命令启动?
A: 不行。普通 dsh 启动不会写入 stent-dsh 的进程级启动标记,Stent 依赖插件会保持 pending 而不挂载;任何声明了 config.stent.patches 的行若在普通 dsh 下被启用,boot 会直接抛出错误提示你改用 stent-dsh 启动。
Q: patch 注册后能撤销吗?
A: 单次注册的 disposer 由所属 Cordis fiber 持有,插件卸载时自动 disable + remove;同一 patch id 仅属于一个属主,其他插件再注册会被直接拒绝;同属主 HMR 重注册会把所有权转给新一代,旧一代清理动作自动 no-op。
Q: 怎么知道 patch 真的命中了目标函数?
A: ctx.stent.list() 返回每个 patch 的有序快照,ctx.stent.bindings(id) 返回实际改写的文件清单;如果声明了 required: true 而启动后绑定为空,launcher 会在 boot 后 loud 失败并点名 patch id 与目标。
Q: 哪些 DSH 客户端面也支持 Stent?
A: stent-dsh 同时发布 ./client 浏览器入口(closure factory 形态),DSH 客户端加载 client.js 时会在浏览器 Cordis 树上安装 ctx.stent,并把 ctx.stentClient 暴露给 Mod 使用 commands 与命名 UI slot。
Q: 卸载这个插件会不会留下残余状态?
A: 卸载时把 stent 与 stent-dsh 两行从 cordis.patch.yml / dsh.profile.bundles 里删除,重启即可;运行时的 hook 安装由注册 fiber 的 effect 跟踪,插件卸载会自动释放。
Q: 移动端或国产 CPU 能用吗?
A: 本插件未声明 os / cpu 限制,跨平台可用;但运行时需要 Node ≥ 22.19.0,且当 loader-thread hook 共存时建议 ≥ 22.22.3 / ≥ 24.11.1,否则 CommonJS 模块可能触发 Node load 校验崩溃。
上手难度
进阶 — 需要理解 Cordis fiber / 启动能力门控、Orchestrion 风格的 AST 改写语义、profile 多 patch layer 合并顺序,并能在 YAML 中描述目标函数(包名 / 版本区间 / 文件路径 / 函数查询),普通用户建议直接复用社区已写好的 patch 描述符。
已知问题与限制
- 必须用
stent-dsh启动器而非普通dsh命令,否则 Stent hook 不安装、Stent 依赖插件保持 pending(packages/stent/src/service.ts:44-47) - hooks 必须早于目标模块首次求值安装;之后注册的 patch 只能对尚未加载的模块生效,已加载模块保持原行为(packages/stent/README.md:64)
- Node 在 22.19.0–22.22.3 / 24.0.0–24.11.1 区间与 loader-thread hook 共存时,同步
registerHooks链对 CommonJS 模块可能让 Node load 校验崩溃;此区间会走异步module.registerfallback(packages/stent/README.zh.md:101) - 同一目标函数上的两个
replacepatch 在注册时会被拒绝;selector 默认改写全部匹配,传index才能只改某一个(packages/stent/README.zh.md:97、141) - 同一 patch id 仅属于一个属主,跨属主再注册直接抛错;同属主 HMR 重注册会移交所有权,旧一代清理 no-op(packages/stent/README.zh.md:89、140-141)
- 格式正确但匹配不到任何东西的目标(如目标包版本不一致、文件布局变化)会让模块保持未变换——安装不会失败,但 patch 静默失效(packages/stent/README.zh.md:96)
- 目标必须是编译后的 JavaScript:Node loader 不会接受原始
.ts,传给.ts路径会失败;浏览器路径会在改写前剥离 TS 注解(packages/stent/README.zh.md:143) - constructor 目标在改写期被显式拒绝(移动的 constructor 体无法携带
super()/new.target),需改为 patch 方法或工厂函数(packages/stent/README.zh.md:97) - 安装
tsdown--watch时,编辑 patches JSON 才会触发 bundle 重建;浏览器侧通过 client-hmr 链送达,路径上偶尔出现 patch 与 bundle 不同步(packages/stent/README.zh.md:117) - DSH 自身 registry 上某些 rc 版本(0.1.0-rc.5)实际未发布,本 bundle 通过
^0.1.0-rc.0范围规避,仍可能遇到上下游 rc 节奏不匹配的小幅延迟(README.md:128-139)
English | 中文
- Status: living document (each section records the decision and its history)
- Scope: this standalone Fabric extension workspace
- Upstream anchors: deepseek-harness snapshots
7b9644f2(0812) /9f9e2782a4(0813), fork tip65bcaf9902(feat-fabric)
This document explains why this repository is shaped the way it is. Every non-obvious arrangement below was reached through a concrete failure recorded in the commit history; the sections follow the repository's evolution rather than its file layout.
1. Purpose: an external Fabric extension, not a fork
deepseek-harness is a private monorepo. The Fabric/Mixin extension layer lives
there as three implementation packages, but a consumer cannot install them from
the registry. This repository externalizes those three packages and publishes
the @oh-my-dsh/cordis-fabric-pack carrier so consumers can install the complete bundle
through the official plugin channel:
dsh plugin --profile <p> add @oh-my-dsh/cordis-fabric-pack
Boundary (hard rule): the workspace contains exactly three complete
implementation packages — cordis-fabric (pure transformation service),
cordis-fabric-api (pure compat facade), and cordis-fabric-dsh (DSH-facing
facades, invariant, profile bootstrap). The root @oh-my-dsh/cordis-fabric-pack is a
separately publishable carrier, not a fourth implementation package. Anything
else — including the official @deepseek-ai/dsh-tool-cordis toolset — remains
an upstream dependency and is not republished here.
2. Host integration: launcher-provided wiring
The three packages install hooks and mount facades through the compiled launcher.
src/fabric-dsh.ts compiles to lib/fabric-dsh.js, while
src/fabric-dsh-preload.ts compiles to lib/fabric-dsh-preload.js; the launcher
injects that compiled preload before the official CLI loads. No host patch
checkout is required.
Everything the official channels already cover is deliberately excluded:
installing the trio (dsh plugin add), bundle roster rows and dependencies,
catalog generation, invariant/gate exemptions for trio-in-workspace, and all
documentation (README*, docs/, .agents/). What remains is what no channel
can provide: launcher bootstrap (apps/cli/src/profile-boot.ts calls
installFabricBootstrap before any target import and
checkFabricRequiredPatches after boot), the clientBundle source-transform
build seam (packages/client/tsdown.client.ts), catalog entries compiled into
the official tool-cordis package, their tests, and the pnpm-policy seams.
2.1 The disabled opt-in rows
The web-app bundle layer inserts cordis-fabric / cordis-fabric-dsh rows as
disabled opt-ins: the pure cordis-fabric package is a library with no
plugin apply, so an enabled row fails every boot ("invalid plugin"). A
profile opts in by enabling the rows; the bundle layer applies on every boot,
so pre-existing profiles are covered without edits.
2.2 The TSX dead end (recorded and reverted)
The dsh source launch (node --import tsx/esm apps/cli/src/bin.ts) once
appeared to need TSX_TSCONFIG_PATH or a register preload: FiberState (a
const enum, only in vendor/cordis/src) failed to resolve. Both workarounds
shipped and were then reverted — the real cause was a stale
TSX_TSCONFIG_PATH in the shell pointing at an old staging checkout. With a
clean environment tsx auto-discovers the entry's tsconfig (extending the base)
and resolves the aliases to src. The official script runs unchanged; no
host-specific workaround is required.
3. Install model: npm bundle
The publishable root bundle @oh-my-dsh/cordis-fabric-pack declares the three published
npm implementation packages:
@oh-my-dsh/cordis-fabric@^0.1.1
@oh-my-dsh/cordis-fabric-api@^0.1.1
@oh-my-dsh/cordis-fabric-dsh@^0.1.1
The same tag workflow publishes the root carrier after those three packages, so its semver dependencies already exist on npm.
This keeps installation to one npm package:
dsh plugin --profile web add @oh-my-dsh/cordis-fabric-pack
At installation, pnpm resolves those npm semver dependencies. At launch, fabric-dsh asks DSH's module-fallback healer to map the bundle's dependency closure into $DSH_HOME/profiles/node_modules, so the Profile and the preload resolve the same trio copies.
- Host source installs declare the bundle in
apps/cli/package.json; run the harness workspace'spnpm installandpnpm run build, then install the published npm bundle through the plugin channel (joining@oh-my-dsh/cordis-fabric-packtodsh.profile.bundles) and enable thecordis-fabric-dshrow. Launches go through the compiledlib/fabric-dsh.js. - Consumer-side builds use the explicit root
buildscript. The trio and the launcher are built with tsdown before packing; no install-time prepare build is required.
3.1 pnpm 11 supply-chain seams
The npm bundle does not require blockExoticSubdeps: false, a Git
prepare allowlist, or dangerouslyAllowAllBuilds in the Profile. The workspace
still allows the native esbuild build and excludes the fast-moving DSH rc
train from minimum-release-age checks:
allowBuilds: esbuildin this workspace;minimumReleaseAgeExclude: ['@deepseek-ai/dsh-*']— the dsh-* rc train ships inside the 24h window and a name-only entry exempts all versions.
4. Registry dependency policy
The dsh-* host packages publish fast rc trains; this repository tracks them through registry ranges, and each lesson below came from a real breakage.
4.1 The dsh-compact trap
@deepseek-ai/[email protected] depended on
@deepseek-ai/dsh-compact, which was never published (upstream deleted the
package after publishing that runtime). The 0.1.0-rc.x series dropped the
dependency; verified installable end-to-end.
4.2 The missing rc.5
Upstream code is versioned 0.1.0-rc.5, but the registry jumps
rc.3 → rc.6 — rc.5 was never published. Ranges therefore read ^0.1.0-rc.0
(resolving the newest published rc, and rc.0 keeps stable releases in range
too). Peers use the same range, which the host workspace's rc.5 satisfies —
host installs reuse workspace packages instead of registry copies.
4.3 Real host types, not a local contract
The trio once declared a host-contracts.ts facade plus a global
@deepseek-ai/cordis Events injection. That broke type-checking across host
packages and was deleted in favor of importing the real @deepseek-ai/dsh-*
types (declared as peers + devDeps) — exactly the upstream shape. ctx.slots
typing comes from dsh-client-runtime's declaration, as upstream.
4.4 Runtime peers of the published libs
With autoInstallPeers: false, the published dsh-* libs' load-time imports
(dsh-scope, dsh-llm, dsh-timeout, dsh-typert-protocol) must be listed
as devDependencies explicitly — each was added after a "Cannot find package"
at test load.
5. Browser client format: the closure factory
The web shell loads /plugins/<id>/client.js as a classic script and resolves
value imports through the loader module table (a synchronous require inside
the factory). Plain ESM bundles cannot load there at all. Consequently both
trio browser halves ship as closure factories:
window.__ModuleLoader__.load({ id: "@oh-my-dsh/cordis-fabric", factory: (require) => { ...; return module.exports; } })
with @deepseek-ai/cordis external (a platform seed) and everything else
inlined. cordis-fabric was converted first; cordis-fabric-dsh followed
(the same gap, fixed after the ex-setting install exposed the first one).
Upstream never notices this — its monorepo builds both through the shared
clientBundle() preset.
5.1 ex-setting's three lessons (same contract, external repo)
The sibling omdsh-dev/ex-setting bundle hit the same contract three times:
- Its
dsh.clientmanifest must be nested ("dsh": { "client": ... }), not a top-leveldshClientfield — client-modules scans the nested form; - its consumer-side build must use the prepare config, not just the local one, or git installs serve the old artifact;
- cross-bundle value imports must not rely on a disabled row's factory — ex-setting inlines/avoids what the module table cannot answer, and installs static styles directly instead of routing them through a Fabric publish the transform could not produce (browser-transform cannot match inside the closure artifact).
6. Test strategy
The upstream suite resolves src through tsconfig paths; this repository only
has registry lib artifacts, which drove the evolution below.
- serve.spec uses a test-local
node:httpadapter for the hostwebServerservice, so exact/prefix routing and real HTTP responses stay covered without a DSH host-webserver test dependency. - hmr-e2e-runner drives config HMR by toggling the row's
disabledflag incordis.yml: the vendored fork'shmr.registerConfigand includeinternal/updateare fork-private and exist in no registry version (verified against latest 1.0.16/1.0.6). - client specs originally faked
CommandUiRuntime/SlotRegistrybecause the runtime rc.1 tree was uninstallable and the bundles are closure factories. After rc.6 became installable the real reason remained the factory format, so the specs now mount the real services through a test module loader (packages/cordis-fabric-dsh/tests/browser/module-loader.ts): happy-dom provideswindow; the__ModuleLoader__sink installs at helper module load; platform seeds (cordis,ui-slots,react) preload as ESM namespaces (the factoryrequireis synchronous and node cannotrequireESM);ui-primitives— a render-only heavy package — is stubbed;materialize()executes a factory with the module-table require (recursing into other registered bundles, memoized,stripClientSuffixnormalizingpkg/client). LoaderbaseUrland fixture URLs are pinned to file paths because happy-dom'slocationishttp://localhost:3000.
7. Timeline (abridged)
| Commit | Decision |
|---|---|
1e04b1a..2a42254 | externalization: standalone Fabric bundle, self-contained template |
4018661, 8ffaac4 | port the upstream three-package split + full host patch; HMR e2e |
d9228c4, 40600d4 | official plugin channel install; source-host install script |
1ba7077, 3331b80 | web-app bundle composes the rows; rows become disabled opt-ins |
7b8e913, 3fd3106 | patch rebases: 0812 baseline → 0813 baseline |
9158f5d | delete host-contracts.ts; real @deepseek-ai/dsh-* types |
30ed5ff, b58c643 | registry dependency policy (rc.5 peer, installable suites) |
58fbe75, 33955ef | both browser halves become closure factories |
aa58a52 | publish publicly (upstream parity) |
62ced22 | revert the TSX workarounds (environment misdiagnosis) |
3fd1a56 | happy-dom + ModuleLoader materializer; real browser services in tests |
8. Future work
- If the registry ever publishes node-importable builds (plain ESM or the
srchalves), the test module loader disappears and the specs import packages directly. - Upstream promoting
createSnapshotStoreout ofdsh-client-runtimeshrinks the seed table. - Upstream publishing
hmr.registerConfig/internal/updatewould let the HMR runner mirror the in-tree config flow again.
查看使用指南 →
该插件的安装步骤、关键要点、FAQ 与兼容性说明(基于已收录字段派生)。
收录徽章
[](https://deepseek-plugin.org/plugins/omdsh-dev/fabric)把这段 markdown 粘贴到你的 GitHub README,链接回本插件详情页。徽章只声明已被本站收录,不代表安全认证。