@khorsheed/dsh-capability-catalog 0.1.98 → 0.1.99

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.
Files changed (46) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.en.md +31 -7
  3. package/README.i18n.yaml +2 -2
  4. package/README.md +31 -7
  5. package/cordis.patch.yml +1 -1
  6. package/lib/client.js +14792 -904
  7. package/lib/index.js +748 -263
  8. package/lib/typert.host.js +614 -295
  9. package/lib/typert.remote-client.d.ts +27 -4
  10. package/lib/typert.remote-client.js +599 -288
  11. package/lib/types/client/CapabilityCatalogCard.js +20 -5
  12. package/lib/types/client/SkillDetailModal.d.ts +13 -5
  13. package/lib/types/client/SkillDetailModal.js +9 -3
  14. package/lib/types/client/locales.d.ts +0 -4
  15. package/lib/types/client/locales.js +4 -8
  16. package/lib/types/envHint.d.ts +7 -1
  17. package/lib/types/envHint.js +52 -18
  18. package/lib/types/evidence.d.ts +142 -0
  19. package/lib/types/evidence.js +179 -0
  20. package/lib/types/frontmatter.d.ts +78 -0
  21. package/lib/types/frontmatter.js +186 -0
  22. package/lib/types/import.js +10 -19
  23. package/lib/types/index.d.ts +8 -3
  24. package/lib/types/index.js +21 -50
  25. package/lib/types/mcpStore.d.ts +6 -8
  26. package/lib/types/mcpStore.js +18 -43
  27. package/lib/types/mcps.d.ts +1 -6
  28. package/lib/types/mcps.js +0 -9
  29. package/lib/types/namespace.d.ts +2 -3
  30. package/lib/types/namespace.js +2 -3
  31. package/lib/types/remote.d.ts +11 -2
  32. package/lib/types/remote.js +4 -1
  33. package/lib/types/scoped-delivery.d.ts +36 -24
  34. package/lib/types/scoped-delivery.js +136 -100
  35. package/lib/types/scoped-edits.js +2 -1
  36. package/lib/types/shellEnv.d.ts +8 -1
  37. package/lib/types/shellEnv.js +37 -16
  38. package/lib/types/skills.d.ts +41 -0
  39. package/lib/types/skills.js +51 -9
  40. package/lib/types/tool-face.d.ts +68 -0
  41. package/lib/types/tool-face.js +82 -0
  42. package/lib/types/types.d.ts +40 -19
  43. package/package.json +20 -18
  44. package/lib/invariant.js +0 -25
  45. package/lib/types/invariant.d.ts +0 -16
  46. package/lib/types/invariant.js +0 -23
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # 变更记录
2
2
 
3
+ ## 0.1.99(2026-10-22)
4
+
5
+ 适配宿主 0.2.1 线:verifiedHost 前移至 0.2.1-rc.1,`@deepseek-ai/dsh-*` peer 区间加宽覆盖 0.2.1 并声明 `engines.dsh`(`>=0.1.5-rc.1 <0.3.0-0`);退役 load 期 invariant 伴生探针(本包为空仪式,无行为变化)。
6
+
7
+ - **preset 生效范围编辑并为一次「保存」**:技能详情里「哪些 preset 生效」的棋盘就是全部界面——空选即所有 preset 生效;一次保存按状态自动路由:未受管技能勾了子集则纳入受管投递、已受管则改范围、把已受管技能的勾选清空再保存则释放回用户技能目录。「移入受管目录」「释放回用户技能目录」两个机制味按钮与文案退场,受管目录概念退出主流程(提示文案写明空选语义与释放去向)。见 [Agent Note](../../.agents/notes/implemented/feature/2026-10-12-preset-scope-single-save.md)
8
+ - **新增只读工具 `read_skill_file`**:限定 preset 投递的技能随投递向对应 preset 注册一个 bundle 文件只读工具——刻意不挂文件系统的模式(如学习模式的白名单技能面)也能读到被投递技能拆出去的参考文档(`search.md`/`notes.md` 等)。省略 `path` 列出 bundle 文件;读取拒绝绝对路径与 `..`,穿越符号链接后仍须留在 bundle 目录内,单文件上限 10 万字符;只对本 preset 可见的技能应答,不扩大任何磁盘可达面。组合里没有工具服务时只缺该工具,技能投递不受影响。见 [Agent Note](../../.agents/notes/implemented/feature/2026-10-12-scoped-skill-bundle-reader.md)
9
+ - **限定 preset 的技能拿得到凭证提示与注入了**:`skill` 加载后的凭证提示改按调用方作用域读取,shellEnv 注入直接扫描受管根——限定到非默认 preset 的技能此前两处都读不到它(3080 实测:已配置、已投递、可加载的技能,模型却答「密钥未设置」);现在提示在 load 时出现、`DSH_<KEY>` 全实例注入。见 [Agent Note](../../.agents/notes/implemented/bug-fix/2026-10-12-scoped-skill-credential-scope.md)
10
+ - **受管投递不再弄丢 frontmatter `metadata:` 块**:技能纳入受管投递后,详情弹窗的凭证表单一度消失——内存投影只保留 `presetScope`,丢掉了 `metadata.credentials` 等声明。现在整个 metadata 块(js-yaml 解析,块损坏退化为无 metadata 而非拒绝投递)随投递保留,仅叠加规范化后的 presetScope。见 [Agent Note](../../.agents/notes/implemented/bug-fix/2026-10-12-managed-skill-metadata-preservation.md)
11
+ - **详情弹窗的环境变量展示修正**:已在 `metadata.credentials` 声明的变量不再同时出现在只读「引用到的环境变量」区;该区提示现在写明配置路径——在 SKILL.md frontmatter 里声明后即出现凭证表单(两份 README 同步到这一现行行为)
12
+ - **详情弹窗按打开时所在模式读 bundle 文件**:preset 对比视图(及任何非默认模式)下打开的技能详情,此前 bundle 文件按默认模式读取而误报缺失(受管技能靠兜底定义幸存,plugin/runtime 来源的直接读丢);现在记录打开时的模式并沿用到底
13
+ - **限定投递 reconcile 串行化**:文件监听、agent-preset 事件与管理 Remote 的并发触发合并为「一趟在途 + 至多一次补跑」,不再为同一 preset 重复注册 provider;等待中的调用方等到的是自己触发的那趟结果
14
+ - **新增纯数据证据面 `produceCapabilityEvidence`**:包根导出的纯函数(不取 cordis Context、不可能挂载 standing scope),供无组合的裸进程产出可验证的 preset 组合证据——与 `snapshotFor` 同算法的摘要 sha、四个分节各带 `complete`/`partial`/`unavailable` 完整度描述(「确认为空」与「未安装/读取失败」从此可区分)、measurement/coverage 元数据;完整度不进入摘要,Remote 动词线格式字节不变。消费契约:摘要只能比不能重算,任何 partial/unavailable 节即「未验证」。见 [Agent Note](../../.agents/notes/implemented/architecture/2026-10-13-capability-catalog-completeness-evidence.md)
15
+
3
16
  ## 0.1.98(2026-09-30)
