Agent SkillsNanmiCoder/dsh-agent-teams › dsh-plugin-development

dsh-plugin-development

GitHub

DeepSeek Harness插件开发执行指南,涵盖环境确认、运行面判断、官方模板参考及仓库取证流程。指导开发者规范实现Host/Client插件,确保契约合规与真实组合验证。

skills/dsh-plugin-development/SKILL.md NanmiCoder/dsh-agent-teams

Trigger Scenarios

需要开发或维护DeepSeek Harness插件时 涉及DSH插件的Bundle、Profile或Service实现时

Install

npx skills add NanmiCoder/dsh-agent-teams --skill dsh-plugin-development -g -y
More Options

Use without installing

npx skills use NanmiCoder/dsh-agent-teams@dsh-plugin-development

指定 Agent (Claude Code)

npx skills add NanmiCoder/dsh-agent-teams --skill dsh-plugin-development -a claude-code -g -y

安装 repo 全部 skill

npx skills add NanmiCoder/dsh-agent-teams --all -g -y

预览 repo 内 skill

npx skills add NanmiCoder/dsh-agent-teams --list

SKILL.md

Frontmatter
{
    "name": "dsh-plugin-development",
    "metadata": {
        "date": "2026-08-13",
        "version": "3.1.0",
        "reference": "https:\/\/github.com\/NanmiCoder\/dsh-agent-teams"
    },
    "description": "开发、维护、分发和验证 DeepSeek Harness (DSH) 插件的执行型 Skill。覆盖 host\/client 形态判断、bundle\/profile 契约、Service 与函数插件、工具、HTTP、持久化、slot、Conversation Node、客户端构建、HMR、GitHub 安装和真实组合验证。"
}

DSH 插件开发

这是正式版导向的执行清单。先判断运行面,再选择官方模板,实现后必须从真实组合和用户安装路径验证。不要把某个项目的偶然实现当成框架契约。

1. 开始前

  1. pwdgit rev-parse --show-toplevelgit status --short --branch 确认项目与用户改动。
  2. 读取 package.jsoncordis.patch.ymltsconfig*.json、构建配置、相关 src/ 和测试。
  3. 不覆盖用户改动,不操作用户明确排除的 profile、端口或实例。
  4. 判断最小运行面:
    • 工具、system prompt、HTTP、持久化、provider:host。
    • slot、Conversation Node、浏览器状态和浮层:client。
    • host 能力且需要 Web 可视化:host + client。
    • 没有 Web 需求:不要声明 dsh.client,也不要构建 client bundle。
  5. 写下插件唯一职责、依赖的 service、贡献的配置行、持久化 owner 和用户可见验证面,再开始编码。

2. 证据与官方参考

2.1 取证顺序

