@deepseek-ai/dsh-skill 0.1.1-rc.2 → 0.1.2-alpha.2

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.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/skill/skill/README.md
5
- README.md: ae8812aeb3cc6583677d22bd5c35639318d15237
6
- README.zh.md: 5eae1d5aeb9eb87072d4b33c26f6c9a6e20cffdf
5
+ README.md: 662c230d8f366fa6dc2f36cdf21e2b9d51493f8b
6
+ README.zh.md: 81737e53b910fbd7dd0ee1e575d719d9f48e6548
package/README.md CHANGED
@@ -1,36 +1,58 @@
1
+ ---
2
+ description: "The skill provider registry for users and maintainers choosing, configuring, or debugging how skills from any source are merged, resolved, and loaded."
3
+ kind: "package-reference"
4
+ ---
5
+
1
6
  # @deepseek-ai/dsh-skill
2
7
 
3
8
  English | [中文](README.zh.md)
4
9
 
5
- Pure agent skill provider registry.
10
+ ## Summary
11
+
12
+ Agents and users can access reusable, task-specific instructions through one lookup no matter where the instructions come from: any provider can contribute skills from local directories, embedded plugin data, or a remote service, and every consumer receives one merged catalog with the winning skill for each name and can load any skill's full instructions on demand. Mount this plugin when skills should be loadable from more than one source or from a non-filesystem source, and skip it when a composition loads no skills. It ships no skill content of its own — pair it with at least one provider (the shipped `dsh-skill-filesystem`), and with `dsh-tool-skill` when agents should load skills.
13
+
14
+ ## Table of Contents
6
15
 