4
17
 
5
18
  - **修复跨实例串读**:受管 skill 根目录的 home 解析兜底从硬编码的 `~/.dsh-official` 改为对齐官方 `resolveDshHome` 优先级(settings 服务的 home → 非空白 `$DSH_HOME` → `~/.dsh`)。旧兜底在桌面 app(settings 不报 home、环境无 `DSH_HOME`)上会把桌面实例指到另一实例的 home——桌面设置页因此列出 3080 的三个受管写作 skill 并挂出「未在任何模式生效」横幅
package/README.en.md CHANGED
@@ -10,7 +10,7 @@ dsh composes each session from an agent preset, and plugins, skill roots, and MC
10
10
 
11
11
  ## Features
12
12
 
13
- - **Skills at a glance** — a three-column preview grid (name, one-line description, source/provider badge) with search, sort, and a 内置/插件/其他 segment filter. Click a card for the detail modal: full description and invocation meta, a unified source browser (bundle file tree on the left, content pane on the right — a content-only skill renders as a single virtual `SKILL.md` node), frontmatter metadata (secret-shaped values render as `···`), and a credential form for every env the skill declares, parsed from `metadata.credentials` and from `$ENV` / `process.env.X` / `env['X']` / `{{env:X}}` references in the body. Values land in the dsh credential store and never cross the wire.
13
+ - **Skills at a glance** — a three-column preview grid (name, one-line description, source/provider badge) with search, sort, and a 内置/插件/其他 segment filter. Click a card for the detail modal: full description and invocation meta, a unified source browser (bundle file tree on the left, content pane on the right — a content-only skill renders as a single virtual `SKILL.md` node), frontmatter metadata (secret-shaped values render as `···`), and a credential form for every credential the skill declares in frontmatter `metadata.credentials` — values land in the dsh credential store and never cross the wire. `$ENV` / `process.env.X` / `env['X']` / `{{env:X}}` references detected in the body are listed separately as read-only runtime references (mostly host-injected facts like `DSH_HOME`, not user secrets).
14
14
  - **Tools with channel attribution** — collapsible cards that say where each tool came from: the author-declared origin tag (exact), the `mcp__` prefix (exact), a generated official-tools whitelist (exact), or an apply-time baseline diff (inferred). A detail modal shows the parameter schema as a tree or raw JSON.
15
15
 
16
16
  - **Install a skill three ways** — one modal: upload (a `SKILL.md`, a `.zip` containing one, or a whole skill folder, read by a dependency-free `node:zlib` zip reader); install from source (an `owner/repo`, a git URL, or a whole pasted `npx skills add <repo> [--skill <name>]` command — GitHub in-repo paths and `tree/`/`blob/` URLs normalize to the same clone, and only `github.com` is split into owner/repo, so GitLab subgroups survive); or pick skills from a local directory. Each source chooses the target root (`$DSH_HOME/skills` or `.agents/skills`) and whether the skill enters the model catalog; the skill-filesystem watcher discovers the result on its own.
@@ -18,7 +18,7 @@ dsh composes each session from an agent preset, and plugins, skill roots, and MC
18
18
  <img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/capability-catalog-2.png" width="640" alt="the Add Skill modal: three install paths — file upload, install from source, pick from a local directory — each with a choice of target root and whether the skill enters the model catalog">
19
19
 
20
20
  - **The mode view** — every session is composed from an agent preset (「模式」), and each preset registers a different set of skills and tools. The mode control beside the search box reads one preset's face (`snapshotAt(presetId)`) — the grid is that mode's face and nothing else — and 全部模式(对比) reads every mode in one call (`modeFaces`) and tags each card with the modes that load it, so "which modes does this capability appear in?" is one click. A mode that cannot be read is reported as unavailable, never rendered as an empty face.
