SuperAPI AI Directory

dsh-plugin-development

Coding & development

View repository

Updated 10-16 01:34

About this skill

DSH Plugin Development

Version applicability: the main body of this document retains the historical template from 2026-08-13 and does not represent the current Harness API. When handling upgrades within the AgentTeams repository, first read the project root directory skills/README.md, then use the in-project dsh-upgrade-audit、plugin-upgrade and plugin-test. Installing this skill independently does not require these companion files; still gather evidence per §2. The following dsh-client-runtime、conversationEvents and other legacy interfaces must be re-verified against the exact target tag and npm artifacts, and cannot be used directly for Alpha.2 or later versions.

This is a release-oriented execution checklist. First determine the runtime surface, then choose the official template, and after implementation you must validate from real composition and the user installation path. Do not treat an incidental implementation of some project as a framework contract.

1. Before Starting

  1. Use pwd、git rev-parse --show-toplevel、git status --short --branch to confirm the project and user changes.
  2. Read package.json、cordis.patch.yml、tsconfig*.json, the build configuration, the relevant src/ and tests.
  3. Do not overwrite user changes, and do not operate on profiles, ports, or instances that the user has explicitly excluded.
  4. Determine the minimal runtime surface:
    • Tools, system prompt, HTTP, persistence, provider: host.
    • Slot, Conversation Node, browser state, and overlays: client.
    • Host capabilities that require Web visualization: host + client.
    • No Web requirement: do not declare dsh.client, and do not build a client bundle.
  5. Write down the plugin's single responsibility, the services it depends on, the configuration lines it contributes, the persistence owner, and the user-visible validation surface before starting to code.

2. Evidence and Official References

2.1 Evidence-Gathering Order