7
- This package owns the `ctx.skills` interface. It does not know whether skills come from local files, embedded plugin data, HTTP, or another backend; providers register those sources with `ctx.skills.registerProvider(...)`. The shipped local implementation is [`@deepseek-ai/dsh-skill-filesystem`](../skill-filesystem).
16
+ - [Use this package](#use-this-package)
17
+ - [Understand the implementation](#understand-the-implementation)
18
+ - [Further Exploration](#further-exploration)
19
+ - [Model Experience](#model-experience)
20
+ - [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
21
+ - [Dev Note](#dev-note)
8
22
 
9
- The registry is host+per-scope layered over [`@deepseek-ai/dsh-scope`](../../core/scope), the shape the tools registry established: a registration files into the layer of its calling context's scope — host rows and repository plugins land in the global layer, a plugin mounted by an agent preset's standing composition lands in that preset's layer — and a read merges the global layer with the viewing scope's chain, the nearest layer winning a duplicate name outright while rank decides duplicates only within one layer.
23
+ -----
10
24
 
11
- ## Service: `SkillRegistry` (ctx key: `skills`)
25
+ <a id="use-this-package"></a>
26
+ ## Use this package
12
27
 
13
- ### Public API
28
+ Mount the plugin to give a composition one skill registry. Skill sources (providers) and consumers (the model-facing catalog and loader, or your own code) all talk to `ctx.skills`; the registry merges everything any provider reports, so one lookup sees skills from every source.
14
29
 
15
- - `ctx.skills.registerProvider(create): () => void` Calls a synchronous provider factory with `{ signal, invalidate }`, then registers its readonly result by `provider.name`, unique within the calling context's layer. Duplicate names in one layer throw, `runtime` is reserved, and failed registration aborts the signal. The exact Cordis disposer unregisters the provider, aborts the signal, and preserves ordered composite teardown.
16
- - `ctx.skills.snapshot({ cwd?, signal?, scope? })` Returns the invocation-neutral `{ skills, complete }` observation for the viewing scope's merged layers. `complete` is false when any provider rejects or explicitly reports incomplete discovery, or when a second catalog revision races the bounded retry; candidates supplied by that observation remain in this result, which is never cached.
17
- - `ctx.skills.list({ cwd?, signal?, scope? })` Borrows the readonly view options, then returns every winning summary for the current workspace, merged across the global layer and the viewing scope's chain and sorted by name. Consumers apply `isModelInvocable(skill)` or `isUserInvocable(skill)` at their own boundary.
18
- - `ctx.skills.get(name, { cwd?, signal?, scope? })` Uses the same readonly options and winning candidate for discovery and loading, rechecks cancellation after discovery or a cache hit, races provider loading against the signal, validates the loaded definition, then returns it regardless of invocation policy.
19
- - `ctx.skills.register(skill): () => void` Registers a readonly runtime embedded skill into the calling context's layer, adding the all-invocable policy and `provider: "runtime"` when omitted. Same-name runtime registrations in one layer are first-wins: a duplicate logs a warning and gets a no-op disposer. Successful registrations return the exact Cordis disposer for ordered composite teardown.
30
+ ### When to choose it
20
31
 
21
- ### Events
32
+ Use `dsh-skill` when agents should load skills from more than one source through one interface, or when the source of skills is not the local filesystem. Avoid it when a composition needs no skill loading at all — the plugin adds a service and a per-lookup discovery cost. The shipped local provider (`dsh-skill-filesystem`) and the model-facing consumer (`dsh-tool-skill`) are separate packages; mount them alongside when the deployment wants local skills and model access.
22
33
 
23
- - `skills/change` is an unfiltered invalidation notification emitted after a provider or runtime contribution is registered or disposed and after an active provider's registration control invalidates. It carries no catalog or diff: each consumer refetches `snapshot()` with its own lookup options. Listener throws and rejected promises are logged and cannot veto the registry mutation or starve later listeners.
34
+ ### Mount and configure
24
35
 
25
- ### Config
36
+ Load the plugin like any Cordis plugin. The only configuration limits how many completed provider catalogs are kept in memory; everything else is provider behavior.
37
+
38
+ ```yaml
39
+ - name: '@deepseek-ai/dsh-skill'
40
+ ```
26
41
 
27
42
  | Field | Default | Meaning |
28
43
  |---|---|---|
29
- | `collectCacheMaxEntries` | `128` | Maximum completed cwd/provider catalogs kept in memory. |
44
+ | `collectCacheMaxEntries` | `128` | Completed cwd/provider catalogs kept in memory |
45
+
46
+ The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-skill) is the exhaustive source for every accepted field.
47
+
48
+ ### What the registry gives you
30
49
 
31
- ### Invocation policy
50
+ - **One merged catalog.** A consumer asks for the current catalog of a workspace and receives every winning skill summary from every provider, sorted by name — no provider-specific ordering or deduplication to do.
51
+ - **On-demand loading.** Asking for one skill by name returns the full instruction body from whichever provider owns the winning candidate; the registry re-validates the loaded definition and rejects a stale selection whose name changed between discovery and load.
52
+ - **Embedded skills.** Plugins register an in-memory skill with `ctx.skills.register(...)`; the registry fills in a default invocation policy and the `runtime` provider label. Same-name runtime registrations in one layer are first-wins with a warning.
53
+ - **Provider registration.** A provider contributes its catalog with `ctx.skills.registerProvider(...)`; registration is synchronous, and the returned disposer removes the provider. `runtime` is a reserved provider name.
32
54
 
33
- `SkillSummary.invocation` is a required typed policy object whose positive booleans `modelInvocable` and `userInvocable` describe the two surfaces independently. Providers return this resolved shape on every candidate and definition; only the `SkillRegistration` input may omit it, in which case `register()` supplies `{ modelInvocable: true, userInvocable: true }`. The registry keeps all four combinations so one discovery result can serve model-facing tools, human-facing commands, and trusted internal callers without conflating their catalogs.
55
+ An invocation policy on every skill decides which surfaces may advertise and load it: `modelInvocable` for model-facing tools and catalogs, `userInvocable` for human-facing commands. The registry keeps all four combinations, so one discovery result can serve both surfaces without conflating their catalogs.
34
56
 
35
57
  | Policy | Model | User |
36
58
  |---|---|---|
@@ -39,33 +61,66 @@ The registry is host+per-scope layered over [`@deepseek-ai/dsh-scope`](../../cor
39
61
  | `{ modelInvocable: false, userInvocable: true }` | excluded | included |
40
62
  | `{ modelInvocable: false, userInvocable: false }` | excluded | excluded |
41
63
 
42
- ### Shared model-facing rendering
64
+ ### Observable success and failures
65
+
66
+ A skill that any provider reports appears in the merged catalog, and loading it by its exact kebab-case name returns the body; an invalid name returns no skill rather than throwing. A provider that fails discovery is logged and skipped, and the observation is reported incomplete so consumers keep their last-good catalog; an explicit incomplete observation still contributes its candidates. A malformed candidate fails fast — the registry validates names, descriptions, invocation booleans, and provider ownership before caching or returning anything.
67
+
68
+ -----
69
+
70
+ <a id="understand-the-implementation"></a>
71
+ ## Understand the implementation
72
+
73
+ <details>
74
+ <summary>Implementation internals — click to expand</summary>
75
+
76
+ This section explains how the registry merges, caches, and invalidates provider catalogs; the observable behavior is fully covered in [Use this package](#use-this-package).
77
+
78
+ ### Design concept
43
79
 
44
- `renderSkillContent(skill)` renders one loaded skill as the canonical `<skill_content>` block (escaped `name` attribute, resource hints, verbatim body). It is the single truth for both loading paths: `dsh-tool-skill` returns it as the `skill` tool result and injects it at the user-explicit gesture boundary, so the model sees one shape regardless of who initiated the load. `escapeText` is exported beside it for consumers embedding prose in the same markup frame. The package also declares the `skill-invocation` `MessageSource` kind ({ name, form: 'instructions' }) that user-explicit injection stamps on its messages transcript consumers present the invocation from this metadata instead of re-parsing the body.
80
+ The package is built on one separation: the registry owns merging, winning resolution, and validation, while providers own where skills come from. A provider is a borrowed same-process object with a `list()` that returns candidates and a `get()` that loads a body; the registry never inspects skill content beyond validating its semantic fields.
45
81
 
46
- `isModelInvocable(skill)` and `isUserInvocable(skill)` read the matching positive field directly. `ctx.skills.get()` remains the trusted, policy-neutral loading primitive, so every user- or model-facing consumer must enforce the predicate that matches its surface before exposing or loading a skill.
82
+ The registry is host+per-scope layered, the shape the tools registry established: a registration files into the layer of its calling context's scope host rows and repository plugins land in the global layer, a plugin mounted by an agent preset's standing composition lands in that preset's layer. A read merges the global layer with the viewing scope's chain; the nearest layer wins a duplicate name outright, and within one layer duplicates resolve by rank, provider registration order, then provider-local order.
47
83
 
48
- ## Provider Contract
84
+ ### Source map
49
85
 
50
- A provider factory runs synchronously and receives one registration-scoped control. `control.signal` aborts when registration fails or is disposed; `control.invalidate()` clears completed catalogs only while that exact registration remains active, so late callbacks cannot affect a replacement with the same name. Immutable providers may ignore the control. Remote setup, authentication, and discovery belong in the provider's awaited `list(options)` call. An array return is shorthand for complete discovery; a provider that collected usable candidates but could not establish an authoritative observation returns `{ candidates, complete: false }`. Provider objects, lookup options, candidates, and definitions are borrowed readonly rather than cloned or rebound. Providers should honor `options.signal`; the registry also stops awaiting uncooperative discovery or loading after cancellation.
86
+ | File | Role |
87
+ |---|---|
88
+ | [`src/index.ts`](src/index.ts) | Plugin entry, `SkillRegistry` service, candidate and definition validation, shared model-facing rendering |
89
+ | [`src/invariant.ts`](src/invariant.ts) | Invariant companion |
51
90
 
52
- The registry validates candidates before caching and definitions before returning them. The winning provider receives the same candidate and opaque `locator` it returned from `list()`, allowing backend-specific file, URL, id, or version handles. Callers and providers must preserve the readonly contract.
91
+ ### Catalog collection
53
92
 
54
- Contract violations fail fast. A rejected provider `list()` is treated as a transient source failure and omitted. An explicit incomplete observation still contributes its candidates for `list()` and `get()`, but makes the aggregate snapshot incomplete and uncacheable. A provider or runtime revision change discards an in-flight result and retries once. If the retry is also superseded, its candidates are returned incomplete and uncached so a continuously invalidating provider cannot monopolize the caller. Within one layer, duplicate names resolve by rank, provider registration order, then provider-local order; across layers the nearest scope's entry wins the name. Summaries are sorted by skill name.
93
+ A read (`list`/`snapshot`) collects each layer's candidates: runtime skills first, then each provider's `list()` result, awaiting providers sequentially and containing failures. Candidates are validated, deduplicated within the layer, and merged across layers; summaries sort by name. Completed collections are cached per cwd, scope chain, and revision up to `collectCacheMaxEntries`; an in-flight collection retries once when a provider or runtime mutation bumps the revision mid-read, and a second change returns the latest candidates as an incomplete, uncached observation.
55
94
 
56
- Definitions remain progressively loaded. `get()` asks the winning provider for the body on every call rather than caching it in this registry. If the returned definition has a different name from the selected candidate, the stale selection is rejected and the registry internally invalidates that exact provider so the next snapshot rediscovers its catalog.
95
+ ### Loading and staleness
57
96
 
58
- ## Runtime Skills
97
+ `get()` selects the winning candidate, races the provider's load against the lookup's abort signal, and rechecks cancellation after selection or a cache hit. The returned definition must match the selected candidate's name; a mismatch invalidates the cached catalogs so the next snapshot rediscovers the provider's skills. Definitions are never cached — every load asks the provider for the current body.
59
98
 
60
- `ctx.skills.register(...)` is a convenience for embedded runtime skills. Runtime skills use rank `250`: project providers can override them, while they override the shipped local provider's custom and user roots. Runtime definitions and nested resource metadata are borrowed readonly; the service materializes one top-level definition to supply omitted invocation and provider defaults. Registration is first-wins within runtime contributions, so a duplicate contribution cannot remove the active one through its disposer.
99
+ ### Invalidation
61
100
 
62
- ## Consumer boundary
101
+ The registry has no TTL: only a provider calling its registration-scoped `invalidate()`, or a runtime registration or disposal, clears completed catalogs. Each invalidation bumps a revision, clears the cache, and emits the unfiltered `skills/change` event; consumers refetch with their own lookup options. `invalidate()` takes effect only while the exact registration that received it is still active, so a late callback cannot disturb a replacement provider with the same name.
63
102
 
64
- The registry does not render model guidance or register model-facing tools. [`@deepseek-ai/dsh-tool-skill`](../tool-skill) consumes `ctx.skills` to provide durable session catalogs and the `skill` tool, so providers remain independent of model-facing behavior.
103
+ </details>
65
104
 
105
+ -----
106
+
107
+ <a id="further-exploration"></a>
108
+ ## Further Exploration
109
+
110
+ Read these pages when the package-level contract is not enough. They move from the shared skill vocabulary to the shipped provider, the model-facing consumer, and the design rationale.
111
+
112
+ - [Skill subsystem reference](../../../docs/subsystems/skills.md) — the registry, provider contract, and local discovery priority.
113
+ - [skill-filesystem package](../skill-filesystem/README.md) — the shipped local provider that discovers skills from disk.
114
+ - [tool-skill package](../tool-skill/README.md) — the consumer that renders the session catalog and the `skill` tool.
115
+ - [Generated configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-skill) — every config field and its source declaration.
116
+ - [Skill invocation policy Agent Note](../../../.agents/notes/implemented/feature/2026-07-28-skill-invocation-policy.md) — the rationale for the model and user invocation controls.
117
+
118
+ -----
119
+
120
+ <a id="model-experience"></a>
66
121
  ## Model Experience
67
122
 
68
- Indirectly, through `dsh-tool-skill`, which renders provider summaries into durable initial or replacement catalog messages and loaded instructions into retained tool results.
123
+ Indirectly, through `dsh-tool-skill`, which renders provider summaries into durable initial or replacement catalog messages and loaded instruction bodies into retained tool results.
69
124
 
70
125
  #### KV Cache effect
71
126
 
@@ -73,7 +128,22 @@ No direct prompt effect. The named consumer owns the durable initial catalog and
73
128
 
74
129
  ## Known Limitations and Deferred Work
75
130
 
131
+ <a id="known-limitations-and-deferred-work"></a>
132
+
133
+
134
+ These limits define when the registry is a poor fit or needs special operational care. They are current package constraints, not a task backlog.
135
+
76
136
  - **Invalidation is provider-driven** — the registry has no TTL and cannot infer that an arbitrary remote source changed; each mutable provider must retain and call its registration-scoped `invalidate()` capability from its own observation mechanism.
77
- - **Providers are queried sequentially** — one slow cooperative provider delays every provider registered after it; cancellation stops the caller's wait but cannot terminate work an uncooperative provider keeps running.
137
+ - **Providers are queried sequentially** — one slow provider delays every provider registered after it; cancellation stops the caller's wait but cannot terminate work an uncooperative provider keeps running.
78
138
  - **Incomplete observations are not retained** — rejected providers are omitted and explicitly supplied candidates remain available only to the current lookup; the registry owns neither a last-good catalog nor per-provider diagnostics.
79
139
  - **Duplicate resolution is first-wins** — later lower-priority candidates within a layer are logged and hidden, and a nearer layer shadows a farther one silently; there is no API to inspect all shadowed definitions.
140
+
141
+ <a id="dev-note"></a>
142
+ ### Dev Note
143
+
144
+ <details>
145
+ <summary>Working context for maintainers — click to expand</summary>
146
+
147
+ This Dev Note is working context for maintainers and is explicitly non-authoritative — shipped behavior and limits live in the sections above and in the code. An open question is whether the registry should retain a last-good catalog or per-provider diagnostics for failed providers, or whether consumers should own that state; the incomplete-observations limitation records the current answer.
148
+
149
+ </details>
package/README.zh.md CHANGED
@@ -1,36 +1,58 @@
1
+ ---
2
+ description: "skill 提供方注册表,供选择、配置或排查来自任意来源的 skill 如何被合并、解析与加载的用户与维护者阅读。"
3
+ kind: "package-reference"
4
+ ---
5
+
1
6
  # @deepseek-ai/dsh-skill
2
7
 
3
8
  [English](README.md) | 中文
4
9
 
5
- agent skill(智能体技能)提供方注册表。
10
+ ## 概述
11
+
12
+ agent(智能体)和用户可以通过单一查找使用可复用的任务专项指令,无论指令来自何处:任意提供方都可以从本地目录、嵌入式插件数据或远程服务贡献 skill(技能),每个消费方都会收到一份合并目录——每个名称对应胜出的 skill——并能按需加载任一 skill 的完整指令。当组合需要从多个来源或非文件系统来源加载 skill 时,请挂载本插件;当组合完全不加载 skill 时,请跳过。它自身不携带任何 skill 内容——请至少搭配一个提供方(随附的 `dsh-skill-filesystem`);需要 agent 加载 skill 时,再搭配 `dsh-tool-skill`。
13
+
14
+ ## 目录
6
15
 
7
- 该包负责 `ctx.skills` 接口。它不知道 skill 来自本地文件、嵌入式插件数据、HTTP 还是其他后端;提供方通过 `ctx.skills.registerProvider(...)` 注册这些来源。已发布的本地实现是 [`@deepseek-ai/dsh-skill-filesystem`](../skill-filesystem)
16
+ - [使用本包](#use-this-package)
17
+ - [理解实现](#understand-the-implementation)
18
+ - [进一步探索](#further-exploration)
19
+ - [模型体验](#model-experience)
20
+ - [已知限制与延期工作](#known-limitations-and-deferred-work)
21
+ - [开发备注](#dev-note)
8
22
 
9
- 注册表基于 [`@deepseek-ai/dsh-scope`](../../core/scope) 采用宿主 + 按 scope 的分层结构,即工具注册表确立的形态:注册落入调用方上下文 scope 对应的层——宿主行与 repository 插件落入全局层,由 agent preset 常驻组合挂载的插件落入该 preset 的层——读取时将全局层与观察 scope 的链合并,最近层直接赢得重名,rank 只在单层内裁决重名。
23
+ -----
10
24
 
11
- ## 服务:`SkillRegistry`(ctx 键:`skills`)
25
+ <a id="use-this-package"></a>
26
+ ## 使用本包
12
27
 
13
- ### 公开 API
28
+ 挂载插件即可让组合拥有一个统一的 skill 注册表。skill 来源(提供方)和消费方(面向模型的目录与 loader,或你自己的代码)都通过 `ctx.skills` 交互;注册表合并任意提供方报告的一切内容,因此一次查找就能看到所有来源的 skill。
14
29
 
15
- - `ctx.skills.registerProvider(create): () => void` 调用同步提供方工厂并向其传入 `{ signal, invalidate }`,随后以在调用方上下文所在层内唯一的 `provider.name` 注册其只读结果。同层重复提供方名称会抛错,`runtime` 为保留名称;注册失败会中止信号。精确的 Cordis disposer 会注销提供方、中止信号,并保持有序组合拆卸。
16
- - `ctx.skills.snapshot({ cwd?, signal?, scope? })` 返回观察 scope 各层合并后、与调用策略无关的 `{ skills, complete }` 观测。任一提供方调用被拒绝或显式报告发现不完整,或有界重试期间又发生目录修订时,`complete` 为 false;该次观测提供的候选项仍保留在此结果中,但该结果绝不缓存。
17
- - `ctx.skills.list({ cwd?, signal?, scope? })` 借用只读视图选项,然后返回当前工作区中的全部胜出摘要;这些摘要在全局层与观察 scope 链之间合并,并按名称排序。消费方在自身边界调用 `isModelInvocable(skill)` 或 `isUserInvocable(skill)`。
18
- - `ctx.skills.get(name, { cwd?, signal?, scope? })` 在发现和加载中使用同一组只读选项和胜出候选项;在发现或缓存命中后重新检查取消,让提供方加载与信号竞速,验证已加载定义,然后无论调用策略如何都将其返回。
19
- - `ctx.skills.register(skill): () => void` 将只读运行时嵌入式 skill 注册进调用方上下文所在层,省略时添加允许模型和用户调用的策略以及 `provider: "runtime"`。同层同名运行时注册使用先到先得:重复项会记录警告,并获得无操作 disposer。成功注册会返回精确的 Cordis disposer,以供有序组合拆卸。
30
+ ### 何时选择
20
31
 
21
- ### 事件
32
+ agent 需要通过同一个接口从多个来源加载 skill,或 skill 来源并非本地文件系统时,选择 `dsh-skill`。当组合完全不需要加载 skill 时,请避免使用——插件会增加一个服务以及每次查找的发现成本。随附的本地提供方(`dsh-skill-filesystem`)和面向模型的消费方(`dsh-tool-skill`)是独立包;部署需要本地 skill 和模型访问时,请一并挂载。
22
33
 
23
- - `skills/change` 是一条不带过滤条件的失效通知,在提供方或运行时贡献注册或释放后,以及活动提供方的注册控制触发失效后发出。它不携带目录或 diff;每个消费方都使用自身的查找选项重新获取 `snapshot()`。监听器抛错或 Promise 拒绝会被记录,既不能否决注册表变更,也不能阻止后续监听器执行。
34
+ ### 挂载与配置
24
35
 
25
- ### 配置
36
+ 像任何 Cordis 插件一样加载即可。唯一配置项限制内存中保留的已完成提供方目录数量;其余都是提供方行为。
37
+
38
+ ```yaml
39
+ - name: '@deepseek-ai/dsh-skill'
40
+ ```
26
41
 
27
42
  | 字段 | 默认值 | 含义 |
28
43
  |---|---|---|
29
- | `collectCacheMaxEntries` | `128` | 内存中保留的最大已完成 cwd/提供方目录数。 |
44
+ | `collectCacheMaxEntries` | `128` | 内存中保留的已完成 cwd/提供方目录数 |
45
+
46
+ 生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-skill)是每个受支持字段的穷尽式真源。
47
+
48
+ ### 注册表提供什么
30
49
 
31
- ### 调用策略
50
+ - **合并后的单一目录。** 消费方查询工作区的当前目录,即可收到来自所有提供方的全部胜出 skill 摘要,并按名称排序——无需自行做提供方特有的排序或去重。
51
+ - **按需加载。** 按名称查询某个 skill,会从拥有胜出候选项的提供方返回完整指令正文;注册表会重新验证加载的定义,并拒绝在发现与加载之间名称发生变化的陈旧选择。
52
+ - **嵌入式 skill。** 插件可用 `ctx.skills.register(...)` 注册内存中的 skill;注册表会补入默认调用策略与 `runtime` 提供方标签。同层同名运行时注册采用先到先得,并记录警告。
53
+ - **提供方注册。** 提供方用 `ctx.skills.registerProvider(...)` 贡献目录;注册是同步的,返回的 disposer(资源释放)会移除该提供方。`runtime` 是保留的提供方名称。
32
54
 
33
- `SkillSummary.invocation` 是一个必填的类型化策略对象,其正向布尔字段 `modelInvocable` 和 `userInvocable` 分别描述两个接口。提供方会在每个候选项和定义中返回这一已解析形状;只有 `SkillRegistration` 输入可以省略它,此时 `register()` 会补入 `{ modelInvocable: true, userInvocable: true }`。注册表保留全部四种组合,使一次发现结果可以同时服务面向模型的工具、面向用户的命令和受信内部调用方,而不会混淆各自的目录。
55
+ 每个 skill 上的调用策略决定哪些接口可以展示并加载它:`modelInvocable` 用于面向模型的工具与目录,`userInvocable` 用于面向用户的命令。注册表保留全部四种组合,因此一次发现结果可以同时服务两个接口,而不会混淆各自的目录。
34
56
 
35
57
  | 策略 | 模型 | 用户 |
36
58
  |---|---|---|
@@ -39,41 +61,89 @@
39
61
  | `{ modelInvocable: false, userInvocable: true }` | 排除 | 包含 |
40
62
  | `{ modelInvocable: false, userInvocable: false }` | 排除 | 排除 |
41
63
 
42
- ### 共享的面向模型渲染
64
+ ### 可观察的成功与失败
65
+
66
+ 任意提供方报告的 skill 都会出现在合并目录中,按其精确 kebab-case 名称加载即可返回正文;无效名称返回无结果而非抛错。发现失败的提供方会被记录并跳过,观测被标记为不完整,因此消费方保留其最后一份可用目录;显式的不完整观测仍会贡献其候选项。格式错误的候选项会快速失败——注册表在缓存或返回任何内容之前,会先验证名称、描述、调用布尔值与提供方归属。
67
+
68
+ -----
69
+
70
+ <a id="understand-the-implementation"></a>
71
+ ## 理解实现
72
+
73
+ <details>
74
+ <summary>实现细节——点击展开</summary>
75
+
76
+ 本节解释注册表如何合并、缓存并失效提供方目录;可观察行为已在[使用本包](#use-this-package)中完整说明。
77
+
78
+ ### 设计理念
43
79
 
44
- `renderSkillContent(skill)` 把一个已加载 skill 渲染为规范的 `<skill_content>` 块(转义后的 `name` 属性、资源提示、原样正文)。它是两条加载路径的唯一真源:`dsh-tool-skill` 将其作为 `skill` 工具结果返回,并在用户显式的手势边界将其注入,因此无论加载由谁发起,模型看到的都是同一种形态。`escapeText` 随之一并导出,供要在同一标记框架中嵌入文案的消费方使用。该包还声明 `skill-invocation` 这个 `MessageSource` kind({ name, form: 'instructions' }),用户显式注入会把它打在自己的消息上——transcript(文本记录)消费方依据这份元数据呈现该次调用,而不是重新解析正文。
80
+ 本包建立在一个分离之上:注册表负责合并、胜出解析与验证,提供方负责 skill 来自哪里。提供方是借用的同进程对象,其 `list()` 返回候选项、`get()` 加载正文;注册表除验证语义字段外,从不检查 skill 内容。
45
81
 
46
- `isModelInvocable(skill)` `isUserInvocable(skill)` 分别直接读取对应的正向字段。`ctx.skills.get()` 仍是受信且与策略无关的加载原语,因此每个面向用户或模型的消费方都必须先执行与自身接口匹配的判定,再暴露或加载 skill。
82
+ 注册表采用宿主 + scope 的分层结构,即工具注册表确立的形态:注册落入调用方上下文 scope 对应的层——宿主行与 repository 插件落入全局层,由 agent preset 常驻组合挂载的插件落入该 preset 的层。读取时将全局层与观察 scope 的链合并;最近层直接赢得重名,单层内重名则依次按 rank、提供方注册顺序与提供方本地顺序裁决。
47
83
 
48
- ## 提供方约定
84
+ ### 源码地图
49
85
 
50
- 提供方工厂同步运行,并接收一项注册作用域内的控制能力。注册失败或释放时,`control.signal` 会中止;仅当该精确注册仍处于活动状态时,`control.invalidate()` 才会清除已完成目录,因此延迟回调无法影响同名替代项。不可变提供方可以忽略该控制能力。远程设置、身份验证和发现应在提供方的 `list(options)` 调用中完成,该调用会被等待。返回数组是完整发现的简写形式;若提供方已收集到可用候选项,却无法建立权威观测,则返回 `{ candidates, complete: false }`。提供方对象、查找选项、候选项和定义都以只读方式借用,而不是克隆或重新绑定。提供方应遵守 `options.signal`;取消后,注册表也会停止等待不协作的发现或加载。
86
+ | 文件 | 职责 |
87
+ |---|---|
88
+ | [`src/index.ts`](src/index.ts) | 插件入口、`SkillRegistry` 服务、候选项与定义验证、共享的面向模型渲染 |
89
+ | [`src/invariant.ts`](src/invariant.ts) | 不变式伴生插件 |
51
90
 
52
- 注册表在缓存前验证候选项,在返回前验证定义。胜出提供方会收到同一候选项和不透明 `locator`,两者都是它从 `list()` 返回的内容,从而支持后端专用文件、URL、id 或版本句柄。调用方和提供方必须保持只读约定。
91
+ ### 目录收集
53
92
 
54
- 违反约定时会快速失败。`list()` 返回的 Promise 被拒绝会被视为瞬时来源失败,并省略其结果。显式的不完整观测仍会为 `list()` `get()` 提供其候选项,但会使聚合快照不完整且不可缓存。提供方或运行时修订发生变化时,会丢弃正在进行的结果并重试一次。如果这次重试也被后续修订取代,则返回其候选项,并将结果标为不完整且不予缓存,以免持续触发失效的提供方一直占用调用方。单层内重复名称依次按 rank、提供方注册顺序和提供方本地顺序解决冲突;跨层则由最近 scope 的条目赢得名称。摘要按 skill 名称排序。
93
+ 读取(`list`/`snapshot`)会收集每一层的候选项:先是运行时 skill,再是各提供方的 `list()` 结果,提供方依次等待、失败被包含。候选项经验证后在层内去重,跨层合并;摘要按名称排序。完成的收集按 cwd、scope 链与 revision 缓存,上限为 `collectCacheMaxEntries`;读取中途提供方或运行时变更使 revision 递增时,进行中的收集会重试一次,第二次变更则返回最新候选项并标记为不完整、不予缓存。
55
94
 
56
- 定义仍采用渐进式加载。`get()` 每次调用都会向胜出提供方请求正文,而不是在此注册表中缓存正文。若返回定义的名称不同于所选候选项,系统会拒绝该陈旧选择,并由注册表在内部使该精确提供方失效,以便下一次快照重新发现其目录。
95
+ ### 加载与陈旧
57
96
 
58
- ## 运行时 skill
97
+ `get()` 选择胜出候选项,让提供方加载与查找的中止信号竞速,并在选择或缓存命中后重新检查取消。返回的定义必须与所选候选项同名;名称不符会使缓存目录失效,以便下一次快照重新发现该提供方的 skill。定义从不缓存——每次加载都向提供方请求当前正文。
59
98
 
60
- `ctx.skills.register(...)` 是嵌入式运行时 skill 的便利接口。运行时 skill 使用 rank `250`:项目提供方可覆盖它们,它们则覆盖已发布本地提供方的自定义根目录和用户根目录。运行时定义和嵌套资源元数据均以只读方式借用;服务只物化一个顶层定义,以补入省略的调用策略和 `provider` 默认值。运行时贡献内的注册使用先到先得,因此重复贡献无法通过其 disposer 移除当前生效的贡献。
99
+ ### 失效
61
100
 
62
- ## 消费方边界
101
+ 注册表没有 TTL:只有提供方调用其注册作用域内的 `invalidate()`,或发生运行时注册或释放时,才会清除已完成的目录。每次失效都会递增 revision、清空缓存,并发出不带过滤条件的 `skills/change` 事件;消费方用各自的查找选项重新获取。`invalidate()` 仅当接收它的那条精确注册仍处于活动状态时才生效,因此延迟回调无法干扰同名替代提供方。
63
102
 
64
- 注册表不渲染模型指引,也不注册面向模型的工具。[`@deepseek-ai/dsh-tool-skill`](../tool-skill) 消费 `ctx.skills` 以提供持久会话目录和 `skill` 工具,因此提供方仍与模型接口独立。
103
+ </details>
65
104
 
105
+ -----
106
+
107
+ <a id="further-exploration"></a>
108
+ ## 进一步探索
109
+
110
+ 当包级约定不够用时阅读以下页面。它们从共享的 skill 词汇逐步进入随附提供方、面向模型的消费方与设计依据。
111
+
112
+ - [skill 子系统参考](../../../docs/subsystems/skills.zh.md)——注册表、提供方约定与本地发现优先级。
113
+ - [skill-filesystem 包](../skill-filesystem/README.zh.md)——从磁盘发现 skill 的随附本地提供方。
114
+ - [tool-skill 包](../tool-skill/README.zh.md)——渲染会话目录与 `skill` 工具的消费方。
115
+ - [生成配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-skill)——每个配置字段及其源声明。
116
+ - [skill 调用策略 Agent Note](../../../.agents/notes/implemented/feature/2026-07-28-skill-invocation-policy.zh.md)——模型与用户调用控制的依据。
117
+
118
+ -----
119
+
120
+ <a id="model-experience"></a>
66
121
  ## 模型体验
67
122
 
68
- 通过 `dsh-tool-skill` 间接影响模型;该包将提供方摘要渲染到持久的初始目录或替换目录消息中,并将已加载指令渲染到已保留工具结果中。
123
+ 通过 `dsh-tool-skill` 间接影响模型;该包将提供方摘要渲染到持久的初始目录或替换目录消息中,并将加载的指令正文渲染到已保留的工具结果中。
69
124
 
70
125
  #### KV Cache 影响
71
126
 
72
127
  不直接影响提示词。指定的消费方负责持久初始目录,以及失效后的仅追加式目录替换。
73
128
 
74
- ## 已知限制与暂缓事项
129
+ ## 已知限制与延期工作
130
+
131
+ <a id="known-limitations-and-deferred-work"></a>
132
+
133
+
134
+ 这些限制说明注册表何时不合适,或何时需要特别的运维注意。它们是当前包约束,不是任务积压。
135
+
136
+ - **失效由提供方驱动**——注册表没有 TTL,无法推断任意远程来源是否已发生变化;每个可变提供方都必须保留其注册作用域内的 `invalidate()` 能力,并由自身的观测机制调用它。
137
+ - **提供方依次查询**——一个缓慢的提供方会延迟其后注册的所有提供方;取消会停止调用方的等待,但无法终止不响应取消的提供方持续运行的工作。
138
+ - **不保留不完整观测**——被拒绝的提供方会被省略,显式提供的候选项也仅在当前查找中可用;注册表既不负责最后一份可用目录,也不负责逐提供方诊断。
139
+ - **重名项的裁决采用先到先得**——系统会记录并隐藏层内较晚出现的低优先级候选项,较近的层会静默遮蔽较远的层;没有 API 可检查全部被遮蔽的定义。
140
+
141
+ <a id="dev-note"></a>
142
+ ### 开发备注
143
+
144
+ <details>
145
+ <summary>维护者的工作上下文——点击展开</summary>
146
+
147
+ 本开发备注是维护者的工作上下文,明确不具权威性——已交付的行为与限制以上文和代码为准。一个开放问题是:注册表是否应保留最后一份可用目录或逐提供方诊断,还是由消费方拥有该状态;「不保留不完整观测」限制记录了当前答案。
75
148
 
76
- - **失效由提供方驱动**:注册表没有 TTL,无法推断任意远程来源是否已发生变化;每个可变提供方都必须保留其注册作用域内的 `invalidate()` 能力,并由自身的观测机制调用它。
77
- - **提供方依次查询**:一个响应取消但速度缓慢的提供方会延迟之后注册的所有提供方;取消会停止调用方等待,但无法终止不响应取消的提供方持续运行的工作。
78
- - **不保留不完整观测**:被拒绝的提供方会被省略,显式提供的候选项也仅在当前查找中可用;注册表既不负责上一份可用目录,也不负责逐提供方诊断。
79
- - **重名项的裁决采用先到先得**:系统会记录并隐藏层内较晚出现的低优先级候选项,较近的层会静默遮蔽较远的层;不提供检查全部被遮蔽定义的 API。
149
+ </details>
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Service } from "@deepseek-ai/cordis";
2
- import { assertNever } from "@deepseek-ai/dsh-llm";
2
+ import { assertNever } from "@deepseek-ai/dsh-util-values";
3
3
  import { NamedEntries, ScopedLayers, scopeChainOf, scopeOf } from "@deepseek-ai/dsh-scope";
4
4
  import z from "@deepseek-ai/schemastery";
5
5
  //#region lib/types/index.js
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-skill",
3
3
  "description": "Agent skill provider registry for the DeepSeek Harness",
4
- "version": "0.1.1-rc.2",
4
+ "version": "0.1.2-alpha.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -32,18 +32,19 @@
32
32
  ],
33
33
  "license": "MIT",
34
34
  "peerDependencies": {
35
- "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
36
- "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
37
- "@deepseek-ai/dsh-scope": "^0.1.1-rc.2",
38
- "@deepseek-ai/cordis": "^4.0.1"
35
+ "@deepseek-ai/cordis": "^4.0.2",
36
+ "@deepseek-ai/dsh-llm": "^0.1.2-alpha.2",
37
+ "@deepseek-ai/dsh-scope": "^0.1.2-alpha.2",
38
+ "@deepseek-ai/dsh-invariants": "^0.1.2-alpha.2"
39
39
  },
40
40
  "dependencies": {
41
- "@deepseek-ai/schemastery": "^3.18.1"
41
+ "@deepseek-ai/dsh-util-values": "^0.1.2-alpha.2",
42
+ "@deepseek-ai/schemastery": "^3.18.2"
42
43
  },
43
44
  "devDependencies": {
44
- "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
45
- "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
46
- "@deepseek-ai/dsh-scope": "^0.1.1-rc.2",
47
- "@deepseek-ai/cordis": "^4.0.1"
45
+ "@deepseek-ai/cordis": "^4.0.2",
46
+ "@deepseek-ai/dsh-invariants": "^0.1.2-alpha.2",
47
+ "@deepseek-ai/dsh-llm": "^0.1.2-alpha.2",
48
+ "@deepseek-ai/dsh-scope": "^0.1.2-alpha.2"
48
49
  }
49
50
  }