agentleFS
PerryLinkPerryLink/dsh-plugin-guide/llms-full.txt

dsh-plugin-guide

llms.txt65 starsChanged 3 months agoApache-2.0View on GitHub

DeepSeek Harness (dsh) plugin development knowledge base, in reading order. One document per section, each preceded by its source path so a citation can name the file it came from. Index: llms.txt. Repository: https://github.com/PerryLink/dsh-plugin-guide Generated by scripts/build-llms-full.mjs from 22 sources. Do not hand-edit. Your agent stops inventing DSH plugin APIs. The DeepSeek Harness plugin-development knowledge base, installed as an agent skill — official docs, a version-compatibility matrix, migration seams and battle-tested pitfalls, plus a scaffolder/checker CLI. Then just ask: "Use…

  • Reads credentials
  • Installs packages
  • Commits and pushes

We split this llms-full.txt into the rules it gives an agent

3 rules. About 0 are common, 1 is distinctive, and 2 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Data: read-only — reads its own bundled guide/ and references/ files.

    line 298
  2. Distinctive:

    Every channel resolves from this repository or from npm, so one tag reaches all of them.

    line 314
  3. Very specific:

    All PerryLink plugins are browsable in the built-in DSH Desktop Market: Market → Sources → add source → paste …

    line 478

≈ “Common” is an estimate: at least 1 in 5 of the 3,225 llms.txt files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
llms-full.txt
# dsh-plugin-guide — full knowledge base

> DeepSeek Harness (dsh) plugin development knowledge base, in reading order.
> One document per section, each preceded by its source path so a citation can
> name the file it came from. Index: llms.txt. Repository:
> https://github.com/PerryLink/dsh-plugin-guide

> Generated by scripts/build-llms-full.mjs from 22 sources. Do not hand-edit.


<!-- source: README.md -->

# [Overview] README.md

<div align="center">

# 🐳 dsh-plugin-guide

**Your agent stops inventing DSH plugin APIs.**

The DeepSeek Harness plugin-development knowledge base, installed *as an agent skill* — official docs, a version-compatibility matrix, migration seams and battle-tested pitfalls, plus a scaffolder/checker CLI.

```sh
dsh plugin --profile web add dsh-plugin-guide
```

Then just ask: *"Use the dsh-plugin-guide skill to build me a … plugin."*

[![Gitee](https://img.shields.io/badge/Gitee-mirror-c71d23?logo=gitee)](https://gitee.com/perrylink/dsh-plugin-guide)
[![dshfind](https://dshfind.com/api/badge/PerryLink/dsh-plugin-guide?metric=downloads)](https://dshfind.com/plugins/PerryLink/dsh-plugin-guide?ref=badge)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/PerryLink/dsh-plugin-guide/badge)](https://api.securityscorecards.dev/projects/github.com/PerryLink/dsh-plugin-guide)

> **Official repository.** This is the only official repository of dsh-plugin-guide, maintained by PerryLink. Same-name repositories under other accounts are not affiliated.

[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
[![DSH plugin](https://img.shields.io/badge/dsh--plugin-✅-green)](https://github.com/topics/dsh-plugin)
[![dsh-doctor](https://raw.githubusercontent.com/PerryLink/dsh-plugin-doctor/main/badges/PerryLink__dsh-plugin-guide.svg)](https://github.com/PerryLink/dsh-plugin-doctor#verified-徽章)
[![DSH Market](https://raw.githubusercontent.com/2BingLing/dsh-market/master/assets/readme/badge-top-rated.svg)](https://dsh.market/)
[![Node](https://img.shields.io/badge/node-%5E22.19%20%7C%7C%20%3E%3D24-brightgreen.svg)](#)
[![CI](https://img.shields.io/github/actions/workflow/status/PerryLink/dsh-plugin-guide/verify.yml?branch=main&label=CI)](https://github.com/PerryLink/dsh-plugin-guide/actions)
[![Version](https://img.shields.io/github/v/tag/PerryLink/dsh-plugin-guide?label=version)](https://github.com/PerryLink/dsh-plugin-guide/releases)
[![npm version](https://img.shields.io/npm/v/dsh-plugin-guide)](https://www.npmjs.com/package/dsh-plugin-guide)
[![npm downloads](https://img.shields.io/npm/dm/dsh-plugin-guide)](https://www.npmjs.com/package/dsh-plugin-guide)

[English](README.md) · [简体中文](README-zh.md) · [Español](README-es.md) · [Português](README-pt.md) · [हिन्दी](README-hi.md)

```sh
dsh plugin --profile <name> add dsh-plugin-guide
```


</div>

---

## Install & uninstall

- **npm channel** (published releases — **the default; start here**): `dsh plugin --profile web add dsh-plugin-guide`. The published tarball already contains `dist/`, so no build step runs.
- **git channel** (latest `main`, or an unreleased commit): `dsh plugin --profile web add github:PerryLink/dsh-plugin-guide#<sha>` — pin a commit for reproducibility. ⚠️ `dist/` is not committed, so pnpm must run this package's `prepare` and will refuse until the build is allowlisted; add it to the profile's `allowBuilds` (see [publish.md](references/official-docs/docs/user/develop/basic/publish.md)).
- **tarball channel**: `pnpm pack` in this repo, then `dsh plugin --profile web add ./dsh-plugin-guide-<version>.tgz`.
- **uninstall**: `dsh plugin --profile web remove dsh-plugin-guide`.

## Why not just read the official docs?

Because the official docs are written for someone who already knows which page to open, and DSH ships a **prerelease every 1.7–2.8 days** with breaking changes. This plugin closes the three gaps that costs authors the most time:

| Gap | What this plugin adds |
|---|---|
| **"Which DSH am I even targeting?"** | A [version matrix](guide/version-matrix.md): the four different values that all call themselves "latest", the frozen sub-package `latest` tags, the breaking-change seams, and how to pick a `peerDependencies` range. **There is no stable line** — every published version is a prerelease. |
| **"What does this break?"** | A [migration guide](guide/migration-0.2.md) covering `0.1.6-alpha.2` → `0.2.1-alpha.2` as **citable seams** with symptom *and* fix, each with its official source — including a data-loss hazard and a change that makes prompts fail before a model request is built. |
| **"Is my plugin actually correct?"** | A [zero-dependency CLI](SKILL.md) (`dsh-plugin-dev new / check / verify`) plus an [offline verbatim mirror](references/official-docs/) of the official docs, so your agent can cite the exact page instead of guessing. |

**Prove it works in 60 seconds:**

```sh
dsh plugin --profile web add dsh-plugin-guide
npx dsh-plugin-guide new hello-plugin      # scaffold a plugin
cd hello-plugin && npx dsh-plugin-guide check --json   # 16 static checks
```

**📖 Prefer reading it as a site?** [perrylink.github.io/dsh-plugin-guide](https://perrylink.github.io/dsh-plugin-guide/) — every chapter as its own page, plus a machine-readable [llms.txt](https://perrylink.github.io/dsh-plugin-guide/llms.txt) for assistants.

**Also on the 1024 store channel:** `npm i -g dsh1024`, then `dsh1024 plugin --profile web add dsh-plugin-guide` (counts toward the [deepseek1024.com](https://deepseek1024.com) install ranking).

<!-- star-cta -->
## ⭐ 如果它帮到了你

这个插件是 [DSH 插件家族](https://github.com/PerryLink)的一员(95 个,全部 Apache-2.0)。如果你在用,**给个 star** —— 它不会解锁任何功能,但会让下一个人在搜索里更容易找到它。

*English:* part of a 95 plugin family for DeepSeek Harness. If it is useful, **a star helps the next person find it** — nothing is gated behind it.


## What is dsh-plugin-guide?

Everything you need to build [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) plugins.

Official docs archive · Cordis primer · community deep-dives · battle-tested pitfalls · agent skill · CLI toolchain


![Workflow diagram for dsh-plugin-guide: request → policy / review → verdict.](docs/assets/dsh-plugin-guide-flow.svg)

*Workflow overview: request → policy / review → verdict. The examples below use the real command surface.*



## Comparison

![Measured comparison chart for dsh-plugin-guide](https://raw.githubusercontent.com/PerryLink/dsh-plugin-guide/main/docs/assets/dsh-plugin-guide-evidence.png)

content counts as stated in the README (What you get)

## Compatibility

| Surface | Status |
|---|---|
| Harness | DeepSeek Harness `dsh-v0.2.1-alpha.2` (`d743267388641bc76f17c45ce8b4c231aed1d32c`, the newest master tag **and** master HEAD, 2026-10-09); npm `latest` = `next` = `0.2.0-rc.2`, `alpha` = `0.2.1-alpha.2`. **There is no stable line** — all 31 npm versions and all 26 GitHub Releases are prereleases. The official-docs mirror is pinned to that same commit (429 md pages, re-synced 2026-10-13; see [references/official-docs/SNAPSHOT.md](references/official-docs/SNAPSHOT.md)). `guide/migration-0.2.md` covers `0.1.6-alpha.2` → `0.2.1-alpha.1` in §0–§12 and `0.2.1-alpha.1` → `0.2.1-alpha.2` as 19 citable seams in §13; the 20 breaking-change guides added since the previous baseline (19 under `v0.2.1-alpha.1` plus one backfilled into `v0.2.0-rc.2`) are all in the mirror. |
| Node | `^22.19.0 || >=24.0.0` (DeepSeek Harness runtime) |
| Platforms | All (plain ESM bundle; no native code, no network) |
| Model | Any (no model interaction) |

## What you get

`dsh-plugin-guide` is the DSH plugin-development knowledge base plus a CLI toolchain, packaged as one installable bundle. The knowledge base registers as the `dsh-plugin-guide` agent skill (visible in every session catalog, loading workflow steps, official docs, and community deep-dives on demand); the `dsh-plugin-dev` CLI adds three mechanical layers on top of it.

- **Plugin contract & hard rules** — effects/disposers, waterfall `next()`, host-owned session events, `MessageSourceMap`, the weakest-mechanism ordering, Schemastery config, UI and performance rules.
- **Official docs archive** — a verbatim copy of the official repo docs (EN + ZH), byte-identical to upstream at the last verified snapshot, including the pages that never reach the documentation site.
- **Version adaptivity** — [guide/version-matrix.md](guide/version-matrix.md): the five different "latest" values, the frozen sub-package `latest` tags, the four breaking-change seams, the canonical peer range and how to pick one, the session-format lineage V0→V4, and the 19 breaking changes that landed in `0.2.1-alpha.2`.
- **Migration guide** — [guide/migration-0.2.md](guide/migration-0.2.md): every breaking change from `0.1.6-alpha.2` to `0.2.1-alpha.1` (session format V4, the settings rebuild, invariant removal, `readBytes`, new slots, retired `plugin` message source), each with its official source and a 12-item checklist.
- **Official in-repo skill, distilled** — [references/official-plugin-dev-skill.md](references/official-plugin-dev-skill.md): the harness's own `cordis-plugin-development` workflow, prohibitions, display-metadata checklist, practices, and UI/performance rules.
- **Cordis primer** — the five concepts and the mechanism timeline (repository-plugin introduced 0809, removed 0811; the two install channels).
- **Verified issue index** — [guide/unfixed-issues.md](guide/unfixed-issues.md): 22 unfixed + 11 newly verified items with `path:line`, workarounds, fixing commits, design-vs-bug table, and an explicit "unreproducible" section.
- **30+ real-world pitfalls** with root cause + fix (cordis dual copies, tsconfig trio, multi-frame zstd sessions, Windows junctions, stale npm `latest`, `file:` installs loading a copy, live patch writes, …).
- **Community deep-dives** — 114 community repositories archived (15 deep-dived), plus a full source index where every fact links to its origin.
- **CLI toolchain** — `dsh-plugin-dev new / check / verify`: scaffold, static-check (including display metadata and multi-patch manifests), and pack-verify DSH plugins; every check links back to the skill section it enforces.
- **Adoption levels (non-official)** — [guide/standards.md](guide/standards.md): the DSH-ENTRY / DSH-FLOW / DSH-BOOST model, the two-axis (depth × evidence) table, what each level does *not* claim, and how to move up. The GB / GB-T / GB-Z comparison is an analogy only; certification grades A–D are orthogonal.

## Knowledge base

| Path | What it is |
|---|---|
| `SKILL.md` | The `dsh-plugin-guide` agent skill: hard rules + task-based development paths |
| `package.json` · `cordis.patch.yml` · `index.js` · `locale/` · `icon.svg` | The installable DSH bundle: `dsh.bundle.patch` manifest, entry point that registers the skill, and the display metadata Plugin Manager reads |
| `guide/plugin-dev-guide.md` | The complete development guide (10 chapters) |
| `guide/version-matrix.md` | **Version adaptivity matrix**: the five "latest" values, frozen sub-package `latest` tags, breaking-change seams, peer-range selection, session-format lineage, and the 19 changes in `0.2.1-alpha.2` |
| `guide/migration-0.2.md` | Breaking-change migration guide for the 0.2 generation |
| `guide/unfixed-issues.md` | Source-verified bug index + fixed-on-master list + design-vs-bug table |
| `guide/quick-reference.md` | One-page cheat sheet (5 languages) |
| `guide/links.md` | Curated URL index: official dev docs (site ↔ local copies) + community doc links + repo-only pages |
| `guide/when-to-retire-a-plugin.md` | Retiring and freezing plugins: the criteria and the measured data behind a portfolio audit (EN + ZH) |
| `guide/choosing-a-plugin.md` | Choosing a plugin: who leads each capability surface, with the measured numbers and the verification caveats (EN + ZH) |
| `references/official-docs/` | Verbatim copy of the official repo docs (EN + ZH) |
| `references/official-plugin-dev-skill.md` | Distilled official in-repo plugin-development skill |
| `references/*.md` | Research reports: repo docs, website, Cordis, the paper, community ecosystem, 114-repo archive (15 deep-dived) |
| `scripts/` | Idempotent download scripts + integrity checker + topic snapshot generator |
| `bin/` · `src/cli/` · `dist/` | The `dsh-plugin-dev` CLI: scaffolder, checker, verifier (TypeScript, tsdown-bundled) |
| `templates/` | TS + JS scaffold skeletons: contract template, Config, tests, cordis.patch.yml, locale metadata, icon, five-language READMEs |
| `downloads/` | Raw snapshots — generated by `scripts/`, not committed |

## CLI toolchain

The bundle ships the zero-runtime-dependency `dsh-plugin-dev` CLI (`bin/` → tsdown-bundled `dist/dsh-plugin-dev.js`). Each check cites the skill section it enforces, so an agent can keep auditing manually.

```sh
dsh-plugin-dev new <name> [--lang ts|js] [--dir <path>] [--force] [--git]
dsh-plugin-dev check [--cwd <dir>] [--json] [--strict]
dsh-plugin-dev verify [--cwd <dir>] [--dsh <bin>] [--pnpm <bin>]
dsh-plugin-dev seams <query> | --since <version>   # is this API in my target host?
```

| Subcommand | What it does |
|---|---|
| `new <name>` | Scaffolds a TS or JS plugin repo: `src/index.ts` contract template, Schemastery Config, tests, tsdown/vitest, commented `cordis.patch.yml`, five-language READMEs. Idempotent; refuses non-empty targets without `--force`. |
| `check` | Static checks: `cordis.patch.yml` validity, `package.json` metadata (`dsh.bundle.patch` pointer, peer deps, engines, files whitelist), five-language README consistency, engineering red-line patterns. Emits CI-consumable JSON. |
| `verify` | `pnpm pack`, then install/start/uninstall the bundle in a clean mkdtemp `DSH_HOME` profile (aligned with `verify:self-contained`). Failures report the log tail plus suggestions. |

### CLI configuration

The CLI has no hardcoded tunables — each is a flag or an environment variable.

| Tunable | Flag | Env | Default |
|---|---|---|---|
| Templates directory | — | `DSH_PLUGIN_DEV_TEMPLATES` | `<package>/templates` |
| dsh binary | `--dsh` | `DSH_PLUGIN_DEV_DSH` | `dsh` |
| pnpm binary | `--pnpm` | `DSH_PLUGIN_DEV_PNPM` | `pnpm` |
| Install/pack timeout | `--timeout` | `DSH_PLUGIN_DEV_TIMEOUT` | `300000` ms |
| Headless smoke timeout | `--smoke-timeout` | `DSH_PLUGIN_DEV_SMOKE_TIMEOUT` | `120000` ms |

### Upstream roadmap

`dsh-plugin-dev` is an upstream candidate for the official plugin-development CLI (planned item C12): the scaffolder/checker/verifier are the mechanical layers, while `SKILL.md` + `guide/` stay the cognitive layer.

## Quick start

```sh
# 1. install the bundle into your profile — npm first
dsh plugin --profile web add dsh-plugin-guide

# 2. restart and verify the row
dsh --profile web --dump-config | grep -A3 'id: dsh-plugin-guide'
```

⚠️ **Use the npm channel, not the git channel, for a first install.** The git channel pulls source, and
`dist/` is not committed, so pnpm has to run this package's `prepare` build script — which pnpm 10+ blocks
until you explicitly allowlist it (`allowBuilds`). That is a documented pitfall of git installs, not a
defect, but it makes the git channel a poor first experience. See [Install & uninstall](#install--uninstall)
for the git and tarball channels when you specifically want an unreleased commit.

Then just ask your agent: *"Use the dsh-plugin-guide skill to build me a … plugin."*

Or drive the CLI directly:

```sh
npx dsh-plugin-guide new hello-plugin            # scaffold a TS plugin repo
npx dsh-plugin-guide check --json                # static-check it
npx dsh-plugin-guide verify                      # pack + clean-profile smoke
```

## Copy as a plain agent skill

You can also copy the whole folder into your agent's skill directory (relative paths stay intact):

```powershell
# Windows (PowerShell)
pwsh -File scripts/install-skill.ps1 `
  -Target "$env:USERPROFILE\.deepseek\skills\dsh-plugin-guide"   # or <project>\.agents\skills\dsh-plugin-guide
```

```bash
# macOS / Linux
pwsh -File scripts/install-skill.ps1 -Target ~/.deepseek/skills/dsh-plugin-guide   # or <project>/.agents/skills/dsh-plugin-guide
```

The installer skips `downloads/` (generated) and `.github/`, then verifies every copied file byte-for-byte. A manual `Copy-Item -Recurse` of the whole folder also works.

### Examples

These are the plugin's real entry points; install once, then call one.

#### Detailed example

**Command**

~~~sh
dsh-plugin-dev seams L3
~~~

**Output**

~~~text
1 seam(s):

L3  webRuntime service replaced by webStartup
    kind        service-renamed
    lands in    0.2.1-alpha.2
    confidence  documented
    official    docs/upgrade-guide/v0.2.1-alpha.1/web-listener-trust-config/guide.md
    removed     service webRuntime; row web-runtime; ctx.webRuntime.lanAddresses; wildcard --host
    instead     webStartup (row web-startup), concrete host literals
    detectable  yes — inspect: src/**/*.ts, cordis.patch.yml
~~~

*Observed from `README.md:152 (CLI toolchain); src/cli/commands/seams.ts:54; data/seams/index.json:44`*

#### Failure branch

**Input**

~~~text
node bin/dsh-plugin-dev.js seams no-such-seam-xyz
~~~

**Output**

~~~text
no seam or capability matches "no-such-seam-xyz". This is an absence of evidence in the knowledge base, not evidence that the API is absent from your target host — check references/official-docs/ before concluding either way.
~~~

*Observed from `plugins/dsh-plugin-guide/src/cli/lib/seams.ts:122-130; plugins/dsh-plugin-guide/src/cli/commands/seams.ts:87-92; plugins/dsh-plugin-guide/tests/seams.test.ts:88-90`*

#### Brief examples



## Configuration

The skill bundle exposes no Schemastery `Config` — it registers the knowledge base as an agent skill with no tunable keys. The `dsh-plugin-dev` CLI reads its tunables from flags and `DSH_PLUGIN_DEV_*` environment variables (see [CLI toolchain](#cli-toolchain)).

## Tools & surfaces

| Surface | Kind | Notes |
|---|---|---|
| `dsh-plugin-guide` | skill | Registered via `ctx.skills`; loads `SKILL.md` + `./guide/` + `./references/` on demand |
| `dsh-plugin-dev` | bin (CLI) | `new` / `check` / `verify` subcommands; not a DSH plugin row |

## Permissions & data

- **Permissions**: declares `filesystem:read` in its workshop manifest.
- **Data**: read-only — reads its own bundled `guide/` and `references/` files. No network requests, no writes, no model calls.

## Security boundaries

- **Read-only knowledge base.** The bundle only reads its own files; it never writes, never calls the network, and never invokes a model.
- **Official docs are verbatim copies.** `references/official-docs/` is never edited here; report issues upstream and re-sync only with `scripts/sync-official-docs.ps1`.
- **Distribution boundaries.** Bundled third-party content keeps its upstream license; see [NOTICE.md](NOTICE.md) (e.g. `downloads/` is local-only; `awesome-dsh-plugins` must not be redistributed).

## Known limitations

- **Official docs are a snapshot.** Re-sync with `scripts/sync-official-docs.ps1` when upstream moves; the freshness stamp and commit hash reference `references/official-docs/SNAPSHOT.md`.
- **`downloads/` is generated, not committed.** Raw snapshots (community repo archives, Discussions, articles) must be generated with the scripts before use.
- **`awesome-dsh-plugins` content is local-only.** Its upstream declares an internal-use constraint, so it is not redistributed with the repo.

## Release channels

Every channel resolves from this repository or from npm, so one tag reaches all of them; only the Gitee mirror is a scheduled copy:

| Channel | Source of truth | How it updates |
|---|---|---|
| npm (`dsh-plugin-guide`) | this repo's `package.json` | `.github/workflows/release.yml` on a `v*` tag: `verify-kit` gate → CHANGELOG section check → `npm publish --provenance` (skipped when the version already exists) |
| GitHub Release | the same tag | the same workflow creates it idempotently from the tag |
| Discovery badges (npm version, GitHub tag, DSH Market, dsh-doctor) | live lookups | nothing to publish — they read npm/the registry on each render |
| Gitee mirror | this repo | a **read-only scheduled mirror**, not a publish target: it is force-aligned to the GitHub tip (`--force-with-lease`) by the family's `gitee-sync` workflow, so `main` is normally one scheduled run behind. Gitee never receives a tag push from here; compare `git ls-remote` against GitHub before reporting the mirror stale |
| DSH Desktop Market / dsh1024 | npm | the market installs the npm package, so a published version is immediately installable; no per-version submission |

## Keeping it fresh

```sh
pwsh -File scripts/sync-official-docs.ps1                     # verbatim docs copy from a local checkout
pwsh -File scripts/download-sources.ps1                       # official site/docs, Cordis, paper
pwsh -File scripts/download-community-repos.ps1               # community repositories (codeload tarballs)
pwsh -File scripts/download-community-articles.ps1            # zh/en/HN community articles
pwsh -File scripts/archive-discussions.ps1                    # official Discussions (needs $env:GH_TOKEN)
pwsh -File scripts/gen-topic-snapshot.ps1 -OutDir <dir>       # dsh-plugin topic census
pwsh -File scripts/verify-kit.ps1 -Checkout <checkout>        # critical paths + link scan + docs drift
```

## Development

The skill bundle (`index.js`) is plain ESM with no build step; the `dsh-plugin-dev` CLI is TypeScript built by tsdown. Gates:

```sh
pnpm install --frozen-lockfile
pnpm run typecheck && pnpm run typecheck:ci
pnpm test
pnpm run build
pnpm run verify:artifacts        # dogfood self-check + scaffold smoke (no network)
pnpm run verify:self-contained   # pack + clean-profile install/start/uninstall smoke
pnpm run check:readmes           # five READMEs share section structure
pnpm run check:parity            # translations have identical block counts
pnpm run check:facts             # self-stated numbers match data/facts.json
pnpm run check:ps1-bom           # PowerShell scripts keep their UTF-8 BOM
pnpm pack
pwsh -File scripts/verify-kit.ps1   # critical paths + link scan (+ docs drift with -Checkout <checkout>)
```

## Topics

`dsh`, `deepseek-harness`, `dsh-plugin`, `cordis`, `agent-skill`, `plugin-development`, `knowledge-base`, `cli`, `scaffold`, `checker`

## Contributors

- [PerryLink](https://github.com/PerryLink) — creator and maintainer: knowledge-base content, the installable-bundle transformation, ecosystem submissions, and community engineering.
- [@Yisa-yi](https://github.com/Yisa-yi) — **product manager**. Took part in the whole design arc, from the first outline to the shipped bundle, and owned the shape of the product rather than its code: the call to ship it as an installable bundle that registers an on-demand agent skill (the docs site renders those same sources rather than carrying a separate content set), the information architecture of the 10-chapter guide, the "every fact links to its origin" rule, and the split between the cognitive layer (`SKILL.md` + `guide/`) and the mechanical layer (`dsh-plugin-dev new / check / verify`). Set the evidence standard behind `guide/unfixed-issues.md` — `path:line` against a named host commit, a design-vs-bug table, and an explicit unreproducible section — and the freeze/retire criteria in `guide/when-to-retire-a-plugin.md`. Held the scope line (read-only, no network, no model calls, no tunable keys) and the five-language README parity requirement.
- Day-to-day maintenance is assisted by DeepSeek Harness agents (they hold no GitHub account and are listed here for transparency, not as contributors).

## PerryLink DSH Plugin Family

This project is part of the DeepSeek Harness plugin family from [PerryLink](https://github.com/PerryLink). The roster stays collapsed below so every README keeps a short first screen; expand it to see every row and its status.

<details>
<summary>Expand all plugins</summary>

| Plugin | Status |
|---|---|
| **[dsh-aqua-input-check](https://github.com/PerryLink/dsh-aqua-input-check)** |  |
| **[dsh-archive-check](https://github.com/PerryLink/dsh-archive-check)** |  |
| **[dsh-auto-review](https://github.com/PerryLink/dsh-auto-review)** |  |
| **[dsh-autotier](https://github.com/PerryLink/dsh-autotier)** |  |
| **[dsh-bid-ca-precheck](https://github.com/PerryLink/dsh-bid-ca-precheck)** |  |
| **[dsh-bid-qual-check](https://github.com/PerryLink/dsh-bid-qual-check)** |  |
| **[dsh-cert-mcp](https://github.com/PerryLink/dsh-cert-mcp)** |  |
| **[dsh-checkpoint-rewind](https://github.com/PerryLink/dsh-checkpoint-rewind)** |  |
| **[dsh-click](https://github.com/PerryLink/dsh-click)** |  |
| **[dsh-composer-history](https://github.com/PerryLink/dsh-composer-history)** |  |
| **[dsh-contract-stance](https://github.com/PerryLink/dsh-contract-stance)** |  |
| **[dsh-customs-doc-check](https://github.com/PerryLink/dsh-customs-doc-check)** |  |
| **[dsh-data-quality](https://github.com/PerryLink/dsh-data-quality)** |  |
| **[dsh-demurrage-ledger](https://github.com/PerryLink/dsh-demurrage-ledger)** |  |
| **[dsh-doublecheck](https://github.com/PerryLink/dsh-doublecheck)** |  |
| **[dsh-drill-script-check](https://github.com/PerryLink/dsh-drill-script-check)** |  |
| **[dsh-eia-guide-check](https://github.com/PerryLink/dsh-eia-guide-check)** |  |
| **[dsh-emergency-plan](https://github.com/PerryLink/dsh-emergency-plan)** |  |
| **[dsh-essay-rubric-check](https://github.com/PerryLink/dsh-essay-rubric-check)** |  |
| **[dsh-evidence-check](https://github.com/PerryLink/dsh-evidence-check)** |  |
| **[dsh-export-ctl-check](https://github.com/PerryLink/dsh-export-ctl-check)** |  |
| **[dsh-fast](https://github.com/PerryLink/dsh-fast)** |  |
| **[dsh-fmea-table-check](https://github.com/PerryLink/dsh-fmea-table-check)** |  |
| **[dsh-forecast-penalty](https://github.com/PerryLink/dsh-forecast-penalty)** |  |
| **[dsh-fund-research](https://github.com/PerryLink/dsh-fund-research)** |  |
| **[dsh-github](https://github.com/PerryLink/dsh-github)** |  |
| **[dsh-gongwen-flow-check](https://github.com/PerryLink/dsh-gongwen-flow-check)** |  |
| **[dsh-gongwen-word-check](https://github.com/PerryLink/dsh-gongwen-word-check)** |  |
| **[dsh-guard-plan-qc](https://github.com/PerryLink/dsh-guard-plan-qc)** |  |
| **[dsh-hazchem-check](https://github.com/PerryLink/dsh-hazchem-check)** |  |
| **[dsh-hazplan-check](https://github.com/PerryLink/dsh-hazplan-check)** |  |
| **[dsh-hidden-risk-map](https://github.com/PerryLink/dsh-hidden-risk-map)** |  |
| **[dsh-hs-classify](https://github.com/PerryLink/dsh-hs-classify)** |  |
| **[dsh-icd-rule-check](https://github.com/PerryLink/dsh-icd-rule-check)** |  |
| **[dsh-industry-research](https://github.com/PerryLink/dsh-industry-research)** |  |
| **[dsh-lawcite-adapter](https://github.com/PerryLink/dsh-lawcite-adapter)** |  |
| **[dsh-laya](https://github.com/PerryLink/dsh-laya)** |  |
| **[dsh-lc-doc-check](https://github.com/PerryLink/dsh-lc-doc-check)** |  |
| **[dsh-learning-gap-check](https://github.com/PerryLink/dsh-learning-gap-check)** |  |
| **[dsh-library](https://github.com/PerryLink/dsh-library)** |  |
| **[dsh-local-ai](https://github.com/PerryLink/dsh-local-ai)** |  |
| **[dsh-lsp-actions](https://github.com/PerryLink/dsh-lsp-actions)** |  |
| **[dsh-mask](https://github.com/PerryLink/dsh-mask)** |  |
| **[dsh-mcp-panel](https://github.com/PerryLink/dsh-mcp-panel)** |  |
| **[dsh-medrec-qc](https://github.com/PerryLink/dsh-medrec-qc)** |  |
| **[dsh-memento](https://github.com/PerryLink/dsh-memento)** |  |
| **[dsh-nsfc-form-check](https://github.com/PerryLink/dsh-nsfc-form-check)** |  |
| **[dsh-nurse-record-check](https://github.com/PerryLink/dsh-nurse-record-check)** |  |
| **[dsh-observe](https://github.com/PerryLink/dsh-observe)** |  |
| **[dsh-origin-rvc-check](https://github.com/PerryLink/dsh-origin-rvc-check)** |  |
| **[dsh-ota-review-check](https://github.com/PerryLink/dsh-ota-review-check)** |  |
| **[dsh-output-styles](https://github.com/PerryLink/dsh-output-styles)** |  |
| **[dsh-paper-doc-adapter](https://github.com/PerryLink/dsh-paper-doc-adapter)** |  |
| **[dsh-permission-rules](https://github.com/PerryLink/dsh-permission-rules)** |  |
| **[dsh-permit-report-check](https://github.com/PerryLink/dsh-permit-report-check)** |  |
| **[dsh-pipeline-check](https://github.com/PerryLink/dsh-pipeline-check)** |  |
| **[dsh-pleading-draft](https://github.com/PerryLink/dsh-pleading-draft)** |  |
| **[dsh-plugin-doctor](https://github.com/PerryLink/dsh-plugin-doctor)** |  |
| **[dsh-plugin-guide](https://github.com/PerryLink/dsh-plugin-guide)** |  |
| **[dsh-plugin-kit](https://github.com/PerryLink/dsh-plugin-kit)** |  |
| **[dsh-plugin-upgrade](https://github.com/PerryLink/dsh-plugin-upgrade)** |  |
| **[dsh-policy-brief-draft](https://github.com/PerryLink/dsh-policy-brief-draft)** |  |
| **[dsh-power-loss-split](https://github.com/PerryLink/dsh-power-loss-split)** |  |
| **[dsh-power-ticket-check](https://github.com/PerryLink/dsh-power-ticket-check)** |  |
| **[dsh-ppap-check](https://github.com/PerryLink/dsh-ppap-check)** |  |
| **[dsh-protest-deadline](https://github.com/PerryLink/dsh-protest-deadline)** |  |
| **[dsh-railway-window](https://github.com/PerryLink/dsh-railway-window)** |  |
| **[dsh-repair-order-qc](https://github.com/PerryLink/dsh-repair-order-qc)** |  |
| **[dsh-research-report](https://github.com/PerryLink/dsh-research-report)** |  |
| **[dsh-review-reply-check](https://github.com/PerryLink/dsh-review-reply-check)** |  |
| **[dsh-rulefile-check](https://github.com/PerryLink/dsh-rulefile-check)** |  |
| **[dsh-safety-brief-check](https://github.com/PerryLink/dsh-safety-brief-check)** |  |
| **[dsh-score](https://github.com/PerryLink/dsh-score)** |  |
| **[dsh-session-sync](https://github.com/PerryLink/dsh-session-sync)** |  |
| **[dsh-site-log-check](https://github.com/PerryLink/dsh-site-log-check)** |  |
| **[dsh-skill-pack-security](https://github.com/PerryLink/dsh-skill-pack-security)** |  |
| **[dsh-soe-decision-check](https://github.com/PerryLink/dsh-soe-decision-check)** |  |
| **[dsh-soilwater-check](https://github.com/PerryLink/dsh-soilwater-check)** |  |
| **[dsh-sop-sync-check](https://github.com/PerryLink/dsh-sop-sync-check)** |  |
| **[dsh-spc-gbt-adapter](https://github.com/PerryLink/dsh-spc-gbt-adapter)** |  |
| **[dsh-talk](https://github.com/PerryLink/dsh-talk)** |  |
| **[dsh-tender-extract](https://github.com/PerryLink/dsh-tender-extract)** |  |
| **[dsh-tender-matrix](https://github.com/PerryLink/dsh-tender-matrix)** |  |
| **[dsh-test-drive](https://github.com/PerryLink/dsh-test-drive)** |  |
| **[dsh-ticktick](https://github.com/PerryLink/dsh-ticktick)** |  |
| **[dsh-translate](https://github.com/PerryLink/dsh-translate)** |  |
| **[dsh-warranty-calc](https://github.com/PerryLink/dsh-warranty-calc)** |  |
| **[dsh-budget](https://github.com/PerryLink/dsh-budget)** | 🧊 FROZEN — see the repo README |
| **[dsh-claude-move](https://github.com/PerryLink/dsh-claude-move)** | 🧊 FROZEN — see the repo README |
| **[dsh-defend](https://github.com/PerryLink/dsh-defend)** | 🧊 FROZEN — see the repo README |
| **[dsh-draw](https://github.com/PerryLink/dsh-draw)** | 🧊 FROZEN — see the repo README |
| **[dsh-reach](https://github.com/PerryLink/dsh-reach)** | 🧊 FROZEN — see the repo README |
| **[dsh-background-agents](https://github.com/PerryLink/dsh-background-agents)** | 🚫 RETIRED — see the repo README |
| **[dsh-session-pin](https://github.com/PerryLink/dsh-session-pin)** | 🚫 RETIRED — see the repo README |
| **[dsh-team-rooms](https://github.com/PerryLink/dsh-team-rooms)** | 🚫 RETIRED — see the repo README |

</details>

## Disclaimer

Community-maintained, **not** an official DeepSeek product. DeepSeek Harness is in developer preview and ships breaking changes; when in doubt, the official docs in `references/official-docs/` are the source of truth.

### Install from the DSH Desktop Market

All PerryLink plugins are browsable in the built-in DSH Desktop Market: **Market → Sources → add source → paste** `https://perrylink-dsh-catalog.perrylink.workers.dev/catalog-source.json` **→ select it**. Installation still goes through the Market's npm-identity verification and your confirmation.

## License

[Apache License 2.0](LICENSE) © 2026 dsh-plugin-guide contributors — our own text (`SKILL.md`, `guide/`, `references/`, `scripts/`, this README) is Apache-2.0; bundled third-party content is documented in [NOTICE.md](NOTICE.md).


<!-- source: SKILL.md -->

# [Agent skill: contract red lines and task-based development paths] SKILL.md

---
name: dsh-plugin-guide
description: Use when developing, reviewing, packaging, debugging, or answering questions about DeepSeek Harness (DSH) plugins — the plugin-based agent harness on vendored Cordis. Applies the official plugin-development constraints (plugin contract, cordis.yml layers, services/events/effects, tool DSL, bundles/profiles) backed by the dsh-plugin-guide knowledge base.
---

# DeepSeek Harness 插件开发(dsh-plugin-guide)

依据官方资料开发 DeepSeek Harness 插件。本技能是**工作流与约束清单**;事实细节一律引用知识库原文,不凭记忆编造。所有"必须/不得"条款来自官方仓库 `AGENTS.md`、`docs/` 与文档站,冲突时以官方原文为准。

## 知识库位置(按顺序找,用第一个存在的)

本技能与知识库随同一目录分发(本 SKILL.md 所在目录即知识库根),路径均为相对路径;单独复制本文件而不带 `guide/`、`references/` 时按回退路径找。以 `dsh-plugin-guide` 插件(bundle)安装时,技能 resourceBase 即包目录,下文的 `./guide/`、`./references/` 相对路径由 DSH 的 `skill` 工具按此目录解析:

1. **本文件同目录**(= 插件包目录 `dsh-plugin-guide`,或 `scripts/install-skill.ps1` 安装的独立技能目录):`./guide/`(综合指南+速查表+文档链接索引)、`./references/`(调研报告与官方文档全文副本 `references/official-docs/docs/`)、`./downloads/`(原始下载物,可选,需按 §知识库维护 的脚本生成)
2. 官方仓库 checkout:`D:\deepseek-harness\`(示例路径,按本机实际安装位置调整;`docs/`、`vendor/cordis/`、`packages/`、`examples/`)
3. 线上:https://github.com/deepseek-ai/deepseek-harness 、 https://deepseek-harness.github.io/deepseek-harness/develop/basic/ 、 https://github.com/cordiverse/cordis

下文相对路径默认相对上述第 1 条(本技能文件所在目录)。

## 开发前置(第一步必做)

0. **先认版本**:跑 `dsh --version`,然后按下表决定读什么。**不要跳过这一步**——本库 19 篇文档里对当前宿主不适用的部分不会自己标注。
   | 你的 `dsh --version` | 含义 | 读什么 |
   |---|---|---|
   | `< 0.1.7-alpha.1` | 旧基线 | 本世代破坏性变更**全部适用** → `guide/migration-0.2.md` §0–§12 逐条改 |
   | `0.1.7-alpha.1` – `0.2.1-alpha.1` | 中间线 | 会话格式 V4 已生效;§13 的 19 条**不适用** |
   | `0.2.1-alpha.2` 或更新 | 最新线 | §13 的 19 条**适用** → `guide/version-matrix.md` §3 |
   | **npm `latest`(= `0.2.0-rc.2`)** | **默认安装拿到的就是这一版** | ⚠️ 它**早于** master 一版:§13 不适用,但 `version-matrix.md` §1.1 的"不存在稳定版"警告适用 |
   **四个"最新"不是同一个东西**:npm `latest`=`next`=`0.2.0-rc.2`、npm `alpha`=`0.2.1-alpha.2`、master 最新 tag = `dsh-v0.2.1-alpha.2`。**不存在稳定版**(全部 31 个 npm 版本都是 prerelease)。完整谱系、子包 `latest` 冻结陷阱与 peer 区间选择见 `guide/version-matrix.md`。
1. 若未读过 Cordis 概念:读 `references/official-docs/docs/cordis-primer.md`(5 个概念,5 分钟);需要动手跟练时跑 `references/official-docs/docs/cordis-tutorial/` 01-07(无 API key 可跑)。
2. 打开 `guide/quick-reference.md`(契约速查)+ `guide/plugin-dev-guide.md`(完整路径)。官方/社区文档 URL 对照见 `guide/links.md`。排查官方运行时行为/未修复 bug 时查 `guide/unfixed-issues.md`(master 基线源码核实的问题清单 + 已修复提交号 + 设计行为对照 + 本世代新核实的 T1–T11)。决定一个插件**该不该继续维护**(官方已进内核 / 第三方采用度显著更高)时查 `guide/when-to-retire-a-plugin.md`(判据 + 实测倍数表);判断某个能力面谁领先时查 `guide/choosing-a-plugin.md`。
2b. **要判断"该改哪一行 / 该找哪个工具"时**,读 `guide/family-toolchain.md`(家族工具链路由:问题→所有者→可引用产物;`dsh-plugin-doctor` 33 项检查全表与裁定语义;本库 16 项检查与 doctor 的交叉对照)。发布流水线(多插件组合的持续可安装/可发布/可升级)见 `guide/release-engineering.md`。
2c. **要回答"这个插件算哪一级 / 怎么升级"时**,读 `guide/standards.md`(DSH-ENTRY / DSH-FLOW / DSH-BOOST:每层回答什么、需要什么证据、不声称什么、怎么自检;"采用深度 × 证据状态"两轴;ENTRY → FLOW → BOOST 的升级路径)。中文稿 `guide/standards.zh-CN.md`。**三层非官方、非认证**,A–D 认证等级与三层正交;`static` 不是 `runtime`,`runtime` 不是 UI 已渲染,`not-measured` 不是通过。
3. 确认目标扩展点:读 `references/official-docs/docs/architecture.md` 的「Where new behavior goes」表与 `references/official-docs/docs/cookbook/extension-cookbook.md` 的 feature→mechanism 表——**新行为必须挂到已文档化扩展点,不得改 agent-loop**;**用最弱的够用机制**(restrict < guard < waterfall 改写 < system-prompt/assemble)。
4. 要写 UI / 要让 agent 自己装插件时,读 `references/official-plugin-dev-skill.md`(官方自带技能的要点与硬性规定)。

## 必须遵守的插件契约(官方红线,逐条核对)

- 插件 = 模块导出 `name` + `apply(ctx, config)`(+可选 `inject: string[]`);依赖的服务在 `apply` 前就绪;依赖服务消失会自动卸载、恢复后自动重载。
- **注册即 effect**:一切贡献走 `ctx.effect()` / `ctx.on()` / 服务 `register()`(返回 disposer);绝不手动 removeListener/clearInterval 式收尾。注册在别的 ctx 上(如 `agent.ctx`)的,**两个拥有者都要留 disposer**。
- **waterfall 监听器必须调用 `next()`**;不调=故意短路(拦截语义)。改写 `agent/pre-step` 决策要**展开**(`{ ...decision, messages }`),否则 `startsRequestSeries` 之类字段会丢。`emit/waterfall/parallel/serial/bail` 语义见速查表。
- **扩展点用"最弱的够用机制"**:`ctx.tools.restrict()`(只移除)< `ctx.tools.guard()`(只拒绝)< waterfall 改写(依赖顺序)< `system-prompt/assemble`(整段替换)。机制越强,越要替其他插件保留贡献。结果只看 `tools/result`;只有要变换结果才用 `tools/post-execute`。
- **模型可见 ⟺ 已记录**:进入模型请求的一切必须能从会话日志重建;新增模型可见输入必须新增 `SessionEventMap` 会话事件(**由宿主侧新增**,见下一条)。
- **绝不要用新的 `type` 追加会话事件**:读方只在事件带信封 `ignorable: true` 时接受未知类型,而运行时 `Session.append()` 写不了该标记——写了会让**整个会话打不开**。插件状态从既有事件推导,或放进 inspection 找到的 storage 服务。
- **`agent.inject()` 的 `source` 不再接受 `{ kind: 'plugin', plugin: '<name>' }`**(会话格式 V4 在消息准入处拒绝):先 `declare module '@deepseek-ai/dsh-llm' { interface MessageSourceMap { '<your-kind>': { kind: '<your-kind>' } } }` 再用;`inject` 不唤醒(要唤醒用 `followup()` / `steer()`)。
- 类型安全事件/服务用 declaration merging(`declare module '@deepseek-ai/cordis'`);事件文档标注 `@mode`。
- 配置用 Schemastery `Schema<Config>`(禁止普通对象);非法配置加载期响亮失败;**不得硬编码可调参数**(判断:cordis.yml 能否改)。要暴露给用户的实时字段用 `Volatile<T>` + `loader/volatile-update`(表单命名空间 = profile 条目 id;settings 已在 0.2 世代重写)。
- 工具走 `defineTool`:`execute` 只返回 `output.schema` 声明的规范 JSON 值;尊重 `exec.signal`;人类可读内容放 `output.render`;UI 卡片 presenter 是**纯函数**(禁 I/O/时钟/随机)。工具 `parameters` 是 **DSH 自己的方言,不是 JSON Schema**;可选钩子 `projectContent()`、`ToolSchema.deferLoading`、`ask.displayReason`。
- **可选服务用 `ctx.get(name)`**;`ctx.<service>` 只对你自己 `inject` 里声明过的服务合法(写未声明的 `ctx` 属性会抛错并可能静默杀掉整个插件的激活)。
- 可替换能力按三层接缝设计:Service Definition / Provider / Consumer;不提前拆。
- 打包:bundle 清单 `"dsh":{"bundle":{"patch":"..."}}`(`patch` **可为有序数组**,每个文件的相对路径按各自目录解析);覆盖按 `id` 整行替换 config(上游删行后 override **静默失效**,升级后跑 `--dump-config` 查 `patch: entry not found`);`!!js`(双感叹号);git 安装需要 `prepare` 脚本与用户侧 `allowBuilds`,发布 npm/tarball 免构建许可。
- **交付必须补展示元数据**:`locale/*.json` 的 `meta.title`/`meta.description` + 导出的 `./icon`(≤256 KiB、realpath 后必须在包内);子路径插件**不读** `package.json`。
- **共享实例的 dsh 包只放 peerDependencies + devDependencies**,绝不放 dependencies(profile 内副本会静默遮蔽运行时版本,核心服务注册失败)。⚠️ **peer 范围会被安装期强制**(早期版本写成"DSH 不校验",**已更正**):`dsh plugin add` 拿运行时版本比对声明的 `@deepseek-ai/dsh-*` 范围,不匹配即**拒绝安装**;`engines.dsh` 才是纯声明、零强制。范围只能表达"能不能装",**表达不了某个 API 在不在**——仍要配运行时探测。区间选择见 `guide/version-matrix.md` §4。
- **UI 插件**:只渲染 slot 里的 React 组件(禁止 iframe 托管页面);样式只用 `--dsw-alias-*` 主题 token;不 `require` 任何 Harness Client 包(抛错组件会把整个 slot 搞白);`dsh.client.inject` **必须是静态数组**;不在组件外写 DOM、不往 `document.body` 追加。
- **性能**:per-session 状态放 `ctx.sessionProjections`(纯同步 apply、忽略即同引用、纯 JSON + `stateVersion`);等持久事件(`turn/end`/`assistant/message`/`tool/result`),实时 token 读 `agent/assistant-stream`;**不轮询 `agent/status`**。
- **禁止**:在会话运行期间改 profile 的 `cordis.patch.yml`(HMR 会重载,实测丢光 preset 工具、杀掉在途 turn、最坏永久废掉 `sessionController`);手写 profile 的 `package.json`;在 profile 目录里跑 pnpm。

## 按任务类型的开发路径

(以下路径均在 `references/official-docs/` 下,为官方文档全文副本)

- **新工具**:`docs/user/develop/basic/tool.md`(教程)→ `docs/cookbook/adding-a-tool.md`(完整契约:参数校验、规范值、后台任务 `ctx.jobs`、策略钩子、Code Mode、UI 卡片、`MessageSourceMap` 归属)→ 参考实现 `packages/shell/tool-bash`(本地 checkout)。
- **新服务/能力**:`docs/user/develop/framework/service.md` + `docs/user/develop/practice/`(三层拆分完整代码)。
- **拦截/策略/hook**:`docs/cookbook/extension-cookbook.md`(permission-gate 范例)+ `docs/event-producer-consumer.md`(全事件矩阵)。
- **新 LLM 提供商**:`docs/user/develop/practice/llm-adapter.md`(StreamChunk 协议)。
- **UI 插件(Web)**:`references/official-plugin-dev-skill.md` §3.2 + `docs/subsystems/slots.md`、`sidebar-right.md`、`client-modules.md`、`web-client.md`、**`docs/ui-radius.md`**(注意 `ui-radius` 在 `docs/` 顶层,**不在** `docs/subsystems/` 下);Chat 行业务节点用 `ctx.uiConversation.events.register()` + `conversation.chat.node` slot。
- **插件配置表单**:`docs/subsystems/settings.md`(已改名 "Plugin Configuration Forms")+ `docs/cookbook/adding-a-settings-card.md`(已重写)。
- **斜杠命令**:`docs/subsystems/commands.md`——命令是**面向人**的入口,与工具(面向模型)是两套注册面;只加工具而想让用户能敲 `/x` 时要另注册命令。
- **权限 / 审批 / 沙箱**:`docs/subsystems/approval.md`(审批流与 `ask` 决策)+ `docs/subsystems/permission-presets.md`(预设档位)+ `docs/subsystems/sandbox.md`(进程约束及其已知边界:Windows ACL 部分强制)。需要拦截工具调用时读 `docs/cookbook/extension-cookbook.md` 的 permission-gate 范例。
- **上下文 / 压缩 / 性能**:`docs/subsystems/compaction.md`(压缩缝与阈值语义)+ `docs/subsystems/spill.md`(溢出存储:只负责存储,无保留策略、无检索 API);`ctx` 自身的 API 面见 `docs/cordis-api/context.md`(**没有** subsystems 版的 context 页)。per-session 派生状态用 `ctx.sessionProjections`(见 §4.8 第 5 条)。
- **打包/发布**:`docs/user/develop/basic/publish.md`(bundle/profile、层顺序、多 patch 数组、peer 解析、git 安装坑)。
- **升级/迁移**:`guide/version-matrix.md`(**版本适配矩阵:先读这个**——五个"最新"、子包 `latest` 冻结、破坏性变更断层、peer 区间选择、会话格式 V0→V4)→ `guide/migration-0.2.md`(本库综述,0.1.6→0.2.1-alpha.1)→ `docs/upgrade-guide/**`(官方逐版本迁移指南)+ `docs/persistence-changes/**`(持久化类型变更记录)+ `docs/session-format-status.md`(会话格式版本权威)。⚠️ **目标宿主是 `0.2.1-alpha.2` 或更新时**:`version-matrix.md` §3 列了该版落的 **19 条破坏性变更**(`webRuntime`→`webStartup`、`mode: both` 被拒、`backgroundMode`/`run_in_background` 移除、`{{cwd}}` 移除、`BundleInfo` 新增必填字段、`dsh plugin --profile` 位置强制等),`migration-0.2.md` **尚未并入**。
- **查服务/事件精确签名**:**优先用会话内 inspection**(`cordis_inspect_list` / `cordis_inspect_query`:Service / Event / Config / Tool / Slots / Theme),其次 `docs/subsystems/*.md` 生成式 Cordis API 区 + `docs/cordis-api/*`;**不要自造第二份静态清单**。站点 URL ↔ 本地副本对照见 `guide/links.md`,社区链接完整清单见 `references/community-ecosystem.md`。
- **官方自带插件开发技能(0.2 世代新增,先读)**:`references/official-plugin-dev-skill.md`(主流程 + 六份分面参考要点 + 模板定位);上游在 `packages/preset/agent-preset/skills/cordis-plugin-development/**`。它规定的官方路径是:工作区写 bundle → `plugin_manager` `install_bundle`(绝对目录)→ `cordis_inspect_query` 核对,**禁止**手写 profile 的 `package.json`/`cordis.patch.yml`、禁止在 profile 目录跑 pnpm。
- **参考社区实现与实测坑**:`references/community-ecosystem.md`、`references/community-repo-deep-dive.md`(首批 15 个开发仓库深读)、`downloads/community-repos/`(**114 个仓库完整源码副本**,需先跑 `scripts/download-community-repos.ps1` 生成)。社区已确认的机制变化(repository-plugin 0811 移除、bundle vs 纯 cordis 双通道)与实测坑清单在 `guide/plugin-dev-guide.md` §7,源码级问题索引在 `guide/unfixed-issues.md`(含本世代新核实的 T1–T11)。官方 Discussions 全量归档(**2026-10-06 刷新**,含 #1629 官方插件脚手架 RFC)与 npm 全家桶元数据分别在 `downloads/github/harness/discussions/` 与 `downloads/npm/`;中英文社区文章 HTML 快照在 `downloads/web/community-articles/`。

## 验证(交付前)

- 加载验证:**免审批**用 `cordis_inspect_list` / `cordis_inspect_query` 确认新行真的挂上(比 `list_plugins` 快且不需要审批);同时跑 `dsh --profile <name> --dump-config` 看有没有 `patch: entry <id> not found`。
- **读安装结果**:`plugin_manager` 的 `application` 与 `warnings` 决定改动是否生效——不是日志、进程列表或页面 boot payload。`failed` 要诊断,`overridden` 表示更高优先层胜出,`restart-required` 表示**尚未生效**;**替换**已安装包需要重启才能载入新的 JS 模块代。
- 行为验证:Web UI 或 `dsh --profile headless "…"` 实测;工具返回/模型可见文本即行为,改动必须重测。**安装成功 ≠ 用户看得见**:展示元数据(标题/描述/图标)要按 `references/official-plugin-dev-skill.md` §3.1 的清单核对,无浏览器控制时如实报告"渲染未验证"。
- 仓库内改动额外走:类型检查、目标包测试、keyless snapshot(模型/产品可见行为必须有组装后转录快照)、双语文档成对、Agent Note(非平凡变更同 PR)。
- 独立插件包:`pnpm pack` 后试装到干净 profile 验证(含 `lib/` 构建产物);本库的 `dsh-plugin-dev verify` 已对齐这条。
- **排障提示**:宿主插件 `apply()` 抛错在 `dsh web` 下**可能完全不可见**(无日志 sink),只表现为工具/监听器静默缺失(见 `guide/unfixed-issues.md` T3);遇到"装了但没反应"先换 headless 或有日志的 profile 复现。

## 知识库维护(需要时)

- 同步官方文档副本:`pwsh -File ./scripts/sync-official-docs.ps1 [-Checkout <deepseek-harness checkout>]`——只同步 git 已跟踪文件(未跟踪草稿与未推送提交不会进来),并刷新 `references/official-docs/SNAPSHOT.md`;README 的"最后核验"日期与提交号引用 SNAPSHOT.md,不要手改。漂移校验:`pwsh -File ./scripts/check-docs-drift.ps1 -Checkout <checkout>`(快速)或 `pwsh -File ./scripts/verify-kit.ps1 -Checkout <checkout>`(全量)。
- 刷新线上资料:`pwsh -File ./scripts/download-sources.ps1`;刷新社区仓库:`pwsh -File ./scripts/download-community-repos.ps1`;刷新社区文章快照:`pwsh -File ./scripts/download-community-articles.ps1`(三个脚本幂等,产出进 `./downloads/`)。刷新官方 Discussions 归档:`$env:GH_TOKEN=<token>; pwsh -File ./scripts/archive-discussions.ps1`(list.json + 精选线程评论,防缩水保护;**2026-10-06 期:list=5000、精选 1648**)。**注意 REST 分页天花板**:`/discussions` 列表最多翻到 5000 条,而仓库实际 total_count≈8850(GraphQL 口径)——需要更全的历史时按 GraphQL `UPDATED_AT DESC` 补抓。话题清单计数重核:`pwsh -File ./scripts/gen-topic-snapshot.ps1 -OutDir <dir> -MaxPages 10`。
- **维护节奏建议**:官方 3 周内发了 9 个 prerelease,所以"最后核验"日期要跟 tag 走;每次官方发版后按 `references/official-docs/docs/upgrade-guide/**`(**无 index、无入链**,必须主动看)+ `docs/persistence-changes/**` + GitHub Releases 说明三处增量更新 `guide/migration-0.2.md` 与 `guide/unfixed-issues.md`。
- 安装/刷新 agent 技能副本:`pwsh -File ./scripts/install-skill.ps1 -Target <skill目录>`(跳过 downloads/ 与 .github/,逐字节校验)。
- 冲突裁决:与官方文档冲突时以 `references/official-docs/`(官方仓库原文)为准;官方文档与**代码**冲突时以代码/产物为准并**明确标注分歧**(本轮已发现一例:`session-format-status.md` 的发布记录落后于代码与 npm)。

## CLI 工具链(dsh-plugin-dev)

本仓库随 bundle 附带零依赖 CLI `dsh-plugin-dev`,把机械检查自动化(知识库仍是认知层,CLI 是机械层)。

> ⚠️ **调用方式**:`dsh-plugin-dev` **通常不在 PATH 上**(bundle 安装只登记技能,不安装二进制)。
> 用本 SKILL.md 所在目录下的入口调用:`node "<resourceBase>/bin/dsh-plugin-dev.js" <子命令>`。
> bundle 安装的包已带 `dist/`;从源码副本用则先跑一次 `pnpm run build`。
> 只有全局 `npm i -g dsh-plugin-guide` 之后才可以直接敲 `dsh-plugin-dev`。

- `new <name>`:参数化脚手架,生成 TS 或 JS 插件仓库骨架(`src/index.ts` 契约模板、Schemastery Config、tests、tsdown/vitest、注释齐全的 `cordis.patch.yml`、五语 README),模板与 `references/official-docs` 同步更新。标志:`--lang <ts|js>`、`--dir <path>`、`--force`、`--git`。
- `check [--json] [--strict] [--cwd <dir>]`:静态检查(`cordis.patch.yml` 合法性、`package.json` 元数据(`dsh.bundle.patch` 指向/peer 依赖/engines/files 白名单)、五语 README 一致性、工程红线模式),输出 CI 可消费的结构化 JSON;每个检查项在输出里引用本知识库对应章节(skill 联动),agent 可继续人工审计。**这 16 项是同族 `dsh-plugin-doctor` 33 项的子集**(逐项对照见 `guide/family-toolchain.md` §3);发布前建议两个都跑。
- `verify [--cwd <dir>] [--base <spec>] [--headless <spec>] [--profile <name>]`:`pnpm pack` 后装入干净临时 `DSH_HOME` profile 做安装+启动+卸载冒烟(对齐官方 verify:self-contained);失败给出日志尾部与建议。
- `seams <query>` / `seams --since <version>`:**回答"这个 API 在我目标宿主里存不存在"**,而不是让你去翻 429 页镜像。`query` 可以是 seam id(`L3`)、能力 id(`A20`)、版本号或任意词。`--since 0.2.0-rc.2` 列出**在该版本之后落地**的所有破坏性变更,并标出哪些**静态检查看不到**。
  - **第三态是强制的**:知识库没有依据时**退出码 3 并明确说"这是知识库没有依据,不等于该 API 不存在"**——绝不把"不知道"渲染成"没有"。
  - 数据源 `data/seams/index.json`:19 条 `L` 走廊 seam + 6 条机制 seam + 22 个能力区,每条带 `confidence`(`documented`/`inferred`/`unknown`)与官方出处;**其中 9 条标 `detectable: false` 并写明原因**(数据丢失、运行时状态、部署相关行为),任何消费者都不得把它们算作通过。

四个子命令均可逆/幂等;网络/子进程尊重超时与 AbortSignal;只清理自己 mkdtemp 的目录。CLI 零运行时依赖,构建产物经 tsdown 打包为单文件 `dist/dsh-plugin-dev.js`。

## 边界

- 本技能是"指引 + 约束 + 资料索引";机械检查由 `dsh-plugin-dev check` 承担,精确 API 以生成式参考为准。
- 不得修改知识库外的 harness 仓库文件,除非用户明确要求;vendor/ 与 `.agents/notes/archived/` 只读。
- 引用 `downloads/` 内容前先确认其存在(该目录不入 git,需按上文脚本生成);`awesome-dsh-plugins` 的归档仅供本地参考,**不得随仓库再分发**(其上游声明内部使用约束,见 NOTICE.md)。


<!-- source: guide/version-matrix.md -->

# [Version adaptivity matrix (latest values, seams, peer ranges, session format)] guide/version-matrix.md

# DSH 版本适配矩阵(version-matrix.md)

> **这份文件回答一个问题**:我的插件要跑在哪个 DSH 上,那个版本有什么、没有什么、
> 哪一版开始行为变了。
>
> **测量时间**:2026-10-09(官方版本线)/2026-10-13(生态数字)。
> **数据来源**:npm registry `@deepseek-ai/dsh`、GitHub REST API(tags/commits/releases)、
> 官方 `docs/upgrade-guide/**`、官方 `docs/persistence-changes/**`。逐条可复现命令见 §7。
>
> **口径**:本文件只写**有出处**的数字。取不到的一律写 `未核`,不写推测值。
> 官方说法与社区实测冲突时分别标注。

---

## 1. 先认版本:五个"最新"不是同一个东西

这是插件作者**第一个**要做的判断。五个数可以不相等,而 peer 范围按线分叉:

| # | 说法 | 值(2026-10-09) | 怎么查 | 性质 |
|---|---|---|---|---|
| 1 | npm `latest` | `0.2.0-rc.2` | `npm view @deepseek-ai/dsh dist-tags` | 默认装的就是这个 |
| 2 | npm `next` | `0.2.0-rc.2` | 同上 | 与 latest 同值 |
| 3 | npm `alpha` | `0.2.1-alpha.2` | 同上 | 最新发布 |
| 4 | master 最新 tag | `dsh-v0.2.1-alpha.2` | GitHub `/tags` | 源码线的名字;**等于 master HEAD** |
| 5 | **本机实际** | — | `dsh --version` | **唯一真正要紧的数** |

### 1.1 没有"稳定版"这回事

- **全部 31 个 npm 版本都带 `-alpha.N` / `-rc.N` 后缀;全部 26 个 GitHub Release 都是 `prerelease: true`。**
- 所以 npm 的 `latest` 标签在这里**不代表稳定**——它只是"默认安装会拿到的那一个"。
- 给作者的有效读法:**最新 = `0.2.1-alpha.2`(npm `alpha`);默认安装 = `0.2.0-rc.2`(npm `latest`/`next`)**。
- 官方 `CONTRIBUTING.md` 明确**不接受外部 PR**,`AGENTS.md` 声明 API 处于 pre-stable。
  插件作者无法"等它稳定",只能**钉版本 + 声明区间**。

### 1.2 ★ 陷阱:子包的 `latest` 是冻住的

主包 `@deepseek-ai/dsh` 每次发版都会重发全部子包到 `alpha`/`next`,但**每个子包的 `latest`
标签停在它第一次被设置的那个版本,永不前进**。裸装子包会装到远古版本:

| 包 | `latest`(冻住的值) | `next` | `alpha` |
|---|---|---|---|
| `@deepseek-ai/dsh` | `0.2.0-rc.2` | `0.2.0-rc.2` | `0.2.1-alpha.2` |
| `@deepseek-ai/dsh-base` | **`0.0.1-rc.1`** | `0.2.0-rc.2` | `0.2.1-alpha.2` |
| `@deepseek-ai/dsh-session` | **`0.0.1-rc.1`** | `0.2.0-rc.2` | `0.2.1-alpha.2` |
| `@deepseek-ai/dsh-tools` | **`0.0.1-rc.1`** | `0.2.0-rc.2` | `0.2.1-alpha.2` |
| `@deepseek-ai/dsh-subagent` | **`0.0.1-rc.1`** | `0.2.0-rc.2` | `0.2.1-alpha.2` |
| `@deepseek-ai/dsh-home-paths` | `0.0.1-rc.3` | `0.2.0-rc.2` | `0.2.1-alpha.2` |
| `@deepseek-ai/dsh-client-ui-primitives` | **`0.0.1-rc.1`** | `0.2.0-rc.2` | `0.2.1-alpha.2` |
| `@deepseek-ai/dsh-agent` | `0.1.0-rc.6` | `0.2.0-rc.2` | `0.2.1-alpha.2` |
| `@deepseek-ai/dsh-typert-protocol` | `0.1.0-rc.6` | `0.2.0-rc.2` | `0.2.1-alpha.2` |
| `@deepseek-ai/dsh-package-manifest` | `0.1.3-alpha.2` | `0.2.0-rc.2` | `0.2.1-alpha.2` |
| `@deepseek-ai/dsh-plugin-manager` | `0.1.6-alpha.2` | `0.2.0-rc.2` | `0.2.1-alpha.2` |
| `@deepseek-ai/dsh-experimental-ptc-runtime-python` | `0.1.6-alpha.1` | `0.2.0-rc.2` | `0.2.1-alpha.2` |
| `@deepseek-ai/dsh-otel` | `0.2.0-rc.1` | `0.2.0-rc.2` | `0.2.1-alpha.2` |

**结论三条**:

1. 永远不要写 `"@deepseek-ai/dsh-tools": "latest"` 或裸 `pnpm add @deepseek-ai/dsh-session`。
2. **不要依赖子包版本号判断宿主代次**——`dsh-agent` 的 `latest` 停在 `0.1.0-rc.6`,
   而它可能正跟着 `0.2.1-alpha.2` 运行。判断代次只认 `dsh --version`。
3. 共享实例的 dsh 包一律 `peerDependencies` + `devDependencies`(**绝不 `dependencies`**),
   按 §4 选区间。

---

## 2. 破坏性变更断层(只列"哪一版开始不一样")

流水账在 §8;这里只列作者真正会撞上的断层。**官方 `docs/upgrade-guide/v<X>/` 的目录名
`X` 是"变更被引入的版本",描述的是从 X 升到**下一个** release 的变更**——
原文措辞是 *"In vX, … was so; the next release no longer…"*。别把目录名当目标版本。

| 断层 | 引入版本(目录名) | 落到哪一版 | 影响面 |
|---|---|---|---|
| **即插即拔基础设施** | `0.1.6-alpha.2` | 同版 | `dsh-hmr` / `dsh-plugin-manager` / `ui-plugin-manager` 三包一次性出现;新增 `ctx.hmr` / `ctx.pluginManager` / `ctx.profileContext` |
| **peer 从"声明"变"运行时门禁"** | `0.1.7-rc.1` | 同版 | `plugin-compatibility.ts` 三文件首次出现;不匹配的行被直接 `disabled`,模块永不导入 |
| **会话格式 V4 + 本世代破坏性变更** | `0.1.7-alpha.1` | 同版 | `SESSION_FORMAT_VERSION` 3→4、settings 重写、`readBytes`、Messages-only 适配器 |
| **0.2.0 世代清理** | `0.2.0-rc.2` | 下一版 | 移除 runtime invariants、schedule bundle 退役、子路径插件不再读 `package.json`、新增 `no-response` 错误码 |
| **0.2.1-alpha.2 大批变更** | `0.2.1-alpha.1` | `0.2.1-alpha.2` | **19 条**破坏性变更(见 §3) |

> ⚠ **两个断层不属于同一层面,别混**:`0.1.6-alpha.2` 解决"能不能热插拔",
> `0.1.7-rc.1` 才解决"版本不匹配时会不会被拦"。`0.1.6-alpha.2` 上的
> `dsh-plugin-manager` **没有** peer 预检。

> ⚠ **`0.1.5-rc.3` 是时间线上的陷阱**:它在 `0.1.6-alpha.2` **之后**发布(2026-09-22),
> 是给留在 0.1.5 线用户的**回补版本**,也是唯一**没有 GitHub Release** 的 tag。
> 它的依赖写成精确版本、没有 `exports`、没有 `dsh-hmr`/`dsh-plugin-manager`。
> **任何"按发布时间排序推断版本新旧"的脚本都会在这里出错——必须按 semver 比较。**

---

## 3. `0.2.1-alpha.1 → 0.2.1-alpha.2`:19 条破坏性变更

如果你在 `0.2.0-rc.2` 或更早的线上,**这 19 条与你无关**;如果你跟的是 master/alpha 线,
**逐条核对**。官方 release notes 也列了这 5 条为 "other changes":Python PTC 沙箱、
`both` 模式移除、`dshHome` 移除、子代理执行统一、Agent Team 直投收件箱。

| # | 变更 | 破坏了什么 | 你要做什么 |
|---|---|---|---|
| 1 | `subagent-activations` | `backgroundMode`/`enableRunInBackground`/`run_in_background` 全部移除;`@deepseek-ai/dsh-subagent-in-process-driver` 包被删;`ctx.subagents.start()`→`startActivation()` | 删两个配置键与工具参数;宿主代码换 `startActivation({ …, delivery: 'caller' })`;自定义本地 provider 要实现 `prepareContinuable()` |
| 2 | `tool-presentation-mode` | `mode` 只接受 `native`/`ptc`,**`both` 被拒绝**;含环境变量 `DSH_TOOLS_MODE=both` 与 `ctx.tools.presentAs('both')` | 改成 `native` 或 `ptc` |
| 3 | `web-listener-trust-config` | **`webRuntime` 服务被移除**,改 `webStartup`(行 `web-startup`);`ctx.webRuntime.lanAddresses` 没了;**通配 host(`0.0.0.0`/`::`)在加载期被拒** | `inject: [webRuntime]`→`[webStartup]`;`trustedHosts` 换服务;通配 host 换具体 IP 字面量 |
| 4 | `working-directory` | 内置 `{{cwd}}` 提示变量被移除;`cwd` 配置从 ACP / DSH-SDK 子代理移除;**保留 `{{cwd}}` 的自定义模板在模型请求前就失败** | 删提示里的目录子句;`cwd` 改由子请求传或先调 `working_directory({ cd })` |
| 5 | `session-title-provider-strategy` | `dsh-session-title-llm` 移除 3 个导出符号(`registerSessionTitleLlmProvider` / `generateSessionTitleWithLlm` / `SessionTitleLlmMessageSelector`);`targetWords`/`targetCjkCharacters` 移到各 provider 自己的 Config | 改用 `ctx.sessionTitle.register({ id, automatic, generate })`,自己搭 system/input |
| 6 | `session-title-adapters` | 标题请求改走 `ctx.llm.prepareCall()`;只挂 `llm/stream` 中间件的组合需另注册 adapter;自定义 adapter 的 `reasoning.efforts` **必须从低到高排** | 用 `ctx.llm.registerAdapter()` 注册标题路由 |
| 7 | `ssh-helper-launch` | `dsh-ssh` 用单一 `launch` 键取代 `node`/`bootstrapPath`/`bootstrapHash`;**要求 SSH protocol 2**;`dsh-ptc-runtime-node` 的 `nodeExecutable`/`bootstrapPath`→`launch` | `launch: { kind: 'node-script' }` 或 `{ kind: 'executable' }`;helper 与 hash 必须同改 |
| 8 | `team-direct-inbox` | Agent Teams 消息直投目标 inbox,独立 mailbox 消失;`send_message` 只返回 `{"sent":true}`;`maxPendingMessagesPerMember` 移除;**未投递的排队消息永不派发 = 数据丢失** | **升级前**让待发消息跑完;升级后必需的显式重发 |
| 9 | `instruction-home-override` | `dsh-agent-instructions` 移除 `dshHome` 配置字段,改用 `@deepseek-ai/dsh-home-paths` 的 `resolveDshHome()`;**残留键被静默忽略** | 删 `dshHome`;改用进程环境变量 `DSH_HOME` |
| 10 | `experimental-package-composition` | CLI 与 Python runtime 的安装依赖**不再包含 hook 桥**;安装与组合分离(装了不等于挂上);只"启用"被删行的 patch 不会把它插回来 | 显式装 peer;选 bundle **先于**覆盖其行;用完整 `insert` 替代对已删行的 override |
| 11 | `native-subagent-bundle-tools` | 选中 bundle 会**额外注册 Host 全局工具**(`tool-subagent-claude-code`/`-codex`),对最小预设也可见;**插件构造或严格校验 `BundleInfo` 必须补两个必填字段 `official`/`availability`**;`PluginPackageRef` 同理 | 补必填字段;删同名冗余 Host 行(会被遮蔽) |
| 12 | `python-ptc-sandbox` | Python PTC runtime **要求 `sandbox` + `sandboxPolicy` 服务**;默认 `read-only`;受限运行会以 `sandbox-unavailable` 失败 | 挂 `dsh-sandbox-local` + `dsh-sandbox-policy`;设 `sandbox-policy` 行的 `mode`/`workspaceRoot` |
| 13 | `python-optional-resources` | Python wheels **不再内置** Office sidecar 与 authoring 解释器;SDK 启动不再下载 | 用前显式 `download_office()` / `download_primary_runtime()`;离线部署预置 `DSH_RESOURCE_CACHE` |
| 14 | `plugin-option-placement` | `dsh plugin` 只认**转发给 pnpm 的参数之前**的 `--profile`;**`dsh plugin add <pkg> --profile <name>` 现在直接失败** | 改成 `dsh plugin --profile <name> add <package>` |
| 15 | `terminal-command-labels` | `TerminalBlockLabels`(`dsh-client-ui-primitives`)新增**必填** `commandLine(line: number): string` | 给每个 labels 对象补 `commandLine` |
| 16 | `deepseek-model-discovery` | `deepseek-official` 路由**无 key 时返回空目录**,Desktop/Web 隐藏 DeepSeek 组 | 配 key 或用 `apiKeyEnv` 指定的环境变量;自定义消费方要处理空目录 |
| 17 | `new-session-generations` | Web/Desktop 新建会话会**同时新建 Agent**,不再复用空会话 | 改过预设定义后用 New Session;要复用用 `openWorkspace`/`connectWorkspace` |
| 18 | `mock-messages-events` | `dsh-llm-mock-server` 会在结构化 SSE 负载旁**同时发 Messages 事件名** | 消费具名事件,别只筛匿名消息 |
| 19 | `desktop-test-auth-popup` | Desktop **测试**认证弹窗默认关闭 | 需要时设 `updates.allowTestAuthPopupWindow: true` 并完全重启 |

另有一条**回填**在 `v0.2.0-rc.2/` 目录里、但我们基线之后才加入的:
`bundled-python-tests`(打包的 NumPy/pandas **测试目录**被移除;要跑上游测试套件请另建
Python 环境)。它放在旧目录属于例外——上游自己的规则是"版本号 bump 后旧目录冻结"。

**出处**:每条对应
`docs/upgrade-guide/v0.2.1-alpha.1/<item>/guide.md`(官方仓库 master);
发布说明 `releases/tag/dsh-v0.2.1-alpha.2`。

---

## 4. 我该声明什么 peer 区间

两条硬事实:

1. **peer 是运行时门禁,不是声明。** 从 `0.1.7-rc.1` 起,`app-boot/src/plugin-compatibility.ts`
   在插件被 import **之前**校验 `peerDependencies` 里的 `@deepseek-ai/dsh` / `@deepseek-ai/dsh-*`;
   不匹配的行被**直接置为 `disabled`**,模块永不导入。
2. **门禁语义是 `includePrerelease: true`**(`plugin-compatibility.ts:77`),比裸 semver **宽松**。
   `engines.dsh` 与 `dsh.manifestVersion` 则是**纯声明、零强制**——写了不生效。

### 4.1 家族内当前使用的规范区间

```
>=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0 || >=0.1.6-0 <0.2.0 || >=0.1.7-0 <0.2.0 || >=0.2.0-0 <0.3.0 || >=0.2.1-0 <0.3.0
```

这是 `dsh-plugin-kit` 的 `data/peer-range.json`(`canonicalRange`,2026-09-26 更新)所记录的
规范值,六个子句按线枚举。**本仓库 `src/cli/templates.ts` 生成的区间与它逐字节一致**,
是仓库内唯一副本。

### 4.2 选区间的判断法

| 你的插件用了什么 | 下界至少要到 |
|---|---|
| 只用 Cordis 基础能力(tools/events/services/effects) | `0.1.2-rc.1`(最早) |
| 用了 `ctx.hmr` / `ctx.pluginManager` | `>=0.1.6-0` |
| 依赖 peer 门禁本身、或 0.1.7 的 API | `>=0.1.7-0` |
| 用了 0.2.0 世代接口(settings 重写后) | `>=0.2.0-0` |
| 用了 §3 里任何一条新形态(如 `webStartup`、`BundleInfo.official`) | `>=0.2.1-0` |

**不要**写开区间上界(如 `>=0.1.2-rc.1 <0.3.0`):它跨过了整代破坏性变更,
门禁放行但运行时会炸。**上界必须按世代切**(`<0.2.0` / `<0.3.0`)。

---

## 5. 会话格式(persistence)版本谱系

插件若读写会话日志,必须认这个表。

| 格式 | 首次写入的版本 | 规模(root/type) | 说明 |
|---|---|---|---|
| V0 | `0.1.2-rc.1` | 54 / 415 | 历史 **V0 参考快照**取自这个 tag |
| **V1** | **从未发布** | — | 没有 tag 的 writer version 是 1(来源 PR #3349) |
| V2 | `0.1.3-alpha.2` | 56 / 435 | — |
| V3 | `0.1.5-alpha.1` | 60 / 467 | 源码 PR #4320 |
| **V4** | `0.1.7-alpha.1` | 63 / 597 | 当前;`SESSION_FORMAT_VERSION` 3→4 的提交是 `c36a83ff…` |

**官方权威记录**:`docs/session-format-status.md` 现在写
`latestReleasedVersion: 4` / `evidenceTag: dsh-v0.2.0-rc.2`。
(我们镜像快照早于该更正,曾把它记为"未解决的矛盾"——**以 live 为准**。)

**三个读数陷阱**:

1. **逐版本持久化档案冻结在 `0.1.5-rc.2`**(2026-09-12 采集,26 tag / 25 次转换)。
   `0.1.6-alpha.1` 之后**没有任何持久化记录**。所以新版本的唯一官方历史是 **upgrade-guide**。
2. **V4 之后仍有 `same-version` 记录**(不改格式号也能加类型)。基线后有
   `2026-09-13-working-directory` 等 5 条,其中 `event:working-directory/change` 是
   **required-on-read**——**旧构建拒绝打开含该事件的日志**。
3. **多帧 zstd**:`session.jsonl.zstd` **每次写入追加一个 zstd frame**,
   `zstdDecompressSync` 一次只解出第一帧(137 行会话可能只得到 1 行)。
   必须按魔数 `28 B5 2F FD` 切分。

---

## 6. 能力 × 版本(22 个原生能力面)

**这一节的"引入版本"是怎么算出来的**(决定了它能怎么用):
对每个能力面取"**首个包含该路径新增提交的 tag**"——`git log --diff-filter=A --reverse -- <path>`
配 `git tag --contains`,再按 tag 创建时间取最早的一个。**这是包/路径层面的引入,不是 API 层面的精确引入**:
一个能力面内部的某个具体函数、配置键、服务键可能比整面晚若干版本。
**要精确到 API 就查 `references/official-docs/docs/subsystems/*.md` 与 `docs/capability-seams.md`**,
不要拿这张表当 API 级答案。

**口径警告(必须连带引用)**:本 checkout 里**最早的 tag 是 `dsh-v0.1.0-rc.7`(2026-08-17)**。
更早的 `dsh-v0.0.1-rc.1` … `dsh-v0.1.0-rc.6` **只在 `docs/persistence-changes/releases/` 里有文档、没有 tag**。
所以表里的 **`≤ rc.7` 意思是"在最老的 tag 上就已经存在",不是"rc.7 引入"**——
真正的引入时间只能靠首提交日期或 Agent Note 日期(表里给了)。

| # | 能力面 | 引入版本 | 更细的时间锚(原文) |
|---|---|---|---|
| A1 | **Subagents** | **≤ `dsh-v0.1.0-rc.7`** | seam ≤ rc.7(note 2026-06-21);continuable note 2026-07-28;控制工具首提交 2026-08-02 |
| A2 | **Agent Teams**(实验) | **`dsh-v0.1.0-rc.8`** | note 2026-08-05;包首提交 2026-08-19 → `rc.8`;profile bundle 2026-09-04 → `0.1.3-alpha.1` |
| A3 | **Workflow 编排** | **`dsh-v0.1.6-alpha.1`** | note/`tool-workflow` 2026-07-05、`tool-ralph` 2026-07-20(均 ≤ rc.7);`workflow-ptc` 2026-09-13 → 本版 |
| A4 | **后台作业**(jobs) | **≤ `dsh-v0.1.0-rc.7`** | 首提交 2026-08-13;整合 note 2026-09-03 |
| A5 | **定时 / cron** | **`dsh-v0.2.1-alpha.1`** | `after` 2026-08-05、定频 + cron 2026-08-06(`31c0c6a9f6`)→ ≤ rc.7;**工具包改为 preset 作用域** 2026-09-24 → 本版 |
| A6 | **目标追踪**(goal) | **≤ `dsh-v0.1.0-rc.7`** | 2026-07-19(三份 note) |
| A7 | **Plan mode** | **≤ `dsh-v0.1.0-rc.7`** | 2026-07-22(note `2026-07-07-plan-mode`) |
| A8 | **Todo** | **≤ `dsh-v0.1.0-rc.7`** | 2026-06-29 |
| A9 | **LSP 导航** | **`dsh-v0.1.3-alpha.1`** | seam 2026-07-16;`lsp-stdio` 2026-08-13(≤ rc.7);`tool-lsp` 2026-09-04 → 本版 |
| A10 | **浏览器自动化** | **`dsh-v0.1.6-alpha.1`** | 2026-09-12/13(note 2026-09-12);**seam 原生、provider 实验性** |
| A11 | **Computer use** | **`dsh-v0.1.6-alpha.1`** | 2026-09-12;**seam 原生、provider 实验性** |
| A12 | **MCP client** | **`dsh-v0.1.3-alpha.1`** | client note 2026-07-07;成组首 tag 2026-09-04 |
| A13 | **可观测 / OpenTelemetry** | **`dsh-v0.2.0-rc.1`** | otel 包 2026-09-25 → 本版;product adapter 2026-09-21 → `0.1.7-alpha.2`;session adapter 2026-09-04 → `0.1.3-alpha.1` |
| A14 | **交付物 / 文档 / 附件** | **`dsh-v0.1.6-alpha.2`** | deliverables 2026-09-14、office-to-pdf 2026-09-16 → 本版;attachment seam 2026-09-04 → `0.1.3-alpha.1` |
| A15 | **会话存储 / 持久化检查点 / 搜索 / fork-resume-export** | **`dsh-v0.1.3-alpha.1`** | session/llm 2026-06-11;检查点策略 2026-08-09(≤ rc.7);session-query-sqlite 等 2026-09-04 → 本版;**V4 格式 2026-09-16** |
| A16 | **Web 访问**(search/fetch) | **`dsh-v0.1.3-alpha.1`** | seam note 2026-06-24;成组首 tag 2026-09-04 |
| A17 | **出站 webhook** | **`dsh-v0.1.2-alpha.1`** | 成组 2026-08-23 → 本版;GitHub 适配器 2026-09-04 → `0.1.3-alpha.1` |
| A18 | **终端 / SSH / shell** | **`dsh-v0.1.6-alpha.1`** | shell/terminal 2026-08-13(≤ rc.7);**SSH 2026-09-12 → 本版**;终端权限 note 2026-09-16 |
| A19 | **权限 / 审批 / 沙箱** | **`dsh-v0.1.3-alpha.1`** | sandbox/permission/user-approval/commands 2026-09-04 → 本版;Windows ACL note 2026-08-08、guards 2026-07-08(均 ≤ rc.7) |
| A20 | **扩展点:skills / 动态插件 / hooks / presets** | **`dsh-v0.1.6-alpha.2`** | skill 2026-07-10、hooks 2026-07-01(≤ rc.7);**plugin-manager 工具 2026-09-14 → 本版**;preset registry 2026-09-21 → `0.1.7-alpha.2`;cordis runners 2026-09-04 → `0.1.3-alpha.1` |
| A21 | **上下文管理:compaction / spill / 观察策略** | **`dsh-v0.1.3-alpha.1`** | compaction seam 2026-06-18、成组 2026-08-13(≤ rc.7);spill 成组 2026-09-04 → 本版;图片 offload 2026-09-08 → `0.1.6-alpha.1` |
| A22 | **插件 / API 面:Remote 控制器 / Typert / SDK / ACP / bundle** | **`dsh-v0.1.2-alpha.1`** | acp 2026-06-16、sdk 2026-07-15、api/typert 2026-07-29–08-07(≤ rc.7);sdk-minimal 2026-08-24 → 本版 |

### 6.1 只在该特写表里的能力(§1–§5 未收)

前几节只列了"会破坏插件"的项;下表补**新增即用得上**的接缝,出处同样是官方 `docs/`:

| 接缝 / 机制 | 引入 | 出处 |
|---|---|---|
| `ctx.hmr`、`ctx.pluginManager`、`ctx.profileContext` | `0.1.6-alpha.2` | 三包同版首次出现(逐 tag `git cat-file -e` 验证) |
| peer 兼容性运行时门禁 | `0.1.7-rc.1` | `plugin-compatibility.ts` 等三文件首次出现 |
| 会话格式 V4 | `0.1.7-alpha.1` | `SESSION_FORMAT_VERSION` 提交 `c36a83ff…` |
| `ctx.mcpResources`、`ctx.browserUse`、`ctx.computerUse`、`ctx.ssh`、`ctx.inspector`、`ctx.dynamicCordisRunner` + `ctx.cordisInspect` | `0.2.1-alpha.1` | 见 A10/A11/A12/A18/A20 行;归档调研与官方 `docs/capability-seams.md` |
| 新 slot `conversation.input.permission`、`sidebar.right.tab.guide.entry` | `0.2.1-alpha.1` | 官方 slot 文档 |
| `webStartup`(取代 `webRuntime`) | `0.2.1-alpha.2` | `upgrade-guide/v0.2.1-alpha.1/web-listener-trust-config` |
| `BundleInfo.official` / `.availability`(**必填**) | `0.2.1-alpha.2` | `upgrade-guide/v0.2.1-alpha.1/native-subagent-bundle-tools` |
| `working_directory` 工具、`create_worktree` | `0.2.1-alpha.2` 前 | live `docs/tool-catalog.md` |
| `@deepseek-ai/dsh-invariants` | ≤`0.2.0-rc.2` 存在 → `0.2.1-alpha.1` **移除** | `upgrade-guide/v0.2.0-rc.2/remove-runtime-invariants` |
| `@deepseek-ai/dsh-experimental-schedule-bundle` | `0.2.0-rc.2` 退役 | `upgrade-guide/v0.2.0-rc.2/schedule-bundle-retired` |
| 子路径插件读自己的 `package.json` 取展示文案 | ≤`0.2.0-rc.2` → `0.2.1-alpha.1` **不再读** | `upgrade-guide/v0.2.0-rc.2/subpath-plugin-display-manifest` |
| `repository-plugin`(`.dsh-plugin`) | 2026-08-09 推出 → **08-11 移除** | 社区实测(`vlln/plugin-registry` 档案,**该仓 2026-10-03 已归档**) |

> 22 面全表连同每面的**工具名、API 名、配置键与逐条出处行号**,来自一次
> read-only 清点。**清点时 checkout 在 `dsh-v0.2.1-alpha.1` / `5badb15`;本库镜像现已同步到 `dsh-v0.2.1-alpha.2` / `d743267`**,
> 本文只摘"引入版本"一列;**要完整清单请按上文口径重跑**,或读官方
> `docs/subsystems/*.md` + `docs/capability-seams.md` + `docs/tool-catalog.md`。

---

## 7. 自检与复现

```powershell
# 五个"最新"里的前四个
npm view @deepseek-ai/dsh dist-tags
# { latest: '0.2.0-rc.2', next: '0.2.0-rc.2', alpha: '0.2.1-alpha.2' }

# 子包 latest 是否冻结(换包名即可)
npm view @deepseek-ai/dsh-tools dist-tags
# { latest: '0.0.1-rc.1', next: '0.2.0-rc.2', alpha: '0.2.1-alpha.2' }

# master 最新 tag 与 HEAD
Invoke-RestMethod -Headers @{'User-Agent'='x'} `
  https://api.github.com/repos/deepseek-ai/deepseek-harness/tags | Select-Object -First 3 name

# 官方 upgrade-guide 目录(新断层会新增一个版本目录)
Invoke-RestMethod -Headers @{'User-Agent'='x'} `
  https://api.github.com/repos/deepseek-ai/deepseek-harness/contents/docs/upgrade-guide

# 本机实际版本 —— 唯一真正要紧的那个
dsh --version
```

**维护节奏**:官方 3 周内发过 9 个 prerelease。每次官方发版后按
`docs/upgrade-guide/**`(**无 index、无入链,必须主动看**)+ `docs/persistence-changes/**`
+ GitHub Releases 三处增量更新本文件与 `guide/migration-0.2.md`。

---

## 8. 与 `guide/migration-0.2.md` 的分工

| 文件 | 回答什么 | 形态 |
|---|---|---|
| **本文件** | 「哪个版本有/没有什么」「我该声明什么区间」 | **速查表 + 版本锚点** |
| `guide/migration-0.2.md` | 「从 0.1.6 升到 0.2 世代要改什么」 | **迁移叙事 + 逐条清单** |

两者双向互链。**冲突时官方原文优先**(`references/official-docs/`);官方文档与代码冲突时
以代码/产物为准,并**显式标注分歧**。

---

## 9. 未核事项(不要当成已知)

- §6 的"引入版本"是**包/路径层面**的(首含该路径的 tag),**不是 API 层面**;面内某个具体
  函数/配置键/服务键可能更晚。精确到 API 请查官方 `docs/subsystems/*.md` 与 `docs/capability-seams.md`。
- §6 的 `≤ rc.7` 是**下限**不是引入点:本 checkout 最老的 tag 是 `dsh-v0.1.0-rc.7`,更早只有文档没有 tag。
- `0.1.2-alpha.2` 首次出现 `dsh.configTrees`,`0.1.7-alpha.1` 又移除它——**中间是否还有变更未逐版核对**。
- 子包 `latest` 冻结的**原因**未确认(值已实测,机制未证)。
- `0.1.0-rc.3` / `-rc.4` 在持久化档案的 tag 清单里存在,但 live tag 列表与 npm 都没有——**原因未知**。
- 24 个基线之前版本的 release note **正文**未逐条读取(正文存在,`0.1.7-rc.1` 有 19,003 字符)。
  写权威的逐版本历史前必须读。
- `dsh-v0.1.2-alpha.1`、`dsh-v0.1.3-alpha.1` 是 **GitHub-only** 版本(有 release,npm 上没有)。


<!-- source: guide/plugin-dev-guide.md -->

# [Plugin development guide (10 chapters)] guide/plugin-dev-guide.md

# DeepSeek Harness 插件开发综合指南

> 本文档是 `dsh-plugin-guide` 的核心成果之一:把官方文档、上游 Cordis 资料与社区经验汇总成一条完整的插件开发路径。
> 事实来源全部记录在 [sources.md](../references/sources.md);官方原文全文副本在 [references/official-docs/](../references/official-docs/)。
> 本文档为"指引",不是"替代品":开发中遇到精确签名/字段,永远以官方文档副本与生成式参考(subsystems 页、cordis-api 页)为准。
> 组合级运营(多仓版本编排、发布流水线、宿主破坏兼容时的迁移波)见 [release-engineering.md](release-engineering.md);中文稿:[release-engineering.zh-CN.md](release-engineering.zh-CN.md)。

---

## 0. 一分钟心智模型

DeepSeek Harness(DSH)是一个**以插件组成的代理框架**("Everything is a Plugin"),底层是 vendored 的 [Cordis](https://github.com/cordiverse/cordis) 插件运行时。

- **插件 = 一个模块**:导出 `name`、可选的 `inject`(依赖的服务名数组)、`apply(ctx, config)` 函数(或对象/类形态)。
- **上下文 `ctx` = 服务仓库**:`ctx.tools`、`ctx.llm`、`ctx.agents` 等都是"服务";插件通过键名找服务,而不是 import 具体实现。
- **注册 = 可逆副作用(effect)**:事件监听、工具注册、定时器等一律经 `ctx.on()` / `ctx.effect()` / 各服务的 `register()` 挂接;插件卸载时框架自动全部撤销 —— 这是 HMR 与热插拔成立的基础。
- **事件 = 扩展点**:`emit`(广播)/ `waterfall`(可短路管线,必须调 `next()`)/ `parallel` / `serial` 四种派发模式;类型经 TypeScript 声明合并(declaration merging)保证类型安全。
- **能力 = 三层接缝(seam)**:Service Definition(声明接口)→ Service Provider(实现)→ Consumer(消费,通常是工具)。
- **配置 = cordis.yml 分层补丁**:bundle(`dsh.bundle`)→ profile(`dsh.profile`)→ 用户/机器层 patch → `--patch` 覆盖层,后者逐行覆盖前者。

最小插件:

```ts
import type { Context } from '@deepseek-ai/cordis'

export const name = 'hello-plugin'
export const inject = ['tools']   // 需要 ctx.tools 时声明;无依赖可省略

export function apply(ctx: Context) {
  // 需要的服务此时已就绪;注册你的能力。
}
```

---

## 1. 官方资料地图(先读哪个)

| 层级 | 文档 | 位置 |
|---|---|---|
| 官网入口 | [deepseek.com/harness](https://www.deepseek.com/harness/) | 在线快照 downloads/web/deepseek-com-harness.html |
| 文档站(用户向,中/英双语) | [develop/basic 系列](https://deepseek-harness.github.io/deepseek-harness/develop/basic/) | 本地副本 references/official-docs/docs/user/develop/basic/ |
| 框架概念 5 分钟版 | [Cordis Primer](https://deepseek-harness.github.io/deepseek-harness/reference/cordis-primer)(`docs/cordis-primer.md`,有 `.zh.md`) | 本地副本 references/official-docs/docs/cordis-primer.md |
| 框架 7 章动手教程 | [Cordis tutorial 01-07](https://deepseek-harness.github.io/deepseek-harness/develop/cordis-tutorial/)(均双语) | references/official-docs/docs/cordis-tutorial/ |
| 插件开发四步教程 | [第一个插件 → 工具 → 配置 → 打包安装](https://deepseek-harness.github.io/deepseek-harness/develop/basic/) | references/official-docs/docs/user/develop/basic/ |
| 框架能力三篇 | [生命周期 / 服务与依赖 / 事件系统](https://deepseek-harness.github.io/deepseek-harness/develop/framework/) | references/official-docs/docs/user/develop/framework/ |
| 实战两篇 | [三层能力拆分 / LLM 适配器](https://deepseek-harness.github.io/deepseek-harness/develop/practice/) | references/official-docs/docs/user/develop/practice/ |
| 用户指南 | [快速开始 / 模型配置 / Python SDK](https://deepseek-harness.github.io/deepseek-harness/guide/quickstart)(含 [python-sdk](https://deepseek-harness.github.io/deepseek-harness/guide/python-sdk)) | references/official-docs/docs/user/guide/ |
| 架构总纲 | [architecture.md](https://deepseek-harness.github.io/deepseek-harness/reference/)(改 `packages/` 前必读) | references/official-docs/docs/architecture.md |
| 扩展点全景 | [extension-cookbook.md](https://deepseek-harness.github.io/deepseek-harness/reference/cookbook/extension-cookbook)(feature → mechanism 表) | references/official-docs/docs/cookbook/extension-cookbook.md |
| 工具契约权威参考 | [adding-a-tool.md](https://deepseek-harness.github.io/deepseek-harness/reference/cookbook/adding-a-tool) + `dsh-tools` README | references/official-docs/docs/cookbook/adding-a-tool.md |
| 事件生产/消费矩阵 | [event-producer-consumer.md](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/event-producer-consumer.md)(生成式,全事件表;未上站) | references/official-docs/docs/event-producer-consumer.md |
| 每个子系统的服务/事件 API | [subsystems/*.md](https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/) 的生成式 Cordis API 区 | references/official-docs/docs/subsystems/ |
| Cordis 核心 API 参考 | [cordis-api/*](https://deepseek-harness.github.io/deepseek-harness/reference/cordis-api/context)(context、events、fiber、registry、service) | references/official-docs/docs/cordis-api/ |
| 上游 Cordis 框架 | [cordiverse/cordis](https://github.com/cordiverse/cordis)(README/docs 已下载) | downloads/github/cordis/ + references/upstream-cordis.md |
| Cordis 论文 | [cordiverse/paper](https://github.com/cordiverse/paper) | downloads/github/paper/ + references/cordis-paper-and-community.md |
| 生态/社区 | [社区开发文档与生态链接](../references/community-ecosystem.md)(topic 清单、awesome 列表、注册中心) | references/community-ecosystem.md + downloads/community/ |
| **官方自带插件开发技能(0.2 世代新增,务必先读)** | 无站点路由(在 preset 包内) | `references/official-plugin-dev-skill.md`(原文抄录)+ 上游 `packages/preset/agent-preset/skills/cordis-plugin-development/**` |
| **逐版本迁移指南(0.2 世代新增)** | 无站点路由 | `references/official-docs/docs/upgrade-guide/**`;本库综述 [migration-0.2.md](migration-0.2.md) |
| 会话格式版本状态 | 无站点路由 | `references/official-docs/docs/session-format-status.md` |
| 运行时自查(比文档更权威) | 会话内 `cordis_inspect_list` / `cordis_inspect_query`(Service / Event / Config / Tool / Slots / Theme) | — |

网站与本地文档是同源的:GitHub Pages 站点由 `website/docs.ts`(副本在 references/official-docs/website-docs.ts)把仓库 `docs/` 投影成中/英两棵路由树;`develop/basic/` 页面 = `docs/user/develop/basic/index.zh.md`。**想"离线全量"看官方文档,读 references/official-docs/docs/ 即可。** 线上 URL 与本地副本的**完整对照**(含 `en/` 英文投影、全部 reference/cookbook/cordis-api/subsystems 路由与 GitHub 直链)见 [links.md](links.md)。

---

## 2. 工程形态与开发环境

### 2.1 三种开发方式

1. **仓库内开发(scratch 目录)**:clone `deepseek-ai/deepseek-harness`,`pnpm install` 后建 `scratch-plugin/`,用 `--patch` 覆盖层挂载本地插件(`pnpm dsh web --patch ./scratch-plugin/cordis.yml`)。适合学教程、改核心。
2. **独立插件包(发布形态)**:一个 npm 包 + `cordis.patch.yml`,经 `dsh plugin --profile <name> add <包>` 装入 profile。适合对外发布。
3. **会话内直接装(0.2 世代官方首选路径)**:在工作区写 bundle,然后用 **`plugin_manager` 工具的 `install_bundle`**(`target` 传**绝对包目录**)装进当前 profile——插件立刻对该 profile 的所有会话生效并跨重启保留。官方明确:**不要**手写 profile 的 `package.json`/`cordis.patch.yml`,**不要**在 profile 目录里跑 pnpm;`install_bundle` 负责这些步骤,而且 profile 之外的每一次手写都需要单独审批。详见 [references/official-plugin-dev-skill.md](../references/official-plugin-dev-skill.md)。

### 2.2 三个概念、三种清单(package.json 的 `dsh` 键)

- **bundle(分发层)**:npm 包,`package.json` 声明 `"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }`;patch 里是"插入/覆盖插件行"的 YAML 数组。**`patch` 也可以是有序数组**:`{ "patch": ["./base.patch.yml", "./web.patch.yml"] }`,按序作为同一层应用,且每个文件里的相对插件路径**相对该文件所在目录**解析(校验失败信息:`dsh.bundle.patch must be a file path or a list of file paths`)。
- **profile(可运行组合)**:`$DSH_HOME/profiles/<name>/`,`package.json` 声明 `"dsh": { "profile": { "bundles": [...] } }`;由 `dsh plugin` / `plugin_manager` 维护,手写不允许。
- **client(浏览器半侧)**:`package.json` 声明 `"dsh": { "client": { "platform": "web", "inject": [...], "immediately": true, "external": [...] } }` 并导出编译好的 `./client`。`inject` **只决定激活顺序**(不是 cordis 服务注入),`immediately` 标记一阶段预取,`external` 列出基线之外的精确模块请求。客户端半侧只挂在**说明符恰为裸包名**的那一行上。
- 另有两个可选作者字段:`dsh.manifestVersion`(清单格式标识,当前 `1`,与 npm 版本、会话格式版本无关)与 `engines.dsh`(兼容范围声明,**当前安装器/加载器都不校验**;会被校验的是 `@deepseek-ai/dsh-*` 的 peerDependencies,见 §7.3)。类型定义来自 `@deepseek-ai/dsh-package-manifest`。

bundle 最小结构:

```
hello-plugin/
├── package.json       # dsh.bundle → cordis.patch.yml;可选 dsh.client + icon + exports
├── cordis.patch.yml   # - insert: [{ id, name: 'dsh-hello-plugin' }]
├── index.js           # export const name / export function apply
├── locale/en.json     # { "meta": { "title": "...", "description": "..." } }(Plugin Manager 展示用)
└── icon.svg           # 导出的 ./icon,≤ 256 KiB 且必须在包内
```

### 2.3 配置分层顺序(后层覆盖前层,逐行按 `id` 整段替换 config)

1. profile 的 `dsh.profile.bundles` 列表顺序(`@deepseek-ai/dsh-base` 永远第一层)
2. profile 自己的 `cordis.patch.yml`
3. 机器级 `$DSH_HOME/cordis.patch.yml`
4. 命令行 `--patch <path>`(argv 顺序)

patch 覆盖某行时**整行 config 被替换而非深合并**——覆盖方必须重述该行需要的全部键。**上游删行/改名后,targeting 该 id 的 override 会静默失效**(只在启动日志里留一行 `patch: entry <id> not found`),所以每次升级后都要跑一次 `--dump-config`。验证实际组合:`dsh --profile <name> --dump-config`。

### 2.4 常用命令

```sh
dsh --version                           # 先确认宿主版本(0.1.7-alpha.1 起 = 会话格式 V4)
dsh --profile web                       # 启动 Web UI
dsh --profile headless "任务文本"        # 一次性无界面执行
dsh --profile <name> --dump-config      # 打印实际生效的插件树;查 patch: entry not found
dsh plugin --profile <name> add <pkg>   # 安装 bundle(转发给 pnpm)
dsh plugin --profile <name> remove <pkg>
dsh plugin --profile <name> install     # 仅补装/修复 profile 依赖(自愈入口)
pnpm dsh web --patch ./scratch-plugin/cordis.yml   # 仓库内开发调试
```

会话内优先用 `plugin_manager` 工具(`list_plugins` / `list_bundles` / `install_bundle` / `set_plugin` / `set_bundle` / `remove_bundle`)而不是 shell:它把安装、bundle 选择、失败与 pending 状态一并返回。注意 `list_plugins`/`list_bundles` 在非 Full access 下同样需要审批,只在结果决定下一步时才调;**用免审批的 `cordis_inspect_query` 确认新行是否真的挂上**。`application: applied` 才算生效;`overridden` 表示被更高优先层盖住,`restart-required` 表示**尚未生效**。

---

## 3. 插件契约(必守规则)

### 3.1 三种插件形态

- **函数形态**(最常用):`export const name`、`export const inject`、`export function apply(ctx, config)`。
- **对象形态**:`export default { name, inject, apply(ctx) {} }`。
- **类形态**(提供服务时用):`export default class X extends Service { static inject = [...]; constructor(ctx) { super(ctx, '服务名') } }`。

### 3.2 生命周期状态机(Fiber)

```
PENDING → LOADING → ACTIVE
                 ↘ FAILED
ACTIVE → UNLOADING → DISPOSED
```

- `inject` 声明的服务未就绪 → 停在 PENDING;就绪才执行 `apply`。
- 依赖的服务消失(provider 被换掉)→ 插件自动卸载,服务恢复后自动重载。
- `apply` 抛错 → FAILED。
- 手动停用:`const fiber = ctx.plugin(plugin); await fiber.dispose()`(保证所有注册撤销、子插件递归卸载、异步清理完成后才 resolve)。
- 卸载时 disposer **逆注册序**触发,但多个异步 disposer 并发执行、无串行完成保证 → 有顺序依赖的清理放进**同一个** `ctx.effect()` 返回的单个 disposer 里串行 await。

### 3.3 注册即 effect

```ts
ctx.on('some-event', handler)          // 监听器:卸载自动移除
ctx.tools.register(tool)               // 工具:卸载自动撤销
ctx.llm.registerAdapter(names, adapter) // 适配器
ctx.effect(() => { const t = setInterval(...); return () => clearInterval(t) })  // 自定义资源
```

框架代管:`ctx.on` / 服务 `register()` / `ctx.effect()`。`HMR`(`@deepseek-ai/cordis-plugin-hmr`)正因注册全部可逆而成立。

#### 3.3.1 ⚠️ async `apply()` 的注册顺序(最容易写成静默缺陷的一条)

`apply` **可以是 async**,但**第一个 `await` 之后再做注册是错的**:从 `await` 返回到注册完成之间存在一个
卸载窗口——插件已开始 `UNLOADING`、effect 却还没建立,于是那次注册**既不会被记入 fiber 的 disposer 集合,
也不会报错**。表现为"插件装了、日志全绿、能力却时有时无"(高负载下才复现)。

**规则:注册必须在第一个 `await` 之前完成。** 需要异步数据才能决定注册内容时,先取数据再注册:

```ts
// ✅ 正确:await 只用于取数据,注册全部落在 await 之前的同步段之后、但不再 await
export async function apply(ctx) {
  const cfg = await loadRemoteConfig()        // 允许 await
  if (!cfg.enabled) return                    // 允许提前返回
  ctx.effect(() => ctx.tools.register(buildTool(cfg)))   // 注册紧随其后,不再 await
}
```

```ts
// ❌ 错误:await 之后才注册,卸载窗口内注册会丢
export async function apply(ctx) {
  ctx.on('a', handlerA)
  await something()
  ctx.tools.register(toolB)                  // ← 这次注册可能永远不会被撤销
}
```

**自查**:`dsh-plugin-dev check` 的 `redline-async-apply-registration` 就是本条的机械检测
(家族内唯一检测器,见 [`family-toolchain.md`](family-toolchain.md) §3.2)。
**官方契约**:`apply` 返回 disposer 函数 / `null` / `undefined` / `Promise<disposer>` / (异步)可迭代——
async `apply` 合法,但**必须返回 `Promise<disposer>`**,不能靠"await 之后再注册"来隐式注册。

### 3.4 服务(Service)

- **消费**:`export const inject = ['tools']` → `apply` 里 `ctx.tools` 已就绪。可选依赖:不写 inject,用 `ctx.get('metrics')?.method()`。
- **提供**:`class MetricsService extends Service { constructor(ctx) { super(ctx, 'metrics') } }` + 声明合并 `declare module '@deepseek-ai/cordis' { interface Context { metrics: MetricsService } }`;消费方 `inject = ['metrics']`。
- **隔离**:cordis.yml 用 `@deepseek-ai/cordis-plugin-group` + `isolate: { shell: true }` 让不同插件组各见各的服务实例(如不同 timeout 的 bash)。
- 内建服务一览(`ctx` 键):`sessions`、`systemPrompt`、`tools`、`agents`、`agentLoop`、`llm`、`skills`、`commands`、`approval`、`jobs`、`fs`、`shell`、`subprocess`、`terminals`、`sandbox`、`codeRuntime`、`sessionPersistence`、`settings`、`credentials`、`workspaceRegistry`、`goals`、`planMode`…… 完整清单与每个服务的公开方法见 `docs/capability-seams.md`(图谱)与 `docs/subsystems/*.md`(生成式 API)。**不要维护第二份静态清单,以生成式区域为准。**

### 3.5 事件(Events)

派发模式(事件契约的一部分,新事件必须用 `@mode` 标注)。**官方共五种**——
权威定义见 `references/official-docs/docs/cordis-api/events.md`(`'emit' | 'parallel' | 'serial' | 'bail' | 'waterfall'`):

| 模式 | await? | 顺序 | 返回值 |
|---|---|---|---|
| `emit` | 否 | 注册序 | 无 |
| `parallel` | 是 | 并行 | 无 |
| `serial` | 是 | 注册序 | 有(首个非空即止) |
| `bail` | 否 | 注册序 | 有(首个非 `null/false/undefined` 即短路,**停止后续**) |
| `waterfall` | 否 | 注册序 | 有(管线结果,逐级传递) |

- **`serial` 与 `bail` 的区别是"停不停"**:`serial` 在首个非空值后停止后续监听器;`bail` 同样短路,但语义归类为"放弃/否决",`ctx.bail` 是其对应调用式。二者都**不是** `emit`(`emit` 永远跑完所有监听器)。
- **waterfall 铁律**:监听器签名 `(payload, next)`,**必须调 `next()` 才能下传**;不调即短路(这正是拦截/网关的语义)。协作式监听器改共享对象后 `next()`;替换结果的监听器负责让下游只看到替换后的结果。仅在"必须最先执行"时才用 `prepend: true`。
- **类型安全**:`declare module '@deepseek-ai/cordis' { interface Events { 'my-plugin/ready': (p: {id: string}) => void } }`。
- **命名空间**:`namespace/action`(`agent/step`、`tools/result`、`session/event`……)。
- **注意区分**:`turn/*`、`step/*`、`tool/call`、`tool/result`、`compaction/*` 是**持久化会话事件类型**(在 `session/event` 里以 `event.type` 出现),不是同名 Cordis 事件。
- **SessionEvent switch 规则**:`SessionEventMap` 是 merge-extensible union,对 `SessionEvent` 的 switch **禁用 `assertNever`**——处理已知 case 后落入文档化 `default` 放行;closed union(如 `StreamChunk`)才以 `assertNever` 收尾。
- **会话事件红线(官方 practices 原文)**:**绝不要用新的 `type` 追加会话事件**——读方只在事件带信封 `ignorable: true` 时接受未知类型,而运行时 `Session.append()` 写不了这个标记(第三参仅为 surface 事件的 `SurfaceIntent`),写了会让整个会话**打不开**。插件状态从既有事件推导,或放进 inspection 找到的 storage 服务。类型层的 merge-extensible 是给宿主新增事件用的,不是插件写入口。见 `references/official-docs/docs/subsystems/session.md` 与 `docs/cordis-primer.md`。
- 全量"谁发谁听"矩阵:`docs/event-producer-consumer.md`(副本在 references/official-docs/docs/)。

### 3.6 配置(Config)

```ts
import Schema from '@deepseek-ai/schemastery'

export interface Config { greeting: string; maxRetries: number }
export const Config: Schema<Config> = Schema.object({
  greeting: Schema.string().default('Hello'),
  maxRetries: Schema.number().default(3),
})
export function apply(ctx: Context, config: Config) { /* config 已校验+补默认 */ }
```

- **不能用普通对象当 `Config`**——必须是 Schemastery schema(实现 Standard Schema)。
- 校验发生在加载期;非法配置**响亮失败**(fail loud),绝不明吞。
- **红线:插件里不得硬编码可调参数**。判断标准:能否只改 cordis.yml 而不用改代码。协议常量、外部规范、安全不变式除外。
- 引用表达式 `!!js`(注意双感叹号)在**该插件的注入服务就绪后**才求值;其余元数据保持字面量。
- 配置热更新 = 卸载旧实例 + 装载新实例(effect 保证不残留旧注册)。

### 3.7 仓库级红线(写进 AGENTS.md,违反会挂门禁)

1. **注册即 effect**:所有贡献走 `ctx.effect()` / `ctx.on()`;registry 的 `register()` 返回 disposer。
2. **waterfall 监听器必须调 `next()`**。
3. **模型可见 ⟺ 已记录**(Model-visible ⟺ logged):任何进入模型请求的内容必须能从会话日志重建;新增模型可见输入必须新增会话事件。~~运行时不变式会断言这一点~~ —— ⚠️ **该强制手段已于 `0.2.1-alpha.1` 随运行时 invariants 整体移除**(`upgrade-guide/v0.2.0-rc.2/remove-runtime-invariants`),所以这条**现在完全靠评审与自觉**,没有任何机械兜底。见 §3.6 与 [`migration-0.2.md`](migration-0.2.md) §4。
4. **跨边界 opaque id 用 branded**:`Branded<B>`(`dsh-brand`,纯类型、零运行时依赖),从不裸 `string`;构造走 per-type factory(`SessionId` / `CallId` / `JobId` / `GoalId` 等),防止不同 id 在类型层互换。
5. **会话事件版本规则**:`SessionEventMap` 成员默认 required-on-read——不认识该事件类型的 build 一律拒绝整份日志,除非该事件带信封的 `ignorable: true`。**插件不得新增事件类型**(见 §3.5 红线):`Session.append()` 写不了信封标记。只有结构格式变更才 bump `SESSION_FORMAT_VERSION`(当前 **4**;V3→V4 把 results 提升为 tool-role 消息、把消息 source 改为生产者自有的 `source.kind`,V3 读方拒绝 V4 日志)。新增**模型可见输入**由宿主侧事件承担(红线 3),插件只消费。
6. **注入的提示词段落要短、且以角色句开头**:插件写进系统提示词的段落(`systemPrompt.section()` 的正文)**第一句应当是说明"你是谁/做什么"的短句**,整段保持精简。原因有二:它是每轮请求都付费的固定前缀;而且段落开头若不是完整句子,模型会把后续内容当作它的延续来读。**自查**:`dsh-plugin-dev check` 的 `redline-persona-role`(段落首句需以 `。.?!` 收尾、首句 ≤200 字符、整段 ≤600 字符)。**这条是"工程卫生"而非官方硬门禁**——官方文档没有对应条款,别在 README 里把它说成官方要求。

---

## 4. 开发一个工具(最常用路径)

教程路径:docs/user/develop/basic/tool.md;权威参考:docs/cookbook/adding-a-tool.md;生产级范例:`packages/shell/tool-bash`(三层包结构)。

### 4.1 最小形态

```ts
import { readFile } from 'node:fs/promises'
import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'

export const name = 'my-tool'
export const inject = ['tools']

export function apply(ctx: Context) {
  ctx.tools.register(defineTool({
    name: 'read_file',
    description: 'Read a file from disk.',       // 模型看到的内容
    parameters: {
      path: { type: 'string', required: true, description: 'Absolute path' },
      limit: { type: 'number' },                 // 缺省即可选
    },
    output: {
      schema: { type: 'string' },                // 规范值(canonical value)的 schema
      render: (_args, value) => [{ type: 'text', text: value }],  // 模型面向内容
    },
    async execute(args, exec) {
      return readFile(args.path, { encoding: 'utf8', signal: exec.signal })
    },
  }))
}
```

schema 自动汇入系统提示词组装;模型可用原生函数调用或 Code Mode 直接 `await tools.read_file(...)`。

### 4.2 execute() 契约硬规则

1. **参数已被校验**:`defineTool` 在执行前按统一 `ParameterSchemaSpec` 校验(类型/必填/字面量/exact-one/嵌套);`execute` 内 `args` 即 `InferArgs` 类型。DSL 表达不了的约束(非空串、正数、跨字段规则)仍需自检。裸 JSON-Schema 工具(如 MCP 来源)自管输入校验。
2. **注册借用你的只读定义**:注册后不得改 schema 或换回调;热换工具 = 释放旧 effect + 注册新工具。
3. **执行身份不可变**:`arguments` 在策略开始前被一次性无损 JSON 快照并冻结;`exec.token` 为不透明 token;`callId/name/arguments/agent/token/signal/parent` 全程不可变。`args` 视为只读。只有 `tools/execute` 包装层可临时替换 `exec.signal`(且不能删除)。
4. **声明并返回一个规范 JSON 值**:`output.schema`(`ValueSchemaSpec`)可为对象/数组/标量/null 根;`execute` 只返回推导值;不要返回内容块、不要让调用方从散文里解析 id。
5. **抛错或返回非法值 ⇒ `isError`**:基础设施故障用 throw;成功的领域结果放进规范值(即使渲染层解释一个"不理想"状态,如非零退出码)。
6. **尊重 `exec.signal`**:触发时取消在途工作(前台工作与其绑定;后台任务改用任务自带取消信号)。
7. **`presentationMeta(args, value)`(可选)**:从同一规范值导出可回放的 JSON 持久化到 `tool/result`,供 UI 卡片回放。
8. **异步通知用 `exec.agent`**:`agent.inject({ content, source: { kind: '<你的 kind>' } })` 把上下文注入**下一条**模型请求(不是唤醒;空闲 agent 保持空闲)。⚠️ **本世代硬性变化**:`source` 不再接受退役的 `{ kind: 'plugin', plugin: '<name>' }` 包装——会话格式 V4 在**消息准入**处直接拒绝它(`packages/core/session/src/index.ts` 的 source 准入;官方 `docs/cookbook/adding-a-tool.md:49` 明写)。插件自己的 kind 必须先经声明合并注册,否则先用宿主已有的 kind:

```ts
declare module '@deepseek-ai/dsh-llm' {
  interface MessageSourceMap {
    'my-plugin': { kind: 'my-plugin' }   // kind 必须与 MESSAGE_SOURCE_KIND_PATTERN 匹配
  }
}
```

`agent.inject()` 只是 `send(input, 'next-step', false)`(`packages/core/agent-loop/src/agent.ts:171`);同族还有 `followup()`(下一回合,唤醒)与 `steer()`(下一步,唤醒)。对已释放的 agent 要 try/catch。

### 4.3 后台任务(长任务)

`run_in_background` 由生产者配置门控,然后 `ctx.jobs.start({ kind, label, owner: exec.agent, run })` 注册。成功分支返回类型化句柄(如 `{ kind: 'background', jobId }`)——**Code Mode 永远不得解析人类散文去恢复 id**。`ctx.jobs.start()` 发布 id 后改用任务自有取消信号:外层调用取消只停止等待、不杀已发布任务;`job_kill`、owner 释放、服务卸载负责其生命周期。参考 `dsh-tool-bash`。

### 4.4 策略钩子(按需选择)

- `tools/pre-execute`:可重排的 allow/deny/ask 门(permission-gate 范例见 extension-cookbook);返回 `PreToolDecision`(`allow | deny | ask`)。不能改写 `exec.arguments`。
- `ctx.tools.guard()`:pre-execute 之后的**单调最终 deny**(后注册的 waterfall 监听器无法翻转)。
- `tools/execute`:包裹实际派发生命周期(超时/重试/指标),仅可换 `exec.signal`。
- `tools/post-execute`:替换展示内容或值、阻断结果、附加模型上下文(改值会重新校验重渲染)。
- `tools/result`:只读观察不可变最终结果(审计/指标/采集用这个;要改结果才用 post-execute)。
- 沙箱化可放进工具执行器实现内;权限系统/AskUserQuestion 经 `tools/pre-execute` 返回 `ask` + `ctx.approval`。

### 4.5 Code Mode(免费获得)

每个可见工具自动成为 `await tools.<name>(args)`,参数/返回类型从同一 schema 推导(`ToolArgsMap`/`ToolOutputMap`)。成功 resolve 到**策略后的最终规范 JSON 值**;失败 reject 真实的 `ToolCallError`(只有 `name/toolName/message` 可查)。设计 `output.schema` 时要当作编程 API 设计:直接返回句柄与字段;人类解释放 `output.render`。

### 4.6 UI 卡片(presentCall/presentResult)

- 卡片类型:`generic`(默认)/ `terminal` / `diff` / `search` / `read` / `web`(`kind: search|fetch`)。
- **纯函数硬规则**:卡片方法在实时流与日志回放都会执行 ⇒ 只能依赖 `args`(+ 结果),**禁止 I/O、会话状态、时钟/随机**。diff 由 args 推导;需要旧文件内容/工作目录时——放进持久化结果元数据或 UI 适配器,而不是 presenter。
- **UI 专属格式不进模型结果**;`defineTool` 对展示路径软校验(老日志参数不崩溃,回退 generic)。
- **中性词表在 `dsh-tools`**;工具永不 import UI/传输类型。

### 4.7 两个新钩子(0.2 世代)

- **`ToolDefinition.projectContent(exec, result)`**:在 `tools/post-execute` 策略**之前**安装"执行期准备好的内容";策略的替换仍然权威(下游只看到策略结果),绕过 post-execute 的管线失败**不会**调用它。适合"结果本身是结构值、但要给模型附一段确定性正文"的场景。
- **`ToolSchema.deferLoading: true`**:标记该工具的 schema **延后加载**,经 `schemaOf()` 进入模型可见 schema。用于工具很多、不想每轮都付全量定义 token 的场景(与 0.1.7-rc.2 起的"动态增加工具且不破坏 KV Cache"配套)。
- **`PreToolDecision` 的 `ask` 新增 `displayReason`**:`{ kind:'ask'; reason?: string; displayReason?: { readonly en: string; readonly [locale: string]: string } }`——`reason` 是**审计用**的批准理由,`displayReason` 是**本地化**的提示文案(审批卡片会按界面语言显示,并引导 agent 用提问语言解释理由)。

### 4.8 插件 UI(Web)与性能:官方 `practices.md` 的硬规则

完整要点见 [references/official-plugin-dev-skill.md](../references/official-plugin-dev-skill.md) §3.2/§3.4;开发 UI 插件前**至少**记住这五条:

1. **先选渲染面**:插件页面必须渲染成 **slot 里的 React 组件**;**不要**从 Host 提供 HTML 再 iframe 嵌进去(iframe 拿不到宿主主题 token、明暗切换与 `ctx.locale`)。
2. **样式只用主题 token**(`cordis_inspect_query` 的 `Theme` 列出的 `--dsw-alias-*`);字面颜色只用于 artwork。**不要** `require` 任何 Harness Client 包(`@deepseek-ai/dsh-client-ui-primitives` 等)——它们会无预警变化,而**抛错的组件会把整个 slot 条目搞白**(console: `slot entry crashed in '<slot>'`)。要对齐宿主就"把 primitive 的标记/CSS/行为抄进插件"。
3. **只经 slot 贡献**:`ctx.slots.inject(ownerKey, () => ctx.slots.register(...))`;会话数据经 slot props 的 selector hook 读**最小切片**;**不要**在组件外写 DOM,也不要往 `document.body` 追加第二个应用。
4. **加 Chat 行**:`ctx.uiConversation.events.register()` 注册事件定义 + 在 `conversation.chat.node` slot 下按定义的 `kind` 注册视图(分页/落位/增量组装由 Conversation 层负责)。Client 需要会话派生值时,在 Host projection 上声明 **`wire.view`**——Client **不自己 fold 会话事件**。
5. **性能**:per-session 状态放进 `ctx.sessionProjections`(`apply(state, event)` 纯同步、忽略的事件返回同一引用;`stateOf()` 读;纯 JSON + 改语义就 bump `stateVersion`);**等持久事件**(`turn/end`/`assistant/message`/`tool/result`),实时 token 从 `agent/assistant-stream` 渲染;**不要轮询 `agent/status`**;注意 `whenIdle()` **不**等于"一次 followup 结束"。

另有一条最容易踩的工程坑:**`dsh.client` 的 `inject` 必须是静态数组**(cordis 不接受函数形式)。曾有一个已发布的 0.2.0 插件把它导出成函数 → 声明为零依赖 → `apply()` 抛 `cannot get property "sidebarRightTabs" without inject` → 日志只留一行 `web boot: 1 entry did not activate` → **插件自愈机制把整个 bundle 禁用了**。单测要断言"`inject` 是数组且覆盖 `apply()` 触及的每个服务"。

**工具的 `parameters` 是 DSH 自己的方言,不是 JSON Schema**;Remote 服务调用是**位置参数**而不是对象参数(`get()` 能用,最有欺骗性);往 `ctx` 上写**未声明**的属性会抛错并可能静默杀掉整个插件的激活——可选服务一律 `ctx.get(name)`,**`ctx.<service>` 只对自己 `inject` 里声明过的服务合法**。

### 4.9 插件内如何发布一个 skill(本仓库就是参考实现)

一个插件可以**自己携带一份 agent 技能**——这是把"知识/流程"交付给宿主的标准做法,也是本仓库
(`dsh-plugin-guide`)唯一在做的事。机制曾经在社区资料里被写错成"systemPrompt section + 工具注册",
**那不是这条路**。

**契约**(官方 `docs/subsystems/skills.md`):

- `ctx.skills` 是 **provider 注册表**,注册是**同步**的,必须在 `apply()` 内完成。
- 插件走的是**运行时贡献**入口 `ctx.skills.register(definition)`,其类型为
  `SkillRegistration = Omit<SkillDefinition, 'invocation' | 'provider'> & { invocation?; provider? }`
  ——`invocation` 省略即模型与用户两侧都可见,`provider` 省略即落到注册表自有的 runtime provider。
- **返回的是 Cordis effect disposer**:撤销该贡献并使发现缓存失效。所以照例写成
  `ctx.effect(() => ctx.skills.register({...}))`。
- 要携带随包资料,给 **`resourceBase: { kind: 'directory', path: <包目录> }`**;技能正文里的
  `./guide/...`、`./references/...` 相对路径就按该目录解析。**这是"渐进披露"得以成立的关键**:
  技能正文只放路由,重资料留在包里按需读,不必一次塞进模型上下文。

**最小可用实现**(取自本仓库 `index.js`,可直接抄):

```js
import { readFileSync } from 'node:fs'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'

export const name = 'my-skill-plugin'
export const inject = ['skills']

const packageRoot = dirname(fileURLToPath(import.meta.url))

export function apply(ctx) {
  const { description, body } = splitFrontmatter(readFileSync(join(packageRoot, 'SKILL.md'), 'utf8'))
  ctx.effect(() =>
    ctx.skills.register({
      name: 'my-skill-plugin',
      source: 'bundled',
      description,                                     // 决定"何时被加载"
      content: body,                                   // 技能正文
      resourceBase: { kind: 'directory', path: packageRoot },
    }),
  )
}
```

**四个实测要点**(每一条都踩过):

1. **`description` 就是触发器,要写"何时使用"而不是"这是什么"。** 宿主只把 name + description 放进目录给模型挑,
   所以写 `Use when developing, reviewing, packaging, debugging, or answering questions about …`
   这类带场景动词的句子,而不是名词短语。
2. **解析 frontmatter 要容忍 CRLF。** 用精确的 `startsWith('---\n')` 会在 Windows 检出(`core.autocrlf=true`)
   上失配,后果是**静默退化**:描述掉回兜底字符串(技能变得不易被选中),且**整份文件连同 frontmatter 被当成技能正文**注入。
   本仓库为此加了 `.gitattributes`(`* text=auto eol=lf`),并把解析改成行尾无关。
3. **截断 frontmatter 与裁掉正文前导空行是两件事。** 只做前者,正文会以换行开头;宿主的 filesystem provider
   自己会裁,但**不是所有 provider 都裁**,自己裁更稳。
4. **单测要真的加载入口模块。** `index.js` 长期零测试,缺陷因此活到发布之后;测试只需一个
   `{ effect, skills: { register } }` 桩,就能断言"注册的 name、用的是 frontmatter 描述还是兜底、
   `resourceBase` 是否指向包目录"。本仓库 `tests/entry.test.ts` 就是这 8 条断言。

**与"工具"的区别**:注册工具(`ctx.tools.register`)会让**每一次请求**都付该工具定义的 token;
注册技能只在模型**按描述选中**它时才加载正文。给 agent 交付"资料/流程"用技能,交付"可执行动作"才用工具。

**相关**:`references/official-plugin-dev-skill.md`(官方自带开发技能的要点)、
`references/official-docs/docs/subsystems/skills.md`(完整契约与 ranked local discovery 表)。

---

## 5. 能力分层:Service Definition / Provider / Consumer

通用可替换能力(如 bash)拆三层:

- **Service Definition**(如 `dsh-shell`):声明 Cordis 服务(`ctx` 键)+ 请求/结果类型(抽象类,不是 TS interface)。
- **Service Provider**(如 `dsh-bash-local` / `dsh-bash-sandbox`):实现。
- **Consumer**(如 `dsh-tool-bash`):消费(`inject: ['shell']`),通常注册为工具。

规则:**不提前拆**(只有角色需要独立演化才分包);Provider 与 Consumer 互不依赖,都只依赖 Definition;默认值在显式 `resolve(request): Spec` 步骤解决(显式 > 隐式)。一个缝(seam)= 三个角色的完整能力,缺一个角色不叫缝。完整示例见 docs/user/develop/practice/(三步代码齐全)。

LLM 适配器同理:继承 `LlmAdapter` 实现 `stream(options)`,`ctx.llm.registerAdapter(providers, adapter)`;`StreamChunk` 协议要点:`block-start/text-delta/block-end/tool-call-delta/usage/finish`,`usage` 在 `finish` 前,`finish` 是最后一块;无法满足的字段抛带稳定 code 的 `LlmError` 而不是静默丢弃。完整见 docs/user/develop/practice/llm-adapter.md。

---

## 6. 扩展点全景(feature → mechanism)

新行为挂到已文档化的扩展点;**改 agent-loop 本身必须同步更新 architecture.md**(插件开发几乎永远不需要改 loop)。来源:architecture.md「Where new behavior goes」+ extension-cookbook「feature → mechanism map」。

| 想做什么 | 机制 |
|---|---|
| 加模型提供商 | `ctx.llm.registerAdapter` |
| 加模型可调能力 | `ctx.tools.register`(schema 自动进提示词组装) |
| 给某个会话换能力集 | agent preset 组合;服务行配 `isolate` realm |
| 加 shell 执行 | 注册 `ctx.shell` 后端(本地实现经 `ctx.subprocess` spawn) |
| 加持久终端 | 注册 `ctx.terminals` 后端 + `dsh-tool-terminal` |
| 加人类命令(斜杠命令) | `ctx.commands` 注册;不经模型回合直接派发 |
| 加后台任务 | `ctx.jobs` 注册;`job_*` 工具收集/停止 |
| 加文件访问/策略 | 注册 `ctx.fs` provider 或监听 `fs/*` 事件 |
| 限制进程 | `ctx.sandbox` 后端;消费者 spawn 前包 argv |
| 拦截请求/工具/回合 | `agent/*`、`tools/*` 事件;`agent/turn-stopping` 停回合 |
| 注入模型上下文 | `agent.inject()`;进入下一条被采纳的请求 |
| UI/编辑器集成 | 驱动 `ctx.agents`;从 `session/event` 渲染 |
| Web 会话业务节点 | 注册 `ConversationNodeDefinition` + keyed 渲染器 |
| 持久会话状态 | 扩展 `SessionEventMap`;从日志渲染与回放 |
| 会话标题 | 注册唯一的 `ctx.sessionTitle` provider |
| 同会话目标 | `ctx.goals`;经 `agent/*` 继续 |
| fork 活跃会话 | `ctx.sessions.fork(source, boundary?, childSessionId?)` |
| 只对一个 agent 注册 | 用该 agent 的 `agent.ctx`(作用域注册) |
| Hook 系统(用户/项目级) | `agent/created`(serial)、`agent/pre-step`、`agent/request`、`tools/pre-execute`、`tools/post-execute`、`agent/turn-stopping` 监听器 |
| 上下文压缩 | `ctx.compaction` 缝 + `dsh-compaction-basic`;自动压力走 serial `agent/pre-step`,溢出恢复走 `agent/request-error` |
| 系统提示词配置 | `ctx.systemPrompt.section()`(带排序与作用域内遮蔽) |
| 计划模式 | `@deepseek-ai/dsh-plan-mode`(`plan/mode` 日志态、`/plan`、`exit_plan_mode` 工具) |
| 会话预设组合 | preset 层:per-session agent composition from preset cordis.yml(`dsh-preset`) |
| 待办列表 | `dsh-todo` 的 `todo_write` 工具(状态进会话日志,可作参考实现) |
| 循环卫生/工具超时 | `dsh-guard`:重复调用提醒 + `tools/execute` 截止时间强制 |
| 子代理委派 | `ctx.subagents` provider 注册表 + `dsh-tool-subagent`(现有多家 provider:in-process spawn / fork / `dsh-subagent-acp` / `-codex` / `-claude-code` / `-dsh-sdk`) |
| 多代理编排 | `ctx.workflowEngine` 缝(Definition/Provider/Consumer)+ PTC workflow engine + `workflow`/`ralph` 工具 Consumer |
| MCP | 每服务器一个插件:发现工具 → `ctx.tools.register()` |
| Skills | ⚠️ **不是** section + 工具注册:插件用 **`ctx.skills.register()`** 注册一个运行时技能(`SkillRegistration` = `SkillDefinition` 去掉 `invocation`/`provider`,两者可省略走默认),**在同一 `apply()` 内同步注册**,返回的 disposer 撤销贡献并使发现缓存失效。随包携带资料时给 **`resourceBase: { kind: 'directory', path: <包目录> }`**,技能正文里的 `./...` 相对路径即按该目录解析。官方参考实现是 `dsh-skill-badge`(注册一个不可变的 `bundled` 候选 + 暴露打包资源目录)。**本仓库 `index.js` 是第二个可跑的例子**——见下方 §"插件内如何发布 skill" |
| 定时任务 | 注册模型可调的调度工具;定时器 → 空闲 `followup(source:{kind:'cron'})` / 忙碌 `inject()` |
| 遥测/回放 | `session/event` → JSONL;回放 = `sessions.create(id, {seed})` |
| 上报通道(0.2 新增) | `ctx.otel`:`createEventReporter(options)` / `createSessionLogReporter(options)`——自带传输与批处理,未用时**不分配** provider/transport;**消费者必须在自己 fiber 释放时 drain 通道** |
| 产品分析事件(0.2 新增) | `ctx.productAnalytics`(`ProductTelemetry`)——**只发显式选定的字段,不自动采集** Session 数据或标识;Desktop 消费者在 `packages/client/product-analytics`,普通 Web 不采集 |
| 语音输入(0.2 新增,实验) | Host 侧三角色(Service Definition / SenseVoice Provider / Remote Consumer);浏览器侧 `TranscriptionRequest`,UI 挂 `conversation.input.activity` 与 `plugins.bundle.activation` |
| Claude Code mod 桥(0.2 新增,实验) | `defineMod({ name, version, root, userConfig, register })` 挂在 `cordis.yml` 的 bridge 行之后;类型从 `@deepseek-ai/dsh-experimental-claude-code-mods` 导入。**官方安全提示:mod 不加沙箱,只挂你愿意当插件运行的 mod** |
| per-session 派生状态(性能首选) | `ctx.sessionProjections`:纯同步 `apply`、忽略的事件返回同引用、`stateOf()` 读、纯 JSON + `stateVersion`;Client 需要派生值时在 Host projection 上声明 `wire.view`(**Client 不自己 fold 会话事件**) |
| 热重载 | 所有注册是 effect → vendored HMR 天然可用。**但**:改 profile 的 `cordis.patch.yml` 会触发重载并可能丢工具/杀在途 turn(见 §7.0 警告与 `unfixed-issues.md` T1)——运行中不要改 |

Hook 的"原生 hook"就是挂在拦截点上的普通 Cordis 插件,不需要外部协议;`dsh-hooks-claude-code`/`dsh-hooks-codex` 是把 Claude Code/Codex hook 配置映射到这些扩展点的桥。

---

## 7. 打包、安装与分发(踩坑重点)

### 7.0 插件形态选型:bundle vs 纯 cordis(两条官方安装通道)

> 时间线(社区实测记录,归档仓库 `vlln/plugin-registry`,**该仓 2026-10-03 已归档**,继任者 `vlln/dsh-skills`):官方 0809 推出 repository-plugin 机制(`.dsh-plugin`),**0811 已从 `vendor/loader/src/repository.ts` 移除**(−258 行,`plugin_search/install/uninstall/status` 四个工具随之不复存在)。此后外部插件只有 web profile 一条官方路径,按是否声明 `dsh.bundle` 分两类。
> **预览期漂移提示**:官方文档里可能仍有残留旧表述(如 `docs/subsystems/skills.md` 的 "repository plugins land in the global layer"),且服务名在快照间会改名(0812 批量重命名:`httpServer→webServer`、`tasks→jobs`、`bash→shell`、`compact→compaction` 等 17 项)——遇到矛盾以**当前宿主的生成式参考**(subsystems 页 Cordis API 区)与社区事实源(`references/community-repo-deep-dive.md` §1.5)为准。

| 需求 | 类型 | 安装通道 | 生效 |
|---|---|---|---|
| 带组合层(多个 insert/config/disabled 行随包分发、官方 `dsh.client` UI 组件) | **bundle 插件**(`"dsh":{"bundle":{"patch":"./cordis.patch.yml"}}`) | `dsh plugin --profile <name> add <pkg>` → 进 `dsh.profile.bundles` 层栈 | **重启生效** |
| 单个 Cordis 插件(Node 工具 / 服务 / 自渲染 UI) | **纯 cordis 插件**(无 `dsh.bundle`,`main` 指向 Cordis entry) | `dsh plugin add` 装依赖 + 在 profile `cordis.patch.yml` 加 insert 行 | **配置 HMR 实时生效** |

安装状态管理文件:profile 的 `package.json`(`dsh.profile.bundles` 层栈)+ `cordis.patch.yml`(insert 行与 disabled 启停,HMR watched)。
Git 源安装支持子路径选择器:`dsh plugin --profile demo add "github:owner/repo#<ref>&path:<subdir>"`(构建产物已入库时可免构建直接装)。

> ⚠️ **"配置 HMR 实时生效"有代价(本世代实测,见 guide/unfixed-issues.md T1)**:`dsh-hmr` 精确监听 `profile/package.json`、`profile.patchPath`、`$DSH_HOME/cordis.patch.yml`,**任何写入**(包括逐字节相同的重写)都触发重载。实测后果:正在跑的会话会丢掉 preset 贡献的全部工具(65 → 41)、在途 turn 被杀,最坏情况重建 `session-controller` 时抛 `file-upload: Agent resolver is already registered` 并**永久失效**(只能整进程重启)。**结论:不要在会话运行期间改 profile patch**;配置改动走 `plugin_manager` / `dsh plugin` 入口,必须手改就先停进程。

### 7.0.1 展示元数据与图标(0.2 世代新增的硬性交付项)

Plugin Manager 与 Settings 会**在不激活插件的前提下**读展示信息,所以"能跑"不等于"交付完成"。官方清单(`references/official-plugin-dev-skill.md` §3.1):

- `locale/en.json`(以及用户语言,如 `locale/zh.json`)写 `{ "meta": { "title": "...", "description": "..." } }`,并在 `exports` 里导出 `"./locale/*.json"`;
- 图标导出为 `"./icon"`(包根插件也可在 `package.json` 顶层写 `icon`,它优先于 `./icon`);支持 SVG/PNG/JPEG/WebP,**≤ 256 KiB**,realpath 后必须仍在包内;
- `files` 必须包含 patch、locale、图标与**每个运行时文件**——但**本地目录安装是链接 checkout,`files` 不过滤链接目录**,要直接核对文件;
- **子路径插件(`my-plugins/search`)不是包,永远不读 `package.json`**:标题/描述走 `my-plugins/search/locale/*.json` 的 `meta.*`,图片走导出的 `my-plugins/search/icon`。
- 可选作者字段:`dsh.manifestVersion`(当前 `1`)与 `engines.dsh`(**当前不校验**,只作声明)。类型来自 `@deepseek-ai/dsh-package-manifest`。
- **真正会被强制的是 peerDependencies**(见 §7.3):`dsh plugin add` 会用运行时的版本比对插件声明的 `@deepseek-ai/dsh-*` 范围,不匹配即**拒绝安装**。

### 7.1 从 GitHub 安装的 build-script 坑

`dsh plugin add github:you/hello-plugin` 拉的是**源码不是构建产物**:不会跑 `build`,TS 包没有 `lib/` 会加载失败。双方配合:

- **作者**:写 `prepare` 脚本(git 安装后 pnpm 会执行)自包含地构建发布入口——不得假设 monorepo 兄弟目录等 dev-only 上下文。官方范例见 [publish.md](../references/official-docs/docs/user/develop/basic/publish.md)(其引用的 turtle-ui 仓库 08-14 核查已 404);活范例参考 [plugin-template](https://github.com/omdsh-dev/plugin-template) 的 `scripts/prepare.mjs`:`prepare` 用专用 tsdown 配置转译 `src/`,无 project references、无类型检查。
- **用户**:pnpm ≥10 默认拒绝运行 git 依赖的 `prepare`,第一次 `add` 会失败并在 profile 的 `pnpm-workspace.yaml` 加:

```yaml
allowBuilds:
  dsh-hello-plugin: true
```

- 该 allowlist 本质是"**允许该包在安装时于沙箱外执行你的机器代码**":只放行可信源,并 pin commit(`github:you/hello-plugin#<sha>`)。
- 不想让用户放行 → 发布构建产物:npm 发布带 `lib/`,或 `pnpm pack` 出 tarball(`dsh plugin add ./xxx.tgz`)。

### 7.2 给 bundle 自己的命令行

bundle 挂一个普通 provider 插件:`inject = ['cmdlineArgs']`,用 `@deepseek-ai/dsh-cmdline` 的 `parseCmdline` 解析自己的 commander program,从 action 提供 app 自有服务;其余行经 `!!js ctx.myAppStartup.port ?? 8080` 读取(部署值作回退)。`--help` 时 provider 不提供服务 → 依赖它的行不激活。

### 7.3 常见坑清单(社区踩坑档案 omdsh-dev/dsh-plugin-dev + plugin-registry + 官方 postmortem,全部实测)

**身份与依赖(最致命)**
- **cordis 双副本 / 双 Cordis 分裂**:插件构建时若从 `.pnpm` 副本解析 cordis,与 harness 的 vendored 副本是"两个模块",`declare module` 增强合并不了 → 报 `Property 'tools' does not exist on type 'Context'`。构建期把 cordis 解析到 harness 的 `vendor/cordis`;npm 安装路径下 peer 必须与宿主同一身份——**scoped `@deepseek-ai/cordis` 与 unscoped `cordis` 混用同样分裂**(dsh-tools 的类型只增强 scoped 版本)。独立包把 cordis 设为 peerDependency(+ dev),版本对齐宿主。
- **官方 `@deepseek-ai/*` 包曾未发布公共 npm**(rc 早期):社区 bundle 的 `dependencies` 留空,靠 profile 的 pnpm 闭包 flat fallback(`$DSH_HOME/profiles/node_modules`)注入;声明了反而解析失败。rc.6 起公开包可用(from-scratch 教程锁 `0.1.0-rc.6`、cordis `4.0.1`),两条时间线的资料都要知道,按当时宿主版本取舍。
- **npm `latest` 标签是过期版本**:库包(`dsh-tools`、`dsh-base`、`dsh-headless`、`dsh-session-persistence-jsonl`、`dsh-settings`、`dsh-workflow` …)的 `latest` **至今**钉在 `0.0.1-rc.1`,所以写 `"latest"` 会装到远古版本,必须写死版本或钉 `next`/`alpha`。**2026-10-06 复核**:`@deepseek-ai/dsh` `latest`=`next`=`0.2.0-rc.2`、`alpha`=`0.2.1-alpha.1`;dsh-tools / dsh-base / dsh-headless / dsh-session-persistence-jsonl 的 `latest` 仍 `0.0.1-rc.1`、`next`=`0.2.0-rc.2`、`alpha`=`0.2.1-alpha.1`;`@deepseek-ai/cordis` `latest`=4.0.4 并有新标签 `dsh-0-2-1-alpha-1`=4.0.5-alpha.1;`@deepseek-ai/schemastery` `latest`=3.18.4;`create-dsh-plugin` `latest`=0.2.3;dsh-core、dsh-sdk 仍 404。历史两次复核(2026-08-14 与 09-04)的数值保留在上一条记录里,可见"`latest` 陷阱"从 rc 早期持续到 0.2 世代。**无作用域 `dsh` 包是无关项目 node-dsh**("A shell written in JavaScript")——官方 CLI 包是 `@deepseek-ai/dsh`,别装错。
- **profile 内安装的 `@deepseek-ai/*` 会静默遮蔽运行时自带版本**(解析优先级 `profile > runtime`):把共享实例的 dsh 包写进 **dependencies** 是错的做法——pnpm 会把它装进 profile,盖掉宿主自带的版本,然后核心服务注册失败(实测 `settings service is absent: mount @deepseek-ai/dsh-settings with @deepseek-ai/dsh-config-editor`,桌面端每次崩溃)。**共享实例的 dsh 包只放 `peerDependencies` + `devDependencies`**;独立版本演进或纯工具型包才放 `dependencies`。
- ⚠️ **peer 范围会被安装期强制**(本节早期版本写成"DSH 不校验 peer 版本范围",**已更正**):`dsh plugin add` 用运行时版本比对插件声明的 `@deepseek-ai/dsh-*` 范围,不匹配即**拒绝安装**,打印声明的范围并给出豁免命令。实测原文:

  ```
  dsh: installation rejected: Plugin dsh-nurse-record-check@0.1.0 is incompatible with
  dsh 0.2.1-alpha.1: peerDependencies {"@deepseek-ai/dsh-tools":">=0.1.2-rc.1 <0.2.0 || …"}.
  To accept this risk explicitly, grant the exact-version exemption for
  dsh-nurse-record-check@0.1.0 on dsh 0.2.1-alpha.1 with `dsh plugin allow-version` or the
  plugin manager, then retry the installation or restart dsh.
  ```

  两个后果:① 范围**少列一个 0.2.x 的 prerelease 元组**,插件在 0.2.x 宿主上就装不上(拦在安装期,不是运行期);② 用**旧版本宿主**打的 tarball 用新版本 `verify` 跑,报错会指向 base bundle 而不是你的插件(`dsh-plugin-dev verify` 的 `--base`/`--headless` 默认值因此必须与 compat workflow 同步)。
- **豁免机制**(被拒后唯一的出路,可用 `dsh plugin --profile <p> --help` 复核):

  ```sh
  dsh plugin --profile <profile> version-exemptions              # 列出已授予的豁免
  dsh plugin --profile <profile> allow-version <pkg@version> \
      --dsh-version <runtime> --accept-risk                      # 授予精确版本豁免
  dsh plugin --profile <profile> revoke-version <pkg@version>    # 撤销
  ```

  豁免是**按 `<包@精确版本> × <宿主版本>` 的精确配对**(不是范围),所以宿主升级后要重新授予。`plugin_manager` 工具面里有对应入口,Web 侧边栏与插件管理页也能授予。**给用户写 README 排错节时把这三条命令抄进去**——这是被拒绝安装时用户唯一能自救的手段。
- **`file:` 安装载入的是副本**:`dsh plugin add file:../my-plugin` 之后,profile 的 `node_modules` 下是**拷贝**(`nodeLinker: hoisted` + 锁文件只记 `version: file:<path>`),所以**改源码不生效、`update` 是 no-op**——只有 `remove` + `add` 才会重装。调试期要频繁改就用 `--patch` 直挂源码目录,或每次显式重装。

**tsconfig 三件套 + 构建陷阱**
- 独立 TS 插件包实测可用形态:`module: esnext` + `moduleResolution: bundler` + `allowImportingTsExtensions: true`(否则 TS5097)+ `rewriteRelativeImportExtensions: true`(否则产物残留 `./x.ts` 导入 → 运行时 ESM 崩溃)+ `lib: ["ES2024"]` + `outDir: lib` + `declarationDir: lib/types`。用 `Buffer`/`node:` 时显式 `"types": ["node"]`(不写 `types` 字段会隐式包含全部 @types,脆弱)。
- **`tsc` 报错仍会 emit 产物**(`noEmitOnError` 默认 false)——构建脚本必须 `tsc ... || exit 1` 或加 `--noEmitOnError`;发布前 `grep -rE "from './[^']+\.ts'" lib/` 验证产物无 `.ts` 残留。
- `main`/`types` 声明 `lib/...` 但 tsconfig 无 `outDir` → 产物落到 src 旁,运行时找不到入口(坑 4)。
- git 安装跑 `prepare` 要**自包含**:不假设 monorepo 兄弟目录、不跑类型检查,用专用 tsdown 配置转译 `src/`(模板 `scripts/prepare.mjs`);`pnpm pack --dry-run --json` 检查最终文件清单。

**Windows 实测**
- junction 创建:`ln -s` 与 `cmd mklink /J`(MSYS 参数转换)都失败,**PowerShell `New-Item -ItemType Junction` 稳定可用**;`@types` 不能整体 junction(内部是 pnpm 符号链接,tsc 无法穿透),要直达 `.pnpm/@types+node@<ver>/node_modules/@types/node` 真实路径。
- `path.resolve()` 返回反斜杠,与外部正斜杠路径比较恒 false(路径逃逸误报);比较前两侧都 `resolve()` 或统一 `replace(/\//g,'/')`。
- vitest:盘符必须大写(`C:/`,小写 `c:/` 报 "Tests no tests");`| tail` 会截掉汇总行,用 `grep -E 'Test Files|Tests '` 取结果。
- **`DSH_PERMISSION_MODE=danger-full-access` 是高风险模式**(Windows 无沙箱后端时仅此可启动,且禁用审批提示)——只用于可信本地开发机,不要写进模板/CI/共享机器。
- `DSH_*` 特殊环境变量必须由启动环境传入,放 `~/.dsh/.env` 会启动报错;凭据在 `$DSH_HOME/.credentials.yaml`(LLM 配置只存引用)。

**运行时/数据**
- **多帧 zstd 会话文件**:会话按 200ms 窗口每批追加一个 zstd frame(19MB 会话 ≈ 12 万帧),单帧解压 API 会误判"只有 header"。逐帧扫描用 `scanZstdFrames`/`createZstdFrameDecoder`(真实导入路径 `@deepseek-ai/dsh-session-persistence-jsonl/src/zstd.ts`;npm 包无 `src/` 时 deep 模式降级 decoder-unavailable,帧级扫描不受影响)。
- worker 内跑 `.ts`(Node ≥23.6 原生 strip-types):worker URL 按源码/产物自适应——`new URL(import.meta.url.endsWith('.ts') ? './worker.ts' : './worker.js', import.meta.url)`。
- 批量 sed 替换后必须 grep 验证命中数(静默失效案例);计数矩阵(工具数等)改动后全局复查引用。
- `!!js` 不是 `!js`;`disabled` 字段在每次挂载决策时对 loader 上下文求值;裸 `cordis.yml` 插件必须出现在解析器 manifest 的 `dependencies`(`verify-cordis-config` 强制)。

---

## 8. 规范与质量门禁(对插件作者的实际影响)

- 包命名 `@deepseek-ai/dsh-<name>`;vendored 包改前缀且 `private: true`;`@deepseek-ai/cordis` 是每个 harness 包的 peerDependency(+dev)。
- ESM 全仓库;跨包用包名、包内相对导入用 `.ts`。
- 文档双语:README/文档有 `.zh.md` 成对;工具描述等模型可见文本即行为。
- 测试:包测试 + keyless snapshot(模型/产品可见行为必须有组装后应用级转录快照)+ CI 100% per-file coverage;fixtures 必须可跨平台回放。
- 门禁:`pnpm run typecheck / lint / build / hygiene / doc-sync`;提交前按 dsh-pre-push-checks 技能选最小检查集。
- Agent Note:非平凡变更必须在同 PR 加 Agent Note。
- 事件 JSDoc 需要 `@mode` 与 payload `@param`;公开服务方法文档化参数与非 void 返回值。
- **跨边界 opaque id 用 branded**(`Branded<B>` from `dsh-brand`):会话/任务/审批等 id 从不裸 `string`(红线 4)。
- **同进程 typed 边界信任 TypeScript**:不要为静态接口已保证的值加运行时校验/兜底;在 parser/config、queued、模型/工具 JSON、durable/file、worker、process、wire 边界必须运行时校验。
- **switch 按 discriminant tags**:closed union 以 `assertNever` 收尾;merge-extensible union 落入文档化 default(`SessionEvent` 是后者,见 §3.5)。
- **独立插件包测试路径**:包级 vitest(fixtures 跨平台回放)+ keyless snapshot(模型/产品可见行为必须有组装后转录快照)+ `pnpm pack` 后装进干净 profile 冒烟(含 `lib/` 构建产物);社区现成的健康检查见 omdsh-dev/dsh-plugin-check,测试结构范例见 plugin-template 的 `tests/`。

### 8.1 社区三层采用标准

社区标准仓 [`PerryLink/dsh-standards`](https://github.com/PerryLink/dsh-standards) 把 DSH 插件的"合格"拆成三个不同问题:**DSH-ENTRY**(兼容底线,G0–G5)、**DSH-FLOW**(采用体验,J1–J8)与 **DSH-BOOST**(Cordis 增效实践,LEV-1–LEV-8)。三者用"采用深度 × 证据状态"两轴记录,不折算成总分;`static` 不是 `runtime`,`runtime` 不是 UI 已渲染,`not-measured` 不是通过;认证 A–D 与三层正交。完整定义、GB / GB-T / GB-Z 类比(仅类比,非官方)、逐层自检与升级路径见 [standards.md](standards.md)(中文稿 [standards.zh-CN.md](standards.zh-CN.md)),本节不重复。

---

## 9. 生态与社区(找参考实现、发插件)

- **官方社群**:[Discord](https://discord.gg/Ycq5dCaS4) · [GitHub Discussions](https://github.com/deepseek-ai/deepseek-harness/discussions) · 发布插件时给仓库加 [`dsh-plugin` topic](https://github.com/topics/dsh-plugin) 提升可见度。
- **话题清单**:[GitHub topic `dsh-plugin`](https://github.com/topics/dsh-plugin)(三期早期快照 304/550/993 供续期对比;**08-15 第四期快照** `downloads/topic-snapshots/dsh-plugin-topic-2026-08-15/`:去重 998 个、抓取期间 API total_count 2668→2671 持续增长——search API 分页上限 1000 条)。
- **精选列表**:[awesome-dsh-plugins](https://github.com/AdamPlatin123/awesome-dsh-plugins)(每日兼容性追踪)、[awesome-deepseek-harness](https://github.com/0xsline/awesome-deepseek-harness)、[Alex-Yanggg/awesome-DSH-plugin](https://github.com/Alex-Yanggg/awesome-DSH-plugin)、[bruc3van/awesome-dsh-plugin](https://github.com/bruc3van/awesome-dsh-plugin);08-14 新增 [walkinglabs](https://github.com/walkinglabs/awesome-deepseek-harness-plugins)、[vvlife](https://github.com/vvlife/awesome-deepseek-harness-plugins) 的 awesome-deepseek-harness-plugins 与 [cccakeee/awesome-dsh-plugins](https://github.com/cccakeee/awesome-dsh-plugins)(完整清单见 community-ecosystem.md §5)。
- **插件注册/分发中心**:[vlln/dsh-skills](https://github.com/vlln/dsh-skills)(✅ **现行**:`make-dsh-plugin` 开发引导 skill + 0.1.6→0.2.0 改名症状表 + 实测踩坑,基线 0.2.0-rc.2);~~[vlln/plugin-registry](https://github.com/vlln/plugin-registry)~~(⚠️ **2026-10-03 归档**,冻结在 0.1.5 基线;其**机制时间线**仍可引用:repository-plugin 0809 推出、0811 移除)、[omdsh-dev/dsh-hub-workshop](https://github.com/omdsh-dev/dsh-hub-workshop)(插件市场/注册 workshop,`INTAKE.md` 是收录权威;dsh-external/hub 08-14 核查已 404);08-14 另出现多个 Web GUI 内插件市场([DSH-Plugins-Marketplace](https://github.com/bradeGithub/DSH-Plugins-Marketplace)、[dsh-plugin-installer](https://github.com/Toukaiteio/dsh-plugin-installer)、[dsh-plugin-marketplace](https://github.com/Scorp1o117/dsh-plugin-marketplace),未深读,信任边界同 dsh-hub-workshop"发现 ≠ 安装权限")。
- **模板与脚手架**:[omdsh-dev/plugin-template](https://github.com/omdsh-dev/plugin-template)(完整生产模板:src 四文件结构 + 7 个开发 skill + tsdown 自包含 prepare + 契约文档 docs/dsh-plugin-contracts.md)、[`npm create dsh-plugin@latest`](https://github.com/whyihaveyou/dsh-suite)(whyihaveyou/dsh-suite 的脚手架,tool/events/webui 三模板)、[omdsh-dev/dsh-plugin-skills](https://github.com/omdsh-dev/dsh-plugin-skills)、[omdsh-dev/dsh-plugin-dev](https://github.com/omdsh-dev/dsh-plugin-dev)(踩坑档案 skill+文档,20 个实测坑)。
- **教程与最小模板**:[Opr4Mp3r/deepseek-harness-plugin-from-scratch](https://github.com/Opr4Mp3r/deepseek-harness-plugin-from-scratch)(代码审计式渐进教程:checkpoint + 反模式 17 坑 + 交付检查单,锁 harness@47f9438/npm rc.6)、[randerous/dsh-turn-meta](https://github.com/randerous/dsh-turn-meta)(最小首插件:agent/pre-step + prepend:true + source 归属注入范例)、[omdsh-dev/fabric](https://github.com/omdsh-dev/fabric)(类 MC Fabric 的 hook 处理器)、[omdsh-dev/dsh-plugin-check](https://github.com/omdsh-dev/dsh-plugin-check)(插件健康检查:清单协议/patch 格式/构建陷阱)、[bobleer/deepseek-harness-plugin-mcp](https://github.com/bobleer/deepseek-harness-plugin-mcp)(经 MCP 发现/安装/运行插件)、[Nagi-ovo/dsh-find-plugins](https://github.com/Nagi-ovo/dsh-find-plugins)、[omdsh-dev/dsh-hub-workshop](https://github.com/omdsh-dev/dsh-hub-workshop)(插件市场/注册 workshop)。
- **兼容性追踪**:[whyihaveyou/dsh-suite](https://github.com/whyihaveyou/dsh-suite)(双语目录 + 每日兼容性 CI,167+ 插件带 🟢/⚪ 徽章)、[AdamPlatin123/awesome-dsh-plugins](https://github.com/AdamPlatin123/awesome-dsh-plugins)(每日兼容性追踪)。
- **社区/学习**:[hikariming/dshfind](https://github.com/hikariming/dshfind)(DSH 学习与分享社区,MDX)。
- **教程与文档型仓库(08-14 晚扫描新增归档)**:[flaqai/deepeseek-harness-guide](https://github.com/flaqai/deepeseek-harness-guide)(15 语言指南)、[Electricitysheep/dsh-handbook](https://github.com/Electricitysheep/dsh-handbook)(14 章双语手册 + PDF)、[flysheep-ai/learn_deepseek_harness](https://github.com/flysheep-ai/learn_deepseek_harness)(s01–s23 渐进课程)、[pingfanfan/hello-dsh](https://github.com/pingfanfan/hello-dsh)(零基础 22 技能实例)、[LaplaceYoung/dsh-book-deepseek-harness](https://github.com/LaplaceYoung/dsh-book-deepseek-harness)(源码拆解书)、[curtiseng/cordis-course](https://github.com/curtiseng/cordis-course)(Cordis 论文中文课程)、[NanmiCoder/dsh-agent-teams](https://github.com/NanmiCoder/dsh-agent-teams)(英文插件开发教程 developing-dsh-plugins.md);**08-15 第七批(14 个)**:桌面壳([anywhere-labs](https://github.com/anywhere-labs/deepseek-harness-desktop)、[cc1252](https://github.com/cc1252/deepseek-harness-desktop)、[ChisaAlter](https://github.com/ChisaAlter/Deepseek-Harness-Desktop) 等 7 个)、[banana770/dsh-qq-bridge](https://github.com/banana770/dsh-qq-bridge)(QQ 桥接)、[zzszmyf/dsh-security-pocs](https://github.com/zzszmyf/dsh-security-pocs)(安全 PoC)、[HenryZ838978/deepseek-harness](https://github.com/HenryZ838978/deepseek-harness)(Python 移植)、[Vengisk/deepseek-harness-termux](https://github.com/Vengisk/deepseek-harness-termux)(Termux);第八批 3 个:[orxz/deepseek-harness-themes](https://github.com/orxz/deepseek-harness-themes)(主题)、[vvlife/whalehub-dsh](https://github.com/vvlife/whalehub-dsh)(WhaleHub 市场)、[dsh-market/dsh-market](https://github.com/dsh-market/dsh-market)……完整 114 仓清单见 [community-ecosystem.md](../references/community-ecosystem.md) §4。
- **本工作区已有实例可参考**:`dsh-chat-import`(JS + cordis.patch.yml)、`dsh-resume-plugin`(多 skill 插件)、`dsh-plugin-claude-bridge`(TS + src/ + tsconfig);114 个社区仓库的**完整源码副本**在 `downloads/community-repos/`(首批 15 个深读报告见 [references/community-repo-deep-dive.md](../references/community-repo-deep-dive.md))。
- **官方 Discussions 最新动态**:官方 [RFC #1629](https://github.com/deepseek-ai/deepseek-harness/discussions/1629)(2026-08-15,官方插件脚手架 template repo + `pnpm create dsh-plugin` 提案,直指 dsh-tools `latest` 版本火车混淆问题)——**全量归档已刷新到 2026-10-06**(`downloads/github/harness/discussions/`:list=5000、精选线程 1648;REST 列表上限 5000 而仓库实际 total_count≈8850,脚本会显式告警)。仓库 API 核实 star≈244k、`has_issues=false`——**没有 issue tracker、不收 PR,Discussions 是唯一渠道,且 Announcements 分类只有 1 条**(官方不在讨论区发版)。**注意**:本轮通读窗口内 3100 条讨论,**没有任何维护者在任何帖里确认过修复或设计决定**;唯一权威的"官方决定"记录是 GitHub **Releases** 的说明(仓库无 CHANGELOG)。引用"官方已确认"时落到 Release 原文或提交号。
- **0.2 世代新增的作者工具(社区)**:[mengjiemy/dsh-plugin-devkit](https://github.com/mengjiemy/dsh-plugin-devkit)(四个命令对准四类"静默失败":`smoke` 在安装前用假 `window`/`react` 加载 client 插件——装一个坏 client 半侧会把整个 UI 拖下水;`sync` 对比源码目录与 `file:` 安装后 DSH **真正加载的副本**;`version` 比对 profile 与 client 自身的 `@deepseek-ai/*` 版本;`css` 检测插件间 `:root` CSS 变量冲突)、`dsh-composition-doctor`(把插件故障关联回组合来源——正对 T3 的诊断空白)、`dsh-xray`(声明能力 vs 实际能力)、`Noob-stupid/dsh-plugin-gating-hub`(升级契约预检 + 自动回滚)。生态规模参考:`topic:dsh-plugin` 约 **17,776 仓**(上限值,标签灌水严重),[dsh-plugin.org](https://dsh-plugin.org) 索引约 12,699,星数最高的市场是 [dsh-market/dsh-market](https://github.com/dsh-market/dsh-market)(约 5.6k)。
- 完整信息与更多链接见 [references/community-ecosystem.md](../references/community-ecosystem.md);官方文档 URL 对照见 [guide/links.md](links.md)。


---

## 9.1 版本兼容与迁移(0.2 世代必读)

- **先认版本**:`dsh --version`,然后读 [version-matrix.md](version-matrix.md)(版本适配矩阵:五个"最新"的区分、子包 `latest` 冻结陷阱、破坏性变更断层、peer 区间选择、会话格式谱系)。`0.1.7-alpha.1` 起包含本世代全部破坏性变更(会话格式 V4)。npm 上 `latest`=`next`=`0.2.0-rc.2`、`alpha`=`0.2.1-alpha.2`,而 master 最新 tag 是 `dsh-v0.2.1-alpha.2`——**这四个"最新"不是同一个东西**;**不存在稳定版**(全部 31 个 npm 版本都是 prerelease)。
- **peer 范围要写全两侧的 prerelease 元组**(如 `>=0.1.2-rc.1 <0.2.0 || … || >=0.2.0-0 <0.3.0 || >=0.2.1-0 <0.3.0`)。⚠️ **这个范围会被安装期强制**,不是文档意图:`dsh plugin add` 用运行时版本比对它,不匹配就拒绝安装并打印你声明的范围。所以少一个 0.2.x clause,插件在 0.2.x 宿主上**根本装不上**(拦在安装期,不是运行期)。范围之外仍要靠运行时特性探测——范围只表达"能不能装",表达不了"某个 API 在不在"。
- **共享实例的 dsh 包只放 peer + dev**,绝不放 dependencies(否则 profile 内副本会遮蔽运行时版本)。
- **升级后必做**:`dsh --profile <name> --dump-config` 看有没有 `patch: entry <id> not found`(上游删行/改名后,你的 override 会**静默失效**);再跑一次真实能力。
- **逐项迁移清单**:见 [migration-0.2.md](migration-0.2.md)(含会话格式 V4、settings 重写、invariants 删除、`readBytes`、客户端 slot/Remote 改名、`agent.inject()` 的 source 包装退役、preset 分叉等全部条目与官方原文出处)。

---

## 10. 从零到发布的标准路径(总结清单)

1. 读 Cordis Primer(5 个概念)→ 跑 Cordis tutorial 01-07(无 key)。
2. 按 docs/user/develop/basic 四步做第一个插件(scratch-plugin + `--patch`);或者在会话里直接走官方路径:写 bundle → `plugin_manager.install_bundle` → `cordis_inspect_query` 核对(见 [references/official-plugin-dev-skill.md](../references/official-plugin-dev-skill.md))。
3. 需要新能力时:先查 architecture「Where new behavior goes」与 extension-cookbook 表选扩展点(**用最弱的够用机制**);tool 类需求读 adding-a-tool.md 全文。
4. 需要可替换能力 → 三层拆分(practice 教程);需要接模型商 → LLM adapter 指南。
5. 配置全部 Schema 化、fail loud;不硬编码可调值;工具 `parameters` 是 DSH 方言不是 JSON Schema。
6. 打包:bundle manifest + cordis.patch.yml(可多文件数组);**补展示元数据**(`locale/*.json` 的 `meta.*` + 导出的 `./icon`);git 安装配 `prepare`;npm/tarball 分发免 allowBuilds。
7. 发布前:包级测试 + 关键 snapshot + typecheck/build/hygiene;README 双语并写明扩展点与模型可见效果;`inject` 是静态数组且覆盖 `apply()` 触及的每个服务。
8. 发布到 dsh-plugin topic / hub / awesome 列表,社区可见;显式声明跨线 peer 范围并按版本发版(组合包升级是社区最常见的"插件被判不兼容"来源)。

---

*本指南由 dsh-plugin-guide 维护;与官方文档冲突时以官方文档(references/official-docs/)为准。*


<!-- source: guide/standards.md -->

# [Adoption levels: DSH-ENTRY / DSH-FLOW / DSH-BOOST (non-official)] guide/standards.md

# DSH plugin adoption levels: ENTRY, FLOW, BOOST

> **Status: community draft, non-official.** This chapter explains the three-level model maintained in the `dsh-standards` project (a private draft at the time of writing; ask the maintainer for access). It is teaching material, not a certificate, ranking, security audit, or official DeepSeek position. If your copy of the canonical clause text or `spec/levels.json` disagrees with this summary, the canonical files win. Chinese version: [standards.zh-CN.md](standards.zh-CN.md).

## Three levels, three different questions

In the DSH plugin ecosystem, "qualified" is at least three questions, and they are not three marks on one ruler. **DSH-ENTRY** asks whether an existing plugin can be recognized, installed, and booted on a target DSH line with little or no code change — the compatibility baseline ("装完能跑"). **DSH-FLOW** asks whether a user can then install, configure, use, recover, upgrade, and uninstall it without surprises — the recommended adoption experience ("用起来顺"). **DSH-BOOST** asks whether the plugin uses Cordis primitives (effects, inject, volatile, events, seams, jobs, bounded parallelism) to make development and use measurably cheaper — guiding practice recorded as evidence cards, not a badge ("用 Cordis 做得更省").

A plugin can be `ENTRY=runtime` and `FLOW=not-run`; a `BOOST`-measured plugin can still fail `ENTRY`. That is why the three levels are judged, cited, and expired **separately**. Never collapse them into one score, never use `BOOST` to infer `ENTRY`, and never call the result a "level" without naming its evidence state.

## The GB analogy (analogy only, not a standard)

> **Analogy only — non-official.** The GB / GB∕T / GB∕Z columns borrow the *intuition* of a baseline / recommended / guidance layering. This project has no standards-issuing body, no standard number, no accreditation, no legal effect, and no DeepSeek endorsement. `DSH` is an abbreviation; DeepSeek Harness is a DeepSeek trademark. Do not describe a plugin as "GB compliant", "nationally certified", "officially certified", or "ranked" on the basis of this model.

| Dimension | GB (analogy) | GB∕T (analogy) | GB∕Z (analogy) |
|---|---|---|---|
| What it stands for | mandatory national standard | recommended national standard | guiding technical document |
| This project | **DSH-ENTRY** | **DSH-FLOW** | **DSH-BOOST** |
| Question | can it be installed and run? | is it smooth after install? | is Cordis used efficiently? |
| Adoption | self-adopted; must pass if you claim it | recommended; not adopting is not "nonconforming" | guidance; register practices and evidence |
| If it fails | cannot claim compatibility | not recommended for broad adoption | cannot claim a speed-up |
| Evidence | G0–G5 + static/runtime state | J1–J8 + journey records | LEV-1–8 + evidence cards |
| Force in this project | none; only self-consistency when cited | none | none |

## Two axes: adoption depth and evidence state

Depth and evidence are independent coordinates. `ENTRY` is not "bronze", `FLOW` is not "silver", and `BOOST` is not "gold": the depth says **what was judged**, the evidence state says **how strongly**. Always write both, for example `ENTRY=runtime, FLOW=manual(J1–J8), BOOST=not-measured`.

| Depth ↓ / evidence → | `not-run` | `recognized` / `static` | `runtime` | `manual` | `measured` | `blocked` / `failed` |
|---|---|---|---|---|---|---|
| **DSH-ENTRY** | untested; no claim | manifest, entry, patch recognized; no boot | real boot; target rows ACTIVE or disabled | — (not a substitute for boot) | — | host/fixture blocked, or plugin-attributable failure |
| **DSH-FLOW** | journey not run; no claim | static prep only (manifest, README); not a journey | automated journey passed on a recorded host | a person ran the journey and recorded steps/logs | — (use runtime/manual per journey) | environment blocked, or journey failure |
| **DSH-BOOST** | not measured; no claim | pattern present in code; not evidence of benefit | — (execution alone is not a measurement) | — | evidence card reproduces on the recorded anchor | benchmark blocked, or claimed gain contradicted |

Cells marked "—" are category errors, not passes. The evidence words mean:

| State | What it supports | What it never means |
|---|---|---|
| `not-run` / `not-measured` | "this was not tested" | a pass, or "should work" |
| `recognized` / `static` | structural recognition: manifest, entry, patch, client export | a running process or a rendered UI |
| `runtime` | a real boot or real execution on a recorded host version/commit | UI correctness, production readiness, security |
| `manual` | a human walked the journey with steps and logs recorded | automation, or a claim that covers other platforms |
| `measured` | a reproducible evidence card with environment, input, command, control, result | a production benchmark or a billing claim |
| `blocked` | the host or fixture prevented judgment | a plugin failure, or a pass |
| `failed` | a reproducible failure attributable to the plugin | "environment issue" without evidence |
| `stale` | past its expiry window | current evidence; quote it only as stale |

Three misreadings this table exists to stop:

- **`runtime` is not UI-rendered.** A booted server-side row, or a client module that loaded, does not prove the slot renders correctly. `client-module-loaded` ≠ `UI-rendered`; render correctness needs its own observation (slot output, console errors, a screenshot).
- **`static` is not `runtime`.** Parsing and recognition say the artifact is structurally plausible. They do not say a process booted it, or that a dependency resolved at boot.
- **`not-measured` is not a pass.** Absence of evidence is a third state with its own label. Write "not tested", not "passes" or "should work".

## DSH-ENTRY: compatibility baseline

**What it answers.** Can an existing plugin be recognized, installed, and booted on the target DSH line without changing its code?

**Required evidence.** Four static gates plus one runtime gate and one process gate:

| Gate | Check | Owner |
|---|---|---|
| G0 | `dsh.bundle.patch` parses | plugin |
| G1 | base + sample compose; inserted row names resolve | plugin |
| G2 | `evaluatePluginCompatibility` admits, or an explicit exact-version exemption applies | host |
| G3 | real `boot()` leaves every target row `ACTIVE` or `disabled` | plugin/host/fixture |
| G4 | when `dsh.client` exists: `exports["./client"]` and a package-root row (static) | plugin |
| G5 | report carries host version/commit and artifact mtime; expired evidence is `stale` | process |

G0–G2 and G4 pass in static inspection, which supports `recognized`; `runtime` requires G3 on a recorded host. `blocked` and `failed` are different outcomes and must be reported with their cause.

**What it does not claim.** Security, UI correctness, production readiness, full feature correctness, official endorsement, or that the plugin will work on an untested DSH line.

**How to self-check.** Record `dsh --version`, the host commit, and the artifact mtime. Run `dsh-plugin-dev check --json` for the 16 local static checks (a subset of `dsh-plugin-doctor`'s 33 — see [family-toolchain.md](family-toolchain.md)), then the standards repo's entry checker (commands below). For bundles, also run `dsh --profile <name> --dump-config` and look for `patch: entry <id> not found`, then boot and read the row states. Write the claim as `DSH-ENTRY: runtime (dsh 0.2.1-rc.2, commit <sha>, 2026-11-01)`.

## DSH-FLOW: adoption experience

**What it answers.** Once installed, can a user install it, configure it, use it, recover from errors, upgrade, and uninstall it without surprises?

**Required evidence.** The eight user journeys J1–J8: install/remove; enable/disable; configuration and validation; first success (quickstart and error triad); permissions and disclosure; upgrade/rollback/uninstall; observability and diagnostics; and client/server shapes (headless, web, client-off). Each journey records the command, environment, result, log path, and time. A manual journey is valid evidence when it says `manual`; a journey that was not run is `not-run`, not a pass.

**What it does not claim.** Certification, ranking, security review, every UI layout, every platform, or that a journey tested on one host generalizes to another.

**How to self-check.** Use a throwaway `DSH_HOME`: `add` → `--dump-config` shows the layer → `remove` → the layer and dependencies are gone. Boot with `{}` and with an invalid value (it must fail loudly with a field path). Follow the README as a new user with a five-minute timer; check the error triad (cause, next command, doc anchor). Test upgrade then rollback, and for client plugins test headless, web, and client-off. Start from [plugin-dev-guide.md](plugin-dev-guide.md) §7 and the release checklist in [release-engineering.md](release-engineering.md).

## DSH-BOOST: guiding practice

**What it answers.** Does the plugin use Cordis primitives to make development and use measurably cheaper, and is the claimed saving reproducible?

**Required evidence.** LEV-1–8: effects are reversible and return to baseline; required services are injected and polling is absent; hot config uses `volatile()` without remounting; event modes match and waterfall observers call `next()`; seams/isolate/group are used only when needed; tool and prompt prefixes stay byte-stable with volatile data in `systemPrompt.context`; long work uses jobs and bounded parallelism with cancellation; and bundle/preset composition is expressed in patches, with an evidence card per practice. Each card records environment, input scale, command, a **negative control**, result, version anchor, and limitations.

**What it does not claim.** A production benchmark, a billing or cache-hit rate, a total score, a ranking, or that one fixture generalizes to another workload. LEV-6 prefix stability is the one practice directly priced in tokens; the others still need a card.

**How to self-check.** Pick one LEV and write the card before optimizing the next. Run the negative control (for example, a normal config field that *does* remount, or a volatile nonce that *does* break the prefix). Record `dsh`, `cordis`, Node, and the commit; a number without an anchor is not evidence. Link the code path to [plugin-dev-guide.md](plugin-dev-guide.md) §3.3 (effects), §3.6 (config), §4.3 (jobs), and §5 (seams).

## How to move up

**ENTRY → FLOW.** First get a real `runtime` claim: boot the bundle and record the host version/commit. Then make the first five minutes work: a quickstart, a working `add`/`remove` in a temporary `DSH_HOME`, empty-config boot, loud invalid-config errors with a field path, and an error triad in every failure message. Test one upgrade and one rollback, and check that uninstall leaves no layer or dependency behind. For client plugins, test headless, web, and client-off before claiming a UI. Relevant chapters: [plugin-dev-guide.md](plugin-dev-guide.md) §7 (packaging, install, and pitfalls), [release-engineering.md](release-engineering.md) §7 (pre-tag gate), and [quick-reference.md](quick-reference.md).

**FLOW → BOOST.** Move timers, listeners, connections, watchers, and subprocesses into `ctx.effect()` with labels, then measure that reloads return to baseline. Declare required services in `inject`, use `ctx.get()` only for optional ones, and remove polling. Make adjustable fields `volatile()` and measure zero remounts on change. Keep tool and prompt prefixes stable; put volatile data in `systemPrompt.context`. Move long work to `ctx.jobs` and bound parallel calls, then verify cancellation. Start with LEV-6 and LEV-3 — the draft already carries measured examples for both, and each has a clean negative control. Relevant chapters: [plugin-dev-guide.md](plugin-dev-guide.md) §3.3, §3.6, §4.3, §4.8; [references/official-plugin-dev-skill.md](../references/official-plugin-dev-skill.md); and the official extension cookbook in [references/official-docs/docs/cookbook/extension-cookbook.md](../references/official-docs/docs/cookbook/extension-cookbook.md).

**Do not move up for its own sake.** A frozen or retiring plugin should not buy a `BOOST` card; check [when-to-retire-a-plugin.md](when-to-retire-a-plugin.md) first. A plugin whose capability is better served by another implementation should be retired, not polished.

## Certification grades are orthogonal

Certification grades A–D describe quality and evidence depth; ENTRY/FLOW/BOOST describe adoption depth. They are orthogonal: `A` is not "ENTRY level", `D` is not "BOOST failed". A certified plugin can still be `ENTRY=blocked`, and a `BOOST=measured` plugin can still have a supply-chain problem. State each scope separately; never compute a total from grade plus level.

## Canonical source and checker commands

- Canonical repository (private draft): `PerryLink/dsh-standards` — clauses `31` (ENTRY), `32` (FLOW), `33` (BOOST), `34` (the GB analogy), and `spec/levels.json`. Ask the maintainer for access.
- Audit tool in that repository: `40-checker/scripts/level-audit.mjs`. It is not bundled with this guide; if your checkout does not have it, use `spec/levels.json` for definitions and the entry checker below.
- Existing entry checker: `40-checker/bin/dsh-plugin-verify.mjs`.

```sh
git clone <your-access-url-for-PerryLink/dsh-standards>
cd dsh-standards
# entry, static only (does not claim runtime)
node 40-checker/bin/dsh-plugin-verify.mjs <plugin-dir> --scope entry --entry-claim static
# audit (run only if the file exists in your checkout)
node 40-checker/scripts/level-audit.mjs <plugin-dir>
```

Scaffolded plugins (`dsh-plugin-dev new`) ship `dsh.standards` as a placeholder — `entry`/`flow` = `not-run`, `boost` = `not-measured`, every `evidence` = `null`. Replace it only with the audit's recorded output; without evidence it stays `not-run`, and no level is read as a pass.

`dsh-plugin-dev check` is a static assistance tool, not a level audit; `dsh-plugin-doctor` maps its R/K/D/X results to an ENTRY status and does not cover FLOW or BOOST. Neither tool can issue a level by itself.

## For reviewers: a short checklist

- [ ] The claim names **both** a level and an evidence state, not just "compatible".
- [ ] Evidence carries the DSH version, host commit, Cordis version, date, and scope.
- [ ] `runtime` was observed, not inferred from `static`; static is labelled `recognized`.
- [ ] No `not-run`, `not-measured`, or `blocked` was rewritten as a pass.
- [ ] UI claims have a render observation; `client-module-loaded` is not called rendered.
- [ ] FLOW claims list which journeys ran, who ran them, the command, result, and log path.
- [ ] BOOST claims have a card with a negative control and a limitation, and no production percentage is inferred from one fixture.
- [ ] Grades A–D are stated separately and are not used as a level.
- [ ] No "official", "certified", "national standard", "GB compliant", or "ranked" wording.
- [ ] Evidence is inside its expiry window; expired evidence is labelled `stale`.

---

*Canonical model: the private `dsh-standards` project. This chapter is non-official and grants no certification. Corrections should go to the canonical repo first, then be mirrored here.*


<!-- source: guide/quick-reference.md -->

# [Quick reference] guide/quick-reference.md

# DeepSeek Harness Plugin Cheat Sheet

> One-page quick reference. Full details: [plugin-dev-guide.md](plugin-dev-guide.md) (Chinese) and [references](../references/).
> Other languages: [中文](quick-reference.zh-CN.md) · [Español](quick-reference.es.md) · [Português](quick-reference.pt.md) · [हिन्दी](quick-reference.hi.md)

## Plugin skeleton

```ts
import type { Context } from '@deepseek-ai/cordis'
import Schema from '@deepseek-ai/schemastery'
import { defineTool } from '@deepseek-ai/dsh-tools'

export const name = 'my-plugin'          // required
export const inject = ['tools']          // required services; omit if none

export interface Config { limit: number }
export const Config: Schema<Config> = Schema.object({
  limit: Schema.number().default(10),    // Schemastery schema, not a plain object
})

export function apply(ctx: Context, config: Config) {
  ctx.effect(() => {                     // registration = effect, undone on unload
    const timer = setInterval(() => {}, 1000)
    return () => clearInterval(timer)
  })
  ctx.on('tools/result', (exec, result) => { /* ... */ })
  ctx.tools.register(defineTool({
    name: 'my_tool',
    description: '…',
    parameters: { x: { type: 'string', required: true } },
    output: { schema: { type: 'string' }, render: (_a, v) => [{ type: 'text', text: v }] },
    async execute(args, exec) { return `hi ${args.x}` },  // honor exec.signal
  }))
}
```

Other forms: object `export default { name, inject, apply }`; class `class X extends Service { static inject=[...]; constructor(ctx){ super(ctx,'key') } }` (use when providing a service).

## Core ctx API (Cordis)

| Purpose | API |
|---|---|
| Register resource + cleanup | `ctx.effect(() => disposer)` |
| Listen to events | `ctx.on(name, handler)` (auto-cleaned) |
| Broadcast / no return | `ctx.emit(name, payload)` |
| Short-circuit value | `ctx.bail(name, input)` (first non-null/false/undefined wins) |
| Ordered value | `await ctx.serial(name, input)` |
| Pipeline | `await ctx.waterfall(name, input, init)`; listeners **must call `next()`** |
| Child contexts | `ctx.extend(meta)` / `ctx.isolate(name, label?)` / `ctx.intercept(name, config)` |
| Mount child plugin | `ctx.plugin(plugin)` → Fiber; `await fiber.dispose()` |
| Optional service lookup | `ctx.get('metrics')?.x()` |
| Logging | `ctx.logger(name)` |

Lifecycle: `PENDING → LOADING → ACTIVE` (apply throws → FAILED); `ACTIVE → UNLOADING → DISPOSED`. A required service disappearing unloads the plugin; it reloads when the service returns.

## Event dispatch modes (five, including bail)

| Mode | awaited | Order | Return value |
|---|---|---|---|
| emit | no | registration order | none |
| waterfall | no | registration order | yes (**not calling next() short-circuits**) |
| parallel | yes | parallel | none |
| serial | yes | registration order | yes (first non-empty stops) |
| bail | no | registration order | yes (first non-null/false/undefined short-circuits and stops the rest) |

Typed events (declaration merging):

```ts
declare module '@deepseek-ai/cordis' {
  interface Events { 'my/event': (p: { id: string }) => void }
  interface Context { myService: MyService }   // when providing a service
}
```

## cordis.yml / layering

```yaml
# scratch-plugin/cordis.yml (--patch overlay)
- insert:
    - id: hello
      name: '/absolute/path/to/scratch-plugin/src/my-plugin.ts'
      config: { greeting: 'Hi' }

# bundle: "dsh": {"bundle":{"patch":"./cordis.patch.yml"}} in package.json
# profile: "dsh": {"profile":{"bundles":["@deepseek-ai/dsh-base","my-bundle"]}}
# effective order: bundles → profile cordis.patch.yml → $DSH_HOME/cordis.patch.yml → --patch
# override by id; the whole config row is replaced (no deep merge) — restate every key
# !!js expressions evaluate after injected services activate; disabled is evaluated on every mount
```

Commands: `dsh --profile web` · `dsh --profile headless "task"` · `dsh --profile X --dump-config` · `dsh plugin --profile X add/remove <pkg>` · `pnpm dsh web --patch ./scratch-plugin/cordis.yml`

Install channels (repository-plugin removed 0811 — only two remain):
- **bundle plugin** (`"dsh":{"bundle":{"patch":"..."}}`) → `dsh plugin add <pkg>` enters the `dsh.profile.bundles` stack; takes effect on restart.
- **plain cordis plugin** (no `dsh.bundle`) → `dsh plugin add <pkg>` installs the dependency + an insert row in the profile `cordis.patch.yml`; **config HMR applies live**.
- git source: `dsh plugin add "github:owner/repo#<sha>&path:<subdir>"` (pin the commit; self-contained `prepare` build + profile `pnpm-workspace.yaml` allowBuilds; npm/tarball needs no build permission).

## Common built-in services (ctx keys)

`sessions` session log/in-memory store · `systemPrompt` prompt assembly · `tools` tool registry + guarded pipeline · `agents` agent registry · `agentLoop` loop driver · `llm` model adapter registry · `skills` skill registry · `commands` human slash commands · `approval` one-shot approval · `jobs` background jobs · `fs` filesystem seam · `shell` bash execution seam · `subprocess` subprocess seam · `terminals` PTY · `sandbox` process confinement seam · `codeRuntime` code execution · `sessionPersistence` persistence · `settings` / `credentials` / `workspaceRegistry` / `goals` / `planMode` / `subagents` / `workflowEngine` / `storage`.
Full list + exact signatures → `references/official-docs/docs/capability-seams.md` + `docs/subsystems/*.md` (generated Cordis API regions).

## Tool policy pipeline (execution order)

```
tools/pre-execute (waterfall, allow|deny|ask) → ctx.tools.guard() (monotonic deny)
→ tools/execute (wrapper; only exec.signal replaceable) → execute(args, exec)
→ tools/post-execute (replace content/value, block, attach context) → finalizeContent
→ tools/result (observe-only) → durable tool/result (session event)
```

Selection: policy gates → pre-execute; non-appealable deny → guard; timeout/retry/metrics → execute; transforming results → post-execute; audit/collection → result.
Code Mode: `await tools.<name>(args)` comes free; success = final canonical JSON value; failure = `ToolCallError(name, toolName, message)`.

## UI cards (pure functions! args(+result) only — no I/O/clock/random)

- `presentCall(args)` → `{card:'generic',title,kind?,rawInput?,content?,locations?}` | `{card:'terminal',title,description?,cwd?}` | `{card:'diff',title,diffs,locations?}`
- `presentResult(args,{content,isError,meta?})` → generic / terminal / diff / search(`shape:'matches'|'paths'`) / read / web(`kind:'search'|'fetch'`)
- Replay metadata: `output.presentationMeta(args, value)` → persisted in `tool/result.meta`

## Three-role capability seam template

Definition (`dsh-my-cap`): `export abstract class MyCapService extends Service { constructor(ctx){super(ctx,'myCap')} abstract execute(req): Promise<res> }` + Context declaration merging.
Provider (`dsh-my-cap-local`): `export function apply(ctx){ ctx.plugin(class MyCapLocal extends MyCapService {...}) }`.
Consumer (`dsh-tool-my-cap`): `inject = ['tools','myCap']`, `ctx.tools.register(defineTool({... execute: args => ctx.myCap.execute(...)}))`.
Rules: don't split preemptively; Provider and Consumer never depend on each other; defaults resolve explicitly via `resolve(request): Spec`.

## LLM adapter essentials

`class MyAdapter extends LlmAdapter { async *stream(options): AsyncIterable<StreamChunk> }` → `ctx.llm.registerAdapter(['provider'], adapter)`.
Chunk protocol: `block-start` → `text-delta*` → `block-end` (complete block) → … → `usage` (before finish) → `finish` (last; `reason: {kind:'stop'|'tool-calls'}`). Throw `LlmError` with a stable code for fields you cannot honor.

## Hard rules (violations = gate failures / wrong behavior)

1. Every registration goes through `ctx.effect()` / `ctx.on()` / a service `register()` (returns a disposer). A registration on another context (e.g. `agent.ctx`) has two owners — keep its disposer in your own effect too.
2. Waterfall listeners must call `next()`; not calling it short-circuits by design. When rewriting an `agent/pre-step` decision, **spread it** (`{ ...decision, messages }`) so fields like `startsRequestSeries` survive.
3. Weakest mechanism that suffices: `ctx.tools.restrict()` (remove only) < `ctx.tools.guard()` (deny only) < waterfall rewrite (order-dependent) < `system-prompt/assemble` (replaces everything). Observe final outcomes on `tools/result`; use `tools/post-execute` only to transform.
4. Model-visible ⇔ logged: new model-visible input requires a new session event (`SessionEventMap`) — added by the host, never by appending a new `type` yourself (see rule 8).
5. Never hardcode tunables (test: can cordis.yml change it?); misconfiguration fails loud. Live-editable user fields use `Volatile<T>` + `loader/volatile-update` (the form namespace is the profile entry id).
6. Standalone plugin packages: cordis is a peerDependency matching the host identity (mixing scoped `@deepseek-ai/cordis` and unscoped splits identities); ESM; `dsh.bundle` manifest (`patch` may be an ordered array); git installs need `prepare` + `allowBuilds`; ship `lib/` or a tarball. Shared-instance dsh packages go in **peerDependencies + devDependencies only** — never `dependencies` (a profile copy silently shadows the runtime). ⚠️ Corrected: **peer ranges ARE enforced at install time** — `dsh plugin add` compares the runtime version against the declared `@deepseek-ai/dsh-*` range and refuses the install; only `engines.dsh` is declarative and unenforced. A range says "can it install", not "does this API exist" — keep runtime probing (range selection: `version-matrix.md` §4).
7. Ship display metadata: `locale/*.json` `meta.title`/`meta.description` plus an exported `./icon` (SVG/PNG/JPEG/WebP, ≤256 KiB, inside the package). Subpath plugins never read their own `package.json`.
8. Bilingual docs in pairs; tool descriptions/prompts are behavior; non-trivial changes need an Agent Note; run the minimal check set before pushing (dsh-pre-push-checks).
9. Opaque cross-boundary ids are branded (`Branded<B>` from `dsh-brand`), never bare `string`.
10. **Never append a session event with a new `type`.** `SessionEventMap` members are required-on-read: a reader that does not know a type refuses the whole log unless the event carries the envelope's `ignorable: true` marker, and live `Session.append()` cannot set that marker (its third argument is a `SurfaceIntent` for surface events only). Derive your state from existing events, or keep plugin-owned data in a storage service found through inspection. Only structural format changes bump `SESSION_FORMAT_VERSION` (currently `4`; the V3→V4 migration lifts tool results into tool-role messages and moves message sources to producer-owned `source.kind`). Switch over `SessionEvent` still falls through a documented `default` — no `assertNever` (merge-extensible union).
11. `agent.inject()`'s `source` no longer accepts the retired `{ kind: 'plugin', plugin: '<name>' }` wrapper — declare your own kind through `MessageSourceMap` first. `inject` does not wake the agent (`followup()`/`steer()` do).
12. Never write a profile `cordis.patch.yml` while sessions run (HMR reloads it: tools vanish from live sessions, in-flight turns die, and a rebuild of `session-controller` can kill it until a full restart). Never hand-write a profile `package.json`; never run pnpm inside the profile directory.
13. UI plugins: render React in a slot (never an iframe-hosted page); style with `--dsw-alias-*` theme tokens only; never `require` a Harness Client package (a throwing component blanks the slot); `dsh.client.inject` must be a **static array**.
14. Performance: keep per-session state in `ctx.sessionProjections` (pure synchronous `apply`, same reference when ignored, plain JSON + `stateVersion`); wait on durable events (`turn/end`, `assistant/message`, `tool/result`) and render live tokens from `agent/assistant-stream`; never poll `agent/status`.

## Community pitfalls quick list (details: guide §7.3 / community-repo-deep-dive.md)

- tsconfig trio: `moduleResolution: bundler` + `allowImportingTsExtensions` + `rewriteRelativeImportExtensions` (+ `lib:["ES2024"]`, explicit `types:["node"]`).
- `tsc` emits despite errors → `tsc || exit 1` / `--noEmitOnError`; grep the build for leftover `.ts` imports before shipping.
- Windows junctions via PowerShell `New-Item -ItemType Junction`; vitest drive letter must be uppercase `C:/`.
- `DSH_PERMISSION_MODE=danger-full-access` is high-risk (no sandbox backend on Windows, approvals disabled); `DSH_*` vars in `~/.dsh/.env` break startup.
- Session files are multi-frame zstd: use `scanZstdFrames`/`createZstdFrameDecoder` (`@deepseek-ai/dsh-session-persistence-jsonl/src/zstd.ts`).
- npm: unscoped `dsh` is the unrelated node-dsh shell — install `@deepseek-ai/dsh` (`latest`=`next`=0.2.0-rc.2, `alpha`=0.2.1-alpha.2); `@deepseek-ai/dsh-tools`, `@deepseek-ai/dsh-base`, `@deepseek-ai/dsh-headless` and `@deepseek-ai/dsh-session-persistence-jsonl` still ship a stale `latest` (0.0.1-rc.1), so pin `next` (0.2.0-rc.2) or `alpha` (0.2.1-alpha.1); `create-dsh-plugin` latest=0.2.3; `@deepseek-ai/cordis` latest=4.0.4 with a new `dsh-0-2-1-alpha-1` tag at 4.0.5-alpha.1; dsh-core/dsh-sdk still unpublished (verified 2026-10-06).
- `resolve()` both sides before path comparisons (Windows backslash trap).

## Documentation links

Official dev docs — site base <https://deepseek-harness.github.io/deepseek-harness> (root = Chinese, `en/` = English; verbatim local copies under `references/official-docs/docs/`):

- Basics: [develop/basic/](https://deepseek-harness.github.io/deepseek-harness/develop/basic/) → [tool](https://deepseek-harness.github.io/deepseek-harness/develop/basic/tool) · [config](https://deepseek-harness.github.io/deepseek-harness/develop/basic/config) · [publish](https://deepseek-harness.github.io/deepseek-harness/develop/basic/publish)
- Framework: [develop/framework/](https://deepseek-harness.github.io/deepseek-harness/develop/framework/) ([service](https://deepseek-harness.github.io/deepseek-harness/develop/framework/service), [events](https://deepseek-harness.github.io/deepseek-harness/develop/framework/events)) · Practice: [develop/practice/](https://deepseek-harness.github.io/deepseek-harness/develop/practice/) ([LLM adapter](https://deepseek-harness.github.io/deepseek-harness/develop/practice/llm-adapter))
- Guides: [quickstart](https://deepseek-harness.github.io/deepseek-harness/guide/quickstart) · [providers](https://deepseek-harness.github.io/deepseek-harness/guide/providers) · [python-sdk](https://deepseek-harness.github.io/deepseek-harness/guide/python-sdk)
- Cordis: [primer](https://deepseek-harness.github.io/deepseek-harness/reference/cordis-primer) · [tutorial](https://deepseek-harness.github.io/deepseek-harness/develop/cordis-tutorial/) · [core API](https://deepseek-harness.github.io/deepseek-harness/reference/cordis-api/context)
- Reference: [architecture](https://deepseek-harness.github.io/deepseek-harness/reference/) · [cookbook/adding-a-tool](https://deepseek-harness.github.io/deepseek-harness/reference/cookbook/adding-a-tool) · [cookbook/extension-cookbook](https://deepseek-harness.github.io/deepseek-harness/reference/cookbook/extension-cookbook) · [subsystems](https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/)
- Full URL ↔ local-copy index: [guide/links.md](links.md)

Community dev docs — templates/tutorials/pitfalls, full list in [references/community-ecosystem.md](../references/community-ecosystem.md): [plugin-template](https://github.com/omdsh-dev/plugin-template) · [dsh-plugin-dev pitfalls](https://github.com/omdsh-dev/dsh-plugin-dev) · [from-scratch tutorial](https://github.com/Opr4Mp3r/deepseek-harness-plugin-from-scratch) · [dsh-plugin-check](https://github.com/omdsh-dev/dsh-plugin-check)

## Key source index

- Official docs verbatim: `references/official-docs/docs/**` (429 md pages, all 212 `.zh.md` pairs included, mirror at `dsh-v0.2.1-alpha.2`); the pages **not on the documentation site** (session-format-status, `upgrade-guide/**`, `persistence-changes/**`, ui-radius, web-styling, module-graph) are listed in [links.md](links.md) §2.1
- Migration checklist: [migration-0.2.md](migration-0.2.md) — §0–§12 cover every breaking change from `0.1.6-alpha.2` → `0.2.1-alpha.1`, and §13 covers `0.2.1-alpha.1` → `0.2.1-alpha.2` as 19 citable seams (`L1`–`L19`); each entry cites its official upgrade-guide source
- The official in-repo plugin-development skill: [references/official-plugin-dev-skill.md](../references/official-plugin-dev-skill.md)
- Repo-root constraints: `references/official-docs/AGENTS.md`, `references/official-docs/packages/AGENTS.md`, `references/official-docs/vendor/README.md`; sync state in `references/official-docs/SNAPSHOT.md`
- Site crawl HTML: `downloads/web/site/**` (EN+ZH full site) + `downloads/manifest.tsv` (download ledger)
- Upstream Cordis: `downloads/github/cordis/**` + research `references/upstream-cordis.md`
- Cordis paper: `downloads/github/paper/**` + research `references/cordis-paper-and-community.md`
- Website research: `references/website-pages.md`
- Repo research: `references/harness-repo.md`
- Community/ecosystem: `references/community-ecosystem.md` + `references/community-repo-deep-dive.md`
- All source URLs: `references/sources.md`


<!-- source: guide/migration-0.2.md -->

# [Migration to the 0.2 generation (breaking changes)] guide/migration-0.2.md

# 迁移到 0.2 世代(migration-0.2.md)

> **适用对象**:所有针对 DeepSeek Harness `0.1.6-alpha.2` 及更早版本开发的第三方插件(bundle / 纯 cordis 插件 / UI 插件 / MCP 配置包)。
> **基线**:本文件对照 `d743267388641bc76f17c45ce8b4c231aed1d32c`(tag `dsh-v0.2.1-alpha.2`,2026-10-09,master HEAD)核验;区间起点为 `ddefc45fbc7f8e46dd73185e68295696d1297887`(tag `dsh-v0.1.6-alpha.2`,2026-09-17)。
> **覆盖范围**:§0–§12 覆盖 `0.1.6-alpha.2` → `0.2.1-alpha.1`;
> **§13 覆盖 `0.2.1-alpha.1` → `0.2.1-alpha.2`(19 个 seam,`L1`–`L19`)**。
> 两段跨度都已有稳定 ID,可被扫描器引用(`dsh-plugin-upgrade` 的走廊目前只到 `legC`)。
> **权威优先级**:官方 `docs/upgrade-guide/**`(原文副本 `references/official-docs/docs/upgrade-guide/`)> 本文件 > 社区文章。官方每一条"next release removes X"都对应本文件的一节。
> 事实核实方式:源码 `path:line` + 提交号,全部可在 `D:\deepseek-harness`(或你的 checkout)复核;无法核实的项已标注 `UNVERIFIED`。

## 0. 先做这三件事

1. **确认你的宿主版本**。`dsh --version`;`0.1.6-alpha.2` 及以前 = 旧基线,`0.1.7-alpha.1` 起 = 已含本文件全部破坏性变更(会话格式 V4 从 `c36a83ff6b` 即 `dsh-v0.1.7-alpha.1` 起)。
2. **跑一次 `dsh --profile <name> --dump-config`**,看有没有 `patch: entry <id> not found` 警告——这是本世代最容易踩的静默失效(插件行被上游删掉/改名后,你的 override 不再命中任何行,且**不报错**)。
3. **按下面的表逐项对照**。破坏性变更集中在四块:会话格式 V4、settings 重写、invariants 删除、客户端 slot / Remote API 改名。

## 1. 版本与发布线(先认清"最新"是哪一个)

| 问题 | 答案(**2026-10-09 复核**;完整谱系见 [version-matrix.md](version-matrix.md)) |
|---|---|
| master 最新 tag | `dsh-v0.2.1-alpha.2`(2026-10-09,`d743267388641bc76f17c45ce8b4c231aed1d32c`;**= master HEAD**) |
| npm `latest` dist-tag | `0.2.0-rc.2`(**不是** master 最新 tag) |
| npm `alpha` / `next` | `alpha`=`0.2.1-alpha.2`、`next`=`0.2.0-rc.2` |
| 是否存在稳定版 | **否**。全部 31 个 npm 版本都是 `-alpha.N`/`-rc.N`,全部 26 个 GitHub Release 都是 `prerelease: true` |
| 区间内发布线(9 个) | `0.1.6-alpha.2`(09-17) → `0.1.7-alpha.1`(09-22) → `0.1.5-rc.3`(09-22) → `0.1.7-alpha.2`(09-22) → `0.1.7-rc.1`(09-23) → `0.1.7-rc.2`(09-24) → `0.2.0-rc.1`(09-28) → `0.2.0-rc.2`(09-29) → `0.2.1-alpha.1`(10-03) |
| Node 门槛 | `^22.19.0 \|\| >=24.0.0`,**区间内未变**(无 `.nvmrc`/`.node-version` 文件) |
| `dsh.version` 字段 | **不存在**。DSH 的包清单只有 `dsh.bundle` / `dsh.profile` / `dsh.client` 三组键 |
| 官方发布说明在哪 | 仓库里**没有 `CHANGELOG.md`**、也没有 `docs/persistence-changes/releases/*` 新条目,所以逐版本说明只在 GitHub **Releases** 的 body 里(`gh release view <tag>` 或 Releases API)。本文件的每一条都对照过 tag `dsh-v0.1.7-alpha.1` / `dsh-v0.2.0-rc.2` / `dsh-v0.2.1-alpha.1` 的发布说明 |

**官方 0.1.7-alpha.1 发布说明里点名的四条插件必改**(原文"其他变更"段):Session 日志升级 V4、工作区文件读取统一为 `readBytes`(旧接口必须迁移)、官方 DeepSeek 适配器**只用 Messages API**(移除 Chat Completions 与 `protocol` 选项)、设置改由当前 Profile 的插件配置保存(旧 `settings.yaml` 只尝试导入一次)。同版本还改了:Agent 预设改由插件组合包声明/安装(旧目录预设需迁移)、组合包支持按序多 patch、插件可声明"无需重载的配置字段"、新增 `--dump-config-schema`、插件可用 locale 声明多语言标题描述 + `package.json` 图标。

**官方 0.2.1-alpha.1 的两条破坏性变更**(原文):移除运行时 invariant 插件及各包的 `./invariant` 导出;输入区统计扩展拆为 `activity` 与 `usage` 两个独立入口(**覆盖旧 `stats` 整行的插件必须改注册 ID**)。同版本:子路径插件不再读独立 `package.json`;插件管理页新增「让 Agent 创建插件」;实验性 Claude Code Mods 兼容层。

**npm 陷阱(最贵的一条)**:库包(`dsh-tools`、`dsh-base`、`dsh-headless`、`dsh-session-persistence-jsonl`、`dsh-session`、`dsh-settings`、`dsh-workflow` …)的 `latest` 至今钉在 **`0.0.1-rc.1`**。写 `"@deepseek-ai/dsh-session": "latest"` 会装到远古版本;必须写死版本或钉 `next`/`alpha`。唯一的例外是 CLI 包 `@deepseek-ai/dsh`(`latest`=`0.2.0-rc.2`)。

**跨线 peer 范围**:`^0.1.x` 与 `^0.2.x` 互不覆盖,要同时支持两条线必须写两侧范围。⚠️ **peer 范围会被安装期强制**(本节早期版本写成"DSH 不校验范围",已更正):`dsh plugin add` 会拿运行时的版本去比插件声明的 peer 范围,不匹配就**拒绝安装**并把声明的范围原样打印出来。跨线支持因此必须**在范围里同时列出两侧的 prerelease 元组**(每个元组一个 clause),否则 0.2.x 宿主装不上——详见 [plugin-dev-guide.md](plugin-dev-guide.md) §7.3 的 `dsh-plugin` 段。范围之外仍要配**运行时特性探测**,因为范围只能表达"能不能装",表达不了"某个 API 在不在"。

## 2. 会话格式 V3 → V4(影响最深,涉及历史日志)

| 事实 | 值 |
|---|---|
| `SESSION_FORMAT_VERSION`(旧基线 `ddefc45`) | `3` |
| `SESSION_FORMAT_VERSION`(`c36a83ff6b` = `dsh-v0.1.7-alpha.1` 及之后) | **`4`**(`packages/core/session/src/types.ts`) |
| 首次写入 V4 的 tag | `dsh-v0.1.7-alpha.1`(2026-09-22)——**你本机装的 0.1.7-alpha.2 CLI 已经在写 V4** |
| 转换包 | `@deepseek-ai/dsh-session-format-v3-to-v4`(npm 自 `0.1.7-alpha.1` 起) |
| 官方 `docs/session-format-status.md` 的记录 | ✅ **已解决**(2026-10-09 复核):上游已更新为 `latestReleasedVersion: 4` / `evidenceTag: dsh-v0.2.0-rc.2`。本项目镜像快照早于该更正(仍写 `latestReleasedVersion: 3` / `dsh-v0.1.5-alpha.1`),**以官方 live 为准** |

对插件作者的后果:

- **V3 读方拒绝 V4 日志**。任何自己解析 `session.v*.jsonl.zstd` 的插件,遇到 0.1.7+ 写的日志必须先经 V3→V4 转换,不能"读到就 fold"。
- **tool 结果不再是 content block**:V4 把 user-role 里的 `tool-result` 提升为 **tool-role 消息**(必填 `toolCallId`,可选 `isError`),并从 content-block union 中移除。同时新增 `developer/message` 事件、`turn/end.reason: 'forked'`、`request/header.system` 键被**禁止**(旧日志里的该键会被读方拒绝)。
- **message source 改为生产者自有**:旧的 plugin wrapper 被 producer-owned `source.kind` 取代(含冻结的改名表与冲突规则;PTC 生产者写 `source.kind: 'ptc-mode'`)。迁移保留每个已准入事件与坐标,未知生产者归属**保留全部自有 JSON 属性**。
- 迁移是**非破坏性**的:写打开会在不改动前代文件的前提下发布当前后继;已存在的 V4 文件不会重跑该转换边。V4 内部的兼容性变更只能通过新的 acknowledgement 记录,**破坏性变更必须再 bump 写入版本**,不能复用 3→4 这条边。
- 只读用法:`docs/persistence-changes/2026-09-16-session-format-v4.md`(原文副本)、`docs/session-format-status.md`、`packages/session/session-format-v3-to-v4/README.md`。

**插件实践**:要同时支持两条线,就要能处理**两种事件形态**(V3 的 user-role tool result + V4 的 tool-role 消息);更稳的做法是**别自己解析日志**——用 `ctx.sessionPersistence` / session projection / `session/event` 监听,把版本差异留给宿主。

## 3. Settings 全面重写(第三方插件最大的破坏点)

`packages/settings/settings-file` 被**整体删除**(36 文件、+1203/−4107),`ctx.settings` 换了类型。

| 旧(≤ `0.1.6-alpha.2`) | 新(≥ `0.1.7-alpha.1`) |
|---|---|
| `@deepseek-ai/dsh-settings` + `@deepseek-ai/dsh-settings-file`(文档 provider,`$DSH_HOME/settings.yaml`) | `@deepseek-ai/dsh-settings` + **`@deepseek-ai/dsh-config-editor`**(直接编辑 Cordis patch) |
| `ctx.settings` = 用户文档注册表 | `ctx.settings` = `SettingsForms extends Service`(`static inject = ['configEditor','profileContext']`) |
| `register(ns, schema, opts)` / `installSection(...)` | **已删除**——表单由 profile 条目的 `Config` schema **自动派生** |
| `SettingsScope<T> = { get, watch, update, replace }` | **已删除** |
| `SettingsRegisterOptions = { base, applies: 'live'\|'restart', validate }` | **已删除**,`applies` 恒为 `'live'` |
| — | `configure({ auto? }, owner?)`、`describe(options?)`、`update(ns, patch, expectedRevision?)`、`replace(ns, section, expectedRevision?)`、`mutate(ns, ops, expectedRevision?)`、`writable`、`documentPath`、`prepareDocument()` |
| — | 事件 `settings/document-updated`;错误 `SettingsConflictError`(`code: 'SETTINGS_CONFLICT'`) |
| 客户端 `ctx.settingsScope`、slot `settings.plugin.item` | **删除** → slot `settings.plugins.tab` |

新机制要点:

- **表单命名空间 = profile 条目 id**(不是插件名):同一插件挂两行、id 不同 → 两张表单。
- **普通(非 volatile)字段不进表单**,只有 `Volatile<T>` 字段会。`Volatile<T>` 来自 `@deepseek-ai/cordis`,schema 写法 `z.<type>().volatile()`;读用 `.get()`,跨字段校验用 `.check()`,变更监听 `loader/volatile-update`。
- 旧 `$DSH_HOME/settings.yaml` 会**一次性导入**当前 profile,然后重命名为 `settings.yaml.imported`;被当前组合拒绝的 section 只留在那个改名文件里。
- 只读用法:`docs/subsystems/settings.md`(已改名 "Plugin Configuration Forms")、`docs/cookbook/adding-a-settings-card.md`(已重写,标题 "Cookbook: live configuration forms")。

## 4. 运行时 invariants 全部删除

- `@deepseek-ai/dsh-invariants` 包、`ctx.invariants` 服务、`InvariantRegistry` / `InvariantInstaller` / `InvariantFailure` / `InvariantError`、以及**每个 `<package>/invariant` 子路径导出**都已不存在;整棵 `packages/runtime-diagnostics/` 被删。
- `sdk-minimal` profile 里五行(`invariants`、`session-invariant`、`agent-invariant`、`scope-invariant`、`agent-loop-invariant`)消失:**仍指向这些 id 的 patch 会打印 `patch: entry <id> not found`**(不报错,只是不生效)。
- 四个 emitter 从"重抛"改为"记录并继续":`credentials/reference-updated`、`credentials/record-updated`、`authorization/settled`、`llm/adapters-updated`。**靠 `INVARIANT` 异常做失败传播的插件必须换成自己的通道。**
- `docs/subsystems/invariants.md` 是区间内**唯一被删除的官方文档**,动作内容搬到了 `docs/upgrade-guide/v0.2.0-rc.2/remove-runtime-invariants/guide.md`。

## 5. Bundle / 清单格式

- `dsh.bundle.patch` 现在接受**有序数组**:`{ "patch": ["./base.patch.yml", "./web.patch.yml"] }`,按序作为同一层应用,且每个文件里的相对插件路径**相对该文件所在目录**解析。校验失败信息:`dsh.bundle.patch must be a file path or a list of file paths`。
- 层顺序未变:`dsh.profile.bundles` 顺序 → profile 自己的 `cordis.patch.yml` → `$DSH_HOME/cordis.patch.yml` → 各 `--patch`(argv 顺序)。**按 id 覆盖时整段 `config` 被替换,不做深合并。**
- 包清单还可以声明 `dsh.manifestVersion`(格式标识,当前 `1`,与 npm 版本、会话格式版本无关)与 `engines.dsh`(作者声明的兼容范围,**当前安装器/加载器都不强制**——真正会被强制的是 `@deepseek-ai/dsh-*` 的 **peerDependencies**,安装期即拒绝不匹配者,见 [plugin-dev-guide.md](plugin-dev-guide.md) §7.3)。类型定义见 `@deepseek-ai/dsh-package-manifest`。
- 展示元数据契约(Plugin Manager / Settings 在**不激活插件**的前提下读取):包根读 `package.json` 的 `name`/`description`/`icon`,或导出 `./locale/*.json` 的 `meta.title`/`meta.description` 与 `./icon`;图标支持 SVG/PNG/JPEG/WebP,**≤ 256 KiB 且必须落在包内**(绝对路径、URL、指向包外的 symlink 都被拒)。子路径插件**永远不读** `<subpath>/package.json`——标题/描述走 `locale/*.json` 的 `meta.*`,图片走导出的 `<subpath>/icon`。
- `dsh.client` 键**未变**:`platform`(web)、`inject`(仅排序,不是 cordis 服务注入)、`immediately`(一阶段预取)、`external`(基线与注入边之外的精确模块请求)。但客户端半侧只挂在**说明符恰为裸包名**的那一行上:把一个包拆成多行的组合包,其 UI 只随根行启停。

## 6. 组合行变化(会让旧 override 打空)

`packages/bundle/base/cordis.patch.yml` 本次删/增的行:

| 删除的行 `name` | 新增的行 `name` |
|---|---|
| `@deepseek-ai/dsh-llm-deepseek` | `@deepseek-ai/dsh-llm-deepseek-api-key` |
| `@deepseek-ai/dsh-settings-file` | `@deepseek-ai/dsh-llm-deepseek-account` |
| | `@deepseek-ai/dsh-authorization` |
| | `@deepseek-ai/dsh-deepseek-account-platform` |
| | `@deepseek-ai/dsh-config-editor` |
| | `@deepseek-ai/dsh-settings` |
| | `@deepseek-ai/dsh-otel` |

**Web 组合新增 preset 分叉**:`packages/bundle/web-app/presets/{standard,minimal,ptc,cordis}.patch.yml`。行集**因 preset 而异**——`standard`/`cordis`/`ptc` 声明时钟读取与四个 `schedule_*` 工具,`minimal` **两者都没有**。假设"Web 组合行集固定"的插件要改成按 preset 探测。

**可选用 bundle 的两次反向变动**(都在本区间):

1. `v0.1.7-rc.2`:`web-app` 曾带 `time-context`/`schedule`/`ui-schedule`(`disabled: true`),"Automation tasks" 可选 bundle `@deepseek-ai/dsh-experimental-schedule-bundle` 负责插入它们。
2. `v0.2.0-rc.2`:该 bundle **又被撤掉**;`web-app` 自己挂 `schedule` + `ui-schedule`,时钟行归 preset。加载 profile 时会自动把该 bundle 从 `dsh.profile.bundles` 移除并重写 `package.json`;**针对 `time-context` 的顶层 override 不再命中任何行**。

## 7. 客户端(UI 插件)破坏点

**Slot 变化**:新增 `plugins.bundle.config`、`plugins.bundle.activation`、`plugins.add.actions`、`plugins.detail.actions`、`plugins.detail.badge`、`plugins.detail.section`、`conversation.input.activity`、`conversation.header`、`conversation.header.leading`、`shell.bottom`、`shell.leading`、`shell.quota-notice`、`sidebar.workspaces.session.menu.item`、`sidebar.workspaces.session.row.action`;**删除** `conversation.session.header.leading`(位置由 `conversation.header.leading` 接管)、`settings.plugin.item`(→ `settings.plugins.tab`)。

**workspace-files Remote(UI 插件直接受影响)**:

| 旧 | 新 |
|---|---|
| `readAll` / `readRelated`(RPC 回调,返回解码后的字节结果) | **`readBytes(path, { range?, baseFile? })`**,经 Connection 二进制 RPC 返回原生 `Uint8Array` |
| `read(path, { offset?, limit? })` | 未变 |
| `changes()`(工作区级) | **`changes(path)`**(单目标;目录被限制在工作区内) |
| `list(path)` | 未变 |

`DocumentPreviewProps` 相应新增 `addResource` / `setResources` 依赖回调;`baseFile` 用于从另一文件目录解析相对目标。

**右侧栏(sidebar-right)新增**:`keepMounted`(跨隐藏/切会话/停靠惰性保留已访问 body)、`tab.actions.bindCommands(commands)` / `SidebarRightTabCommands`(目前是可选 `refresh`)、`tab.refreshShortcut`、`focusedTarget(element?)`、`commandTarget(element?)`、`SidebarRightTarget` + `isTargetCurrent(target)`。保留语义也变了:根作用域的 `rightbar` 为当前会话与已初始化 `keepMounted` 的后台会话**各留独立座位**,重载会恢复保存的布局(旧行为是全部回到折叠默认)。

**其它客户端新增**:客户端服务 `ctx.jobs`(`IJobs`,基于 `ClientJobsModel`)+ `ctx.jobController` + `remote.job.{list,follow,kill}`;workspaces follow 流新增 `pinned` 增量,已归档会话设置页被侧栏 `ArchivedFilter` 取代。

**输入区统计的注册 ID 变化(0.2.1-alpha.1 官方破坏性变更)**:原来一个 `stats` 入口被拆成 **`activity`** 与 **`usage`** 两个独立扩展入口。任何"整行覆盖旧 `stats`"的插件必须改成分别注册/覆盖这两个 ID,否则统计区无声消失。

**客户端条目的激活语义(血泪案例)**:`dsh.client` 的 `inject` **必须是静态数组**——cordis 不接受函数形式的 `inject`。曾有一个 0.2.0 插件把 `inject` 导出成函数,结果声明为零依赖 → `apply()` 抛 `cannot get property "sidebarRightTabs" without inject` → 日志只留一行 `web boot: 1 entry did not activate` → **插件自愈机制把整个 bundle 禁用了**(快照落在 `.dsh/recovery/`),这才让桌面端下次能启动。教训:单测要断言"`inject` 是数组且覆盖 `apply()` 触及的每个服务",并对着一个 cordis 等价语义的严格 `ctx` 跑。

**另一个高频坑**:`apply()` 里往 `ctx` 上写**未声明**的属性会抛错,并可能静默杀掉整个插件的激活;可选服务一律用 `ctx.get(name)`,**不要**用 `ctx.<name>`(`ctx.<service>` 只对你自己 `inject` 里声明过的服务合法)。Remote 服务调用是**位置参数**不是对象参数(`get()` 能用,最有欺骗性);工具 `parameters` 是 DSH 自己的方言,**不是 JSON Schema**。

## 8. 工具 DSL 的新增项(无改名)

| 符号 | 变化 |
|---|---|
| `ToolDefinition.projectContent?(exec, result): ContentBlock[] \| undefined` | **新增**:在 `tools/post-execute` 策略**之前**安装执行期准备好的内容;策略的替换仍然权威;绕过 post-execute 的管线失败不会调用它 |
| `ToolSchema.deferLoading?: true` | **新增**标记,会经 `schemaOf()` 进入模型可见 schema |
| `PreToolDecision` 的 `ask` 变体 | **变化**:`{ kind:'ask'; reason?: string }` → `{ kind:'ask'; reason?: string; displayReason?: { readonly en: string; readonly [locale: string]: string } }`。`reason` 是审计用的批准理由,`displayReason` 是本地化的提示文案 |
| `MessageSourceMap` 增强点 | **新增**(`@deepseek-ai/llm`);tool registry 声明 `'tool-registry': { kind: 'tool-registry' }` |

### 8.1 `agent.inject()` 的 source 包装被退役(V4 拒绝)

```ts
// ❌ 旧写法:`kind: 'plugin'` 这个 catch-all 已被删除
agent.inject({ content, source: { kind: 'plugin', plugin: 'my-plugin' } })

// ✅ 新写法:先声明合并自己的 kind,再用它
declare module '@deepseek-ai/dsh-llm' {
  interface MessageSourceMap { 'my-plugin': { kind: 'my-plugin' } }
}
agent.inject({ content, source: { kind: 'my-plugin' } })
```

`MessageSourceMap` 现在只有 `user` / `model` / `tool` / `system-prompt` 四个内建成员(`packages/llm/llm/src/message.ts:110-115`),插件 kind 靠声明合并加入;V4 在消息准入处拒绝旧的 `{ kind: 'plugin', plugin: … }` 包装(官方 `docs/cookbook/adding-a-tool.md:49` 原文警告)。`agent.inject()` 本身只是 `send(input, 'next-step', false)`(`packages/core/agent-loop/src/agent.ts:171`)——不唤醒;要唤醒用 `followup()`(下一回合)或 `steer()`(下一步)。

## 9. 官方新增的插件可挂能力

| 能力 | 一句话 | 挂点 |
|---|---|---|
| OTel 上报(`docs/subsystems/otel.md`) | 普通事件与 Session 日志通道的共享工厂;自带传输与 SDK 批处理,未使用时**不分配** provider/transport,通道之间不共享队列 | `ctx.otel`:`createEventReporter(options)` / `createSessionLogReporter(options)`;**消费者必须在自己 fiber 释放时 drain 通道**。包 `packages/telemetry/otel`(`packages/telemetry` 为新区) |
| 产品遥测(`docs/subsystems/product-telemetry.md`) | 只发显式选定的分析事件,**不自动采集** Session 数据或标识 | `ctx.productAnalytics`(`ProductTelemetry`);Desktop 消费者在 `packages/client/product-analytics`,普通 Web 客户端不采集 |
| 语音输入(`docs/subsystems/voice-input.md`,实验) | 三角色(Definition / SenseVoice Provider / Remote Consumer)+ 可选 bundle | Host 侧 `SpeechProvider` / `SpeechProviderId` / `SpeechSpec` / `transcribe()`;浏览器侧 `TranscriptionRequest`,并挂新 slot `conversation.input.activity` 与 `plugins.bundle.activation` |
| Claude Code mods(`docs/subsystems/claude-code-mods.md`) | 让 Claude Code mod 以 DSH 插件形式运行,并逐条列出与 Claude Code 2.1.287 的差异 | `defineMod({ name, version, root, userConfig, register })`,作为 `cordis.yml` 一行挂在 bridge 之后;类型从 `@deepseek-ai/dsh-experimental-claude-code-mods` 导入。**官方原文安全提示:mod 不加沙箱,只挂你愿意当插件运行的 mod**(hooks 模块拥有 Node 全局与进程全部权限) |
| 插件配置表单(`docs/subsystems/settings.md`) | 见 §3 | `ctx.settings` + `Volatile<T>` + `loader/volatile-update` |
| 圆角规范(`docs/ui-radius.md`) | 按组件角色/尺寸取统一圆角的样式契约 | 无代码缝,UI 插件照做 |
| 反向代理部署(`docs/user/guide/public-deployments.md`) | `dsh --profile web --public-url … --trusted-host …` | 不是插件挂点,但决定客户端插件里的 URL 怎么写 |

## 10. 官方新增的"插件开发"一等公民资料(务必先读)

DSH 自带的 preset 里有一整套**官方插件开发技能**,这是本世代最重要的新增资料:

| 文件 | 内容 |
|---|---|
| `packages/preset/agent-preset/skills/cordis-plugin-development/SKILL.md` | 官方权威流程:在工作区写 bundle → `plugin_manager` `install_bundle`(传绝对目录)→ 用 `cordis_inspect_query` 查精确 API。**明确禁止**手写 profile 的 `package.json`/`cordis.patch.yml`、禁止在 profile 目录里跑 pnpm |
| `.../references/host-plugin.md` | bundle 清单、展示元数据与图标清单、Host 导出形态、安装/启用/观察 |
| `.../references/ui-plugin.md` | `dsh.client`(`platform`/`immediately`/`inject`)、客户端懒工厂、slot 注册、主题与本地化 |
| `.../references/practices.md` | **插件实践总纲**:会话日志唯一真源、注册即 effect 的所有权、扩展点"用最弱的机制"排序、稳定性、性能(session projections)、UI 规则 |
| `.../references/mcp-bundle.md` | 纯配置 bundle 接 MCP server(`streamable-http` / `stdio`,`failOnStartupError: true`) |
| `.../references/user-actions.md` | 同一操作双调用方(UI 动作 + agent 工具);授权类动作必须**仅限用户** |
| `.../references/verification.md` | 无浏览器控制时的页面/面板设计核查 |
| `.../templates/{decoration,mcp}/**` | 可直接复制的 UI 插件与 MCP bundle 模板(含 locale 与 icon) |
| `.../skills/cordis-composition-reference/SKILL.md` | Loader patch 方言 + 可安装插件包清单 |
| `.../skills/editing-cordis-compositions/SKILL.md` | agent preset 组合的编辑流程 |

会话内可直接用 `cordis_inspect_list` / `cordis_inspect_query` 查**精确**服务方法、事件 `@mode`、已挂插件的 `Config` JSON Schema、可用 Tool、实时 Client Slot 树与主题 token——比读文档更权威。

## 11. 升级检查单

- [ ] `dsh --version` ≥ `0.1.7-alpha.1`(要 V4),或明确停留在 `0.1.6-alpha.2` 及以前并锁死。
- [ ] `dsh --profile <name> --dump-config` 无 `patch: entry … not found`。
- [ ] 删掉所有 `@deepseek-ai/dsh-invariants` / `<pkg>/invariant` 的 import 与行;失败传播换成自己的通道。
- [ ] `ctx.settings.register/installSection`、`SettingsScope`、`applies: 'restart'` 全部替换为 `SettingsForms` + `Volatile<T>`;把要暴露的字段标成 volatile。
- [ ] UI:`settings.plugin.item` → `settings.plugins.tab`;`conversation.session.header.leading` → `conversation.header.leading`;`readAll`/`readRelated` → `readBytes`;`changes()` → `changes(path)`。
- [ ] 自己解析会话日志的代码:处理 V4 tool-role 消息、`developer/message`、`turn/end.reason: 'forked'`;或改为消费 `session/event` / projection。
- [ ] 不再用新 `type` 追加会话事件(见 §2 与 `guide/plugin-dev-guide.md` §3.5 红线)。
- [ ] 补展示元数据:`locale/*.json` 的 `meta.title`/`meta.description` + 导出的 `./icon`(≤ 256 KiB、包内),并核 `files` 白名单。
- [ ] 若把 patch 拆成多文件:改成 `dsh.bundle.patch` 数组,确认每个文件里的相对路径按各自目录解析。
- [ ] 若覆盖 `schedule` / `time-context` / `ui-schedule` / `schedule_*`:按 §6 核对 preset 分叉与 bundle 撤并。
- [ ] 依赖里没有 `"latest"` 形式的 `@deepseek-ai/*`(库包 `latest` 仍是 `0.0.1-rc.1`)。
- [ ] ⚠️ peer 范围覆盖**每条线一个 clause**(六子句规范形,见 [version-matrix.md](version-matrix.md) §4.1),且知道它**会被安装期强制**——不是文档意图:`dsh plugin add` 拿运行时版本比对,不匹配直接**拒绝安装**。**不要写 `>=0.1.2-rc.1 <0.3.0` 这种跨代开区间**:它放行安装,但运行时会炸。范围之外仍要配运行时探测。
- [ ] `pnpm pack` 后装进干净 `DSH_HOME` profile 冒烟(`dsh-plugin-dev verify` 已对此对齐)。

## 13. 新走廊:`0.2.1-alpha.1` → `0.2.1-alpha.2`(19 个 seam)

> **本节解决本文件此前的最大缺口**:`dsh-plugin-upgrade` 的走廊只覆盖到 `legC`(`0.1.5-rc.2 → 0.1.6-alpha.2`),
> 而本文件此前是 `0.2.1-alpha.1` 之前唯一有内容的文档,且**没有任何 seam ID**——没法被扫描器引用。
> 本节给该跨度**稳定 ID(`L1`–`L19`)**、**症状**、**修法**与**官方出处**。
>
> **适用判定**:`dsh --version` 落在 `0.2.1-alpha.2`(或更新)时,**逐条核对**;
> 停在 `0.2.0-rc.2` 及更早的**与本节无关**。
> 官方出处全部已随镜像落地:`references/official-docs/docs/upgrade-guide/v0.2.1-alpha.1/<item>/guide.md`。
>
> ⚠️ **目录名陷阱**:`v0.2.1-alpha.1/` 是**变更被引入时仓库的版本号**,描述的是
> **从该版升到下一版**的变更——即这 19 条**落在 `0.2.1-alpha.2`**。原文措辞是
> *"In v0.2.1-alpha.1, X was so; the next release no longer…"*。

| ID | 官方项 | 受影响面 | 症状(不修的后果) | 修法 |
|---|---|---|---|---|
| **L1** | `subagent-activations` | 子代理工具/服务 | `backgroundMode`、`enableRunInBackground`、`run_in_background` 全部失效;`dsh-subagent-in-process-driver` 包**被删**;`ctx.subagents.start()` 不存在 | 删两个配置键与工具参数;`startActivation({ …, delivery: 'caller' })` 后 `await activation.result`;自定义本地 provider 要实现 `prepareContinuable()` |
| **L2** | `tool-presentation-mode` | 工具呈现 | **`mode: both` 被拒绝**;`DSH_TOOLS_MODE=both` 与 `ctx.tools.presentAs('both')` 同样被拒 | 改为 `native` 或 `ptc` |
| **L3** | `web-listener-trust-config` | Web profile / 覆盖层 | **`webRuntime` 服务消失** → 注入它的 patch **永久等待**,Connection 起不来;`ctx.webRuntime.lanAddresses` 没了;**通配 host 加载期被拒** | `inject: [webRuntime]` → `[webStartup]`;`trustedHosts` 换服务;删 `lanAddresses`;通配 host 换具体 IP 字面量 |
| **L4** | `working-directory` | 提示词模板 / ACP·SDK 子代理 / `presentationMeta` | **保留 `{{cwd}}` 的自定义模板在发出模型请求前就失败**;`config.cwd` 被移除;嵌套 PTC 调用里的投影错误会让该次调用以 `ToolOutputError` 失败 | 删提示词里的目录子句并清空;`cwd` 改由子请求传或先调 `working_directory({ cd })`;保证投影纯且 JSON 安全 |
| **L5** | `session-title-provider-strategy` | 会话标题 | 三个导出符号消失(`registerSessionTitleLlmProvider` / `generateSessionTitleWithLlm` / `SessionTitleLlmMessageSelector`);`targetWords`/`targetCjkCharacters` 移出共享 Config | 改用 `ctx.sessionTitle.register({ id, automatic, generate })`,自己搭 system/input;拒绝 `tool-calls`/`max-tokens` 收尾 |
| **L6** | `session-title-adapters` | LLM 适配器 | 标题请求改走 `ctx.llm.prepareCall()`;只挂 `llm/stream` 中间件的组合不再被服务;`reasoning.efforts` 顺序错会被拒 | 用 `ctx.llm.registerAdapter()` 注册标题路由;`efforts` **从低到高**排 |
| **L7** | `ssh-helper-launch` | SSH / 远端 PTC | `node`/`bootstrapPath`/`bootstrapHash` 三键消失;**要求 SSH protocol 2**;helper 与 hash 不同步即失败 | 改 `launch: { kind: 'node-script' }` 或 `{ kind: 'executable' }`;helper 与 hash **必须同改** |
| **L8** | `team-direct-inbox` | Agent Teams(**数据丢失**) | `send_message` 只回 `{"sent":true}`(无 id、无 queued);`maxPendingMessagesPerMember` 被移除;**未投递的排队消息永不派发** | ⚠️ **升级前**让待发消息跑完;升级后必需的显式重发;改读 `sent` |
| **L9** | `instruction-home-override` | 指令加载 | `dsh-agent-instructions` 的 `dshHome` 字段被移除,**残留键被静默忽略**(不报错) | 删 `dshHome`;改用进程环境变量 `DSH_HOME`;用 `--dump-config` 复核 |
| **L10** | `experimental-package-composition` | profile 组合 | CLI 与 Python runtime 的安装依赖**不再含 hook 桥**;**装了不等于挂上**;只"启用"已被删行的 patch **不会把它插回来** | 显式装 peer;选 bundle **先于**覆盖其行;用完整 `insert` 替代对已删行的 override |
| **L11** | `native-subagent-bundle-tools` | Bundle 元数据(**类型破坏**) | 选中 bundle 会额外注册 Host 全局工具,对最小预设也可见;**构造或严格校验 `BundleInfo` 必须补两个必填字段 `official`/`availability`**,`PluginPackageRef` 同理 | 补必填字段;删同名冗余 Host 行(会被遮蔽) |
| **L12** | `python-ptc-sandbox` | Python PTC | Python runtime **要求 `sandbox` + `sandboxPolicy` 服务**,默认 `read-only`;受限运行以 `sandbox-unavailable` 失败 | 挂 `dsh-sandbox-local` + `dsh-sandbox-policy`;设 `sandbox-policy` 行的 `mode`/`workspaceRoot` |
| **L13** | `python-optional-resources` | Python 运行时资源 | wheels **不再内置** Office sidecar 与 authoring 解释器;SDK 启动不再自动下载 | 用前显式 `download_office()` / `download_primary_runtime()`;离线预置 `DSH_RESOURCE_CACHE` |
| **L14** | `plugin-option-placement` | CLI 调用 | **`dsh plugin add <pkg> --profile <name>` 现在直接失败** | 改为 `dsh plugin --profile <name> add <package>`(`--profile` 必须在转发参数之前) |
| **L15** | `terminal-command-labels` | 终端 UI 插件(**类型破坏**) | `TerminalBlockLabels` 新增**必填** `commandLine(line: number): string` | 给每个 labels 对象补 `commandLine`,返回本地化名称 |
| **L16** | `deepseek-model-discovery` | 模型发现 | `deepseek-official` 路由**无 key 时返回空目录** → Desktop/Web 隐藏 DeepSeek 组 | 配 key 或用 `apiKeyEnv`(默认 `DEEPSEEK_API_KEY`)指定的环境变量;消费方要处理空目录 |
| **L17** | `new-session-generations` | 客户端集成 | 新建会话**同时新建 Agent**,不再复用空会话(改过预设定义后旧会话不会自动跟上) | 改预设后用 New Session;要复用用 `openWorkspace`/`connectWorkspace` |
| **L18** | `mock-messages-events` | 测试支撑 | `dsh-llm-mock-server` 会在结构化 SSE 负载旁**同时发具名 Messages 事件** | 消费具名事件与其 `data` 字段,别只筛匿名消息 |
| **L19** | `desktop-test-auth-popup` | Desktop 测试构建 | 所有**测试**认证弹窗默认关闭;测试网关 cookie 不过重启 | 需要时设 `updates.allowTestAuthPopupWindow: true` 并**完全重启** |

**另有一条回填**(在 `v0.2.0-rc.2/` 目录里、但基线之后才加入):
`bundled-python-tests` —— 打包的 NumPy/pandas **测试目录**被移除;要跑上游测试套件请
**另建** Python 环境(别用会被升级替换的托管运行时目录)。它放在旧目录属于例外——
上游自己的规则是"版本号 bump 后旧版本目录冻结"。

**官方发布说明**(双语,点名上述变更):
`https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.2.1-alpha.2`。

> **本节与 `guide/version-matrix.md` §3 是同一批事实的两种形态**:矩阵按"作者视角的破坏清单"写,
> 本节按"可被扫描器引用的 seam 表"写(`L1`–`L19` + 症状 + 修法)。
> **给 `dsh-plugin-upgrade` 提议 `legD` 走廊时,直接引用本节 ID。**
> 本库的 `dsh-plugin-dev check` 中 `redline-async-apply-registration` 是家族内该缺陷的唯一检测器,
> 已被 `legC` 的 W0 卡引用为 E2 的预防门——同一条联动可以复用到本节。

---

## 14. 已知分歧与未核实项(诚实标注)

1. ✅ **已解决(2026-10-13)**:上游 `docs/session-format-status.md` 已是 `latestReleasedVersion: 4` / `evidenceTag: dsh-v0.2.0-rc.2`,与代码和 npm 一致;本项目 `references/official-docs/` 镜像**已重同步**到 `d743267388641bc76f17c45ce8b4c231aed1d32c`(`dsh-v0.2.1-alpha.2`,2026-10-13),该矛盾在本库内部也一并消失。
2. `UNVERIFIED`:未解包已发布的 npm tarball 逐字节确认产物与 tag 一致。
3. `UNVERIFIED`:仓库内无 `CHANGELOG.md`、无 `docs/persistence-changes/releases/*` 新条目,因此**没有官方逐版本 release notes 可引**;tag 对应的提交主题是唯一线索。
4. `ctx.agent` 在两个基线都**不存在**(不是"被移除")。
5. 官方 `docs/upgrade-guide/` **没有 index、也没有任何入链**(`docs/AGENTS.md`、README、脚本都不引用)——靠本文件与 `SKILL.md` 做入口。
6. `packages/` 的改名检测在大 diff 下会被 git 跳过,因此"新增包"清单可能把改名当成新增。

---

*维护:官方每次发版后重跑 `pwsh -File scripts/sync-official-docs.ps1`,再按 `references/official-docs/docs/upgrade-guide/**` 与 `docs/persistence-changes/**` 增量更新本文件;行号与提交号引用 `references/official-docs/SNAPSHOT.md` 的基线。*


<!-- source: guide/family-toolchain.md -->

# [Family toolchain router (check catalogs and crosswalk)] guide/family-toolchain.md

# 家族工具链路由(family-toolchain.md)

> **这份文件回答**:我的问题该找哪个工具?哪个文档是这项事实的**权威所有者**?
> 以及 `dsh-plugin-dev check` 的 16 项检查和 `dsh-plugin-doctor` 的 33 项检查是什么关系。
>
> **原则**:**一个事实只有一个所有者。** 本知识库只负责"认知 + 索引",
> 机械检查交给工具;发现重复时**链接**而不是**复述**。
>
> **测量时间**:2026-10-13。

---

## 1. 路由表:问题 → 所有者 → 可引用产物

| 你的问题 | 权威所有者 | 可引用产物 | 命令 |
|---|---|---|---|
| 这个插件包结构对不对? | `dsh-plugin-doctor` | `SPEC.md`(规范)R0–R8 | `npx @perrylink/dsh-plugin-doctor --only R` |
| 我的 Cordis 用法有没有踩契约? | `dsh-plugin-doctor` | `SPEC.md` §4.2b,K1–K14 | `… --only K` |
| 装了能不能起来? | `dsh-plugin-doctor` D 组 / `dsh-test-drive` | D0–D3/D9;test-drive 的四态记录 | `… --only D` |
| 我在各个目录里被收录对了吗? | `dsh-plugin-doctor` CC 组 | CC1–CC5 | `… --only CC` |
| 我的插件质量几分? | `dsh-score` | 五维评分 + 排行榜 JSON/MD | `dsh-score <repo\|pkg>` |
| 怎么从旧世代升上来? | `dsh-plugin-upgrade` | 走廊卡(`legAB` 20 seams / `legC` E1–E5) | `dsh-plugin-upgrade-scan` |
| 共享代码怎么写? | `@perrylink/dsh-plugin-kit` | `data/peer-range.json`、seam/gate 契约、新插件骨架 | — |
| 社区认证是什么等级? | `dsh-plugin-certification` + `dsh-cert-mcp` | 五维 A–E、等级 A–D、安全否决 | MCP:`get_certification` |
| 目标宿主这一版有什么? | **本知识库** | [`version-matrix.md`](version-matrix.md) | `dsh --version` |
| 该改哪一行? | **本知识库** | [`migration-0.2.md`](migration-0.2.md) | — |
| 官方怎么规定的? | **本知识库镜像** | `references/official-docs/` | — |

---

## 2. `dsh-plugin-doctor`:33 项检查全表

规范来源是 **`SPEC.md`**(spec `v1`,checkset 字符串
`R0-R8+K1-K14+D0-D3,D9+CC1-CC5/3`),ID 空间**已冻结**(SPEC §3.3)。
共 **33** 项 = 9 (R) + 14 (K) + 5 (D) + 5 (CC)。

> ⚠️ **不要引用 doctor 的 README 核对检查 ID。** README 在 5 处写 `K1–K9` 和"16 checks",
> 而规范 `SPEC.md` 定义的是 **K1–K14** 和 **21 项规范门禁**——README 在 K10–K14 落地时没更新。
> README 内部还自相矛盾(43 / 41 / 37 仓 vs `data/verified-repos.json` 的 40 条)。

### 2.1 裁定词表(SPEC §2,规范,五种)

| 裁定 | 含义 |
|---|---|
| `pass` | 已评估且成立 |
| `warn` | 已评估、看似违规,**启发式**,需人工复核,**单独不足以定罪** |
| `fail` | 已评估且违规 → 计入失败 |
| `error` | **检查本身无法完成** → 计入失败 |
| `skip` | **未评估** —— **绝不能渲染成 pass** |

正交分类:`critical`(目前仅 `R1`)与 `category` ∈
{`plugin-defect`, `policy`, `not-applicable`, `environment`, `infrastructure`, `unsupported-host`, `unstable`}。
**后四类永远不是插件缺陷**;`policy` 表示这是"家族约定而规范并不共享"(R5、R8)。

**降级规则**(SPEC §3.2,规范):被请求的组里**每一项都 `skip`**,则该组"未被评估",
不得报告成功;零检查执行、或组名匹配不到任何东西,同样不得报告成功。
规范里记了理由:**曾经一个写坏的组名让 35 个仓库的 CI 门禁全绿,而实际什么都没查。**

**K 组覆盖模式**(SPEC §3.4,规范披露):`src`(带 `src/` → 完整预期覆盖)·
`lib-fallback`(无 `src/`,退到 `main` 所在目录 → 真实覆盖但**不等价**,K3–K7 可能少报)·
`none`(都不存在 → 降级)。

### 2.2 R 组 —— 静态 · 包结构(R0–R8)

| ID | 要求 | 裁定 | critical |
|---|---|---|---|
| `R0` | 基础清单字段 `name`/`version`/`license`(SPDX)/`type`;仓内有 `README.md` + `LICENSE` | warn · pass | 否 |
| `R1` | **激活门 `dsh.bundle.patch`** 声明为非空字符串 | fail · pass | **是——唯一 critical** |
| `R2` | tarball 完整性:`npm pack --dry-run --json --ignore-scripts` 成功;文件表含声明的入口**与** patch 文件且都非空;tarball 名 = name+version | fail · pass · skip | 否 |
| `R3` | `cordis.patch.yml` 结构(启发式):存在、解析为顶层 YAML **数组**;空数组合法;非空必须含 `- insert:` 与 ≥1 个 `id:`;每个 `name:` 等于或命名空间在包名之下 | skip · fail · pass | 否 |
| `R4` | 入口契约:`main`/`exports` 入口存在且同时导出 `apply` 与 `name`;`inject` 是字符串字面量数组 | fail · warn · pass · skip | 否 |
| `R5` | 依赖策略:① 任何位置不许裸 `cordis`/`schemastery`;② 导入的 `@deepseek-ai/cordis` 必须被声明;③ 它必须在 `peerDependencies` **和** `devDependencies`,**绝不** `dependencies`;④ `@deepseek-ai/dsh` 不得是直接 `dependencies` | fail · pass · skip | 否(`policy`) |
| `R6` | `engines.node` 兼容 `^22.19.0 \|\| >=24.0.0`(只声明 Node 23 会失败) | fail · warn · pass | 否 |
| `R7` | `files` 白名单已声明且覆盖入口 + patch;需要构建时入口不得指向源码 | fail · pass | 否 |
| `R8` | 不得有指向已被取代的预发布线的陈旧 peer 范围(`0.1.0-rc.*`、`0.1.1-rc.*`、`0.1.2-alpha.*`、`0.1.3-alpha.*`),也不得有开顶 `>=` 范围 | skip · fail · warn · pass | 否(`policy`;"报告**陈旧**,不是**损坏**") |

**规范门禁**(SPEC §1):`R0, R1, R3, R5, R6, R7, R8, K1–K14` = **21 项**,
故意排除 R2 和 R4(它们读的是**构建产物**)。

### 2.3 K 组 —— 静态 · Cordis 契约扫描(K1–K14)

K1–K9 回答"这个插件自身正确吗";**K10–K14 回答"它会不会干扰别的插件,或被别的插件悄悄吃掉"**。
全部为启发式;没有发现源码文件时一律 `skip`。

| ID | 含义 | 裁定 |
|---|---|---|
| `K1` | 每个 `ctx.<service>` 访问要么是 Context 内建、要么在 `inject` 声明、要么经 `ctx.get(name)` | skip · warn · pass |
| `K2` | **活数据序列化红线** —— 绝不 `JSON.stringify(ctx…)`、`structuredClone(ctx…)`、`{...ctx…}` | skip · **fail** · pass |
| `K3` | 定时器/全局监听被 `ctx.effect`(或 `ctx.on`)包裹,或显式自清理 | skip · warn · pass |
| `K4` | 定义 `Schema` 的文件里不得引入箭头函数 | skip · warn · pass |
| `K5` | `apply` 返回形态(v4 Effect 契约):disposer 函数、`null`、`undefined`、`Promise<disposer>`、或(异步)可迭代;async `apply` 合法但必须返回 `Promise<disposer>` | skip · warn · pass |
| `K6` | 每个注入的服务名要么是已知宿主接缝、要么带命名空间(含 `.`)、要么是自己 `provide` 的 | skip · warn · pass |
| `K7` | 无 v3 遗留 API:`ctx.using/scope/runtime/lifecycle/collect/accept/decline/alias/off/fork`、`ctx.config`、`ctx.start()/stop()`、v3 的 `inject: {required, optional}` | skip · **fail**(硬名单)· warn(软名单)· pass |
| `K8` | `export const Config` 必须是 Standard Schema 校验器,不是普通对象字面量 | skip · **fail** · pass |
| `K9` | 对象插件不得设 `name: 'apply'`(框架会把它重置为 `undefined`) | skip · warn · pass |
| `K10` | 用 `ctx.on(<waterfall 事件>, …)` 注册的监听器必须引用 `next` | skip · warn · pass |
| `K11` | 注册的工具名不得与宿主内建或保留字 `run_code` 冲突,也不得用 `tools.data.delete(def.name)` + 重新注册的遮蔽写法 | skip · warn · pass |
| `K12` | 传给 `super(ctx, '<name>')` / `ctx.provide('<name>')` 的键不得等于宿主接缝名 | skip · warn · pass |
| `K13` | `cordis.patch.yml` 不得带顶层(非 `insert`)且 `id` 属于宿主的行(按 id 定位的 patch **整行替换 config,不做深合并**) | skip · warn · pass |
| `K14` | **跨仓注入点冲突** —— 任何自有名字(服务键、工具名、命令名、patch `insert` 行 id)都不得与同工作区的兄弟插件仓重复 | skip · warn · pass |

**K14 有规范的"守卫感知豁免"**(SPEC §4.2b):在 `package.json` 的
`dsh-plugin-doctor.crossPlugin.exempt` 或旁挂 `dsh-plugin-doctor.yml` 里声明
`{kind, name, peer?, guard?}` 列表,`kind` ∈ {`service`, `tool`, `command`, `patch insert id`};
被豁免的冲突算 `pass`。工作区里少于两个插件仓时降级为 `skip`——
所以随包发布的 CI workflow(不传 `--workspace`)不会因此挂构建。

K10/K11/K13 的参照数据取自上游 `docs/event-producer-consumer.md` 的 `@mode` 列、
工具目录各节,以及基线 DSH `0.2.0-rc.2` 的 `packages/bundle/base/cordis.patch.yml` 顶层 id。

### 2.4 D 组 —— 动态 · 沙箱冒烟(D0–D3, D9)

| ID | 要求 | 裁定 |
|---|---|---|
| `D0` | `npm pack --ignore-scripts` 成功;tarball 写进**沙箱**,不是被测仓库 | fail · pass |
| `D1` | 宿主从 tarball 安装:`dsh plugin --profile headless add <tarball>`(带 `--ignore-scripts`)把包名写进 `dsh.profile.bundles` | skip · fail · pass |
| `D2` | `dsh --profile headless --dump-config` 输出层标记 `# == <package name>` | skip · fail · pass |
| `D3` | **无密钥 headless 冒烟(`MISSING_CREDENTIAL` 判据)**:无凭据时以**退出码 1 + `dsh: MISSING_CREDENTIAL`** 结束(有凭据环境下退出 0 也算 pass)。明确致命:`NO_ADAPTER`、`ERR_MODULE_NOT_FOUND`、`SyntaxError`、`TypeError`、`ReferenceError`、`Cannot find module` | skip · fail · pass |
| `D9` | **沙箱隔离** —— 沙箱被隔离而非删除;运行打印隔离路径;删除是单独显式步骤 | pass · fail |

D1 里"不是插件缺陷"的情形:缺 `pnpm`/`npm`(`infrastructure`)、宿主版本装不上(`unsupported-host`)、
pnpm `ignored-builds` 阻断(`environment`,报为 `skip`)。

### 2.5 CC 组 —— 生态 · 目录收录(CC1–CC5)

渠道数据缺失时一律 `skip`,**永不判失败**。

| ID | 要求 | 裁定 |
|---|---|---|
| `CC1` | 认证注册表 spec v1:若已收录,条目需带 `grade`、`snapshot` 和五维(`manifest`、`buildHygiene`、`supplyChain`、`releaseIntegrity`、`installSmoke`)的 `evidence`,无 `veto`,且 `installSmoke ≠ install-fail` | skip · warn · pass |
| `CC2` | `adp-list` 条目:`url`/`name`/`category` 齐备;`category` 在 **23 值枚举**内;`url` 与仓库一致;`description.en` 单行;`tarball` 若存在须是 GitHub Release 上的 `https` `.tgz` | skip · fail · pass |
| `CC3` | `dsh-catalog` 条目:`npm`/`repo`/`displayName`/`categories`/`summary` 齐备;`repo` 一致;`categories` 非空数组;`summary` ≤1000 字符、不含安装命令文本、且未被截断(须以标点结尾) | skip · fail · pass |
| `CC4` | `dshWorkshop` 清单:若声明,`lifecycle.activation` 必须是**五个枚举值**之一 | skip · fail · pass |
| `CC5` | **家族三重门——规范明说"这不是生态标准"**:Apache-2.0 许可、五语 README、三种 seam 角色,由 `dsh-plugin-kit` 校验 CLI 检查。非 PerryLink 工作区报 `skip`(`not-applicable`) | skip · fail · pass |

> **CC4 的五个值**(doctor SPEC 断言,但**只有 doctor 的 `SURVEY.md` §1.5 列出了它们**):
> `immediate`、`hot-reload`、`restart-plugin`、`restart-profile`、`restart-host`;
> `restartRequired` 判据为 `/^restart-/`。

### 2.6 退出码契约

| 码 | 含义 |
|---|---|
| `0` | 无 fail/error,且每个被请求的组**真的跑过** |
| `1` | 存在 fail/error(插件缺陷) |
| `2` | 用法错误 / 未知组 / 未知选项 |
| `3` | 基础设施错误 |
| `4` | 宿主版本不支持,**不是插件裁定** |
| `5` | 结果不稳定,步骤超时或被杀 |
| `6` | **降级(degraded)**——被请求的组从未真正运行 |

`--allow-degraded` 把 6 降为 0,但 JSON 里 `degraded` 字段仍非空。
**两道防假绿**:`--only` 组名匹配不到 → 立即 `2`;被请求的组全部 `skip` → `6`。

**组别名**:`R` = R0–R8,`K` = K1–K9 *(README)* / **K1–K14** *(SPEC,以此为准)*,
`D` = D0–D3/D9,`CC` = CC1–CC5。**建议用 ASCII 别名**——
一个被双重错误编码的 CJK 组名匹配不到任何东西(这正是 35 仓假绿的成因)。

---

## 3. 六项检查族的交叉对照

| 工具 | 检查项 | 层级 | 与本库的关系 |
|---|---|---|---|
| `dsh-plugin-dev check`(**本库**) | **16** 项 | 静态 | 每项经 `src/cli/skill-sections.ts` 链回本知识库章节 |
| `dsh-plugin-doctor` | **33** 项(规范门禁 21) | 静态 + 动态 + 生态 | 超集;R8 与我们的 `manifest-peers` 重叠但**更严**(查开顶区间与陈旧线) |
| `omdsh-dev/dsh-plugin-check` | **33** 项(README 只列 26) | 静态 | 独立实现,基线停在 0.1.5-rc.1 |
| `dsh-session-health` | 11 项检测 ID | 静态 | 会话文件层面 |
| `dsh-security-audit` | 6 个 action | 静态 | 四态诚实模型(skipped/error 永不记为 pass) |
| `dsh-cert-mcp` / certification | 5 维(A–E) | 生态 | 认证等级 A–D + 安全否决 |

### 3.1 本库 `dsh-plugin-dev check` 的 16 项 ID

`patch-valid` · `patch-ids-unique` · `manifest-bundle-patch` · `manifest-main` ·
`manifest-peers` · `manifest-engines` · `manifest-files` · `manifest-package-manager` ·
`display-meta` · `readme-five-langs` · `readme-consistency` ·
`redline-persona-role` · `redline-waterfall-next` · `redline-no-hardcoded-tunables` ·
`redline-effect-registration` · `redline-async-apply-registration`

### 3.2 与 doctor 的对应关系(谁更严就用谁)

| 本库检查 | doctor 对应 | 说明 |
|---|---|---|
| `patch-valid` | `R3` | doctor 额外要求 `name:` 命名空间在包名之下 |
| `patch-ids-unique` | `K13` | doctor 从"重复"扩展到"是否占用宿主 id" |
| `manifest-bundle-patch` | `R1` | **R1 是唯一 critical**,我们的是普通 fail |
| `manifest-main` | `R4` | doctor 还查导出是否含 `apply` + `name` |
| `manifest-peers` | `R5` + `R8` | **doctor 更严**:查裸 `cordis`、查 `dependencies` 误置、查陈旧线与开顶区间 |
| `manifest-engines` | `R6` | — |
| `manifest-files` | `R7` | — |
| `manifest-package-manager` | — | 本库独有 |
| `display-meta` | — | 本库独有(doctor 无展示元数据检查) |
| `readme-five-langs` | `CC5`(仅家族内) | doctor 只在 PerryLink 工作区查 |
| `readme-consistency` | — | 本库独有 |
| `redline-persona-role` | — | 本库独有 |
| `redline-waterfall-next` | `K10` | 重叠 |
| `redline-effect-registration` | `K3` + `K5` | doctor 拆成两项 |
| `redline-async-apply-registration` | `K5` 部分 | ⚠️ **我们是家族内该缺陷的唯一检测器**——`dsh-plugin-upgrade` 的 `legC` W0 卡明确点名"guide async-apply lint"作为 E2 的预防门。doctor 只查返回形态 |
| `redline-no-hardcoded-tunables` | `K8` 部分 | doctor 查 `Config` 是否 Standard Schema |

> **结论**:**我们的 16 项是 doctor 33 项的子集,但有 5 项 doctor 没有**
> (`manifest-package-manager`、`display-meta`、`readme-consistency`、
> `redline-persona-role`、`redline-async-apply-registration`)。
> 两者**不是替代关系**:本库的检查**每一项都链回可读章节**,doctor 的检查**有裁定语义与退出码契约**。
> 严肃发布请**两个都跑**。

---

## 4. 规范事实:只认一个所有者

| 事实 | 所有者 | 值 |
|---|---|---|
| canonical peer 区间 | `dsh-plugin-kit` → `data/peer-range.json` | 六子句(见 [version-matrix.md](version-matrix.md) §4.1) |
| 家族花名册 | `dsh-plugin-kit` → `data/repos.json` | **95 条**(94 插件 + 1 基建);活跃 87 / 冻结 5 / 退役 3 |
| 家族检查 ID 空间 | `dsh-plugin-doctor` → `SPEC.md` | 33 项,已冻结 |
| 认证等级定义 | `dsh-plugin-certification` → README(即规范) | 五维 A–E,等级 A–D |
| 认证注册表内容 | 同上 → `data/certified.json` | 截至 2026-10-13:**仅 1 条**(`dsh-auto-review`,A 级,快照 2026-09-07) |
| 已核验仓名单 | `dsh-plugin-doctor` → `data/verified-repos.json` | **40 条** |
| 升级走廊与 seam | `dsh-plugin-upgrade` | `legAB` 20 seams;`legC` E1–E5 |
| DSH 版本谱系与 peer 区间选择 | **本知识库** | [`version-matrix.md`](version-matrix.md) |

> ⚠️ **花名册计数存在五个互相冲突的说法**,引用前先看日期:
> `data/repos.json`(2026-10-12)95 条 · certification README 94/86 · doctor README 42/34 ·
> doctor 自己的表 45 行 · `dsh-catalog` README 自相矛盾(开头 96 / 计数节 42)·
> `dsh-kit` README "87 活跃 + 5 冻结 = 92"。
> **以 `data/repos.json` 为准**;本库 `guide/when-to-retire-a-plugin.md` 的
> "44 仓 / 7 冻结"是 **2026-10-05 那次审计的快照**,测的是不同时点,**不要直接换算**。

---

## 5. 本知识库在家族里的位置

```
                        认知层(读)                    机械层(跑)
                  ┌──────────────────────┐      ┌──────────────────────────┐
   官方契约 ──────┤  dsh-plugin-guide    │      │  dsh-plugin-doctor       │ 33 项,有裁定语义
   + 版本谱系     │  (本库)             │      │  dsh-test-drive          │ 隔离冒烟
   + 迁移叙事     │  guide/ + references/ │      │  dsh-score               │ 五维评分
   + 踩坑档案     │  + dsh-plugin-dev    │      │  dsh-cert-mcp            │ 认证查询
                  │    check(16 项)     │      │  dsh-plugin-upgrade      │ 走廊扫描
                  └──────────────────────┘      └──────────────────────────┘
                            ↑                                  ↑
                       回答"为什么/该改哪"                回答"过没过/几分"
```

**分工一句话**:本库负责**认知**(为什么、该改哪一行、这一版有什么),
工具链负责**判定**(过没过、几分、能不能装)。**两者都链到本库章节**——
`dsh-plugin-dev check` 经 `src/cli/skill-sections.ts`,
`dsh-plugin-upgrade` 的 `legC` 卡直接点名本库的 async-apply lint。

---

## 6. 未核事项

- `dsh-plugin-doctor` 的 `--dsh` 默认值:README 兼容性单元格写 peer 下界 `0.1.2-rc.1`,
  用法注释却写 npm latest——**两处冲突**。
- SPEC checkset 字符串里的 `/3` 含义未确认。
- CC2 的 23 值 `category` 枚举**家族内无人发布完整列表**(doctor 的 `adp-list` 可导出)。
- `data/verified.json`(区别于 `verified-repos.json`)当前内容未读。
- 本表**未执行任何家族工具**;所有行为描述均为**文档化行为**,非实测行为。


<!-- source: guide/when-to-retire-a-plugin.md -->

# [When to retire or freeze a plugin] guide/when-to-retire-a-plugin.md

# How I decide whether a DSH plugin should keep being maintained

> Chinese: [when-to-retire-a-plugin.zh-CN.md](when-to-retire-a-plugin.zh-CN.md)

## 0. What I did, and why it is public

On 2026-10-05 I pulled the whole DSH plugin ecosystem from npm — **324 real plugins** — and benchmarked every plugin I publish against its rivals on the same capability surface, by weekly downloads. The outcome: **3 retired, 7 frozen, 1 reclassified**. I am publishing the criteria because "should this keep going" is a question every plugin author meets, and most meet it without data.

## 1. The criteria

One thing first: **this is not a purge, it is a self-audit.** The official project is not eliminating third-party plugins — `CONTRIBUTING.md:19` says plainly that packages in the official repository are not inherently more important than community packages, and that the official showcase is "an idea, an official showcase, and a source of inspiration, but **not a mandate from us**". The pressure comes from other third-party authors. Section 3 shows the data.

**Two hard criteria.**

**Hard criterion A: the same capability has been taken into the host's core.** Wording matters. Not "the official repo has a package with a similar name", but "the official composition **mounts it by default**", or "the official project has made it a **first-class subsystem**". The gap between those two readings is the single easiest mistake to make in this kind of review.

**Hard criterion B: on the same capability surface, a third party has a significantly better-adopted implementation.** "Significantly" means one order of magnitude. Any such comparison is only valid as a **relative** magnitude inside one ecosystem, never as an absolute number.

**One soft criterion.**

**Soft criterion C: maintenance cost versus real usage.** This one is easy to misuse. My own Web profile now runs the stock bundle set and installs none of the family's plugins. So "do I still use it" cannot be the criterion — for me these repositories are **published artifacts**, not **personal tools**. The two questions that can be criteria are: **does anyone out there still need this**, and **is this maintenance cost still worth paying**.

**One meta-rule.**

**Only file-level evidence counts.** Every conclusion has to point at a specific file, a specific line, or a specific npm / GitHub API reading. Anything that cannot is marked "unverified" and stays out of the decision. This rule corrected four of my own starting premises this round, two of them directly about whether the official project has a capability at all — one where it does and simply does not mount it, and one where it does mount it and I had assumed otherwise.

**Three dispositions.**

| Disposition | Count | Packages | Meaning |
|---|---|---|---|
| 🪦 **Retired** | 3 | `dsh-background-agents`, `dsh-session-pin`, `dsh-team-rooms` | **Compatibility updates stop**: RETIRED banner at the top of the README, npm `deprecate` applied, GitHub repository archived (read-only) |
| 🧊 **Frozen** | 6 | `dsh-budget`, `dsh-draw`, `dsh-claude-move`, `dsh-reach`, `dsh-wechat`, `dsh-defend` | **No new features — but still working and not deprecated**; only genuine breakage gets fixed |
| ✅ **Reclassified** | 1 | `dsh-catalog` | First judged as a retirement; after review, kept: it is an internal family tool and does not compete with third-party catalogs. **Still maintained** |

Two earlier corridor packages, `dsh-plugin-upgrade-015` (stopped at npm `0.1.1`) and `dsh-plugin-upgrade-016` (never published), are also retired; plugin upgrades continue through the single `dsh-plugin-upgrade` line.

## 2. The data, part one: the 3 retirements

This tier used hard criterion A almost exclusively.

| Package | Official counterpart | Why it was judged retired |
|---|---|---|
| `dsh-background-agents` | `tool-subagent` (`backgroundMode: continuable`) + `tool-subagent-control` + `tool-jobs` | The official composition **mounts it by default**. And the package's **own README already said so**: the background-agent half is *superseded by DSH's native continuable subagents* |
| `dsh-session-pin` | the `client-ui-workspace` pin action | Mounted **by default**: the session context menu has `Pin session`, rows have a hover pin button, pins can be reordered by dragging inside the pinned block, and the host persists `pinnedSessionIds` and broadcasts `pinned` delta frames. What is left for the plugin is appearance work — row colours, a pinned panel, a header toggle |
| `dsh-team-rooms` | `dsh-experimental-agent-team` | The official project turned "teams" into a **first-class subsystem**: an implicit root roster, a persistent mailbox (offline members receive mail on return, with de-duplication), a shared task DAG (compare-and-swap against overwrites, dependency unlocking, file-write conflict warnings), `wait_agent`. It **also** satisfies hard criterion B: its own weekly downloads are **589 — the lowest in the family**, while `@nanmicoder/dsh-agent-teams` sits at **13,273 (22×)** |

One honest qualification on `dsh-team-rooms`: the official `dsh-experimental-agent-team` is **optional**, `disabled` by default in base, and enabling it **requires disabling** `send_message` / `interrupt_agent` / `list_agents`. So this is not a "mounted by default" supersession. It entering the core, plus the third-party 22× adoption, is what makes the case.

One unflattering number I am leaving in: after the host shipped a built-in pin, `dsh-session-pin` **still had 1,015 weekly downloads**. The report offers two readings — appearance enhancements are genuinely wanted, or the download metric carries noise. I cannot separate the two with the data I have, so I retired it on the criteria and left the number standing here.

## 3. The data, part two: the 7 freezes — the multiplier table

Measured 2026-10-05 from the npm registry API. **On method first**: npm weekly downloads are **not** user counts (they include CI, mirrors and repeated installs), so the table below is used purely for the **relative** magnitude between packages, never as an absolute figure.

| Capability surface | My package (weekly downloads) | Third-party rival on the same surface (weekly downloads) | Multiplier | Where the rival is stronger |
|---|---|---|---|---|
| Cost / usage metering | `dsh-budget` 1,079 | **`dsh-cost-meter` 33,526** | **31×** | 170+ model price entries and quota lookups for 11 coding plans |
| Cross-session memory | `dsh-memento` 1,202 | **`@openviking/dsh-memory-plugin` 10,160** | **8×** | An official organisation repository from ByteDance volcengine — company-level resourcing |
| IM bridging | `dsh-reach` ~0.6k / `dsh-wechat` 752 | **`@xmanrui/dsh-im` 17,384** | **23×** | One package covers 10 channels (WeChat / Feishu / WeCom / DingTalk / QQ / Telegram / WhatsApp / Slack / Discord / Matrix) |
| Injection and destructive-command defence | `dsh-defend` 937 | **`cc-safety-net` 13,087** | **14×** | Blocks destructive commands and secret-file access directly as a coding-agent CLI hook — a short adoption path and a wide audience |
| Image generation | `dsh-draw` 973 | **`dsh-image-gen` 7,216** | **7×** | Multi-provider |
| Session / configuration migration | `dsh-claude-move` 843 | **`dsh-chat-import` 5,111** | **6×** | Covers 25+ AI coding agents |

**Four background readings.**

- **The size of the field.** Two keywords intersected: 8,620 hits, 772 candidates, and after checking the `dsh` field package by package, **324 genuine plugins**. (A note on scope: this counts **only packages on npm that declare a `dsh` manifest and can actually be installed with `dsh plugin add`**. Community directory sites publish much larger entry counts, because they also count GitHub repositories, sub-directories and unpublished candidates. The two figures should not be quoted against each other, and must not be added together.)
- **This is not an isolated case.** **Of 16 capability surfaces, my packages are not first on 14**, most of them behind by **3–31×**. The widest gap is on context insight: `dsh-fast` 917 against `dsh-context` 38,999 (**43×**).
- **First in the family is not first on the surface.** `dsh-mcp-panel` at 2,472 is the family's top package, but `dsh-mcp-connector` on the same surface is 7,556 (3×).
- **Nothing here was abandoned.** Before this review, every affected repository had been pushed on 2026-10-05 itself. The retirements and freezes are a **deliberate judgement**, not a cleanup of dead projects.

**Why these seven?** They satisfy two things at once: the rival is not merely better-adopted but **broader in capability** (multi-provider, multi-channel, multi-agent, or organisation-backed); and further investment from me on that surface is unlikely to produce something only I can provide.

Two counter-examples have to travel with the criteria, or they collapse into "behind means retire":

- **`dsh-fast` (917 against `dsh-context` 38,999, 43×) stays as it is.** It is a lightweight package with a low maintenance cost; freezing it would save very little. **Being behind is not a criterion.**
- **Hard criterion B alone can be enough, with hard criterion A failing.** For `dsh-draw` the official counterpart is **zero** (`text-to-image` / `image_gen` / `txt2img` have 0 hits across the host repository), and the same is true for `dsh-memento` (there is no memory seam in the official `capability-seams.md`, and `docs/user/guide/mcp-memory.md:5,31` states outright that **no memory server is present in the shipped composition**). **"The official project does not do this" does not mean "I should keep investing"** — the memory surface has at least 10 rivals, and `dsh-memento` sits around 8th.

## 4. Where I am still first

Listing only the losing half would be misleading. This is the contemporaneous "still only me" list (2026-10-05):

| Capability surface | My packages (weekly downloads) | Status |
|---|---|---|
| LSP action surface | `dsh-lsp-actions` **1,170** | **No strong rival anywhere in the ecosystem.** The official LSP **deliberately excludes** rename / formatting / diagnostics / symbol lists |
| Plugin certification, health checks, scoring | `dsh-plugin-doctor` 218, `dsh-score`, `dsh-test-drive`, `dsh-plugin-certification` | **No rival.** The ecosystem only has `dsh-why` (137) and `dsh-fix` (1,503) — tools that repair a broken plugin, not a scoring system |
| Research / report engines | `dsh-research-report` 1,019, `dsh-industry-research` 926, `dsh-fund-research` 802 | **Essentially unrivaled** (vertical domains) |
| Data quality | `dsh-data-quality` 769 | Only `dsh-data-cleaning-agent` comes close |
| Declarative permissions + network policy | `dsh-permission-rules` **1,459** | `dsh-perm-gate` is **slightly higher** at 1,851 (the report marks this row "rival marginally ahead"), but its **process-level HTTP / CONNECT proxy network policy** has not been replicated by anyone |
| GitHub CI automation | `dsh-github` 359 | No rival (also the lowest, but genuinely nobody else is doing it) |

Two cross-checks:

- **The star leader is not the flagship security plugin — it is the research line.** `dsh-research-report` (215★) and `dsh-industry-research` (212★) are both above `dsh-memento` (139★), `dsh-permission-rules` (118★) and `dsh-mcp-panel` (75★).
- **"The official project lacks it" and "a third party has not built it" are two independent facts.** The report confirms at least eight surfaces where the official project is still at zero (`dsh-defend`, `dsh-mask`, `dsh-budget`, `dsh-draw`, `dsh-translate`, `dsh-library`, `dsh-autotier`, the `dsh-plugin-*` toolchain). Some of those have been taken by third parties; some are still empty. **An empty space is not a moat. Nobody doing it, and me being able to do it well, is a moat.**

## 5. "Frozen" does not mean "deprecated"

This section is for users.

| Status | Will the package be deleted or unpublished? | Will bugs still be fixed? | npm state | Repository state |
|---|---|---|---|---|
| 🪦 **Retired** (3) | No | **No** — including compatibility updates | `deprecate` applied | Archived, read-only |
| 🧊 **Frozen** (6) | No | **Yes** — but only a minimal compatibility fix, and only when a breaking host change makes the package completely unusable | Normal, **not** deprecated | Normal, **not** archived |
| Everything else | No | Yes | Normal | Normal |

**If you have one installed, there is nothing you need to do.** The frozen packages install and run exactly as before: no interface changes, no versions pulled. The only thing that would require action is a host upgrade that actually breaks one — in that case, please open an issue and I will fix it. **If what you want is new features, please use the rivals named in the section 3 table.** Every one of them is significantly better adopted on its surface, and that is precisely why the package was frozen.

**Why not express this by deleting packages?** Because deleting them means the next person cannot find the package, and cannot find the reason it was retired either. So the family tables **keep every row and only add a status note**: retired and frozen repositories stay listed in every family table with a `🚫 RETIRED` / `🧊 FROZEN` status column, and their original descriptions are left untouched.

## 6. Eight self-check questions for other plugin authors

1. **Does the official project "have a package", or "mount it by default"?** The official `experimental/*`, `computer-use`, `browser-use`, `lsp` and `speech-to-text` packages are **not in the stock composition**. "The official project has a package" is not "the user gets the capability on install".
2. **Does a line in `base` mean the user has the capability?** Not necessarily. The Web patch **disables the base process-level model-facing tool rows one by one** and hands them to each agent preset to remount. Reading only `base/cordis.patch.yml` produces a systematically over-optimistic answer.
3. **Did you search for rivals by capability keyword, or by package name?** Comparing names alone misses every rival: `dsh-cost-meter` and `dsh-budget` share no name at all, and neither do `cc-safety-net` and `dsh-defend`.
4. **Does the same name mean the same thing?** The official `dsh-experimental-auto-review` and `dsh-auto-review` share a name but invert the security model: the official one reviews with the current agent's own provider/model and says of itself that it *can allow unsafe actions, deny useful work, and spend additional tokens*, with no deterministic exemptions and no persistent grants; mine is an independent read-only second-model reviewer that **fails closed**. That is not a supersession.
5. **Can a second signal corroborate your download count?** `dsh-budget` has 1,079 weekly downloads and **10 stars** — high downloads look like CI and mirror noise, while low stars look like real appeal. In the other direction, `dsh-auto-review` carries **10 open issues**, which is what real use and real bug reports look like. Download counts alone mislead.
6. **Is "do I still use it" your criterion?** If you no longer use it, it should not be (my own Web profile runs the stock bundle set today). Replace it with two questions: **does anyone out there still need this, and is the maintenance cost still worth it?**
7. **How far behind do you have to be?** There is no threshold. `dsh-fast` is 43× behind and stays as it is; `dsh-defend` is 14× behind and is frozen. The price of freezing is admitting you are no longer the best choice on that surface, so it is only worth paying for packages where further investment will not produce anything distinctive.
8. **Is your decision written into the README?** A decision that lives only in your head, or only in an internal report, does not exist. Each frozen package carries a `## Maintenance status: 🧊 FROZEN` section with the measured comparison table and a note on where the rival is stronger (six, each carrying a `status` of `frozen` in `dsh-plugin-kit/data/repos.json`); each of the three retired packages carries a RETIRED banner at the top of its README. **At a minimum, users should be able to learn from the README why you stopped.**

## 7. Statement of fact

- **The family today**: the family tables list **44 plugin repositories** (45 rows in the tables that include `dsh-plugin-upgrade-015`), of which 🚫 **3 are retired** and 🧊 **6 are frozen**; the rest are maintained as active repositories. Licensing is predominantly Apache-2.0.
- **Where the repositories are**: https://github.com/PerryLink
- **Measurement date**: 2026-10-05, from that self-audit — a full npm registry pull, live GitHub REST API measurements, and file-by-file evidence from the host source tree. **The conclusions are checkable**: each one points at a specific file or a specific API reading, and anything that could not was marked "unverified".

One last line, for anyone who might want to take something over: **`dsh-reach` and `dsh-wechat`, the two IM bridging lines, are open for a new maintainer.** The reason they are frozen is specific — `@xmanrui/dsh-im` covers 10 channels in a single package, and that is a better job than mine. If those two lines are useful to you and you want them to keep growing, both repositories carry permissive licences (`dsh-reach` is Apache-2.0, `dsh-wechat` is MIT); please open an issue and we can talk about the handover.


<!-- source: guide/choosing-a-plugin.md -->

# [Choosing a plugin: who leads each capability surface] guide/choosing-a-plugin.md

# Choosing a DSH plugin: who leads each capability surface, measured

> Chinese: [choosing-a-plugin.zh-CN.md](choosing-a-plugin.zh-CN.md)

## 1. How to read this guide

This guide answers one question: **for a specific job, which plugin should you install right now?** It is not organised by catalog category. The catalog's 11 categories are machine-assigned labels, not user problems, and the candidates for a single job are often scattered across several of them. Below, the guide is organised by *what you want to do*. Each surface gives the leader, the measured numbers, one line on what it is good at, and an install command you can run as-is.

Only two fields drive the judgement, both from the 1024Store public API:

- **Installs** (`installs`, cumulative install count recorded by 1024Store)
- **npm weekly downloads** (`npm7d`)

**Stars are not used.** The reason is in section 2, and it is the single most important convention in this article.

Limitations, stated up front:

1. **⚠️ The most important one: the "install count" is a 1024Store-channel figure, not total adoption.** 1024Store's install count **only records installs performed through its own wrapper `dsh1024`**; installs done with the official `dsh plugin` command **are not counted**. The store's own README states it plainly: *"using the official `dsh plugin` command still works, but it will not be counted in DSH 1024Store install statistics."* Supporting evidence: `dsh1024` itself has only **355** weekly downloads. So **"3 installs" does not mean "only 3 people use it."** Throughout this article, install-count rankings should be read as **install activity through the 1024Store channel**, **not total adoption** — a limitation that applies to every install-based comparison and ranking below.
2. **Stars are not a quality signal, and in this dataset they are barely a signal at all.** The catalog lists monorepo sub-packages as separate entries, and every entry inherits its parent repository's star count. In the official star-descending Top 60, a single repository — `zhu1090093659/dsh-web` — occupies **27 slots** (all its entries show 8,400 stars), and those 60 rows come from only **27 distinct parent repositories**. A plainer counter-example: `reactive-resume`, a resume builder, has 43,851 stars, yet its catalog entry shows **66** installs and **223** weekly downloads; `omdsh-dev/DSH-better-sidebar` has only 4,006 stars but **127** installs and **57,806** weekly downloads. **Stars and real usage can point in opposite directions here.**
3. **Install counts include failed retries.** The data carries a `fail` field: the DSH entry for `volcengine/OpenViking` shows **401** installs against **448** failures; `omdsh-dev/DSH-better-sidebar` shows **127** installs against **333** failures. Install count is therefore not successful-install count, and certainly not user count.
4. **npm weekly downloads include CI and mirror traffic, and mislead on their own — but do not let that turn a real plugin into a fake one.** The extreme case: `MichengAI/dsh-codex-ui` shows **86,997** weekly downloads — the highest in the sample — with only **3** installs. Independent verification found it to be **a real, active community web-UI plugin compatible with the current host** (`@michengai/dsh-codex-ui` v1.1.28 / Apache-2.0 / 92 versions / repository created 2026-08-14 / a Host compatibility section covering `0.2.0-rc.1` and `rc.2` / 0 open issues). The 86,997 is a genuine download figure: it is not a name collision (there is no bare `dsh-codex-ui` on npm) and not its own CI (four workflows, no cron). **The problem is not that it is fake, but that a download figure cannot be read directly as adoption** — its daily curve is step-shaped (a steady 24,648–24,968 for five consecutive days, 9/21–9/25), which is why the shape of the curve matters as much as the number. The reverse also occurs: entries with real installs and no npm figure at all.
5. **Category labels are machine-assigned and can be wrong.** In this sample, a balance widget is filed under "fun" and a context-insight panel under "ui". Hence this guide is organised by need, not by category.
6. **The sample is not the whole catalog.** The analysis covers **2,200 entries** — the top 200 of each of 11 categories, de-duplicated by entry id — not all 13,760 catalog rows. Entries with roughly 2 installs still appear at the tail, so full long-tail coverage cannot be guaranteed.

7. **Check whether the plugin you pick has moved recently.** Across the top 200 entries by installs, last push was within **7 days** for **152** entries, **8–30 days** for **25**, **31–60 days** for **23**, and **over 60 days** for **0**. The set as a whole is active, but two of the leaders named below have gone noticeably quiet and are flagged at the relevant surfaces in section 3.

**Verification note**: every leader named in this article was independently verified — its README and package.json were read, its peer range against the current host `@deepseek-ai/dsh@0.2.0-rc.2` was tested with semver, and its open issues were checked. **The compatibility and licensing warnings throughout section 3 are the outcome of that verification.** Verification date: 2026-10-06.

## 2. The ten plugins most people want

One thing first: **the highest-install entry overall (818 installs) is `imsai-sh/awesome-deepseek-harness-plugins`, but it is not a plugin at all — it is 1024Store's catalog/data repository and cannot be installed** (its README states it is generated from the API by `scripts/build-readme.mjs` and lists 13,759 entries; its package.json is `"private": true` with **no bin, no dependencies and no `dsh` field**, and the client lives in a different repository, `dsh1024`). The table below excludes it and lists community plugins only.

| # | Plugin | Installs | Installers | 30d | Fail | npm 7d | Last push (UTC) | What it is good at |
|---:|---|---:|---:|---:|---:|---:|---|---|
| 1 | `volcengine/OpenViking` memory plugin | 401 | 363 | 131 | 448 | 10,160 | 2026-10-06 (0d) | Long-term memory, knowledge retrieval and skills; first on both installs and 30-day growth |
| 2 | `tt-a1i/archify` | 268 | 244 | 115 | 145 | 7,702 | 2026-10-05 (0d) | Architecture, flow, sequence and data-flow diagrams packaged as a skill, exported as self-contained HTML |
| 3 | `liustack/modlens` | 173 | 154 | 37 | 123 | 16,691 | 2026-10-04 (2d) | A vision bridge for text-only models: paste an image, get structured JSON evidence |
| 4 | `vectorize-io/hindsight` | 169 | 148 | 73 | 145 | 19,056 | 2026-10-05 (0d) | Learnable project memory with automatic recall, retention and per-repo isolation |
| 5 | `omdsh-dev/DSH-better-sidebar` | 127 | 99 | 38 | 333 | 57,806 | 2026-10-05 (0d) | A full sidebar workbench: file editing, terminal, Git, subagents, plus third-party tabs |
| 6 | `NanmiCoder/dsh-agent-teams` | 109 | 97 | 38 | 38 | 13,890 | 2026-10-05 (1d) | Multi-agent teams that split long tasks across cooperating agents |
| 7 | `MeteorNOX/DeepSeek-Balance-Whale-Widget` | 108 | 93 | 44 | 60 | 57,320 | 2026-10-05 (1d) | A persistent balance widget in the corner that keeps your account balance in view |
| 8 | `Tencent/BrowserSkill` | 95 | 83 | 51 | 27 | 7,455 | 2026-09-30 (6d) | Lets the agent drive a real, logged-in browser; 30-day growth of 51 is the top band |
| 9 | `bowenliang123/dsh-context` | 50 | 46 | 10 | 19 | 38,839 | 2026-10-05 (1d) | A context-insight panel: what the window holds now, how it evolved, compaction and injection events |
| 10 | `Han-1413141/dsh-cost-meter` | 72 | 65 | 24 | 43 | 31,807 | 2026-10-05 (0d) | Session and daily cost stats, budget gauge, balance, peak/off-peak pricing with one-click sync |

A note on row 9: `bowenliang123/dsh-context` and `Han-1413141/dsh-cost-meter` (row 10) are different things — the former shows the **context window**, the latter shows **money** — but most people want both, so both are listed. Strictly by installs, row 9 would be `Tencent/WeKnora` at 75 installs, but its scope is a document knowledge base with a much narrower audience, so it was left out.

## 3. Surface-by-surface selection

Each surface below gives the leader, the measured numbers, one line on its strength, and a ready-to-run install command. **Where no clear leader exists, this section says so and lists the measured values of the top two or three candidates instead.**

### A. Cross-session memory

**Leader: the DSH memory plugin from `volcengine/OpenViking`** — **401** installs / 363 installers / **131** in 30 days / 448 failures / **10,160** weekly downloads. It is first on installs, installers and 30-day growth, and the only memory entry above 400 installs.

**Three things to know before using it:** ① **License = AGPL-3.0** (most entries here are MIT/Apache, so the licensing difference deserves separate attention); ② its DSH entry is **a client bundle that talks to a server** (auto-recall, session capture, `viking://` URI protection, an MCP tool surface) — **it implements neither the memory store nor retrieval itself**, so the description above can mislead you into thinking it is a complete memory system; ③ "self-evolving" appears only in the `## Research` section of that repository's README, where the subject is **a different research system, VikingMem**, and it is not a product promise — so this section does not use that wording.

The second tier is `vectorize-io/hindsight` (169 installs / 19,056 weekly downloads) and `omdsh-dev/dsh-mnemon` (12 installs but **13,825** weekly downloads).

```bash
dsh plugin --profile web add @openviking/dsh-memory-plugin
```

### B. Cost and usage

This surface is really two different jobs; do not conflate them.

**Leader for money: `Han-1413141/dsh-cost-meter`** — **72** installs / 65 installers / 24 in 30 days / 43 failures / **31,807** weekly downloads. Session and daily cost statistics, a budget gauge, the official balance, a history board, and peak/off-peak pricing with one-click price sync. **Note: "170+ model price entries" and "11 Coding Plan providers" currently rest on the author's own claim only** — the files behind them (`lib/provider-prices.js`, `lib/coding-plans.js`) do exist, but the entries were not counted one by one.

```bash
dsh plugin --profile web add dsh-cost-meter
```

**Leader for the context window: `bowenliang123/dsh-context`** — **50** installs / 46 installers / **38,839** weekly downloads (highest on this surface). It draws the composition and evolution of the context window: share against window size, per-request history, compaction and injection events, and message-level token counts. **Compatibility note (a declaration-layer fact, not proof it fails to run): its `peerDependencies` say `>=0.1.5-rc.1`, and under semver prerelease rules `satisfies("0.2.0-rc.2")` returns `false`.** This is a **declaration-layer** phenomenon — the author's intent is plainly unbounded (the README says 0.1.5-rc.1 and up) and the repository was still receiving commits on the verification date — but it means the automated compatibility check may warn before install, so judge by what actually installs.

```bash
dsh plugin --profile web add dsh-context
```

**Candidates for tokens and balance (no clear leader)**: `Ychris12138/dsh-usage-stats` (20 installs / 2,226 weekly downloads), `zh667/TokenLedger` (13 installs / 258 weekly downloads), `feibi-mochi/deepseek-harness-wallet`. Each does one thing, all under 20 installs, and the gaps are too small to declare a leader.

```bash
dsh plugin --profile web add @ychris12138/dsh-usage-stats
```

### C. IM and remote access

**IM bridge leader: `xmanrui/dsh-im`** — **73** installs / 69 installers / 25 in 30 days / 23 failures / **17,384** weekly downloads. It actually covers **12 IM channels**: Feishu, WeChat, DingTalk, WeCom, **WeCom apps**, QQ, **Slack**, Telegram, **Discord**, WhatsApp, **iMessage** and **Matrix** (plus one AI Office connector). **⚠️ Known failure: issue #299 reports "connection timeouts on desktop 0.2.0-rc.2" — the only failure report in this article that lands directly on the current host**, so desktop users should check that issue's status first.

```bash
dsh plugin --profile web add @xmanrui/dsh-im
```

**Mobile and remote access leader: `shaobeichen/dsh-pocket`** — **30** installs / 26 installers / 11 in 30 days / 3,753 weekly downloads. Run `dsh web` on the desktop and scan a QR code to reach it from a phone over LAN or public network, mirrored live. **⚠️ ① Compatibility unknown: it declares no `dsh-*` peer range at all** (only cordis `^4.0.1`), so there is **no declaration-layer guarantee** for the current host; **② License = GPL-2.0**, unlike the MIT/Apache of most entries; **③ Maintenance: its last push was 2026-09-16, so it has been quiet for 19 days**, making it one of the slower-updating candidates on this surface.

```bash
dsh plugin --profile web add dsh-pocket
```

**Candidates for multi-host remote workspaces (no clear leader)**: `wenbin-wb/dsh-bridge` (2 installs / 5,543 weekly downloads; LAN QR, Cloudflare tunnel or self-hosted WebSocket tunnel), `flymysql/dsh-remote` (1 install / 5,212 weekly downloads; multi-host SSH workspaces), `summer1238/dsh-remote-web-gateway` (1 install). All at 2 installs or fewer, yet none with a low npm figure — no leader can be determined.

### D. Injection and permission safety

**No clear leader on this surface.** Measured values, with gaps inside the noise band:

| Candidate | Installs | Installers | 30d | Fail | npm 7d | Scope |
|---|---:|---:|---:|---:|---:|---|
| `NanmiCoder/dsh-auto-mode` | 13 | 9 | 7 | 0 | 991 | Safe automatic permission handling that keeps controls while allowing automation |
| `PerryLink/dsh-defend` | 3 | 2 | 3 | 0 | 937 | Detects injection, jailbreak and secret leakage at three seams; allow/ask/block tiers |
| `PerryLink/dsh-permission-rules` | 2 | — | — | — | 1,965 | Declarative allow/deny/ask rules plus process-level network policy |
| `moon09300731/dsh-approval-gate` | 5 | 4 | 2 | 1 | 1,083 | Predicts irreversible operations; safe ones auto-approve, dangerous ones go to a human |

`NanmiCoder/dsh-auto-mode` has the most installs and zero failures; `PerryLink/dsh-permission-rules` has the highest weekly downloads (1,965). **This data cannot say which is the better tool.**

**⚠️ Read before using: the repository behind `NanmiCoder/dsh-auto-mode` has been archived (`archived: true`, read-only).** It no longer accepts issues or pull requests, so nothing will be fixed. Its security-policy description was verified as accurate, but **the archived state means this line of work has stopped evolving** — factor that in when treating it as a candidate.

```bash
dsh plugin --profile web add @nanmicoder/dsh-auto-mode
```

### E. MCP management

**No clear leader on this surface**; the two candidates take entirely different routes:

- `duhu2000/dsh-mcp-connector` — **5** installs / 5 installers / **7,150** weekly downloads (highest here). A general-purpose MCP connector, connection manager and extension marketplace: connect MCP servers, discover tools and prompts, with OAuth/PKCE and API-key support and JSON import.
- `PerryLink/dsh-mcp-panel` — **13** installs / 13 installers / 9 in 30 days / 2 failures / 2,472 weekly downloads. A read-only runtime management panel: an `/mcp` command and a settings page showing connection state, registered tools, errors and reconnect counts.

By installs the latter is ahead; by npm the former is nearly 2.9× the latter. **One leans toward connecting and onboarding, the other toward observing and diagnosing — they are not two implementations of the same job.**

```bash
dsh plugin --profile web add dsh-mcp-connector
```

### F. Vision and image understanding

**Leader: `liustack/modlens`** — **173** installs / 154 installers / 37 in 30 days / 123 failures / **16,691** weekly downloads. A vision bridge for text-only models: paste an image and get structured JSON evidence (OCR, layout, semantics). It leads on both installs and npm, and is the only vision entry above 150 installs.

```bash
dsh plugin --profile web add @liustack/modlens
```

Second tier: `ysr666/dsh-vision-router` (48 installs / 10,168 weekly downloads; a built-in keyless vision chain plus pixel-level tools) and `Anionex/dsh-vision-toolkit` (35 installs / 3,652 weekly downloads; image Q&A, multi-image comparison, long-screenshot OCR, screenshot-to-UI). Both overlap with modlens but each has its own emphasis.

### G. Sidebar and UI foundation

**Leader: `omdsh-dev/DSH-better-sidebar`** — **127** installs / 99 installers / 38 in 30 days / **57,806** weekly downloads (second highest in the whole sample). A full sidebar workbench: built-in file rendering and editing, Git and subagent views, and third-party plugins can register new tabs. **Note: the terminal is not something it provides** — its README states that the terminal and its `node-pty` were **handed back to DSH's built-in `ui-sidebar-terminal`**, and its own npm description deliberately omits the terminal.

```bash
dsh plugin --profile web add dsh-better-sidebar
```

Other parts of the same surface (they do not conflict and can be installed separately):

| Part | Installs | npm 7d | Note |
|---|---:|---:|---|
| `omdsh-dev/dsh-genui` | 26 | 9,982 | Renders interactive UI inside assistant replies: layout, charts, forms, quizzes, mermaid, 3D scenes |
| `omdsh-dev/dsh-at-file` | 26 | — | Codex-style `@file` references: search and reference workspace files in the composer |
| `zhu1090093659/dsh-web` `dsh-doctor` | 36 | — | Task board, git graph, right panel, remote mobile UI, pet, token stats and skin center |
| `zhu1090093659/dsh-web` `dsh-plugin-manager` | 59 | 27,649 | Client-side plugin management: install, enable, remove |

```bash
dsh plugin --profile web add @changfenhuang/dsh-genui
```

### H. TUI

**Leader: `ccch1mneyyy/dsh-TUI`** — **42** installs / 34 installers / 10 in 30 days / 92 failures / **16,008** weekly downloads (highest here). A Claude Code-style full-screen terminal UI: pixel-whale header, live status line, streaming thought expansion.

```bash
dsh plugin --profile web add @deepseek-harness-tui/dsh-tui
```

The second candidate on this surface is `huiliyi37/dsh-tianshu-tui` at 3 installs / 544 weekly downloads — an order of magnitude smaller.

### I. Plugin marketplace and distribution

**No clear leader on this surface**; the two routes differ enormously in scale and in kind:

| Candidate | Installs | Installers | npm 7d | Scope |
|---|---:|---:|---:|---|
| `imsai-sh/awesome-deepseek-harness-plugins` | 818 | 763 | — | **1024Store catalog/data repository** (not a plugin; cannot be installed) |
| `dsh-market/dsh-market` | 53 | 43 | — | Browse the whole community catalog inside DSH, filter by category, one-click install |
| `dshplugin/dsh-plugin-hub` | 5 | 5 | **6,198** | Community in-app marketplace; the entry claims 4,000+ curated plugins, updated daily |
| `zhu1090093659/dsh-web` `dsh-market` | 33 | 25 | — | A workshop UI for discovering and installing community plugins |
| `kingOfSoySauce/dsh-skin-market` | 26 | 24 | 2,761 | A skin marketplace (themes only, not general plugins) |

The highest install count belongs to `imsai-sh/awesome-deepseek-harness-plugins`, which is a catalog/data repository and **not an installable plugin**; among third parties, `dsh-market` leads on installs (53) while `dsh-plugin-hub` leads on npm (6,198, with just 5 installs). **The two figures point to different conclusions, so this surface gets no verdict.**

```bash
dsh plugin --profile web add github:dsh-market/dsh-market
```

### J. Desktop shell

**No clear leader on this surface**; the three candidates have different trajectories:

- `anywhere-labs/dsh-desktop` `dsh-plugin-desktop` — **79** installs / 63 installers / **7** in 30 days / **363** failures / 129 weekly downloads. A modern desktop shell that treats the desktop itself as a plugin container. Highest installs, but only 7 in the last 30 days and 363 failures.
- The `zhu1090093659/dsh-web` aggregate package — **55** installs / **24** in 30 days / only **3** failures. Its 30-day growth is the highest of the three.
- `dsh-tauri-desk/deepseek-harness-desktop` `dsh-tauri` — 1 install; the Tauri desktop shell.

```bash
dsh plugin --profile web add dsh-plugin-desktop
```

### K. Browser control

**Leader: `Tencent/BrowserSkill`** — **95** installs / 83 installers / **51** in 30 days / **27** failures / 7,455 weekly downloads. A CLI plus extension that lets the agent drive a real, logged-in browser and automate tasks without disturbing you. Its 30-day growth of 51 is in the top band, and its failure count is the lowest in that band. **⚠️ Known security issue: issue #402 reports "an unauthenticated loopback daemon able to drive the browser extension"**, meaning a local process can drive the browser without authorization — assess that exposure before deploying.

```bash
dsh plugin --profile web add @wxg-prc-cpg/browser-skill-dsh-plugin
```

Other routes, each with single-digit installs: `Lum1104/dsh-browser` (5 installs; a Chrome sidebar extension needing no vision), `Fisfzy/ego-browser` (9 installs; 13 structured tools and a bundled runtime), `wqty123/dsh-browser` (5 installs / 5,515 weekly downloads).

### L. Plugin development toolchain

**No clear leader on this surface.** `PerryLink/dsh-mcp-panel` ranks 4th in this sample's `dev` category with **13** installs; `PerryLink/dsh-plugin-guide` has 6 installs / 1,012 weekly downloads. Everything else on the surface sits at 1–2 installs: `MicroMilo/upstream-radar` (2 installs / 484 weekly downloads; dependency security monitoring), `Airmetro/dsh-update-checker` (8 installs / 2,819 weekly downloads; host version detection), plus a group of candidates with real npm volume but zero installs.

**Measured values across this surface are uniformly low — not enough to support a "leader" claim.**

```bash
dsh plugin --profile web add dsh-mcp-panel
```

### M. Agent teams and orchestration

**Leader: `NanmiCoder/dsh-agent-teams`** — **109** installs / 97 installers / 38 in 30 days / **38** failures / **13,890** weekly downloads. It is first on installs, installers, 30-day growth and npm, and leads the runner-up by a wide margin.

```bash
dsh plugin --profile web add @nanmicoder/dsh-agent-teams
```

Second tier: `Q00/ouroboros` (8 installs; staged evaluation with budget control) and `MichengAI/dsh-automation` (7 installs / 4,003 weekly downloads; scheduled and automated tasks).

### N. Web search

**No clear leader on this surface**; two candidates are tied on installs:

| Candidate | Installs | Installers | 30d | Fail | npm 7d | Scope |
|---|---:|---:|---:|---:|---:|---|
| `liustack/modsearch` | 30 | 25 | 12 | 16 | 7,347 | Search the web and X, returning structured JSON evidence (search/fetch/citations) |
| `anysearch-team/anysearch-dsh` | 30 | 30 | 13 | 35 | 1,749 | Web search and advanced search tools |
| `DDDMUC/dsh-free-search` | 18 | 16 | 6 | 4 | 14,568 | Keyless free web search (DuckDuckGo) |

Installs are tied at 30 apiece; `dsh-free-search` has the highest npm figure (14,568) but only 18 installs. **Each leads on one metric, so no leader is declared.**

```bash
dsh plugin --profile web add @liustack/modsearch
```

### O. Office documents and deliverables

**No clear leader on this surface**; the candidates trade wins across dimensions:

| Candidate | Installs | npm 7d | Scope |
|---|---:|---:|---|
| `dream-num/dsh-univer-office` | 19 | **12,227** | Spreadsheets, documents, presentations, multi-dimensional tables, canvas, with live preview and worktree review |
| `Tencent/WeKnora` `dsh-weknora` | **75** | 1,217 | Turns raw documents into a queryable RAG, an autonomous reasoning agent and a self-maintaining wiki |
| `Devin-AXIS/iPolloWork` `ppt-studio` | 24 | 443 | Presentation authoring and editing |

The highest install count is `dsh-weknora` (75) while the highest npm figure is `dsh-univer-office` (12,227) — nearly 10× apart. They do not solve the same problem (knowledge base versus document output), **so they are not comparable on one surface.**

```bash
dsh plugin --profile web add dsh-univer-office
```

### P. Image generation

**No clear leader, and overall volume on this surface is clearly low.** The highest-install image-generation entry in the sample is `shanliuling/dsh-image-gen` (**8** installs / 7 installers / **7,216** weekly downloads; multi-vendor support for Gemini, OpenAI and Seedream), followed by `dickpy/dsh-imagegen` (4 installs / 2,661 weekly downloads). **Installs are in the single digits — real adoption for this surface inside the catalog is still very low.**

```bash
dsh plugin --profile web add dsh-image-gen
```

### Q. Session rewind and archiving

**Rewind leader: `Anionex/dsh-turn-rewind`** — **24** installs / 17 installers / 5 in 30 days / **4** failures / 1,504 weekly downloads. It rolls back session and workspace state on a persistent Change Ledger, with the lowest failure count among its peers. **⚠️ Positioning correction: it is really "message-anchored project-file recovery, with optional conversation rewind", and it does modify project files on disk** — do not assume it only rolls back the conversation and leaves your workspace untouched. Confirm the scope you want restored before using it.

```bash
dsh plugin --profile web add @anionex/dsh-turn-rewind
```

**Archive and session-management leader: `dream12347/dsh-session-manager`** — **20** installs / 17 installers / **9** in 30 days / **3** failures. Delete with trash/restore/purge, statistics, continue/pause, open the log folder, workspace grouping and sorting, and a context-compaction threshold. **⚠️ Note the maintenance state, though: its last push was 2026-08-27, so it has been quiet for 39 days — the quietest of any leader named in this article. With only 20 installs and 9 in 30 days the sample is small to begin with, so check whether the repository is still responsive before adopting it.**

```bash
dsh plugin --profile web add dsh-session-manager
```

Other candidates: `zhu1090093659/dsh-web` `dsh-session-archive` (19 installs / **25,779** weekly downloads; archives and stores whole sessions), `z953218350/dsh-archive-manager` (16 installs / 2,351 weekly downloads), `SiriLee/dsh-rewind` (11 installs / 8,546 weekly downloads). Note that `dsh-session-archive` has the surface's highest npm figure but only the third-highest install count — **where the two metrics disagree, this guide ranks by installs first, npm second, and shows the disagreement.**

### R. Skins and themes

**Leader: the skin center from `zhu1090093659/dsh-web` (`skin-center`)** — **75** installs / 67 installers / 26 in 30 days / 55 failures. It browses and applies themes in one place and has the highest install count on the theme surface.

```bash
dsh plugin --profile web add @linxin666/dsh-client-ui-skin-center
```

Behind it: `Small-tailqwq/dsh-deep-whale` (31 installs), `kingOfSoySauce/dsh-skin-market` (26 installs / 2,761 weekly downloads), `elysia395/dsh-wallpaper-engine` (23 installs / **13,004** weekly downloads, the highest npm figure on the surface).

### S. Fun and companions

**Leader: `MeteorNOX/DeepSeek-Balance-Whale-Widget`** — **108** installs / 93 installers / 44 in 30 days / **57,320** weekly downloads. A balance widget that lives in the corner of the interface, with drag-to-snap and rolling-number animation. It is first on both installs and npm for this surface.

```bash
dsh plugin --profile web add dsh-whale-widget
```

Behind it: `zhu1090093659/dsh-web` `dsh-liangshen` (18 installs), `dsh-pet` (14 installs), `vlln/whale-girl` (13 installs / 1,044 weekly downloads), `PC2005-cloud/dsh-pet` (8 installs).

### T. Finding evidence beyond the page (search and fetch)

See surface N above; not repeated here.

## 4. The surfaces where he is not first (an honest list)

This section is the load-bearing wall of the article's credibility. It is not softened.

**The author's entries number just six in the top 200 by installs:**

| Entry | Installs | Installers | 30d | Fail | npm 7d |
|---|---:|---:|---:|---:|---:|
| `PerryLink/dsh-mcp-panel` | 13 | 13 | 9 | 2 | 2,472 |
| `PerryLink/dsh-plugin-guide` | 6 | 6 | 5 | 1 | 1,012 |
| `PerryLink/dsh-local-ai` | 4 | 4 | 3 | 3 | 900 |
| `PerryLink/dsh-defend` | 3 | 2 | 3 | 0 | 937 |
| `PerryLink/dsh-claude-move` | 3 | 3 | 3 | 0 | 843 |
| `PerryLink/dsh-output-styles` | 3 | 1 | 3 | 0 | 826 |

**For comparison: the top community plugin in this article has 401 installs.** That gap needs no commentary.

Surface by surface — **on all of the following he is not first:**

| Surface | Leader (measured) | The author's entry (measured) |
|---|---|---|
| Cross-session memory | `OpenViking` memory plugin 401 / npm 10,160 | `dsh-memento` did not reach the top 200; the 20th-place threshold on the memory surface is **1** install, and its npm figure is 1,202 |
| Cost and usage | `dsh-cost-meter` 72 / npm 31,807 | no entry on this surface |
| IM and remote | `xmanrui/dsh-im` 73 / npm 17,384 | no entry on this surface |
| Injection and permission safety | see surface D: **no clear leader** | `dsh-defend` 3 installs; `dsh-permission-rules` 2 installs / npm 1,965 |
| Image generation | surface maximum is 8 installs (`dsh-image-gen` / npm 7,216) | `dsh-draw` does not appear in this sample's top 200 |
| MCP management | **no clear leader**; `dsh-mcp-connector` 5 installs / npm 7,150 | `dsh-mcp-panel` 13 installs / npm 2,472 |
| Session migration | `Nwflower/dsh-chat-import` 10 installs / npm 5,111 | `dsh-claude-move` 3 installs / npm 843 |
| Output styles | no strong entry inside this sample | `dsh-output-styles` 3 installs / npm 826 |
| Plugin marketplace | **no clear leader** (best third party: 53 installs) | his marketplace entries are not in this list |
| Context insight | `bowenliang123/dsh-context` 50 installs / npm 38,839 | no entry on this surface |
| Plugin development knowledge | no strong entry inside this sample | `dsh-plugin-guide` 6 installs / npm 1,012 |

**Two asymmetries must be spelled out rather than glossed over.**

1. For `dsh-claude-move` and `dsh-output-styles`, the comparison target — "the strongest third party on the same surface" — **cannot be found in this sample's top 200**. That is not the same as the author being first; it means the surface is thin in the catalog data.
2. `dsh-memento` did not reach the top 200 by installs, but the 20th-place threshold on the memory surface is only 1 install. "Not in the top 200" and "zero installs" are different claims, and the latter cannot be inferred.

**Separately, an earlier independent adoption survey by the author (2026-10-05, npm registry) reached the same conclusion in a harsher form.** Those weekly download figures **are not part of this article's data source, so they appear only as background**:

- Cost: his 1,079 ← third party **33,526**
- Memory: his 1,202 ← third party **10,160**
- IM: his 752 ← third party **17,384**
- Safety: his 937 ← third party **13,087**
- Image generation: his 973 ← third party **7,216**

In that same survey `dsh-mcp-panel` at 2,472 was first within his own family, yet a stronger third party existed on the surface (`dsh-mcp-connector` at 7,556). **In this sample that comparison still holds** — 7,150 against 2,472, about 2.9×.

**In one line:** the author's only foothold in the first tier is `dsh-mcp-panel`, which barely reaches this sample's top 5 in the `dev` category; the other five entries are in single-digit installs. **For the question "who is first", the answer is almost never him.**

## 5. Common traps when choosing a plugin

1. **Do not sort by stars to pick a plugin.** The catalog treats monorepo sub-packages as separate entries and each inherits the parent's star count — one repository holds 27 of the official star Top 60. A reproducible check is in section 6.
2. **What you see are entries, not plugins.** The same repository appears many times (`zhu1090093659/dsh-web` occupies 27 star-ranking slots). **When one repository name repeats through a leaderboard, that is parent-repo spill, not 27 popular plugins.** Every leaderboard referenced here has been de-duplicated by parent repo.
3. **Install counts contain failed retries.** 401 installs against 448 failures, and 127 installs against 333 failures, are both real readings. When an entry looks popular, glance at its `fail`.
4. **Failures can exceed installs by more than tenfold.** `nexu-io/open-design` `dsh-runtime`: **73** installs against **1,103** failures. **A high failure count does not automatically mean poor quality** — it can simply reflect heavy environment dependencies or many build steps — but it is a risk signal you should have before choosing.
5. **npm weekly downloads and install counts can diverge sharply; read both.** The extreme case: `MichengAI/dsh-codex-ui` at **86,997** weekly downloads (highest in the sample) with **3** installs; `omdsh-dev/dsh-mnemon`'s `dsh-mnemon-strategy-scoped` at **7,627** weekly downloads with **0** installs. **Zero installs with real npm volume usually means CI, mirrors, or a sub-package pulled in by its parent — not users — but confirm the package is a real plugin before concluding that: `dsh-codex-ui` is the case where that inference went wrong (real, active and compatible with the current host; see section 1, item 4).**
6. **Category labels are not trustworthy.** A balance widget is filed under "fun", a context panel under "ui", and the "tools" category mixes knowledge bases, vision, presentations and marketplaces. **Search by need, not by category.**
7. **30-day growth reflects the present better than total installs.** `anywhere-labs/dsh-desktop`'s desktop plugin has 79 total installs but only **7** in 30 days and 363 failures, while the `zhu1090093659/dsh-web` aggregate has 55 installs, **24** in 30 days and only 3 failures. **Totals are history; 30-day growth is now.**
8. **Do not treat "highest install count" as a recommendation — or as a plugin.** The highest-install entry, `imsai-sh/awesome-deepseek-harness-plugins`, is 1024Store's **catalog/data repository and cannot be installed**; it is not a community plugin, and it has been excluded from the recommendation table.

## 6. Data sources and how to reproduce

**Source and conventions**

- Endpoint: `deepseek1024.com/api/v2/plugins` (DSH 1024Store)
- Catalog total: **13,760** entries (the `catalogTotal` key in the v2 JSON)
- API generated at `2026-10-06T04:31:08.815Z`; locally normalised at `2026-10-06T06:43:06.608Z`
- Sample analysed here: **2,200 entries** (top 200 of each of 11 categories, de-duplicated by entry id) — **not the full catalog**
- Sort fields: `installs` (raw `installCount`), `npm7d` (raw `npmDownloads7d`)
- Supporting fields: `installers` (`installerCount`), `installs30d`, `fail` (`failureCount`), `pushedAt` (last push to the default branch), `latestReleaseAt` (may be null)

**Reproducing the star-spill finding**

1. Take the official Top 60 sorted by stars descending.
2. Group by `repo` and count how many slots each repository occupies.
3. Result: those 60 rows come from only **27 distinct parent repositories**, and `zhu1090093659/dsh-web` alone occupies **27 slots** with an identical star count (8,400) — direct evidence that sub-packages inherit the parent's stars.
4. Cross-check: `nexu-io/open-design` 99,560★, `tt-a1i/archify` 78,074★, `vectorize-io/hindsight` 45,905★, `reactive-resume` 43,851★ — all **parent-repository** star counts, not the DSH entry's own.

**Reproducing "stars and usage can be inverted"**

Read the `stars`, `installs` and `npm7d` columns for any two entries. The comparison used here is `reactive-resume` (43,851★ / 66 installs / 223 weekly downloads) against `omdsh-dev/DSH-better-sidebar` (4,006★ / 127 installs / 57,806 weekly downloads).

**Data files used**

- `00-live/_tmp-research/selection-dataset-2026-10-06-v2.md` (human-readable: A = top 100 de-duplicated by installs, B = top 100 by weekly downloads, C = top 20 per category by installs)
- `00-live/_tmp-research/selection-dataset-2026-10-06-v2.json` (exact values; keys `byInstalls` / `byNpm7d` / `dedupByRepoTop100` / `starSpillEvidence` / `sampledEntries`)
- `D:\Projects\dsh-plugin-supersession-review-20261005.md` (used only for one background survey in section 4, where the different convention is stated explicitly)

**Known gaps**

- Feature-level comparison: the body of this article compares adoption only; apart from the compatibility, licensing and failure warnings flagged in section 3, the "what it is good at" lines still rest on each entry's own stated scope and were not comprehensively verified. (Those section-3 warnings come from independent verification of the leaders: reading their README and package.json, testing peer ranges with semver, and checking open issues, on 2026-10-06.)
- Long tail: the sample is the top 200 per category, so coverage below roughly 2 installs is not guaranteed to be complete.
- Five third-party rivals from the author's earlier survey (including `cc-safety-net`) **are not in this article's data source**, so they appear as background only and are never used as selection evidence.

***

**Conclusion**: what this guide can tell you is which entries people actually install, how many installs they have, and how many failures. What it cannot tell you is which plugin is better written. **The former is what this data is good at; the latter requires reading the README and trying it yourself.** Every surface where the data cannot support a verdict is labelled "no clear leader" with the candidates' measured values listed — no winner was manufactured.


<!-- source: guide/release-engineering.md -->

# [Release engineering] guide/release-engineering.md

# Release Engineering for a DSH Plugin Portfolio

> Chinese: [release-engineering.zh-CN.md](release-engineering.zh-CN.md)

The official documentation teaches you how to build **one** plugin. It does not cover how to keep **dozens** of them installable, publishable, and upgradeable across a harness that ships compatibility-breaking changes by design. This chapter is that missing part, written from a live portfolio of 38 bundle plugins, 39 npm packages and 500+ published versions.

---

## 0. The contract you are actually maintaining

A plugin is not "a TypeScript package". It is four contracts, and a portfolio breaks whenever one of them drifts:

| Contract | Where it lives | Breaks when |
|---|---|---|
| Bundle manifest | `package.json` -> `dsh.bundle.patch` | the patch file is renamed or the key is dropped |
| Install surface | npm package name + `dist-tags` | a publish lands on the wrong tag, or never happens |
| Compatibility window | `peerDependencies` on `@deepseek-ai/*` | the harness moves an alpha/rc line and the range no longer admits it |
| Discoverability | GitHub topic `dsh-plugin` + package `keywords` | the repo is renamed, or a fork is created without topics |

Everything below is about keeping those four in sync at portfolio scale, cheaply.

---

## 1. Layout and naming that scale

Two habits separate 5 plugins from 50:

1. **One repository per plugin**, each independently releasable. A monorepo makes one failing gate block every plugin's release.
2. **A predictable name**: `dsh-<capability>` for the package, the repository named identically. Scoped names (`@you/dsh-x`) are fine; keep the repository name stable, because the discovery mechanism is a GitHub topic and a rename silently drops you out of search.

Keep a per-repo `AGENTS.md` stating that repository's own rules. As a portfolio grows, the repository file becomes the authority for that repo, and cross-repo conventions live in one shared document (this one).

---

## 2. The version-line matrix

During developer preview the harness publishes several lines at once, for example `0.1.2-rc.1`, `0.1.5-alpha.1`, `0.1.5-rc.1`, `0.1.6-alpha.2`, `0.1.7-alpha.2`. A portfolio pinned to one line breaks the moment a user installs from another.

The pattern that admits all windows (one clause per prerelease tuple; each `-0` floor covers every prerelease of its tuple, never the bare `>=0.1.7` form):

```jsonc
{
  "peerDependencies": {
    "@deepseek-ai/cordis": "^4.0.2",
    "@deepseek-ai/dsh-tools": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0 || >=0.1.6-0 <0.2.0 || >=0.1.7-0 <0.2.0 || >=0.2.0-0 <0.3.0 || >=0.2.1-0 <0.3.0"
  },
  "devDependencies": {
    "@deepseek-ai/dsh-tools": "0.2.1-alpha.2"
  }
}
```

⚠️ **Do not write the open form `>=0.1.2-rc.1 <0.3.0`.** It looks like the widest safe range and is the opposite: it spans an entire breaking-change generation, so the gate admits a `0.2.x` host while your plugin still calls `0.1.x` APIs — an install-time pass followed by a runtime break. **Upper bounds must be cut per generation** (`<0.2.0` / `<0.3.0`), and the six-clause shape above is the canonical range recorded in `dsh-plugin-kit`'s `data/peer-range.json` (`canonicalRange`, updated 2026-09-26). `dsh-plugin-doctor`'s R8 flags the open form.

A range is only as wide as its newest clause, and semver makes this easy to get wrong in a way that looks right: **a range admits a prerelease only if some comparator carries that same `[major, minor, patch]` tuple AND a prerelease.** So a band ending at `>=0.1.6-0 <0.2.0` admits `0.1.6-alpha.2` but *not* `0.1.7-alpha.2` — it reads as "everything up to 0.2" and behaves as "nothing after the 0.1.6 tuple". Adding the line's own clause is the whole fix; check it with `semver.satisfies('0.1.7-alpha.2', range)`, never by eye.

Rules that follow:

- **`devDependencies` pin the newest published line.** Your typecheck must fail before your users do.
- **`dependencies` pin the stable/rc line; `peerDependencies` admit both windows.** Never narrow a peer range just to make your own CI green - you will silently uninstall yourself from users on the other line.
- **Never re-pin 30 repositories by hand.** A scripted wave (patch bump, pin update, gate, publish) is the only sustainable path; run it as one atomic batch with a rollback note.

---

## 3. The release pipeline

Per repository the release is a tag push that triggers a workflow:

```yaml
on:
  push:
    tags: ['v*']
jobs:
  publish:
    steps:
      - run: pnpm install --frozen-lockfile
      - run: pnpm run build
      - run: npm publish --provenance --access public
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```

Five things that cost real time to learn:

1. **`--frozen-lockfile` is a gate, and it is right.** A release commit that forgets `pnpm-lock.yaml` fails here. Never release from a dirty lockfile state.
2. **Publish with `--provenance`.** One flag produces a signed supply-chain attestation - the cheapest trust signal you can give a reviewer.
3. **Secrets are per repository.** `NPM_TOKEN` must exist in every repository that publishes. When a token is rotated, reseed all of them, or the failure appears much later, one repo at a time.
4. **A pushed tag cannot be re-published.** If the artifact was wrong: fix the commit, force-move the tag, re-run, and accept that the first attempt is public history.
5. **Know the registry replication delay.** A verification job running immediately after a publish can still resolve the previous version ("The latest release is X"). Re-run after `latest` settles instead of "fixing" a package that was never broken.

---

## 4. One version, three surfaces

Publish once, mirror everywhere, make the mirror idempotent:

| Surface | Mechanism | Failure mode to watch |
|---|---|---|
| npm | tag-triggered workflow | dist-tag lands on the wrong line |
| GitHub Release | the same tag-triggered workflow (idempotent) | release notes drift from CHANGELOG |
| Gitee (mirror) | scheduled sync workflow | mirror HEAD behind upstream after a force-move |

Verify all three before calling a release done. A release that exists on npm but not in the mirror is a half release, and mirror users will report a bug you already fixed.

The GitHub Release is created by the **same tag workflow** that publishes to npm - never by hand. Manual creation is exactly how release notes drift from `CHANGELOG.md`, and the gap is invisible from the registry: a version that exists on npm but has no Release page looks complete to `npm view` and incomplete to anyone reading the repository. Keep the step idempotent, so a re-run, a backfilled tag, or an already-existing Release page cannot fail the job:

```sh
if gh release view "$TAG" >/dev/null 2>&1; then
  echo "release $TAG already exists; skipping"
  exit 0
fi
node scripts/changelog-section.mjs "$VERSION" > release-notes.md || true
if [ -s release-notes.md ]; then
  gh release create "$TAG" --title "$TAG" --notes-file release-notes.md
else
  gh release create "$TAG" --generate-notes
fi
```

Three details carry the weight. The job needs `permissions: contents: write` (the publish job's `contents: read` is not enough, and job-level permissions override the workflow's). It belongs in a **separate job that `needs:` the publish job**, so a tag whose `CHANGELOG.md` lacks that section cannot turn a successful npm publish into a red release. And the `|| true` plus the `-s` test is what makes the missing-section case degrade to generated notes instead of failing - a release page with generated notes beats no release page at all.

---

## 5. README parity is part of the build

If the portfolio ships READMEs in several languages, treat them as build artifacts:

- The language set is fixed and checked. Adding a section to one language and not the others must fail CI.
- Enforce encoding discipline before tagging: UTF-8 without BOM, no mojibake, no replacement characters. A published README cannot be repaired - npm metadata is immutable, and the registry may not even display your corrected file.
- When scripting anchors, remember word boundaries do not behave as expected after CJK characters.

---

## 6. Migration waves when the harness breaks compatibility

The harness states plainly that compatibility-breaking changes will happen. Plan for waves, not for stability:

1. **Detect.** Run a typecheck matrix against the newest published line before users hit it. A removed event type or a changed signature surfaces as a compile error in one repository, which tells you the blast radius for all of them.
2. **Triage.** Separate pin updates, mechanical code changes, and semantic redesigns. Only the first two belong in a wave; the third gets its own release and its own changelog entry.
3. **Execute atomically.** One batch of patch bumps, one gate run, one publish wave, and a rollback note per repository (previous tag plus the exact pin to restore).
4. **Record the incident.** Two examples worth writing down, because both were self-inflicted and both cost a release cycle:
   - A release commit without the lockfile turned CI red; fixed by amending the commit and force-moving the tag.
   - A lockfile generated by a different package manager contained workspace-link entries that a clean install could not reproduce; fixed by deleting it, reinstalling in isolation, and regenerating.

**Rule of thumb**: if a wave touches more than about four repositories at once, the CI queue saturates and runs settle out of order. Batch accordingly.

---

## 7. The pre-tag gate

Before every tag push:

- [ ] `pnpm install --frozen-lockfile` clean
- [ ] typecheck against the newest published line, not the one you developed on
- [ ] unit tests green
- [ ] package invariants: the `files` whitelist contains the built entry, and the entry exists
- [ ] README language parity plus encoding audit (no BOM, no mojibake, no replacement characters)
- [ ] `CHANGELOG.md` has a section for the version you are about to tag
- [ ] the tag workflow creates the GitHub Release itself, idempotently, from that section (section 4)
- [ ] the version bump matches intent (patch = wave, minor = feature, major = breaking)
- [ ] the tag does not already exist on the remote

After the tag:

- [ ] npm `dist-tags.latest` equals the new version
- [ ] the GitHub Release exists with notes
- [ ] the mirror is synced
- [ ] every consumer repository that vendor-pins this package was bumped in the same batch

---

## 8. Registry behaviours that look like your bug but are not

Document these once so nobody debugs them twice:

- **`versions[].readme` can be empty** for every published version of a package even though the tarball README is byte-identical to your repository. The package page then shows a stale README. It is a registry-side presentation behaviour, reproducible through both the CLI and CI channels - do not "fix" it by re-publishing.
- **`description` is truncated** at roughly 255 characters in package metadata. Compare a truncated form, not the full string, when verifying a publish.
- **Scoped packages** may return placeholder values on some download-count endpoints. Use the range endpoint rather than the point endpoint.
- **Published does not mean visible.** See item 5 in section 3.

---

## 9. Local tooling that fakes a result

Each of these returns a confident wrong answer rather than an error, and each one cost a real debugging cycle. Written down so the next person recognises the shape:

- **`npm view <pkg>@<ver> A B --json` invents absences.** Asking for two fields at once can report a field that exists as empty, which reads as "the published package lost its `peerDependencies`". Query one field per call; when it matters, unpack the published tarball and read its `package.json` - that is the ground truth. An audit that flagged eight packages this way was wrong about all eight.
- **A content search that honours `.gitignore` returns false negatives.** Where the workspace root ignores everything (`*` plus a single negation), a search across it finds nothing, and "nothing" reads as proof of absence. Before concluding a pattern is absent, confirm it with a scan that does not consult ignore rules.
- **Windows PowerShell 5.1's `Set-Content -Encoding utf8` writes a BOM.** A JSON request body beginning `EF BB BF` fails to parse, and the error blames the payload's contents rather than its encoding. Write machine-read files through an explicit encoder: `[System.IO.File]::WriteAllText($path, $text, (New-Object System.Text.UTF8Encoding($false)))`.
- **On Windows, `rd /s /q` cannot remove a tree containing a reserved device name** (`NUL`, `CON`, `AUX`, `PRN`, `COM1`-`COM9`, `LPT1`-`LPT9`). It reports success and leaves the entire ancestor chain in place. Delete that entry through a `\\?\`-prefixed long path, then assert the directory is actually gone - a zero exit code is not evidence that the tree disappeared.

---

## 10. What this buys you

A portfolio maintained this way behaves like a product: users on any harness line can install any plugin, every version is attested, mirrors agree, and a breaking upstream change costs one wave instead of an outage. It is also the strongest form of ecosystem contribution - the official project asks for exactly this, and explicitly does not rank official packages above community ones.

Corrections and additions are welcome; the practical entry points are listed in [links.md](links.md).


<!-- source: guide/unfixed-issues.md -->

# [Unfixed upstream issues (source-verified)] guide/unfixed-issues.md

# DSH master 未修复问题与常见误解(源码核实版)

> 本文件是 `dsh-plugin-guide` 知识库对官方仓库社区讨论的**源码级结论收拢**:
> 每个条目都在基线提交上逐行核实过(read/grep),附 `path:line`、临时规避与原文讨论链接。
> 汇总帖见官方 Discussions [DSH master 仍未修复的问题清单(社区核实版)](https://github.com/deepseek-ai/deepseek-harness/discussions/6520)。
>
> - 初版基线:`c291e7961a515f6d7af9304e7fd1d257929aef26`(2026-09-10 快照,0.1.5-rc.2 世代)
> - 复核基线 1:`0d1f50007f9bca3f52b06e1c3074fa14d5fb0720`(2026-09-15,0.1.6-alpha.1 世代;区间 666 提交)
> - 复核基线 2:`ddefc45fbc7f8e46dd73185e68295696d1297887`(`dsh-v0.1.6-alpha.2`,2026-09-19;+882 提交)
> - **复核基线 3:`5badb15009ae1756c3afe0ae0cef1faafc290ccc`(`dsh-v0.2.1-alpha.1`,2026-10-03;+2300 余提交 / 8763 改动文件)——2026-10-06 全表重核,逐条复核覆盖 #1–#32 与 S1–S7**:
>
> ⚠️ **阅读本表前必读的基线口径**:本文件全部 `path:line` 与"已修复提交号"**钉在复核基线 3(`5badb15`,2026-10-03)**。
> 本库的官方文档镜像已在 **2026-10-13 重同步到 `d743267388641bc76f17c45ce8b4c231aed1d32c`(`dsh-v0.2.1-alpha.2`)**——
> 也就是说**镜像比本表新一版**。二者不是矛盾,是两个不同用途的基线:
> 镜像求"与上游逐字节一致",本表求"每条结论有可复核的行号锚点"。
> **在 `5badb15` 上复核行号**(`git -C <checkout> show 5badb15:<path>`),或用镜像里的路径但**预期行号可能已漂移**。
> 下一轮全表重核的目标基线是 `d743267`;在重核完成前,请把"未修复"读作"**截至 2026-10-03 未修复**"。
>   - **转入已修复 4 项**:#2(协议平面压平为 Messages-only,旧 chat-completions 半边删除;**后继形态 #8836 仍在**)、#5(`code`→`ptc` 迁移)、#10(pi-ai ^0.87.1 带上 `x-opencode-session`)、#22(压缩阈值改为 `min(window×ratio, window−reserved−headroom)`)、#29(telemetry 读取拍平 + 迁移产物强校验)——共 5 项,其中 #5 的修复**早于上一基线**,说明旧条目本身就过时。
>   - **判为不可复现 1 项**:#4(read_image)移入 §2,保留记录但**不要再当 bug 传播**。
>   - **部分修复 3 项**:#1(同级已接受、更窄仍抛)、#13(windowsHide 半侧已消失,自愈半侧仍在)、#19(`./invariant` 子路径已随 invariants 整体移除而消失,残留 devDependency `dsh-attachment` 引用)、S5(SKILL.md 半侧早已修好,仅剩生成文件/文档措辞残留)——共 4 项。
>   - **新核实 11 项**:§3 的 T1–T11(社区在 2026-09-15→10-06 窗口内提出,并经源码或已发布产物双向复核)。
>   - **行号全面刷新**:#23/#24/#26/#28/#30/#31/#32/S2/S4/S6/S7 的行号整体前移了一代;S5 引用**过时两代**;S1 的失败模式静态不可复现,已降级。
>   - 区间内两处上游改名/重写已并入条目:`llm-deepseek` 协议平面压平(`99e22ebbeb`,`src/common/*` → `src/*`、chat-completions 删除)、`preset/agent-presets` → `preset/agent-preset`(`d1e22a7e24`)。
> - 核实人:PerryLink([dsh-plugin-guide](https://github.com/perrylink/dsh-plugin-guide) 维护者)
> - 用法:开发插件/排障时按「症状 → 位置 → 规避」查;条目后附原讨论,官方有新回复时以原帖为准。
> - 诚实标注:无法在源码复核的环节已注明 `UNVERIFIABLE`;**判为不可复现的条目不删除**,移入 §2 保留为"疑似误诊"记录。
> - **维护者参与度**:本轮通读窗口内 3100 条讨论,**没有任何维护者在任何帖里确认过修复或设计决定**;`#6520` 最后活动为 2026-09-23,无维护者回复。官方 Release 说明是唯一权威的"官方决定"记录(仓库无 CHANGELOG、无 issue tracker——API 核实 `has_issues=false`)。引用"官方已确认"时务必落到 Release 原文或提交号。

## 1. 仍未修复(22 项 + 2 项部分修复(#1、#13)+ 1 项残留面(#29),按严重度排序)

| # | 问题 | 位置(@5badb15,2026-10-06 复核) | 临时规避 | 讨论 |
|---|---|---|---|---|
| 1 | 同级/更窄的 `sandbox_permissions`:**同级自 `61c548e200` 起已接受**(该修复早于上一基线),更窄/不受支持的目标仍报错 | `packages/sandbox/sandbox/src/escalation.ts:171-179`(同级在 `:173` 返回;`:177-178` 仍抛"not strictly wider";旧基线同文件 `:153-161`) | persona 注明「已是该模式就别带 sandbox_permissions」 | #4021 #4481 #4672 #4742 #4763 #4976 #4990 #5570(同族 #5238 #5298 #6215) |
| 2 | 适配器把**空白文本块**原样送上线路 → 云端端点 HTTP 400,整会话报废(**旧条目的 chat-completions 半边已随协议删除消失,这是 V4 / Messages-only 之后的后继形态,0.2.1-alpha.1 仍在**) | `packages/llm/llm-deepseek/src/serialize.ts:30`(`assistant()` 在 `:26` 声明,`:30` 的 `case 'text'` **无守卫**);同文件两个兄弟转换器对同一 case 有守卫:`:65`(真值判断)、`:93`(长度判断)。实测每次出现的都是 `{ type:'text', text:'\n' }`——**长度 1,从不为 `''`**,所以 `length === 0` 抓不到;该包内基于 `trim()` 的守卫全库搜索为空。自托管路由容忍,**切到云模型才炸** | 无产品内规避;补丁要求=回放时丢弃**纯空白**文本块(并同样应用到 `:65`/`:93`,因为其他 role 也会发 `"\n"`)。另注意 npm 产物 `files` 只含 `lib/index.js` 与 `lib/types/**/*.d.ts`——**不发布 `src/`**,所以源码锚点在 npm 包里对不上 | #8836(后继);#5466 #1850 #6218 #6431(原始族) |
| 3 | windows-acl 沙箱缓存临时目录消失后该会话永久损坏 | `packages/sandbox/sandbox-local/src/index.ts:423-425`(缓存命中无存在性复核;旧 `:415-417`);runner 首检 `packages/sandbox/sandbox-windows-acl/src/runner.ts:110-114`。区间新增的 ACL 诊断技能(`1d56bd5628`/`927b59e138`/`5bab07157f`)只修 ACL,不修消失的临时目录 | 重启 dsh host 重建快照 | #6483(同族 #5034) |
| 6 | exFAT 卷 write 必败(EISDIR)+ 盘根写入 EPERM | `packages/fs/fs-local/src/fsio.ts:633-638`(硬链接发布无回退,默认 `link` `:608`、`throwGuardedCreateFailure` `:536-571`)、`:597-598`(mkdir 不容忍盘根 EPERM)。`fsio.ts` 在本区间**零改动**,行号与上一基线完全一致 | 目标放 NTFS;勿直写盘根 | #5704 #4981(相关 #2402) |
| 7 | Node < 22.19/24.2 安装后静默深埋失败(无友好版本门) | `apps/cli/src/bin.ts:26-27`(区间重构过 bin.ts,但**仍未加**运行时版本检查);`apps/cli/package.json` 无 engines;仅根 `package.json:8-9`(npm 只警告);`apps/cli`/`packages/boot` 内无 `process.versions.node` 检查 | 升级 Node ≥ 24.2(或 22.19+);全局安装替代 npx | #6115 #6124 #6126 |
| 8 | 单个损坏插件条目令所有对话请求 REQUEST_EXTENSION | `packages/llm/llm-deepseek/src/request-extensions.ts:24-29`(该文件已由 `99e22ebbeb` 从 `src/common/` 上移一级;异常一律升级);`packages/llm/plugin-package-inventory-deepseek/src/index.ts:124`(抛错);registry 扇出 `packages/llm/deepseek-llm-api-extensions/src/index.ts:108-113`;默认挂载 `packages/bundle/base/cordis.patch.yml:77-78`。区间新增的 `onOmitted` 加固**只覆盖序列化失败**,不覆盖抛错的贡献者 | profile patch 禁用该插件,或修复损坏条目 | #6161(相关 #5683 #5968 #6108) |
| 9 | PTC 模式零参数工具绑定必失败 | `packages/ptc-runtime/ptc-runtime-node/src/bootstrap.ts:326-335`(+ `index.ts:319`);`json-wire.ts:201`(零参调用得到 `undefined` → `binding arguments must be lossless JSON`)。`packages/ptc-runtime` 源码在本区间**零改动** | 无产品内规避;帖内一行修复未合入 | #6065 |
| 11 | Python SDK 跨进程续接旧会话只跑不落盘 | `packages/sdk/server/src/server.ts:261-294`(旧 `:259-292`;只查进程内表;恒 `agents.create`);`packages/sdk` 与 `python/` 全树只有 initialize/session-prompt/shutdown 三类 RPC,**无任何 resume** | 同一进程内复用实例循环多轮 | #4591 #5950 #4954(相关 #1414) |
| 13 | `dsh plugin` 子命令无法自愈 profile 依赖(PARTIAL:windowsHide 半侧已消失) | **原引用的 `apps/cli/src/plugin.ts:120-163` 早已不存在**(该文件在**上一基线之前**的 2026-09-14 由 `98b92b683c` 删除)。现子进程在 `packages/boot/plugin-manager/src/operations.ts:357`,走 execa(默认 `windowsHide: true`),故弹窗半侧已消失;**自愈半侧仍在**:`runPluginCommand`(`:544-557`)只转发调用方的 pnpm 参数,boot 提示仍指向 `dsh plugin --profile X install`(`packages/boot/app-boot/src/profile.ts:715`) | 用 `dsh plugin --profile <p> install`(旧建议"手动 pnpm install"已过时) | #5537(#4024) |
| 14 | 插件经 `connection.rpc.handle()` 注册的通道静默失效(405) | `packages/client/connection/src/rpc-host.ts:87`(`const owner = this.ctx`,与上一基线逐字节相同)、`:191-194`(`owner.effect(() => owner.webServer.register(route))`);inject 现为 `['credentials']`,位置 `packages/client/connection/src/index.ts:89`(旧条目写的 `:69` 在上一基线就已不准)。**caveat**:vendored Cordis 的 accessor 走影子代理,`this.ctx` 是"读取方"的 ctx,故"owner 取服务自身 ctx"的因果表述无法从源码确证,405 症状需要活体复现 | 打社区补丁 cb9b6e2;等上游合并 | #6227(同族 #6270 #6289 #6337 #6513 #6681) |
| 16 | pwsh 沙箱对临时根未加保护的 realpath(RAM 盘报 EISDIR) | `packages/sandbox/sandbox-windows-acl/src/path-boundary.ts:11-13`(`realpathSync.native` 无 try/catch;与上一基线逐字节相同);对照 `packages/sandbox/sandbox/src/roots.ts:30-41` 已有回退先例(同样未改) | TEMP/TMP 指回物理盘或子目录 | #6018 |
| 17 | 编程式 `agents.create` 缺 model 时静默死轮 | `packages/core/agent-loop/src/index.ts:371`(旧条目 `:421`/报告 `:422-424` 均有偏差;`{{model}}` 仍绑 `context.agent?.options.model` 无 `agentDefaultModel` 回退);`packages/core/agent-loop/src/agent.ts:581-582` 的 throw 与旧基线一致;webhook 已有回退先例 `packages/webhook/webhook/src/session.ts:63-66` | 先 `agentDefaultModel.currentSelection()` 再显式传 provider/model | #4967 |
| 18 | grep/read 行预览从列 0 截断,2000 字节外的匹配被隐藏 | `packages/fs/tool-fs-search/src/grep.ts:35`、`search-core.ts:324-325`(`kind:'head'`)、`packages/fs/tool-fs/src/read-render.ts:11`(`:70` 同类)——全部未变,行号与上一基线一致。细节:**grep 的截断按字节**(TextRetainer),**read 的截断按字符**(`line.length`) | 单行大文件改用 shell 提取区间 | #4982 |
| 20 | /compact 在 agent 未空闲时一律报「active compaction」,诊断串味 | `packages/compaction/compaction-basic/src/index.ts:428-433`(旧 `:410-416`);`packages/core/agent-loop/src/agent.ts:183-184`(旧 `:157-158`);面向用户的文案 `packages/command/command-compact/src/index.ts:29`——三处与旧基线逐字相同 | 等 turn 完全结束再 /compact | #6223 |
| 21 | todo 任务栏在回合中断后永久消失 | `packages/todo/tool-todo/src/index.ts:129`(投影 apply 在 `turn/start` 无条件返回 null;旧 `:140`)、`:133`(`stateVersion: 2`;旧 `:144`);区间只改了 `DESCRIPTION_*` 文案。修复需 bump 2→3(投影缓存 ver 不匹配即丢弃,`packages/session/session-projection-cache/README.md:81`) | 无产品内规避(模型自觉重写不可靠) | #6524(已并入汇总帖 #6520) |
| 23 | overflow 分支 retainTokens = 0,一次清掉约 98%(手动 /compact 同源) | `packages/compaction/compaction-basic/src/index.ts:299`——overflow 直接 `selectCompactableRange(session, measurement, 0)`(旧 `:281-285`);手动 /compact 同样是 `0`(`:394-398`)。**新增缓解**:`maxOverflowRetries: 0` 可拒绝这次零保留 pass;后端现已**按 preset 作用域**(`packages/bundle/web-app/presets/*.patch.yml:67-83`,host 树禁用 `packages/bundle/web-app/cordis.patch.yml:527-528`——旧引用的 `:432-439` 已失效) | 设 `maxOverflowRetries: 0`;确认 preset 隔离域确实挂载了 compaction-basic | #5416 #5650 #6672 |
| 24 | tool-result 剪枝跑在压缩选区之前,摘要器输入已失真 | `packages/compaction/compaction-basic/src/index.ts:294-302`(overflow:prune → select)、`:323-331`(pressure:prune → remeasure → select);剪枝器已是独立包 `packages/compaction/compaction-tool-result-pruner/src/index.ts:136-182` | **(本次新增可用的规避)** 在自己编写的 preset 里**不挂** `tool-result-pruner` 行,或抬高 `thresholdChars` | #5766(相关实测 1:1 剪枝标记,来自 #6520 条目提交者) |
| 25 | 压缩后旧轮推理整段不回传(实测推理占摘要器输入 58.8%) | 压缩交易把选区整体替换为摘要(`packages/compaction/compaction-basic/src/region.ts:173` compactSurfaceRegion + replace 操作 `:506-509`;旧 `:174`/`:472-500`),旧轮 reasoning 位于被替换区间内、不再回传;区间内**没有**新增推理保留路径 | 无产品内规避 | #6480 #6510 #3002(实测证据为准,见 #6520) |
| 26 | token-meter 的 CJK 增量低估(实测 +26% ~ +57%,纯中文样本 1.57×) | `packages/llm/token-meter/src/estimate.ts:13`(CHARS_PER_TOKEN = 4;estimate 只作用于每步增量,总量 provider-anchored)。**上游现已把它记为已知限制**(`token-meter/README.md:154`、`compaction-basic/README.md:257`)——回答社区时按"已知限制"表述,别当未发现的 bug | 无产品内规避;注意按真实用量预算 | #6361 #5632 #6688(实测口径见 #6520) |
| 28 | 读路径 `SessionLogScanner` 默认 `recoverable`:seq gap/损坏行被静默截断,直到后续 turn/end 才重抛("valid aborted-turn 被当作 no more history") | `packages/session/session-persistence-jsonl/src/index.ts:964`(无 recovery 实参);`src/format.ts:404`(默认值)、`:499-503`/`:513-517`(issue 暂存);gap 逻辑 `session-format-v1-to-v2/src/codec.ts:104-112`;strict 仅 verify 路径 `generation.ts:586,600`。格式已升 V4 但**这条读路径未动**,行为现已被测试钉死(`tests/jsonl.spec.ts:2227-2249`)。新增的相邻守卫:`format.ts:493`(assertV4RowAdmission)、`:468`(assertReleasedV4Relationships) | 备份日志手动修 gap;修复方向=给 `:964` 传 `'strict'` 或暴露 `format.ts` 的 issue | #6562 #3631 |
| 30 | http-proxy 把 `[::1]` 写进子进程 `no_proxy`/`NO_PROXY` → httpx 系 MCP server 崩溃(undici 专用括号项泄漏到子进程 env) | `packages/util/http-proxy/src/policy.ts:33`(LOOPBACK_NO_PROXY 仍含 `['localhost','127.0.0.1','::1','[::1]']`,注释 `:25-32` 自认是为 undici);env 写入 `install.ts:79-92,113-129`、子进程覆盖 `:262-279`;子进程消费方 `packages/subprocess/subprocess/src/index.ts:75`。**src 在本区间零改动** | env 写入侧只写裸 `::1`,undici 消费处保留括号项(两处消费者分离) | #6655 |
| 31 | 粘贴图片惰性持有 File 快照:剪贴板同步(如微信输入法跨设备复制)后提交时 FileReader NotFoundError | `packages/client/ui-conversation/src/client/service.ts:74-81`(只存惰性 `file`)、`:125-137`(提交时才 FileReader)、`:309-325`(文件类立即上传、图片类惰性);该文件本区间仅 +6/−2 | 粘贴后立即发送,或拖拽/文件选择 | #6673 |
| 32 | web-fetch NAT64 探测无守卫:无 DNS64 网络(ipv4only.arpa 不解析)下所有双栈主机 fetch 全灭 | `packages/web/web-fetch-http/src/network.ts:90-92`(无 try/catch)、`:113-117`(discoverNat64Prefixes)、`:38`;SSRF 检查独立(现 `:96-108`)。**src 未改动** | 无产品内规避;补丁方向=ENOTFOUND/ENODATA 视为无前缀 | #6664 |
| 29 | 原生 V4 的 `user/message` content **未做校验**(只校验 `source`)→ 手改或插件注入的缺 `content` 行仍在投影层抛错。迁移路径的同类症状已由 `f4a32dbd0a` 修掉(见 §5),这条是**残留面** | 投影读取 `session-turn-outline/src/projection.ts:39`;对照:迁移产物的强校验在 `session-format-v3-to-v4/src/validation.ts:52-129` | 注入 `user/message` 时带完整 `content` | #6686 |

## 2. 疑似误诊(1 项,保留记录,勿当 bug 传播)

| # | 原报问题 | 复核结论(@5badb15) |
|---|---|---|
| 4 | read_image 所有预设一调即失败(cannot get property 'fs' without inject) | **UNREPRODUCIBLE(疑似误诊)**:`packages/fs/tool-fs/src/index.ts:70-72` 与 `read-image.ts:209,263`(旧 `:265`)在本区间**未改动**,`vendor/cordis/src/reflect.ts` 同样未改;真机跑该包真实 spec `vitest run packages/fs/tool-fs/tests/read-image.spec.ts` = **41/41 通过**,而该 spec 正是走 `ctx.inject(['attachments'], …)` + `ctx.tools.execute` 这条路径,且它不在本区间的测试 diff 里。结论:既不是"修复了",也不是可复现缺陷——遇到同症状请先确认是否被"在未注入 attachments 的上下文里直接调 read_image"之类用法触发,再开帖。 |

## 3. 本世代新核实的问题(社区提出 + 源码/产物双向复核,T1–T11)

> 这一组是 2026-09-15 → 10-06 窗口内社区新开帖、并经**源码或已发布产物**双向复核的问题,之前不在本清单里。T 编号独立,避免与 §1 的编号冲突。

| # | 问题 | 位置 / 证据 | 规避 | 讨论 |
|---|---|---|---|---|
| T1 | **运行中修改 `cordis.patch.yml` 具破坏性**:`dsh-hmr` 精确监听 `profile/package.json`、`profile.patchPath`、`$DSH_HOME/cordis.patch.yml`(`@deepseek-ai/dsh-hmr/lib/index.js:353-376`,0.2.0-rc.2),任何写入(**包括逐字节相同的重写**)都触发重载 | 实测三种后果:① 会话内工具从 **65 → 41**(丢失 `bash`/`edit`/`read`/`write`/`grep`/`glob`/`web_fetch`/`web_search`/`skill`/`subagent_fork`/`workflow`/`todo_write`/`present`/全部 `job_*`,只剩 preset 之外挂载的行);② 运行中会话的 turn 被杀;③ 重建 `session-controller` 时抛 `file-upload: Agent resolver is already registered`,`sessionController` **永久失效**(只有整进程重启能恢复,之后每次重载同样失败) | **运行中不要写 profile patch**。改配置走 profile 管理入口(`plugin_manager` / `dsh plugin`)而不是手写 YAML;必须手改就先停进程 | #8635 #8857 #8968 |
| T2 | `plugin add` **不补齐既有依赖的 layer 行**:`packages/boot/plugin-manager/src/operations.ts:95`/`:99`(`if (beforeDeps.has(name)) continue`),产物侧同一代码在 `lib/index.js:271`/`:275`(固定 176 行偏移)。后果:`dsh.profile.bundles` 掉了行之后,之后任何 `add` 都不会修回来;"先发布无 `dsh.bundle`、后加上 `dsh.bundle`"的包**永远不会**被登记为 bundle 层,且**第二次尝试不打印任何警告** | 实测仍在 `0.2.1-alpha.1`(源码与产物一致) | 先 `remove` 再 `add`;或手工补 `dsh.profile.bundles` | #7850 #8821 |
| T3 | 宿主插件(Host plugin)激活失败**在 `dsh web` 下不可见**:`Fiber._reload` 捕获异常 → `ctx.logger.error` → 置 FAILED、**不重抛**(`vendor/cordis/src/fiber.ts:646-665`);`web` profile 没有日志导出器,那行日志**没有 sink**,终端一片空白,而工具/监听器已经静默缺失。`apply()` 返回普通对象只得到 `TypeError('Invalid effect')`,**不报插件名也不报值** | 交叉引用 #5145(同族客户端侧 #7891) | 排障时用 headless/带日志的 profile 复现,或逐个禁用 bundle 二分;社区工具 dsh-composition-doctor 专门把故障关联回组合来源 | #8633 |
| T4 | profile 内安装的 `@deepseek-ai/*` 包会**静默遮蔽**运行时自带版本:解析优先级 `profile > runtime`。最小复现:插件把 `"@deepseek-ai/dsh-settings": "^0.1.0-rc.6"` 写进 **dependencies** → pnpm 装进 profile 的 `0.1.0-rc.8` 盖掉自带的 `0.2.0-rc.2` → `settings service is absent: mount @deepseek-ai/dsh-settings with @deepseek-ai/dsh-config-editor`,桌面端每次崩溃 | `POST /api/settings/describe` 返回 `gateway/internal` | 共享实例的 dsh 包只放 **peerDependencies + devDependencies**,**绝不放 dependencies**(§migration-0.2 §1 的 npm 段同理) | #8862 #8864 |
| T5 | 会话内 **tool-call id 不唯一** → Web Chat/Trajectory 组装器抛 `received more than one start Match` 后**永久停止产节点**(实时与回放两条路径都无降级分支) | `packages/client/ui-conversation/src/client/conversation/assembler.ts` 的 `acceptMatch` | 无产品内规避;自己生成 `callId` 的插件务必全局唯一 | #6520 内提案(XGntsl,对照 `0d1f50007f` 核实)+ ≥10 个独立帖(自 `0.1.1-rc.2` 起) |
| T6 | 含 `turn: null` 的 `step` 块的会话**永远渲染不完**:白屏卡在 "Loading history…",渲染循环不推进、CPU 升高;而契约检查报 0 违规,官方 `Session` 构造与 `deriveMessages` 都成功 | 渲染路径在无法归属的块上循环 | 无产品内规避 | #6520 内提案(yamingmou) |
| T7 | **桌面端 `cordis` preset 丢失全部文件系统 skill**:preset 唯一的 skill provider 目录指向 `app.asar` 内部(`resolve('@deepseek-ai/dsh-agent-preset/package.json') + /skills`),读取抛 `TypeError: Cannot mix BigInt and other types`;`discoverRoot` **没有 per-root try/catch**,且自定义目录**先于**默认根被扫描,于是一次失败终止整个 provider 的 `list()`——连带丢失项目的 `.dsh/skills` 与用户的 `~/.dsh/skills`、`~/.agents/skills`;`dsh-tool-skill` 又以 `snapshot.complete` 为发布门槛,导致模型侧 skill 目录整体消失(日志里同一条 `skipped` 反复出现即是信号) | `dsh-hooks`/skill provider 链路;`isAbsentSkillPathError` 只吞 `ENOENT`/`ENOTDIR`/`FS_NOT_FOUND`/`FS_NOT_DIRECTORY`,其余错误照抛 | 无产品内规避;把 skill 放默认根而不是 symlink 进 `~/.dsh/skills`(该路径不解决问题) | #8649 #8763 #8996 |
| T8 | 第三方插件在会话观察者里抛异常会**放倒整个宿主**(观察者回调外侧没有隔离边界) | 无 per-listener 隔离 | 自己插件里所有回调 try/catch | #8585 |
| T9 | 插件 `pre-step` 观察监听器返回 `undefined` → **放倒全部会话**(`installModelSelection` 中间件链未加守卫) | `packages/core/agent-loop/**` 选择中间件链 | 观测型监听器要原样返回传入的 decision(改写时用 `{ ...decision, … }`) | #8778 |
| T10 | `dsh plugin` 依赖**系统 pnpm**(不自带):产物在 `@deepseek-ai/dsh-plugin-manager/lib/index.js` 用 `execa(options.command ?? "pnpm", …)`(L324/517/650/694/733),`pnpmCommand` 默认 `"pnpm"`(L1354),`dsh/node_modules` 里没有 pnpm——PATH 里去掉 pnpm,所有市场安装 `exit 1`,且 UI 可能只显示 `exit 1`。第二个缺陷:`install-spec.js` **校验但不注入版本**,裸包名会被 profile 的 lockfile 解析到旧版(实测 `@latest`、`@^2`、`pnpm update --latest`、`pnpm add …@latest` **全部失败**,只有显式 `@2.8.4` 成功) | 源码级复核 | 保证 PATH 有 pnpm;装包时**显式写版本** | #8905 #8922 #8310 |
| T11 | **组合包升级后大批第三方插件被判不兼容**:`evaluatePluginCompatibility()` 只返回**不满足**的 peer,且**不带任何"哪个版本能满足"的字段**,UI 无法告诉用户该装哪个版本;`minimumReleaseAge`(24h 冷却)让 `pnpm update` 成为静默 no-op、锁文件检查抛 `ERR_PNPM_MINERELEASE_AGE_VIOLATION`(可用 `--config.minimum-release-age=0`,但桌面宿主**拒绝**市场追加的 pnpm 参数);另有锁文件污染模式:一旦两个 `name@version` 落进 `minimumReleaseAgeExclude`,该 profile 之后**每次**安装都失败 | `packages/boot/app-boot/src/plugin-compatibility.ts` | 插件显式声明两侧 peer 范围(如 `>=0.1.2-rc.1 <0.3.0`)并按版本发版;用户侧遇冷却期用 `--config.minimum-release-age=0` | #8199 #8310 |

**相关但不单列**(同族或用户侧):#8537 提出 `dsh --profile <name> --ephemeral` 与一等公民 `dsh plugin verify <tarball>`(install → composition check → visibility probe → uninstall → rollback)——正是本仓库 `dsh-plugin-dev verify` 与 `PerryLink/dsh-test-drive` 在做的事;同帖给出 `sanitizeProfile` **按设计**把 profile 的 `cordis.patch.yml` 改名成 `.bak-<timestamp>`(JSDoc 明写 "patches are never parsed"),桌面端 `disableAllPlugins` 走的正是这条路径,实测用户文件 2622 B → 837 B,而 `backupPath` 只在 `console.info` 里打印、**UI 不显示**。#7401 单个 MCP 工具 `inputSchema` 含不支持关键字会**拒绝整个 server**;#7995 v0→v1 迁移拒收写入方唯一会产出的 `subagent/descriptor` version 2;#8836 见 §1 #2(V4 后继形态);#8904 升级插件后有一个 client entry 没有 fiber → 刷新页面即崩整个应用。

## 4. 次级清单(已核实、优先级较低,6 项)

| # | 问题 | 位置(@5badb15) | 规避 | 讨论 |
|---|---|---|---|---|
| S1 | Python SDK bundled runtime 清单缺 `dsh-attachment-local`(`python/sdk-runtime/package.json:27` 只有 `dsh-attachment`)——**但静态不可复现**:`dsh-base` 把 `dsh-attachment-local` 列为产品依赖(`packages/bundle/base/package.json:42`),且 `scripts/verify-runtime-closure.ts:83-92` 会递归走依赖图,实跑 `pnpm run verify-runtime-closure` **通过**("4 agent presets and 185 workspace packages form a closed runtime dependency graph")。**降级为清单一致性观察**:除非有人能在构建出的 wheel 里复现 `ERR_MODULE_NOT_FOUND`,不要再当缺陷传播 | 见左 | 无需规避 | #4377 |
| S2 | 会话列表 RPC 是一次性全量快照,无分页/懒加载 | `packages/api/session-controller/src/index.ts:258-261`(list 忽略 `_request`)、`src/list.ts:131-164`、`src/types.ts:264-271`(cursor 仍只是保留位,无 `nextCursor`) | 拆分工作区/清理旧会话/ssh -C | #6017 |
| S4 | 冷/种子会话列表行回退显示工作区文件夹名 | `packages/api/session-controller/src/client/sessions/service.ts:116-123`(`displayTitleOf` → `workspaceTitleOf(cwd)` → id),调用点 `:620` | 打开会话一次生成标题投影 | #6316 #6207(相关 #3375 #5368) |
| S5 | cordis 组合 skill 里残留已废弃的 `cordis_inspect` 措辞(**PARTIAL**:SKILL.md 正文半侧早在上一基线**之前**就由 `ed32f57f88`(2026-09-16)修好——旧条目引的是 2026-09-10 的 165 行版本,已过时两代) | 残留未修:`packages/extensions/tool-cordis/src/api-catalog.ts:6`、`cordis-client-runner/src/client/slot-catalog.ts:7,74`、`docs/subsystems/slots.md:194`(+.zh);SKILL.md 现位于 `packages/preset/agent-preset/skills/editing-cordis-compositions/SKILL.md:84`(改名 `d1e22a7e24`)。**注意**:那三个"快照"(`cordis-history ui.expected.md`、`session.v2/v3.jsonl`)是**故意的历史回放/迁移 fixture,不要重新生成** | 文档修复型 PR | #6679 |
| S6 | SIGTERM 无在途 turn 排空路径;5s 宽限硬编码;无 `dsh restart` | `apps/cli/src/process-shutdown.ts:4`(5s)、`:69-75`(第二信号强退);`profile-boot.ts:269,280-281`;dispose=cancel+whenIdle 现位于 `packages/core/agent-loop/src/index.ts:544-545`(旧 `:594-595`);`apps/cli/src/args.ts` 里**没有** restart 模式 | 第二信号即强退是固定语义;drain 属 feature request | #6665 |
| S7 | LLM 出站超时修复未合入;undici 全局 dispatcher 由 http-proxy 独占(>5min prefill 在 ~302s 被 body timeout 终止) | `packages/util/http-proxy/src/install.ts:208-213`(`setGlobalDispatcher`)、`new Agent({factory})` `:145-156`(**无 bodyTimeout**);全库无 `httpBodyTimeoutMs`/`bodyTimeout`/`egress.ts`(新增的 `tests/egress.spec.ts` 只测代理路由);pi-ai 看门狗 300_000 在 `llm-pi-ai/src/config.ts:47`(按路由覆盖 `:344`)、`adapter.ts:354-355` | 每请求新建 fetch 绕过共享 socket 记账 | #5673 |
| S8 | 创造模式 `cordis_define`/`cordis_run` 动态工具已退役 | `docs/tool-catalog.md` 只剩两个只读检查工具(`cordis_inspect_list`/`cordis_inspect_query`);运行期创作的替代面 = `plugin_manager` 工具 + `ctx.pluginManager` + `OPTIONAL_BUNDLES` | 0.1.5-rc.2 世代 `docs/tool-catalog.md` 的 `cordis_*` 工具段(旧快照 :269-501)、`docs/capability-seams.md` 的 `ctx.dynamicCordisRunner` | 无(上游工具退役,非缺陷) |

## 5. 已在 master 修复(旧帖一律更新即可,无需改代码)

带 ✅ 的是本次(2026-10-06,基线 `5badb15`)新转入的条目:

- ✅ **纯推理轮以空 content 落盘 → 之后每轮 400、整会话报废(#2)**:`99e22ebbeb` 在区间内把 `llm-deepseek` 的协议平面**压平为 Messages-only**——旧 `src/protocols/chat-completions/serialize.ts` 及其同族 5 文件 / 1255 行被**整体删除(无改名)**,`protocols/messages/*` 上移为 `src/*`;`src/config.ts:206-207` 表明 `protocol:` 键现在**直接抛错**,空 content 分支随之消失。**仍需要的动作**:已被写坏的存量日志照旧要手工修(备份后解压 `session.v3.jsonl.zstd`,删/改占位后重压);若你依赖显式 `protocol: chat-completions`,删掉该配置项。
- ✅ **升级后旧 `code` 预设会话无法 resume(#5)**:`ff33f79eb1` 在 `session-format-v2-to-v3` 的迁移里把 `code` 映射为 `ptc`(header 与所有 agent-preset/selected,`migration.ts:18`、`:140-151`)。**注意该修复早于上一基线**,旧条目本身已过时。残留属设计:原生 V3/V4 里的 `code` **不会**被重新解释,registry 仍抛 `agent-preset/not-found`(`packages/preset/agent-preset-registry/src/index.ts:207`),且该包在 `d1e22a7e24`(2026-09-21)拆包,无 git 改名配对。存量会话仍建议复制内置 ptc 预设或改绑 standard/ptc。
- ✅ **opencode-go 路由缺 `x-opencode-session` 头 + 缺 4.1-flash 目录项(#10)**:`6ed596f71b` 把 `packages/llm/llm-pi-ai/package.json:44` 的 `pi-ai` 抬到 `^0.87.1`;实测已装 0.87.1 的 `dist/providers/opencode-headers.js` 会设置 `x-opencode-session` 并包装 opencode/opencode-go,目录里也有 `deepseek-v4.1-flash`(仓库侧 patch 不触碰该 wrapper)。
- ✅ **压缩阈值按整窗口算,1M 窗口下高于 provider 实际输入上限(#22)——本次新修复**:`555b664b08` 把阈值改为 `Math.floor(Math.min(contextWindow × ratio, contextWindow − reservedCompletionTokens − headroomTokens))`(`packages/compaction/compaction-basic/src/config.ts:191-194`),新增 `headroomTokens`(默认 `65_536`,`:75`),保留输出在 `index.ts:314-318` 接线;窗口 ≤ `maxTokens + headroom` 时抛 `TargetPressureConfigError`(后续 `ec7030afd3`)。**新公开配置键**:`headroomTokens`、`maxTokens`——旧帖里"按模型覆盖 `thresholdRatio`"的规避可以撤掉。同区间 `llm-deepseek` 的 `src/common/defaults.ts` 由 `99e22ebbeb` 改名为 `src/defaults.ts`(`DEFAULT_CONTEXT_WINDOW`/`DEFAULT_MAX_TOKENS` 仍是 1M/256K)。
- **windowsHide 弹窗族**:`a05b5fbe79` + `cc8099dc5f`;CLI 侧现由 execa 承担(见 §1 #13)。
- **Windows 目录选择器 CJK 截断族**(U+XX00 低字节为 0):`51c242749a` 重写 readUtf16 → `koffi.decode(..., 'str16')`。覆盖 #643 #1660 #2126 #2227 #3010 #3313。
- **`--expose-internals` HMR 启动失败**:`c685582d54` + `675efe73f2`;loader 回退 `node-addon-require-builtin`。
- **compat.supportsDeveloperRole 可配置**:`884f7b9c41` + `cf4a27c471` + 文档 `30a838cda3`。
- **子代理模型快照 → request-time 选择**:`f76a225a7d`(PR #2663)。
- **pnpm 11 构建失败(npm_execpath)**:`89674edc93`。
- **fs-ext/node-gyp Windows 构建族**:`d927cbff99`(换成预编译 `@deepseek-ai/node-addon-system`)。
- **koffi 32KB 崩溃**:`141d72d7cf`。
- **非 loopback HTTP crypto.randomUUID**:新增 `dsh-util-crypto`。
- **ACP server 已发布**:`dsh --profile acp`。
- **MCP structuredContent 要求条件化**:`e1633fbc3f`(自 0.1.0-rc.7+)。
- **旧 v0→v3 迁移校验**:多处收紧/修复已随 0.1.5-rc 发布(历史会话不可加载先试升级)。
- **轮次导航条(turn rail)load-and-jump**:`b3064cca77` + `6af1ee49b1`。
- **轨迹面板首 token 时间(#6129)**:`e779831f40`(2026-09-14 经 `a85778448a` 进 master)。
- **桌面端打包 payload smoke 引用已移除的 fs-ext(#27)**:`6b05ed53e9`(只进 `dsh-v0.1.6-alpha.2`);本次复核确认 `apps/desktop/scripts/prepare-dsh.ts` 里已无 `checkFsExt()`。
- ✅ **v0→v3 迁移后 stock 投影读取崩溃(#29)——本次新修复**:`f4a32dbd0a`。telemetry 读取被拍平(`session-telemetry/src/coordinator.ts:273` 现在读 `message.isError`,旧代码读 `message.content[0].isError`),并且**迁移产物在 hydrate 前被强校验**(`session-format-v3-to-v4/src/validation.ts:52-129`,经 `session-persistence-jsonl/src/format.ts:468` 的 `assertReleasedV4Relationships` 接入;拒绝路径 `session-format/src/catalog.ts:231-251`)。症状因此从"hydrate 崩溃"变成"**带明确错误的拒绝加载**"。**残留面**已单列为 §1 的 #29(原生 V4 的 `user/message` content 仍不校验)。
- **运行时 invariants 全部移除(本区间新增的"官方移除",不是修复)**:`@deepseek-ai/dsh-invariants`、`ctx.invariants`、`<pkg>/invariant` 子路径、整棵 `packages/runtime-diagnostics`、`sdk-minimal` 的五条相关行全部消失;四个 emitter 从重抛改为记录并继续。若你的旧帖/插件依赖它们,见 [migration-0.2.md](migration-0.2.md) §4。
- **Node 版本门槛**:根 `package.json:8-9` engines `^22.19.0 || >=24.0.0`(本区间未变);实测实际下限 ≈ 24.2(24.0/24.1 有 undefined 报错)。

## 6. 设计行为 / 常见误解(不是 bug,快速对照)

| 现象 | 结论 | 关键位置 |
|---|---|---|
| 本地模型把工具调用输出成 `<DSML\|function_calls>` 文本 | 适配器只解析 `delta.tool_calls`,不解析文本标签;这是服务端职责 | `packages/llm/llm-deepseek/src/translate.ts` |
| 自定义 provider 显示上下文 262k 而非 1M | 262144 是未声明容量时的内置默认假设,可 settings 覆盖 | `packages/llm/llm-pi-ai/src/config.ts:64,330`、`catalog.ts:901` |
| 没填 key 却在扣 DeepSeek 余额 | 默认凭据引用环境变量 `DEEPSEEK_API_KEY`,启动环境只读且优先级最高 | `packages/llm/llm-deepseek-api-key/**`;`packages/credentials/credentials-local/README.md:75-80` |
| web_search 用 deepseek-v4-flash 而非会话模型 | 独立搜索 provider(web-search-deepseek),模型/凭据/端点与聊天分离 | `packages/web/web-search-deepseek/src/provider.ts:38,207-221` |
| `dsh --profile tui` 不存在 | tui 非内置;README 中为示例,社区方案 `dsh plugin --profile tui add github:deepseek-harness/turtle-ui` | `apps/cli/README.md`、`apps/cli/reference/README.md:70-72` |
| `--host 0.0.0.0` 被拒绝 | 刻意不支持(远程代码执行风险);用 LAN IP + `--trusted-host` 或 SSH 隧道;反向代理部署见 `docs/user/guide/public-deployments.md` | `packages/bundle/web-app/src/startup.ts:74-75` |
| 工具定义每轮都发 | 无「每 N 轮」开关;工具集不变时前缀缓存复用。新增 `ToolSchema.deferLoading: true` 可标记延后加载 | `packages/core/agent-loop/src/agent.ts`;`packages/core/tools/src/index.ts` |
| 同级权限请求报错 | 见 §1 #1(同级已接受,只剩更窄/不受支持的目标抛错) | `packages/sandbox/sandbox/src/escalation.ts:171-179` |
| bash/run_code 的 `description` 必填 | 刻意设计(活动列表/UI 展示用);空串另有执行期拒绝 | `packages/shell/tool-bash/src/index.ts`;`packages/core/tools/src/ptc.ts`(#3874) |
| 界面 token 远小于计费 | 界面=主会话 usage 之和;子代理独立会话/日志不计入;无费用熔断功能 | `ui-trajectory/src/client/layout.ts:758,952-955`;`subagent-in-process-driver/src/index.ts:113`(#6688) |
| skill 目录监视挡 Windows 插件更新 | `watch: false` 开关已存在(未文档化);Windows 可用 `watchUsePolling` | `packages/skill/skill-filesystem/src/index.ts:82-83`(#6674) |
| headless 每次运行新会话 | 默认新会话 `session-<uuid>`;`--session-id <id>` 可续跑已存在会话(缺 `sessionPersistence`/`sessionQuery` 会 fail loud);无"继续最近会话"便捷形式;多轮另可走 Connection RPC/gateway | `packages/bundle/headless/src/index.ts:279,342`;`startup.ts:43-45`(#6677) |
| 想"回退到第 N 轮重跑" | 无原位 rewind;fork 生成新会话继承前缀,边界禁落在开启 turn 内 | `packages/core/session/src/index.ts`;`ui-chat/src/client/locale.ts:71-72`(#6652) |
| 自定义 provider 想调思考强度 | 模型条目声明 `reasoningEfforts`(off/minimal/low/medium/high/xhigh/max)→ thinkingLevelMap;仅 llm-pi-ai | `packages/llm/llm-pi-ai/src/catalog.ts:607,690-743`(#1058) |
| 编译报错引用不存在的导出(如 PersistenceCoordinator) | 旧产物与源码混装,非配置问题;彻底重建 | 常量已改名搬家 `session-query/src/config.ts:12`(#5622) |
| 自动压缩"开始"阶段不可见 | 节点模型已按 lifecycle-first(start=compaction/start);仅渲染层被 checkpoint 门控 | `ui-chat/src/client/conversation-nodes/compaction.ts:39-45,52-53`(#6675) |
| 插件想写自己的会话事件 | **不要写**:未知类型要带信封 `ignorable: true` 才被接受,而 `Session.append()` 写不了该标记,写入会让会话打不开。改用既有事件推导或 storage 服务 | `packages/core/session/src/types.ts`(`SessionEvent.ignorable`);官方 `.../cordis-plugin-development/references/practices.md` |

## 7. 相关资源

- 官方汇总帖(社区核实版):<https://github.com/deepseek-ai/deepseek-harness/discussions/6520>
- 本世代破坏性变更的迁移路径:[migration-0.2.md](migration-0.2.md)
- 官方逐版本迁移指南(原文副本,仓库内**无 index、无入链**):`references/official-docs/docs/upgrade-guide/**`
- 官方仓库 checkout 路径约定见 [SKILL.md](../SKILL.md)(本知识库以 `D:\deepseek-harness` 为示例)。
- 每项条目引用的讨论号均可拼为 `https://github.com/deepseek-ai/deepseek-harness/discussions/<编号>` 直接查看原始分析。

---

*维护说明:官方有新提交/新回复时,本文件的「仍未修复」条目可能过时——以原帖与官方答复为准;更新时保留基线提交号与核实日期。*


<!-- source: guide/links.md -->

# [Curated links] guide/links.md

# 文档链接索引(links.md)

> `dsh-plugin-guide` 的 URL 索引:官方开发/参考文档的**线上 URL ↔ 本地副本**对照,加上社区开发文档的常用入口。
> **官方文档 URL 的唯一家**:官方链接在本文件维护,别处只引用本文件;社区链接的完整清单家是 [community-ecosystem.md](../references/community-ecosystem.md),这里只列开发时最常用的入口,避免两份清单漂移。
> 文档站根路由为中文,`en/` 前缀为英文投影;本地副本统一在 `references/official-docs/docs/`,`.zh.md` 为中文对。
> 引用本文件的位置:[SKILL.md](../SKILL.md)、[plugin-dev-guide.md](plugin-dev-guide.md) §1/§9、[quick-reference.md](quick-reference.md)、README。

## 1. 官方开发文档(develop)

站点基址 `https://deepseek-harness.github.io/deepseek-harness`;下表链接为中文根路由,英文页在路径前加 `en/`。

| 主题 | 站点路由 | 本地副本(references/official-docs/docs/) |
|---|---|---|
| 第一个 Harness 插件(入门) | [develop/basic/](https://deepseek-harness.github.io/deepseek-harness/develop/basic/) | user/develop/basic/index.md |
| 开发一个 Tool | [develop/basic/tool](https://deepseek-harness.github.io/deepseek-harness/develop/basic/tool) | user/develop/basic/tool.md |
| 插件配置 | [develop/basic/config](https://deepseek-harness.github.io/deepseek-harness/develop/basic/config) | user/develop/basic/config.md |
| 打包与安装插件 | [develop/basic/publish](https://deepseek-harness.github.io/deepseek-harness/develop/basic/publish) | user/develop/basic/publish.md |
| 插件与生命周期 | [develop/framework/](https://deepseek-harness.github.io/deepseek-harness/develop/framework/) | user/develop/framework/index.md |
| 服务与依赖 | [develop/framework/service](https://deepseek-harness.github.io/deepseek-harness/develop/framework/service) | user/develop/framework/service.md |
| 事件系统 | [develop/framework/events](https://deepseek-harness.github.io/deepseek-harness/develop/framework/events) | user/develop/framework/events.md |
| 能力的三层拆分 | [develop/practice/](https://deepseek-harness.github.io/deepseek-harness/develop/practice/) | user/develop/practice/index.md |
| LLM 适配器 | [develop/practice/llm-adapter](https://deepseek-harness.github.io/deepseek-harness/develop/practice/llm-adapter) | user/develop/practice/llm-adapter.md |
| Cordis 框架教程 01–07 | [develop/cordis-tutorial/](https://deepseek-harness.github.io/deepseek-harness/develop/cordis-tutorial/) | cordis-tutorial/ |

## 2. 官方参考文档(reference)

| 主题 | 站点路由 | 本地副本(references/official-docs/docs/) |
|---|---|---|
| 架构总纲 | [reference/](https://deepseek-harness.github.io/deepseek-harness/reference/) | architecture.md |
| Cordis 入门(5 分钟) | [reference/cordis-primer](https://deepseek-harness.github.io/deepseek-harness/reference/cordis-primer) | cordis-primer.md |
| 能力接缝 | [reference/capability-seams](https://deepseek-harness.github.io/deepseek-harness/reference/capability-seams) | capability-seams.md |
| Agent 生命周期 | [reference/agent-lifecycle](https://deepseek-harness.github.io/deepseek-harness/reference/agent-lifecycle) | agent-lifecycle.md |
| Tool 执行管线 | [reference/tool-execution-pipeline](https://deepseek-harness.github.io/deepseek-harness/reference/tool-execution-pipeline) | tool-execution-pipeline.md |
| 生成式参考(配置/Tool/持久化) | [reference/config-catalog](https://deepseek-harness.github.io/deepseek-harness/reference/config-catalog) · [tool-catalog](https://deepseek-harness.github.io/deepseek-harness/reference/tool-catalog) · [persistence-catalog](https://deepseek-harness.github.io/deepseek-harness/reference/persistence-catalog) | config-catalog.md / tool-catalog.md / persistence-catalog.md |
| Cordis 核心 API | [reference/cordis-api/context](https://deepseek-harness.github.io/deepseek-harness/reference/cordis-api/context) · [events](https://deepseek-harness.github.io/deepseek-harness/reference/cordis-api/events) · [fiber](https://deepseek-harness.github.io/deepseek-harness/reference/cordis-api/fiber) · [registry](https://deepseek-harness.github.io/deepseek-harness/reference/cordis-api/registry) · [service](https://deepseek-harness.github.io/deepseek-harness/reference/cordis-api/service) · [inherited](https://deepseek-harness.github.io/deepseek-harness/reference/cordis-api/inherited) | cordis-api/ |
| 开发手册(Cookbook) | [adding-a-package](https://deepseek-harness.github.io/deepseek-harness/reference/cookbook/adding-a-package) · [adding-a-tool](https://deepseek-harness.github.io/deepseek-harness/reference/cookbook/adding-a-tool) · [adding-an-llm-adapter](https://deepseek-harness.github.io/deepseek-harness/reference/cookbook/adding-an-llm-adapter) · [extension-cookbook](https://deepseek-harness.github.io/deepseek-harness/reference/cookbook/extension-cookbook)(`adding-a-conversation-node` 已在 alpha.3 上游移除) | cookbook/ |
| 子系统生成式服务/事件 API | [reference/subsystems/](https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/)(每个子系统一页:tools、shell、session、web、skills、subagent、settings、otel、product-telemetry、voice-input、claude-code-mods……) | subsystems/ |
| API 网关 | [reference/api-gateway](https://deepseek-harness.github.io/deepseek-harness/reference/api-gateway) | api-gateway.md |
| 公开部署(反向代理) | [guide/public-deployments](https://deepseek-harness.github.io/deepseek-harness/guide/public-deployments)(`--public-url` / `--trusted-host`) | user/guide/public-deployments.md |

## 2.1 未上文档站的仓库内文档(只有 GitHub 直链 + 本地副本)

这些文件在 `references/official-docs/docs/` 里**有全文副本**,但**不在站点路由表**(`website-docs.ts`)里,所以站上找不到——只能读本地副本或 GitHub:

| 主题 | 本地副本 | GitHub 直链 |
|---|---|---|
| 会话格式版本状态(**版本权威**) | `docs/session-format-status.md`(**版本权威**;镜像基线已更新为 `latestReleasedVersion: 4` / `evidenceTag: dsh-v0.2.0-rc.2`) | [blob](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/session-format-status.md) |
| 逐版本迁移指南(**无 index、无入链**,6 篇) | `docs/upgrade-guide/v0.1.7-rc.2/**`、`docs/upgrade-guide/v0.2.0-rc.2/**`(schedule-optional-bundle、transcript-view-legacy-normal、account-sign-in-errors、remove-runtime-invariants、schedule-bundle-retired、subpath-plugin-display-manifest) | [目录](https://github.com/deepseek-ai/deepseek-harness/tree/master/docs/upgrade-guide) |
| 持久化类型变更记录(含 V4 声明) | `docs/persistence-changes/**`(`2026-09-16-session-format-v4.md`、`finalized/v4.json`、`historical-formats/v3.md`…) | [目录](https://github.com/deepseek-ai/deepseek-harness/tree/master/docs/persistence-changes) |
| 圆角规范 | `docs/ui-radius.md` | [blob](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/ui-radius.md) |
| Web 样式 | `docs/web-styling.md` | [blob](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/web-styling.md) |
| 模块图 / 依赖目录 / 持久化 schema | `docs/module-graph.md`、`docs/dependency-catalog.json`、`docs/persistence-schema.json` | [docs 目录](https://github.com/deepseek-ai/deepseek-harness/tree/master/docs) |
| **官方自带插件开发技能**(preset 内,最该读的一份) | 不在 `docs/` 下:`packages/preset/agent-preset/skills/cordis-plugin-development/**`;本库要点摘录见 [../references/official-plugin-dev-skill.md](../references/official-plugin-dev-skill.md) | [目录](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/preset/agent-preset/skills) |

## 3. 官方仓库与直链(GitHub,master 分支)

- 仓库根:https://github.com/deepseek-ai/deepseek-harness · 官网:https://www.deepseek.com/harness/
- 开发红线 [AGENTS.md](https://github.com/deepseek-ai/deepseek-harness/blob/master/AGENTS.md)(本地副本 references/official-docs/AGENTS.md)
- **官方 Discussions(Issues 关闭,反馈主渠道)**:https://github.com/deepseek-ai/deepseek-harness/discussions —— **API 核实 `has_issues=false`(无 issue tracker、不收 PR),Discussions 是唯一渠道**;**2026-10-06 全量刷新归档**于 `downloads/github/harness/discussions/`(list.json 含正文;精选线程含评论;`scripts/archive-discussions.ps1` 可带 token 幂等刷新)——**list=5000、精选 1648**。⚠️ REST 列表最多翻到 5000 条(脚本会显式告警),而仓库实际 total_count≈8850(GraphQL 口径)。仓库元数据(star≈244k、fork≈29k、最后 push 2026-10-03)见 `downloads/github/harness/repo.json`。
- **官方 Releases(唯一权威的"官方决定"记录)**:https://github.com/deepseek-ai/deepseek-harness/releases —— 仓库内**没有 CHANGELOG**,逐版本变更只在 Release 说明里;破坏性变更在正文里以"破坏性变更/Breaking change"标注。本库 `guide/migration-0.2.md` 已对照 `0.1.7-alpha.1` / `0.2.0-rc.2` / `0.2.1-alpha.1` 三份原文明列。
- **重要社区帖(本世代)**:[#1629 RFC:官方插件脚手架](https://github.com/deepseek-ai/deepseek-harness/discussions/1629)、[#6520 未修复问题清单(本库汇总帖)](https://github.com/deepseek-ai/deepseek-harness/discussions/6520)、[#8199 升级后大批插件被判不兼容](https://github.com/deepseek-ai/deepseek-harness/discussions/8199)、[#8633 宿主插件失败在 web 下不可见](https://github.com/deepseek-ai/deepseek-harness/discussions/8633)、[#8968 改 patch 永久废掉 sessionController](https://github.com/deepseek-ai/deepseek-harness/discussions/8968)、[#8590 社区 0.1.5→0.2.0 移植实录](https://github.com/deepseek-ai/deepseek-harness/discussions/8590)、[#8537 插件验收 profile 隔离提案](https://github.com/deepseek-ai/deepseek-harness/discussions/8537)、[#8669 真实插件构建日志(12 评论)](https://github.com/deepseek-ai/deepseek-harness/discussions/8669)。
- **npm 包**:`@deepseek-ai/dsh`(https://www.npmjs.com/package/@deepseek-ai/dsh)等全家桶 72 个包元数据归档于 `downloads/npm/`(README 含全表);`create-dsh-plugin`(https://www.npmjs.com/package/create-dsh-plugin)
- 未上站的仓库内文档(本地副本 references/official-docs/docs/ 同名文件):
  - [docs/event-producer-consumer.md](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/event-producer-consumer.md) — 全事件生产/消费矩阵
  - [docs/defensive-patterns.md](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/defensive-patterns.md) — 防御性模式
  - [docs/glossary.md](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/glossary.md) — 术语表
  - [docs/testing.md](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/testing.md) — 测试政策
- 仓库根文件(本地副本 references/official-docs/):BENCHMARK.md、CLAUDE.md(symlink→AGENTS.md)、CONTRIBUTING.md/.zh.md/.i18n.yaml、README-zh.md/.i18n.yaml、THIRD_PARTY_NOTICES.md、LICENSE
- 上游 Cordis 框架:https://github.com/cordiverse/cordis · Cordis 论文:https://github.com/cordiverse/paper · **Cordis 文档站源:https://github.com/cordiverse/docs(60+ md,本地 `downloads/github/cordis/docs/`)**

## 4. 社区开发文档(常用入口)

完整 114 仓库清单、awesome 列表与归档位置见 [community-ecosystem.md](../references/community-ecosystem.md);逐仓库深读见 [community-repo-deep-dive.md](../references/community-repo-deep-dive.md)(首批 15 个);08-14 晚生态扫描(26 高优文档型 + 19 中优 skill/契约 + 20 低优目录)见 `downloads/_research/github-ecosystem-scan.md`;08-15 第七批(14 个桌面端/桥接/安全/移植仓库)与第八批(3 个主题/市场仓库)见 community-ecosystem.md §4.6/§4.7。

| 仓库 | 用途 |
|---|---|
| [omdsh-dev/plugin-template](https://github.com/omdsh-dev/plugin-template) | 生产级独立插件模板:src 四文件结构 + 7 个开发 skill + 自包含 prepare + 契约文档 |
| [omdsh-dev/dsh-plugin-dev](https://github.com/omdsh-dev/dsh-plugin-dev) | 踩坑档案:20 个实测坑 + 环境基线 |
| [Opr4Mp3r/deepseek-harness-plugin-from-scratch](https://github.com/Opr4Mp3r/deepseek-harness-plugin-from-scratch) | 代码审计式渐进教程:17 反模式 + 交付检查单 |
| [vlln/dsh-skills](https://github.com/vlln/dsh-skills) | ✅ **现行**(`plugin-registry` 于 2026-10-03 归档后的继任者):`make-dsh-plugin` 开发引导 skill + 0.1.6→0.2.0 改名症状表 + 实测踩坑(`gotchas.md`) |
| ~~[vlln/plugin-registry](https://github.com/vlln/plugin-registry)~~ | ⚠️ **已归档(2026-10-03)**,冻结在 0.1.5 基线。仍可引其 repository-plugin 0809→0811 机制时间线,但**不要再当作活项目** |
| [whyihaveyou/dsh-suite](https://github.com/whyihaveyou/dsh-suite) | 双语插件目录 + `npm create dsh-plugin` 脚手架 + 每日兼容性 CI |
| [omdsh-dev/dsh-plugin-check](https://github.com/omdsh-dev/dsh-plugin-check) | 插件健康检查:清单协议 / patch 格式 / 构建陷阱 |
| [omdsh-dev/dsh-plugin-skills](https://github.com/omdsh-dev/dsh-plugin-skills) | agent 会话内搭建/测试插件的 skill 集 |
| [randerous/dsh-turn-meta](https://github.com/randerous/dsh-turn-meta) | 最小首插件模板(agent/pre-step + prepend:true) |
| [flaqai/deepeseek-harness-guide](https://github.com/flaqai/deepeseek-harness-guide) | **15 语言**开发/插件构建指南(最接近官方文档多语翻译) |
| [Electricitysheep/dsh-handbook](https://github.com/Electricitysheep/dsh-handbook) | 从 0 到 1 深度手册(14 章双语 + 2 PDF,174★) |
| [flysheep-ai/learn_deepseek_harness](https://github.com/flysheep-ai/learn_deepseek_harness) | 可运行渐进课程 s01–s23(33 SKILL) |
| [pingfanfan/hello-dsh](https://github.com/pingfanfan/hello-dsh) | 零基础插件开发教程(22 个中文技能实例) |
| [LaplaceYoung/dsh-book-deepseek-harness](https://github.com/LaplaceYoung/dsh-book-deepseek-harness) | 《深入理解 DeepSeek Harness》源码拆解书(42 md + PDF) |
| [curtiseng/cordis-course](https://github.com/curtiseng/cordis-course) | Cordis 论文交互式中文课程与译文 |
| [NanmiCoder/dsh-agent-teams](https://github.com/NanmiCoder/dsh-agent-teams) | 英文插件开发教程 `docs/developing-dsh-plugins.md` + dsh-plugin-development SKILL |
| [openma-ai/deepseek-harness-typescript-sdk](https://github.com/openma-ai/deepseek-harness-typescript-sdk) | TypeScript SDK(类型/DTS 参考);Rust 版 [Loner1024/deepseek-harness-sdk-rs](https://github.com/Loner1024/deepseek-harness-sdk-rs) |
| [anywhere-labs/deepseek-harness-desktop](https://github.com/anywhere-labs/deepseek-harness-desktop) | Electron 桌面端(macOS/Windows 深度适配,08-15 归档);同类 [cc1252](https://github.com/cc1252/deepseek-harness-desktop)、[ChisaAlter](https://github.com/ChisaAlter/Deepseek-Harness-Desktop) 等 |
| [zzszmyf/dsh-security-pocs](https://github.com/zzszmyf/dsh-security-pocs) | 三个漏洞 + 链式利用 PoC(!!js 配置执行/沙箱逃逸),配套官方讨论 #817/#454/#523/#250 |
| [HenryZ838978/deepseek-harness](https://github.com/HenryZ838978/deepseek-harness) | Python 移植(`pip install deepseek-harness` + MCP server + 16 协议怪癖/270+ 试验) |
| [banana770/dsh-qq-bridge](https://github.com/banana770/dsh-qq-bridge) | QQ ↔ Harness 桥接;npm 配套 [dsh-plugin-adapter-qq](https://www.npmjs.com/package/dsh-plugin-adapter-qq) |

## 5. 官方与社区渠道

- Discord:https://discord.gg/Ycq5dCaS4 · GitHub 讨论区:https://github.com/deepseek-ai/deepseek-harness/discussions(归档见 §3)
- **未修复问题清单(社区核实版,2026-09-13)**:官方讨论 [#6520](https://github.com/deepseek-ai/deepseek-harness/discussions/6520)(PerryLink 发布:20+4 项 master 未修复 bug,逐条 path:line + 规避 + 原帖链接);本地结构化对照见 [unfixed-issues.md](unfixed-issues.md)(含「已修复提交号」与「设计行为对照」两节)
- 插件话题页:https://github.com/topics/dsh-plugin(08-15 快照在 `downloads/topic-snapshots/dsh-plugin-topic-2026-08-15/`,位置记录见 [sources.md](../references/sources.md) §D.2)
- awesome 列表(插件发现):见 [community-ecosystem.md](../references/community-ecosystem.md) §5
- **中文社群与媒体**:微信公众号「DeepSeek Harness 团队」(黑鲸头像,08-12 注册)、企微小助手群(官方 README 链接);[V2EX](https://global.v2ex.co/t/1234341) 系列线程(1234203/1234320/1234341/1234424/1234521…);[Bilibili 教程视频](https://www.bilibili.com/video/BV1WmgF6qEMn/)(速通/安装/实战共 9 支,清单见归档 README);知乎提问 [2040450519303288568](https://www.zhihu.com/question/2040450519303288568)(403 反爬,需浏览器)
- **英文社群与媒体**:Hacker News([主发布线程 49285244](https://hn.edgecompute.app/item/49285244) + 5 条子线程,镜像已归档);X/Twitter(官方 @deepseek_ai [发布帖](https://twitter.com/deepseek_ai/status/2087887408440164663),digg 聚合见 [Web UI 截图帖](https://digg.com/tech/7fx4ofvh)/[内测招募帖](https://digg.com/tech/silt5bft));[OpenRouter Ori Harness](https://openrouter.ai/docs/guides/ori/harness)(OpenRouter 的 DSH 集成);[Ollama 官方集成](https://docs.ollama.com/integrations/deepseek-harness)
- 社区文章归档(**149 篇**,`downloads/web/community-articles/`,`scripts/download-community-articles.ps1` 幂等刷新):中文教程/实测(cnblogs、CSDN、51CTO、阿里云、V2EX、locdd、36氪、量子位、B站…)、英文教程(dev.to、AgentPedia、ExplainX、X-CMD、Ollama…)、HN 镜像线程 7 条
- 中文速查 + 踩坑:[V2EX 1234341](https://global.v2ex.co/t/1234341);X-CMD 一键安装:https://www.x-cmd.com/install/deepseek-harness/ ;Ollama 集成:https://docs.ollama.com/integrations/deepseek-harness

## 6. 断链校验

本文件与 README、SKILL.md、guide/*.md 的相对链接由 `scripts/verify-kit.ps1` 扫描;新增链接后运行 `pwsh -File scripts/verify-kit.ps1` 验证。


<!-- source: references/official-plugin-dev-skill.md -->

# [Distilled official in-repo plugin-development skill] references/official-plugin-dev-skill.md

# 官方自带插件开发技能(references/official-plugin-dev-skill.md)

> **这是什么**:DeepSeek Harness 自带的 preset(`packages/preset/agent-preset/`)里有一整套**官方插件开发技能**,是 0.2 世代最重要的新增资料——它描述的正是"在当前 Harness 里、由 agent 自己写并安装插件"的官方流程。
> **本文件性质**:**要点摘录 + 原文定位**(不是逐字全文;逐字全文随官方文档副本一起在 `references/official-docs/` 之外的仓库路径,需 checkout 才能读)。凡写"官方明确"的地方都能在上游文件里逐字查到。
> **基线**:`d743267388641bc76f17c45ce8b4c231aed1d32c`(`dsh-v0.2.1-alpha.2`,2026-10-09)。升级宿主后若与上游冲突,以上游为准。
> **上游路径**(在 checkout 里,如 `D:\deepseek-harness`):`packages/preset/agent-preset/skills/`

## 1. 四个技能与各自职责

| 技能 | 职责 |
|---|---|
| `cordis-plugin-development/SKILL.md` | 主流程:设计/评审/启用/安装/配置/调试插件、bundle、页面、面板、工具、MCP 连接 |
| `cordis-plugin-development/references/{host-plugin,ui-plugin,mcp-bundle,practices,user-actions,verification}.md` | 六份分面参考(见 §3) |
| `cordis-plugin-development/templates/{decoration,mcp}/**` | 可直接复制的 UI 插件模板与 MCP bundle 模板(含 locale 与 icon) |
| `cordis-composition-reference/SKILL.md` | Loader patch 方言 + 可安装插件包清单 |
| `editing-cordis-compositions/SKILL.md` | agent preset 组合的编辑流程 |
| `agent-experience/SKILL.md` | 面向"模型体验"的写作/设计约束 |

## 2. 主流程(`cordis-plugin-development/SKILL.md` 的硬性规定)

**安装方式(官方唯一推荐)**:在工作区用普通文件写 bundle,然后用 **`plugin_manager` 工具的 `install_bundle`**,`target` 传**绝对包目录**装进当前 profile。改动对该 profile 的**所有会话**生效并跨重启保留。

**官方明确禁止**(原文禁止项,逐条):
- 不写 profile 的 `package.json`;
- 不写 profile 的 `cordis.patch.yml`;
- **不在 profile 目录里跑 pnpm**;
- 不在 `$DSH_HOME` 下创建包。

理由:`install_bundle` 会做这些步骤,而"profile 之外的每一次手写都需要单独审批"。另外 `plugin_manager` 的每个动作(**包括 `list_plugins` / `list_bundles`**)在没有 Full access 时也需要审批,所以**只在结果决定下一步时才调**。

**"先交付能跑的插件"五步**:
1. 先定结果与落点。未指定的可视落点=当前 Harness Web UI;独立图片文件或 HTML **不算**完成。先实现一个小的第一版并**先装上**,再做视觉打磨。
2. **只查这一版需要的 API**:先 `cordis_inspect_list`,再有针对性地 `cordis_inspect_query`。UI 类要查 Client 的 `Slots.listSubTree` 与目标 slot 的注册选项/props;超出静态装饰(工具策略、agent 上下文、会话派生状态、Chat 行)要先读 `references/practices.md`;涉及用户动作再读 `references/user-actions.md`。**选定 slot 与注册 API 之后才开始写代码。**
3. 复制匹配的模板文件到一个工作区目录,或直接在工作区写包/patch/必需的 Host·Client 文件。装之前**完成 `references/host-plugin.md` 的展示清单**(本地化标题/描述、原创或有许可的图标、按需导出资源与分发校验)——Host-only 与纯配置 bundle 同样适用。然后检查 JS 语法与清单,安装。**不要先做预览 HTML、mock 壳、design 变体、截图脚本或栅格化工具**。
4. 读安装结果:**`application` 与 `warnings` 决定改动是否生效**——不是日志、进程列表,也不是页面的 boot payload。用**免审批**的 `cordis_inspect_query` 确认新行,而不是 `list_plugins`。`application: applied` 之后,实际用一下能力,或检查实时 Client 注册。
5. 在同一插件里修掉观察到的缺陷;请求的结果能用就收尾,报告位置、验证状态与任何展示资源例外的理由。**不要继续做投机变体、可选功能或 mock 预览。**

**知识来源的优先级(官方顺序)**:
1. **inspection**——`cordis_inspect_query` 回答精确的 Service 方法、Event 模式(`Service`/`Event`)、已挂插件的 Config JSON Schema(`Config.listConfigs`:先按 `name` 过滤分页目录,再查 `entry` id)、本 Agent 可调用的 Tool(`Tool`)、实时 Client Slots 与主题 token(`Slots`/`Theme`)。
2. **包文档**——`Config.listConfigs` 带 `name` 找到该包的 entry,查一个 `entry` 会返回 `packageDir`;读 `<packageDir>/README.md`。bundle 包从 dsh 安装位置解析、profile 安装的 bundle 从 profile 解析,**不要**从 `$DSH_PROFILE_DIR` 猜路径。
3. **源码**——已安装包只带构建后的 `lib/index.js` 与 `lib/types/**/*.d.ts`(含 JSDoc),**没有 `src/`**;源码 checkout 才有 `packages/<group>/<name>/src`。先看 inspection 与 README,再从具体的安装/运行失败出发做源码级诊断。

**环境变量**:profile 启动的 Harness 在每次 shell 调用里都设置 `DSH_PROFILE`(profile 名)与 `DSH_PROFILE_DIR`(其目录,`node_modules` 只含 profile 安装的 bundle);**不以 profile 启动时不设置**。Bash 读 `$DSH_PROFILE`,Windows preset 用的 PowerShell 读 `$env:DSH_PROFILE`。`dsh --profile "$DSH_PROFILE" --dump-config` 打印组合后的 profile。

**Desktop 下的读取限制(官方警告)**:Desktop 里该技能目录位于 `app.asar` 内,**只有 Host 进程自己的文件读取**能打开它;shell 命令(`ls`/`cat`/`cp`/`cmp`)、glob 与搜索工具(走原生 ripgrep 进程)、`node`、pnpm **全部失败**。**永远不要就地安装或语法检查模板**——先把模板内容复制到工作区。

**启用随包发布但默认关闭的插件**:行 id 与原因写在随包 patch(源码 checkout 的 `packages/bundle/*/cordis.patch.yml`)里。写一个工作区 bundle,用 patch 覆盖该行为 `disabled: false`,并把它依赖的 Host 行一起插入;源码 checkout 的 `apps/cli/config/examples/<feature>/cordis.yml` 会列出这些行。**随 dsh 发布的包从 dsh 安装位置解析,所以这种 bundle 不声明对它们的依赖。**

## 3. 六份分面参考的要点

### 3.1 `host-plugin.md` — bundle 与 Host 插件

- 最小 Host-only bundle **不需要依赖、安装脚本或构建步骤**:
  ```json
  { "name": "@local/my-plugin", "version": "1.0.0", "private": true, "type": "module",
    "exports": { ".": "./index.js" }, "dsh": { "bundle": { "patch": "./cordis.patch.yml" } } }
  ```
- **展示元数据与图标清单**(Plugin Manager / Settings 在**不激活**插件的前提下读取):`locale/en.json` 里写 `meta.title`/`meta.description`,并按用户语言补 `locale/zh.json` 等;图标须原创或有再分发许可、保留署名;导出 `./locale/*.json` 与 `./icon`,并把 patch、locale、图标与**每个运行时文件**都放进 `files`。**本地目录安装链接的是 checkout,`files` 不过滤链接目录**——要直接检查文件。图标支持 SVG/PNG/JPEG/WebP,**≤ 256 KiB**,且 realpath 之后必须仍在包内(绝对路径、URL、指向包外的 symlink 都被拒)。**文件名合规不等于字节能渲染。**
- 包根插件也可以在 `package.json` 顶层写 `icon`(优先于 `./icon`,路径相对清单目录)。**子路径插件(如 `my-plugins/search`)不是包,永远不读 `package.json`**——它导出 `./search/locale/*.json` 与 `./search/icon`。
- Host 插件导出形态(**不要混用**):`export function apply(ctx, config) {}` + 可选 `export const inject = ['tools']` + `export const Config`;或者一个 **service class 作为 default export**。所有资源都在 `apply` 里用 `ctx.effect`/`ctx.on` 注册并返回清理函数。声明了 `Config` 的插件会在激活时校验该行的 `config`——**写 config 之前先查 `Config.listConfigs` 拿 schema,并跟进返回文档里的 `$defs` 引用**。
- 安装/启用/观察:`install_bundle` 负责包安装与 bundle 选择,**不要用 shell 复现**;只有用户明确批准了报告出来的 pending build 脚本后才传 `approvedBuilds`。`list_plugins`/`list_bundles` 给出精确标识符,`set_plugin`/`set_bundle` 开关,`remove_bundle` 移除。**分开看"保存状态"与"激活结果"**:`failed` 要诊断,`overridden` 表示更高优先层胜出,`restart-required` 表示**未生效**。新装 bundle 可以经 HMR 激活;**替换已安装包需要重启**才能载入新的 JS 模块代。

### 3.2 `ui-plugin.md` — Web 页里的 UI 插件

- 读 `templates/decoration/` 并把文件写进你的 bundle 目录:它的 `package.json` 在 bundle patch 之外加 **`dsh.client` 段**(`platform`/`immediately`/`inject`)与 **`./client` 导出**。`index.js` 导出 `export function apply() {}`;patch 插入一行,行名即包名。
- 简单绘图优先选**已分配空间**的 slot,如 `conversation.composer.dock`;第一版就留在这个 slot 的流内,**不要**围绕宿主控件规划运动轨迹。只有需要覆盖层且落点已知时才用 `shell.overlay`。
- 浏览器产物注册一个**懒工厂,其 id 等于包名**。React 来自浏览器模块表——**不需要**第二份 React、CDN script 或 UMD 查找。编译型源码用部署自带的 Client 构建工具产出该格式;非基线运行时 import 在 `dsh.client.external` 里声明。
- `templates/decoration/client.js` 通过 `ctx.slots.inject` + `ctx.slots.register` 把懒工厂注册进 `conversation.composer.dock`;换 slot 时按 `Slots.listSubTree` 给出的 props/options 来写。
- **工厂保持无副作用**:样式、定时器、监听器等资源在 `apply` 里用 `ctx.effect`/`ctx.on` 注册并返回清理函数;组件局部样式渲染成 React 元素,卸载即移除。容器与控件继承宿主主题;只有 artwork 可以用自己的配色。**可见文案走 Client locale 服务。不要替换 app root,也不要往 `document.body` 追加第二个应用。不要读别的插件的 DOM、样式表或组件源码去猜位置——选一个已经分配好空间的 slot。**

### 3.3 `mcp-bundle.md` — 接一个 MCP server

- 纯配置 bundle:清单只要有唯一 name、version 与 `dsh.bundle.patch`,**不需要** Host/Client 入口文件。patch 插入**已安装的** `@deepseek-ai/dsh-mcp-client`,配 `serverName`、`transport: streamable-http`、`url`、`failOnStartupError: true`。
- 换掉 endpoint、经 `plugin_manager` 装好,然后调 `mcp__demo__ping` 之类新出现的 `mcp__<serverName>__<tool>` 验证。
- stdio 用 `transport: stdio` + `command` + 可选 `args`/`env`/`cwd`;`Config.listConfigs` 查已装行会返回完整 client schema。**环境凭据会被清洗**:用 Loader 的 `!!js` 引用已有凭据,**不要把密钥粘进对话文本**。失败时**修同一个 bundle,不要造重复的**。

### 3.4 `practices.md` — 插件实践总纲(最值得先读的一份)

**六条原则**
1. **会话日志是唯一真源**:模型看到的一切都必须能从已提交的会话事件重建;fork/resume/replay 都从日志派生。**插件内存只是派生缓存。**
2. **注册是被 ctx 拥有的 effect**:插件卸载、agent 释放、slot 折叠、profile patch 都会撤销对应 ctx 上注册的东西。**要选对拥有者 ctx**;注册在别的 ctx 上(如 `agent.ctx`)的注册有**两个**拥有者——必须把它的 disposer 也留在自己插件的 effect 里,这样任一侧 teardown 都能移除。
3. **框架负责驱动,插件只负责计算**:session projection、Conversation 组装、slot 渲染已经替你订阅、缓存、发布。自己去订阅/重扫/写 DOM 就是绕过这套增量机制。
4. **扩展点是共享的:用最弱的够用机制**。由弱到强:`ctx.tools.restrict()`(只能移除工具)→ `ctx.tools.guard()`(只能拒绝)→ waterfall 监听器(可改写、依赖注册顺序)→ `system-prompt/assemble`(替换整个组装结果)。机制越强,你要替其他插件保留的东西越多。
5. **别的插件、别的 Harness 版本会读你写的数据**:未知事件类型、更老的读方、更老的缓存状态都会遇到你写的东西——按 Harness 提供的信封字段与版本声明兼容性。
6. **插件 UI 就是 Harness UI 的一部分**:用户看到的是一个应用,所以插件要用宿主的主题 token、locale 与布局模式,并匹配宿主组件的外观与行为。**能不能做到由"在哪渲染"决定——先选渲染面再写视图**;后面在错误的面里做样式救不回来。

**稳定性(逐条可执行)**
- 不拥有该决策的 waterfall 监听器(`agent/pre-step`、`agent/request`、`llm/stream`、`tools/pre-execute`、`tools/execute`、`tools/post-execute`)**必须返回 `next()`**。改写 `agent/pre-step` 决策时要**展开**它(`{ ...decision, messages }`),否则 `startsRequestSeries` 之类字段会丢。
- 必须与顺序无关地成立的拒绝用 `ctx.tools.guard()`(同步);需要 await(比如问用户)的决策从 `tools/pre-execute` 返回 `ask`。只对某个 agent 隐藏工具=在那个 agent 的 ctx 上 `ctx.tools.restrict()`(它让 schema 展示、查找、执行保持一致)。**最终结果只看 `tools/result`**;只有要变换结果才用 `tools/post-execute`。**不要监听 `system-prompt/assemble` 来增删工具或文本。**
- 加提示词文本用 `ctx.systemPrompt.section()`。加 per-agent 上下文用 `agent.inject()`——它在调用时记为 `agent/inbox/spliced`,进入下一次获准的 step。**`agent/request` 监听器不能改请求消息。**
- per-agent 行为注册在 `agent.ctx` 上(在 `agent/created` 监听器里拿到),这样该 agent 释放时一起移除。**把它包在一个 `agent.ctx.effect()` 里,并且把这个 disposer 按 agent 记在自己插件的 effect 里**——卸载插件本身**不会**释放 `agent.ctx` 的注册。
- 可选服务放进 `inject` 或 `ctx.inject([...], ...)`,这样在没有它们的 profile 里插件保持不激活,而不是抛错。
- **绝不要用新的 `type` 追加会话事件**:读方只在事件带信封 `ignorable: true` 时接受未知类型,而运行时 `Session.append()` 写不了这个标记,于是**会话会拒绝重新打开**。插件状态从既有事件推导,或放进 inspection 找到的 storage 服务。
- 可调值放进插件的 `Config`,让用户在 `cordis.patch.yml` 里改——**用户的 patch 层能跨升级保留**。

**性能**
- per-session 状态放进 `ctx.sessionProjections` 单元,**不要**订阅 `session/event` 再重扫 `session.events`。`apply(state, event)` 是**纯同步**的,对它忽略的事件**返回同一个引用**,所以未变状态在下游零成本;读取用 `stateOf()`。`view()` 在值未变时返回同一引用,从而抑制发布。
- projection 状态保持**纯 JSON**,字段或 fold 语义变化时 bump `stateVersion`:投影缓存会 checkpoint 它,冷读只回放尾巴,过期 checkpoint 被丢弃。
- **等持久事件**(`turn/end`、`assistant/message`、`tool/result`),实时 token 从 `agent/assistant-stream` 渲染。**不要轮询 `agent/status`。** `whenIdle()` **不**代表"一次 followup 结束了"——多个输入可以共享同一个运行区间。
- 会启动工作的定时器调用 `agent.followup()`(它**唤醒** agent);`agent.inject()` **不唤醒**,所以注入的上下文可能在 inbox 里等到别的输入才被采纳。定时器要在拥有它的 effect 里清除。

**UI**
- 插件页面渲染成 slot 里的 React 组件。**不要**从 Host 提供一个 HTML 页面再 iframe 嵌进去:iframe 文档拿不到宿主主题 token、明暗切换和 `ctx.locale`。
- 用 `cordis_inspect_query` 的 `Theme` 列出的主题 token(`--dsw-alias-*`)做样式;字面颜色只用于 artwork。token 是最低风险的匹配方式:token 改名只会让外观退化,**永远不会弄坏渲染**。间距/字号/行模式照抄同类宿主页面(管理类列表的参照是 Plugin Manager 页)。
- **不要** `require('@deepseek-ai/dsh-client-ui-primitives')` 或把任何 Harness Client 包当模块加载;`dsh.client.inject` 只做激活排序、保持允许即可。它们会不打招呼地变化,纯 JS 插件没有类型检查,而**抛错的组件会把你的 slot 条目整块搞白**(console 里是 `slot entry crashed in '<slot>'`)。**自己写控件并对齐宿主**:从 primitive 里把标记、CSS 与行为抄进插件(DSH 源码 checkout 的 `src/*.tsx` 与 `*.module.css`,或已安装包的 `lib/index.js` 与 `lib/**/*.css`),或在连着的页面里观察渲染出来的宿主控件。抄来的 class 要改到你自己插件的前缀下,只保留 `--dsw-alias-*` token 引用,并保留用户依赖的行为(Modal 焦点与 Escape、`role="switch"` + `aria-checked`、Tooltip 落位)。这样 token 就是唯一的共享样式依赖。
- 只通过 slot 贡献:`ctx.slots.inject(ownerKey, () => ctx.slots.register(...))`——回调里的注册在拥有它的声明折叠时释放、恢复时重新安装。会话数据经 slot props 的 selector hook 读取,订阅**最小切片**。**不要**在组件外写 DOM,也不要往 `document.body` 追加。
- 加 Chat 行:用 `ctx.uiConversation.events.register()` 注册事件定义,并在 `conversation.chat.node` slot 下按该定义的 `kind`(即渲染键)注册视图;分页、Turn/Step 落位与增量组装由 Conversation 层负责。
- Client 需要会话派生值时,在 Host projection 上声明 **`wire.view`**——值算好了再送到 Client,**Client 不自己 fold 会话事件**。

### 3.5 `user-actions.md` — 用户动作与 agent 工具

- **一个操作,两个调用方**:① 实现成一次 Host 服务方法(返回结果或显式状态,失败带原因);② UI 动作经 inspection 找到的 Client 可调 Host 入口(如 `ctx.remote.commands.execute()` 跑的会话命令)调用它并显示失败;③ 用参数与语义一致的 agent 工具暴露同一操作(相关操作可用一个 `action` 参数共用工具),工具调同一个方法并把结果作为工具结果返回。
- **授权/确认类动作仅限用户**(批准工具调用、回答 agent 的提问、放宽策略)。**不要在 UI 与工具两条路径里各维护一份操作逻辑。**
- 验证:双调用方的操作**只用工具**跑一遍,比较状态变化、返回值与失败是否与 UI 动作一致;用户专属动作要验证 agent **无法**执行或授权。

### 3.6 `verification.md`

无浏览器控制时的页面/面板设计核查流程(配合 §2 第 4-5 步使用)。

## 4. 与本知识库的关系

- 本文件是**索引与摘录**;机制细节(精确签名、事件 `@mode`、Config schema)永远以 **inspection** 与 `references/official-docs/docs/**` 为准。
- 官方这份技能覆盖"在会话里让 agent 写并安装插件";本知识库另外覆盖**对外发布**(npm/tarball/git 通道、`prepare` + `allowBuilds`、五语 README、质量门禁)与**版本迁移**([migration-0.2.md](../guide/migration-0.2.md))。
- 上游路径随宿主版本变化(本文件基线 `d743267`);`preset/agent-presets` → `preset/agent-preset` 的改名发生在 `d1e22a7e24`。


<!-- source: references/dev-resource-landscape.md -->

# [Competing and adjacent plugin-development resources] references/dev-resource-landscape.md

# DSH「插件开发」类资源全景与同类对照(dev-resource-landscape.md)

> **本文是什么**:把围着「**怎么开发 DSH 插件 / 怎么学 DSH**」这件事的同类项目梳理成一张图,
> 说清 `dsh-plugin-guide` 与它们各自占哪个格子、谁真有使用量、谁只是存在。
>
> **测量时间**:2026-10-13。GitHub 数字取 `api.github.com/repos/<owner>/<repo>`,npm 数字取
> `api.npmjs.org/downloads/point/last-week/`(窗口 2026-10-05 → 2026-10-11)。
> 凡未实测的格子一律写 `未测`,不填推测值。
>
> **口径提醒**:`★` 是 GitHub 星数,`npm/wk` 是 npm 周下载。**两者不可互换**——
> 文档型项目靠星数传播,可安装插件靠 npm 传播;本生态里同一个项目常常只有其中一个是真的。

---

## 0. 一句话结论

`dsh-plugin-guide` 占的格子是「**给 agent 读的插件开发知识库 + 机检 CLI**」。
这一格**有 7 个同类**(见 §2),但**没有体量相当的对手**:第二名 `Pasumao/dsh-plugin-dev-kb`
4★、其余全在 0–1★,而它是 **50★ / 2,520 npm 周下载**——同一设计意图,差一个数量级。

**真正的竞争不在这个格子里,而在它外面两格**:

| 格子 | 代表 | 规模 | 对 `dsh-plugin-guide` 的含义 |
|---|---|---|---|
| 入门手册(面向人) | `Electricitysheep/dsh-handbook` | **863★** | 它吃掉了「第一次学 DSH」的人群,`dsh-plugin-guide` 不该去抢 |
| 生态目录(面向人) | `awesome-dsh-plugin` 18,588★、`imsai-sh` 目录 282★ | 千–万级★ | 它们吃掉了「找插件」的流量,而这是发现插件作者的主入口 |
| **插件开发知识库(面向 agent)** | **`dsh-plugin-guide` 50★ / 2,520 npm-wk** | **本格第一** | 护城河是「给 agent 用 + 有 CLI 能验」,不是「文档多」 |

---

## 1. 判同维度(什么样的项目算「和 dsh-plugin-guide 相类似」)

不能只按名字里的 `guide` 找。按**交付形态**分,相似度分四档:

| 档位 | 判据 | 与 `dsh-plugin-guide` 的重合度 |
|---|---|---|
| **A 同类** | 插件开发知识库,**装载成 agent skill**,agent 自己查 | 高——同一个产品定义 |
| **B 同模式** | 打包成 agent skill 的「开发方法论」,但主题不同(写 skill、发布、测试) | 中——同一交付机制,互补 |
| **C 同人群** | 面向插件作者的工具链:脚手架 / 静态检查 / 沙箱冒烟 / 评分 / 迁移 | 中——它查资料,这些做事 |
| **D 邻接** | 面向人的教程 / 手册 / 目录 / 市场 | 低——抢的是同一批「新来的人」,不抢同一功能 |

---

## 2. A 档:同类(插件开发知识库,给 agent 用)

| 项目 | 交付形态 | ★ | 最近推送 | 与 `dsh-plugin-guide` 的差异 |
|---|---|---:|---|---|
| **[PerryLink/dsh-plugin-guide](https://github.com/PerryLink/dsh-plugin-guide)** | skill + 官方文档全文副本 + 10 章指南 + `dsh-plugin-dev` CLI(new/check/verify) | **50** | 2026-10-12 | **本格基线**。唯一同时有「知识库」和「机检 CLI」的 |
| [Pasumao/dsh-plugin-dev-kb](https://github.com/Pasumao/dsh-plugin-dev-kb) | skill:官方文档离线镜像(站点中英 168 页 + 仓库补充 52 篇)+ 主题导航 + 全文检索 | 4 | 2026-10-09 | **最直接的同类**。差异:只有检索,没有开发指南正文、没有踩坑档案、没有 CLI |
| [Wenaixi/dsh-plugin-dev](https://github.com/Wenaixi/dsh-plugin-dev) | 中文「权威规范 + 开发者 skill」纯文本规范,两条不可谈判规则(源优先、隔离实例验收) | 1 | **2026-10-11** | **在版本上最新**:明确写 DSH `0.2.0-rc.2`。无 CLI、无文档副本 |
| [RayYeung1989/dsh-plugin-development](https://github.com/RayYeung1989/dsh-plugin-development) | 与 agent 无关的通用 `SKILL.md` + `references/` + `templates/{server,client,dual-half}-plugin` | 1 | 2026-08-14 | 停在 `0.1.0-rc.6`,已过两代 |
| [lansi-ai/dsh-plugin-starter](https://github.com/lansi-ai/dsh-plugin-starter) | 可加载 `SKILL.md` + 双半(Host+Client)插件模板 | 1 | 2026-08-25 | 更偏模板而非知识库 |
| [gezi-wen/dsh-plugin-development](https://github.com/gezi-wen/dsh-plugin-development) | skill:怎么开发 / 调试 / 发布(host+client 双半包骨架、丢弃式 profile、发 npm 的坑) | 0 | 2026-09-20 | 同一产品定义,但停在 9 月、无 CLI、无文档副本 |
| [w2112515/dsh-plugin-development](https://github.com/w2112515/dsh-plugin-development) | 可移植的插件开发 / 审计 agent skill(`未测`) | 未测 | 未测 | 同类但更薄 |

**读法**:A 档一共 7 个,全是「把插件开发资料打包给 agent」这一件事的不同完成度。
`dsh-plugin-guide` 的 50★ 在本档是压倒性的(第二名 4★),npm 周下载 **2,520** 已超过
`dsh-plugin-doctor`(2,469),说明它是靠**真实安装**而不是靠 star 在跑的。

> ⚠️ **本档唯一需要盯的动向**:`Wenaixi/dsh-plugin-dev`(2026-10-11 推送)明确对齐
> DSH `0.2.0-rc.2`,而 `dsh-plugin-guide` 的基线是 `0.2.1-alpha.1`(见根 README 兼容表)。
> 版本对齐是这一档的**唯一硬指标**——DSH 是 developer preview,协议会破。
> `RayYeung1989`(0.1.0-rc.6)和 `lansi-ai`(8 月)就是没跟上的例子。

---

## 3. B 档:同模式(打包成 skill 的开发方法论,主题相邻)

这几个和 `dsh-plugin-guide` 是**互补而非竞争**——同一个坑,不同侧面。

| 项目 | skill 讲什么 | ★ | npm/wk | 最近推送 |
|---|---|---:|---:|---|
| [omdsh-dev/dsh-plugin-skills](https://github.com/omdsh-dev/dsh-plugin-skills) | 从脚手架到分层测试的 skill 集 | 13 | 50 | 2026-08-11 |
| [gezi-wen/dsh-skill-authoring](https://github.com/gezi-wen/dsh-skill-authoring) | **怎么写 DSH skill**:frontmatter、description 触发、篇幅拆分、A/B 验证 | 1 | 332 | 2026-09-20 |
| [vlln/plugin-registry](https://github.com/vlln/plugin-registry) | `make-dsh-plugin` 引导 skill + 薄控制台;**repository-plugin 0809→0811 机制时间线的权威记录**。⚠️ **2026-10-03 已 archive**,skill 迁到 [vlln/dsh-skills](https://github.com/vlln/dsh-skills) | 58 | 31 | 2026-10-03(归档) |
| [omdsh-dev/dsh-plugin-check](https://github.com/omdsh-dev/dsh-plugin-check) | 插件健康检查:**33 项**清单协议 / patch 格式 / 构建陷阱 / hub 收录状态,注册 `plugin_check` 工具 | **25** | 52 | 2026-09-10 |
| [wingsky-1/dsh-plugin-hub](https://github.com/wingsky-1/dsh-plugin-hub) · `dsh-verify-isolated` | 隔离环境浏览器验证 skill(临时 DSH_HOME、独立 profile、raw-CDP 驱动) | 未测 | 368 | 未测 |

> `vlln/plugin-registry` 的 58★ 是 B 档最高,值得单独注意:它记录的是**机制演变**
> (`.dsh-plugin` 格式 0809 上线、0811 移除),这类「历史事实源」是 `dsh-plugin-guide`
> `guide/plugin-dev-guide.md` 已经引用并依赖的东西。**它现在归档了——即本档唯一的知识资产
> 停更,`make-dsh-plugin` 的维护责任转移到了 `vlln/dsh-skills`。**
> `dsh-plugin-guide` 里凡是引用 `plugin-registry` 的地方,都应改指 `vlln/dsh-skills`
> 或标注「已归档,冻结在 0.1.5 基线」。

---

## 4. C 档:同人群(插件作者工具链)

面向的是同一个用户(插件作者),做的是 `dsh-plugin-guide` 里 `dsh-plugin-dev` CLI 之外的事。
**PerryLink 自家在这个档位有 7 个仓库**——这是 `dsh-plugin-guide` 真正的护城河所在。

### 4.1 PerryLink 家族工具链(同作者,天然互操作)

| 项目 | 干什么 | ★ | npm/wk |
|---|---|---:|---:|
| [PerryLink/dsh-plugin-doctor](https://github.com/PerryLink/dsh-plugin-doctor) | 零依赖静态 + 沙箱冒烟检测器:结构 R / Cordis 契约 K / 无密钥 headless D / 目录收录 CC | 4 | **2,469** |
| [PerryLink/dsh-test-drive](https://github.com/PerryLink/dsh-test-drive) | 一次性 profile 里做安装 + 冒烟,产出 pass/fail 矩阵 | 12 | 872 |
| [PerryLink/dsh-score](https://github.com/PerryLink/dsh-score) | 五维质量评分 + 排行榜(消费 test-drive 的结果) | 12 | 1,125 |
| [PerryLink/dsh-plugin-kit](https://github.com/PerryLink/dsh-plugin-kit) | 共享零依赖工具箱:Provider seam、审批闸、新插件骨架 | 1 | 907 |
| [PerryLink/dsh-plugin-upgrade](https://github.com/PerryLink/dsh-plugin-upgrade) | 插件作者升级 skill:按 peer band 路由到对应走廊(legAB / legC) | 2 | 914 |
| [PerryLink/dsh-plugin-certification](https://github.com/PerryLink/dsh-plugin-certification) | 社区认证规范 + 五维机检评分 + 注册表(数据仓库,不可安装) | 0 | 未发布 |
| [PerryLink/dsh-cert-mcp](https://github.com/PerryLink/dsh-cert-mcp) | 只读 MCP server,把认证注册表暴露给任意 agent(`dsh-cert-mcp` 1,160 npm/wk) | 0 | 1,160 |

### 4.2 第三方同档工具

| 项目 | 干什么 | ★ | npm/wk |
|---|---|---:|---:|
| [BiBoyang/dsh-eval-harness](https://github.com/BiBoyang/dsh-eval-harness) | YAML 用例驱动真实 agent 回归评测 + baseline PASS/WARN/FAIL 门禁 + F0–F5 混沌包。⚠️ README 声明 **npm 渠道已弃用,改为 GitHub-only**(npm 上最新 0.3.1 / 2026-08-24) | 14 | 24(已弃用渠道) |
| [MicroMilo/upstream-radar](https://github.com/MicroMilo/upstream-radar) | 常驻**兼容性**测试与依赖监控:盯精确版本、在一次性 CI 里装并跑、产出 `compatible/incompatible/unknown/blocked` 报告(npm 关键词含 supply-chain,但主职是兼容性不是安全扫描) | 11 | 411 |
| [goatliamia/dsh-plugin-maker](https://github.com/goatliamia/dsh-plugin-maker) | 骨架生成 + 契约检查 + 插件体检 + 影响面分析 + 上游兼容追踪 | 2 | 未发布 |
| [CMSKL/dsh-plugin-observatory](https://github.com/CMSKL/dsh-plugin-observatory) | 本地清单 / bundle patch 的有界静态兼容审计(`plugin_audit`)+ 进程内 Loader 生命周期观察(`plugin_observe`)。仅覆盖到 DSH 0.1.0-rc.6,**可能已停更** | 1 | 133 |
| [Jesse-njx/dsh-plugin-manager](https://github.com/Jesse-njx/dsh-plugin-manager) | `dsh pm`:多源搜索(awesome + GitHub + npm)、按 profile 装卸更新、只读 `dsh pm doctor` 审计。⚠️ `@dsh-pm/cli` **从未发布到 npm**,仓库自 2026-08-28 无推送 | 0 | 186(非本包) |
| [f-infinite-z/dsh-plugin-ops](https://github.com/f-infinite-z/dsh-plugin-ops) | 启动前健康闸 + 故障归因 + 白名单可逆修复 + 插件行管理面板 | 0 | 未测 |

**读法**:C 档的 `npm/wk` 明显比 ★ 更"真"。
`dsh-plugin-doctor` 4★却有 2,469 周下载,`dsh-cert-mcp` 0★却有 1,160——
**这一档是靠命令被跑起来的,不是靠 star 被看见的。**

---

## 5. D 档:邻接(面向人的教程 / 手册 / 目录)

这一档**不与 `dsh-plugin-guide` 争功能,但与它争"新来的人"**。

### 5.1 手册 / 教程(人读)

| 项目 | 定位 | ★ | npm/wk | 最近推送 |
|---|---|---:|---:|---|
| [Electricitysheep/dsh-handbook](https://github.com/Electricitysheep/dsh-handbook) | 从 0 到 1 深度手册:安装/插件开发/性能调优/实测案例 + 中英 PDF —— **本档体量第一** | **863** | 263 | 2026-10-04 |
| [sandbaseai/deepseek-harness-handbook](https://github.com/sandbaseai/deepseek-harness-handbook) | Agent-first 多语手册:173 篇有源码出处的运行时/插件/MCP/沙箱指南 | **235** | 未发布 | 2026-08-31 |
| [bugmaker2/dsh-plugin-template](https://github.com/bugmaker2/dsh-plugin-template) | 最小 Hello World 插件模板 | **110** | 未测 | 2026-08-27 |
| [pingfanfan/hello-dsh](https://github.com/pingfanfan/hello-dsh) | 零基础插件开发教程(22 个中文技能实例) | **98** | 未发布 | 2026-09-14 |
| [whyihaveyou/dsh-suite](https://github.com/whyihaveyou/dsh-suite) | 活插件目录(每小时刷新、每日兼容测试)+ `npm create dsh-plugin` 脚手架 | 59 | 71(create-dsh-plugin) | 2026-10-13 |
| [omdsh-dev/plugin-template](https://github.com/omdsh-dev/plugin-template) | 基于 turtle-ui 官方仓库的插件模板 | 13 | 未测 | 2026-09-16 |
| [BiBoyang/dsh-eval-harness](https://github.com/BiBoyang/dsh-eval-harness) | 见 C 档 | 14 | 24 | 2026-10-04 |
| [flaqai/deepeseek-harness-guide](https://github.com/flaqai/deepeseek-harness-guide) | 15 语言开发与插件构建指南(53 md + 4 SKILL) | 14 | 未测 | 2026-08-14 |
| [Hubert-hwk/dsh-for-humans](https://github.com/Hubert-hwk/dsh-for-humans) | 费曼学习法通俗教程(12 章 + 图解 + 测验) | 7 | 未测 | 2026-08-14 |
| [kitimzhe/dsh-plugin-authoring-guide](https://github.com/kittimzhe/dsh-plugin-authoring-guide) | 手把手:从 0 到 1 造一个插件(双语 + 两个已发布插件复盘) | 0 | 未发布 | 2026-09-07 |
| [LaplaceYoung/dsh-book-deepseek-harness](https://github.com/LaplaceYoung/dsh-book-deepseek-harness) | 《深入理解 DeepSeek Harness》源码级拆解书(37 章 + PDF) | 20 | 未测 | 2026-08-14 |
| [AgriciDaniel/deepseek-harness-brain](https://github.com/AgriciDaniel/deepseek-harness-brain) | 证据闸门式「第二大脑」可视化指南 | 11 | 未测 | 2026-08-21 |

同档还有大量 1–3★ 的项目(`anweat/dsh-plugin-dev-guide` 3★、
`whyihaveyou/dsh-plugin-tutorial` 2★、`DumplingHuman/dsh-plugin-tutorial` 2★、
`Opr4Mp3r/deepseek-harness-plugin-from-scratch` 1★、
`dnegxuantian/deepseek-harness-book` 2★、`flysheep-ai/learn_deepseek_harness` 6★、
`SIMON-WORLD/dsh-toolkit` 0★、`863683348/dsh-starter-zh` 0★),
完整清单见本目录 [`community-ecosystem.md`](community-ecosystem.md) §4.2。

### 5.2 目录 / 市场(找插件)

| 项目 | 定位 | ★ | 备注 |
|---|---|---:|---|
| [awesome-dsh-plugin/awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin) | **生态正典清单** + 网站(每插件页含 star 与安装命令),CC0-1.0 | **18,588** | 2026-10-13 推送。**这是 D 档真正的流量入口** |
| [0xsline/awesome-deepseek-harness](https://github.com/0xsline/awesome-deepseek-harness) | 第二份精选清单(插件 / 工具 / 基建) | **1,174** | 2026-10-10 推送 |
| [imsai-sh/awesome-deepseek-harness-plugins](https://github.com/imsai-sh/awesome-deepseek-harness-plugins) | 1024Store:11,000+ 插件的目录 + 市场 + 查询 API | **282** | 本地归档在 `awesome-deepseek-harness-plugins/`(648 条 JSON 目录) |
| [awesome-deepseekharness/awesome-deepseek-harness](https://github.com/awesome-deepseekharness/awesome-deepseek-harness) | 精选 awesome 清单 | 21 | 本地归档 `awesome-deepseek-harness-awe/` |
| `awesome-dsh-plugins-kejixiaoliang` | 306+ 插件、14 个手编分类、双语 | — | 本地归档,见 `docs/taxonomy.md` |
| [ylwl1997/dshbase](https://github.com/ylwl1997/dshbase) → dshbase.com | 社区指南 + 插件目录,标注「干净 profile 装得上且能启动」的实测结果 | 1 | 站长 youliqn,声明与 DeepSeek 无关;"122 个插件实测"是自述 |
| `dshplugin.online` / `dsh.market` / `www.dsh-marketplace.com` | 纯网站目录 | — | 无对应主仓库,收录标准自述 |

---

## 6. 位置图:`dsh-plugin-guide` 站在哪

```
                        面向人  ←──────────────────────→  面向 agent
                          │                                  │
  「学 DSH」             │  dsh-handbook          863★      │
                          │  deepseek-harness-handbook 235★  │
                          │  dsh-for-humans          7★      │
                          │                                  │
  「写插件」             │  hello-dsh              98★      │ ★ dsh-plugin-guide  50★
                          │  anweat/dev-guide        3★      │     2,520 npm/wk
                          │  authoring-guide         0★      │   ├ dsh-plugin-dev-kb  4★
                          │                                  │   └ 其余 5 个        0–1★
                          │                                  │
  「验插件」             │                                  │ dsh-plugin-doctor 2,469 npm/wk
                          │                                  │ dsh-score 1,125 · dsh-test-drive 872
                          │                                  │ dsh-eval-harness         24 npm/wk
                          │                                  │
  「找插件」             │  awesome-dsh-plugin  18,588★     │ dsh-find-plugin   4,168 npm/wk
                          │  0xsline/awesome     1,174★      │   (183★,同组织下)
                          │  dsh-suite 59★ · imsai-sh 282★   │
                          │  dshbase / dshplugin.online      │
```

**四个可读出的结论**:

1. **右上格(面向 agent 的写插件知识库)只有它一个站住了。** 50★ 在绝对量上很小,
   但在这一格里是第二名的 12 倍,且是唯一带 CLI 的。
2. **周下载比 star 诚实。** `dsh-plugin-guide` 2,520 npm/wk 是 `dsh-handbook`(263)的近 10 倍,
   尽管后者 star 是它的 17 倍——**手册被人 star,知识库被人安装。**
   这两个数字测的是两件不同的事,不该放在同一张榜上比。
3. **`dsh-find-plugin`(4,168 npm/wk,183★)比 `dsh-plugin-guide` 更高**,且这一生态里
   「找插件」的流量一直大于「写插件」的流量。**插件作者是 `dsh-plugin-guide` 的用户,
   而找到这些作者要靠目录**——这是它目前最明显的分发缺口。
4. **D 档的量级和 A 档差三个数量级**(18,588★ vs 50★)。这不是质量问题,
   是**受众数量**问题:读手册的人比写插件的人多两个数量级。
   拿 A 档的 star 去比 D 档没有意义——**要比的是同一格里的相对位置。**

---

## 7. 与 `dsh-plugin-guide` 的差异点(护城河清单)

把上面 A/B/C 三档逐项对照,`dsh-plugin-guide` **独有**的能力:

| 能力 | 同类有吗 | 说明 |
|---|---|---|
| 官方文档**逐字节副本** + 完整性校验 | `dsh-plugin-dev-kb` 有镜像,但无校验脚本 | 见 `references/official-docs/`、`scripts/` |
| 10 章**开发指南正文**(心智模型 → 契约 → 打包 → 发布) | 无。同类只做检索,不做成文 | `guide/plugin-dev-guide.md` |
| **踩坑档案**(30+ 条,含根因与修复) | `omdsh-dev/dsh-plugin-dev` 有 20 条,但已停更 | `guide/unfixed-issues.md` |
| **迁移指南**(0.1.6 → 0.2.1 全部破坏性变更 + 12 项清单) | `dsh-plugin-upgrade` 覆盖升级走廊,但不含文档层对照 | `guide/migration-0.2.md` |
| **机检 CLI**(`dsh-plugin-dev new/check/verify`) | `dsh-plugin-doctor` 更强,但它是**独立工具**不是知识库的一部分 | `bin/dsh-plugin-dev.js` |
| **按需加载**(skill + 目录 resourceBase,渐进披露) | 同类多数是把全文塞进 skill | `SKILL.md` |
| 多语言 README(en/zh/es/pt/hi)+ `llms.txt` | `flaqai` 有 15 语言,但只是 README 层 | 对 agent 消费友好 |

**反过来,它明显缺的两块**:

- **没有面向人的入口页**。`dsh-handbook` 863★ 靠的是"给新手的 PDF + 在线站"。
  `dsh-plugin-guide` 有站点(`perrylink.github.io/dsh-plugin-guide`)但定位是"章节页",
  不是"第一次来该看什么"。
- **没有进入主流目录的显眼位置**。在本地 648 条档案目录里它被归到 `skill` 类
  (`catalog/plugins/perrylink--dsh-plugin-guide.json`,2026-09-13 收录),
  而 D 档的目录项目 star 是它的 5 倍以上。

---

## 8. 复现方式

```powershell
# GitHub 星数与最近推送
Invoke-RestMethod -Headers @{'User-Agent'='x'} https://api.github.com/repos/PerryLink/dsh-plugin-guide |
  Select-Object stargazers_count, pushed_at, archived

# npm 周下载(窗口由 end 决定)
Invoke-RestMethod https://api.npmjs.org/downloads/point/last-week/dsh-plugin-guide
```

**本次测量的原始数据**:
- GitHub 元数据:`D:\Projects\_archive\_devkb-gh-metadata.csv`(46 仓)
  与 `_devkb-gh-metadata-2.csv`(20 仓)
- npm 下载:本文表格内联,窗口 2026-10-05 → 2026-10-11
- 生态对话档案:`downloads/github/harness/discussions/list.json`(官方 Discussions 归档)

## 9. 官方面(比对基准,不是同类)

同类对照必须先把**官方自己**那一格说清——它是所有人的上游,也定义了「什么算插件开发资料」。

| 官方资产 | 内容 | 出处 |
|---|---|---|
| 开发文档树 `docs/user/develop/` | 只有 `basic/`、`framework/`、`practice/` 三支;**没有 `develop/index.md`**(该 URL 404) | GitHub contents API |
| `docs/user/develop/basic/` | `index`《Your first plugin》、`tool`《Build a tool》、`config`《Plugin configuration》、`publish`《打包与安装》 | 逐一核验 |
| `docs/user/develop/framework/service.md` | 服务与依赖 | — |
| `docs/cordis-tutorial/` | 7 章:first-plugin / lifecycle-and-effects / services / events / config / composition-and-hmr / into-the-harness;另引 `docs/cordis-primer.md` | 索引页已取 |
| 内置 skill `cordis-plugin-development` | 位于 `packages/preset/agent-preset/skills/cordis-plugin-development/SKILL.md`,附 `references/{host-plugin,ui-plugin,mcp-bundle,verification,practices,user-actions}.md` 与 `templates/{decoration,mcp}/`;同级 skill 还有 `cordis-composition-reference`、`editing-cordis-compositions` | 已取全文 |
| 官方 npm 版本锚点 | `@deepseek-ai/dsh` latest = **0.2.0-rc.2**(2026-09-29 发布) | npm registry |

> **这就是 `dsh-plugin-guide` 的存在理由**:官方只有 4 篇 basic + 7 章 Cordis 教程,
> 没有迁移指南、没有踩坑档案、没有可执行的检查器,且**文档站上不出现的仓库内页面
> (如 `publish.zh.md`、persistence 历史格式)只能靠镜像拿到**。
> 对照见 `references/official-plugin-dev-skill.md` 与 `references/official-docs/`。

---

## 10. 未测 / 存疑项(不要当成已知)

**A. 本轮**(GitHub API 限流 403,未取到星数):
`yanhua1010/dsh-harness-tutorial`、`dshworks/howto-dsh`、`omdsh-dev/dsh-plugin-dev`、
`Nagi-ovo/dsh-find-plugins`、`curtiseng/cordis-course`、`qomob/DSHwiki`、
`JingHao-Leon/deepseek-harness-guide`、`bobleer/deepseek-harness-plugin-mcp` 等 12 仓。
**本文标 `未测` 的都属此类。**

**B. 单源数据**:星数主要来自 `ungh.cc`(2026-10-13),仅 `dsh-plugin-doctor` 用 GitHub API 复核;
`deepseek-harness` 的 248,670★ 未做第二源交叉验证,当近似值看。

**C. 自述未复核**:
- `dshbase.com` 的「122 个插件实测」是自述,未见目录条目逐条复核。
- `SIMON-WORLD/dsh-toolkit`(0★)自述「基于 173 条痛点调研」的中文知识库,
  仓库无描述、未见更新,未深读。
- `wingsky-1/dsh-plugin-hub`、`f-infinite-z/dsh-plugin-ops` 只有片段信息,未整仓核对。

**D. 上游仓库自身的 README 不可信处**(引用前必查):
- `dsh-plugin-doctor` README 戳的是 `0.4.6`(2026-10-04),npm latest 已是 **0.7.0**(2026-10-10),
  其 `npx` 示例还写着 `@0.5.0`。**该仓库的版本号一律以 npm 为准。**
- `dsh-handbook` README 写「280+ Stars」,实测 **863**——README 陈旧,计数为准。
- PerryLink 家族花名册自相矛盾:doctor README 说 42 / 34 活跃,
  certification README 说 94 / 86 活跃 / 1 已认证。**两处都是取到的原文,它们互相冲突。**
- `omdsh-dev/dsh-plugin-check` 的 npm 发布**未证实**,README 只给 `github:` 装法。
- `ret-c1` 与 `retc1`:GitHub 元数据规范化为 `retc1/harness-plugin-tutorial`,`ret-c1` 靠重定向解析。

**E. 已确认失效的引用**:`vlln/plugin-registry` 于 **2026-10-03 归档**。
本项目 `guide/plugin-dev-guide.md`、`references/community-ecosystem.md` 中引用它的地方
需改指 `vlln/dsh-skills`,否则会指向一个冻结在 0.1.5 基线的归档仓库。


<!-- source: references/harness-repo.md -->

# [Harness repository reference] references/harness-repo.md

# DeepSeek Harness 仓库文档调研

> 调研对象:本地完整 checkout `D:\deepseek-harness`(只读)+ 公开仓库 `https://github.com/deepseek-ai/deepseek-harness`。
> 结论来源以本地仓库文档为准;每条事实标注具体文件路径或 URL。本地 checkout 未覆盖或无法核实的内容标注 `[unverified]`。

> ⚠️ **基线声明(2026-10-13 追加,读本文件前必看)**
>
> 本文件是**某一时点的仓库调研快照**,**不是现行 API 手册**。其中的机制可能已被上游移除,
> 而原文不会自动更新。**引用任何 API/配置键之前,先到 `references/official-docs/`(逐字镜像)核对现行版本**;
> 冲突时以镜像与官方代码为准。
>
> **已知本文件内含已被移除的机制**(保留原文以便追溯,但**不得照抄**):
>
> | 位置 | 文中表述 | 现行状态 |
> |---|---|---|
> | §(`agent.inject` 一条) | `source: { kind: 'plugin', plugin: '<name>' }` | ❌ **已退役**。会话格式 V4 在消息准入处拒绝该形态;改用 `declare module '@deepseek-ai/dsh-llm'` 声明自有 kind |
> | §(`ctx.tools` 一条) | config `mode: native \| code \| both` | ❌ **`both` 已被拒绝**(官方 `upgrade-guide/v0.2.1-alpha.1/tool-presentation-mode`);只接受 `native` / `ptc` |
> | §(包不变式一条) | 每个包拥有 `./invariant`,由 `verify-package-invariants` 强制 | ❌ **运行时 invariants 已整体移除**(官方 `upgrade-guide/v0.2.0-rc.2/remove-runtime-invariants`),`./invariant` 子路径不复存在 |
>
> 见 [`guide/version-matrix.md`](../guide/version-matrix.md) §3 与 [`guide/migration-0.2.md`](../guide/migration-0.2.md) §4/§13。

DeepSeek Harness(DSH)是一个**基于 vendored Cordis 框架的插件式 agent harness**,公开宣传语为 **"Everything is a Plugin"(一切都是插件)**(见 GitHub 仓库标题 `deepseek-ai/deepseek-harness: DeepSeek Harness: Everything is a Plugin.`)。模型适配器、工具注册表、会话日志、甚至 agent loop 本身都是插件,因此每一部分都能从配置替换。

---

## 1. 整体架构:一切皆插件

### 1.1 核心主张

`docs/architecture.md` §Cordis 开门见山:

> [Cordis](official-docs/docs/cordis-primer.md) is the framework under dsh: plugins contribute services, typed events, and reversible effects to a shared context. Every part of the product is a plugin, including the model adapter, the tool registry, the session log, and the agent loop itself, so every part is replaceable from configuration.

> There is no privileged core to patch: you extend dsh by mounting a plugin beside the others, and registrations are effects that unwind when their plugin unloads.

来源:`D:\deepseek-harness\docs\architecture.md`(L11–L13)。

### 1.2 框架层:vendored Cordis

Cordis 框架以**源码 vendored(拷贝)**方式进入 monorepo,而非 npm 依赖,目的是让 harness 完全拥有自己的框架层(可审计、可补丁、可固定版本)。所有 vendored 包被重命名为 `@deepseek-ai` scope(`cordis` → `@deepseek-ai/cordis`,`@cordisjs/plugin-<x>` → `@deepseek-ai/cordis-plugin-<x>`)。

来源:`D:\deepseek-harness\vendor\README.md`(L1–L7)。

vendored 清单(`vendor/README.md` §Manifest,L13–L24):

| 目录 | npm 名 | 上游名 | 版本 | 上游仓库 |
|---|---|---|---|---|
| `cosmokit/` | `@deepseek-ai/cosmokit` | cosmokit | 1.8.1 | github.com/deepseek-harness/cosmokit |
| `schemastery/` | `@deepseek-ai/schemastery` | schemastery | 3.18.0 | github.com/deepseek-harness/schemastery |
| `cordis/` | `@deepseek-ai/cordis` | cordis | 4.0.0-rc.7 | github.com/cordiverse/cordis |
| `loader/` | `@deepseek-ai/cordis-plugin-loader` | @cordisjs/plugin-loader | 1.0.0-rc.5 | github.com/cordiverse/cordis |
| `include/` | `@deepseek-ai/cordis-plugin-include` | @cordisjs/plugin-include | 1.0.4 | github.com/deepseek-harness/cordis |
| `group/` | `@deepseek-ai/cordis-plugin-group` | @cordisjs/plugin-group | 1.0.0 | github.com/deepseek-harness/cordis |
| `timer/` | `@deepseek-ai/cordis-plugin-timer` | @cordisjs/plugin-timer | 1.1.2 | github.com/deepseek-harness/cordis |
| `hmr/` | `@deepseek-ai/cordis-plugin-hmr` | @cordisjs/plugin-hmr | 1.0.15 | github.com/deepseek-harness/cordis |
| `logger-console/` | `@deepseek-ai/cordis-plugin-logger-console` | @cordisjs/plugin-logger-console | 1.0.0 | github.com/deepseek-harness/cordis |

(08-14 核查:`deepseek-harness/cordis` 已 404,`deepseek-harness` org 的 fork 仓库不再公开可访问;其源码以 harness 仓库 `vendor/` 内的 vendored 副本为准。)

`vendor/README.md` 还记录了一份 exhaustive 的**本地修改日志(18 条)**,其中对插件作者最重要的是:
- `cordis/src/fiber.ts` lifecycle hardening(effect 创建在 owner 处于 `UNLOADING` 时被拒绝;`PENDING`/`LOADING` 时合法)(L38,条目 6)。
- Loader/Include 事务化配置调和、`!!js` 延迟解析(L40 条目 8、L47 条目 15)。
- `include` 的 `applyEntryPatches(data, patches, warn)` 与 `entryListSchema` 被导出,供 `dsh --dump-config` 精确重放 patch 算法(L43 条目 11)。
- `disabled` 字段插值(L50 条目 18)。

### 1.3 仓库布局

根 `AGENTS.md` §Repository layout(工作区 `D:\deepseek-harness\Project\Plugins` 之外只读)给出了完整布局。要点:

```
vendor/      Vendored Cordis 源码
packages/    @deepseek-ai/dsh-<pkg> workspaces,位于 packages/<group>/<pkg>/
  core/        session, system-prompt, tools, agent, agent-loop(产品 API 主干)
  api/         远程 BFF 组装 + Typert RPC 网关
  typert/      类型图生成器/加载器/运行时注册表
  llm/         LLM 能力:Service Definition/Consumer + DeepSeek providers
  shell/       bash 能力:Service Definition + local/pwsh providers + shell Consumers
  subprocess/  subprocess 能力 + local 进程树 provider
  fs/          filesystem 能力 + policy
  lsp/         language-server 能力
  skill/       skill provider registry + local 实现 + catalog/loader tool
  web/         web 能力:Service Definition + search/fetch providers + tool Consumer
  compaction/  compaction 能力 + basic provider
  context/     请求上下文插件
  subagent/    subagent 能力 + providers + delegation Consumers
  bundle/      可安装的 dsh --profile patch-layer 包(base/web-app/headless)
  workflow/    workflow 能力 + worker-thread provider + tool Consumer
  todo/        todo_write tool
  plan/        plan mode(logged state)
  preset/      从 preset cordis.yml 做每会话 agent 组装
  guard/       loop-hygiene + tool-timeout 插件
  self-modification/  agent 检查/挂载自己的插件
  hooks/       Claude Code/Codex hook 桥 + wire 协议库
  session/     持久会话数据:persistence/projection/titles/telemetry
  identity/    匿名身份
  settings/    用户设置能力 + file provider
  credentials/ credential-reference 能力 + env/.env provider
  acp/         automation-only Agent Client Protocol server
  interaction/ approval/interaction 能力、permission、commands、ask-user
  boot/        共享 app-bin glue
  sdk/         JSON-RPC 协议、server、TypeScript client
  examples/    demo bundles(agent-spine + CLI/ACP/JSON-RPC bins)
  support/     dev/test 基础设施
  util/        零依赖工具
python/      Python SDK + bundled runtime(见 python/README.md)
native/      @deepseek-ai/node-addon-landlock-run 源码来源(见 native/README.md)
examples/    可运行的 cordis.yml leaves(挂载在 packages/examples bundles 上)
.agents/     Agent workflows 与 Agent Notes(notes/)
docs/        architecture、generated catalogs、postmortems、cookbook
scripts/     仓库门禁与生成器
website/     VitePress 投影选中的双语 docs/ 源
```

来源:`D:\deepseek-harness\AGENTS.md` §Repository layout(也见 `D:\deepseek-harness\CLAUDE.md` 的 symlink 说明:`CLAUDE.md` 是 `AGENTS.md` 的软链接)。

### 1.4 用户插件放在哪里

三类"叶子"(leaf)都在 `examples/` 下,是**可运行的 Cordis 组装**,不是 build target:

- `examples/headless-agent/` — 一次性 headless coding agent(挂 `@deepseek-ai/dsh-agent-spine-demo`)
- `examples/acp-agent/` — ACP 自动化 server(挂 `@deepseek-ai/dsh-acp-demo`)
- `examples/jsonrpc-agent/` — JSON-RPC(挂 `@deepseek-ai/dsh-sdk-jsonrpc-demo`)
- `examples/mcp-memory/`、`examples/web-cordis/`、`examples/web-schedule/` — 其它演示叶子

来源:`D:\deepseek-harness\docs\cookbook\extension-cookbook.md` §Runnable wirings(L91–L93)、`D:\deepseek-harness\examples\AGENTS.md`。

`examples/AGENTS.md`(L1–L5)规定:
- `examples/` 是一个 workspace 成员,是**可运行/测试 Cordis 配置的模块解析根**,不是 build target。
- `examples/package.json` 声明这些配置加载的包;每个 leaf 自己的 `package.json` 只是元数据。
- 可复用逻辑抽到 `packages/`(那里有 per-file coverage 和 README 门禁);examples 只保留 `cordis.yml` 布线、demo 产物、e2e/snapshot 场景。

**用户自建插件**的推荐路径见 `docs/user/develop/basic/` 教程序列:`index.md`(第一个插件)→ `tool.md`(建工具)→ `config.md`(配置)→ `publish.md`(打包成 bundle 安装进 profile)。用户本地插件通过 `--patch ./scratch-plugin/cordis.yml` overlay 挂载(`docs/user/develop/basic/tool.md` L43 的 `pnpm dsh web --patch ./scratch-plugin/cordis.yml`)。

### 1.5 Profiles 与 bundles

`docs/architecture.md` §Profiles and bundles(L15–L37)是权威定义:

- **profile**:命名组装,存于 Harness home(`$DSH_HOME/profiles/<name>`)。它列出自己堆叠的 bundles、持有的 out-of-tree 插件,以及用户自己的 `cordis.patch.yml`。`web` 与 `headless` 是内置模板。
- **bundle**:Cordis 配置行及其挂载代码的**分发格式**;它插入的内容能被其上方的层继续 patch。
- 两者都在自己的 `package.json` 里通过 `dsh` 字段自声明:`dsh.profile` 列出 profile 的 bundles,`dsh.bundle` 指向 bundle 的 patch 文件。

内置 bundle(`docs/architecture.md` L25):
- [`dsh-base`](../packages/bundle/base/README.md) — 每个 profile 的第一层:模型适配器、工具、持久化、sandbox/approval 策略、settings、credentials、telemetry。
- [`dsh-web-app`](../packages/bundle/web-app/README.md) — 加浏览器应用。
- [`dsh-headless`](../packages/bundle/headless/README.md) — 加一次性 runner(无 server)。

**层应用顺序**(`docs/architecture.md` L27):每个 profile 按列出顺序的 bundle → profile 的 `cordis.patch.yml` → home 级 `$DSH_HOME/cordis.patch.yml` → 每个 `--patch` overlay。patch 按 `id` 定位一行并**整体替换其 config**,或插入新行。

查看实际启动树:`dsh --profile web --dump-config`(L33)。

`docs/user/develop/basic/publish.md` 用可运行例子补齐了两个 manifest 的具体形状:

bundle manifest(`dsh.bundle`):
```json
{ "name": "dsh-hello-plugin", "version": "0.1.0", "type": "module",
  "main": "index.js", "files": ["index.js", "cordis.patch.yml"],
  "dsh": { "bundle": { "patch": "./cordis.patch.yml" } } }
```
profile manifest(`dsh.profile`):
```json
{ "name": "dsh-profile-demo", "private": true,
  "dependencies": { "dsh-hello-plugin": "link:/path/to/hello-plugin" },
  "dsh": { "profile": { "bundles": ["@deepseek-ai/dsh-base", "dsh-hello-plugin"] } } }
```

关键语义(`publish.md` L112–L128):层顺序 = ①profile `dsh.profile.bundles` 列表顺序(`@deepseek-ai/dsh-base` 第一)→ ②profile 自身 `cordis.patch.yml` → ③home 级 `$DSH_HOME/cordis.patch.yml` → ④每个 `--patch <path>`(按 argv 顺序)。**靠后的层按行取胜;patch 替换整行 config 而非深合并 key**。安装命令 `dsh plugin --profile <name> add <package>` 转发给 pnpm(L77–L101)。

### 1.6 核心包(`ctx` 键)

`docs/architecture.md` §Core packages(L43–L51):

| Package | Owns | `ctx` key |
|---|---|---|
| `core/session` | append-only `SessionEvent` log + 内存 store | `ctx.sessions` |
| `core/system-prompt` | prompt-section 与 tool-schema 组装 | `ctx.systemPrompt` |
| `core/tools` | scoped tool registry + guarded 执行管线 | `ctx.tools` |
| `core/agent` | `Agent` 接口、live registry、`agent/*` events | `ctx.agents` |
| `core/agent-loop` | 实现该接口的默认 driver | `ctx.agentLoop` |
| `core/scope` | per-agent scoped-registration primitive | library,无 key |
| `llm/llm` | message/stream 词汇 + adapter seam | `ctx.llm` |

完整的能力 seam / 服务图(每个 `ctx.<key>` 的 role=seam/core/bundle、owner 包、实现包、直接消费者)由生成器生成在 `docs/capability-seams.md`(含 mermaid 图 + 表格,L1–L471)。它把服务分三类:**core spine service**(如 `ctx.sessions`/`ctx.tools`)、**swappable capability seam**(如 `ctx.shell`/`ctx.fs`/`ctx.subprocess`/`ctx.llm`/`ctx.skills`)、**bundle/composition point**(如 `ctx.agentLoop`)。

---

## 2. 插件契约(plugin contract)

### 2.1 三种插件形态

`docs/cordis-tutorial/01-first-plugin.md`(L53–L77)+ `docs/cordis-primer.md`:

1. **函数插件**(最常见):module 导出 `apply(ctx)`,可选 `inject`/`Config`/`name`。
2. **对象插件**:导出含 `apply(ctx)` 方法的对象。
3. **类插件**:`Service` 子类(`class MyService extends Service { constructor(ctx){ super(ctx,'key') } }`)。

`packages/AGENTS.md` §Plugin exports(L5)约定:
> service packages default-export their service class; function plugins named-export `name` / `inject` / `Config` / `apply` and have no default export.

**混合两种形式会让 Loader 丢弃函数插件的 namespace**(postmortem `docs/postmortem/0001-acp-default-export-drops-inject.md`)。

`01-first-plugin.md` 关键事实:
- `apply` 抛错 = loud failure(进程崩),不是跳过条目(L81–L89)。
- module 无法解析(拼错路径/包名)时通过 Cordis logger service 报告而非崩进程,boot 时可能被吞(L91)。
- `name` 导出是可选的诊断标签。

### 2.2 注册即 effect,disposer 语义

`docs/cordis-tutorial/02-lifecycle-and-effects.md` 是权威:

- 插件可因配置编辑、HMR、显式 dispose、或**失去 required service** 而卸载。通过 Cordis API 做的注册是 effect,卸载时自动撤销;Cordis 不管的资源须用 `ctx.effect()` 包裹并返回 disposer(L5)。
- `ctx.effect(() => { ...; return () => cleanup() })` — effect body 在加载时运行,disposer 在卸载时运行(L65)。
- **已经是 effect 的内建 API**(L84–L92):`ctx.on(event, listener)`(卸载时移除 listener)、`ctx.plugin(child)`(随父卸载)、service 注册、harness registry 的 `register()`(返回的 disposer 自动挂到调用插件)。
- disposer **按注册逆序启动,但多个 async disposer 并发运行**;若 teardown 必须有序,放进一个 disposer 内依次 await(L94)。
- `ctx.plugin(heartbeat)` 返回 **fiber**(一个已加载插件实例的运行时句柄);`fiber.dispose()` resolve 于所有清理(含 async disposer)完成后,并递归卸载子插件(L62–L66)。

**Fiber 状态机**(L68–L82):
```
PENDING → LOADING → ACTIVE → UNLOADING → DISPOSED
                 ↘ FAILED
```
- `PENDING`:已声明,但 required service 尚不可用。
- `LOADING/ACTIVE`:`apply` 运行中 / 已完成。
- `FAILED`:`apply` 或 config 校验抛错。
- `UNLOADING/DISPOSED`:disposers 运行中 / 全部拆除。

`docs/cordis-primer.md` §Practical Rules(L44):每个注册都应有 disposer(`ctx.effect()` 返回一个,或用 Cordis helper);teardown 顺序相关时放进一个 effect。

### 2.3 Service:注入依赖与声明合并

`docs/cordis-tutorial/03-services.md` + `docs/user/develop/framework/service.md`:

- **提供 service**:`super(ctx, 'greeter')` 在运行时把实例注册到 `ctx.greeter`;注册本身是 effect(卸载 provider 即移除 service)。编译期靠 TypeScript **declaration merging**:
```ts
declare module '@deepseek-ai/cordis' {
  interface Context { greeter: GreeterService }
}
```
(`03-services.md` L14–L18、L39–L40)。不写这段,运行时仍工作,但消费者失去类型安全。

- **消费 service**:`export const inject = ['greeter']`。Cordis 让插件停在 `PENDING` 直到所有 listed service 存在,`apply` 内 `ctx.greeter` 保证就绪。**加载顺序由依赖而非文件顺序决定**(`03-services.md` L59)。

- **依赖在加载后仍被追踪**:required service 消失(provider 卸载/hot-replace)→ 依赖插件也卸载,service 回来后再加载(L76–L78)。这就是配置里换 provider 能工作的原因。

- **可选依赖**:不加 `inject`,用 `ctx.get('greeter')` 在使用点探测(返回 `undefined` 时插件仍运行)(L80–L90)。`packages/AGENTS.md` L6 强调:`ctx.get(name)` 读全局 service store,`ctx.<name>` property proxy 是拓扑敏感的,只用于 declared injection。

- `Service` 子类自身就是插件(类形态),`ctx.plugin(GreeterService)` 像普通插件一样挂载。

- service 名是**每应用一个扁平命名空间**;自建服务要加前缀(harness 已占用 `tools`/`llm` 等 plain names)(`03-services.md` L92–L94)。

### 2.4 Typed events + declaration merging + dispatch modes

`docs/cordis-tutorial/04-events.md` + `docs/cordis-primer.md` §Dispatch Modes:

**声明**(`04-events.md` L14–L21):
```ts
declare module '@deepseek-ai/cordis' {
  interface Context { stats: StatsService }
  interface Events { 'stats/report'(name: string, count: number): void }
}
```
`interface Events` 合并是 event 系统的类型安全机制;`namespace/action` 命名约定让扁平 event 命名空间可读。

**五个 dispatch mode**(`04-events.md` L84–L91;`cordis-primer.md` L19–L24 的表格):

| Mode | 调用 | 语义 | Awaited? | 有返回值? |
|---|---|---|---|---|
| `emit` | `ctx.emit(name, ...args)` | 同步广播;返回值/ promise 不 await 不收集 | No | No |
| `parallel` | `await ctx.parallel(...)` | 全部并发执行,一起 await | Yes | No |
| `serial` | `await ctx.serial(...)` | 顺序 await;首个非 null/false/undefined 返回值胜出并停 | Yes | Yes |
| `bail` | `ctx.bail(...)` | serial 的同步版 | No | Yes |
| `waterfall` | `ctx.waterfall(name, ...args, next)` | around-middleware | No | Yes |

**dispatch mode 是 event 公共契约的一部分**。harness 新 event 用 `@mode` 标签文档化,供生成 catalog 检查声明与 dispatch 点一致(`cordis-primer.md` L26)。

**Waterfall 语义**(`cordis-primer.md` L28–L34、`04-events.md` L94–L140):listener 收到 `(...args, next)`。调用 `next()` 把(可能被包装的)结果委派给下一个服务;不调用就短路(veto)。值通过 `next()` 的返回值传播。**只观察/注解的 waterfall listener 必须调用 `next()`**;返回而不调用是刻意的短路。忘掉 `next()` 会静默吞掉下游默认行为。`prepend: true` 只在 listener 必须跑在普通注册之前时用。

harness 的 waterfall 例子:`agent/request`、`approval/request`、`tools/pre-execute`、`tools/execute`、`tools/post-execute`。

### 2.5 Capability seam:Service Definition / Provider / Consumer

`docs/glossary.md` §capability-seam(L7–L9)定义:

- **seam** — 一个 *swappable capability*,含三个角色:一个 **Service Definition**(拥有 `ctx.<key>` 和词汇类型的 Cordis `Service` — 抽象类如 `ShellExecutor`,或具体 registry 如 `WebRuntime`,**绝不是 TypeScript `interface`**)、一个或多个 **Service Provider**、一个或多个注入该 service 的 **Consumer**。
- 权威例子(`glossary.md` L9、`docs/user/develop/practice/index.md` §Bash example):`packages/shell` — `dsh-shell`(Service Definition)、`dsh-bash-local`/`dsh-bash-sandbox`(providers)、`dsh-tool-bash`(Consumer)。
- 角色通常在独立演化时分属独立包,但一个包可拥有多个角色(`dsh-llm` 同时拥有 Service Definition 与 Consumer)。
- **seam 是完整能力,绝不是单一角色**;称呼构成部分时用角色/类/service/contract/extension point 词。

`docs/architecture.md` §Capability seams(L98–L102):一个 provider 替换就能改变整个产品——filesystem 与 subprocess providers 共享同一个 execution world,指向远程 sandbox 可让 Bash、PTY、LSP 一起迁移,无需 provider 分支。

三包拓扑的落地模板是 `docs/cookbook/adding-a-package.md` §3 Decide the package topology 的"Name the role that exists"(L45–L71),给出一张角色命名词表(`Controller`/`Store`/`Directory`/`Presenter`/`Registry`/`Runtime`/`Resolver`/`Binder`/`Engine`/`Policy`/`Executor`/`Gateway`/`Provider`/`Backend`/`Handle`/`Config`/`Service`),并规定:
- 单数 `ctx` key 用于一个 engine/runtime/policy/controller/resolver/store/current config;复数 key 用于 registry 或拥有多个命名成员的服务。类角色与 key 数必须一致。
- 不要用一个 Cordis `Context` key 承载互不兼容的 host/client 声明(declaration merging 两边都会看到)。
- `SDK` 只用于 JSON-RPC client/server 协议;产品拼写是 `Typert`(不是 `TypeRT`/`typeRT`)。

### 2.6 Config schema(Schemastery / Standard Schema)

`docs/cordis-tutorial/05-config.md` + `docs/user/develop/basic/config.md`:

- 导出同名 `Config` 接口 + Schemastery schema:
```ts
export interface Config { greeting: string; targets: string[] }
export const Config: Schema<Config> = Schema.object({
  greeting: Schema.string().default('Hello'),
  targets: Schema.array(String).default(['world']),
})
export function apply(ctx: Context, config: Config) { ... }
```
(`05-config.md` L11–L31)。
- 本仓库用 **Schemastery**(vendored `@deepseek-ai/schemastery` 3.18.0)做 schema;Cordis 本身接受任何 [Standard Schema](https://standardschema.dev/) validator,**导出普通对象作为 `Config` 不行**(`05-config.md` L34、`config.md` L45)。
- 校验在加载时运行;无效配置 `ValidationError` 使 fiber 进 `FAILED`(`05-config.md` L63–L68)。default 会填充,`apply` 收到完整、已验证的 config。
- schema 可表达的更严格校验:`Schema.string().required()`、`Schema.union(['fast','accurate']).default('fast')`(`config.md` L51–L72)。
- **原则**(`config.md` §Design principles、`AGENTS.md` §Conventions):不要硬编码 tunable——**两个部署可能想设不同值的东西都必须是 config 字段**(检验标准:`cordis.yml` 能否不改代码就改它);self-contained 约束写进 schema 让它在加载时 fail loud;引用 service/注册资源的约束用 dependency injection。
- HMR:配置编辑会 hot-replace 插件(卸载旧实例、加载新实例),因注册是 effect 而不会残留旧注册(`config.md` L98–L100)。

### 2.7 `!!js` 配置、`disabled`、overlays

`docs/cordis-primer.md` §Loader Configuration(L36–L38)+ `05-config.md` §Computed config values(L70–L80):

- `!!js` tag 只允许在 `config` 内、以及 entry 的 `disabled` 字段里(**是 `!!js` 不是 `!js`**,见根 `AGENTS.md` §Secrets/.env)。
- `disabled: !!js ...` 在每次 mount 决策时针对 loader context 求值(本仓库扩展),可让行按平台/环境自门控。
- 其它 metadata(`name`/`id`/`inject`/…)保持静态,表达式在那里只是 truthy 数据。
- `@deepseek-ai/cordis-plugin-include` 把 `!!js` 解析成表达式节点;Loader 在 declared injections 激活后、针对该插件 context(`ctx.serviceName`)插值 entry 的 `config`,在每次 mount 决策时插值 `disabled`;Include 保留嵌套行表达式直到目标激活;其余 metadata 保持字面量。
- 环境选择插件时用 **overlays**。

真实例子(`packages/bundle/base/cordis.patch.yml`):
- `bash-sandbox`: `disabled: !!js process.platform === 'win32'`(L180)
- `sandbox-policy.config.mode`: `!!js process.env.DSH_PERMISSION_MODE ?? 'workspace-write'`(L175)
- `session-telemetry-otel.config.mode`: `!!js process.env.DSH_TELEMETRY_MODE || 'DISABLED'`(L151)
- `session-persistence-jsonl.config.root`: `!!js dshHomePath('sessions')`(L101)

### 2.8 `verify-cordis-config` 强制门禁

`scripts/verify-cordis-config.ts`(L1–L497)是 `doc-sync` 的一部分,强制以下约束(`AGENTS.md` §Conventions 也引用:"Raw/Web `cordis.yml` bare plugins must appear in their resolver manifest's `dependencies`; `verify-cordis-config` enforces it"):

1. **metadata 插值约束**(`metadataFields = ['id','name','group','inject','intercept','isolate']`,L41;`metadataExpressionErrors` L431–L453):`disabled` 是唯一可插值的 metadata 字段——它的 `!!js` 表达式节点合法且必须可解析(用 `new Function` 只编译不执行,L462–L472),嵌套在它下面的表达式是 truthy 数据;**其余每个 metadata 字段必须完全静态**,出现 `!!js` 报 `!!js is not interpolated here`。
2. **依赖解析**(`missingPluginDependencies` L342–L364):每个裸插件 specifier 的包名必须出现在对应 resolver manifest(`examples/package.json`、`apps/cli/package.json` 或某个 bundle manifest)的 `dependencies` 中。
3. **source plane 解析**(`validateSourcePlaneResolution` L300–L340):本地 workspace 包的每个 specifier 必须通过 `tsconfig.base.json` 的 `paths` 解析到 `.ts`/`.tsx` 源文件,否则 tsx source launch 在干净 checkout 会失败。
4. **preset 平面分离**(`validatePresetPlaneSeparation` L147–L171):shipped agent preset 不得重复 host composition 仍在运行的行(一行属于恰好一个 plane)。
5. **client halves 声明**(`validateClientHalvesDeclared` L115–L128):`packages/client` 包导出 `./client` 就必须声明 `dsh.client`(反之亦然)。
6. 目录选择器 chooser 的隐式 backend/surface 包依赖也强制解析(L44–L58)。

配套 spec:`scripts/verify-cordis-config.spec.ts`。

### 2.9 Group / isolate(服务隔离)

`docs/cordis-tutorial/06-composition-and-hmr.md` L21 + `docs/user/develop/framework/service.md` §Service isolation(L111–L139):

- `id` 给 entry 稳定身份(loader 借此区分"编辑已有条目"vs"删了再加")。
- `disabled: true` 卸载插件但保留条目。
- **Groups** 把子列表嵌套为一单元加载/卸载;**`isolate`** 给 group 自己的 service 名实例——两个 group 各见不同配置的 `shell` provider 互不影响。
- 例子(`service.md` L115–L137)用 `@deepseek-ai/cordis-plugin-group` + `isolate: { shell: true }` + `config:` 子列表。
- 没有 `id` 的 entry 每次读取都生成 id,配置文件一改就算 removed-plus-added 并 remount(`06` L59)。

---

## 3. 插件生命周期与扩展点

### 3.1 Agent loop 与 turn flow

`docs/architecture.md` §Turn flow(L63–L90)给出 step/turn 定义与流程:

- **step** = 一次模型请求 + 它调用的工具。**turn** = 零或多个 step。
- 流程(L67–L82):
```
turn/start
  claim next-step input plus one queued message
  assemble prompt sections + tool schemas
  -> agent/pre-step                   reject | enter(messages)
     reject, or a first enter rewritten empty -> close the turn with no step
     step/start
     append entered messages as user/message
     derive model history from the log
     agent/request -> llm/stream -> assistant/chunk* -> assistant/message
     tool/call* -> tools/pre-execute -> tools/execute -> tools/post-execute -> tool/result*
     step/end
     tools owe another request, or next-step input arrived -> claim -> next step
  -> agent/turn-stopping
turn/end
```
- `turn/*`、`step/*`、`user/message`、`assistant/*`、`tool/*` 是**持久 session event**;其余是三个域里的 live 扩展点。`agent/pre-step`、`agent/request`、`llm/stream`、三个 `tools/*` 事件是 waterfall(listener 必须调 `next()`);`agent/turn-stopping` 是 serial 且无 `next()`(L84)。

### 3.2 扩展点地图(新行为放哪)

`docs/architecture.md` §Where new behavior goes(L104–L127)给了"目标 → 机制"表,例如:

| 目标 | 机制 |
|---|---|
| 加模型 provider | 在 `ctx.llm` 注册 adapter |
| 加模型能力 | 在 `ctx.tools` 注册;schema 自动进入 prompt 组装 |
| 给单会话不同能力集 | 组合 agent preset;那里的 service 行需要 `isolate` realm |
| 加 shell 执行 | 注册 `ctx.shell` backend;local 经 `ctx.subprocess` spawn |
| 加人命令 | 注册 `ctx.commands`;不经模型 turn 分发 |
| 加后台工作 | 注册 `ctx.jobs`;`job_*` tools 收集/停止 |
| 拦截请求/工具/turn | 用 `agent/*` 或 `tools/*` event |
| 加模型上下文 | 调 `agent.inject()`;落在下一次 admitted request |
| 加持久会话状态 | 扩展 `SessionEventMap`;从 log 渲染/replay |
| 同一会话目标 | 用 `ctx.goals`;经 `agent/*` 继续 |
| fork 会话 | `ctx.sessions.fork(source, boundary?, childSessionId?)` |
| 把注册限定到一个 agent | 用该 agent 的 `agent.ctx` |

`docs/cookbook/extension-cookbook.md` §The feature → mechanism map(L95–L129)把每个产品 feature 映射到 listener,并给出五种插件骨架示例代码:
1. **A tool plugin**(`ctx.tools.register(defineTool(...))`)
2. **A hook plugin (permission-gate)**(`ctx.on('tools/pre-execute', async (exec, next) => { if(!isAllowed) return {kind:'deny', reason}; return next() })`,返回 `PreToolDecision`)
3. **A UI plugin**(监听 `session/event` 渲染 `assistant/chunk`,用 `agent.followup()`/`agent.steer()` 回输)
4. **An external protocol driver**(adapt wire peer 到 `ctx.agents`,`AgentHandle.dispose()` 达 quiescence;`packages/acp/acp` 是 worked example)
5. **Runnable wirings**(`examples/*/cordis.yml`)

### 3.3 Tool 注册配方(cookbook: adding-a-tool.md)

`docs/cookbook/adding-a-tool.md` 是**模型可见工具契约的权威参考**。最小形态(L9–L38):

```ts
import { defineTool } from '@deepseek-ai/dsh-tools'
export const name = 'my-tool'
export const inject = ['tools']
export function apply(ctx: Context) {
  ctx.tools.register(defineTool({
    name: 'read_file',
    description: 'Read a file from disk.',
    parameters: {
      path: { type: 'string', required: true, description: 'Absolute path' },
      limit: { type: 'number' },
    },
    output: {
      schema: { type: 'string' },
      render: (_args, value) => [{ type: 'text', text: value }],
    },
    async execute(args, exec) {
      return readFile(args.path, { encoding: 'utf8', signal: exec.signal })
    },
  }))
}
```

`execute()` 契约要点(L40–L56):
- **args 已为你校验**:`defineTool` 在 `execute` 前用 `ParameterSchemaSpec` 校验 model 生成的 `arguments`(类型、required key、literal 约束、exact-one union、嵌套值)。`args` 匹配 `InferArgs`。仍需手查 DSL 不表达的约束(非空字符串、正数、跨字段)。
- **注册借用你的 readonly definition**:不 mutate schema/替换 callback;热换工具 = dispose owning effect 再注册新实例。
- **执行身份受保护**:registry 一次性递归 materialize 无损 JSON arguments、在策略开始前 freeze、分配不透明 `exec.token`;`callId`/`name`/`arguments`/`agent`/`token`/必需 caller-owned `signal`/可选 `parent` 在 dispatch 中不可变。
- **声明并返回一个 canonical JSON 值**:`output.schema` 用 `ValueSchemaSpec`(object/array/scalar/null root);`execute` 只返回推断值,registry 快照/校验/freeze 后交给 `output.render(args, value)`。
- **throw 或返回无效值 = `isError`**。throw 用于基础设施失败。
- **遵守 `exec.signal`**(取消在途工作)。
- 可选 `output.presentationMeta(args, value)` 派生可回放的 JSON(持久到 `tool/result`)。
- 可选 `exec.agent` 用于异步通知:`agent.inject({ content, source: { kind: 'plugin', plugin: '<name>' } })` 是下一次请求看到的持久上下文,不是唤醒(idle agent 保持 idle)。
  > ❌ **已退役(2026-10-13 标注)**:`{ kind: 'plugin', plugin }` 这个形态在会话格式 V4 起**被消息准入处拒绝**。改用 `declare module '@deepseek-ai/dsh-llm' { interface MessageSourceMap { '<your-kind>': { kind: '<your-kind>' } } }` 声明自有 kind,再传 `{ kind: '<your-kind>' }`。见 [`guide/plugin-dev-guide.md`](../guide/plugin-dev-guide.md) §3 与 [`guide/migration-0.2.md`](../guide/migration-0.2.md)。
- 后台工作:`run_in_background` 用 producer config 门控,经 `ctx.jobs.start({ kind, label, owner: exec.agent, run })` 注册。

**执行策略与观察**(L57–L59):`tools/pre-execute`(可扩展 allow/deny/ask)、`ctx.tools.guard()`(最终单调 deny)、`tools/execute`(包裹 dispatch 加 deadline/retry/metrics)、`tools/post-execute`(替换呈现/值、block、附加上下文)、`tools/result`(观察不可变最终结果)。

**Code Mode 免费获得工具**(L61–L65):每个可见注册工具可作为 `await tools.<name>(args)`;生成的 `ToolArgsMap`/`ToolOutputMap` 从同一 schema 推导精确类型;失败 reject 真实 `ToolCallError`(只暴露 `name`/`toolName`/`message`)。

**UI 呈现**(L67–L90):`output.render` 是模型内容;UI card 由纯函数 `presentCall`/`presentResult` 声明,返回 `card`-tagged render intent(`generic`/`terminal`/`diff`/`search`/`read`/`web`)。硬规则:**纯度**(live streaming 与 REPLAY 都要跑,必须只是 `args`(+result) 的纯函数,无 I/O/session state/clock/random);UI-only 格式不进 model result;`defineTool` 对显示路径软校验(malformed args 返回 `undefined` 走 generic fallback 而非 throw)。

`packages/core/tools/README.md` 给出 `ctx.tools` 服务(`ToolRuntime`)的完整 API:`register`/`presentAs`/`restrict`/`get`/`schemas`/`guard`/`execute`/`executionMode`,以及 config `mode: native | code | both`、`defineTool` schema DSL(`ParameterSchemaSpec`/`ValueSchemaSpec`、`oneOf`、`additionalProperties`)、Code Mode 的 `run_code` 保留 transport、并行执行、`ToolDefinition`/`ToolExecution`/`PreToolDecision`/`PostToolDecision`/`ToolGuard` 等关键类型(L1–L198)。
> ❌ **`both` 已不再合法(2026-10-13 标注)**:`mode` 现只接受 **`native`** 或 **`ptc`**(`code` 线已改名为 PTC)。传 `both`(含环境变量 `DSH_TOOLS_MODE=both` 与 `ctx.tools.presentAs('both')`)会被直接拒绝。官方出处:`upgrade-guide/v0.2.1-alpha.1/tool-presentation-mode`。

### 3.4 Skill 插件机制(packages/skill)

`packages/skill/skill/README.md` 定义 `ctx.skills`(`SkillRegistry`):

- **纯 agent skill provider registry**,不知道 skill 来自本地文件/插件数据/HTTP(`registerProvider` 注册来源)。shipped local 实现是 `@deepseek-ai/dsh-skill-filesystem`。
- host+per-scope 分层,套在 `@deepseek-ai/dsh-scope` 上:注册落到调用 context 的 scope layer;读取合并 global layer + viewing scope chain,最近层同名胜出。
- 公共 API:`registerProvider(create)`、`snapshot({cwd,signal,scope})`、`list(...)`、`get(name,...)`、`register(skill)`(runtime embedded skill,rank 250)。
- 事件 `skills/change`(unfiltered invalidation notification,不带 catalog,消费者自己 `snapshot()` 重取)。
- config `collectCacheMaxEntries`(默认 128)。
- **Invocation policy**:`SkillSummary.invocation` 是必需 typed policy(`modelInvocable`/`userInvocable` 两个独立布尔,四种组合)。
- `renderSkillContent(skill)` 渲染 canonical `<skill_content>` block(`dsh-tool-skill` 与用户显式注入共用同一形状)。
- 消费者边界:registry 不渲染模型引导也不注册工具;`@deepseek-ai/dsh-tool-skill` 消费 `ctx.skills`(durable session catalogs + `skill` tool)。

skill 目录结构:`packages/skill/` 下有 `skill`(registry/Service Definition)、`skill-filesystem`(本地 provider)、`skill-badge`(provider,默认 `disabled: true`)、`tool-skill`(Consumer)。

`capability-seams.md` 对应行:`ctx.skills` = seam,owner `skill`,实现 `skill-badge`/`skill-filesystem`,消费者 `tool-skill`。

### 3.5 Bundle / profile 系统 + preset

- **bundle**(`packages/bundle/README.md`):`packages/bundle/base/`(patch only,第一层)、`web-app/`(web patch + runtime glue)、`headless/`(挂 `headless-runner`)。`packages/bundle/base/cordis.patch.yml`(L1–L451)是实际的第一层 patch,列出全部 base 行(`llm`、`session`、`typert*`、`agent`、`agent-loop`、`tools`、`system-prompt`、`fs-sandbox`、`llm-deepseek`、`skill*`、`subagent*`、`workflow*`、`tool-*`、`plan-mode`、`goal*` 等),是看真实配置行形状的最佳示例。
- **profile**:`dsh.profile.bundles` 列表;`web`/`headless` 模板。
- **preset**(`packages/preset/agent-presets/README.md`):一个 **preset** 是持有一份 `agent.cordis.yml` 的目录;roster 在每进程**只挂载一次**到一个 standing scope,每个命名它的会话通过 `dsh-scope` 的 parent chain 让 agent scope key 父级化到该 mount。视图解析 `agent → preset → global`(近者遮蔽远者)。
  - Service `AgentPresets`(`ctx.agentPresets`)API:`defaultId`/`list()`/`resolve(id?)`/`mount(agentCtx,id?)`/`composeFrom(agentCtx,parentCtx)`/`composedPreset`/`recompose`/`standingKeyFor`/`roots`/`authorable`/`read`/`copy`/`remove`。
  - `mount()` 唯一受支持调用点是 agent factory 的 `setup(agentCtx)` hook。
  - preset 行解析:**包名从 host composition 解析**(本地 preset 在用户 home 下,Node 向上走 `node_modules` 找不到 harness);相对路径从 preset 自身目录解析;绝对路径转 `file:` URL。
  - 显示元数据在可选 `preset.yml`(`name`/`description`)。
  - config:`default`(必填)、`roots`(默认 `[]`)、`includeUserRoot`(默认 `true`,追加 `<dshHome>/.agent-presets`)。
  - mount 拒绝三类:unscoped target、never-usable row、publishes-into-root-realm row(service 要放 `isolate` realm)。
  - preset 文件是 input,不是持久化目标(mounted subtree 覆盖 `write()` 为 no-op)。
  - 切换 = `agent-preset/selected` session event(model-visible ⟺ logged 规则)。

---

## 4. 插件作者必须满足的约定与门禁

根 `AGENTS.md` §Conventions + §Commands + `packages/AGENTS.md` + `docs/testing.md` + `docs/development.md` 是权威来源。

### 4.1 包命名与 manifest 不变式

- 每个 npm 包是 `@deepseek-ai/dsh-<name>`;vendored 包 rescope(映射在 `docs/rescope.md`)且 `private: true`。`@deepseek-ai/cordis` 是每个 harness 包的 peerDependency(+ dev)。
- `packages/AGENTS.md` §Adding a package(`docs/cookbook/adding-a-package.md` L25)的 `package.json` 不变式(`pnpm run constraints` / `scripts/check-workspace-constraints.ts` 强制):`private: true`、`version` 匹配 root、`type: module`、`main: "lib/index.js"`、`types: "lib/types/index.d.ts"`、`exports["."].types: "./lib/types/index.d.ts"`、`exports["."].default: "./lib/index.js"`、`@deepseek-ai/cordis` 同时在 peerDependencies 和 devDependencies(同 range)、每个 dsh peer dep 镜像进 devDependencies、`@deepseek-ai/schemastery` 进 `dependencies`(运行时 validator)、`files` 列表精确为 `lib/index.js`、`lib/invariant.js`、`lib/types/**/*.d.ts` + 包特定 runtime 产物。
- 包内相对 import 用显式 `.ts` 说明符(`export * from './types.ts'`),编译期改写为 `.js`,声明保留 `.ts`,NodeNext 消费者能解析到 sibling `.d.ts`。

### 4.2 ESM 与 source/artifact 平面

- **ESM everywhere**(`"type": "module"`)。跨包用包名,包内相对 import 用 `.ts`。
- 配置子进程跑 built `lib/`(plain Node);source 回归用声明的 launcher。`dsh` CLI source launch 经 tsx 的 ESM-only hook(`node --import tsx/esm`);它触及的模块必须保持 ESM(无 CJS-only exports)。
- **Source plane vs artifact plane,永不混用**:静态门禁和测试经 tsconfig `paths` 把 workspace import 解析到 `src`,在干净树上通过;消费 built `lib/` 的门禁要显式声明该依赖(`docs/development.md` §TypeScript project layout)。

### 4.3 类型安全与文档门禁

- `strict: true` + `noImplicitAny`;每个残留 `any` 解释为何无法收窄。
- 每个 module/export 有简洁 JSDoc(`@param`/`@returns`),由 `verify-export-jsdoc` 强制。
- typed events 用 declaration merging + merge-extensible maps;event JSDoc 需 `@mode` 和 payload `@param`;scoped keys 缺 payload 需 `@dshScopeScan unsupported`。
- `SessionEventMap` 成员默认 required-on-read,无信封豁免:不认其类型的 build 一律拒绝该 log;只有结构格式变更才 bump `SESSION_FORMAT_VERSION`。
- switch 用 discriminant tags:closed union 结尾 `assertNever`;merge-extensible union 走文档化 default。
- opaque 跨边界 id 用 branded(`Branded<B>` from `dsh-brand`),从不裸 `string`。
- 每个包拥有 `./invariant`(注册 manifest 名;检查 event/data 关系或给空 installer 一个包特定 `No runtime invariant:` 理由),由 `verify-package-invariants` 强制(`packages/AGENTS.md` L18)。
  > ❌ **整条机制已移除(2026-10-13 标注)**:运行时 invariant 插件及各包的 `./invariant` 导出已在 `0.2.1-alpha.1` 删除,`verify-package-invariants` 门禁随之不复存在。官方出处:`upgrade-guide/v0.2.0-rc.2/remove-runtime-invariants`。**不要在新插件里建 `src/invariant.ts`**,也不要按本节校验清单照做。
- package README 的 Model Experience 格式(canonical Model Experience format,`docs/cookbook/adding-a-package.md` §4)+ `Known Limitations and Deferred Work` 段落由 `verify-package-readme-model-experience.ts` / `verify-package-readme-limitations.ts` 门禁。

### 4.4 双语文档

- 人类文档须中英双语,`.zh.md` 配对;契约在 `docs/i18n/README.md`(pairing contract)。routine 双语工作按 `docs/AGENTS.md`;`dsh-translate-docs` 只在用户显式调用时运行。
- 文档分层(tier taxonomy,`docs/AGENTS.md` L15–L34):一个事实一个 home。生成的英文源(`cordis-surface` 区域、`tool-catalog`、`config-catalog`、`persistence-catalog`、`module-graph`)从源生成且 freshness-gated;中文对照经 pairing workflow。
- `doc-sync` 聚合所有文档门禁:`verify-md-links`(死链/死锚点)、`verify-type-equiv`(`ts type-equiv`/`ts public-api` 粘贴不漂移)、`verify-doc-budgets`(词数上限)、`verify-agent-note-format`、`verify-archived-agent-notes`、`verify-config-catalog`、`doc-typecheck`(fenced `ts` block 必须编译)等。leaf 列表在 `scripts/run-gates.ts`。

### 4.5 测试与快照门禁

`docs/testing.md` 分层:
- **Unit**(`pnpm run test`):vitest。每个 registry 要有 HMR-safety 测试(dispose contributing fiber,断言清理)。
- **Coverage gate**(`pnpm run test:coverage`):CI 覆盖门禁 = `packages/*/*/src` 每文件 100%(`test:coverage` 而非 `test` 是门禁)。
- **Real-API e2e**(`pnpm run test:e2e`):with-key;无 key 自跳过。
- **Snapshot**(`pnpm run test:snapshot`):keyless 期望输出覆盖外部行为(transport contract + presentation;持久 log pin assembled backend behavior)。ACP boots 真实 automation-server example,replay 录制 session,diff normalized JSON-RPC + 重持久化 log。record 用 `pnpm run test:snapshot:record`。
- **Web browser snapshot**(`pnpm run test:web`;Linux PR gate)。

关键政策(`testing.md` L33、L47–L49):**product-visible 插件要求 non-unit REAL-composition 测试**(手搭 `ctx.plugin(...)` 套件不够,须 boot 测试专用 `cordis.yml` 经 Loader 和 app/process)。**每个非平凡 model-/protocol-/human-visible 变更在同一 PR 里通过可运行 example 的 snapshot suite 增/改一个 keyless scenario**;包测试、e2e-only 断言、mock-only fixture 不能替代 assembled application transcript。

`examples/AGENTS.md` §E2E smokes:每个 example 都要有 keyless(boot 真实 cordis.yml 经 Loader)和 with-key(真实模型 prompt)两个 smoke。

### 4.6 其它门禁与命令

`AGENTS.md` §Commands + `docs/development.md`:
```sh
pnpm install              # node ^22.19 || >=24(pnpm@11.7.0 pinned)
pnpm run clean
pnpm run test             # vitest unit
pnpm run test:coverage    # CI coverage gate:packages/*/*/src 每文件 100%
pnpm run test:e2e         # real-API;无 DEEPSEEK_API_KEY 自跳过
pnpm run test:snapshot    # keyless ACP/headless replay
pnpm run test:snapshot:record
pnpm run typecheck        # strict;Host lib phase 先于 Client
pnpm run lint             # oxlint
pnpm run duplication      # 跨文件 TS clone 检测
pnpm run build            # tsc 出 lib/types,tsdown 打包 runtime
pnpm run hygiene          # knip + publint + workspace constraints + NodeNext consumer check
pnpm run check:windows-wine  # 仅诊断已知 Windows 失败(需 wine)
pnpm run doc-sync         # 所有文档门禁
pnpm run website:build    # VitePress build(兼死链检查)
pnpm dsh --profile headless "task"   # 从源跑一任务(需 key)
```

`pnpm run hygiene` 含 `publint`(验证 package entrypoint 对 built `lib/*.js`)+ `verify-node-next-types`(对临时 NodeNext consumer 验证 built declarations)——需要先 `pnpm run build`。

### 4.7 仓库级约定(作者必须遵守的行为规则)

根 `AGENTS.md` §Conventions 的 standing rules(与插件开发直接相关者):

- **Registrations are effects**:每个贡献经 `ctx.effect()`/`ctx.on()`;registry 的 `register()` 返回 disposer。
- **Model-visible ⟺ logged**:任何到达模型请求的东西必须能从 session log 重建;新的 model-visible input 需要新 session event。
- **Plugins, not loop changes**:新行为放在文档化扩展点上;改 `agent-loop` 需更新 `docs/architecture.md`。
- **A capability seam 由 Service Definition / Service Provider / Consumer 组成**;完整,绝不一角色;只在角色独立演化时拆分。
- **No hardcoded tunables in plugins**:部署差异是 validated `Config` 字段,可从 cordis.yml 改;`DEFAULT_*` 常量或 test hook 不算可配置性。
- **Misconfiguration fails loud**:load 时 self-contained 就 load 时失败,否则最早可解析点失败;绝不静默跳过缺失 referent。
- **Explicit > implicit at package boundaries**:defaulting 是 owning 实现里的显式 `resolve(request): Spec` 步骤,不是 `run()` 里隐藏的 `?? default`(`dsh-shell` 的 request/spec 拆分是模板)。
- **Waterfall listeners MUST call `next()`**。
- **Trust TypeScript at typed same-process boundaries**:只在 parser/config、queued、model/tool JSON、durable/file、worker、process、wire 边界做运行时校验。
- **一个 async 操作 = 一个 lifecycle controller 或 transaction**。
- **非平凡变更必须同 PR 加 Agent Note**(`.agents/notes/README.md` §When to write one;`docs/AGENTS.md` L39)。
- TODO markers:`FIXME`/`TODO`/`XXX` 按紧急度。
- 文件以恰好一个 trailing newline 结尾(`git diff --cached --check` 在 pre-commit 门禁)。

Agent Notes 的生命周期/分类/格式见 `.agents/notes/README.md`:路径 `{lifecycle}/{class}/yyyy-mm-dd-topic-title.md`,lifecycle ∈ `proposed`/`implemented`/`rejected`(+ frozen `archived/{class}/`),class ∈ `feature`/`bug-fix`/`simplification`/`architecture`/`process`/`testing`;头部三行固定 `# Agent Note: <title>` + 空行 + `Status: <status>`;强制 `## Alternatives considered`。

---

## 5. 每个事实的路径/URL 索引(摘要)

本地 checkout 权威文件(全部已 `read` 核实):

| 事实 | 文件 |
|---|---|
| 整体架构 / 一切皆插件 / profile+bundle / 扩展点地图 | `D:\deepseek-harness\docs\architecture.md` |
| 术语:capability-seam、agent-scope、goal、human command、loop hierarchy、Ralph | `D:\deepseek-harness\docs\glossary.md` |
| Cordis 五思想 / dispatch modes / waterfall / loader config | `D:\deepseek-harness\docs\cordis-primer.md` |
| 插件三形态 / 生命周期 / effect / fiber 状态机 / service / events / config / HMR | `D:\deepseek-harness\docs\cordis-tutorial\01~07-*.md` |
| Cordis 核心 API + inherited tier | `D:\deepseek-harness\docs\cordis-api\{context,events,fiber,service,registry,inherited}.md` |
| 工具契约(defineTool/execute/output/UI card) | `D:\deepseek-harness\docs\cookbook\adding-a-tool.md` |
| 加包 checklist / 命名词表 / package README 格式 | `D:\deepseek-harness\docs\cookbook\adding-a-package.md` |
| 扩展插件骨架 / feature→mechanism map | `D:\deepseek-harness\docs\cookbook\extension-cookbook.md` |
| LLM adapter 契约 | `D:\deepseek-harness\docs\cookbook\adding-an-llm-adapter.md` |
| vendored 包 manifest + 本地修改日志 + sync 程序 | `D:\deepseek-harness\vendor\README.md` |
| Agent Notes 布局/分类/格式 | `D:\deepseek-harness\.agents\notes\README.md` |
| 包级规则 | `D:\deepseek-harness\packages\AGENTS.md` |
| examples 规则 | `D:\deepseek-harness\examples\AGENTS.md` |
| 能力 seam 图 + 服务表 | `D:\deepseek-harness\docs\capability-seams.md` |
| 配置目录(每包 config 声明) | `D:\deepseek-harness\docs\config-catalog.md`(generated,3151 行) |
| 测试政策 / snapshot / coverage | `D:\deepseek-harness\docs\testing.md` |
| 开发指南 / TS project layout / CI | `D:\deepseek-harness\docs\development.md` |
| 文档标准 / tier taxonomy / 双语 | `D:\deepseek-harness\docs\AGENTS.md` |
| `ctx.tools` 服务 + Code Mode + 并行 | `D:\deepseek-harness\packages\core\tools\README.md` |
| `ctx.skills` 服务 | `D:\deepseek-harness\packages\skill\skill\README.md` |
| `ctx.agentPresets` 服务 | `D:\deepseek-harness\packages\preset\agent-presets\README.md` |
| bundle 包 | `D:\deepseek-harness\packages\bundle\README.md` + `packages\bundle\{base,web-app,headless}\cordis.patch.yml` |
| base 层实际配置行 | `D:\deepseek-harness\packages\bundle\base\cordis.patch.yml` |
| headless 示例组装 | `D:\deepseek-harness\examples\headless-agent\cordis.yml` |
| 用户教程(建插件/工具/配置/发布) | `D:\deepseek-harness\docs\user\develop\basic\{index,tool,config,publish}.md` |
| 框架教程(service/events/三角色能力) | `D:\deepseek-harness\docs\user\develop\framework\{service,events}.md` + `docs\user\develop\practice\index.md` |
| `verify-cordis-config` 强制门禁 | `D:\deepseek-harness\scripts\verify-cordis-config.ts` + `scripts\verify-cordis-config.spec.ts` |
| 根约定 + 命令 | `D:\deepseek-harness\AGENTS.md`(`CLAUDE.md` 是软链接) |

公开仓库 URL(经 web_search 核实存在):

- 仓库首页:https://github.com/deepseek-ai/deepseek-harness (标题 "DeepSeek Harness: Everything is a Plugin.")
- README:https://github.com/deepseek-ai/deepseek-harness/blob/master/README.md 与 `README-zh.md`
- 架构文档:https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/architecture.md (及 `.zh.md`)
- 配置目录:https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/config-catalog.md
- 发布教程:https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/develop/basic/publish.md
- Cordis primer:https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/cordis-primer.md
- 能力 seam:https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/glossary.md
- npm 包示例:https://www.npmjs.com/package/@deepseek-ai/dsh-settings-file

> 公开仓库 master 分支的文档结构与本地 checkout 一致(architecture.md、config-catalog.md、cordis-primer.md、glossary.md、publish.md 等在 master 均可访问)。公开 issues/discussions 中"专门讨论插件开发契约"的**独立线程内容未在本次检索中抓到**——本次 web_search 返回的源主要是文档镜像(README/docs/cookbook/cordis-tutorial),因此"公开 issue/discussion 里的额外插件开发约定"视为 `[unverified]`;其余所有事实均已本地读档核实。

---

## 全部来源

**本地(只读 checkout `D:\deepseek-harness`,全部 `read` 核实):**
1. `docs/architecture.md`
2. `docs/glossary.md`
3. `docs/cordis-primer.md`
4. `docs/cordis-tutorial/01-first-plugin.md` ~ `07-into-the-harness.md`
5. `docs/cookbook/adding-a-tool.md`
6. `docs/cookbook/adding-a-package.md`
7. `docs/cookbook/extension-cookbook.md`
8. `docs/cookbook/adding-an-llm-adapter.md`
9. `docs/capability-seams.md`
10. `docs/config-catalog.md`
11. `docs/testing.md`
12. `docs/development.md`
13. `docs/AGENTS.md`
14. `docs/user/develop/basic/{index,tool,config,publish}.md`
15. `docs/user/develop/framework/{service,events}.md`
16. `docs/user/develop/practice/index.md`
17. `packages/AGENTS.md`
18. `examples/AGENTS.md`
19. `vendor/README.md`
20. `.agents/notes/README.md`
21. `packages/bundle/README.md` + `packages/bundle/base/cordis.patch.yml` + `packages/bundle/{web-app,headless}/cordis.patch.yml`
22. `packages/skill/skill/README.md`
23. `packages/preset/agent-presets/README.md`
24. `packages/core/tools/README.md`
25. `examples/headless-agent/cordis.yml`
26. `scripts/verify-cordis-config.ts` + `scripts/verify-cordis-config.spec.ts`
27. 根 `AGENTS.md` / `CLAUDE.md`

**Web(web_search):**
- https://github.com/deepseek-ai/deepseek-harness
- https://github.com/deepseek-ai/deepseek-harness/blob/master/README.md
- https://github.com/deepseek-ai/deepseek-harness/blob/master/README-zh.md
- https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/architecture.md
- https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/config-catalog.md
- https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/develop/basic/publish.md
- https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/cordis-primer.md
- https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/glossary.md
- https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/capability-seams.md
- https://www.npmjs.com/package/@deepseek-ai/dsh-settings-file


<!-- source: references/upstream-cordis.md -->

# [Upstream Cordis reference] references/upstream-cordis.md

# Cordis 框架调研

> **归档更正(2026-08-13 下载复核)**:本报告撰写时子代理沙箱禁网,未验证 cordis.io。知识库下载脚本实测:`https://cordis.io/` 返回 **308 永久重定向到自身**(curl 跟随即死循环;`www.`/`http` 变体亦不可达)——该站点当前无法访问或已改址。上游权威以 **GitHub 仓库**(`downloads/github/cordis/`:README、package.json、仓库树、全部 md 文档)+ **DSH vendored 源码**(`vendor/cordis/src/*`)为准;cordis.js.org 是否 301 至 cordis.io 仍标 [unverified]。

> 目标读者:DeepSeek Harness 插件开发者。本文为 Cordis 插件框架(https://github.com/cordiverse/cordis)的基础知识调研,作为 DSH 插件开发的知识底座。
>
> 语言约定:中文叙述,保留英文技术术语(`ctx.effect`、`Service`、`Fiber`、`waterfall`、`inject` 等)。
>
> 标注约定:每条事实尽量给出**精确 URL**。凡无法通过一手来源(源码 / 官方文档 / 仓库元数据)核实的论断,标注 **[unverified]**。

---

## 0. 关键结论先行(务必先读)

Cordis 存在**两个明显不同的 API 谱系**,调研时必须区分,否则会混淆:

| 谱系 | 版本 | 代表 API | 状态 |
|---|---|---|---|
| **经典 Cordis**(Koishi 时代) | `cordis` v1–v3,`@cordisjs/core` v3 | `ctx.scope`、`ctx.select`、`ctx.using`、`ctx.provide/inject/get/set`、`ctx.effect/on/root/plugin`;`ctx.loader`(`@koishijs/loader`)、`ctx.registry`(`@koishijs/registry`) | 历史版本,npm 上仍可查(`cordis` 1.5.2 / 2.1.1 / 2.3.1,`@cordisjs/core` 3.17.7) |
| **Cordis v4(重写版)** | `cordis` 4.x,即 DSH 所 vendored 的版本 | `ctx.effect/on/root/plugin/inject`、`ctx.get/set/provide`、`ctx.extend/isolate/intercept`(取代 `scope/select`)、`ctx.registry`(内置)、`ctx.reflect/fiber/events/logger`、`ctx.mixin/accessor`;`ctx.loader` 来自 `@cordisjs/plugin-loader`(loader 本身已是插件) | 当前上游 master 与 DSH 使用 |

**这是本调研最重要的一个事实**:用户问题清单里的 `ctx.scope` 与 `ctx.select/selector` 属于**经典谱系**;DSH 所 vendored 的 v4 已经用 `ctx.extend()` / `ctx.isolate()` / `ctx.intercept()` 取代了它们。`ctx.loader` 在 v4 不再是核心内置,而是 `@cordisjs/plugin-loader` 提供的一个 **Service**(见 §5)。`ctx.registry` 在 v4 变为核心内置(`ctx.registry` 永远存在),不再需要单独安装 registry 包。

- 上游仓库:https://github.com/cordiverse/cordis ,仓库描述为 "Meta-Framework of Spatiotemporal Composability"(时空可组合性的元框架)。作者为 Shigma(Koishi 作者)。
- 上游论文仓库:https://github.com/cordiverse/paper —— "A Programming Paradigm for Spatiotemporal Composability"(《一种时空可组合性的编程范式》)。
- 官方文档站:https://cordis.io/ (当前,v4 API;例如 https://cordis.io/zh-CN/api/core/context.html 、https://cordis.io/zh-CN/api/std/loader.html 、https://cordis.io/zh-CN/guide/starter/config.html )。经典文档站为 https://koishi.js.org/ (Koishi 文档中的 Context 页,如 https://koishi.js.org/api/core/context.html )以及更早的 cordis.js.org。
- DSH 对 Cordis 的 vendored 快照与本地文档(一手、权威):
  - vendor 源码与同步清单:https://github.com/deepseek-ai/deepseek-harness/blob/master/vendor/README.md
  - 概念入门:https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/cordis-primer.md
  - API 参考(生成式):`docs/cordis-api/{context,events,fiber,registry,service,inherited}.md`
  - 动手教程:`docs/cordis-tutorial/01…07`
  - 术语表:https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/glossary.md

---

## 1. 核心插件范式:一切皆插件(Everything is a Plugin)

### 1.1 为什么“一切皆插件”

Cordis 的定义性观点是:**应用完全由插件组合而成**。DSH 的 `AGENTS.md` 第一句即是 "DeepSeek Harness is a plugin-based agent harness on vendored Cordis: **everything is a plugin**"。上游将其提炼为“时空可组合性(Spatiotemporal Composability)”:一个应用 = 若干插件(空间维度,谁和谁并存)在时间轴上的挂载/卸载(时间维度,何时生效)。

由此得到三个推论(见 DSH `docs/cordis-primer.md` 的 “Cordis In Five Ideas”):

1. **插件是一个实现 Service 的对象**:可以是带 `inject` / `apply(ctx)` 字段的函数,也可以是 `Service` 子类(其生命周期由 Cordis 挂载进当前 context)。
2. **context 是服务的仓库**:服务通过稳定键 `ctx.<key>`(如 `ctx.tools`、`ctx.llm`、`ctx.sessions`)被声明;其它插件**通过键名而非 import 具体实现**来获取服务。
3. **依赖通过 `inject` 声明**:插件列出所需服务,并在这些服务存在前一直等待,因此**加载顺序由服务依赖表达,而非手工 boot 序列**。

这意味着:没有“框架引导代码(bootstrap glue)”——插件只描述它贡献什么,`cordis.yml` 负责组合出整个应用。DSH 教程第一章的表述:“There is no framework bootstrap code in your file: a plugin describes what it contributes, and `cordis.yml` composes the application.”(https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/cordis-tutorial/01-first-plugin.md)

### 1.2 插件 = 函数(或类 / 对象),可返回 disposer

`ctx.plugin()` 接受三种插件形态(vendored 源码 `vendor/cordis/src/registry.ts`,`Plugin` 类型):

```ts
// 1. 函数插件:直接调用 callback(ctx, config)
export function apply(ctx: Context, config: Config) { /* ... */ }

// 2. 对象插件:带 apply(ctx, config) 方法
export const objectPlugin = {
  name: 'object-plugin',
  apply(ctx: Context, config: Config) { /* ... */ },
}

// 3. 类插件:Service 子类,用 new (ctx, config) 构造
export class MyService extends Service {
  constructor(ctx: Context) { super(ctx, 'myService') }
}
```

- 函数插件最常见的形态是**具名导出 `apply`**(loader 从模块读取 `apply`),但 `ctx.plugin(fn)` 直接接受函数本身。
- 插件可以返回一个 **disposer**(或 disposer 的数组/迭代器/async 迭代器,见 §2.1 的 `Effect`),在插件卸载时释放资源——这正是“插件 = `function(ctx) → disposer`”这一经典表述的准确含义。注意:v4 中 disposer 的登记被统一收敛进 `ctx.effect()`(见 §2.1),插件主体(`apply`)的返回值与 `ctx.effect` 的执行体共享同一套 `Effect` 形状处理逻辑(`vendor/cordis/src/fiber.ts` 的 `Effect` 类型与 `_execute`)。

### 1.3 Fiber:一个已加载插件实例的运行时句柄

`ctx.plugin()` 返回一个 **Fiber**(同时是 thenable,`await fiber` 等待其完成加载):

- 状态机(`vendor/cordis/src/fiber.ts` 的 `FiberState`):`PENDING → LOADING → ACTIVE → UNLOADING → DISPOSED`,旁路 `FAILED`。
  - `PENDING`:已声明,但某个 `inject` 的服务尚不可用。
  - `LOADING / ACTIVE`:`apply` 正在执行 / 已执行完成。
  - `FAILED`:`apply` 或 config 校验抛错。
  - `UNLOADING / DISPOSED`:disposer 正在运行 / 全部拆除。
- `fiber.uid`:registry 内唯一 id,根 fiber 为 `0`,dispose 后为 `null`。
- `fiber.dispose()`:卸载插件,resolve 时全部清理(含 async disposer)已结束,并**递归卸载其挂载的子插件**。
- `fiber.restart()` / `fiber.update(config)`:按当前/新 config 重载。
- `fiber.getEffects()`:返回当前效果的 `EffectMeta` 诊断树。
- `fiber.name`:展示名,取自最近的具名祖先,否则 `'root'`。

教程第二章(https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/cordis-tutorial/02-lifecycle-and-effects.md )给出完整状态机与示例。

---

## 2. Context API

`Context` 是核心对象:一切 service / event / 生命周期 API 都经 `ctx` 到达。运行时 `Context` 是一个 **Proxy**:普通属性读取走 service 解析器(`ReflectService.handler`),而 `extend()` / `isolate()` / `intercept()` 派生作用域子 context,**不修改父 context**(`vendor/cordis/src/context.ts`)。

### 2.1 生命周期与 effect

- `ctx.effect(execute, label?)`:登记一个带清理语义的副作用。`execute` 立即执行,产生的 disposer 被收集,并在「返回的 disposer 被调用」或「所属 fiber 卸载」二者先发生时运行。**disposer 以注册逆序运行**;可以是 async(卸载时 await 它们)。执行体返回形状 `Effect`:
  - 单个 disposer `() => T`
  - 一个 disposer 的 `Promise`
  - (async)可迭代的多个 disposer——**generator effect 每 yield 一个 disposer 就立即登记一个**
  - 在已 dispose 的 fiber 上调用会抛 `CordisError('INACTIVE_EFFECT')`;返回非法形状抛 `TypeError`。
- `ctx.fiber`:当前 fiber(拥有此 context 的插件实例)。`ctx.effect()` 委托给它。
- **disposer 逆序执行、多个 async disposer 并发**:教程第二章明确提醒“如果拆除步骤必须顺序执行,把它们放进同一个 disposer 并在此 await”(https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/cordis-tutorial/02-lifecycle-and-effects.md )。

> **经典谱系对照**:`ctx.effect()` 在经典 Cordis 中即存在,语义一致(“返回 disposer,随插件卸载而回收”)。v4 额外把 `ctx.on()`、`ctx.plugin()`、`ctx.provide()` 等全部实现为 effect(见下),即 AGENTS.md 的 “**Registrations are effects**”。

### 2.2 作用域派生(v4 取代 scope/select 的机制)

- `ctx.extend(meta?)`:创建继承当前 scope 的子 context,叠加额外元数据;子 context 原型继承父的所有属性,`meta` 自有属性遮蔽继承项;**父不受影响**。
- `ctx.isolate(name, label?)`:为服务 `name` 创建独立服务作用域的子 context。在该子 context 之下,对服务 `name` 的读写解析到新 label 而非父的 label,因此可在不影响父作用域的前提下提供不同实现。传相同 `label` 给两次 `isolate()` 会**合并这两个作用域**(默认 label 是全新 symbol)。
- `ctx.intercept(name, config)`:为在其下启动的插件追加服务专属 intercept config(见 §3.3)。该 config 会被 merge 进服务的解析后 config(祖先条目优先,见 `Service[symbols.resolveConfig]`);父 context 不受影响。

> **`ctx.scope` / `ctx.select` 的说明**:这是**经典 Cordis(v2/v3)**的作用域 API——`ctx.scope` 派生隔离作用域、`ctx.select(name)` 创建在所选服务作用域中运行的选择器。它们在 v4 被 `extend/isolate/intercept` 取代。本文在 vendored v4 源码(`vendor/cordis/src/context.ts`)中**未找到** `scope`/`select` 属性(grep 仅命中注释里的普通英文 “scope”),因此 DSH 实际使用的 v4 无此二者。经典 API 的精确语义请以 Koishi 文档为准(https://koishi.js.org/api/core/context.html 、https://deepwiki.com/koishijs/koishi/2.1-context );下文对该二 API 的转述标注 **[unverified]**:`ctx.scope` 创建隔离作用域(服务提供在作用域内隔离),`ctx.select(name)` 返回一个可在选中服务作用域中消费/提供该服务的 `Selection` 对象(用于隔离多个同名服务的实例)。另有提交 `feat: support ctx.inject(), deprecate ctx.using()`(https://github.com/cordiverse/cordis/commit/b5cd65aa27a18b90aeb6c8f036570cf1a374277a )表明经典 `ctx.using()` 被 `ctx.inject()` 取代。**[unverified 部分如上]**

### 2.3 环境句柄与混合成员

`ctx` 上的环境句柄(`vendor/cordis/src/context.ts` 的 `Context` 接口):

- `ctx.root`:应用根 context(每个子 context 共享它)。标注 `@experimental`。
- `ctx.events`:事件总线 `EventsService`,其方法也 mixin 到 `ctx`(`ctx.on/emit/…`)。
- `ctx.logger`:日志服务 `LoggerService`,可 `ctx.logger(name)` 生成具名 logger,也可直接 `ctx.logger.info(...)`。
- `ctx.registry`:插件注册表 `RegistryService`,其方法 mixin 到 `ctx`(`ctx.plugin/inject`)。
- `ctx.reflect`:反射层 `ReflectService`,支撑 context proxy 与 `ctx.get/provide/…`。
- `ctx.baseUrl?`:解析相对插件/模块 specifier 的 base URL(运行时设定)。
- 静态符号:`Context.effect / filter / isolate / intercept`(对应 `symbols.effect/filter/isolate/intercept`,键为 `Symbol.for('cordis.*')`)。`Context.is(value)` 跨 realm/多份 cordis 判定是否为 context(以全局 symbol 品牌,而非 `instanceof`)。

`ctx` 上由 `ReflectService` mixin 出的方法(`vendor/cordis/src/reflect.ts` 构造器):

```ts
this.mixin('reflect', ['get', 'set', 'provide', 'accessor', 'mixin'])
this.mixin('fiber', ['runtime', 'effect'])
this.mixin('registry', ['inject', 'plugin'])
this.mixin('events', ['on', 'once', 'parallel', 'emit', 'serial', 'bail', 'waterfall'])
```

- `ctx.get(name, strict?)`:无需 inject 直接从 store 读服务;`strict=true`(默认)只返回 provider fiber 当前 ACTIVE 的实现,否则可能返回 `undefined`。这是“可选依赖”的读取方式。
- `ctx.set(name, value)`:覆写某已提供服务的值;**只有提供该服务的 fiber 才能 set**,set 未提供名抛错。
- `ctx.provide(name, value)`:以当前 fiber 为所有者登记一个服务实现;fiber ACTIVE 后对同 isolation 作用域的依赖方可见;disposer 运行或 fiber 卸载时注销(唤醒依赖方)。同作用域重名抛错。
- `ctx.accessor(name, { get, set? })`:定义由 get/set 钩子支撑的计算型 context 属性;随 fiber 卸载移除。
- `ctx.mixin(name, keys)` / `ctx.mixin(source, keys)`:把服务成员直接暴露到 `ctx`(如 `ctx.on` 转发到 `ctx.events.on`,方法绑定到该服务);随 fiber 卸载移除。

> 来源(一手):https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/cordis-api/context.md 与 `vendor/cordis/src/{context,reflect}.ts`。

### 2.4 scope forking 与 dispose 语义小结

- “作用域分叉(scope forking)”在 v4 = `extend()` 派生原型子 context;服务隔离 = `isolate()`;配置拦截 = `intercept()`。三者都不改动父 context(copy-on-write 原型链)。
- 事件监听过滤也通过 context 实现:每个 context 可携带一个 **listener filter**(`Context.filter` symbol,`ctx[Context.filter]`),每次事件分发都会查询它(见 §4.4)。
- dispose 语义:所有注册都是 effect;卸载按 fiber 为单位,disposer 逆序、async 并发;子插件随父插件递归卸载。

---

## 3. Services(服务)

### 3.1 服务定义与提供

- 基类 `Service`(`vendor/cordis/src/service.ts`):在 `ctx` 上暴露具名 API 的抽象基类。子类在构造器调用 `super(ctx, name)`——服务**立即注册**,并随所属 fiber 自动移除。
- `super(ctx, name)` 内部调用 `ctx.reflect.provide(name, this, this[Service.check])`(`service.ts` 构造器),等价于 `ctx.provide(name, value)`。
- `name ??= this.constructor['provide']`:服务名默认取静态 `provide` 字段。
- 若服务带 `[Service.invoke]` 执行体,则实例变为**可调用对象**(如 `ctx.logger()`)。
- 消费方通过 `inject` 或 `ctx.get(name)` 获取;类型上通过 `declare module '@deepseek-ai/cordis' { interface Context { <key>: <Type> } }` 的 **declaration merging** 把键加进 `Context` 接口(编译期,无运行时代码)。

DSH 教程第三章的完整例子(https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/cordis-tutorial/03-services.md ):

```ts
import { Service, type Context } from '@deepseek-ai/cordis'

declare module '@deepseek-ai/cordis' {
  interface Context { greeter: GreeterService }
}

export class GreeterService extends Service {
  constructor(ctx: Context) { super(ctx, 'greeter') }
  greet(who: string) { return `Hello, ${who}!` }
}

export const name = 'greeter'
export function apply(ctx: Context) { ctx.plugin(GreeterService) }
```

消费方:

```ts
export const name = 'consumer'
export const inject = ['greeter']
export function apply(ctx: Context) { console.log(ctx.greeter.greet('world')) }
```

### 3.2 `inject`:声明依赖

- `inject` 有数组/对象两种形式(`Inject` 类型,`vendor/cordis/src/registry.ts`):数组形式仅请求服务;对象形式把每个服务名映射到可选 intercept config。
- `ctx.inject(deps, callback)` 是 `ctx.plugin({ inject, apply: callback })` 的简写:当所需服务可用时执行 callback,且**每当所需服务变化时卸载并重跑**。
- **依赖在加载后仍被跟踪**:若所需服务在运行中消失(其 provider 被卸载或被热替换),所有依赖方插件也会被卸载,并在服务恢复时重新加载——从而避免运行中的消费者持有不可用服务的引用。这也是“服务替换”可配置化的基础。
- 可选依赖:不用 `inject`,在 `apply` 内部用 `ctx.get(name)`(返回 `undefined` 表示未提供)。

### 3.3 intercept(配置拦截 / provider 无关的服务配置)

- `inject` 的对象形式 / `ctx.intercept(name, config)` / loader entry 的 `intercept` 字段,都会把一段 config 合并进该服务在插件 context 下的“解析后 config”。
- 合并规则(`Service[symbols.resolveConfig]`):祖先条目优先(更靠近根的条目先合并),`base` 前置、`head` 后置;服务若声明 `Config.merge` 则用其合并,否则浅 `Object.assign`。
- 典型用途:loader 服务自身声明 `Loader.Intercept = { await?: boolean }`,让依赖 `loader` 的插件在 loader entries 尚在加载时保持 PENDING(`vendor/loader/src/index.ts` 的 `[Service.check]`)。

### 3.4 隔离状态 vs 共享状态

- 默认:同一服务名在一个作用域内**唯一**(同作用域重名 `provide` 抛错 `service "x" has been registered at <fiber>`)。
- 隔离通过 `ctx.isolate(name)` 或 loader 的 `isolate` entry 字段实现:不同 label 各持一份实现。
- Loader 层有 `LocalRealm`(entry 本地隔离,label 后缀 `#<id>`)与 `GlobalRealm`(具名共享隔离,label 后缀 `@<label>`)两种 realm(`vendor/loader/src/config/isolate.ts`)。`isolate: { shell: true }` = 每个 entry 本地隔离;`isolate: { shell: 'shared-label' }` = 同一 label 的 entry 共享同一隔离实例。
- DSH 文档的服务隔离示例(https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/develop/framework/service.md#service-isolation ):两个 group 各自 `isolate: { shell: true }`,则 `plugin-a` 与 `plugin-b` 各看到自己组的 Bash 实例(如 `timeoutMs` 5000 vs 60000),互不影响。

### 3.5 `Service` 的静态符号(扩展点)

`vendor/cordis/src/service.ts` 定义:`Service.init`(类插件构造后运行实例方法)、`Service.check`(传给 `ctx.provide` 的可用性谓词)、`Service.config`(phantom intercept-config 类型参数)、`Service.invoke`(使服务可调用的调用体,如 `ctx.logger()`)、`Service.extend`(派生扩展服务实例)、`Service.tracker`(context 追踪元数据)、`Service.resolveConfig`(上文 intercept 解析)。这些符号键均为 `Symbol.for('cordis.*')`(`vendor/cordis/src/utils.ts`)。

---

## 4. Events(事件)

### 4.1 五种 dispatch mode

`vendor/cordis/src/events.ts` 定义 `DispatchMode` 与五个方法(也 mixin 到 `ctx`):

| Mode | 调用 | 语义 |
|---|---|---|
| `emit` | `ctx.emit(name, ...args)` | 同步广播;忽略返回值,不 await 返回的 promise |
| `parallel` | `await ctx.parallel(name, ...args)` | 所有监听器并发执行,一起 await(`Promise.allSettled`,有错抛 `AggregateError`) |
| `serial` | `await ctx.serial(name, ...args)` | 按注册顺序 await;第一个非 `null/false/undefined`(bail)返回并停止后续 |
| `bail` | `ctx.bail(name, ...args)` | `serial` 的同步版 |
| `waterfall` | `ctx.waterfall(name, ...args, next)` | around-middleware,见 §4.3 |

dispatch mode 是事件公开契约的一部分。DSH 约定新事件用 `@mode` JSDoc 标签标注,供生成目录核对声明与分发点(`docs/cordis-primer.md`)。

### 4.2 类型化事件(declaration merging)

- 事件名经 TypeScript **declaration merging** 声明:`declare module '@deepseek-ai/cordis' { interface Events { 'stats/report'(name: string, count: number): void } }`。
- 然后 `ctx.emit('stats/report', ...)`、`ctx.on('stats/report', (name, count) => ...)` 全类型化。
- 命名约定 `namespace/action`(教程第四章,https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/cordis-tutorial/04-events.md )。DSH 的 AGENTS.md 进一步要求:事件 JSDoc 需 `@mode` 与 payload `@param`;作用域键缺 payload 时需 `@dshScopeScan unsupported`。

### 4.3 waterfall 语义(拦截与短路的机制)

`ctx.waterfall(name, ...args, next)`:每个监听器收到 `(...args, next)`。调用 `next()` 委派给下一个监听器(最终是内置行为);不调用 `next()` 即**否决(veto)短路**剩余链条。`next()` 的返回值沿链回传,监听器可包装/替换它。

纪律(DSH `docs/cordis-primer.md` 与 AGENTS.md):**只观察/注解的 waterfall 监听器必须调用 `next()`**;不调用即刻意短路。DSH 用 waterfall 实现可包装/回答的决策点(如 `agent/request` 替换模型调用配置、`approval/request` 由策略代为回答)。

### 4.4 hooks 与过滤(topic selection / event bus 语义)

- `ctx.on(name, listener, options?)`:登记监听器,返回 disposer;`options` 可为布尔(`prepend` 简写)或 `EventOptions { prepend?, global? }`。
- `ctx.once(name, listener, options?)`:首调后自 dispose。
- `prepend: true`:把监听器插入到同事件已有监听器之前。
- `global: true`:忽略 context filter 检查(见下)。
- **过滤(topic selection)**:`EventsService.dispatch()` 在解析监听器时执行过滤——`const filter = thisArg?.[Context.filter]`,然后 `.filter(hook => hook.global || !filter || filter.call(thisArg, hook.ctx))`。即:
  - 分发方法的第一个参数可以是显式 `thisArg`(作为监听器的 `this`,同时用于过滤)。
  - 若 `thisArg` 携带 `[Context.filter]`(一个 `(listenerCtx) => boolean`),则只有 `global` 监听器或通过 filter 的监听器收到事件。
  - 这就是 DSH 的“作用域过滤分发(scoped dispatch)”在 Cordis 层的机制底座:事件针对某 agent 活动时,以该 agent 的 scope carrier 作为 `thisArg` 分发,filter 只放行无标签监听器与主题自身监听器(见 `docs/glossary.md` 的 `agent-scope`)。
- **事件总线语义**:监听器随所属 fiber 自动 dispose(`register()` 内部就是 `ctx.fiber.effect(...)`,返回 `() => this.unregister(...)`)。`internal/` 前缀事件为框架内部事件。

### 4.5 内置框架事件(`internal/*`)

`vendor/cordis/src/events.ts` 的 `Events` 接口:`internal/plugin`(fiber 创建 / uid 清除)、`internal/status`(fiber 状态变化)、`internal/config`(waterfall,注入激活后解析原始 config)、`internal/service`(服务绑定拦截钩子,无核心 producer)、`internal/update`(waterfall,应用 fiber config 更新,跳过 `next()` 否决)、`internal/get`(waterfall,经 context proxy 读服务)、`internal/set`(waterfall,写服务)、`internal/listener`(bail,监听器注册拦截,非空返回替代注册)、`internal/dispatch`(非 internal 事件分发前的诊断)。

DSH 的 `docs/cordis-api/inherited.md` 还列出 loader/hmr 增补事件:`exit`、`loader/config-update`、`loader/entry-init`、`loader/partial-dispose`、`loader/patch-context`、`hmr/change`、`hmr/reload`。

---

## 5. Loader 与配置(cordis.yml)

### 5.1 Loader 是插件,提供 `ctx.loader` 服务

关键提交:`feat(loader): loader as a cordis plugin`(https://github.com/cordiverse/cordis/commit/fd408a422375972b4ff02a9647063919d055b133 )。v4 中 loader 是 `@cordisjs/plugin-loader`(DSH 里为 `@deepseek-ai/cordis-plugin-loader`),一个 `Service`,通过 `declare module` 增补 `interface Context { loader: Loader }`(`vendor/loader/src/index.ts`)。挂载方式:

```ts
import { Context } from 'cordis'
import Loader from '@cordisjs/plugin-loader'
const root = new Context()
await root.plugin(Loader, { baseUrl: import.meta.url })
```

Loader 核心 API(`vendor/loader/README.md`):

| API | 作用 |
|---|---|
| `loader.create(options, parent?, position?)` | 新增并启动 entry |
| `loader.update(id, options, parent?, position?)` | 更新/移动/重启 entry |
| `loader.remove(id)` | 停止并删除 entry |
| `loader.resolve(id)` | 按 id(含嵌套 `a:b`)解析 entry |
| `loader.resolveGroup(id)` | 解析根组或嵌套组 |
| `loader.await()` | 等待挂起的 import 与 fiber 重载 |
| `loader.locate(fiber?)` | 返回拥有某 fiber 的 entry id |

`@cordisjs/plugin-include` 提供文件后端的 loader tree(读写 YAML/JSON),是 `cordis.yml` 的落地实现。

### 5.2 cordis.yml 的 schema(entry list)

`cordis.yml` 是**顶层数组**,每个元素是一个 entry(`EntryOptions`,`vendor/loader/src/config/entry.ts`):

| 字段 | 说明 |
|---|---|
| `id` | 所在 entry tree 内的稳定 id(用于 diff/更新/删除) |
| `name` | 被 import 的模块 specifier(相对路径或 npm 包名) |
| `config` | 传给插件的配置 |
| `group` | 标记为嵌套组,其 `config` 是子 entry 列表 |
| `disabled` | 停止该 entry 及其后代,但不删除 |
| `inject` | 为该 entry 追加所需服务或 intercept config |
| `intercept` / `isolate` | 服务配置拦截 / 服务隔离(由 loader 的 isolate 子插件增补,`vendor/loader/src/config/isolate.ts`) |

最小示例(`vendor/include/README.md`):

```yaml
- id: timer
  name: '@cordisjs/plugin-timer'
- id: app
  name: ./plugins/app
  config:
    message: hello
```

Include 插件 config:`path`(YAML/JSON 路径,相对 `ctx.baseUrl` 解析)、`initial`(文件缺失时写入的初始列表)、`patches`(读后应用的运行时补丁)、`enableLogs`(loader 日志开关)。文件缺失且无 `initial` 时抛错;有 `initial` 则写出后回读。

### 5.3 插件解析(plugin resolution)

`EntryTree.import(name)`(`vendor/loader/src/config/tree.ts`):

1. `name` 以 `cordis:` 开头 → 读 `ctx.loader.builtins[name.slice(7)]`(内置)。
2. 否则,若存在 Node 内部模块 loader(`ModuleLoader.fromInternal()`,经 `node-addon-require-builtin` 或 `--expose-internals` 取得,兼容 Node 22/23 v1 与 Node 24+ v2 形状),用 `internal.import(name, baseUrl, {})`。
3. 否则,`name` 以 `.` 开头 → `import(new URL(name, baseUrl).href)`(相对 base URL);否则 `import(name)`(裸包名)。

模块导出经 `Loader.unwrapExports()` 归一化 ESM/CJS/default 导出形状(`exports.default ?? exports`,处理 `__esModule`)。DSH 约定 Raw/Web `cordis.yml` 裸插件必须出现在其 resolver manifest 的 `dependencies` 中(由 `verify-cordis-config` 强制)。

### 5.4 `!!js` 计算表达式

- `@cordisjs/plugin-include` 用 js-yaml 的 `Type('tag:yaml.org,2002:js')` 把 `!!js` 标量解析为 `{ __jsExpr: '<expr>' }`(`vendor/include/src/index.ts` 的 `JsExpr` / `entryListSchema`)。
- Loader 在 entry 激活、声明注入已激活之后,对 entry 的 `config` 做 `interpolate(ctx, config)`——递归把 `__jsExpr` 节点替换为 `evaluate(ctx, expr)`(`new Function('ctx','expr','with(ctx){ return eval(expr) }')`,`vendor/loader/src/config/utils.ts`)。
- 用法(DSH 教程第五章,https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/cordis-tutorial/05-config.md ):

  ```yaml
  - name: './config-demo.ts'
    config:
      greeting: !!js process.env.DEMO_GREETING ?? 'Hello'
  ```

- **DSH 扩展**:`disabled: !!js ...` 在每次挂载决策时对 loader context 求值(原始节点留在 options 里,写回保持 `!!js` 形式);`disabled` 是唯一被插值的元数据字段,其余元数据(`name/id/inject/…`)保持字面量。`!!js` 只作用于 `config` 与 entry 的 `disabled` 字段(DSH `docs/cordis-primer.md` “Loader Configuration”)。注意 `!!js`(双感叹号)而非 `!js`(AGENTS.md 强调 "allows `!!js` (never `!js`)")。
- Group/Include 作为“tree carrier”(声明 `EntryGroup.key`)保持自身 config 字面量,以便其嵌套行里的 `!!js` 在各行自己的 fiber 中惰性求值(`vendor/loader/src/index.ts` 的 `internal/config` 钩子)。

### 5.5 filters / patches(过滤与补丁)

- `@cordisjs/plugin-include` 的 `patches` 是对 entry 列表应用的**运行时补丁**(`PatchOptions`,`vendor/include/src/index.ts`):每个 patch 可 `id` 定位目标、`name` 校验(不匹配则警告跳过)、覆盖任意字段,或 `insert`(无 `id` 时追加到顶层;有 `id` 且目标是 group 时插入其子列表)。
- `applyEntryPatches(data, patches, warn)` 是导出的纯函数(DSH 本地修改 #11),供 `dsh --dump-config` 与挂载共用同一 patch 语义;输入永不被原地修改,结果为 detached clone;插入的行会被立即索引,以便**同一列表里后出现的 patch 可配置/禁用先前插入的行**。
- **排序/顺序**:entry **并发启动**,列表位置不保证加载顺序;顺序由服务依赖(`inject`)决定(教程第一章明确:“Entries start concurrently, so list position guarantees nothing… ordering comes from service dependencies, not from position in the file.”)。这对应 §6 的依赖解析。

### 5.6 groups(分组)

`@cordisjs/plugin-group`(DSH `@deepseek-ai/cordis-plugin-group`,默认导出 loader 的 `Group`):

```yaml
- id: tools
  name: '@cordisjs/plugin-group'
  group: true
  config:
    - id: logger
      name: '@cordisjs/plugin-logger-console'
```

- 组自身永远视为启用;禁用组 entry 会阻止其子 entry 运行。
- 嵌套 entry id 用 `:` 分隔(`EntryTree.sep = ':'`),如上例为 `tools:logger`。
- 组作为**一个单元**加载/卸载;可 `isolate` 服务(§3.4)。
- 组更新是事务性的(DSH 本地修改 #8):候选并发启动、收集全部结果、失败时撤销新增并恢复旧条目。

### 5.7 config 校验

插件导出 `Config` 作为 **Standard Schema** 校验器(`@standard-schema/spec`),`RegistryService.plugin()` 在启动前用 `runtime.Config['~standard'].validate(config)` 校验,失败抛 `ValidationError`(`vendor/cordis/src/fiber.ts`)。DSH 用 Schemastery(`Schema.object(...)`)产出 schema。坏 config 使 fiber 进入 `FAILED`、加载失败,**绝不半配置启动**("fail loud")。同步校验仅支持(async 校验抛 `TypeError('Async config validation is not supported')`)。插件 `Config` 也可以是 `Transform`(`schema: true` + `Config: (S) => T`,把用户 config 转运行时 config)。

---

## 6. Registry 与发现(Registry & discovery)

### 6.1 RegistryService(内置 `ctx.registry`)

`vendor/cordis/src/registry.ts`:`ctx.registry` 是核心内置(不再需要单独的 registry 包)。它规范化插件形态、跟踪插件 runtime、启动 fiber、提供对活跃插件 callback 的 map 式遍历:

- `registry.resolve(plugin)`:把三种形态解析为可执行 callback(`typeof plugin === 'function'` 直接返回;对象取其 `apply`)。
- `registry.get/has/delete(plugin)`:查/删 runtime;`delete` 会 dispose 该插件的所有 fiber 并移除 runtime(对应提交 `feat: use ctx.registry.delete() when removing plugins`,https://github.com/cordiverse/cordis/commit/99a7cc6f91c408d18e9165215f7d3afa052f5d1a )。
- `registry.size / keys() / values() / entries() / forEach()`:Map 式遍历已注册 runtime。
- `registry.plugin(plugin, config, getOuterStack?)`:创建(或复用)runtime,再在当前 context 启动新 fiber;非法形态或当前 fiber 已 dispose 抛错。
- 每个 `Plugin.Runtime` 记录:`name`、`fibers`(`DisposableList<Fiber>`)、`callback`(registry 身份键)、`Config`。一个插件 callback 的所有 fiber 共享一个 runtime。

### 6.2 插件名 / 身份

- 插件展示名 `name`(`Plugin.Base.name`,即 `export const name = ...` 或函数名),用于 fiber 诊断与 logger 名。`fiber.name` 继承最近具名祖先,否则 `'root'`。
- 提供名 `provide`(`Plugin.Base.provide`,字符串或数组)供 `Service` 与 loader 读取(声明“我提供哪些服务”)。
- 依赖声明 `inject`、intercept 消费声明 `intercept`(`Dict<boolean>`)。
- `@Inject(name, config?)` 装饰器:类上并入静态 `inject`;方法上延迟到服务可用时再调用。

### 6.3 依赖解析(dependency resolution)

`Fiber` 的依赖状态机(`vendor/cordis/src/fiber.ts`):

- 构造时把 `inject` 解析为 map(`Inject.resolve`),并为每条带 intercept config 的依赖挂到子 context 的 intercept。
- 发布 `internal/plugin` 通知**之后**才解析依赖(loader 可能在该通知中扩展 `inject`),对每个注入名 `_checkImpl(name)`(取当前 isolation label 下的 `Impl`,有 `check` 谓词则执行)。
- `_refresh()` 计算 epoch:所有注入服务的 `fiber.uid` 拼接;只要缺一个实现,epoch = `INACTIVE` → fiber 保持 `PENDING`。
- 服务变化经 `ReflectService.notify(names)` 广播:遍历 registry 所有 runtime/fiber,对注入这些名字的 fiber 重查实现并 `_refresh()`(触发 load/unload),并 emit `internal/service`(带 filter 限定作用域)。这正是“依赖方随服务增删自动重载”的机制。
- `ctx.get(name, strict=false)` 可绕过 inject 直接读(严格模式要求 provider ACTIVE)。

### 6.4 discovery(发现)

- 服务发现:消费者**不 import 具体实现**,通过 `ctx.<key>`(proxy 经 `internal/get` waterfall + 沿 fiber 链向上查 `fiber.store`)或 `ctx.get(name)` 解析。
- 插件发现:`ctx.registry.values()` 枚举所有 runtime/fiber(教程第六章用其诊断 PENDING 状态,https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/cordis-tutorial/06-composition-and-hmr.md )。
- 模块发现:loader 按 specifier 解析(§5.3),支持 `cordis:` 内置、Node 内部 loader、相对/裸 specifier。

---

## 7. 典型示例(canonical code shapes)

以下均为 DSH 教程/源码中的规范形态(每段已在正文给出出处)。

### 7.1 最小函数插件 + cordis.yml

```ts
import type { Context } from '@deepseek-ai/cordis'
export const name = 'hello'
export function apply(ctx: Context) { console.log('hello from my first plugin') }
```

```yaml
- name: './hello.ts'
```

### 7.2 带 effect 的资源与子插件

```ts
export function apply(ctx: Context) {
  const fiber = ctx.plugin(heartbeat)          // 从代码挂载子插件,返回 Fiber
  ctx.effect(() => {                            // 外部资源包成 effect
    const timer = setTimeout(async () => { await fiber.dispose(); process.exit(0) }, 700)
    return () => clearTimeout(timer)            // disposer:卸载时运行
  })
}
```

### 7.3 服务定义 + 消费者

见 §3.1(`GreeterService` / `consumer` 例子)。

### 7.4 类型化事件 + waterfall 短路

```ts
declare module '@deepseek-ai/cordis' {
  interface Events { 'demo/transform'(input: string, next: () => Promise<string>): Promise<string> }
}
export function apply(ctx: Context) {
  ctx.on('demo/transform', async (input, next) => (await next()).toUpperCase())   // 包装下游结果
  ctx.on('demo/transform', async (input, next) => {                               // 决策短路
    if (input.includes('blocked')) return '** blocked **'
    return next()
  })
}
```

### 7.5 带 schema 的配置插件

```ts
import Schema from '@deepseek-ai/schemastery'
export interface Config { greeting: string; targets: string[] }
export const Config: Schema<Config> = Schema.object({
  greeting: Schema.string().default('Hello'),
  targets: Schema.array(String).default(['world']),
})
export function apply(ctx: Context, config: Config) { /* config 已完整校验 */ }
```

### 7.6 上游 README 快速上手(vendored `vendor/cordis/README.md`)

```ts
import { Context, Service } from 'cordis'
declare module 'cordis' {
  interface Context { counter: Counter }
  interface Events { 'app/ready'(message: string): void }
}
class Counter extends Service {
  value = 0
  constructor(ctx: Context) { super(ctx, 'counter') }
  next() { return ++this.value }
}
const greeter = Object.assign((ctx: Context) => {
  ctx.on('app/ready', (message) => ctx.logger.info('%s #%d', message, ctx.counter.next()))
}, { inject: ['counter'] })
const root = new Context()
await root.plugin(Counter)
await root.plugin(greeter)
root.emit('app/ready', 'started')
await root.fiber.dispose()
```

### 7.7 HMR 组合

```yaml
- id: logger
  name: '@deepseek-ai/cordis-plugin-logger-console'
- id: timer
  name: '@deepseek-ai/cordis-plugin-timer'
- id: hmr
  name: '@deepseek-ai/cordis-plugin-hmr'
  config:
    root: ['.']
- id: hello
  name: './hello.ts'
```

---

## 8. Cordis → DeepSeek Harness 概念映射

DSH 是“vendored Cordis 上的、基于插件的 agent harness”,因此 Cordis 概念被大量直接复用并加了领域约定。

### 8.1 vendoring 与 rescope

- DSH 将 Cordis 及其基础库**源码 vendored** 进 `vendor/`(不再依赖 npm),以便“完全拥有框架层(可审计、可打补丁、可钉版本)”(https://github.com/deepseek-ai/deepseek-harness/blob/master/vendor/README.md )。
- 全部改名为 `@deepseek-ai` scope:`cordis` → `@deepseek-ai/cordis`;`@cordisjs/plugin-<x>` → `@deepseek-ai/cordis-plugin-<x>`。目录名与版本号不变。
- 上游 SHA(manifest):`cordis` 4.0.0-rc.7(package.json 显示 4.0.1)、`@cordisjs/plugin-loader` 1.0.0-rc.5 来自 https://github.com/cordiverse/cordis @ `56b3d4f725681cf4556c1a8695a709cc3b6eed74`;`include` 1.0.4 / `group` 1.0.0 / `timer` 1.1.2 / `hmr` 1.0.15 / `logger-console` 1.0.0 来自 https://github.com/deepseek-harness/cordis @ `abb0a307cb1d3b0947f455d590cf5ba922d4caa4`(**08-14 核查:该 fork 仓库已 404**);`cosmokit` 1.8.1、`schemastery` 3.18.0。→ 注意:include/group/timer/hmr/logger-console 来自 **DSH 自己的 cordis fork**,core 与 loader 来自上游 cordiverse。
- 18 条本地修改记录在 `vendor/README.md`(生命周期加固、事务化 loader/include、`!!js`/`disabled` 插值、`@deepseek-ai` rescope 等)。

### 8.2 capability seam(能力接缝)= Service Definition / Provider / Consumer

DSH 把“可替换能力”建模为三角色(`docs/glossary.md` 的 `capability-seam`):

- **Service Definition**:拥有 `ctx.<key>` 与词汇类型的 Cordis `Service`(抽象类如 `ShellExecutor`,或具体 registry 如 `WebRuntime`,**从不是 TS `interface`**)。
- **Service Providers**:提供该服务的一个或多个实现。
- **Consumers**:`inject` 该服务的消费方。

典型:`packages/shell` = `dsh-shell`(Service Definition)、`dsh-bash-local` / `dsh-bash-sandbox`(providers)、`dsh-tool-bash`(Consumer)。一个包可同时拥有多个角色(`dsh-llm` 同时拥有 Service Definition 与 Consumer)。AGENTS.md 强调:“能力接缝 = Service Definition / Service Provider / Consumer 三角色,**永远完整、绝不止一个角色**;仅当角色独立演化时才拆分”。这直接落地了 Cordis 的 §3(服务)与 §1(一切皆插件)。

### 8.3 “scope”一词的两义(易混淆)

DSH 在 Cordis 之上复用“scope”但含义不同(`docs/glossary.md` 的 `agent-scope`):

- Cordis v4 的 `ctx.isolate`/作用域 = 服务实例隔离(§2.2、§3.4)。
- DSH 的 **agent-scope** = 按 agent 的注册可见性(全局 vs 归属某 scope key),其分发过滤机制正是 Cordis 事件系统的 `thisArg` + `Context.filter`(§4.4),与经典 Cordis 的 `ctx.scope`(隔离作用域)**不是一回事**。DSH 的 `ctx.fiber`/`ctx.isolate` 才是 Cordis 原生隔离,agent-scope 是 DSH 自建概念。

### 8.4 事件与注册约定(AGENTS.md 落地 Cordis)

- “**Registrations are effects**”:一切贡献都经 `ctx.effect()` / `ctx.on()`;registry 的 `register()` 返回 disposer。
- “**Waterfall listeners MUST call `next()`**”:来自 Cordis 的 waterfall 语义(§4.3)。
- 类型化事件用 declaration merging + 可合并扩展的 map;事件 JSDoc 需 `@mode` 与 payload `@param`。
- “**Switch on discriminant tags**”:闭包联合用 `assertNever`,可合并扩展联合落入文档化默认分支。
- “**Model-visible ⟺ logged**”:凡到达模型请求的内容必须可由 session log 重建——这是 DSH 在 Cordis 事件系统之上的持久化不变量。
- 服务方法公开文档化参数与返回值;`SessionEventMap` 成员默认 required-on-read。

### 8.5 DSH 直接复用 Cordis 的位置

- `docs/cordis-primer.md`:DSH 自己的“Cordis 五条理念”浓缩。
- `docs/cordis-api/*`:由 `scripts/gen-cordis-catalog.ts` 从 vendored 源码生成、`verify-cordis-catalog` 校验的 API 参考。
- `docs/cordis-tutorial/01…07`:逐步教程,第 7 章把 Cordis 范式接到真实 harness 服务(`ctx.tools.register(defineTool(...))`、`tools/result` 事件、`inject: ['tools']`)。
- 启动器 `vendor/cordis/bin.js`:创建 root `Context` + 挂载 Loader + 读 `./cordis.yml`。
- 示例叶子 `examples/**/cordis.yml`(如 `examples/headless-agent/cordis.yml`)即完整应用组合。

---

## 9. 来源列表(Source list)

### 上游(cordiverse)

1. cordiverse/cordis 仓库:https://github.com/cordiverse/cordis
2. cordiverse/paper(《一种时空可组合性的编程范式》):https://github.com/cordiverse/paper
3. Cordis 文档站(当前 v4 API):https://cordis.io/ ;Context API:https://cordis.io/zh-CN/api/core/context.html ;Loader(实验性):https://cordis.io/zh-CN/api/std/loader.html ;配置:https://cordis.io/zh-CN/guide/starter/config.html
4. 关键提交:loader 变为插件 https://github.com/cordiverse/cordis/commit/fd408a422375972b4ff02a9647063919d055b133 ;`ctx.inject()` 取代 `ctx.using()` https://github.com/cordiverse/cordis/commit/b5cd65aa27a18b90aeb6c8f036570cf1a374277a ;`ctx.registry.delete()` 移除插件 https://github.com/cordiverse/cordis/commit/99a7cc6f91c408d18e9165215f7d3afa052f5d1a ;`ctx.collect()` https://github.com/cordiverse/cordis/commit/3649a2a51815c359521bdc7037142d84f7e6f021 ;events `this` 参数 https://github.com/cordiverse/cordis/commit/e99a3444884eaa3722543d7af105fe9090b6b41a ;services and mixins https://github.com/cordiverse/cordis/commit/86ea62d5b9e4ead60175ee2b7b6409ffb24e8361 ;lifecycle→events 重命名 https://github.com/cordiverse/cordis/commit/15a8c3de50f9e8dcec24074a12a4980591dcbd80 ;traceable object https://github.com/cordiverse/cordis/commit/90257b25bedb88cb9db94c8b005b0737f01a7f8c
5. cordiverse/webui(Cordis App 的 WebUI):https://github.com/cordiverse/webui

### 经典 Cordis / Koishi(用于谱系对照)

6. Koishi Context API(含经典 `ctx.scope`/`ctx.select` 语义):https://koishi.js.org/api/core/context.html ;raw:https://raw.githubusercontent.com/koishijs/koishi/15bb8ba9d48aea26dfd4b4d559322c6bc0d4755d/docs/api/core/context.md
7. DeepWiki 对 Koishi Context 的梳理:https://deepwiki.com/koishijs/koishi/2.1-context
8. Koishi 论坛“关于服务与依赖”:https://forum.koishi.xyz/t/topic/3246/2
9. npm 版本线索:`cordis` https://socket.dev/npm/package/cordis/overview/2.3.1 (及 1.5.2 / 2.1.1)、`@cordisjs/core` https://socket.dev/npm/package/@cordisjs/core/overview/3.17.7

### DeepSeek Harness(vendored 源码 + 本地文档,一手权威)

10. vendoring 清单与 SHA:https://github.com/deepseek-ai/deepseek-harness/blob/master/vendor/README.md
11. 概念入门:https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/cordis-primer.md (中文版 cordis-primer.zh.md)
12. API 参考:https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/cordis-api/context.md 、.../events.md 、.../fiber.md 、.../registry.md 、.../service.md 、.../inherited.md
13. 教程:https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/cordis-tutorial/01-first-plugin.md (02-lifecycle-and-effects / 03-services / 04-events / 05-config / 06-composition-and-hmr / 07-into-the-harness)
14. 服务与依赖(含服务隔离):https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/develop/framework/service.md
15. 术语表:https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/glossary.md
16. vendored 源码(本地读,路径即 GitHub 对应路径):`vendor/cordis/src/{context,events,fiber,logger,reflect,registry,service,utils,index}.ts`、`vendor/loader/src/{index,internal}.ts`、`vendor/loader/src/config/{entry,group,isolate,tree,utils}.ts`、`vendor/include/src/index.ts`、`vendor/group/src/index.ts`、`vendor/{cordis,loader,include,group}/README.md`

### 其它

17. Standard Schema 规范:https://standardschema.dev/
18. Schemastery:https://github.com/shigma/schemastery
19. 社区综述(DSH 第三方文章,非官方):https://www.cnblogs.com/sing1ee/p/22455466

---

## 附:无法核实项清单

- **经典 Cordis `ctx.scope` / `ctx.select` / `ctx.using` 的精确签名与语义**:未能抓取 koishi.js.org 原文(沙箱禁网),仅能据 Koishi Context 页标题与历史提交推断其存在与被取代关系;具体签名与行为标注 **[unverified]**。
- **cordis.js.org 是否仍在线 / 是否 301 至 cordis.io**:未能访问验证,**[unverified]**(搜索仅返回 cordis.io 与 koishi.js.org 两处当前在线文档)。
- **上游 master 的 README 与 docs 目录结构**:以 vendored 快照(pin 于上述 SHA)为准;上游 master 可能已继续演进,**[unverified]**。


<!-- source: references/cordis-paper-and-community.md -->

# [Cordis paper and community] references/

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.