dsh-plugin-guide 0.3.16 → 0.3.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,6 +3,8 @@
3
3
  # 🐳 dsh-plugin-guide
4
4
  - **1024 store channel**: `npm i -g dsh1024` once, then `dsh1024 plugin --profile web add dsh-plugin-guide` (counts toward the [deepseek1024.com](https://deepseek1024.com) install ranking).
5
5
  [![Gitee](https://img.shields.io/badge/Gitee-mirror-c71d23?logo=gitee)](https://gitee.com/perrylink/dsh-plugin-guide)
6
+ [![dshfind](https://dshfind.com/api/badge/PerryLink/dsh-plugin-guide?metric=downloads)](https://dshfind.com/plugins/PerryLink/dsh-plugin-guide?ref=badge)
7
+ [![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)
6
8
 
7
9
  **Everything you need to build [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) plugins.**
8
10
 
@@ -13,6 +15,7 @@
13
15
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
14
16
  [![DSH plugin](https://img.shields.io/badge/dsh--plugin-✅-green)](https://github.com/topics/dsh-plugin)
15
17
  [![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-徽章)
18
+ [![DSH Market](https://raw.githubusercontent.com/2BingLing/dsh-market/master/assets/readme/badge-top-rated.svg)](https://dsh.market/)
16
19
  [![Node](https://img.shields.io/badge/node-%5E22.19%20%7C%7C%20%3E%3D24-brightgreen.svg)](#)
17
20
  [![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)
18
21
  [![Version](https://img.shields.io/github/v/tag/PerryLink/dsh-plugin-guide?label=version)](https://github.com/PerryLink/dsh-plugin-guide/releases)
@@ -29,7 +32,7 @@
29
32
 
30
33
  | Surface | Status |
31
34
  |---|---|
32
- | Harness | DeepSeek Harness `dsh-v0.1.5-rc.2` (adapted 2026-09-09): the session envelope keeps its ignorable field for stored-log read compatibility only - Session.append still cannot stamp it, so audit-gate behavior is unchanged. Verified 2026-09-11 against the dsh-v0.1.5-rc.2 master checkout (full gate chain + profile install smoke). |
35
+ | Harness | DeepSeek Harness `dsh-v0.1.7-alpha.2` (re-synced 2026-09-18, `ddefc45`): the official-docs snapshot is refreshed to alpha.2, the checker now expects the four-clause peer range (`… || >=0.1.6-0 <0.2.0 || >=0.1.7-0 <0.2.0`) single-sourced from the scaffold templates, and a new red line fails `async apply` functions that register after their first `await`. Local gate chain green (40 tests, typecheck, `verify` dogfood); the compat job's real alpha.2 run lands with the next CI push. |
33
36
  | Node | `^22.19.0 || >=24.0.0` (DeepSeek Harness runtime) |
34
37
  | Platforms | All (plain ESM bundle; no native code, no network) |
35
38
  | Model | Any (no model interaction) |
@@ -206,13 +209,16 @@ pwsh -File scripts/verify-kit.ps1 # critical paths + link scan (+ docs drift w
206
209
 
207
210
  ## PerryLink DSH Plugin Family
208
211
 
209
- This project is one of the [40 DeepSeek Harness plugins](https://github.com/PerryLink) maintained by [PerryLink](https://github.com/PerryLink). If this one helps you, the others likely will too:
212
+ This project is one of the **45 DeepSeek Harness plugins** maintained by [PerryLink](https://github.com/PerryLink). If this one helps you, the others likely will too:
210
213
 
211
214
  | Plugin | One-liner |
212
215
  |---|---|
213
216
  | **[dsh-auto-review](https://github.com/PerryLink/dsh-auto-review)** | Second-model auto-review on the approval chain, fail-closed by default | |
217
+ | **[dsh-autotier](https://github.com/PerryLink/dsh-autotier)** | Automatic strong/cheap model-tier routing with deterministic risk guards and a `/tier` command | |
214
218
  | **[dsh-background-agents](https://github.com/PerryLink/dsh-background-agents)** | Durable background child agents with a Web UI sidebar, messaging and interrupt | |
215
219
  | **[dsh-budget](https://github.com/PerryLink/dsh-budget)** | Cost governance for DeepSeek Harness: budgets, carbon, and latency in one panel. | |
220
+ | **[dsh-catalog](https://github.com/PerryLink/dsh-catalog)** | DSH Desktop Market standard catalog source for the PerryLink family | |
221
+ | **[dsh-cert-mcp](https://github.com/PerryLink/dsh-cert-mcp)** | Read-only MCP server exposing the certification registry: grades, snapshots and five-dimension evidence | |
216
222
  | **[dsh-checkpoint-rewind](https://github.com/PerryLink/dsh-checkpoint-rewind)** | Claude Code /rewind-equivalent: snapshots, session forks, one-shot restore | |
217
223
  | **[dsh-claude-move](https://github.com/PerryLink/dsh-claude-move)** | Migrate Claude Code sessions, memory, skills and CLAUDE.md into DSH | |
218
224
  | **[dsh-click](https://github.com/PerryLink/dsh-click)** | Cross-platform native desktop control for DeepSeek Harness — Windows first. | |
@@ -225,6 +231,7 @@ This project is one of the [40 DeepSeek Harness plugins](https://github.com/Perr
225
231
  | **[dsh-fund-research](https://github.com/PerryLink/dsh-fund-research)** | Deterministic research reports for Chinese public mutual funds | |
226
232
  | **[dsh-github](https://github.com/PerryLink/dsh-github)** | GitHub PR/issues integration for DSH, every write gated by approval | |
227
233
  | **[dsh-industry-research](https://github.com/PerryLink/dsh-industry-research)** | Industry research orchestration that seals its deliverables through this plugin's `ctx.researchReport.assemble` | |
234
+ | **[dsh-laya](https://github.com/PerryLink/dsh-laya)** | Laya typed decisions (`noul`/`choice`/`score`) as a first-class Cordis service and model-visible tools | |
228
235
  | **[dsh-library](https://github.com/PerryLink/dsh-library)** | Local document knowledge base for DeepSeek Harness. | |
229
236
  | **[dsh-local-ai](https://github.com/PerryLink/dsh-local-ai)** | Local-model (Ollama) integration for DeepSeek Harness. | |
230
237
  | **[dsh-lsp-actions](https://github.com/PerryLink/dsh-lsp-actions)** | LSP diagnostics, formatting, completion, code actions and rename over language servers | |
@@ -234,27 +241,24 @@ This project is one of the [40 DeepSeek Harness plugins](https://github.com/Perr
234
241
  | **[dsh-observe](https://github.com/PerryLink/dsh-observe)** | OpenTelemetry and Langfuse observability exporter for DeepSeek Harness. | |
235
242
  | **[dsh-output-styles](https://github.com/PerryLink/dsh-output-styles)** | Claude Code outputStyles-equivalent runtime style switching | |
236
243
  | **[dsh-permission-rules](https://github.com/PerryLink/dsh-permission-rules)** | Claude Code-style declarative allow/deny/ask permission rules with audit | |
237
- | **[dsh-reach](https://github.com/PerryLink/dsh-reach)** | Multi-channel approval/question bridge: WeChat/Telegram/Feishu, session console |
244
+ | **[dsh-plugin-certification](https://github.com/PerryLink/dsh-plugin-certification)** | Community certification registry with repro-checkable grades and badges | |
245
+ | **[dsh-plugin-doctor](https://github.com/PerryLink/dsh-plugin-doctor)** | Zero-dependency static + sandbox smoke detector for DSH plugins | |
246
+ | **[dsh-plugin-guide](https://github.com/PerryLink/dsh-plugin-guide)** | Plugin-development knowledge base as an on-demand agent skill | |
247
+ | **[dsh-plugin-kit](https://github.com/PerryLink/dsh-plugin-kit)** | Shared zero-runtime-dependency toolkit for the PerryLink DSH plugins | |
248
+ | **[dsh-plugin-upgrade](https://github.com/PerryLink/dsh-plugin-upgrade)** | One-package, one-corridor-index plugin upgrade skill: routes a repository to the matching closed corridor card | |
249
+ | **[dsh-plugin-upgrade-015](https://github.com/PerryLink/dsh-plugin-upgrade-015)** | Merged `0.1.3-alpha.1` → `0.1.5-rc.1` upgrade corridor card plus a zero-dependency seam scanner | |
250
+ | **[dsh-reach](https://github.com/PerryLink/dsh-reach)** | Multi-channel approval/question bridge: WeChat/Telegram/Feishu, session console | |
238
251
  | **[dsh-research-report](https://github.com/PerryLink/dsh-research-report)** | Verifiable research-report engine: content-addressed evidence ledger and sealed versions | |
239
252
  | **[dsh-score](https://github.com/PerryLink/dsh-score)** | Multi-dimensional quality scoring for DeepSeek Harness plugins. | |
240
253
  | **[dsh-session-pin](https://github.com/PerryLink/dsh-session-pin)** | Pin sessions in the Web sidebar with durable ordering | |
241
254
  | **[dsh-session-sync](https://github.com/PerryLink/dsh-session-sync)** | Cross-device session sync for DeepSeek Harness — a dedicated git mirror of your session store. | |
242
255
  | **[dsh-skill-pack-security](https://github.com/PerryLink/dsh-skill-pack-security)** | Security-audit skill pack: secret scan, dependency and supply-chain review | |
243
256
  | **[dsh-talk](https://github.com/PerryLink/dsh-talk)** | Voice-first session loop for DeepSeek Harness: talk to it, hear it answer. | |
257
+ | **[dsh-team-rooms](https://github.com/PerryLink/dsh-team-rooms)** | Cross-session team rooms: shared message bus, task board and timeline | |
244
258
  | **[dsh-test-drive](https://github.com/PerryLink/dsh-test-drive)** | Isolated install-and-smoke test drives for DeepSeek Harness plugins. | |
245
- | **[dsh-ticktick](https://github.com/PerryLink/dsh-ticktick)** | TickTick/Dida365 task bridge: session-header panel + 11 tools |
259
+ | **[dsh-ticktick](https://github.com/PerryLink/dsh-ticktick)** | TickTick/Dida365 task bridge: session-header panel + 11 tools | |
246
260
  | **[dsh-translate](https://github.com/PerryLink/dsh-translate)** | Vendor parameter translation and deterministic JSON repair for DeepSeek Harness. | |
247
- | **[dsh-wechat](https://github.com/pan17/dsh-wechat)** | WeChat ↔ DSH bridge (Tencent iLink bot): text/image/file/voice, approvals in chat |
248
- | **[dsh-autotier](https://github.com/PerryLink/dsh-autotier)** | Automatic strong/cheap model-tier routing with deterministic risk guards and a `/tier` command | |
249
- | **[dsh-catalog](https://github.com/PerryLink/dsh-catalog)** | DSH Desktop Market standard catalog source for the PerryLink family | |
250
- | **[dsh-cert-mcp](https://github.com/PerryLink/dsh-cert-mcp)** | Read-only MCP server exposing the certification registry: grades, snapshots and five-dimension evidence | |
251
- | **[dsh-kit](https://github.com/PerryLink/dsh-kit)** | One-command starter pack that installs the core family | |
252
- | **[dsh-plugin-certification](https://github.com/PerryLink/dsh-plugin-certification)** | Community certification registry with repro-checkable grades and badges | |
253
- | **[dsh-plugin-kit](https://github.com/PerryLink/dsh-plugin-kit)** | Shared zero-runtime-dependency toolkit for the PerryLink DSH plugins | |
254
- | **[dsh-plugin-portal](https://github.com/PerryLink/dsh-plugin-portal)** | Zero-dependency static portal rendering the whole plugin family as one page | |
255
- | **[dsh-plugin-upgrade-015](https://github.com/PerryLink/dsh-plugin-upgrade-015)** | Merged `0.1.3-alpha.1` → `0.1.5-rc.1` upgrade corridor card plus a zero-dependency seam scanner | |
256
- | **[dsh-team-rooms](https://github.com/PerryLink/dsh-team-rooms)** | Cross-session team rooms: shared message bus, task board and timeline | |
257
- | **[dsh-plugin-doctor](https://github.com/PerryLink/dsh-plugin-doctor)** | Zero-dependency static + sandbox smoke detector for DSH plugins | |
261
+
258
262
 
259
263
  ## Disclaimer
260
264
 
@@ -134,6 +134,15 @@ function isDir(path) {
134
134
 
135
135
  //#endregion
136
136
  //#region src/cli/templates.ts
137
+ /**
138
+ * The canonical `@deepseek-ai/dsh-*` peer range, single-sourced here and
139
+ * substituted into every scaffolded package.json through the
140
+ * `{{dshPeerRange}}` placeholder. Keep it identical to the canonical range in
141
+ * `dsh-plugin-kit/data/peer-range.json`: one clause per prerelease tuple is
142
+ * required by semver's prerelease rule, and the `-0` floor covers the whole
143
+ * 0.1.6 tuple (never the bare `>=0.1.6` form).
144
+ */
145
+ const DSH_PEER_RANGE = ">=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";
137
146
  /** Resolve the templates root: env override first, then candidate walks. */
138
147
  function resolveTemplatesRoot() {
139
148
  const env = process.env.DSH_PLUGIN_DEV_TEMPLATES;
@@ -159,7 +168,7 @@ function walkFiles(dir, base = "") {
159
168
  }
160
169
  /** Substitute placeholders in template text. */
161
170
  function renderTemplate(text, context) {
162
- return text.replaceAll("{{pkgName}}", context.pkgName).replaceAll("{{name}}", context.name).replaceAll("{{version}}", context.version).replaceAll("{{year}}", context.year);
171
+ return text.replaceAll("{{pkgName}}", context.pkgName).replaceAll("{{name}}", context.name).replaceAll("{{version}}", context.version).replaceAll("{{year}}", context.year).replaceAll("{{dshPeerRange}}", DSH_PEER_RANGE);
163
172
  }
164
173
  /** Render every file of a language template. */
165
174
  function renderScaffold(lang, context) {
@@ -295,6 +304,11 @@ const SKILL_SECTIONS = {
295
304
  file: "guide/plugin-dev-guide.md",
296
305
  section: "§3.3",
297
306
  heading: "注册即 effect(disposer 可逆)"
307
+ },
308
+ "redline-async-apply-registration": {
309
+ file: "guide/plugin-dev-guide.md",
310
+ section: "§3.3",
311
+ heading: "async apply 首个 await 之后不得注册(卸载窗口竞态)"
298
312
  }
299
313
  };
300
314
  /** Fallback citation for any check id not in the table. */
@@ -499,7 +513,8 @@ function runCheck(options) {
499
513
  checkRedlinePersonaRole(root),
500
514
  checkRedlineWaterfallNext(root),
501
515
  checkRedlineNoHardcodedTunables(root),
502
- checkRedlineEffectRegistration(root)
516
+ checkRedlineEffectRegistration(root),
517
+ checkRedlineAsyncApplyRegistration(root)
503
518
  ];
504
519
  const report = buildReport(root, readCliVersion(), checks);
505
520
  return {
@@ -775,7 +790,7 @@ function checkManifestPeers(root, pkg) {
775
790
  const expected = {};
776
791
  for (const imp of harnessImports) if (imp === "@deepseek-ai/cordis") expected[imp] = "^4.0.2";
777
792
  else if (imp === "@deepseek-ai/schemastery") expected[imp] = "^3.18.2";
778
- else if (imp.startsWith("@deepseek-ai/dsh-")) expected[imp] = ">=0.1.0-rc.8 <0.2.0";
793
+ else if (imp.startsWith("@deepseek-ai/dsh-")) expected[imp] = DSH_PEER_RANGE;
779
794
  const problems = [];
780
795
  for (const [pkgName, range] of Object.entries(expected)) {
781
796
  const declared = peers[pkgName];
@@ -1117,6 +1132,215 @@ function checkRedlineEffectRegistration(root) {
1117
1132
  skillRef: ref
1118
1133
  };
1119
1134
  }
1135
+ /** Registration idioms that must never run after the first `await` of an async apply. */
1136
+ const ASYNC_APPLY_REGISTRATION = [
1137
+ ["ctx.effect", /\bctx\.effect\s*\(/],
1138
+ ["ctx.on", /\bctx\.on\s*\(/],
1139
+ ["ctx.provide", /\bctx\.provide\s*\(/],
1140
+ ["ctx.plugin", /\bctx\.plugin\s*\(/],
1141
+ ["register()", /\.register\s*\(/]
1142
+ ];
1143
+ /** Braces of the block whose `{` is at `openIdx`, skipping strings and comments. Returns the inner body or undefined. */
1144
+ function blockBody(text, openIdx) {
1145
+ let depth = 0;
1146
+ let quote = null;
1147
+ let lineComment = false;
1148
+ let blockComment = false;
1149
+ for (let i = openIdx; i < text.length; i++) {
1150
+ const ch = text[i];
1151
+ const next = text[i + 1];
1152
+ if (lineComment) {
1153
+ if (ch === "\n") lineComment = false;
1154
+ continue;
1155
+ }
1156
+ if (blockComment) {
1157
+ if (ch === "*" && next === "/") {
1158
+ blockComment = false;
1159
+ i++;
1160
+ }
1161
+ continue;
1162
+ }
1163
+ if (quote) {
1164
+ if (ch === "\\") {
1165
+ i++;
1166
+ continue;
1167
+ }
1168
+ if (ch === quote) quote = null;
1169
+ continue;
1170
+ }
1171
+ if (ch === "/" && next === "/") {
1172
+ lineComment = true;
1173
+ i++;
1174
+ continue;
1175
+ }
1176
+ if (ch === "/" && next === "*") {
1177
+ blockComment = true;
1178
+ i++;
1179
+ continue;
1180
+ }
1181
+ if (ch === "\"" || ch === "'" || ch === "`") {
1182
+ quote = ch;
1183
+ continue;
1184
+ }
1185
+ if (ch === "{") depth++;
1186
+ else if (ch === "}") {
1187
+ depth--;
1188
+ if (depth === 0) return text.slice(openIdx + 1, i);
1189
+ }
1190
+ }
1191
+ }
1192
+ /** Index just past the first top-level (brace-depth 0) `await` keyword, or -1. */
1193
+ function firstTopLevelAwait(body) {
1194
+ let depth = 0;
1195
+ let quote = null;
1196
+ let lineComment = false;
1197
+ let blockComment = false;
1198
+ for (let i = 0; i < body.length - 5; i++) {
1199
+ const ch = body[i];
1200
+ const next = body[i + 1];
1201
+ if (lineComment) {
1202
+ if (ch === "\n") lineComment = false;
1203
+ continue;
1204
+ }
1205
+ if (blockComment) {
1206
+ if (ch === "*" && next === "/") {
1207
+ blockComment = false;
1208
+ i++;
1209
+ }
1210
+ continue;
1211
+ }
1212
+ if (quote) {
1213
+ if (ch === "\\") {
1214
+ i++;
1215
+ continue;
1216
+ }
1217
+ if (ch === quote) quote = null;
1218
+ continue;
1219
+ }
1220
+ if (ch === "/" && next === "/") {
1221
+ lineComment = true;
1222
+ i++;
1223
+ continue;
1224
+ }
1225
+ if (ch === "/" && next === "*") {
1226
+ blockComment = true;
1227
+ i++;
1228
+ continue;
1229
+ }
1230
+ if (ch === "\"" || ch === "'" || ch === "`") {
1231
+ quote = ch;
1232
+ continue;
1233
+ }
1234
+ if (ch === "{") depth++;
1235
+ else if (ch === "}") depth--;
1236
+ else if (depth === 0 && ch === "a" && body.startsWith("await", i)) {
1237
+ const before = i === 0 ? "" : body[i - 1];
1238
+ const after = body[i + 5] ?? "";
1239
+ if (!/[A-Za-z0-9_$]/.test(before) && !/[A-Za-z0-9_$]/.test(after)) return i + 5;
1240
+ }
1241
+ }
1242
+ return -1;
1243
+ }
1244
+ /** First top-level registration idiom after `fromIdx` (nested function bodies excluded), or undefined. */
1245
+ function registrationAfter(body, fromIdx) {
1246
+ let depth = 0;
1247
+ let quote = null;
1248
+ let lineComment = false;
1249
+ let blockComment = false;
1250
+ for (let i = fromIdx; i < body.length; i++) {
1251
+ const ch = body[i];
1252
+ const next = body[i + 1];
1253
+ if (lineComment) {
1254
+ if (ch === "\n") lineComment = false;
1255
+ continue;
1256
+ }
1257
+ if (blockComment) {
1258
+ if (ch === "*" && next === "/") {
1259
+ blockComment = false;
1260
+ i++;
1261
+ }
1262
+ continue;
1263
+ }
1264
+ if (quote) {
1265
+ if (ch === "\\") {
1266
+ i++;
1267
+ continue;
1268
+ }
1269
+ if (ch === quote) quote = null;
1270
+ continue;
1271
+ }
1272
+ if (ch === "/" && next === "/") {
1273
+ lineComment = true;
1274
+ i++;
1275
+ continue;
1276
+ }
1277
+ if (ch === "/" && next === "*") {
1278
+ blockComment = true;
1279
+ i++;
1280
+ continue;
1281
+ }
1282
+ if (ch === "\"" || ch === "'" || ch === "`") {
1283
+ quote = ch;
1284
+ continue;
1285
+ }
1286
+ if (ch === "{") depth++;
1287
+ else if (ch === "}") {
1288
+ if (depth === 0) break;
1289
+ depth--;
1290
+ continue;
1291
+ }
1292
+ if (depth !== 0) continue;
1293
+ for (const [kind, re] of ASYNC_APPLY_REGISTRATION) {
1294
+ const m = re.exec(body.slice(i));
1295
+ if (m) return [kind, body.slice(i, i + Math.min(m[0].length, 40))];
1296
+ }
1297
+ }
1298
+ }
1299
+ function checkRedlineAsyncApplyRegistration(root) {
1300
+ const ref = skillRefFor("redline-async-apply-registration");
1301
+ const files = listSourceFiles(root);
1302
+ const flagged = [];
1303
+ for (const file of files) {
1304
+ const text = readText(file) ?? "";
1305
+ const re = /export\s+async\s+function\s+apply\s*\(/g;
1306
+ let match;
1307
+ while ((match = re.exec(text)) !== null) {
1308
+ const bodyStart = text.indexOf("{", match.index);
1309
+ if (bodyStart < 0) continue;
1310
+ const body = blockBody(text, bodyStart);
1311
+ if (body === void 0) continue;
1312
+ const awaitIdx = firstTopLevelAwait(body);
1313
+ if (awaitIdx < 0) continue;
1314
+ const hit = registrationAfter(body, awaitIdx);
1315
+ if (hit) flagged.push(`${relativePath(root, file)}: async apply calls ${hit[0]} (${hit[1]}) after its first await — registration belongs in ctx.effect()/ctx.on() or before any await`);
1316
+ }
1317
+ }
1318
+ if (flagged.length > 0) return {
1319
+ id: "redline-async-apply-registration",
1320
+ severity: "error",
1321
+ kind: "heuristic",
1322
+ status: "fail",
1323
+ message: "async apply registers after its first await (unload-window race)",
1324
+ skillRef: ref,
1325
+ detail: flagged
1326
+ };
1327
+ if (!files.some((f) => /export\s+async\s+function\s+apply\s*\(/.test(readText(f) ?? ""))) return {
1328
+ id: "redline-async-apply-registration",
1329
+ severity: "error",
1330
+ kind: "heuristic",
1331
+ status: "skip",
1332
+ message: "no async apply function found to inspect",
1333
+ skillRef: ref
1334
+ };
1335
+ return {
1336
+ id: "redline-async-apply-registration",
1337
+ severity: "error",
1338
+ kind: "heuristic",
1339
+ status: "pass",
1340
+ message: "no registration after the first await of any async apply",
1341
+ skillRef: ref
1342
+ };
1343
+ }
1120
1344
  function relativePath(root, file) {
1121
1345
  return file.replace(root.replace(/[\\/]$/, ""), "").replace(/^[\\/]/, "");
1122
1346
  }
@@ -1466,7 +1690,7 @@ function suggestionsFor(step, result) {
1466
1690
  const base = [`tail of "${step}":\n${tailOf(result)}`];
1467
1691
  if (result.timedOut) base.push(`command timed out; raise --timeout or --smoke-timeout`);
1468
1692
  if (step === "install") {
1469
- base.push("ensure the dsh CLI is @deepseek-ai/dsh@0.1.5-rc.1 (older builds such as the rc.6 line do not satisfy the compat pin)");
1693
+ base.push("ensure the dsh CLI is @deepseek-ai/dsh@0.1.7-alpha.2 (older builds such as the rc.6 line do not satisfy the compat pin)");
1470
1694
  base.push("confirm the profile allowlist matches the repo compat workflow (native builds allowlisted)");
1471
1695
  }
1472
1696
  if (step === "headless") base.push("a hang usually means an injected service stayed pending; the smoke timeout surfaces exactly that");
@@ -1587,8 +1811,8 @@ async function main(argv, cwd = process.cwd()) {
1587
1811
  dshBin: resolveDsh(flagString(flags, "dsh")),
1588
1812
  pnpmBin: resolvePnpm(flagString(flags, "pnpm")),
1589
1813
  profile: flagString(flags, "profile", "compat") ?? "compat",
1590
- base: flagString(flags, "base", "@deepseek-ai/dsh-base@0.1.5-rc.1") ?? "@deepseek-ai/dsh-base@0.1.5-rc.1",
1591
- headless: flagString(flags, "headless", "@deepseek-ai/dsh-headless@0.1.5-rc.1") ?? "@deepseek-ai/dsh-headless@0.1.5-rc.1",
1814
+ base: flagString(flags, "base", "@deepseek-ai/dsh-base@0.1.7-alpha.2") ?? "@deepseek-ai/dsh-base@0.1.7-alpha.2",
1815
+ headless: flagString(flags, "headless", "@deepseek-ai/dsh-headless@0.1.7-alpha.2") ?? "@deepseek-ai/dsh-headless@0.1.7-alpha.2",
1592
1816
  timeoutMs: timeoutMs(flags, "timeout", "DSH_PLUGIN_DEV_TIMEOUT", DEFAULT_TIMEOUT_MS),
1593
1817
  smokeTimeoutMs: timeoutMs(flags, "smoke-timeout", "DSH_PLUGIN_DEV_SMOKE_TIMEOUT", DEFAULT_SMOKE_TIMEOUT_MS)
1594
1818
  });
@@ -300,7 +300,7 @@ LLM 适配器同理:继承 `LlmAdapter` 实现 `stream(options)`,`ctx.llm.re
300
300
  | 同会话目标 | `ctx.goals`;经 `agent/*` 继续 |
301
301
  | fork 活跃会话 | `ctx.sessions.fork(source, boundary?, childSessionId?)` |
302
302
  | 只对一个 agent 注册 | 用该 agent 的 `agent.ctx`(作用域注册) |
303
- | Hook 系统(用户/项目级) | `agent/session-start`、`agent/pre-step`、`agent/request`、`tools/pre-execute`、`tools/post-execute`、`agent/turn-stopping` 监听器 |
303
+ | Hook 系统(用户/项目级) | `agent/created`(serial)、`agent/pre-step`、`agent/request`、`tools/pre-execute`、`tools/post-execute`、`agent/turn-stopping` 监听器 |
304
304
  | 上下文压缩 | `ctx.compaction` 缝 + `dsh-compaction-basic`;自动压力走 serial `agent/pre-step`,溢出恢复走 `agent/request-error` |
305
305
  | 系统提示词配置 | `ctx.systemPrompt.section()`(带排序与作用域内遮蔽) |
306
306
  | 计划模式 | `@deepseek-ai/dsh-plan-mode`(`plan/mode` 日志态、`/plan`、`exit_plan_mode` 工具) |
@@ -34,22 +34,24 @@ Keep a per-repo `AGENTS.md` stating that repository's own rules. As a portfolio
34
34
 
35
35
  ## 2. The version-line matrix
36
36
 
37
- 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`. A portfolio pinned to one line breaks the moment a user installs from another.
37
+ 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.
38
38
 
39
- The pattern that admits both windows:
39
+ 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):
40
40
 
41
41
  ```jsonc
42
42
  {
43
43
  "peerDependencies": {
44
- "@deepseek-ai/cordis": "^4.0.2",
45
- "@deepseek-ai/dsh-tools": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0"
44
+ "@deepseek-ai/cordis": "^4.0.4",
45
+ "@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"
46
46
  },
47
47
  "devDependencies": {
48
- "@deepseek-ai/dsh-tools": "0.1.5-rc.1"
48
+ "@deepseek-ai/dsh-tools": "0.1.7-alpha.2"
49
49
  }
50
50
  }
51
51
  ```
52
52
 
53
+ 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.
54
+
53
55
  Rules that follow:
54
56
 
55
57
  - **`devDependencies` pin the newest published line.** Your typecheck must fail before your users do.
@@ -34,22 +34,24 @@
34
34
 
35
35
  ## 2. 版本线矩阵
36
36
 
37
- 开发者预览期,宿主会同时存在多条线,例如 `0.1.2-rc.1`、`0.1.5-alpha.1`、`0.1.5-rc.1`。只钉一条线的组合,在用户从另一条线安装的那一刻就坏。
37
+ 开发者预览期,宿主会同时存在多条线,例如 `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`。只钉一条线的组合,在用户从另一条线安装的那一刻就坏。
38
38
 
39
- 同时接受两个窗口的写法:
39
+ 同时接受全部窗口的写法(每个预发布 tuple 一条子句;每个 `-0` 下界覆盖该 tuple 的全部预发布,绝不写成裸 `>=0.1.7` 形态):
40
40
 
41
41
  ```jsonc
42
42
  {
43
43
  "peerDependencies": {
44
- "@deepseek-ai/cordis": "^4.0.2",
45
- "@deepseek-ai/dsh-tools": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0"
44
+ "@deepseek-ai/cordis": "^4.0.4",
45
+ "@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"
46
46
  },
47
47
  "devDependencies": {
48
- "@deepseek-ai/dsh-tools": "0.1.5-rc.1"
48
+ "@deepseek-ai/dsh-tools": "0.1.7-alpha.2"
49
49
  }
50
50
  }
51
51
  ```
52
52
 
53
+ 范围的宽度只等于它最新那条子句,而 semver 在这里很容易让人写错、且**看起来是对的**:**一个范围只有在某条比较符带着同一个 `[major, minor, patch]` tuple 且带预发布时,才接纳该预发布版本。** 所以一条止于 `>=0.1.6-0 <0.2.0` 的带子接纳 `0.1.6-alpha.2`,却**不**接纳 `0.1.7-alpha.2`——读起来像"0.2 之前全都要",行为上却是"0.1.6 tuple 之后一概不要"。补上该线自己的子句就是全部修法;用 `semver.satisfies('0.1.7-alpha.2', range)` 去验,**不要用眼睛看**。
54
+
53
55
  由此得到的规则:
54
56
 
55
57
  - **`devDependencies` 钉最新已发布的线。** 类型检查要赶在你的用户之前报错。
@@ -11,7 +11,7 @@
11
11
  > - 用法:开发插件/排障时按「症状 → 位置 → 规避」查;条目后附原讨论,官方有新回复时以原帖为准。
12
12
  > - 诚实标注:无法在源码复核的环节(依赖未安装的半边)已注明。
13
13
 
14
- ## 1. 仍未修复(29 项 + 2 项部分修复(#1、#2)+ 1 项已修复(#27,保留为修复记录),按严重度排序)
14
+ ## 1. 仍未修复(30 项 + 2 项部分修复(#1、#2)+ 1 项已修复(#27,保留为修复记录),按严重度排序)
15
15
 
16
16
  | # | 问题 | 位置(@ddefc45,2026-09-19 复核) | 临时规避 | 讨论 |
17
17
  |---|---|---|---|---|
@@ -47,8 +47,9 @@
47
47
  | 30 | http-proxy 把 `[::1]` 写进子进程 `no_proxy`/`NO_PROXY` → httpx 系 MCP server 崩溃(undici 专用括号项泄漏到子进程 env) | `packages/util/http-proxy/src/policy.ts:33`(LOOPBACK_NO_PROXY 含 `[::1]`,注释 `:25-32` 自认是为 undici)、`:206-210`;`install.ts:79-92,113-129`;harness 自身匹配器无需括号项(`:279-295` 去括号) | env 写入侧只写裸 `::1`,undici 消费处保留括号项(两处消费者分离) | #6655 |
48
48
  | 31 | 粘贴图片惰性持有 File 快照:剪贴板同步(如微信输入法跨设备复制)后提交时 FileReader NotFoundError | `ui-conversation/src/client/service.ts:73-80`(browserDraftAttachment 只存 File+objectURL)、`:124-135`(base64ImageOf)、`:286-291`;对比文件类立即上传 `:316-324` | 粘贴后立即发送,或拖拽/文件选择 | #6673 |
49
49
  | 32 | web-fetch NAT64 探测无守卫:无 DNS64 网络(ipv4only.arpa 不解析)下所有双栈主机 fetch 全灭 | `packages/web/web-fetch-http/src/network.ts:90-92`(无 try/catch)、`:113-134`(discoverNat64Prefixes)、`:38`;SSRF 检查独立(`:96-106`) | 无产品内规避;补丁方向=ENOTFOUND/ENODATA 视为无前缀 | #6664 |
50
+ | 33 | 官方 cookbook 仍在教已删除的 `settings.plugin.item` 槽(alpha.2 已改为 `plugins.item` 列表槽:`id`/`order`/`label` + props `{view:'summary'|'page'}`) | `docs/cookbook/adding-a-settings-card.md:50,62-63`(+.zh;@ddefc45,0.1.6-alpha.2 世代);正确槽树见 `docs/subsystems/slots.md:132-134`(`plugins.item`/`plugins.bundle.config`/`plugins.row.config`) | 照本 KB 订正版写(已本地改写并登记本条;重同步会复原,需按本条重新打补丁) | 无(上游 doc bug,未开帖) |
50
51
 
51
- ## 2. 次级清单(已核实、优先级较低,6 项)
52
+ ## 2. 次级清单(已核实、优先级较低,7 项)
52
53
 
53
54
  | # | 问题 | 位置 | 规避 | 讨论 |
54
55
  |---|---|---|---|---|
@@ -58,6 +59,7 @@
58
59
  | S5 | cordis preset 的 SKILL.md 仍在教已废弃工具名(6 处旧名 vs 实现注册 7 个新名) | `packages/preset/agent-presets/presets/cordis/skills/editing-cordis-compositions/SKILL.md:32,34,64,80,118,122`;新名 `packages/extensions/tool-cordis/src/index.ts:45,64,100,152,244,333,355`;另有生成文件/文档残留 `tool-cordis/src/api-catalog.ts:6`、`cordis-client-runner/.../slot-catalog.ts:7,74`、`docs/subsystems/slots.md:176`(+.zh)与三个快照(`ui.expected.md:36`、`session.v2.jsonl:17`、`session.v3.jsonl:18`) | 文档修复型 PR;快照测试需重生成 | #6679 |
59
60
  | S6 | SIGTERM 无在途 turn 排空路径;5s 宽限硬编码;无 `dsh restart` | `apps/cli/src/process-shutdown.ts:4,69-75`;dispose=cancel+whenIdle `agent-loop/src/index.ts:594-595`;launcher 无 restart(`apps/cli/src/args.ts:145-201`) | 第二信号即强退是固定语义;drain 属 feature request | #6665 |
60
61
  | S7 | LLM 出站超时修复未合入;undici 全局 dispatcher 由 http-proxy 独占(>5min prefill 在 ~302s 被 body timeout 终止) | 无 `egress.ts`/`httpBodyTimeoutMs`;`llm-pi-ai/src/config.ts:46`(300s 空闲看门狗)、`adapter.ts:355`;`util/http-proxy/src/install.ts:208-220` | 每请求新建 fetch 绕过共享 socket 记账 | #5673 |
62
+ | S8 | 创造模式 `cordis_define`/`cordis_run` 动态工具已退役 | 0.1.6-alpha.2 的 `docs/tool-catalog.md` 只剩两个只读检查工具(`cordis_inspect_list`/`cordis_inspect_query`);运行期创作的替代面 = `plugin_manager` 工具 + `ctx.pluginManager` + `OPTIONAL_BUNDLES`(@ddefc45,0.1.6-alpha.2 世代) | 0.1.5-rc.2 世代 `docs/tool-catalog.md` 的 cordis_* 工具段(旧快照 :269-501);`docs/capability-seams.md` 的 `ctx.dynamicCordisRunner` | 无(上游工具退役,非缺陷) |
61
63
 
62
64
  ## 3. 已在 master 修复(旧帖一律更新即可,无需改代码)
63
65
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-guide",
3
- "version": "0.3.16",
3
+ "version": "0.3.17",
4
4
  "description": "The dsh-plugin-guide knowledge base plus the dsh-plugin-dev CLI toolchain: official docs, Cordis primer, community deep-dives, and battle-tested pitfalls registered as an on-demand agent skill, with a scaffolder, static checker, and pack verifier for building DSH plugins.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -50,6 +50,7 @@
50
50
  "build": "tsdown",
51
51
  "typecheck": "tsc --noEmit",
52
52
  "typecheck:ci": "tsc --noEmit --pretty false",
53
+ "typecheck:checkout": "tsc -p tsconfig.checkout.json --noEmit",
53
54
  "test": "vitest run",
54
55
  "check:readmes": "node scripts/check-readme-sync.mjs",
55
56
  "check:kit": "pwsh -File scripts/verify-kit.ps1",
@@ -63,12 +64,12 @@
63
64
  "typescript": "^5.9.3"
64
65
  },
65
66
  "devDependencies": {
66
- "@deepseek-ai/dsh-attachment": "0.1.5-rc.2",
67
+ "@deepseek-ai/dsh-attachment": "0.1.7-alpha.2",
67
68
  "@types/node": "^22.20.1",
68
69
  "vitest": "^3.2.7"
69
70
  },
70
71
  "peerDependencies": {
71
- "@deepseek-ai/dsh": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0"
72
+ "@deepseek-ai/dsh": ">=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"
72
73
  },
73
74
  "peerDependenciesMeta": {
74
75
  "@deepseek-ai/dsh": {
@@ -105,7 +106,7 @@
105
106
  "filesystem:read"
106
107
  ],
107
108
  "compatibility": {
108
- "dshVersions": ["0.1.2-rc.1","0.1.5-rc.2"]
109
+ "dshVersions": ["0.1.2-rc.1","0.1.5-rc.2","0.1.7-alpha.2"]
109
110
  },
110
111
  "capability": {
111
112
  "id": "dsh-plugin-guide",
@@ -47,22 +47,22 @@ export function apply(ctx: Context, config: Config) {
47
47
 
48
48
  ## 2. Register the card (browser half)
49
49
 
50
- The card registers into `settings.plugin.item` under its namespace and owns everything inside it — chrome, controls, and copy. It reads and writes through `ctx.settingsScope`, which fences each write with the revision it read:
50
+ The card registers into the `plugins.item` list slot under its namespace and owns everything inside it — chrome, controls, and copy. The list slot takes `id`, `order`, and `label`, and hands the component a `props` object with `view: 'summary' | 'page'` (render the compact summary on the Plugins page and the full form on its own page). It reads and writes through `ctx.settingsScope`, which fences each write with the revision it read:
51
51
 
52
52
  ```ts ignore-check
53
53
  import type { Context as ClientContext } from '@deepseek-ai/cordis'
54
- // Type-only: the keyed slot's declaration. Cross-plugin collaboration goes
54
+ // Type-only: the list slot's declaration. Cross-plugin collaboration goes
55
55
  // through cordis services; a value import fails the client bundle-purity gate.
56
- import type {} from '@deepseek-ai/dsh-client-ui-settings-plugins/client'
56
+ import type {} from '@deepseek-ai/dsh-client-ui-plugin-manager/client'
57
57
 
58
58
  export const inject = ['slots', 'locale', 'connection', 'remote', 'settingsScope']
59
59
 
60
60
  export function apply(ctx: ClientContext): void {
61
61
  const card = new MyPluginCardController(ctx.settingsScope.bind({ namespace: 'my-plugin' }))
62
- ctx.slots.inject('settings.plugin.item', () => ctx.slots.register({
63
- name: 'settings.plugin.item',
64
- key: 'my-plugin',
65
- locale: 'settings.myPlugin',
62
+ ctx.slots.inject('plugins.item', () => ctx.slots.register({
63
+ id: 'my-plugin',
64
+ order: 100,
65
+ label: 'settings.myPlugin',
66
66
  inject: () => card.inject(),
67
67
  }, MyPluginCard),
68
68
  )
@@ -75,7 +75,7 @@ The scope snapshot carries what a form needs: the resolved `value`, the composit
75
75
 
76
76
  The **Plugin configuration** tab reads which namespaces the Host serves and dispatches one slot key per namespace. A card is rendered when the Host serves its key and skipped when it does not, so a deployment that never composed the Host half shows no trace of the card. A served namespace no card claims renders nothing — that is how the namespaces owned by other pages (`ui-theme`, `permission`, `llm-*`) stay off this tab.
77
77
 
78
- Cards appear in the order they registered into the slot; a keyed entry declares no `order` of its own.
78
+ Cards appear sorted by the `order` each entry declares in the `plugins.item` list slot.
79
79
 
80
80
  ## Packaging
81
81
 
@@ -47,22 +47,22 @@ export function apply(ctx: Context, config: Config) {
47
47
 
48
48
  ## 2. 注册卡片(浏览器半侧)
49
49
 
50
- 卡片以自己的命名空间为键注册进 `settings.plugin.item`,并拥有其中的一切——外观、控件与文案。它通过 `ctx.settingsScope` 读写,后者用读取时的 revision 为每次写入设栅:
50
+ 卡片以自己的命名空间为键注册进 `plugins.item` 列表槽,并拥有其中的一切——外观、控件与文案。列表槽接受 `id`、`order`、`label`,并给组件传 `props` 对象,其中 `view: 'summary' | 'page'`(在 Plugins 页渲染紧凑摘要、在专属页渲染完整表单)。它通过 `ctx.settingsScope` 读写,后者用读取时的 revision 为每次写入设栅:
51
51
 
52
52
  ```ts ignore-check
53
53
  import type { Context as ClientContext } from '@deepseek-ai/cordis'
54
- // Type-only: the keyed slot's declaration. Cross-plugin collaboration goes
54
+ // Type-only: the list slot's declaration. Cross-plugin collaboration goes
55
55
  // through cordis services; a value import fails the client bundle-purity gate.
56
- import type {} from '@deepseek-ai/dsh-client-ui-settings-plugins/client'
56
+ import type {} from '@deepseek-ai/dsh-client-ui-plugin-manager/client'
57
57
 
58
58
  export const inject = ['slots', 'locale', 'connection', 'remote', 'settingsScope']
59
59
 
60
60
  export function apply(ctx: ClientContext): void {
61
61
  const card = new MyPluginCardController(ctx.settingsScope.bind({ namespace: 'my-plugin' }))
62
- ctx.slots.inject('settings.plugin.item', () => ctx.slots.register({
63
- name: 'settings.plugin.item',
64
- key: 'my-plugin',
65
- locale: 'settings.myPlugin',
62
+ ctx.slots.inject('plugins.item', () => ctx.slots.register({
63
+ id: 'my-plugin',
64
+ order: 100,
65
+ label: 'settings.myPlugin',
66
66
  inject: () => card.inject(),
67
67
  }, MyPluginCard),
68
68
  )
@@ -75,7 +75,7 @@ scope 快照携带表单所需的一切:解析后的 `value`、组装层 `base
75
75
 
76
76
  **插件配置**标签页读取 Host 服务了哪些命名空间,并为每个命名空间派发一个 slot 键。当 Host 服务了某卡片的键时它被渲染,否则被跳过,因此从未组装过 Host 半侧的部署不会留下这张卡片的任何痕迹。被服务却无人认领的命名空间什么都不渲染——归其他页面所有的那些命名空间(`ui-theme`、`permission`、`llm-*`)正是这样留在本标签页之外的。
77
77
 
78
- 卡片按其注册进该 slot 的顺序出现;keyed entry 不声明自己的 `order`。
78
+ 卡片按各自在 `plugins.item` 列表槽中声明的 `order` 排序显示。
79
79
 
80
80
  ## 打包
81
81
 
@@ -6,7 +6,7 @@ Un plugin de DeepSeek Harness (DSH) generado con [`dsh-plugin-dev new`](https://
6
6
 
7
7
  | Superficie | Estado |
8
8
  |---|---|
9
- | Harness | DeepSeek Harness `0.1.5-rc.1` |
9
+ | Harness | DeepSeek Harness `0.1.7-alpha.2` |
10
10
  | Node | `^22.19.0 || >=24.0.0` |
11
11
  | Plataformas | Todas (ESM puro; sin código nativo, sin red) |
12
12
 
@@ -6,7 +6,7 @@
6
6
 
7
7
  | सतह | स्थिति |
8
8
  |---|---|
9
- | Harness | DeepSeek Harness `0.1.5-rc.1` |
9
+ | Harness | DeepSeek Harness `0.1.7-alpha.2` |
10
10
  | Node | `^22.19.0 || >=24.0.0` |
11
11
  | प्लेटफ़ॉर्म | सभी (शुद्ध ESM; कोई नेटिव कोड नहीं, कोई नेटवर्क नहीं) |
12
12
 
@@ -6,7 +6,7 @@ Um plugin do DeepSeek Harness (DSH) gerado com [`dsh-plugin-dev new`](https://gi
6
6
 
7
7
  | Superfície | Estado |
8
8
  |---|---|
9
- | Harness | DeepSeek Harness `0.1.5-rc.1` |
9
+ | Harness | DeepSeek Harness `0.1.7-alpha.2` |
10
10
  | Node | `^22.19.0 || >=24.0.0` |
11
11
  | Plataformas | Todas (ESM puro; sem código nativo, sem rede) |
12
12