When behavior is uncertain, gather evidence in order; do not guess:

  1. The current project and the installed node_modules/@deepseek-ai/* 's package.json、exports、types、README。
  2. DeepSeek Harness checkout explicitly provided by the environment; read-only analysis, no modifications.
  3. Clone the official repository to gather evidence (see §2.3).
  4. If information is still insufficient, use the current release's exports/types as the boundary, choose the minimal implementation that can fail safely, and annotate assumptions.

Do not hardcode local absolute paths, and do not access or relay unauthorized private repository contents.

2.2 Official Template Selection

If a Harness checkout is provided (provided by the environment or cloned per §2.3), prioritize reading these templates according to plugin form; paths are relative to the checkout root directory:

Target Primary Reference Learning Focus
Host Service / HTTP packages/host/webserver Service、static Config、Service.init, route disposer, connection cleanup
Minimal client plugin packages/client/ui-message-feedback inject、apply, locale, per-session controller, slot registration and cleanup
Slot / Conversation Node packages/client/ui-conversation + packages/client/ui-slots SlotMap, slot kind/scope, children claiming, keyed node renderer
Bundle Layering packages/bundle/base + packages/bundle/web-app Top-level patch array, row id override, whole-segment config replacement, load order
Simple persistence backend packages/storage/storage-json register → disposer → close, explicit root, concurrent open gating
Crash-safe logging packages/session/session-persistence-jsonl Atomic publish, fsync, concurrent no-clobber, torn-tail handling
Tool plugin packages/fs/tool-fs defineTool, schema, render, optional capability mounting
Client testing packages/test-support/client-runtime jsdom、SlotTestRuntime、mount/dispose、fake service

Complex plugins are only used to supplement evidence, not as a starting template. If delegating read-only research, the prompt must require files, line ranges, contracts, and minimal recommendations.

2.3 Official repository fallback layer

The official repository https://github.com/deepseek-ai/deepseek-harness is a public, MIT-licensed citable source of evidence. Evidence gathering must specify the target version, the corresponding tag/commit, and the npm release status; do not equate the default branch with the version the user installed. When fallback evidence gathering is needed:

  1. Choose a temporary directory: use a directory provided by the user or the environment, for example SCRATCH="$(mktemp -d)"; do not hardcode absolute paths from the local machine.

  2. Reuse an existing checkout: verify the remote, HEAD, and working tree status; use git show <exact-tag>:<path> and similar read-only methods to inspect the target version, and do not reset the existing checkout. When version objects need to be completed, fetch the explicit tag and preserve existing changes.

  3. When there is no checkout, clone to a temporary directory (read-only evidence gathering, no pnpm installneeded), then read files via the exact tag/commit:

    git clone --filter=blob:none https://github.com/deepseek-ai/deepseek-harness.git "$SCRATCH/dsh-official"
    
  4. Clone only the official deepseek-ai/deepseek-harness; do not access or relay unauthorized private repository content. Analyze cloned content read-only as well, without modifying it.

After entering, locate:

  1. First read the root AGENTS.md(CLAUDE.md is its symlink): the repository layout, commands, and conventions are explained all at once, and it is the official entry point for agents.
  2. Then use the group table of packages/README.md to confirm which packages/<group>/<pkg>。
  3. According to the §2.2 template table, read the corresponding package's README.md and src/; evidence conclusions give files and line ranges.

Evolution fallback: the historical template paths in §2.2 are only an index; everything is subject to the target version's actual code and release artifacts; when paths or names drift, use the target version's packages/README.md to locate the new position and report the correction. Record the target tag, the resolved commit, and the actually installed dependency versions; having a tag on GitHub does not mean npm has published it, nor does it mean this project has passed acceptance with that version.

3. Bundle, Profile, and package contracts

3.1 Two concepts

  • Bundle is the package distributed by the author:package.json.dsh.bundle.patch points to the configuration layer.
  • Profile is the combination run by the user:$DSH_HOME/profiles/<name>/package.json.dsh.profile.bundles stores an ordered bundle list.
  • Plugin authors write bundles;dsh plugin creates and maintains profiles. Do not hand-write user profile manifests.

3.2 Minimal dual-sided 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"]
    }
  }
}

Rules:

  • Host-only packages remove ./client and dsh.client。
  • Client packages must have both dsh.client.platform: "web" and a genuinely existing exports["./client"]。
  • dsh.client.inject is informational metadata delivered with the graph (used for preflight display / HMR diff), and does not determine the activation order of client fibers; prefetching is driven by dsh.client.immediately , and the actual dependency waiting comes from the export const injectexported by the client bundle (§5.1); the two do not substitute for each other.
  • dsh.client.immediately is an optional prefetch marker used only for startup-critical entries; ordinary third-party plugins should not enable it by default.
  • The current authoritative field is dsh.client; historical compatibility fields are added only when the target production deployment still explicitly reads them.
  • exports、files and the Git/release artifacts must be consistent; no entry may point to a nonexistent file.
  • Shared runtimes such as DSH, Cordis, and React should preferentially be declared as peers to avoid duplicating runtime identity; version ranges are evidenced from the target production version's package metadata.

3.3 Patch layer

cordis.patch.yml Must be a top-level array:

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

Note:

  • id is a stable row identity in the configuration tree;name is a package name or export path resolvable by Node.
  • Later layers override earlier layers by id ; the target row's config is a whole-segment replacement, not a deep merge, so required keys must be restated when overriding.
  • The effective order is profile bundles → profile cordis.patch.yml → $DSH_HOME/cordis.patch.yml → command line --patch; the latter wins.
  • When a package has no dsh.bundle , it only becomes an ordinary dependency and does not automatically become a profile layer.

4. Host-side implementation

4.1 Function plugins

Ordinary plugins usually export:

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 imported from @deepseek-ai/schemastery (not zod);static Config = Config references the exported schema, equivalent to the official inline static Config: z<Config> = z.object({...}) .
  • inject is a required service; when it is not satisfied, the fiber remains pending, and the framework activates it after the service is ready. Do not simulate dependency injection with polling.
  • Put Config defaults in the schema; any value that may need to change per deployment should become configuration, not a source-code constant.
  • For optional services, use ctx.get() to check or ctx.inject([...], childCtx => ...) for lazy mounting; do not race ahead of sibling providers in apply() .

4.2 Service plugins

When a plugin provides a stable service, refer to host/webserver:

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; put asynchronous startup in Service.init。
  • Initialization failure should fail the fiber and be reported by the starter; do not swallow composition errors.
  • The registration method returns a disposer; the party that owns the resource is responsible for closing it.

4.3 Effect ownership

All long-lived resources must belong to the current fiber:

  • Routes, listeners, watchers, timers, React roots, DOM, sockets, and temporary services must all be cleanable.
  • Use ctx.on() or ctx.effect(() => disposer, label)。
  • The disposer order is usually: stop external entry points/unregister from the registry → wait for or cancel in-flight work → close resources.
  • When binding is needed after a service is available, use "try immediately + service event/ctx.inject retry + idempotent guard"; do not register repeatedly.

4.4 Tools

Use ctx.tools.register(defineTool(...)):

  • description Clearly state when to call it, required preconditions, failure semantics, and side effects.
  • parameters and output.schema both use the @deepseek-ai/dsh-tools value-schema DSL (after compilation it is a supported subset of JSON Schema):parameters is an implicitly open object root, required fields use inline property required: true;output.schema declares the canonical return value and is enforced by assertSupportedJsonSchema at registration time. The two are two faces of the same DSL, not two languages.
  • output.render Give the model stable, compact, decidable text.
  • Get the current session, workspace, and owner from exec.agent , not by guessing from global process state.
  • For asynchronous work, observe or forward exec.signal; write operations must have idempotency, locking, or a conflict strategy.

4.5 HTTP

  • Inject the current official Web server service, and use a structured minimal interface to reduce coupling.
  • Routes are registered via ctx.effect(() => ctx.webServer.register({ kind: 'exact' | 'prefix', path, handler })) ; duplicate (kind, path) will throw an error.
  • State interfaces should explicitly set caching policy: sensitive or real-time snapshots prefer Cache-Control: no-store, revalidatable resources use no-cache; static resources use an explicit allowlist and the correct content type.
  • Path decoding, request body parsing, and handler rejection must all be converted into explicit 4xx/5xx and must not become unhandled rejections.
  • Ownership of exact routes, longest prefixes, and fallbacks must not conflict; unknown plugin resources return 404 and do not fall through to the SPA fallback.
  • When permissions or local capabilities are involved, adopt minimal exposure, loopback/trust boundaries, and method allowlists.

4.6 Persistence and Concurrency

First determine whether to reuse the official storage/session persistence service or whether the plugin owns an independent medium. Either way:

  • Path configuration is specified explicitly; do not use process.cwd() default values scattering user data.
  • State establishes clear isolation dimensions by workspace, session, owner, or business id.
  • Read-modify-write of the same resource is serialized; concurrent creation uses no-clobber semantics.
  • Human-readable JSON must use a temporary file in the same directory + fsync + atomic publish; append logs must handle torn tails. Concurrent creation uses link()+unlink() no-clobber protocol, do not use rename() silent overwrite.
  • The Registry backend cleanup order is unregister first, then close.
  • Recovery and HMR must not assume creation events will be replayed; when needed, explicitly scan and backfill existing objects.

5. Client-side Implementation

5.1 Minimal Entry

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 merge.
  • Client registration, controller, listener, style, and DOM must all be disposed along with the client fiber.
  • Per-session state is bucketed by SessionId ; on connection reset, only resynchronize objects that have already been read.

5.2 Slot Four-Step Contract

  1. Declare: pull in types from the official package that provides the slot; only custom owners extend via module augmentation SlotMap。
  2. Claim: the parent entry's children table declares child slots; declaring means owning the render right, do not contend for someone else's seat.
  3. Register: the activation order of owners and contributors is not guaranteed, use ctx.slots.inject(key, () => ctx.slots.register({ name, children?, store?, locale?, inject?, ...kind 参数 }, Component)) to wait for declaration;children is simultaneously the claim table for child slots (claiming means owning the render right). kind parameter: keyed requires key, list requires id(can add order/label), chain requires select; single/keyed/list can add priority for cell hiding (same cell and same priority will throw an error). Registering directly to an undeclared slot will throw an error.
  4. Render: owners use renderSlot/renderSlotChain; contributors do not import the owner's implementation component.

Before choosing a seam, first check the current official types. Common session UI seams include:conversation.session.header.actions/.utilities、conversation.view、conversation.chat.node、conversation.chat.commandview、conversation.chat.assistant-actions、conversation.chat.turnTail、conversation.input.dock、conversation.composer.dock、conversation.composer.bar、conversation.input.left/conversation.input.right/conversation.input.plan/conversation.input.model. For global overlays use shell.overlay(list/root), do not touch root single slot. Do not write slot names based only on old documentation; rely on the current official ui-conversation/src/client/contract/slots.ts SlotMap.

5.3 Conversation Node

A Conversation Node is a combination of "event folding + keyed slot renderer":

  1. Define shared event types and merge them into the session event map.
  2. Put uiConversation into inject, and call ctx.uiConversation.events.register(definition):
    • match to select events;
    • start creates node state;
    • update deterministically folds by seq;
    • buildViewNode generates stable view nodes.
  3. merge ChatNodeDataMap/node kind type.
  4. To conversation.chat.node register a renderer with the same key.

Red lines:

  • Replaying the same event sequence must yield the same node, without reading time, random numbers, or current disk state.
  • match Return a stable business id and start|update role; the node engine uses conversationContextKey(kind, businessId) for deduplication within the current session. Cross-session persistent caching separately includes the owner session in the key and must not be conflated into the engine contract.
  • Events are written to the business owner session; shared host/client event files remain type-only, preferably with zero runtime imports, to avoid mutual contamination of Context augmentation across the dual tsconfig.
  • Disk/server snapshots can serve as the real-time UI source of truth; the event stream is used for conversation projection, auditing, and deterministic history. Do not confuse the responsibilities of the two.

5.4 Portal fallback

If a semantically correct slot is available, do not use a fixed portal. For app-wide overlays, prefer registering shell.overlay(list/root, click-through until your entry actively enables pointer events); only use a body portal when there truly is no global corner slot:

  • React root, host DOM, window listener, and global attributes all have disposers.
  • Follow the session list, filtering by the current owner; collapse immediately on navigation.
  • On wide screens the main column can yield; on narrow screens fall back to an overlay; rely only on stable data-*, do not couple to hashed classes.
  • Existing activities restored on first screen show only a badge, to avoid large layout shifts from auto-expanding after the first request returns; new activities that appear once stable then auto-expand.
  • The panel is limited to a portion of the container/viewport height, with the content area scrolling internally; set a separate cap for narrow screens.
  • Polling uses no-store, an in-flight guard, response shape validation, and unmount protection; on failure, retain the last successful snapshot.
  • Support keyboard,:focus-visible、aria-*, Escape, and reduced motion; hover/focus only previews, click pins the state.

6. TypeScript and Client Build

6.1 Dual tsc program

Host and client use two programs; file names can be chosen according to project layout. The official repository uses tsconfig.host.json and tsconfig.client.json as two aggregate programs for host/client checks respectively: host excludes packages/client/*/src/** and *.client.* tests; the client aggregate includes CSS module declarations, client tests, and build scripts for each client package, with shared leaves entering via project references, and each packages/client/* package also maintains its own composite tsconfig for in-package type checking. JSX uses .tsx and react-jsx; relative TS imports must be correctly rewritten to emitted JS.

This avoids declaration merge conflicts between the host session and browser runtime for Context services with the same name.

6.2 Client bundle

Prefer reusing the current official Harness client tsdown helper or a verified template; do not hand-write the loader protocol. Artifacts should be automatically wrapped by the build as:

window.__ModuleLoader__.load({ id, factory: (require) => { /* bundle */ } })

