dsh-plugin-development
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDSH 插件开发
DSH Plugin Development
这是正式版导向的执行清单。先判断运行面,再选择官方模板,实现后必须从真实组合和用户安装路径验证。不要把某个项目的偶然实现当成框架契约。
This is an execution checklist oriented towards official releases. First determine the runtime context, then select the official template, and after implementation, verify through real combinations and user installation paths. Do not treat accidental implementations of a certain project as framework contracts.
1. 开始前
1. Before Starting
- 用 、
pwd、git rev-parse --show-toplevel确认项目与用户改动。git status --short --branch - 读取 、
package.json、cordis.patch.yml、构建配置、相关tsconfig*.json和测试。src/ - 不覆盖用户改动,不操作用户明确排除的 profile、端口或实例。
- 判断最小运行面:
- 工具、system prompt、HTTP、持久化、provider:host。
- slot、Conversation Node、浏览器状态和浮层:client。
- host 能力且需要 Web 可视化:host + client。
- 没有 Web 需求:不要声明 ,也不要构建 client bundle。
dsh.client
- 写下插件唯一职责、依赖的 service、贡献的配置行、持久化 owner 和用户可见验证面,再开始编码。
- Confirm the project and user changes using ,
pwd, andgit rev-parse --show-toplevel.git status --short --branch - Read ,
package.json,cordis.patch.yml, build configurations, relatedtsconfig*.jsonfiles and tests.src/ - Do not overwrite user changes, and do not manipulate profiles, ports or instances explicitly excluded by users.
- Determine the minimal runtime context:
- Tools, system prompt, HTTP, persistence, provider: host.
- Slots, Conversation Nodes, browser state and floating layers: client.
- Host capabilities that require Web visualization: host + client.
- No Web requirements: Do not declare , and do not build client bundles.
dsh.client
- Write down the plugin's unique responsibilities, dependent services, contributed configuration lines, persistence owners, and user-visible verification surfaces before starting coding.
2. 证据与官方参考
2. Evidence and Official References
2.1 取证顺序
2.1 Evidence Collection Order
行为不确定时按顺序取证,不猜:
- 当前项目及已安装 的
node_modules/@deepseek-ai/*、exports、types、README。package.json - 环境明确提供的 DeepSeek Harness checkout;只读分析,不修改。
- 克隆官方仓库取证(见 §2.3)。
- 信息仍不足时,以当前正式版 exports/types 为边界,选择可安全失败的最小实现并标注假设。
不要写死本机绝对路径,也不要访问或转述未授权的私有仓库内容。
When behavior is uncertain, collect evidence in the following order instead of guessing:
- , exports, types, and README of the current project and installed
package.json.node_modules/@deepseek-ai/* - DeepSeek Harness checkout provided by the environment; analyze in read-only mode, do not modify.
- Clone the official repository for evidence collection (see §2.3).
- If information is still insufficient, take the current official version's exports/types as the boundary, choose the minimal implementation that can fail safely and mark assumptions.
Do not hardcode local absolute paths, and do not access or relay content from unauthorized private repositories.
2.2 官方模板选择
2.2 Official Template Selection
若提供了 Harness checkout(环境提供或按 §2.3 克隆),优先按插件形态阅读这些模板;路径以 checkout 根目录为基准:
| 目标 | 主参考 | 学习重点 |
|---|---|---|
| Host Service / HTTP | | |
| 最小 client 插件 | | |
| Slot / Conversation Node | | |
| Bundle 分层 | | 顶层 patch 数组、行 id 覆盖、整段 config 替换、加载顺序 |
| 简单持久化 backend | | register → disposer → close、显式 root、并发打开门禁 |
| 崩溃安全日志 | | 原子发布、fsync、并发 no-clobber、torn-tail 处理 |
| 工具插件 | | |
| Client 测试 | | jsdom、SlotTestRuntime、mount/dispose、fake service |
复杂插件只用于补证据,不作为起步模板。若要委派只读调研,提示词必须要求给出文件、行区间、契约与最小建议。
If a Harness checkout is provided (by the environment or cloned as per §2.3), prioritize reading these templates according to the plugin type; paths are based on the checkout root directory:
| Objective | Primary Reference | Key Learning Points |
|---|---|---|
| Host Service / HTTP | | |
| Minimal client plugin | | |
| Slot / Conversation Node | | |
| Bundle Layering | | Top-level patch array, line ID override, whole-section config replacement, loading order |
| Simple persistence backend | | register → disposer → close, explicit root, concurrent access guard |
| Crash-safe logging | | Atomic publish, fsync, concurrent no-clobber, torn-tail handling |
| Tool plugin | | |
| Client testing | | jsdom, SlotTestRuntime, mount/dispose, fake service |
Complex plugins are only used to supplement evidence, not as starting templates. If delegating read-only research, the prompt must require specifying files, line ranges, contracts and minimal suggestions.
2.3 官方仓库兜底层
2.3 Official Repository Fallback
官方仓库 是公开、MIT 许可的可引用证据源(默认分支 ;开发者预览阶段无 release tag,不 pin 版本)。需要兜底取证时:
https://github.com/deepseek-ai/deepseek-harnessmaster-
选临时目录:用用户或环境提供的目录,例如;不要写死本机绝对路径。
SCRATCH="$(mktemp -d)" -
复用已有 checkout:若已存在,且
$SCRATCH/dsh-official指向官方、根目录含git remote -v与AGENTS.md,直接复用;需要更新时LICENSE(或删除后重克隆)。同一任务只维护这一个目录,避免反复克隆。git -C "$SCRATCH/dsh-official" fetch --depth 1 origin master && git -C "$SCRATCH/dsh-official" reset --hard origin/master -
浅克隆(只读取证,无需):
pnpm installshgit clone --depth 1 https://github.com/deepseek-ai/deepseek-harness.git "$SCRATCH/dsh-official" -
只克隆官方;不要访问或转述未授权的私有仓库内容。对克隆内容同样只读分析,不修改。
deepseek-ai/deepseek-harness
进入后定位:
- 先读根 (
AGENTS.md是它的符号链接):仓库布局、命令与约定一次讲清,是官方给 agent 的入口。CLAUDE.md - 再用 的 group 表确认目标包位于哪个
packages/README.md。packages/<group>/<pkg> - 按 §2.2 模板表读对应包的 与
README.md;取证结论给出文件与行区间。src/
演进兜底:官方仓库处于开发者预览、迭代极快、无兼容承诺、无 release tag,§2.2 的模板路径只是索引,一切以当前 checkout 的实际代码为准;路径或名称漂移时,用 定位新位置并回报修正,不要凭旧文档猜。需要复现一致证据时记录 。
packages/README.mdgit rev-parse HEADThe official repository is a public, MIT-licensed reference source (default branch ; no release tags during developer preview, do not pin versions). For fallback evidence collection:
https://github.com/deepseek-ai/deepseek-harnessmaster-
Choose a temporary directory: Use a directory provided by the user or environment, e.g.,; do not hardcode local absolute paths.
SCRATCH="$(mktemp -d)" -
Reuse existing checkout: Ifexists, and
$SCRATCH/dsh-officialpoints to the official repository, and the root directory containsgit remote -vandAGENTS.md, reuse it directly; to update, runLICENSE(or delete and re-clone). Maintain only this one directory per task to avoid repeated cloning.git -C "$SCRATCH/dsh-official" fetch --depth 1 origin master && git -C "$SCRATCH/dsh-official" reset --hard origin/master -
Shallow clone (read-only for evidence collection, no need for):
pnpm installshgit clone --depth 1 https://github.com/deepseek-ai/deepseek-harness.git "$SCRATCH/dsh-official" -
Only clone the official; do not access or relay content from unauthorized private repositories. Analyze cloned content in read-only mode, do not modify.
deepseek-ai/deepseek-harness
After entering, locate content in the following order:
- First read the root (
AGENTS.mdis its symlink): It clearly explains repository layout, commands and conventions, and is the official entry point for agents.CLAUDE.md - Then use the group table in to confirm which
packages/README.mdthe target package is located in.packages/<group>/<pkg> - Read the corresponding package's and
README.mdaccording to the template table in §2.2; specify files and line ranges in evidence conclusions.src/
Evolution fallback: The official repository is in developer preview, iterates rapidly, has no compatibility commitments, and no release tags. The template paths in §2.2 are only indexes; everything is subject to the actual code of the current checkout. If paths or names drift, locate the new position using and report corrections, do not guess based on old documents. Record when consistent evidence reproduction is needed.
packages/README.mdgit rev-parse HEAD3. Bundle、Profile 与 package 契约
3. Bundle, Profile and Package Contracts
3.1 两个概念
3.1 Two Concepts
- Bundle 是作者分发的包:指向配置层。
package.json.dsh.bundle.patch - Profile 是用户运行的组合:保存有序 bundle 列表。
$DSH_HOME/profiles/<name>/package.json.dsh.profile.bundles - 插件作者写 bundle;创建和维护 profile。不要手写用户 profile manifest。
dsh plugin
- Bundle: The package distributed by the author: points to the configuration layer.
package.json.dsh.bundle.patch - Profile: The combination run by the user: stores the ordered bundle list.
$DSH_HOME/profiles/<name>/package.json.dsh.profile.bundles - Plugin authors write bundles; creates and maintains profiles. Do not manually write user profile manifests.
dsh plugin
3.2 最小双面 package
3.2 Minimal Dual-Face Package
jsonc
{
"name": "dsh-my-plugin",
"type": "module",
"main": "lib/index.js",
"types": "lib/types/index.d.ts",
"exports": {
".": { "types": "./lib/types/index.d.ts", "default": "./lib/index.js" },
"./client": { "types": "./lib/types/client/index.d.ts", "default": "./lib/client.js" },
"./cordis.patch.yml": "./cordis.patch.yml",
"./package.json": "./package.json"
},
"files": ["lib", "cordis.patch.yml", "README.md"], // 目录或显式清单均可;官方仓库常用显式文件清单
"dsh": {
"bundle": { "patch": "./cordis.patch.yml" },
"client": {
"platform": "web",
"inject": ["@deepseek-ai/dsh-client-runtime"]
}
}
}规则:
- Host-only 包删除 与
./client。dsh.client - Client 包必须同时有 和真实存在的
dsh.client.platform: "web"。exports["./client"] - 是随图下发的信息性元数据(预检展示 / HMR diff 用),不决定 client fiber 的激活顺序;预取由
dsh.client.inject驱动,真正的依赖等待来自 client bundle 导出的dsh.client.immediately(§5.1),两者互不替代。export const inject - 是仅供启动关键入口使用的可选预取标记;普通第三方插件不要默认开启。
dsh.client.immediately - 当前权威字段是 ;历史兼容字段只有在目标正式部署仍明确读取时才添加。
dsh.client - exports、和 Git/发布产物必须一致;任何入口都不能指向不存在的文件。
files - DSH、Cordis、React 等共享运行时优先声明为 peer,避免复制 runtime identity;版本范围从目标正式版 package metadata 取证。
jsonc
{
"name": "dsh-my-plugin",
"type": "module",
"main": "lib/index.js",
"types": "lib/types/index.d.ts",
"exports": {
".": { "types": "./lib/types/index.d.ts", "default": "./lib/index.js" },
"./client": { "types": "./lib/types/client/index.d.ts", "default": "./lib/client.js" },
"./cordis.patch.yml": "./cordis.patch.yml",
"./package.json": "./package.json"
},
"files": ["lib", "cordis.patch.yml", "README.md"], // Directories or explicit lists are acceptable; official repositories often use explicit file lists
"dsh": {
"bundle": { "patch": "./cordis.patch.yml" },
"client": {
"platform": "web",
"inject": ["@deepseek-ai/dsh-client-runtime"]
}
}
}Rules:
- Remove and
./clientfor host-only packages.dsh.client - Client packages must have both and a valid
dsh.client.platform: "web".exports["./client"] - is informational metadata delivered with the bundle (used for pre-check display / HMR diff), and does not determine the activation order of client fibers; prefetch is driven by
dsh.client.inject, and real dependency waiting comes fromdsh.client.immediatelyexported by the client bundle (§5.1), the two are not interchangeable.export const inject - is an optional prefetch mark only for startup critical entries; do not enable it by default for ordinary third-party plugins.
dsh.client.immediately - The current authoritative field is ; historical compatible fields are only added if the target official deployment explicitly reads them.
dsh.client - Exports, and Git/release artifacts must be consistent; no entry can point to a non-existent file.
files - Shared runtimes such as DSH, Cordis, React are preferably declared as peers to avoid duplicating runtime identity; version ranges are collected from the target official version's package metadata.
3.3 Patch 层
3.3 Patch Layer
cordis.patch.ymlyaml
- insert:
- id: my-plugin
name: dsh-my-plugin
config: {}注意:
- 是配置树中稳定的行身份;
id是 Node 可解析的包名或导出路径。name - 后层按 覆盖前层;目标行的
id是整段替换,不是深合并,因此覆盖时要重述所需键。config - 生效顺序是 profile bundles → profile →
cordis.patch.yml→ 命令行$DSH_HOME/cordis.patch.yml;后者获胜。--patch - 包没有 时只会成为普通依赖,不会自动成为 profile 层。
dsh.bundle
cordis.patch.ymlyaml
- insert:
- id: my-plugin
name: dsh-my-plugin
config: {}Notes:
- is the stable line identity in the configuration tree;
idis a Node-resolvable package name or export path.name - Later layers override earlier layers by ; the
idof the target line is a full-section replacement, not a deep merge, so required keys must be restated when overriding.config - The effective order is profile bundles → profile →
cordis.patch.yml→ command line$DSH_HOME/cordis.patch.yml; the latter takes precedence.--patch - Packages without will only become ordinary dependencies and will not automatically become part of the profile layer.
dsh.bundle
4. Host 面实现
4. Host-Side Implementation
4.1 函数插件
4.1 Function Plugins
普通插件通常导出:
ts
export const name = 'my-plugin'
export const inject = ['tools']
export interface Config { enabled: boolean }
export const Config = z.object({ enabled: z.boolean().default(true) })
export function apply(ctx: Context, config: Config): void {}- 从
z导入(不是 zod);@deepseek-ai/schemastery引用导出的 schema,与官方内联的static Config = Config等价。static Config: z<Config> = z.object({...}) - 是必需 service;未满足时 fiber 保持 pending,框架会在服务就绪后激活,不要用轮询模拟依赖注入。
inject - Config 默认值放 schema;任何部署可能需要改变的值都应成为配置,而不是源码常量。
- 可选 service 用 判断或
ctx.get()惰性挂载;不要在ctx.inject([...], childCtx => ...)中抢跑兄弟 provider。apply()
Ordinary plugins usually export:
ts
export const name = 'my-plugin'
export const inject = ['tools']
export interface Config { enabled: boolean }
export const Config = z.object({ enabled: z.boolean().default(true) })
export function apply(ctx: Context, config: Config): void {}- is imported from
z(not zod);@deepseek-ai/schemasteryreferences the exported schema, which is equivalent to the official inlinestatic Config = Config.static Config: z<Config> = z.object({...}) - is the required service; if not satisfied, the fiber remains pending, and the framework will activate it after the service is ready, do not use polling to simulate dependency injection.
inject - Config default values are placed in the schema; any value that may need to be changed in deployment should be a configuration, not a source code constant.
- Optional services are judged using or lazily mounted using
ctx.get(); do not preempt sibling providers inctx.inject([...], childCtx => ...).apply()
4.2 Service 插件
4.2 Service Plugins
当插件提供稳定 service 时,参考 :
host/webserverts
export class MyService extends Service {
static Config = Config
constructor(ctx: Context, config: Config) {
super(ctx, 'myService')
}
async [Service.init](): Promise<void> {}
}- 构造器声明 service key;异步启动放在 。
Service.init - 初始化失败应让 fiber 失败并由启动方报告,不要吞掉组合错误。
- 注册方法返回 disposer;拥有资源的一方负责关闭资源。
When a plugin provides stable services, refer to :
host/webserverts
export class MyService extends Service {
static Config = Config
constructor(ctx: Context, config: Config) {
super(ctx, 'myService')
}
async [Service.init](): Promise<void> {}
}- The constructor declares the service key; asynchronous startup is placed in .
Service.init - Initialization failures should cause the fiber to fail and be reported by the starter, do not swallow combination errors.
- The registration method returns a disposer; the party that owns the resource is responsible for closing it.
4.3 Effect 所有权
4.3 Effect Ownership
所有长生命周期资源必须归当前 fiber:
- route、listener、watcher、timer、React root、DOM、socket、临时 service 都必须可清理。
- 用 或
ctx.on()。ctx.effect(() => disposer, label) - disposer 顺序通常是:停止外部入口/注销 registry → 等待或取消在途工作 → 关闭资源。
- 需要服务后绑定时,用“立即尝试 + service 事件/重试 + 幂等 guard”,不要重复注册。
ctx.inject
All long-lifecycle resources must belong to the current fiber:
- Routes, listeners, watchers, timers, React roots, DOM, sockets, temporary services must all be cleanable.
- Use or
ctx.on().ctx.effect(() => disposer, label) - The disposer order is usually: stop external entry/unregister registry → wait for or cancel in-progress work → close resources.
- When services need to be bound later, use "immediate attempt + service event/retry + idempotent guard", do not register repeatedly.
ctx.inject
4.4 工具
4.4 Tools
使用 :
ctx.tools.register(defineTool(...))- 写清何时调用、必要前置条件、失败语义和副作用。
description - 与
parameters都用output.schema的 value-schema DSL(编译后是受支持的 JSON Schema 子集):@deepseek-ai/dsh-tools是隐式开放对象根、必填用属性内联parameters;required: true声明 canonical 返回值并在注册时被output.schema强制校验。二者是同一 DSL 的两个面,不是两套语言。assertSupportedJsonSchema - 给模型稳定、紧凑、可判定的文本。
output.render - 从 获取当前会话、工作区和 owner,不从全局进程状态猜。
exec.agent - 异步工作观察或转发 ;写操作要有幂等、锁或冲突策略。
exec.signal
Use :
ctx.tools.register(defineTool(...))- clearly states when to call, necessary prerequisites, failure semantics and side effects.
description - Both and
parametersuse the value-schema DSL fromoutput.schema(compiled into a supported subset of JSON Schema):@deepseek-ai/dsh-toolsis an implicit open object root, required fields use inlineparametersin attributes;required: truedeclares the canonical return value and is forcibly validated byoutput.schemaduring registration. The two are two aspects of the same DSL, not two sets of languages.assertSupportedJsonSchema - provides stable, compact, determinable text for the model.
output.render - Obtain the current session, workspace and owner from , do not guess from global process state.
exec.agent - Observe or forward for asynchronous work; write operations must have idempotent, lock or conflict strategies.
exec.signal
4.5 HTTP
4.5 HTTP
- 注入当前正式版 Web server service,并用结构化最小接口降低耦合。
- 路由通过 注册;重复 (kind, path) 会抛错。
ctx.effect(() => ctx.webServer.register({ kind: 'exact' | 'prefix', path, handler })) - 状态接口显式设置缓存策略:敏感或实时快照优先 ,可重验证资源使用
Cache-Control: no-store;静态资源使用明确白名单和正确 content type。no-cache - path decode、请求体解析和 handler rejection 都要转成明确 4xx/5xx,不能成为未处理 rejection。
- exact route、最长 prefix、fallback 的所有权不能冲突;未知插件资源返回 404,不落入 SPA fallback。
- 涉及权限或本机能力时采用最小暴露、回环/信任边界和方法白名单。
- Inject the current official version's Web server service, and use a structured minimal interface to reduce coupling.
- Routes are registered via ; duplicate (kind, path) will throw an error.
ctx.effect(() => ctx.webServer.register({ kind: 'exact' | 'prefix', path, handler })) - Explicitly set cache policies for status interfaces: sensitive or real-time snapshots prioritize , re-verifiable resources use
Cache-Control: no-store; static resources use explicit whitelists and correct content types.no-cache - Path decode, request body parsing and handler rejection must all be converted to explicit 4xx/5xx, cannot become unhandled rejections.
- Ownership of exact routes, longest prefixes, and fallbacks cannot conflict; unknown plugin resources return 404, do not fall into SPA fallback.
- When involving permissions or local capabilities, adopt minimal exposure, loopback/trust boundaries and method whitelists.
4.6 持久化与并发
4.6 Persistence and Concurrency
先判断应复用正式版 storage/session persistence service,还是插件拥有独立介质。无论哪种:
- 路径配置显式指定;不要用 默认值散落用户数据。
process.cwd() - 状态按 workspace、session、owner 或业务 id 建立清晰隔离维度。
- 同一资源的读改写串行化;并发创建采用 no-clobber 语义。
- 人可读 JSON 要用同目录临时文件 + fsync + 原子发布;追加日志要处理 torn tail。并发创建用 +
link()的 no-clobber 协议,勿用unlink()静默覆盖。rename() - Registry backend 的清理顺序是 unregister 再 close。
- 恢复与 HMR 不能假设创建事件会重放;需要时显式扫描和回填已有对象。
First determine whether to reuse the official version's storage/session persistence service, or if the plugin has an independent medium. In either case:
- Path configurations are explicitly specified; do not use defaults to scatter user data.
process.cwd() - States are isolated by clear dimensions such as workspace, session, owner or business ID.
- Read-modify-write operations for the same resource are serialized; concurrent creation uses no-clobber semantics.
- Human-readable JSON uses temporary files in the same directory + fsync + atomic publish; append logs handle torn tails. Concurrent creation uses the no-clobber protocol of +
link(), do not useunlink()to silently overwrite.rename() - The cleanup order for registry backends is unregister then close.
- Recovery and HMR cannot assume that creation events will be replayed; if needed, explicitly scan and backfill existing objects.
5. Client 面实现
5. Client-Side Implementation
5.1 最小入口
5.1 Minimal Entry
ts
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
export const inject = ['slots']
export function apply(ctx: ClientContext): void {}- 类型贡献使用 type-only import 拉入 Context/SlotMap merge。
- client 注册、controller、listener、style 和 DOM 都必须随 client fiber dispose。
- per-session 状态按 分桶;连接重置时只重同步已经读过的对象。
SessionId
ts
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
export const inject = ['slots']
export function apply(ctx: ClientContext): void {}- Type contributions use type-only imports to pull in Context/SlotMap merges.
- Client registrations, controllers, listeners, styles and DOM must all be disposed along with the client fiber.
- Per-session states are bucketed by ; only resynchronize objects that have been read when the connection is reset.
SessionId
5.2 Slot 四步契约
5.2 Four-Step Slot Contract
- 声明:从提供 slot 的官方包拉入类型;自定义 owner 才通过 module augmentation 扩展 。
SlotMap - 认领:父 entry 的 表声明子 slot;声明即占有渲染权,不要争抢别人的 seat。
children - 注册:owner 与贡献者的激活顺序不保证,使用 等待声明;
ctx.slots.inject(key, () => ctx.slots.register({ name, children?, store?, locale?, inject?, ...kind 参数 }, Component))同时是子 slot 的认领表(认领即占有渲染权)。kind 参数:keyed 必填children、list 必填key(可加id/order)、chain 必填label;single/keyed/list 可加select做 cell 隐藏(同 cell 同 priority 会抛错)。向未声明 slot 直接 register 会抛错。priority - 渲染:owner 使用 /
renderSlot;贡献者不 import owner 的实现组件。renderSlotChain
选择接缝时先检查当前正式版类型。常见会话 UI 接缝包括:/、、、、、、、、、///。全局浮层用 (list/root),不要碰 单槽。不要仅凭旧文档写 slot 名,以当前正式版 的 SlotMap 为准。
conversation.session.header.actions.utilitiesconversation.viewconversation.chat.nodeconversation.chat.commandviewconversation.chat.assistant-actionsconversation.chat.turnTailconversation.input.dockconversation.composer.dockconversation.composer.barconversation.input.leftconversation.input.rightconversation.input.planconversation.input.modelshell.overlayrootui-conversation/src/client/contract/slots.ts- Declaration: Pull in types from the official package that provides slots; only extend via module augmentation for custom owners.
SlotMap - Claiming: The parent entry's table declares child slots; declaration means occupying rendering rights, do not compete for others' seats.
children - Registration: The activation order of owners and contributors is not guaranteed; use to wait for declaration;
ctx.slots.inject(key, () => ctx.slots.register({ name, children?, store?, locale?, inject?, ...kind parameters }, Component))is also the claiming table for child slots (claiming means occupying rendering rights). Kind parameters: keyed requireschildren, list requireskey(can addid/order), chain requireslabel; single/keyed/list can addselectfor cell hiding (same cell with same priority will throw an error). Directly registering to an undeclared slot will throw an error.priority - Rendering: Owners use /
renderSlot; contributors do not import the owner's implementation components.renderSlotChain
When choosing seams, first check the current official version's types. Common session UI seams include: /, , , , , , , , , ///. Global floating layers use (list/root), do not touch the single slot. Do not write slot names based solely on old documents, refer to the SlotMap in the current official version's .
conversation.session.header.actions.utilitiesconversation.viewconversation.chat.nodeconversation.chat.commandviewconversation.chat.assistant-actionsconversation.chat.turnTailconversation.input.dockconversation.composer.dockconversation.composer.barconversation.input.leftconversation.input.rightconversation.input.planconversation.input.modelshell.overlayrootui-conversation/src/client/contract/slots.ts5.3 Conversation Node
5.3 Conversation Node
Conversation Node 是“事件折叠 + keyed slot renderer”的组合:
- 定义共享事件类型,并 merge 到 session event map。
- :
conversationEvents.register(definition)- 选择事件;
match - 创建节点状态;
start - 按 seq 确定性折叠;
update - 生成稳定的 view node。
buildViewNode
- merge /节点 kind 类型。
ChatNodeDataMap - 向 注册相同 key 的 renderer。
conversation.chat.node
红线:
- 重放同一事件序列必须得到同一节点,不读时间、随机数或当前磁盘状态。
- 返回稳定业务 id 和
match角色;节点引擎在当前会话内使用start|update去重。跨会话持久化缓存另行把 owner session 纳入 key,不能混成引擎契约。conversationContextKey(kind, businessId) - 事件写入业务 owner 会话;共享 host/client 事件文件保持 type-only、最好零运行时 import,避免双 tsconfig 的 Context augmentation 相互污染。
- 磁盘/服务端快照可作为实时 UI 真相;事件流用于对话投影、审计和确定性历史,两者职责不要混淆。
Conversation Node is a combination of "event folding + keyed slot renderer":
- Define shared event types and merge them into the session event map.
- :
conversationEvents.register(definition)- selects events;
match - creates node states;
start - folds deterministically by seq;
update - generates stable view nodes.
buildViewNode
- Merge /node kind types.
ChatNodeDataMap - Register a renderer with the same key to .
conversation.chat.node
Red Lines:
- Replaying the same event sequence must produce the same node, do not read time, random numbers or current disk state.
- returns a stable business ID and
matchrole; the node engine usesstart|updatefor deduplication within the current session. Cross-session persistent caching separately includes the owner session in the key, cannot be confused with engine contracts.conversationContextKey(kind, businessId) - Events are written to the business owner's session; shared host/client event files remain type-only, preferably with zero runtime imports, to avoid Context augmentation conflicts between dual tsconfigs.
- Disk/server snapshots can be used as the source of truth for real-time UI; event streams are used for dialogue projection, auditing and deterministic history, their responsibilities should not be confused.
5.4 Portal 兜底
5.4 Portal Fallback
能用语义正确的 slot 就不用 fixed portal。全应用浮层优先注册 (list/root,click-through 直到你的 entry 主动开启 pointer events);确无全局角落 slot 时才 body portal:
shell.overlay- React root、host DOM、window listener、全局 attribute 都有 disposer。
- 跟随 session list,按当前 owner 过滤;导航时立即收起。
- 宽屏可让主列礼让,窄屏退回 overlay;只依赖稳定 ,不要耦合哈希 class。
data-* - 首屏恢复的已有活动只显示徽标,避免首次请求返回后自动展开造成大幅布局位移;稳定后出现的新活动再自动展开。
- 面板限制为容器/视口的一部分高度,内容区内部滚动;窄屏单独设上限。
- 轮询使用 、in-flight guard、响应形状校验和 unmount 防护;失败保留最后成功快照。
no-store - 支持键盘、、
:focus-visible、Escape、reduced motion;hover/focus 只预览,click 才固定状态。aria-*
Use semantically correct slots instead of fixed portals whenever possible. Full-application floating layers prioritize registering to (list/root, click-through until your entry actively enables pointer events); only use body portals when there is truly no global corner slot:
shell.overlay- React roots, host DOM, window listeners, global attributes all have disposers.
- Follow the session list and filter by current owner; collapse immediately when navigating.
- On wide screens, allow the main column to yield, on narrow screens fall back to overlay; only rely on stable attributes, do not couple with hash classes.
data-* - Existing activities restored on the first screen only display logos, avoid automatic expansion after the first request returns causing large layout shifts; new activities that appear after stabilization can expand automatically.
- Panels are limited to a portion of the container/viewport height, with internal scrolling in the content area; set a separate upper limit for narrow screens.
- Polling uses , in-flight guard, response shape validation and unmount protection; retain the last successful snapshot on failure.
no-store - Support keyboard, ,
:focus-visible, Escape, reduced motion; hover/focus only previews, click to fix state.aria-*
6. TypeScript 与 Client 构建
6. TypeScript and Client Building
6.1 双 tsc program
6.1 Dual tsc Programs
Host 和 client 使用两个 program;文件名可按项目布局选择,官方仓库用 与 两个聚合 program 分别做 host/client 检查:host 排除 与 测试;client 聚合含各 client 包的 CSS module 声明、client 测试与构建脚本,共享 leaf 经 project references 进入,每个 包还各自维护一个 composite tsconfig 做包内类型检查。JSX 使用 和 ;相对 TS import 必须能正确重写为 emitted JS。
tsconfig.host.jsontsconfig.client.jsonpackages/client/*/src/***.client.*packages/client/*.tsxreact-jsx这样避免 host session 与 browser runtime 对同名 Context service 的 declaration merge 冲突。
Host and client use two separate programs; file names can be chosen according to project layout. Official repositories use two aggregate programs and for host/client checks respectively: host excludes and tests; client aggregates CSS module declarations, client tests and build scripts for each client package, shared leaf packages are included via project references, and each package also maintains its own composite tsconfig for intra-package type checking. JSX uses and ; relative TS imports must be correctly rewritten to emitted JS.
tsconfig.host.jsontsconfig.client.jsonpackages/client/*/src/***.client.*packages/client/*.tsxreact-jsxThis avoids declaration merge conflicts for Context services with the same name between host sessions and browser runtimes.
6.2 Client bundle
6.2 Client Bundle
优先复用当前正式版 Harness 的 client tsdown helper或已验证模板,不手写 loader 协议。产物应由构建自动包装为:
js
window.__ModuleLoader__.load({ id, factory: (require) => { /* bundle */ } })构建必须保留:
- host/client 两半产物并存(client build 不清空 host 输出);
- sourcemap;
- CSS Modules 编译与 注入;
style[data-plugin] - 从 emitted 找回
lib/资源的路径回退;src/ - client bundle purity gate。
Prioritize reusing the current official version's Harness client tsdown helper or verified templates, do not write loader protocols manually. Artifacts should be automatically wrapped by the build as:
js
window.__ModuleLoader__.load({ id, factory: (require) => { /* bundle */ } })The build must retain:
- Coexistence of host/client artifacts (client build does not clear host output);
- Sourcemaps;
- CSS Modules compilation and injection;
style[data-plugin] - Path fallback to retrieve resources from emitted
src/;lib/ - Client bundle purity gate.
6.3 Client import 纯度
6.3 Client Import Purity
浏览器模块表只回答正式版平台 seed 模块和明确豁免。规则:
- 平台模块以正式版 和官方 client 构建配置为准;React、Cordis、slots、web-react、primitives、attachment、schema-form 等由模块表提供。
packages/client/web/src/platform.ts - 是官方构建配置中明确标注的临时豁免,不是普通平台模块;不要把它泛化为可任意导入 runtime 值的许可。
@deepseek-ai/dsh-client-runtime/client - 纯类型 import 会被擦除,可以跨包拉入类型贡献。
- wire types、生成 remote codec 或明确 vendored 的纯库只有在官方模板允许时才 inline。
- 其他跨插件值 import 禁止;协作必须走 Cordis service/remote/slot。否则构建期纯度门或运行时 require 都会失败。
The browser module table only answers official version platform seed modules and explicit exemptions. Rules:
- Platform modules are based on the official version's and official client build configurations; React, Cordis, slots, web-react, primitives, attachment, schema-form are provided by the module table.
packages/client/web/src/platform.ts - is an explicit temporary exemption in the official build configuration, not an ordinary platform module; do not generalize it as permission to arbitrarily import runtime values.
@deepseek-ai/dsh-client-runtime/client - Pure type imports are erased, can pull in type contributions across packages.
- Wire types, generated remote codecs or explicitly vendored pure libraries can only be inlined if allowed by official templates.
- Other cross-plugin value imports are prohibited; collaboration must go through Cordis service/remote/slot. Otherwise, either the build-time purity gate or runtime require will fail.
7. 分发、安装与生效边界
7. Distribution, Installation and Effective Boundaries
7.1 安装
7.1 Installation
dsh plugin --profile <name> <args...>dsh.bundlesh
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:<owner>/<repo>GitHub 分发不要求发布 npm,但必须选择一种构建策略(Git 获取的是源码,不是构建产物):
- 官方主推:提供自包含 (官方 turtle-ui 模式);pnpm ≥10 默认拦截 Git 依赖的构建脚本,用户需在 profile 的
prepare显式pnpm-workspace.yaml后重跑allowBuilds。这会执行第三方代码,应固定 commit 并只信任已审查仓库。add - 备选(无交互安装):把 exports 指向的完整、最新 提交进 Git;用户无需执行依赖脚本,但非官方推荐路径。
lib/
README 只给经过全新 profile 验证的推荐命令。安装后重启目标 profile。
dsh plugin --profile <name> <args...>dsh.bundlesh
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:<owner>/<repo>GitHub distribution does not require publishing to npm, but one build strategy must be selected (Git retrieves source code, not build artifacts):
- Official Recommendation: Provide self-contained (official turtle-ui mode); pnpm ≥10 blocks build scripts for Git dependencies by default, users need to explicitly add
prepareto the profile'sallowBuildsand re-runpnpm-workspace.yaml. This executes third-party code, so commit should be fixed and only trusted reviewed repositories should be used.add - Alternative (Non-interactive Installation): Commit the complete, latest pointed to by exports into Git; users do not need to execute dependency scripts, but this is not the official recommended path.
lib/
The README only provides verified recommended commands for fresh profile installations. Restart the target profile after installation.
7.2 HMR 与重启
7.2 HMR and Restart
- client HMR 需要 等构建 watcher 持续重写
tsdown --watch;host HMR 只负责 stat 检测文件变化,再通过 rev/SSE 触发 browser fiber 的 dispose/reload。lib/client.js - 只有 bundle 内容变化可以 client HMR;package manifest、exports、插件集合、profile bundles 和 host 代码变化需要重启。
- 普通 build 后没有 watcher 时,刷新现有 DSH 页面。
- 不启动独立 Vite server 替代 DSH GUI;Web shell 依赖 host 注入的 。
window.__DSH_BOOT__
- Client HMR requires a build watcher such as to continuously rewrite
tsdown --watch; host HMR only detects file changes via stat, then triggers browser fiber dispose/reload via rev/SSE.lib/client.js - Only bundle content changes can trigger client HMR; changes to package manifests, exports, plugin collections, profile bundles and host code require a restart.
- When there is no watcher after a normal build, refresh the existing DSH page.
- Do not start an independent Vite server to replace the DSH GUI; the Web shell depends on injected by the host.
window.__DSH_BOOT__
8. 验证矩阵
8. Verification Matrix
8.1 基线
8.1 Baseline
sh
pnpm typecheck
pnpm build
pnpm test # package.json 声明时运行
pnpm verify # package.json 声明时运行
git diff --check先读取 ,不要假设所有仓库都有同名聚合脚本:官方 Harness 使用 / 与多个 gate;第三方插件可自定义 。项目级 verify/check 至少覆盖:
package.json.scriptscheck:cicheck:allverify-*verify- 纯业务规则和状态迁移;
- 临时目录中的文件往返、锁、归档/恢复;
- client 可独立测试的投影/折叠纯函数;
- canonical Skill 与镜像一致性(若项目提供镜像)。
sh
pnpm typecheck
pnpm build
pnpm test # Run if declared in package.json
pnpm verify # Run if declared in package.json
git diff --checkFirst read , do not assume all repositories have the same aggregated scripts: Official Harness uses / and multiple gates; third-party plugins can customize . Project-level verify/check should cover at least:
package.json.scriptscheck:cicheck:allverify-*verify- Pure business rules and state transitions;
- File round-trips, locks, archiving/restoration in temporary directories;
- Client-side projection/folding pure functions that can be tested independently;
- Canonical Skill and mirror consistency (if the project provides mirrors).
8.2 Host 与真实组合
8.2 Host and Real Combinations
- 单元测试覆盖 schema、service、失败和 disposer。
- 有 registry/backend 接口时使用共享 contract suite。
- 不只手搓 :至少一个测试通过真实 Loader/patch 组合启动,断言用户可见表面。
ctx.plugin() - 先用 创建非内置 scratch profile,再执行
dsh plugin --profile <scratch> add <pkg>,确认 bundle 层、行 id、name、config 和注入顺序;内置dsh --profile <scratch> --dump-config/webprofile 可由 launcher 初始化。另有headless:只打印 bundle 层、跳过用户层与--dump-default-config,可作坏--patch时的恢复诊断。cordis.patch.yml - 真实任务使用 ;不要发明
dsh --profile headless "一个小而可判定的任务"子命令。dsh run
- Unit tests cover schemas, services, failures and disposers.
- Use shared contract suites when there are registry/backend interfaces.
- Do not only manually call : At least one test starts via real Loader/patch combination and asserts user-visible surfaces.
ctx.plugin() - First create a non-built-in scratch profile using , then execute
dsh plugin --profile <scratch> add <pkg>to confirm bundle layers, line IDs, names, configs and injection order; built-indsh --profile <scratch> --dump-config/webprofiles can be initialized by the launcher. There is alsoheadless: Only prints bundle layers, skips user layers and--dump-default-config, can be used for recovery diagnosis when--patchis broken.cordis.patch.yml - Use for real tasks; do not invent the
dsh --profile headless "a small, determinable task"subcommand.dsh run
8.3 Client
8.3 Client
- client 测试使用 jsdom lane;通过 SlotTestRuntime 或最小 fake services mount 插件。
- 断言 slot 注册、渲染、session 隔离、connection reset、dispose 后 registry/DOM/style/controller 均清理。
- 每个 registry 贡献至少有一个 HMR/dispose 安全测试。
- GUI 使用独立 web profile 和真实浏览器,验证名册、路由、交互、刷新、宽窄屏、滚动、焦点和 reduced motion。
- Client tests use the jsdom lane; mount plugins via SlotTestRuntime or minimal fake services.
- Assert slot registration, rendering, session isolation, connection reset, and cleanup of registry/DOM/style/controller after dispose.
- At least one HMR/dispose safety test for each registry contribution.
- Use an independent web profile and real browser for GUI verification, including roster, routing, interaction, refresh, wide/narrow screens, scrolling, focus and reduced motion.
8.4 从零安装与 Git 分发
8.4 Zero-Install and Git Distribution
- 使用全新临时 /profile。
DSH_HOME - 按 README 的精确命令安装。
- 断言 profile dependency 与 。
dsh.profile.bundles - 断言所有 exports、host/client bundle、patch 和静态资源存在。
- 必须出现插件层。
--dump-config - 启动后检查 host route、client roster 和真实 UI。
仓库仍私有时,可把待发布内容复制到临时 Git repo 并提交,再通过 安装;这能验证“Git 获取的内容”而不是当前 checkout 的未提交文件。前提: 在 PATH、目录是已提交的真实 Git 仓库;若包声明了 ,还需在 profile 的 加 (与 §7.1 相同门禁)。只删除本任务创建的精确临时目录。
git+file://...gitpreparepnpm-workspace.yamlallowBuilds- Use a brand-new temporary /profile.
DSH_HOME - Install according to the exact commands in the README.
- Assert profile dependencies and .
dsh.profile.bundles - Assert that all exports, host/client bundles, patches and static resources exist.
- The plugin layer must appear in .
--dump-config - After startup, check host routes, client roster and real UI.
When the repository is still private, copy the content to be released to a temporary Git repo and commit, then install via ; this verifies "content retrieved via Git" instead of uncommitted files in the current checkout. Prerequisites: is in PATH, the directory is a committed real Git repository; if the package declares , must also be added to the profile's (same gate as §7.1). Only delete the exact temporary directory created for this task.
git+file://...gitprepareallowBuildspnpm-workspace.yaml9. 完成标准
9. Completion Criteria
完成前逐项确认:
- 运行面最小,manifest、exports、patch 与产物一致。
- 必需 inject 和可选 service 边界清楚;pending/failed 状态可诊断。
- route、registry、timer、watcher、DOM、React root 和存储均可清理。
- Conversation Node 可确定性重放,owner 与去重维度正确。
- client import 未越过模块表,host/client 类型隔离。
- 持久化有并发与崩溃语义,不依赖偶然 cwd。
- typecheck、build、verify、真实组合、从零安装和需要的 GUI 验证通过。
- README 安装命令与实际分发形态一致。
- 未执行未经授权的 commit、push、发布或 visibility 变更。
Confirm the following items one by one before completion:
- Minimal runtime context, consistent manifest, exports, patches and artifacts.
- Clear boundaries between required injects and optional services; pending/failed states are diagnosable.
- Routes, registries, timers, watchers, DOM, React roots and storage are all cleanable.
- Conversation Nodes can be deterministically replayed, with correct owners and deduplication dimensions.
- Client imports do not cross the module table, host/client types are isolated.
- Persistence has concurrency and crash semantics, does not rely on accidental cwd.
- Typecheck, build, verify, real combination, zero-install and required GUI verification pass.
- README installation commands are consistent with the actual distribution form.
- No unauthorized commits, pushes, releases or visibility changes have been executed.