行为不确定时按顺序取证,不猜:

  1. 当前项目及已安装 node_modules/@deepseek-ai/*package.json、exports、types、README。
  2. 环境明确提供的 DeepSeek Harness checkout;只读分析,不修改。
  3. 克隆官方仓库取证(见 §2.3)。
  4. 信息仍不足时,以当前正式版 exports/types 为边界,选择可安全失败的最小实现并标注假设。

不要写死本机绝对路径,也不要访问或转述未授权的私有仓库内容。

2.2 官方模板选择

若提供了 Harness checkout(环境提供或按 §2.3 克隆),优先按插件形态阅读这些模板;路径以 checkout 根目录为基准:

目标 主参考 学习重点
Host Service / HTTP packages/host/webserver Servicestatic ConfigService.init、route disposer、连接清理
最小 client 插件 packages/client/ui-message-feedback injectapply、locale、per-session controller、slot 注册与清理
Slot / Conversation Node packages/client/ui-conversation + packages/client/ui-slots SlotMap、slot kind/scope、children 认领、keyed node renderer
Bundle 分层 packages/bundle/base + packages/bundle/web-app 顶层 patch 数组、行 id 覆盖、整段 config 替换、加载顺序
简单持久化 backend packages/storage/storage-json register → disposer → close、显式 root、并发打开门禁
崩溃安全日志 packages/session/session-persistence-jsonl 原子发布、fsync、并发 no-clobber、torn-tail 处理
工具插件 packages/fs/tool-fs defineTool、schema、render、可选能力挂载
Client 测试 packages/test-support/client-runtime jsdom、SlotTestRuntime、mount/dispose、fake service

复杂插件只用于补证据,不作为起步模板。若要委派只读调研,提示词必须要求给出文件、行区间、契约与最小建议。

2.3 官方仓库兜底层

官方仓库 https://github.com/deepseek-ai/deepseek-harness 是公开、MIT 许可的可引用证据源(默认分支 master;开发者预览阶段无 release tag,不 pin 版本)。需要兜底取证时:

  1. 选临时目录:用用户或环境提供的目录,例如 SCRATCH="$(mktemp -d)";不要写死本机绝对路径。

  2. 复用已有 checkout:若 $SCRATCH/dsh-official 已存在,且 git remote -v 指向官方、根目录含 AGENTS.mdLICENSE,直接复用;需要更新时 git -C "$SCRATCH/dsh-official" fetch --depth 1 origin master && git -C "$SCRATCH/dsh-official" reset --hard origin/master(或删除后重克隆)。同一任务只维护这一个目录,避免反复克隆。

  3. 浅克隆(只读取证,无需 pnpm install):

    git clone --depth 1 https://github.com/deepseek-ai/deepseek-harness.git "$SCRATCH/dsh-official"
    
  4. 只克隆官方 deepseek-ai/deepseek-harness;不要访问或转述未授权的私有仓库内容。对克隆内容同样只读分析,不修改。

进入后定位:

  1. 先读根 AGENTS.mdCLAUDE.md 是它的符号链接):仓库布局、命令与约定一次讲清,是官方给 agent 的入口。
  2. 再用 packages/README.md 的 group 表确认目标包位于哪个 packages/<group>/<pkg>
  3. 按 §2.2 模板表读对应包的 README.mdsrc/;取证结论给出文件与行区间。

演进兜底:官方仓库处于开发者预览、迭代极快、无兼容承诺、无 release tag,§2.2 的模板路径只是索引,一切以当前 checkout 的实际代码为准;路径或名称漂移时,用 packages/README.md 定位新位置并回报修正,不要凭旧文档猜。需要复现一致证据时记录 git rev-parse HEAD

3. Bundle、Profile 与 package 契约

3.1 两个概念

  • Bundle 是作者分发的包:package.json.dsh.bundle.patch 指向配置层。
  • Profile 是用户运行的组合:$DSH_HOME/profiles/<name>/package.json.dsh.profile.bundles 保存有序 bundle 列表。
  • 插件作者写 bundle;dsh plugin 创建和维护 profile。不要手写用户 profile manifest。

3.2 最小双面 package

{
  "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 包删除 ./clientdsh.client
  • Client 包必须同时有 dsh.client.platform: "web" 和真实存在的 exports["./client"]
  • dsh.client.inject 是随图下发的信息性元数据(预检展示 / HMR diff 用),不决定 client fiber 的激活顺序;预取由 dsh.client.immediately 驱动,真正的依赖等待来自 client bundle 导出的 export const inject(§5.1),两者互不替代。
  • dsh.client.immediately 是仅供启动关键入口使用的可选预取标记;普通第三方插件不要默认开启。
  • 当前权威字段是 dsh.client;历史兼容字段只有在目标正式部署仍明确读取时才添加。
  • exports、files 和 Git/发布产物必须一致;任何入口都不能指向不存在的文件。
  • DSH、Cordis、React 等共享运行时优先声明为 peer,避免复制 runtime identity;版本范围从目标正式版 package metadata 取证。

3.3 Patch 层

cordis.patch.yml 必须是顶层数组:

- insert:
    - id: my-plugin
      name: dsh-my-plugin
      config: {}

注意:

  • id 是配置树中稳定的行身份;name 是 Node 可解析的包名或导出路径。
  • 后层按 id 覆盖前层;目标行的 config 是整段替换,不是深合并,因此覆盖时要重述所需键。
  • 生效顺序是 profile bundles → profile cordis.patch.yml$DSH_HOME/cordis.patch.yml → 命令行 --patch;后者获胜。
  • 包没有 dsh.bundle 时只会成为普通依赖,不会自动成为 profile 层。

4. Host 面实现

4.1 函数插件

普通插件通常导出:

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@deepseek-ai/schemastery 导入(不是 zod);static Config = Config 引用导出的 schema,与官方内联的 static Config: z<Config> = z.object({...}) 等价。
  • inject 是必需 service;未满足时 fiber 保持 pending,框架会在服务就绪后激活,不要用轮询模拟依赖注入。
  • Config 默认值放 schema;任何部署可能需要改变的值都应成为配置,而不是源码常量。
  • 可选 service 用 ctx.get() 判断或 ctx.inject([...], childCtx => ...) 惰性挂载;不要在 apply() 中抢跑兄弟 provider。

4.2 Service 插件

当插件提供稳定 service 时,参考 host/webserver

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;拥有资源的一方负责关闭资源。

4.3 Effect 所有权

所有长生命周期资源必须归当前 fiber:

  • route、listener、watcher、timer、React root、DOM、socket、临时 service 都必须可清理。
  • ctx.on()ctx.effect(() => disposer, label)
  • disposer 顺序通常是:停止外部入口/注销 registry → 等待或取消在途工作 → 关闭资源。
  • 需要服务后绑定时,用“立即尝试 + service 事件/ctx.inject 重试 + 幂等 guard”,不要重复注册。

4.4 工具

使用 ctx.tools.register(defineTool(...))

  • description 写清何时调用、必要前置条件、失败语义和副作用。
  • parametersoutput.schema 都用 @deepseek-ai/dsh-tools 的 value-schema DSL(编译后是受支持的 JSON Schema 子集):parameters 是隐式开放对象根、必填用属性内联 required: trueoutput.schema 声明 canonical 返回值并在注册时被 assertSupportedJsonSchema 强制校验。二者是同一 DSL 的两个面,不是两套语言。
  • output.render 给模型稳定、紧凑、可判定的文本。
  • exec.agent 获取当前会话、工作区和 owner,不从全局进程状态猜。
  • 异步工作观察或转发 exec.signal;写操作要有幂等、锁或冲突策略。

4.5 HTTP

  • 注入当前正式版 Web server service,并用结构化最小接口降低耦合。
  • 路由通过 ctx.effect(() => ctx.webServer.register({ kind: 'exact' | 'prefix', path, handler })) 注册;重复 (kind, path) 会抛错。
  • 状态接口显式设置缓存策略:敏感或实时快照优先 Cache-Control: no-store,可重验证资源使用 no-cache;静态资源使用明确白名单和正确 content type。
  • path decode、请求体解析和 handler rejection 都要转成明确 4xx/5xx,不能成为未处理 rejection。
  • exact route、最长 prefix、fallback 的所有权不能冲突;未知插件资源返回 404,不落入 SPA fallback。
  • 涉及权限或本机能力时采用最小暴露、回环/信任边界和方法白名单。

4.6 持久化与并发

先判断应复用正式版 storage/session persistence service,还是插件拥有独立介质。无论哪种:

  • 路径配置显式指定;不要用 process.cwd() 默认值散落用户数据。
  • 状态按 workspace、session、owner 或业务 id 建立清晰隔离维度。
  • 同一资源的读改写串行化;并发创建采用 no-clobber 语义。
  • 人可读 JSON 要用同目录临时文件 + fsync + 原子发布;追加日志要处理 torn tail。并发创建用 link()+unlink() 的 no-clobber 协议,勿用 rename() 静默覆盖。
  • Registry backend 的清理顺序是 unregister 再 close。
  • 恢复与 HMR 不能假设创建事件会重放;需要时显式扫描和回填已有对象。

5. Client 面实现

5.1 最小入口

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 分桶;连接重置时只重同步已经读过的对象。

5.2 Slot 四步契约

  1. 声明:从提供 slot 的官方包拉入类型;自定义 owner 才通过 module augmentation 扩展 SlotMap
  2. 认领:父 entry 的 children 表声明子 slot;声明即占有渲染权,不要争抢别人的 seat。
  3. 注册:owner 与贡献者的激活顺序不保证,使用 ctx.slots.inject(key, () => ctx.slots.register({ name, children?, store?, locale?, inject?, ...kind 参数 }, Component)) 等待声明;children 同时是子 slot 的认领表(认领即占有渲染权)。kind 参数:keyed 必填 key、list 必填 id(可加 order/label)、chain 必填 select;single/keyed/list 可加 priority 做 cell 隐藏(同 cell 同 priority 会抛错)。向未声明 slot 直接 register 会抛错。
  4. 渲染:owner 使用 renderSlot/renderSlotChain;贡献者不 import owner 的实现组件。

选择接缝时先检查当前正式版类型。常见会话 UI 接缝包括:conversation.session.header.actions/.utilitiesconversation.viewconversation.chat.nodeconversation.chat.commandviewconversation.chat.assistant-actionsconversation.chat.turnTailconversation.input.dockconversation.composer.dockconversation.composer.barconversation.input.left/conversation.input.right/conversation.input.plan/conversation.input.model。全局浮层用 shell.overlay(list/root),不要碰 root 单槽。不要仅凭旧文档写 slot 名,以当前正式版 ui-conversation/src/client/contract/slots.ts 的 SlotMap 为准。

5.3 Conversation Node

Conversation Node 是“事件折叠 + keyed slot renderer”的组合:

  1. 定义共享事件类型,并 merge 到 session event map。
  2. conversationEvents.register(definition)
    • match 选择事件;
    • start 创建节点状态;
    • update 按 seq 确定性折叠;
    • buildViewNode 生成稳定的 view node。
  3. merge ChatNodeDataMap/节点 kind 类型。
  4. conversation.chat.node 注册相同 key 的 renderer。

红线:

  • 重放同一事件序列必须得到同一节点,不读时间、随机数或当前磁盘状态。
  • match 返回稳定业务 id 和 start|update 角色;节点引擎在当前会话内使用 conversationContextKey(kind, businessId) 去重。跨会话持久化缓存另行把 owner session 纳入 key,不能混成引擎契约。
  • 事件写入业务 owner 会话;共享 host/client 事件文件保持 type-only、最好零运行时 import,避免双 tsconfig 的 Context augmentation 相互污染。
  • 磁盘/服务端快照可作为实时 UI 真相;事件流用于对话投影、审计和确定性历史,两者职责不要混淆。

5.4 Portal 兜底

能用语义正确的 slot 就不用 fixed portal。全应用浮层优先注册 shell.overlay(list/root,click-through 直到你的 entry 主动开启 pointer events);确无全局角落 slot 时才 body portal:

  • React root、host DOM、window listener、全局 attribute 都有 disposer。
  • 跟随 session list,按当前 owner 过滤;导航时立即收起。
  • 宽屏可让主列礼让,窄屏退回 overlay;只依赖稳定 data-*,不要耦合哈希 class。
  • 首屏恢复的已有活动只显示徽标,避免首次请求返回后自动展开造成大幅布局位移;稳定后出现的新活动再自动展开。
  • 面板限制为容器/视口的一部分高度,内容区内部滚动;窄屏单独设上限。
  • 轮询使用 no-store、in-flight guard、响应形状校验和 unmount 防护;失败保留最后成功快照。
  • 支持键盘、:focus-visiblearia-*、Escape、reduced motion;hover/focus 只预览,click 才固定状态。

6. TypeScript 与 Client 构建

6.1 双 tsc program

Host 和 client 使用两个 program;文件名可按项目布局选择,官方仓库用 tsconfig.host.jsontsconfig.client.json 两个聚合 program 分别做 host/client 检查:host 排除 packages/client/*/src/***.client.* 测试;client 聚合含各 client 包的 CSS module 声明、client 测试与构建脚本,共享 leaf 经 project references 进入,每个 packages/client/* 包还各自维护一个 composite tsconfig 做包内类型检查。JSX 使用 .tsxreact-jsx;相对 TS import 必须能正确重写为 emitted JS。

这样避免 host session 与 browser runtime 对同名 Context service 的 declaration merge 冲突。

6.2 Client bundle

优先复用当前正式版 Harness 的 client tsdown helper或已验证模板,不手写 loader 协议。产物应由构建自动包装为:

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。

6.3 Client import 纯度

浏览器模块表只回答正式版平台 seed 模块和明确豁免。规则:

  • 平台模块以正式版 packages/client/web/src/platform.ts 和官方 client 构建配置为准;React、Cordis、slots、web-react、primitives、attachment、schema-form 等由模块表提供。
  • @deepseek-ai/dsh-client-runtime/client 是官方构建配置中明确标注的临时豁免,不是普通平台模块;不要把它泛化为可任意导入 runtime 值的许可。
  • 纯类型 import 会被擦除,可以跨包拉入类型贡献。
  • wire types、生成 remote codec 或明确 vendored 的纯库只有在官方模板允许时才 inline。
  • 其他跨插件值 import 禁止;协作必须走 Cordis service/remote/slot。否则构建期纯度门或运行时 require 都会失败。

7. 分发、安装与生效边界

7.1 安装

dsh plugin --profile <name> <args...> 是 profile 目录里的 pnpm 转发层,成功后按安装状态和 dsh.bundle 对账 bundle 列表。因此支持 npm、路径、tarball 和 Git:

npx -p @deepseek-ai/dsh dsh plugin --profile web add github:<owner>/<repo>

GitHub 分发不要求发布 npm,但必须选择一种构建策略(Git 获取的是源码,不是构建产物):

  • 官方主推:提供自包含 prepare(官方 turtle-ui 模式);pnpm ≥10 默认拦截 Git 依赖的构建脚本,用户需在 profile 的 pnpm-workspace.yaml 显式 allowBuilds 后重跑 add。这会执行第三方代码,应固定 commit 并只信任已审查仓库。
  • 备选(无交互安装):把 exports 指向的完整、最新 lib/ 提交进 Git;用户无需执行依赖脚本,但非官方推荐路径。

README 只给经过全新 profile 验证的推荐命令。安装后重启目标 profile。

7.2 HMR 与重启

  • client HMR 需要 tsdown --watch 等构建 watcher 持续重写 lib/client.js;host HMR 只负责 stat 检测文件变化,再通过 rev/SSE 触发 browser fiber 的 dispose/reload。
  • 只有 bundle 内容变化可以 client HMR;package manifest、exports、插件集合、profile bundles 和 host 代码变化需要重启。
  • 普通 build 后没有 watcher 时,刷新现有 DSH 页面。
  • 不启动独立 Vite server 替代 DSH GUI;Web shell 依赖 host 注入的 window.__DSH_BOOT__

8. 验证矩阵

8.1 基线

pnpm typecheck
pnpm build
pnpm test             # package.json 声明时运行
pnpm verify           # package.json 声明时运行
git diff --check

先读取 package.json.scripts,不要假设所有仓库都有同名聚合脚本:官方 Harness 使用 check:ci/check:all 与多个 verify-* gate;第三方插件可自定义 verify。项目级 verify/check 至少覆盖:

  • 纯业务规则和状态迁移;
  • 临时目录中的文件往返、锁、归档/恢复;
  • client 可独立测试的投影/折叠纯函数;
  • canonical Skill 与镜像一致性(若项目提供镜像)。

8.2 Host 与真实组合

  • 单元测试覆盖 schema、service、失败和 disposer。
  • 有 registry/backend 接口时使用共享 contract suite。
  • 不只手搓 ctx.plugin():至少一个测试通过真实 Loader/patch 组合启动,断言用户可见表面。
  • 先用 dsh plugin --profile <scratch> add <pkg> 创建非内置 scratch profile,再执行 dsh --profile <scratch> --dump-config,确认 bundle 层、行 id、name、config 和注入顺序;内置 web/headless profile 可由 launcher 初始化。另有 --dump-default-config:只打印 bundle 层、跳过用户层与 --patch,可作坏 cordis.patch.yml 时的恢复诊断。
  • 真实任务使用 dsh --profile headless "一个小而可判定的任务";不要发明 dsh run 子命令。

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。

8.4 从零安装与 Git 分发

  1. 使用全新临时 DSH_HOME/profile。
  2. 按 README 的精确命令安装。
  3. 断言 profile dependency 与 dsh.profile.bundles
  4. 断言所有 exports、host/client bundle、patch 和静态资源存在。
  5. --dump-config 必须出现插件层。
  6. 启动后检查 host route、client roster 和真实 UI。

仓库仍私有时,可把待发布内容复制到临时 Git repo 并提交,再通过 git+file://... 安装;这能验证“Git 获取的内容”而不是当前 checkout 的未提交文件。前提:git 在 PATH、目录是已提交的真实 Git 仓库;若包声明了 prepare,还需在 profile 的 pnpm-workspace.yamlallowBuilds(与 §7.1 相同门禁)。只删除本任务创建的精确临时目录。

9. 完成标准

完成前逐项确认:

  • 运行面最小,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 变更。

Version History

  • 763d88f Current 2026-08-19 11:33

Same Skill Collection

.dsh/skills/dsh-plugin-development/SKILL.md

Metadata

Files
0
Version
763d88f
Hash
8ca81567
Indexed
2026-08-19 11:33

inicio - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-08-20 01:53
浙ICP备14020137号-1 $mapa de visitantes$