The build must preserve:

  • Both host/client halves coexist (the client build does not clear host output);
  • sourcemap;
  • CSS Modules compilation and style[data-plugin] injection;
  • Path fallback for recovering lib/ from emitted src/ resources;
  • client bundle purity gate。

6.3 Client import purity

The browser module table only answers for official platform seed modules and explicit exemptions. Rules:

  • Platform modules use the official packages/client/web/src/platform.ts and the official client build configuration shall prevail; React, Cordis, slots, web-react, primitives, attachment, schema-form, etc. are provided by the module table.
  • @deepseek-ai/dsh-client-runtime/client is a temporary exemption explicitly marked in the official build configuration, not an ordinary platform module; do not generalize it into permission to arbitrarily import runtime values.
  • Pure type imports are erased and can pull type contributions across packages.
  • Wire types, generated remote codecs, or explicitly vendored pure libraries are inlined only when the official template allows it.
  • Other cross-plugin value imports are prohibited; collaboration must go through Cordis service/remote/slot. Otherwise, either the build-time purity gate or the runtime require will fail.

7. Distribution, Installation, and Effective Boundaries

7.1 Installation

dsh plugin --profile <name> <args...> is the pnpm forwarding layer in the profile directory; after success, it reconciles the bundle list according to the installation status and dsh.bundle . Therefore, npm, paths, tarballs, and Git are supported:

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