21
- - **Preset-scoped skills** — the plugin owns a managed root (`$DSH_HOME/capability-catalog/skills`) whose skills declare the presets they belong to in frontmatter `presetScope`; the detail modal edits it (save, adopt an installed skill into the managed root, release it back to the user root), and a diagnostic row keeps managed skills no mode loads reachable instead of hidden.
21
+ - **Preset-scoped skills** — the plugin owns a managed root (`$DSH_HOME/capability-catalog/skills`) whose skills declare the presets they belong to in frontmatter `presetScope`; the detail modal edits it as one checkbox grid plus a single Save — adopt, re-scope and release (an emptied pick) route internally — and a scoped skill's frontmatter metadata (e.g. `credentials`) survives delivery intact. A read-only `read_skill_file` tool registered with each delivery lets a mode without fs tools read a managed skill's bundle files, and a diagnostic row keeps managed skills no mode loads reachable instead of hidden.
22
22
  - **MCP server management** — paste an `mcp.json` server entry and the dialog parses the transport and detects credentials (a credential-looking field becomes a `secretRef:` marker in the stored config, and the Remote only ever reports `configured` state — values never reach the browser). Connect to discover a server's tools, toggle a whole server or a single tool, and every enabled tool is registered on `ctx.tools` as `mcp__<server>__<tool>` so the model can call it. Configured servers survive restarts through the settings service.
23
23
 
24
24
  <img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/capability-catalog-3.png" width="640" alt="the Add MCP server modal: paste an mcp.json server entry and the transport and credentials are detected automatically">
@@ -44,7 +44,7 @@ dsh plugin --profile web remove @khorsheed/dsh-capability-catalog
44
44
  ## Compatibility
45
45
 
46
46
  - npm release line (`@deepseek-ai/dsh@0.1.5-rc.1`): ✅ full — 0.1.5-rc.1 full-line boot-verified (42 packages including capture, 2026-09-25) through three compat layers: the [preset-registry dual-name probe](../../.agents/notes/implemented/bug-fix/2026-09-25-preset-registry-dual-name-probe.md), [dual-shape typert codecs](../../.agents/notes/implemented/bug-fix/2026-09-25-typert-codec-dual-shape.md), and [typert faces carrying zod@4](../../.agents/notes/implemented/bug-fix/2026-09-25-typert-faces-carry-zod-v4.md). `minHost` is 0.1.5-rc.1, and 0.1.95 is this package's first published release — older hosts have no compatible line.
47
- - source line (deepseek-harness master): ✅ (verifiedHost: 0.1.7-rc.2) — 0.1.7-rc.2 is also the 3080 production-verified line.
47
+ - source line (deepseek-harness master): ✅ (verifiedHost: 0.2.1-rc.1) — host-021 wave: full build+test green against the 0.2.1-rc.1 tree (including the 3080 production deployment acceptance); no other surface changes. 0.1.7-rc.2 was the previous 3080 production-verified line.
48
48
 
49
49
  Per-mode capability reads (the mode picker, `snapshotAt` / `snapshotFor` / `modeFaces`, preset-scoped skill delivery) resolve a preset's standing scope through whichever roster face the host line offers: lease-free `standingKeyFor` on 0.1.5, the leased `acquireScope` on rc.1 — rc.1 removed `standingKeyFor`, and catalog builds before this dual-face fix silently read the global layer on rc.1. The lease is released after every read, on the listing and the fingerprint path alike. Machine-readable: `dsh.compat` in `package.json` (`minHost`, `verifiedHost`, `notes`).
50
50
 
