omdsh-dev/stent
灵感来源于MC Fabric的Cordis/DSH hook处理器
项目介绍Project Overview
该 DSH 插件将 deepseek-harness 中私有的 Stent/Mixin 扩展层外置为三个独立 npm 包(stent、stent-api、stent-dsh),并以 @oh-my-dsh/stent-pack 为载体通过官方通道一键安装。运行时由 launcher 注入 stent-dsh-preload,并通过 stent-dsh capability 守门,未声明 inject: ['stent'] 的插件无法挂载注册表。适用于需要 Stent UI 与 Cordis 工具集协同的 DSH profile。务必注意:bundle 内仅含三个实现包,其它上游依赖(如 dsh-tool-cordis)由用户自身从上游安装,本仓库不重发布。
This DSH plugin externalizes the private Stent/Mixin extension layer from deepseek-harness as three npm packages (stent, stent-api, stent-dsh), packaged as the @oh-my-dsh/stent-pack carrier for one-step install through the official channel. At launch, the launcher injects stent-dsh-preload, and a stent-dsh capability gate blocks Stent-dependent plugins under plain dsh. Use it when a DSH profile needs Stent UI and Cordis toolset integration. Note: the bundle ships only the three implementation packages; other upstream dependencies such as dsh-tool-cordis are not republished.
请帮我了解并安装插件:【stent】【https://github.com/omdsh-dev/stent】
把上面这条消息直接发给当前会话里的 DSH,让它帮你了解并安装。安装命令不一定准确,发给 DSH 更稳。Send this message to DSH in your current session. CLI install commands may not be accurate across systems — DSH will figure it out for you.
或使用命令行安装(适合开发者)Or use CLI install (for developers)
命令行安装CLI Install
dsh plugin --profile web add @oh-my-dsh/stent-pack
把 omdsh-dev/stent 加入你的 DSH 配置(web profile)即可启用。
READMEREADME
RFC: dsh-external-stent — repository purpose, architecture, and decision record
English | 中文
- Status: living document (each section records the decision and its history)
- Scope: this standalone Stent extension workspace
- Upstream anchors: deepseek-harness snapshots
7b9644f2(0812) /9f9e2782a4(0813), fork tip65bcaf9902(feat-stent)
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 Stent extension, not a fork
deepseek-harness is a private monorepo. The Stent/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/stent-pack carrier so consumers can install the complete bundle
through the official plugin channel:
dsh plugin --profile <p> add @oh-my-dsh/stent-pack
Boundary (hard rule): the workspace contains exactly three complete
implementation packages — stent (pure transformation service),
stent-api (pure compat facade), and stent-dsh (DSH-facing
facades, invariant, profile bootstrap). The root @oh-my-dsh/stent-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/stent-dsh.ts compiles to lib/stent-dsh.js, while
src/stent-dsh-preload.ts compiles to lib/stent-dsh-preload.js. The bin only
resolves the DSH path and forwards its arguments; it injects the compiled
preload through NODE_OPTIONS=--import ... before the official CLI loads. The
preload owns profile composition, dependency healing, argv normalization,
environment setup, and hook registration. No host patch checkout is required.
The preload also records a process-local stent-dsh
launch capability, so Stent-dependent plugins stay unavailable under plain
dsh even if low-level hooks were installed by another path. The same
capability gate is enforced by getStent(ctx): a plugin that omitted
inject: ['stent'] cannot mount the registry through the accessor under plain
dsh and fails loudly instead.
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: the launcher-owned preload/bootstrap and its tests, the
clientBundle source-transform build seam, catalog entries compiled into the
official tool-cordis package, and the pnpm-policy seams.
2.1 The disabled opt-in rows
The web-app bundle layer inserts the stent / stent-dsh rows as
disabled opt-ins. A dynamic patch plugin marks its row with a Stent
configuration marker (for example config: { stent: true }); the launcher
only uses that marker to enable the row through a generated overlay. It never
extracts patch metadata from YAML. Plugin code registers the target metadata
and executable handler through ctx.stent.register() after installStentHooks() has
been installed. Plain dsh leaves these rows disabled.
2.2 Dynamic patch registration
The Node launcher installs installStentHooks() before the
official CLI imports target plugins. A plugin's ctx.stent.register({ id, target, operation, priority, required, handler }) call is the single source of
truth for a patch: runtime metadata updates the loader matcher, while the
handler remains in process memory and is never serialized. New imports use the
new matcher immediately. If a target was already loaded, the loader schedules
CJS/ESM cache re-transformation under the new matcher when the synchronous Node
hooks are available; an async loader-thread fallback applies the update to
future ESM loads.
Removing a patch refreshes the matcher and re-transforms loaded targets when
possible. Enabling or disabling a handler does not need a code transform,
because transformed bridge calls dispatch against the live runtime registry.
required: true is checked from the runtime registry after boot, not from a
YAML descriptor list. Profile YAML may mark a row as Stent-dependent for
activation, but it must not contain config.stent.patches descriptors.
2.3 Public API boundaries
The @oh-my-dsh/stent package is intentionally split by platform:
@oh-my-dsh/stent— platform-free runtime, bridge, service, and patch types;@oh-my-dsh/stent/node— Node hook installation, binding flush, and cache re-transformation;@oh-my-dsh/stent/browser— build transforms, package identity resolvers, and runtime bundle serving;@oh-my-dsh/stent/client— browser Cordis client artifact;@oh-my-dsh/stent/testing— isolated child-process fixtures.
Orchestrion configuration, wire serialization, module identity internals, and
loader-thread implementation remain private under packages/stent/src/transform
and packages/stent/src/node. Browser transforms accept public StentPatchStub
arrays and convert them internally; Node hooks read only the live runtime
registry. The package no longer exports platform implementation files as
compatibility subpaths.
2.4 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/stent-pack declares the three published
npm implementation packages:
@oh-my-dsh/stent@^0.1.1
@oh-my-dsh/stent-api@^0.1.1
@oh-my-dsh/stent-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/stent-pack
At installation, pnpm resolves those npm semver dependencies. At launch, stent-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 pack:build, then install the published npm bundle through the plugin channel (joining@oh-my-dsh/stent-packtodsh.profile.bundles). A profile boot through the compiledlib/stent-dsh.jsenables the integration row through its generated overlay. - Consumer-side builds use the explicit root
pack:buildscript. The trio and the launcher are built with their package-owned tsdown commands 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/dsh-client-runtime@0.0.1-rc.1 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/stent", factory: (require) => { ...; return module.exports; } })
with @deepseek-ai/cordis external (a platform seed) and everything else
inlined. stent was converted first; stent-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 Stent 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/stent-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. Linting
Each package runs Oxlint from its own package root with the pinned DSH toolchain (oxlint plus oxlint-tsgolint) and selected type-aware TypeScript rules; the root and package configs share a checked-in baseline. Warnings are failures. Generated lib/ output, JavaScript fixture launchers, and build configs stay outside this TypeScript lint face.
Oxfmt is the workspace formatter, with the shared policy in .oxfmtrc.json: two-space indentation, single quotes, no semicolons, trailing commas, and a 120-column print width. Each package owns its fmt and fmt:check commands; the carrier root's pack:fmt:check orchestrates the check across the root and all implementation packages. Generated lib/ output, fixture trees, JavaScript launcher fixtures, and build configs remain outside the formatting face.
The carrier root owns only the launcher under src/ and its root integration tests under tests/. Its lint, lint:fix, test, build, and knip commands never scan implementation package files; it has no pack:lint:fix command. Each implementation package declares its own toolchain and exposes independent lint, lint:fix, test, build, and knip commands scoped to that package; type checking is provided by each package's type-aware lint command rather than a separate typecheck script. The pack:* scripts at the carrier root are orchestration only: they keep the root-package checks separate from the package-owned commands and invoke the latter with pnpm --filter.
8. Timeline (abridged)
| Commit | Decision |
|---|---|
1e04b1a..2a42254 |
externalization: standalone Stent 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 |
9. 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.
nexu-io/open-design
ruvnet/ruflo
amruthpillai/reactive-resume
esengine/DeepSeek-Reasonix
volcengine/OpenViking
Molunerfinn/PicGo
titanwings/distilly
titanwings/colleague-skill