GitHub distribution does not require publishing to npm, but a build strategy must be chosen (Git fetches source code, not build artifacts):

  • Officially recommended: provide a self-contained prepare(the official turtle-ui pattern); pnpm ≥10 blocks build scripts of Git dependencies by default, so users need to explicitly pnpm-workspace.yaml in the profile's allowBuilds and rerun add. This executes third-party code, so commits should be pinned and only reviewed repositories trusted.
  • Alternative (non-interactive installation): commit the complete, up-to-date lib/ pointed to by exports into Git; users do not need to run dependency scripts, but this is not the officially recommended path.

The README only provides recommended commands verified on a fresh profile. Restart the target profile after installation.

7.2 HMR and Restart

  • client HMR requires build watchers such as tsdown --watch to continuously rewrite lib/client.js; host HMR is only responsible for stat-detecting file changes, then triggering dispose/reload of the browser fiber via rev/SSE.
  • Only bundle content changes can trigger client HMR; changes to package manifests, exports, plugin sets, profile bundles, and host code require a restart.
  • After a normal build with no watcher, refresh the existing DSH page.
  • Do not start a standalone Vite server to replace the DSH GUI; the Web shell depends on the host-injected window.__DSH_BOOT__。

8. Verification Matrix

8.1 Baseline

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