@@ -54,16 +54,40 @@ Per-mode capability reads (the mode picker, `snapshotAt` / `snapshotFor` / `mode
54
54
  - **Untagged plugin tools fall back to heuristics** — a tool whose author did not set an origin tag may be listed under 内置/builtin; the tab's 「为什么我的插件工具不在这里?」 modal (and [docs/tool-origin-guide.md](../../docs/tool-origin-guide.md)) explains the one-line fix.
55
55
  - **The MCP bridge covers the text-result case** — discovered tools are registered text-in/text-out, without image/attachment projection; and on a composition without the settings service, MCP server state is in-memory only and resets on restart.
56
56
 
57
+ ## The workspace integration seam
58
+
59
+ Producing capability-composition EVIDENCE in the process where the composition runs, and handing it to another process as plain data to verify, is a supported consumption form: the package root exports `produceCapabilityEvidence(options)` — every input explicit (a caller-verified `presetId` and absolute `workdir`, the `skills` / `tools` registry slices to measure, an optional already-resolved opaque `scope`, and the caller's `coverage` declaration) — and the output is one JSON-safe bundle: the composition digest `sha`, the per-section `completeness`, the `measurement` object (`kind: 'preset-composition'`, with presetId + workdir + an optional revision), and the `coverage` declaration. The digest reuses this package's own `catalogSnapshot` / `hashOf` (the tools side is read through the same `tool-face` implementation the Remote verbs use), so over the same registries it is byte-identical to the one `snapshotFor` stamps — a contract test pins that parity.
60
+
61
+ ```ts
62
+ import { produceCapabilityEvidence } from '@khorsheed/dsh-capability-catalog'
63
+
64
+ const evidence = await produceCapabilityEvidence({
65
+ presetId: 'eval-lean', // caller-verified
66
+ workdir: '/abs/path/to/project', // a relative path is refused
67
+ skills: ctx.skills, tools: ctx.tools, // the registry slices to measure
68
+ coverage: { directories: ['…/skills'], services: ['skills', 'tools'] },
69
+ })
70
+ // → { measurement, sha, completeness, coverage }
71
+ ```
72
+
73
+ The mounting side's obligations and the semantics:
74
+
75
+ - **Per-section completeness is part of the evidence**: every section the snapshot covers (skills / tools / mcpServers / channels) carries a `{ state, reason? }` descriptor — `complete` = scanned and everything found is included (zero found is complete-with-zero, distinguishable from failure); `partial` = some sources failed and the rows are a subset (`reason` names the failed source class — registry read failed / registry reported incomplete discovery / skill bodies unreadable / origin tags unreadable — never secret content, and never raw error text, which may carry paths); `unavailable` = the section could not be measured at all (service not installed / read failed / no permission). "Scanned, it is empty" and "not installed / unreadable" are therefore two distinguishable answers.
76
+ - **The digest records what it covered**: the digest itself covers only the capability face (the same algorithm and the same exclusions as `snapshotFor`), and the measurement object plus the coverage declaration travel in the bundle — the caller declares which directories and services were read, so the consumer can check "is this digest measuring the composition I expected?" An empty or partial digest cannot be mistaken for a complete one: a section whose completeness is not `complete` is "not verified".
77
+ - **Consumers never recompute, and never treat partial as complete**: the digest algorithm is owned by THIS package (what is compared across processes is the digest, not the rows); any `partial` / `unavailable` section is "not verified" — never read as complete, and never read as "the section is empty".
78
+ - **The Remote verbs' standing-scope cost is unchanged, and local-only**: `snapshotAt` / `snapshotFor` / `modeFaces` MOUNT a not-yet-composed preset by design (the roster's single-flight standing mount), and their output is byte-identical to before. The production face therefore resolves no scope — the API has no roster parameter at all (in the normal target-side topology the process's own registries already ARE the composition's face, so no scope is needed; an already-resolved scope may be passed as an opaque token, and resolving and releasing it stays the caller's business) — and mounts or registers no cordis service, so it runs in a bare process (the published tarball is the target-side runtime, with no dependency beyond what this package already has).
79
+ - **Local behavior is unchanged when this face is unused**: the Remote verbs never carry the completeness field; the listing's and fingerprint's output, and the "a listing degrades, a fingerprint refuses" split, are byte-identical to before.
80
+
57
81
  ## How it works
58
82
 
59
83
  <details>
60
84
  <summary>Internals (click to expand)</summary>
61
85
 
62
- **Architecture.** The host half is `CapabilityCatalogService`, a Typert Remote service in the `capabilityCatalog` namespace; the browser half mounts the generated Remote via `ctx.remote.$mount` and registers a standalone `settings.section` slot (id `capabilities`, order 20 — right after Plugins) through `slots.inject`, so apply order never matters. The identity triangle is the `capability-catalog` row id in `cordis.patch.yml`, the npm name, and `src/invariant.ts`'s `PACKAGE_NAME`. The package root exports the service, the fingerprint helpers (`hashOf` / `capsTag` / `canonicalCapabilities`), the preset-scope/delivery primitives, and the tool-origin helpers; `/client` exports the browser plugin body.
86
+ **Architecture.** The host half is `CapabilityCatalogService`, a Typert Remote service in the `capabilityCatalog` namespace; the browser half mounts the generated Remote via `ctx.remote.$mount` and registers a standalone `settings.section` slot (id `capabilities`, order 20 — right after Plugins) through `slots.inject`, so apply order never matters. The identity anchors are the `capability-catalog` row id in `cordis.patch.yml` and the npm name. The package root exports the service, the fingerprint helpers (`hashOf` / `capsTag` / `canonicalCapabilities`), the preset-scope/delivery primitives, and the tool-origin helpers; `/client` exports the browser plugin body.
63
87
 
64
88
  **Channel attribution.** `ToolSchema` carries no source field, so a tool's channel is resolved in precedence order: the author-declared origin tag (a `Symbol.for('dsh.tool.origin')` property on the retained definition, read back via `ctx.tools.get(name)` — the system-prompt projection strips it, so it never crosses the model wire), then the `mcp__` prefix (server name by longest configured-prefix match), then a generated official-tools whitelist (`scripts/gen-official-tools.mts`, generated from the pinned harness checkout), then an apply-time baseline diff. The baseline is taken inside the deferred `ctx.inject(['tools'], …)`, so the registry's own mount order cannot produce an empty baseline that mislabels every tool as plugin.
65
89
 
66
- **The mode view.** An instance composes every session from an agent preset: the shipped standard / minimal / PTC / 创造 presets plus deployment- or user-authored ones, each registering a different set of skills and tools. Picking a preset reads the catalog at that preset's standing scope (`snapshotAt(presetId)`), and the grid is that mode's face and nothing else — a skill delivered only to 写作模式 does not appear under 开发模式, and the tab's counts are the mode's counts. 全部模式(对比) reads every preset's face in one call (`modeFaces`) and shows the union, with a chip row on each card naming the modes that load it; a capability in every readable mode collapses to one `全部模式 · N` pill, and a long partial list shows two chips plus `+N`, expanding in place. A mode that cannot be read (a broken composition, a roster that resolves no standing scope) keeps its row and is reported as unreadable rather than rendered as an empty face — "this mode loads nothing" and "this mode could not be read" are different claims. An unscoped managed skill (no `presetScope`) is delivered to every preset; one scoped to a preset this deployment does not supply appears in no mode's face, and the tab then shows it under a diagnostic line (`N 个受管 skill 未在任何模式生效`) whose cards open the detail modal, where its scope can be changed or released — without that list, filtering by mode would make such a skill unreachable rather than merely hidden. Reading a mode is not free: resolving a preset's standing scope MOUNTS its composition (the roster's single-flight standing mount), so the comparison is an explicit choice, never done on open; the client reads faces once per session behind a single-flight cache shared with the detail modal. Detail and delete follow the same read position: opening a card reads that capability's detail and bundle at the mode the card came from, and a delete targets the same one.
90
+ **The mode view.** An instance composes every session from an agent preset: the shipped standard / minimal / PTC / 创造 presets plus deployment- or user-authored ones, each registering a different set of skills and tools. Picking a preset reads the catalog at that preset's standing scope (`snapshotAt(presetId)`), and the grid is that mode's face and nothing else — a skill delivered only to 写作模式 does not appear under 开发模式, and the tab's counts are the mode's counts. 全部模式(对比) reads every preset's face in one call (`modeFaces`) and shows the union, with a chip row on each card naming the modes that load it; a capability in every readable mode collapses to one `全部模式 · N` pill, and a long partial list shows two chips plus `+N`, expanding in place. A mode that cannot be read (a broken composition, a roster that resolves no standing scope) keeps its row and is reported as unreadable rather than rendered as an empty face — "this mode loads nothing" and "this mode could not be read" are different claims. An unscoped managed skill (no `presetScope`) is delivered to every preset; one scoped to a preset this deployment does not supply appears in no mode's face, and the tab then shows it under a diagnostic line (`N 个受管 skill 未在任何模式生效`) whose cards open the detail modal, where its scope can be changed (clearing every pick and saving restores it to a plain user skill) — without that list, filtering by mode would make such a skill unreachable rather than merely hidden. Reading a mode is not free: resolving a preset's standing scope MOUNTS its composition (the roster's single-flight standing mount), so the comparison is an explicit choice, never done on open; the client reads faces once per session behind a single-flight cache shared with the detail modal. Detail and delete follow the same read position: opening a card reads that capability's detail and bundle at the mode the card came from, and a delete targets the same one.
67
91
 
68
92
  Card badges name the source (插件 / 内置 / 用户 / 项目 / 自定义); a managed skill's `preset` scope is policy, not provenance, so it is shown where that policy is the subject — the orphan list and the detail modal.
69
93
 
@@ -71,8 +95,8 @@ Card badges name the source (插件 / 内置 / 用户 / 项目 / 自定义); a m
71
95
 
72
96
  | skill | what the section shows |
73
97
  |---|---|
74
- | in the managed root | the preset grid + 保存 + 释放回用户技能目录 |
75
- | user / project / custom root | the preset grid + 移入受管目录并可限定 preset (no 保存: the selection is the scope adopt installs it with) |
98
+ | in the managed root | the preset grid + one Save (emptying every pick and saving releases it back to the user root — a plain user skill visible in every mode) |
99
+ | user / project / custom root | the preset grid + one Save (picking a subset and saving adopts it into the managed root with that scope; none picked = already visible everywhere, nothing to write) |
76
100
  | plugin-provided (`runtime`) | why it cannot be set, plus the modes that actually load it (chips from the shared faces cache) |
77
101
  | built-in (`bundled`) | why it cannot be set — it follows the deployment composition |
78
102
  | no roster / no managed delivery in this deployment | the unavailable note |
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # last confirmed-consistent state. Both languages carry equal authority; after
3
3
  # editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/capability-catalog/README.en.md
5
- packages/capability-catalog/README.en.md: ddaa99ddeed488cfceacd86c683abdf2c96f6ecd
6
- packages/capability-catalog/README.md: 3504e86204c2b18a7d114ac7faf9618389988851
5
+ packages/capability-catalog/README.en.md: 222a8ae644169c3c625ca734d58f8b585acd2a1e
6
+ packages/capability-catalog/README.md: 510e62d63b148d5ed809efcf8479b5e99c2646b0
package/README.md CHANGED
@@ -10,7 +10,7 @@ dsh 的每个会话都由一个 agent preset 组合而成,插件、技能目
10
10
 
11
11
  ## 特性
12
12
 
13
- - **技能一览**——三列预览网格(名字、一行描述、来源/provider 徽标),带搜索、排序和 内置/插件/其他 分段过滤。点卡片进详情弹窗:完整描述与调用面 meta、统一源码浏览器(左边 bundle 文件树、右边内容窗格——内容合成型 skill 渲染成单个虚拟 `SKILL.md` 节点)、frontmatter metadata(形似密钥的值显示为 `···`),以及凭据表单——从 `metadata.credentials` 与正文里的 `$ENV` / `process.env.X` / `env['X']` / `{{env:X}}` 引用解析出的每个 env 都可在此配置。值落在 dsh 凭据库,不上 wire。
13
+ - **技能一览**——三列预览网格(名字、一行描述、来源/provider 徽标),带搜索、排序和 内置/插件/其他 分段过滤。点卡片进详情弹窗:完整描述与调用面 meta、统一源码浏览器(左边 bundle 文件树、右边内容窗格——内容合成型 skill 渲染成单个虚拟 `SKILL.md` 节点)、frontmatter metadata(形似密钥的值显示为 `···`),以及凭据表单——skill 在 frontmatter `metadata.credentials` 声明的每项凭据都可在此配置,值落在 dsh 凭据库,不上 wire;正文里 `$ENV` / `process.env.X` / `env['X']` / `{{env:X}}` 引用到的变量则单独列为只读的运行时引用(多为 `DSH_HOME` 一类宿主注入的事实,不是用户密钥)。
14
14
  - **工具带渠道归因**——折叠卡标明每个工具从哪来:作者声明的 origin 标记(精确)、`mcp__` 前缀(精确)、生成的官方工具白名单(精确)、apply 时序差分(推断)。详情弹窗把参数 schema 展示为结构树或原始 JSON。
15
15
 
16
16
  - **三种方式装技能**——一个弹窗:上传(单个 `SKILL.md`、含 SKILL.md 的 `.zip`、或整个技能文件夹,零依赖 `node:zlib` 解压);命令安装(`owner/repo`、git URL,或整条粘贴来的 `npx skills add <repo> [--skill <名字>]` 命令——GitHub 仓库内路径与 `tree/`/`blob/` URL 都归一到同一个克隆,只有 `github.com` 会拆成 owner/repo,GitLab 子组按原样存活);或从本机目录勾选。每个来源都可选目标根(`$DSH_HOME/skills` 或 `.agents/skills`)与是否进模型 catalog;skill-filesystem watcher 会自动发现结果。
@@ -18,7 +18,7 @@ dsh 的每个会话都由一个 agent preset 组合而成,插件、技能目
18
18
  <img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/capability-catalog-2.png" width="640" alt="新增 Skill 弹窗:文件上传 / 命令安装 / 从本机目录三种安装方式,每个来源可选目标根与是否进模型 catalog">
19
19
 
20
20
  - **模式视图**——每个会话都由一个 agent preset(「模式」)组合而成,不同模式注册的 skill 与 tool 各不相同。搜索框旁边的模式选择框按**该 preset 的 standing scope** 读(`snapshotAt(presetId)`)——网格就是那个模式的能力面,不再合并任何东西;「全部模式(对比)」一次调用读回每个模式的面(`modeFaces`),给每张卡片标上加载它的模式 chip,「这个能力出现在哪些模式」一点即达。读不到的模式标为「无法读取」,绝不渲染成空面。
21
- - **按 preset 投递的技能**——插件自管一个受管根(`$DSH_HOME/capability-catalog/skills`),其中技能用 frontmatter `presetScope` 声明属于哪些模式;详情弹窗直接编辑(保存、把已装技能收编进受管根、释放回用户技能目录),不在任何模式生效的受管技能由一条诊断行保持可达,而不是被过滤没了。
21
+ - **按 preset 投递的技能**——插件自管一个受管根(`$DSH_HOME/capability-catalog/skills`),其中技能用 frontmatter `presetScope` 声明属于哪些模式;详情弹窗直接编辑:勾选网格 + 一个「保存」,收编/改范围/释放(清空勾选)都在内部自动路由,被限定技能的 frontmatter metadata(如 `credentials`)在投递中原样保留。随投递注册的只读 `read_skill_file` 工具让不挂 fs 的模式也能读受管 skill 的 bundle 分册;不在任何模式生效的受管技能由一条诊断行保持可达,而不是被过滤没了。
22
22
  - **MCP server 管理**——粘贴一段 `mcp.json` server 配置,弹窗自动解析传输、识别凭据(形似凭据的字段在存储的配置里变成 `secretRef:` 标记,Remote 只回 `configured` 状态——值永远到不了浏览器)。连接后发现该 server 的工具,整台 server 或单个工具都可开关,每个启用的工具都以 `mcp__<server>__<tool>` 注册到 `ctx.tools`,模型当场可调。配置的 server 经 settings 服务跨重启存活。
23
23
 
24
24
  <img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/capability-catalog-3.png" width="640" alt="新增 MCP 服务器弹窗:粘贴一段 mcp.json server 配置,自动解析传输与凭据">
@@ -44,7 +44,7 @@ dsh plugin --profile web remove @khorsheed/dsh-capability-catalog
44
44
  ## Compatibility
45
45
 
46
46
  - npm 发布线(`@deepseek-ai/dsh@0.1.5-rc.1`):✅ 完整——0.1.5-rc.1 全量 boot 实证通过(42 包含 capture,2026-09-25),经三层兼容修复:[preset-registry 双名探测](../../.agents/notes/implemented/bug-fix/2026-09-25-preset-registry-dual-name-probe.md)、[typert codec 双形状](../../.agents/notes/implemented/bug-fix/2026-09-25-typert-codec-dual-shape.md)、[face 自带 zod@4](../../.agents/notes/implemented/bug-fix/2026-09-25-typert-faces-carry-zod-v4.md)。`minHost` 即 0.1.5-rc.1,且 0.1.95 是本包首个发布——更早的宿主没有可用发布线。
47
- - 源码线(deepseek-harness master):✅(verifiedHost: 0.1.7-rc.2)——0.1.7-rc.2 同时是 3080 生产实证线。
47
+ - 源码线(deepseek-harness master):✅(verifiedHost: 0.2.1-rc.1)——host-021 波对 0.2.1-rc.1 树全量 build+test 绿(含 3080 生产部署验收),无其它表面变化;0.1.7-rc.2 为此前的 3080 生产实证线。
48
48
 
49
49
  按模式的能力面读取(模式下拉、`snapshotAt` / `snapshotFor` / `modeFaces`、按 preset 的技能投递)按宿主线走两条 roster 面解析 preset 的 standing scope:0.1.5 的无租约 `standingKeyFor`,rc.1 的租约式 `acquireScope`——rc.1 删除了 `standingKeyFor`,本次双线修复前的目录版本在 rc.1 上会静默读成全局层。清单与指纹两条路径每次读完都释放租约。机器可读字段见 `package.json` 的 `dsh.compat`(`minHost`、`verifiedHost`、`notes`)。
50
50
 
@@ -54,16 +54,40 @@ dsh plugin --profile web remove @khorsheed/dsh-capability-catalog
54
54
  - **未打标的插件工具退回启发式**——作者没设 origin 标记的工具可能被归到「内置」;设置页的「为什么我的插件工具不在这里?」弹窗(与 [docs/tool-origin-guide.md](../../docs/tool-origin-guide.md))给出了一行的修法。
55
55
  - **MCP 桥只覆盖文本结果形态**——发现的工具按纯文本进出注册,无图片/附件投影;组合里没有 settings 服务时,MCP server 状态只活在内存里,重启即重置。
56
56
 
57
+ ## 工作区接入接缝
58
+
59
+ 把能力组合的**证据**在生产它的进程里算出来、以纯数据交给另一个进程核验,是受支持的消费方式:包根导出 `produceCapabilityEvidence(options)`——输入全部显式(调用方已核验的 `presetId` 与绝对 `workdir`、要测量的 `skills` / `tools` 注册表切片、可选的已解析不透明 `scope`、调用方的覆盖声明 `coverage`),输出是一份 JSON 安全的数据包:组合摘要 `sha`、逐节完备性 `completeness`、测量对象 `measurement`(`kind: 'preset-composition'`,带 presetId + workdir + 可选 revision)与覆盖声明 `coverage`。摘要复用本包自己的 `catalogSnapshot` / `hashOf`(工具侧读取与 Remote 动词共用同一份 `tool-face` 实现),所以同一份注册表输入给出的摘要与 `snapshotFor` 钉出来的逐字节一致——合同测试钉死了这条对等。
60
+
61
+ ```ts
62
+ import { produceCapabilityEvidence } from '@khorsheed/dsh-capability-catalog'
63
+
64
+ const evidence = await produceCapabilityEvidence({
65
+ presetId: 'eval-lean', // 调用方已核验
66
+ workdir: '/abs/path/to/project', // 相对路径拒绝
67
+ skills: ctx.skills, tools: ctx.tools, // 要测量的注册表切片
68
+ coverage: { directories: ['…/skills'], services: ['skills', 'tools'] },
69
+ })
70
+ // → { measurement, sha, completeness, coverage }
71
+ ```
72
+
73
+ 接入侧的义务与语义:
74
+
75
+ - **逐节完备性是证据的一部分**:snapshot 覆盖的每一节(skills / tools / mcpServers / channels)各带一个 `{ state, reason? }` 描述符——`complete` = 扫完且发现的全部在内(零发现也是 complete-with-zero,与失败可区分);`partial` = 部分来源失败、行只是子集(reason 点名失败的来源类别——注册表读失败 / 注册表自报发现不完整 / 技能体读不出 / origin 标记读不到,绝不带秘密内容,也不带可能含路径的错误原文);`unavailable` = 该节根本不可测(服务未安装 / 读取失败 / 无权限)。「扫过了,是空的」与「没装 / 没读成」因此是两种可区分的答案。
76
+ - **摘要记录它覆盖了什么**:digest 本身只覆盖能力面(与 `snapshotFor` 同一算法、同一套排除项),测量对象与覆盖声明随行在证据包里——调用方声明读了哪些目录与哪些服务,消费方据此核对「这份摘要量的是不是我期望的那份组合」。一份空的或部分的摘要不会被误读成完整的:完备性不是 `complete` 的节就是「未核验」。
77
+ - **消费方绝不重算、绝不把 partial 当 complete**:摘要算法归本包所有(跨进程比对的是摘要本身,不是行);任何 `partial` / `unavailable` 的节一律按「未核验」处理——既不当作完整,也不当作「该节为空」。
78
+ - **Remote 动词的 standing-scope 代价不变,且只在本机用**:`snapshotAt` / `snapshotFor` / `modeFaces` 按设计会**挂载**一个尚未组合的 preset(roster 的 single-flight standing mount),它们的输出与之前逐字节一致。生产面因此不解析任何 scope——API 里根本没有 roster 参数(正常的目标侧拓扑里,本进程的注册表就是该组合的能力面,无需 scope;已解析的 scope 可作为不透明令牌传入,解析与租约释放始终是调用方的事),也不挂载、不注册任何 cordis 服务,裸进程即可运行(发布的 tarball 就是目标侧运行时,依赖不超出本包既有依赖)。
79
+ - **不用此面时本地行为不变**:Remote 动词从不携带完备性字段,清单与指纹的输出、以及「清单降级 / 指纹拒绝」的分流与之前逐字节一致。
80
+
57
81
  ## 实现原理
58
82
 
59
83
  <details>
60
84
  <summary>内部结构(点击展开)</summary>
61
85
 
62
- **架构。** host 半是 `CapabilityCatalogService`,一个 `capabilityCatalog` 命名空间的 Typert Remote 服务;浏览器半经 `ctx.remote.$mount` 挂载生成的 Remote,再经 `slots.inject` 注册独立 `settings.section` 槽位(id `capabilities`,order 20——紧随「插件」区块),因此 apply 顺序永远不影响。身份三角:`cordis.patch.yml` 的 `capability-catalog` 行 id、npm 包名、`src/invariant.ts` 的 `PACKAGE_NAME`。包根导出服务本体、指纹 helper(`hashOf` / `capsTag` / `canonicalCapabilities`)、preset-scope/投递原语与工具来源 helper;`/client` 导出浏览器插件体。
86
+ **架构。** host 半是 `CapabilityCatalogService`,一个 `capabilityCatalog` 命名空间的 Typert Remote 服务;浏览器半经 `ctx.remote.$mount` 挂载生成的 Remote,再经 `slots.inject` 注册独立 `settings.section` 槽位(id `capabilities`,order 20——紧随「插件」区块),因此 apply 顺序永远不影响。身份锚点:`cordis.patch.yml` 的 `capability-catalog` 行 id 与 npm 包名。包根导出服务本体、指纹 helper(`hashOf` / `capsTag` / `canonicalCapabilities`)、preset-scope/投递原语与工具来源 helper;`/client` 导出浏览器插件体。
63
87
 
64
88
  **渠道归因。** `ToolSchema` 不携带来源字段,所以工具渠道按优先级解析:作者声明的 origin 标记(留在注册表按引用保留的 definition 上的 `Symbol.for('dsh.tool.origin')` 属性,经 `ctx.tools.get(name)` 读回——系统提示投影会剥掉它,所以永不上模型 wire),然后 `mcp__` 前缀(按最长已配置前缀匹配 server 名),然后生成的官方工具白名单(`scripts/gen-official-tools.mts`,由锁定的 harness 检出生成),最后 apply 时序差分。基线在延迟的 `ctx.inject(['tools'], …)` 里取,注册表自己的挂载顺序不可能造成空基线、把每个工具误标成插件。
65
89
 
66
- **模式视图。** 实例把每个会话组合自一个 agent preset:内置的 标准/极简/PTC/创造,加上部署或用户自建的 preset,各自注册不同的 skill 与 tool。选中某个 preset 就按该 preset 的 standing scope 读(`snapshotAt(presetId)`),网格**就是那个模式的能力面**——只投递给写作模式的 skill 不会出现在开发模式下,标签页计数也是这个模式的计数。「全部模式(对比)」一次调用读回每个 preset 的面(`modeFaces`)显示并集,每张卡片一排模式 chip 标明哪些模式加载它;每个可读模式都加载的能力折叠成一颗 `全部模式 · N`,部分匹配但很长时只显示两颗 chip + `+N`,就地展开。读不到的模式(组合失败、roster 解不出 standing scope)保留自己的行并标为「无法读取」,而不是渲染成空面——「这个模式什么都没有」和「这个模式读不出来」是两回事。没声明 `presetScope` 的受管 skill 投递给每个 preset;范围指向本部署没有的 preset 的受管 skill 不属于任何模式的面,技能页此时给一条诊断行(`N 个受管 skill 未在任何模式生效`),展开点卡片进详情即可改范围或释放——没有这条出口,按模式过滤就不只是隐藏,而是让这类 skill 彻底够不着。读一个模式**不是免费的**:解析 preset 的 standing scope 会**挂载**它的组合(roster 的 single-flight standing mount),所以对比是一次显式选择,打开页面绝不做;客户端每个会话只读一次能力面(single-flight 缓存,和详情弹窗共用)。详情与删除跟着同一个读位置走:点卡片读的是该卡片来源模式下的详情与技能包,删除同样针对这个模式。
90
+ **模式视图。** 实例把每个会话组合自一个 agent preset:内置的 标准/极简/PTC/创造,加上部署或用户自建的 preset,各自注册不同的 skill 与 tool。选中某个 preset 就按该 preset 的 standing scope 读(`snapshotAt(presetId)`),网格**就是那个模式的能力面**——只投递给写作模式的 skill 不会出现在开发模式下,标签页计数也是这个模式的计数。「全部模式(对比)」一次调用读回每个 preset 的面(`modeFaces`)显示并集,每张卡片一排模式 chip 标明哪些模式加载它;每个可读模式都加载的能力折叠成一颗 `全部模式 · N`,部分匹配但很长时只显示两颗 chip + `+N`,就地展开。读不到的模式(组合失败、roster 解不出 standing scope)保留自己的行并标为「无法读取」,而不是渲染成空面——「这个模式什么都没有」和「这个模式读不出来」是两回事。没声明 `presetScope` 的受管 skill 投递给每个 preset;范围指向本部署没有的 preset 的受管 skill 不属于任何模式的面,技能页此时给一条诊断行(`N 个受管 skill 未在任何模式生效`),展开点卡片进详情即可改范围(清空勾选并保存即还原为普通用户技能)——没有这条出口,按模式过滤就不只是隐藏,而是让这类 skill 彻底够不着。读一个模式**不是免费的**:解析 preset 的 standing scope 会**挂载**它的组合(roster 的 single-flight standing mount),所以对比是一次显式选择,打开页面绝不做;客户端每个会话只读一次能力面(single-flight 缓存,和详情弹窗共用)。详情与删除跟着同一个读位置走:点卡片读的是该卡片来源模式下的详情与技能包,删除同样针对这个模式。
67
91
 
68
92
  卡片徽标标的是**来源**(插件 / 内置 / 用户 / 项目 / 自定义);受管 skill 的 `preset` 范围是策略不是来源,所以只在「范围就是主题」的地方出现——未生效清单和详情弹窗。
69
93
 
@@ -71,8 +95,8 @@ dsh plugin --profile web remove @khorsheed/dsh-capability-catalog
71
95
 
72
96
  | skill | 该区块显示 |
73
97
  |---|---|
74
- | 在受管根里 | preset 勾选网格 + 保存 + 释放回用户技能目录 |
75
- | 用户 / 项目 / 自定义根 | preset 勾选网格 + 移入受管目录并可限定 preset(没有「保存」:勾选就是 adopt 装进去时的范围) |
98
+ | 在受管根里 | preset 勾选网格 + 一个「保存」(清空勾选并保存 = 释放回用户技能目录,恢复为所有模式可见的普通用户技能) |
99
+ | 用户 / 项目 / 自定义根 | preset 勾选网格 + 一个「保存」(勾了子集再保存 = 收编进受管根并以该范围投递;全不勾 = 已经处处可见,无需写入) |
76
100
  | 插件提供(`runtime`) | 说明为什么不能设,并列出实际加载它的模式(chip 取自共用的能力面缓存) |
77
101
  | 内置(`bundled`) | 说明为什么不能设——跟随部署组合 |
78
102
  | 该部署没有 preset 名单 / 没有受管投递 | 不可用提示 |
package/cordis.patch.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  # capability-catalog bundle patch: inserts the plugin row into the composing
2
- # profile. The browser half (settings.plugin.item card) is discovered through
2
+ # profile. The browser half (the settings.section card) is discovered through
3
3
  # the package.json dsh.client block; this row activates the host half (the
4
4
  # skills snapshot + add-skill Remote). The @-prefixed name must stay quoted —
5
5
  # it is a YAML reserved character.