First read package.json.scripts; do not assume all repositories have an aggregate script with the same name: the official Harness uses check:ci/check:all and multiple verify-* gates; third-party plugins may customize verify. Project-level verify/check must at least cover:

  • Pure business rules and state transitions;
  • File round-trips, locks, archiving/restoration in temporary directories;
  • Projection/folding pure functions that the client can test independently;
  • Consistency between canonical Skill and mirrors (if the project provides mirrors).

8.2 Host and Real Composition

  • Unit tests cover schemas, services, failures, and disposers.
  • Use a shared contract suite when registry/backend interfaces are available.
  • Do not just hand-roll ctx.plugin(): at least one test starts through a real Loader/patch composition and asserts the user-visible surface.
  • First use dsh plugin --profile <scratch> add <pkg> to create a non-built-in scratch profile, then run dsh --profile <scratch> --dump-configto confirm the bundle layer, line id, name, config, and injection order; built-in web/headless profile can be initialized by the launcher. Also available is --dump-default-config: prints only the bundle layer, skips the user layer and --patch, usable as recovery diagnostics when cordis.patch.yml is broken.
  • Real tasks use dsh --profile headless "一个小而可判定的任务"; do not invent dsh run subcommands.

8.3 Client

  • client tests use the jsdom lane; mount the plugin via SlotTestRuntime or minimal fake services.
  • Assert that slot registration, rendering, session isolation, connection reset, and post-dispose cleanup of registry/DOM/style/controller all hold.
  • Every registry contribution has at least one HMR/dispose safety test.
  • GUI uses an independent web profile and a real browser to verify roster, routing, interaction, refresh, wide/narrow screens, scrolling, focus, and reduced motion.

8.4 From-scratch installation and Git distribution

  1. Use a fresh temporary DSH_HOME/profile。
  2. Install using the exact commands from the README.
  3. Assert profile dependency and dsh.profile.bundles。
  4. Assert that all exports, host/client bundle, patch, and static assets exist.
  5. --dump-config The plugin layer must appear.
  6. After startup, check the host route, client roster, and real UI.

While the repository is still private, you can copy the content to be published into a temporary Git repo and commit it, then install via git+file://... ; this verifies "what Git fetches" rather than uncommitted files in the current checkout. Prerequisites:git is in PATH, the directory is a committed real Git repository; if the package declares prepare, you also need to add to the profile's pnpm-workspace.yaml the allowBuilds(same gate as §7.1). Only delete the exact temporary directories created by this task.

9. Completion criteria

Confirm each item before completion:

  • The runtime surface is minimal; manifest, exports, patch, and artifacts are consistent.
  • Required inject and optional service boundaries are clear; pending/failed states are diagnosable.
  • route, registry, timer, watcher, DOM, React root, and storage can all be cleaned up.
  • Conversation Node can be replayed deterministically, with correct owner and deduplication dimensions.
  • client imports do not cross the module table; host/client types are isolated.
  • Persistence has concurrency and crash semantics and does not depend on an incidental cwd.
  • typecheck, build, verify, real composition, from-scratch installation, and required GUI verification pass.
  • README installation commands match the actual distribution form.
  • No unauthorized commit, push, release, or visibility change was performed.