@x1a0f3n9/dsh-tool-skill 0.1.5-rc.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DeepSeek
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,6 @@
1
+ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
2
+ # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
+ # after editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write packages/skill/tool-skill/README.md
5
+ README.md: eeee637c25bc38138deaf907e6bf73202f408b96
6
+ README.zh.md: f2c1adf49a838e84fd488f0cb803d3aa30f8b8ca
package/README.md ADDED
@@ -0,0 +1,258 @@
1
+ ---
2
+ description: "The model-facing skill catalog and loader tool for users and maintainers understanding what agents see, or configuring the session skill catalog."
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # @x1a0f3n9/dsh-tool-skill
7
+
8
+ English | [中文](README.zh.md)
9
+
10
+ ## Summary
11
+
12
+ Agents can discover and load skills during a session. Before the first request, when model-invocable skills exist and the `skill` tool is visible, they receive a durable catalog of available skill names and capped descriptions, and can use the `skill` tool to load full instructions. Users can invoke a user-invocable skill with `/name`, which injects the same instructions into that step. Catalog changes append a complete replacement, including an empty catalog that retires old names; configure `catalogDescriptionMaxLength` to limit each description.
13
+
14
+ ## Table of Contents
15
+
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)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## Use this package
27
+
28
+ Mount the plugin alongside the skill registry to give agents a session skill catalog and the `skill` loader tool. It requires `ctx.agents`, `ctx.tools`, and `ctx.skills`.
29
+
30
+ ### When to choose it
31
+
32
+ Use it when agents should discover and load skills during a session. Skip it when skill loading is handled by another consumer or not needed at all — without it, providers and the registry still work, but nothing renders a catalog or a tool for the model.
33
+
34
+ ### Mount and configure
35
+
36
+ Load the plugin together with the skill registry and at least one provider. The only configuration caps the normalized description length rendered in the catalog.
37
+
38
+ ```yaml
39
+ - name: '@x1a0f3n9/dsh-skill'
40
+ - name: '@x1a0f3n9/dsh-skill-filesystem'
41
+ - name: '@x1a0f3n9/dsh-tool-skill'
42
+ ```
43
+
44
+ | Field | Default | Meaning |
45
+ |---|---|---|
46
+ | `catalogDescriptionMaxLength` | `500` | Maximum normalized description length rendered in the session catalog; minimum 3 |
47
+
48
+ The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-tool-skill) is the exhaustive source for every accepted field.
49
+
50
+ ### What the model gets
51
+
52
+ - **A session catalog.** When model-invocable skills exist and the `skill` tool is visible, the agent receives a durable user-role message before its first request, listing each skill's name and a capped description; the message tells the model to load a skill with the tool before acting on it, and never to infer instructions from the summary alone.
53
+ - **A loader tool.** The model calls `skill` with the exact skill name and receives the full instruction body plus resource guidance in a canonical `<skill_content>` block; the result is retained as ordinary tool history.
54
+ - **Explicit user invocation.** A `/name` token in direct user input that names a user-invocable skill injects that skill's instructions into the step, without the model having to load it.
55
+ - **Live catalog updates.** Later membership, description, or visibility changes append a complete replacement catalog; removing every skill appends an empty catalog that retires older names.
56
+
57
+ ### Observable success and failures
58
+
59
+ Loading a listed skill returns its full instructions; the model sees one canonical shape whether the load came from the tool or from a user's explicit invocation. An invalid name reports `Error: invalid skill name "<name>"`, an unknown name reports the skill is unknown or no longer available, and a skill disabled for model invocation reports it is not available for model invocation. The catalog is omitted entirely when no catalog was ever published and either no model-invocable skills exist or the `skill` tool is hidden or shadowed; after a catalog has been published, either visibility loss — the `skill` tool hidden or shadowed by a same-name scoped tool — or removal of every skill instead appends an empty catalog that retires older names.
60
+
61
+ -----
62
+
63
+ <a id="understand-the-implementation"></a>
64
+ ## Understand the implementation
65
+
66
+ <details>
67
+ <summary>Implementation internals — click to expand</summary>
68
+
69
+ This section explains how the catalog and the invocation boundary are built; the observable behavior is fully covered in [Use this package](#use-this-package) and the Model Experience section below.
70
+
71
+ ### Design concept
72
+
73
+ The package is built on two ideas. First, the catalog is a durable projection, diffed by a digest over the published entries rather than the rendered prose, so the `<system-reminder>` framing can never force a republish and consumers never re-parse the `<available_skills>` block. Second, one canonical rendering serves both load paths — the tool result and the user-explicit injection — through `renderSkillContent` shared from `dsh-skill`, so the model sees the same `<skill_content>` shape regardless of who initiated the load.
74
+
75
+ ### Source map
76
+
77
+ | File | Role |
78
+ |---|---|
79
+ | [`src/index.ts`](src/index.ts) | Plugin entry: tool registration, catalog and gesture pre-step listeners, rendering and digest |
80
+ | — | No runtime invariant companion is published; this model-facing adapter has no independent lifecycle stream; execution relations are owned by the capability seam it calls. |
81
+
82
+ ### Catalog lifecycle
83
+
84
+ At each eligible `agent/pre-step`, the plugin snapshots the calling session's skill catalog, applies exact `skill` tool visibility, filters to model-invocable skills, and compares a digest of the entries against the newest visible `skill-catalog` message in the session log. When the digest changed, it hands the `enter` decision a durable user-role message containing the complete replacement catalog; an empty replacement explicitly retires earlier names. An incomplete provider snapshot emits nothing and preserves the last-good view for the next pre-step. The visibility check compares against the exact tool definition this plugin registered, so a scoped same-name shadow removes both the schema and its guidance; the plugin works mounted globally or inside one agent's composition.
85
+
86
+ ### Invocation boundary
87
+
88
+ The `/name` gesture listener scans only claimed user messages: a whitespace-bounded token naming a user-invocable skill in the workspace catalog injects the same `<skill_content>` rendering as a `user`-role instructions context appended after every other injection. Unknown names and user-disabled skills stay ordinary prose. This is the only entry point for `disable-model-invocation` skills, which the catalog and the `skill` tool never expose.
89
+
90
+ </details>
91
+
92
+ -----
93
+
94
+ <a id="further-exploration"></a>
95
+ ## Further Exploration
96
+
97
+ Read these pages when the package-level contract is not enough. They move from the registry vocabulary behind the catalog to the exact tool schema and the design rationale.
98
+
99
+ - [Skill subsystem reference](../../../docs/subsystems/skills.md) — the registry and provider vocabulary behind the catalog.
100
+ - [skill package](../skill/README.md) — the registry and the shared `renderSkillContent` rendering.
101
+ - [Generated tool catalog](../../../docs/tool-catalog.md#deepseek-aidsh-tool-skill) — the exact `skill` schema the model receives.
102
+ - [User-explicit skill invocation Agent Note](../../../.agents/notes/archived/feature/2026-08-08-user-explicit-skill-invocation.md) — the `/name` gesture design.
103
+
104
+ -----
105
+
106
+ <a id="model-experience"></a>
107
+ ## Model Experience
108
+
109
+ ### Session catalog
110
+
111
+ #### What the model sees
112
+
113
+ If model-invocable skills exist and this exact `skill` tool is visible, the agent receives the catalog template below as a durable user-role message before the first request, with one data-dependent entry per sorted skill. Later membership, description, or visibility changes append a complete replacement using the same `<available_skills>` envelope; deleting every skill appends an empty envelope with an explicit instruction not to use older names. The template's closing sentence is the rule against double-loading: the user-explicit gesture boundary (the pre-step listener below) injects the same `renderSkillContent` output (shared from `@x1a0f3n9/dsh-skill`) inline, and the catalog tells the model to follow that block instead of re-loading the skill through the tool; the replacement-catalog template carries the same anti-double-loading rule in both arms, including the emptied catalog.
114
+
115
+ ##### Skill catalog template
116
+
117
+ ```markdown
118
+ <system-reminder>
119
+ A skill is a reusable set of task-specific instructions. The following skills are available in this session:
120
+
121
+ <available_skills>
122
+ - `<name>`: <normalized-and-capped-description>
123
+ </available_skills>
124
+
125
+ If the user names a skill, or the task clearly matches a skill's description, call the `skill` tool with the exact skill name before taking task actions. Load all applicable skills, then follow their full instructions. This catalog contains summaries only; do not infer or follow a skill's instructions until it has been loaded.
126
+ A user may also invoke a skill directly; its <skill_content> block then appears in this conversation. Follow it, and do not call the `skill` tool again for that skill.
127
+ </system-reminder>
128
+ ```
129
+
130
+ #### Token effect
131
+
132
+ Repeated input cost scales with skill count and `catalogDescriptionMaxLength`; no initial catalog tokens are sent when the list is empty or the tool is hidden or shadowed. Each actual catalog change adds one retained complete replacement message.
133
+
134
+ #### KV Cache effect
135
+
136
+ The initial durable catalog is appended after the existing reusable prefix. Dynamic changes are append-only history after that catalog, so earlier reusable tokens stay intact while each newly appended catalog and later turns form a new suffix. A new or resumed instance with a changed digest may affect cache reuse from the newly appended catalog position.
137
+
138
+ ### Tool schema
139
+
140
+ #### What the model sees
141
+
142
+ The model sees the generated [`skill` schema](../../../docs/tool-catalog.md#deepseek-aidsh-tool-skill).
143
+
144
+ #### Token effect
145
+
146
+ Fixed schema cost per request where the tool is visible.
147
+
148
+ #### KV Cache effect
149
+
150
+ Prefix-stable while the tool definition and visibility are unchanged. Shadowing, restrictions, or plugin lifecycle changes may invalidate reuse from this schema.
151
+
152
+ ### Tool result
153
+
154
+ #### What the model sees
155
+
156
+ A successful call uses the result template and the provider-managed, directory, URL, or opaque resource guidance below.
157
+
158
+ ##### Skill result template
159
+
160
+ ```markdown
161
+ <skill_content name="<escaped-name>">
162
+ <skill_resources>
163
+ <resource-guidance>
164
+ </skill_resources>
165
+
166
+ <skill_instructions>
167
+ <provider-owned-instruction-body>
168
+ </skill_instructions>
169
+ </skill_content>
170
+ ```
171
+
172
+ ##### Provider-managed resource guidance
173
+
174
+ ```markdown
175
+ Resources for this skill are managed by provider "<provider>".
176
+ Load referenced resources only as needed.
177
+ ```
178
+
179
+ ##### Directory resource guidance
180
+
181
+ ```markdown
182
+ Base directory for this skill: <path>
183
+ Resolve relative paths mentioned by this skill against the base directory before using them. Load referenced resources only as needed.
184
+ ```
185
+
186
+ ##### URL resource guidance
187
+
188
+ ```markdown
189
+ Base URL for this skill: <url>
190
+ Resolve relative URLs mentioned by this skill against the base URL before using them. Load referenced resources only as needed.
191
+ ```
192
+
193
+ ##### Opaque resource guidance
194
+
195
+ ```markdown
196
+ Resources for this skill: <description>
197
+ Load referenced resources only as needed.
198
+ ```
199
+
200
+ #### Token effect
201
+
202
+ Loaded instructions are data-dependent tool-result tokens, resent on later steps until compaction; no duplicate `agent.inject()` copy is made.
203
+
204
+ #### KV Cache effect
205
+
206
+ Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.
207
+
208
+ ### Tool errors
209
+
210
+ #### What the model sees
211
+
212
+ Invalid or stale selections return exactly `Error: invalid skill name "<name>"`, `Error: skill "<name>" is unknown or no longer available`, or `Error: skill "<name>" is not available for model invocation`. Provider-thrown lookup text is data-dependent and receives the same `Error: <message>` wrapper.
213
+
214
+ #### Token effect
215
+
216
+ Only a failing call adds these retained tokens.
217
+
218
+ #### KV Cache effect
219
+
220
+ Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.
221
+
222
+ ### User-explicit invocation injection
223
+
224
+ #### What the model sees
225
+
226
+ A whitespace-bounded `/name` token anywhere in a claimed user message, naming a user-invocable skill in the workspace catalog, injects that skill's full `<skill_content>` rendering (the exact result-template shape above) as a `user`-role instructions context appended after every other injection of that step — background first, the material to act on last. Only direct user input is scanned, the check runs on the loaded definition, and unknown or user-disabled names stay ordinary prose. This is the sole entry point for `disable-model-invocation` skills, which the catalog and the `skill` tool never expose; the catalog's closing sentence tells the model to follow the injected block instead of re-loading it.
227
+
228
+ #### Token effect
229
+
230
+ Each gesture adds one rendered skill body to that turn as injected context — the same size as the tool result for the same skill, paid deterministically at the user's request instead of at the model's discretion. Repeated gestures for one skill within one step inject once.
231
+
232
+ #### KV Cache effect
233
+
234
+ Append-only; the injection lands after the reusable request prefix inside the step's message batch and does not invalidate existing KV-cache entries.
235
+
236
+ ## Known Limitations and Deferred Work
237
+
238
+ <a id="known-limitations-and-deferred-work"></a>
239
+
240
+
241
+ These limits define when the catalog or the loader is a poor fit. They are current package constraints, not a task backlog.
242
+
243
+ - **The catalog omits `whenToUse`, source, and provider metadata** — routing is based only on name and a capped description; `whenToUse` remains provider metadata and is not rendered by the loaded wrapper either.
244
+ - **Loaded instruction bodies have no size cap** — a provider can return a skill large enough to consume substantial next-step context; only catalog descriptions are truncated.
245
+ - **Resources are guidance, not attachments** — the tool reports a base directory/URL/opaque hint but neither enumerates nor fetches referenced files for the model.
246
+ - **Loading is one-shot text** — there is no partial, streaming, or cached-content handle when a remote provider is slow or a skill body is large.
247
+ - **Catalog replacement is whole-list** — one changed name or description appends every visible summary; this keeps stale-name retirement explicit but costs tokens proportional to the catalog.
248
+ - **Bodies are not versioned** — body-only edits do not change the catalog digest or notify the model; a later tool call reads the current provider content while earlier tool results remain historical facts.
249
+
250
+ <a id="dev-note"></a>
251
+ ### Dev Note
252
+
253
+ <details>
254
+ <summary>Working context for maintainers — click to expand</summary>
255
+
256
+ None.
257
+
258
+ </details>
package/README.zh.md ADDED
@@ -0,0 +1,258 @@
1
+ ---
2
+ description: "面向模型的 skill(技能)目录与加载工具,供希望了解 agent(智能体)看到的内容或配置会话 skill 目录的用户与维护者阅读。"
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # @x1a0f3n9/dsh-tool-skill
7
+
8
+ [English](README.md) | 中文
9
+
10
+ ## 概述
11
+
12
+ agent 可以在会话期间发现并加载 skill。在首次请求前,如果存在模型可调用 skill 且 `skill` 工具可见,agent 会收到一份持久目录,列出可用 skill 的名称与有长度上限的描述,并可用 `skill` 工具加载完整指令。用户可以用 `/name` 调用某个用户可调用的 skill,把相同的指令注入该步骤。目录变更会追加一份完整替换,其中空目录会停用旧名称;可配置 `catalogDescriptionMaxLength` 来限制每条描述的长度。
13
+
14
+ ## 目录
15
+
16
+ - [使用本包](#use-this-package)
17
+ - [理解实现](#understand-the-implementation)
18
+ - [进一步探索](#further-exploration)
19
+ - [模型体验](#model-experience)
20
+ - [已知限制与延期工作](#known-limitations-and-deferred-work)
21
+ - [开发备注](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## 使用本包
27
+
28
+ 与 skill 注册表一起挂载该插件,即可让 agent 拥有会话 skill 目录和 `skill` 加载工具。它需要 `ctx.agents`、`ctx.tools` 与 `ctx.skills`。
29
+
30
+ ### 何时选择
31
+
32
+ 当 agent 应在会话期间发现并加载 skill 时使用它。当 skill 加载由其他消费方处理或完全不需要时,请跳过——没有它,提供方与注册表仍可工作,但不会有任何东西为模型渲染目录或工具。
33
+
34
+ ### 挂载与配置
35
+
36
+ 与 skill 注册表和至少一个提供方一起加载该插件。唯一配置项限制目录中渲染的规范化描述长度。
37
+
38
+ ```yaml
39
+ - name: '@x1a0f3n9/dsh-skill'
40
+ - name: '@x1a0f3n9/dsh-skill-filesystem'
41
+ - name: '@x1a0f3n9/dsh-tool-skill'
42
+ ```
43
+
44
+ | 字段 | 默认值 | 含义 |
45
+ |---|---|---|
46
+ | `catalogDescriptionMaxLength` | `500` | 会话目录中渲染的规范化描述最大长度;最小为 3 |
47
+
48
+ 生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-tool-skill)是每个受支持字段的穷尽式真源。
49
+
50
+ ### 模型得到什么
51
+
52
+ - **会话目录。** 当存在模型可调用 skill 且 `skill` 工具可见时,agent 会在首次请求前收到一条持久的用户角色消息,列出每个 skill 的名称与有长度上限的描述;该消息告诉模型在着手任务前先用工具加载 skill,且绝不能仅凭摘要推断指令。
53
+ - **加载工具。** 模型以精确的 skill 名称调用 `skill`,并收到完整指令正文以及规范的 `<skill_content>` 块中的资源指引;该结果作为普通工具历史保留。
54
+ - **用户显式调用。** 直接用户输入中的 `/name` token 若指名某个用户可调用 skill,会把该 skill 的指令注入该步骤,而无需模型自行加载。
55
+ - **实时目录更新。** 后续成员关系、描述或可见性变化会追加完整的替换目录;删除全部 skill 时会追加空目录,停用较早的名称。
56
+
57
+ ### 可观察的成功与失败
58
+
59
+ 加载列出的 skill 会返回其完整指令;无论加载来自工具还是用户的显式调用,模型看到的都是同一种规范形态。无效名称会报告 `Error: invalid skill name "<name>"`,未知名称会报告该 skill 未知或已不可用,被禁用模型调用的 skill 会报告其不可用于模型调用。如果从未发布过目录,并且不存在模型可调用 skill,或 `skill` 工具被隐藏或遮蔽,则会整体省略目录;目录发布后,无论可见性丧失——`skill` 工具被隐藏或被同名作用域工具遮蔽——还是删除全部 skill,都会改为追加空目录来停用旧名称。
60
+
61
+ -----
62
+
63
+ <a id="understand-the-implementation"></a>
64
+ ## 理解实现
65
+
66
+ <details>
67
+ <summary>实现细节——点击展开</summary>
68
+
69
+ 本节解释目录与调用边界如何构建;可观察行为已在[使用本包](#use-this-package)和下方模型体验章节中完整说明。
70
+
71
+ ### 设计理念
72
+
73
+ 本包建立在两个想法之上。第一,目录是一种持久投影,按已发布条目的 digest 而非渲染后的正文做差异比较,因此 `<system-reminder>` 包装永远不会强制重新发布,消费方也不需要重新解析 `<available_skills>` 块。第二,一条规范渲染服务两条加载路径——工具结果与用户显式注入——经由共享自 `dsh-skill` 的 `renderSkillContent`,因此无论加载由谁发起,模型看到的都是同一种 `<skill_content>` 形态。
74
+
75
+ ### 源码地图
76
+
77
+ | 文件 | 职责 |
78
+ |---|---|
79
+ | [`src/index.ts`](src/index.ts) | 插件入口:工具注册、目录与手势 pre-step 监听器、渲染与 digest |
80
+ | — | 不发布运行时不变式伴生入口;这个面向模型的适配器没有独立的生命周期流;执行关系由它调用的能力 seam 负责。 |
81
+
82
+ ### 目录生命周期
83
+
84
+ 在每次符合条件的 `agent/pre-step`,插件都会快照调用会话的 skill 目录,应用 `skill` 工具的精确可见性,过滤出模型可调用的 skill,并把条目 digest 与会话日志中最新可见的 `skill-catalog` 消息做比较。digest 变化时,它把包含完整替换目录的持久用户角色消息交给 `enter` 决策;空替换会显式停用较早的名称。提供方快照不完整时不发送任何内容,并为下一次 pre-step 保留最后一份可用视图。可见性检查针对本插件所注册的精确工具定义,因此作用域内同名的遮蔽项会同时移除 schema 及其指引;该插件既可全局挂载,也可挂在单个 agent 的组合内。
85
+
86
+ ### 调用边界
87
+
88
+ `/name` 手势监听器只扫描已认领的用户消息:若某个以空白为界、指名工作区目录中用户可调用 skill 的 token 出现,则把同一份 `<skill_content>` 渲染作为 `user` 角色的指令上下文注入,追加在该步骤所有其他注入之后。未知名称与用户不可调用的名称保持为普通行文。这是 `disable-model-invocation` skill 唯一的入口,目录与 `skill` 工具永不暴露这类 skill。
89
+
90
+ </details>
91
+
92
+ -----
93
+
94
+ <a id="further-exploration"></a>
95
+ ## 进一步探索
96
+
97
+ 当包级约定不够用时阅读以下页面。它们从目录背后的注册表词汇逐步进入精确工具 schema 与设计依据。
98
+
99
+ - [skill 子系统参考](../../../docs/subsystems/skills.zh.md)——目录背后的注册表与提供方词汇。
100
+ - [skill 包](../skill/README.zh.md)——注册表与共享的 `renderSkillContent` 渲染。
101
+ - [生成工具目录](../../../docs/tool-catalog.zh.md#deepseek-aidsh-tool-skill)——模型接收的精确 `skill` schema。
102
+ - [用户显式 skill 调用 Agent Note](../../../.agents/notes/archived/feature/2026-08-08-user-explicit-skill-invocation.md)——`/name` 手势设计。
103
+
104
+ -----
105
+
106
+ <a id="model-experience"></a>
107
+ ## 模型体验
108
+
109
+ ### 会话目录
110
+
111
+ #### 模型看到什么
112
+
113
+ 如果存在模型可调用 skill,且可见的正是这个 `skill` 工具,agent 会在第一个请求之前收到下方目录模板,其中包含每个已排序 skill 的一条随数据而定的条目。该目录是一条持久的用户角色消息。后续成员关系、描述或可见性的变化会使用同一个 `<available_skills>` 信封追加完整替换;删除所有 skill 时,会追加一个空信封,并明确指示不得使用旧名称。模板的结尾一句是防止双重加载的规则:用户显式的手势边界(下文的 pre-step 监听器)会把同一份 `renderSkillContent` 输出(共享自 `@x1a0f3n9/dsh-skill`)内联注入,目录则告诉模型遵循该块,而不是再经工具重新加载该 skill;替换目录模板的两个分支——包括清空后的目录——都携带同一条防双重加载规则。
114
+
115
+ ##### Skill 目录模板
116
+
117
+ ```markdown
118
+ <system-reminder>
119
+ A skill is a reusable set of task-specific instructions. The following skills are available in this session:
120
+
121
+ <available_skills>
122
+ - `<name>`: <normalized-and-capped-description>
123
+ </available_skills>
124
+
125
+ If the user names a skill, or the task clearly matches a skill's description, call the `skill` tool with the exact skill name before taking task actions. Load all applicable skills, then follow their full instructions. This catalog contains summaries only; do not infer or follow a skill's instructions until it has been loaded.
126
+ A user may also invoke a skill directly; its <skill_content> block then appears in this conversation. Follow it, and do not call the `skill` tool again for that skill.
127
+ </system-reminder>
128
+ ```
129
+
130
+ #### Token 影响
131
+
132
+ 重复输入成本随 skill 数量和 `catalogDescriptionMaxLength` 增长;当列表为空或工具被隐藏或遮蔽时,不会发送初始目录 token。每次实际目录变更都会添加一条保留的完整替换消息。
133
+
134
+ #### KV Cache 影响
135
+
136
+ 初始持久目录追加在现有可重用前缀之后。动态变更作为该目录之后的仅追加历史,因此较早的可重用 token 保持不变,每条新追加的目录和后续轮次都会形成新的后缀。新建或恢复的实例如果 digest 发生变化,可能会从新追加的目录位置起影响缓存重用。
137
+
138
+ ### 工具 schema
139
+
140
+ #### 模型看到什么
141
+
142
+ 模型会看到生成的 [`skill` schema](../../../docs/tool-catalog.zh.md#deepseek-aidsh-tool-skill)。
143
+
144
+ #### Token 影响
145
+
146
+ 工具可见时,每次请求都有固定的 schema token 开销。
147
+
148
+ #### KV Cache 影响
149
+
150
+ 工具定义和可见性不变时,前缀稳定。遮蔽、限制或插件生命周期变更可能从该 schema 起使重用失效。
151
+
152
+ ### 工具结果
153
+
154
+ #### 模型看到什么
155
+
156
+ 成功调用使用下方结果模板,以及提供方管理的资源指引、目录资源指引、URL 资源指引或不透明资源指引。
157
+
158
+ ##### Skill 结果模板
159
+
160
+ ```markdown
161
+ <skill_content name="<escaped-name>">
162
+ <skill_resources>
163
+ <resource-guidance>
164
+ </skill_resources>
165
+
166
+ <skill_instructions>
167
+ <provider-owned-instruction-body>
168
+ </skill_instructions>
169
+ </skill_content>
170
+ ```
171
+
172
+ ##### 提供方管理的资源指引
173
+
174
+ ```markdown
175
+ Resources for this skill are managed by provider "<provider>".
176
+ Load referenced resources only as needed.
177
+ ```
178
+
179
+ ##### 目录资源指引
180
+
181
+ ```markdown
182
+ Base directory for this skill: <path>
183
+ Resolve relative paths mentioned by this skill against the base directory before using them. Load referenced resources only as needed.
184
+ ```
185
+
186
+ ##### URL 资源指引
187
+
188
+ ```markdown
189
+ Base URL for this skill: <url>
190
+ Resolve relative URLs mentioned by this skill against the base URL before using them. Load referenced resources only as needed.
191
+ ```
192
+
193
+ ##### 不透明资源指引
194
+
195
+ ```markdown
196
+ Resources for this skill: <description>
197
+ Load referenced resources only as needed.
198
+ ```
199
+
200
+ #### Token 影响
201
+
202
+ 已加载指令是取决于数据的工具结果 token,并在后续步骤中重新发送,直到压缩;不会制作重复的 `agent.inject()` 副本。
203
+
204
+ #### KV Cache 影响
205
+
206
+ 仅追加;新可见内容位于可重用请求前缀之后,不会使现有 KV Cache 条目失效。
207
+
208
+ ### 工具错误
209
+
210
+ #### 模型看到什么
211
+
212
+ 无效或陈旧选择会精确返回 `Error: invalid skill name "<name>"`、`Error: skill "<name>" is unknown or no longer available` 或 `Error: skill "<name>" is not available for model invocation`。提供方抛出的查找文本取决于数据,并套用同一个 `Error: <message>` 包装层。
213
+
214
+ #### Token 影响
215
+
216
+ 只有失败调用会添加这些已保留 token。
217
+
218
+ #### KV Cache 影响
219
+
220
+ 仅追加;新可见内容位于可重用请求前缀之后,不会使现有 KV Cache 条目失效。
221
+
222
+ ### 用户显式调用注入
223
+
224
+ #### 模型看到什么
225
+
226
+ 已认领用户消息中任意位置、以空白为界、指名工作区目录中某个用户可调用 skill 的 `/name` token,会把该 skill 的完整 `<skill_content>` 渲染(与上文结果模板完全相同的形态)作为 `user` 角色的指令上下文注入,追加在该步骤所有其他注入之后——背景在前,模型要着手处理的材料在最后。只扫描直接的用户输入,检查在已加载定义上进行,未知名称和用户不可调用的名称保持为普通行文。这是 `disable-model-invocation` skill 唯一的入口,目录和 `skill` 工具永不暴露这类 skill;目录的结尾一句会告诉模型遵循注入块,而不是重新加载它。
227
+
228
+ #### Token 影响
229
+
230
+ 每次手势会把一份渲染后的 skill 正文作为注入上下文加进该轮次——尺寸与同一 skill 的工具结果相同,该成本会随用户请求必然产生,而非由模型自行决定。同一步骤内对同一 skill 的重复手势只注入一次。
231
+
232
+ #### KV Cache 影响
233
+
234
+ 仅追加;注入落在该步骤的消息批次中、可重用请求前缀之后,不会使现有 KV Cache 条目失效。
235
+
236
+ ## 已知限制与延期工作
237
+
238
+ <a id="known-limitations-and-deferred-work"></a>
239
+
240
+
241
+ 这些限制说明目录或加载器何时不合适。它们是当前包约束,不是任务积压。
242
+
243
+ - **目录省略 `whenToUse`、来源和提供方元数据**——路由只基于名称和有长度上限的描述;`whenToUse` 仍是提供方元数据,加载后的包装层也不渲染它。
244
+ - **已加载指令正文没有大小上限**——提供方可返回足以占用大量下一步上下文的 skill;只有目录描述会被截断。
245
+ - **资源是指引,而非附件**——工具报告基础目录/URL/不透明提示,但既不列举也不为模型获取引用文件。
246
+ - **加载是一次性文本**——远程提供方缓慢或 skill 正文很大时,不提供部分内容、流式输出或缓存内容句柄。
247
+ - **目录替换采用全量列表**——一个名称或描述发生变化,就会追加所有可见摘要;这样能显式停用陈旧名称,但 token 成本与目录大小成正比。
248
+ - **正文不做版本化**——仅修改正文不会改变目录 digest,也不会通知模型;后续工具调用会读取提供方的当前内容,而先前工具结果仍是历史事实。
249
+
250
+ <a id="dev-note"></a>
251
+ ### 开发备注
252
+
253
+ <details>
254
+ <summary>维护者的工作上下文——点击展开</summary>
255
+
256
+ 无。
257
+
258
+ </details>
package/lib/index.js ADDED
@@ -0,0 +1,396 @@
1
+ import { createHash } from "node:crypto";
2
+ import z from "@deepseek-ai/schemastery";
3
+ import { defineTool } from "@x1a0f3n9/dsh-tools";
4
+ import { createUserMessage } from "@x1a0f3n9/dsh-llm";
5
+ import "@deepseek-ai/cordis";
6
+ import { escapeText, isModelInvocable, isSkillName, isUserInvocable, renderSkillContent } from "@x1a0f3n9/dsh-skill";
7
+ //#region ../../util/brand/src/index.ts
8
+ /**
9
+ * Apply a compile-time number brand without changing the value.
10
+ * @param value - number admitted by the domain that owns the target brand.
11
+ * @returns the same number with the requested compile-time brand.
12
+ */
13
+ function brandNumber(value) {
14
+ return value;
15
+ }
16
+ //#endregion
17
+ //#region ../../core/session/src/types.ts
18
+ /**
19
+ * Admit a numeric value as an existing Session event position.
20
+ * @param value - non-negative safe integer admitted by the owning log operation.
21
+ * @returns the same number with the Session-sequence brand.
22
+ */
23
+ function SessionSeq(value) {
24
+ if (!Number.isSafeInteger(value) || value < 0 || Object.is(value, -0)) throw new TypeError(`SessionSeq must be a non-negative safe integer, got ${String(value)}`);
25
+ return brandNumber(value);
26
+ }
27
+ //#endregion
28
+ //#region lib/types/index.js
29
+ /**
30
+ * Durable session skill catalog and model-facing `skill` loader tool.
31
+ *
32
+ * @module @x1a0f3n9/dsh-tool-skill
33
+ */
34
+ const name = "tool-skill";
35
+ const inject = [
36
+ "agents",
37
+ "tools",
38
+ "skills"
39
+ ];
40
+ const DEFAULT_CATALOG_DESCRIPTION_MAX_LENGTH = 500;
41
+ /** Durable entry list mirroring the rendered catalog lines, for non-model consumers. */
42
+ function catalogSourceEntries(skills, descriptionMaxLength) {
43
+ return skills.map((skill) => ({
44
+ name: skill.name,
45
+ description: catalogDescription(skill.description, descriptionMaxLength)
46
+ }));
47
+ }
48
+ /** Validate and default the model-facing skill catalog configuration. */
49
+ const Config = z.object({ catalogDescriptionMaxLength: z.number().default(DEFAULT_CATALOG_DESCRIPTION_MAX_LENGTH) });
50
+ /**
51
+ * Register the model-facing skill loader and its visibility-matched
52
+ * durable session catalog. The catalog is emitted only when the calling agent
53
+ * resolves this plugin's exact tool registration; a restriction or scoped
54
+ * same-name shadow therefore removes both the schema and its call guidance.
55
+ */
56
+ function apply(ctx, config = {}) {
57
+ const catalogDescriptionMaxLength = config.catalogDescriptionMaxLength ?? DEFAULT_CATALOG_DESCRIPTION_MAX_LENGTH;
58
+ assertPositiveInteger("catalogDescriptionMaxLength", catalogDescriptionMaxLength, 3);
59
+ const skillTool = defineTool({
60
+ name: "skill",
61
+ description: "Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.",
62
+ parameters: { name: {
63
+ type: "string",
64
+ required: true,
65
+ description: "The exact skill name from the available skills list."
66
+ } },
67
+ output: {
68
+ schema: {
69
+ type: "object",
70
+ additionalProperties: false,
71
+ properties: {
72
+ name: {
73
+ type: "string",
74
+ required: true
75
+ },
76
+ provider: {
77
+ type: "string",
78
+ required: true
79
+ },
80
+ resourceBase: { oneOf: [
81
+ {
82
+ type: "object",
83
+ additionalProperties: false,
84
+ properties: {
85
+ kind: {
86
+ type: "string",
87
+ required: true,
88
+ const: "directory"
89
+ },
90
+ path: {
91
+ type: "string",
92
+ required: true
93
+ }
94
+ }
95
+ },
96
+ {
97
+ type: "object",
98
+ additionalProperties: false,
99
+ properties: {
100
+ kind: {
101
+ type: "string",
102
+ required: true,
103
+ const: "url"
104
+ },
105
+ url: {
106
+ type: "string",
107
+ required: true
108
+ }
109
+ }
110
+ },
111
+ {
112
+ type: "object",
113
+ additionalProperties: false,
114
+ properties: {
115
+ kind: {
116
+ type: "string",
117
+ required: true,
118
+ const: "opaque"
119
+ },
120
+ description: {
121
+ type: "string",
122
+ required: true
123
+ }
124
+ }
125
+ }
126
+ ] },
127
+ content: {
128
+ type: "string",
129
+ required: true
130
+ }
131
+ }
132
+ },
133
+ render: (_args, value) => [{
134
+ type: "text",
135
+ text: renderSkillContent(value)
136
+ }]
137
+ },
138
+ async execute(args, exec) {
139
+ if (!isSkillName(args.name)) throw new Error(`invalid skill name "${args.name}"`);
140
+ const lookup = {
141
+ cwd: exec.agent?.session.header.cwd,
142
+ signal: exec.signal,
143
+ scope: exec.agent
144
+ };
145
+ const summary = (await ctx.skills.list(lookup)).find((skill) => skill.name === args.name);
146
+ if (!summary) throw new Error(`skill "${args.name}" is unknown or no longer available`);
147
+ if (!isModelInvocable(summary)) throw new Error(`skill "${args.name}" is not available for model invocation`);
148
+ const skill = await ctx.skills.get(args.name, lookup);
149
+ if (!skill) throw new Error(`skill "${args.name}" is unknown or no longer available`);
150
+ if (!isModelInvocable(skill)) throw new Error(`skill "${args.name}" is not available for model invocation`);
151
+ return {
152
+ name: skill.name,
153
+ provider: skill.provider,
154
+ ...skill.resourceBase !== void 0 ? { resourceBase: { ...skill.resourceBase } } : {},
155
+ content: skill.content
156
+ };
157
+ },
158
+ presentCall(args) {
159
+ return {
160
+ card: "generic",
161
+ title: `Load skill ${args.name}`,
162
+ kind: "read",
163
+ rawInput: args.name
164
+ };
165
+ }
166
+ });
167
+ ctx.tools.register(skillTool);
168
+ ctx.on("agent/pre-step", async ({ agent, messages, signal }, next) => {
169
+ const decision = await next();
170
+ if (decision.kind === "reject") return decision;
171
+ const names = invokedSkillNames(messages);
172
+ if (names.length === 0) return decision;
173
+ signal.throwIfAborted();
174
+ const lookup = {
175
+ cwd: agent.session.header.cwd,
176
+ signal,
177
+ scope: agent
178
+ };
179
+ const injections = [];
180
+ for (const name of names) {
181
+ const skill = await ctx.skills.get(name, lookup);
182
+ signal.throwIfAborted();
183
+ if (skill === void 0 || !isUserInvocable(skill)) continue;
184
+ const source = {
185
+ kind: "skill-invocation",
186
+ name,
187
+ form: "instructions"
188
+ };
189
+ injections.push(createUserMessage({
190
+ content: [{
191
+ type: "text",
192
+ text: renderSkillContent(skill)
193
+ }],
194
+ source
195
+ }));
196
+ }
197
+ if (injections.length === 0) return decision;
198
+ return {
199
+ ...decision,
200
+ messages: [...decision.messages, ...injections]
201
+ };
202
+ });
203
+ ctx.on("agent/pre-step", async ({ agent, signal }, next) => {
204
+ const decision = await next();
205
+ if (decision.kind === "reject") return decision;
206
+ signal.throwIfAborted();
207
+ const snapshot = ctx.tools.get(skillTool.name, agent) === skillTool ? await ctx.skills.snapshot({
208
+ cwd: agent.session.header.cwd,
209
+ signal,
210
+ scope: agent
211
+ }) : {
212
+ skills: [],
213
+ complete: true
214
+ };
215
+ signal.throwIfAborted();
216
+ if (!snapshot.complete) return decision;
217
+ const skills = snapshot.skills.filter(isModelInvocable);
218
+ const entries = catalogSourceEntries(skills, catalogDescriptionMaxLength);
219
+ const digest = digestCatalogEntries(entries);
220
+ const history = catalogHistory(agent);
221
+ const existing = catalogMessage(decision.messages);
222
+ if (history.visibleDigest === digest) return existing === void 0 ? decision : {
223
+ ...decision,
224
+ messages: decision.messages.filter((message) => message.id !== existing.message.id)
225
+ };
226
+ if (existing !== void 0 && digestCatalogEntries(existing.entries) === digest) return decision;
227
+ if (!history.published && skills.length === 0) return existing === void 0 ? decision : {
228
+ ...decision,
229
+ messages: decision.messages.filter((message) => message.id !== existing.message.id)
230
+ };
231
+ const catalog = history.published ? renderCatalogUpdate(entries) : renderCatalogMessage(entries);
232
+ return {
233
+ ...decision,
234
+ messages: existing === void 0 ? [...decision.messages, catalog] : decision.messages.map((message) => message.id === existing.message.id ? catalog : message)
235
+ };
236
+ });
237
+ }
238
+ function renderCatalogMessage(entries) {
239
+ return createUserMessage({
240
+ content: [{
241
+ type: "text",
242
+ text: [
243
+ "<system-reminder>",
244
+ "A skill is a reusable set of task-specific instructions. The following skills are available in this session:",
245
+ "",
246
+ "<available_skills>",
247
+ ...renderCatalogEntries(entries),
248
+ "</available_skills>",
249
+ "",
250
+ "If the user names a skill, or the task clearly matches a skill's description, call the `skill` tool with the exact skill name before taking task actions. Load all applicable skills, then follow their full instructions. This catalog contains summaries only; do not infer or follow a skill's instructions until it has been loaded.",
251
+ "A user may also invoke a skill directly; its <skill_content> block then appears in this conversation. Follow it, and do not call the `skill` tool again for that skill.",
252
+ "</system-reminder>"
253
+ ].join("\n")
254
+ }],
255
+ source: {
256
+ kind: "skill-catalog",
257
+ form: "catalog",
258
+ entries
259
+ }
260
+ });
261
+ }
262
+ function renderCatalogUpdate(entries) {
263
+ const availability = entries.length === 0 ? ["No skills are currently available through the `skill` tool. Do not use names from earlier skill catalogs.", "A user may still invoke a skill directly; its <skill_content> block then appears in this conversation. Follow it, and do not call the `skill` tool for it."] : ["Use only names in this replacement catalog. If the user names a listed skill, or the task clearly matches its description, call the `skill` tool with the exact name before acting.", "A user may also invoke a skill directly; its <skill_content> block then appears in this conversation. Follow it, and do not call the `skill` tool again for that skill."];
264
+ return createUserMessage({
265
+ content: [{
266
+ type: "text",
267
+ text: [
268
+ "<system-reminder>",
269
+ "The available skill catalog changed. This complete catalog replaces every earlier available-skills list in this session:",
270
+ "",
271
+ "<available_skills>",
272
+ ...renderCatalogEntries(entries),
273
+ "</available_skills>",
274
+ "",
275
+ ...availability,
276
+ "</system-reminder>"
277
+ ].join("\n")
278
+ }],
279
+ source: {
280
+ kind: "skill-catalog",
281
+ form: "catalog",
282
+ update: true,
283
+ entries
284
+ }
285
+ });
286
+ }
287
+ /**
288
+ * Model-facing catalog lines, projected from the same entries the source records.
289
+ * The pseudo-XML escaping belongs to this frame, not to the published fact, so it
290
+ * is applied here and never stored. Names are `isSkillName`-validated and carry
291
+ * no escapable character.
292
+ */
293
+ function renderCatalogEntries(entries) {
294
+ return entries.map((entry) => `- \`${entry.name}\`: ${escapeText(entry.description)}`);
295
+ }
296
+ /**
297
+ * Catalog identity over the durable entry list rather than the rendered prose.
298
+ * The entries are what changes; the surrounding `<system-reminder>` framing is
299
+ * written for the model and must not decide whether a republish is needed.
300
+ */
301
+ function digestCatalogEntries(entries) {
302
+ const canonical = entries.map((entry) => JSON.stringify([entry.name, entry.description])).join("\n");
303
+ return createHash("sha256").update(canonical).digest("hex");
304
+ }
305
+ /**
306
+ * Entries of one durable catalog message, or undefined when the record is not a
307
+ * usable catalog.
308
+ *
309
+ * `agent.session.snapshotEvents()` may contain a resumed, forked, or externally written seed,
310
+ * and seed validation only guarantees a source object with a non-empty `kind`;
311
+ * no per-kind field is checked there. An unreadable record is therefore treated
312
+ * as "not this plugin's catalog" — the posture the replaced content digest had —
313
+ * rather than throwing inside the step listener, which would fail every
314
+ * subsequent turn of that session.
315
+ */
316
+ function readCatalogEntries(source) {
317
+ const entries = source.entries;
318
+ if (!Array.isArray(entries)) return void 0;
319
+ const readable = [];
320
+ for (const entry of entries) {
321
+ if (typeof entry !== "object" || entry === null) return void 0;
322
+ const { name, description } = entry;
323
+ if (typeof name !== "string" || name === "" || typeof description !== "string") return void 0;
324
+ readable.push({
325
+ name,
326
+ description
327
+ });
328
+ }
329
+ return readable;
330
+ }
331
+ function catalogHistory(agent) {
332
+ const visible = new Set(agent.session.surface.nodes);
333
+ let published = false;
334
+ for (let index = agent.session.seq - 1; index >= 0; index -= 1) {
335
+ const event = agent.session.eventAt(SessionSeq(index));
336
+ if (event === void 0) throw new Error(`skill catalog cannot read seq ${String(index)} below the current Session length`);
337
+ if (event.type !== "user/message" || event.data.source.kind !== "skill-catalog") continue;
338
+ const entries = readCatalogEntries(event.data.source);
339
+ if (entries === void 0) continue;
340
+ const digest = digestCatalogEntries(entries);
341
+ published = true;
342
+ if (visible.has(event.seq)) return {
343
+ visibleDigest: digest,
344
+ published
345
+ };
346
+ }
347
+ return { published };
348
+ }
349
+ function catalogMessage(messages) {
350
+ for (const message of messages) {
351
+ if (message.source.kind !== "skill-catalog") continue;
352
+ const entries = readCatalogEntries(message.source);
353
+ if (entries !== void 0) return {
354
+ message,
355
+ entries
356
+ };
357
+ }
358
+ }
359
+ function catalogDescription(value, maxLength) {
360
+ const normalized = value.replaceAll(/\s+/g, " ").trim();
361
+ return normalized.length <= maxLength ? normalized : `${normalized.slice(0, maxLength - 3)}...`;
362
+ }
363
+ function assertPositiveInteger(name, value, minimum = 1) {
364
+ if (!Number.isInteger(value) || value < minimum) throw new Error(`tool-skill: ${name} must be an integer greater than or equal to ${minimum}`);
365
+ }
366
+ /**
367
+ * A whitespace-bounded `/name` token (the public skill-name grammar) anywhere
368
+ * in the text — the same word-boundary shape the transcript chip decoration
369
+ * uses, so a gesture reads as one wherever it sits in the sentence. A second
370
+ * `/` or any non-boundary character breaks the match, which keeps file paths
371
+ * (`/usr/bin`) and fractions (`5/8`) out.
372
+ */
373
+ const SKILL_GESTURE = /(^|\s)\/([a-z0-9]+(?:-[a-z0-9]+)*)(?=\s|$)/g;
374
+ /**
375
+ * `/name` gesture tokens from the claimed user messages, deduplicated in
376
+ * first-seen order. Every text block of direct user input is scanned; no
377
+ * other source can forge a gesture.
378
+ * @param messages - the step's claimed batch.
379
+ * @returns candidate skill names, unvalidated against the registry.
380
+ */
381
+ function invokedSkillNames(messages) {
382
+ const names = [];
383
+ for (const message of messages) {
384
+ if (message.source.kind !== "user") continue;
385
+ for (const block of message.content) {
386
+ if (block.type !== "text") continue;
387
+ for (const match of block.text.matchAll(SKILL_GESTURE)) {
388
+ const name = match[2];
389
+ if (name !== void 0 && !names.includes(name)) names.push(name);
390
+ }
391
+ }
392
+ }
393
+ return names;
394
+ }
395
+ //#endregion
396
+ export { Config, apply, inject, name };
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Durable session skill catalog and model-facing `skill` loader tool.
3
+ *
4
+ * @module @x1a0f3n9/dsh-tool-skill
5
+ */
6
+ import type { Context } from '@deepseek-ai/cordis';
7
+ import z from '@deepseek-ai/schemastery';
8
+ export declare const name = "tool-skill";
9
+ export declare const inject: string[];
10
+ /**
11
+ * Durable provider and item records for one published session skill catalog. The catalog is a
12
+ * `catalog`-form context, so it records the entries it published beside the
13
+ * model-facing prose: a consumer presenting the list must not re-parse the
14
+ * `<available_skills>` block, whose framing exists for the model.
15
+ */
16
+ export interface SkillCatalogSource {
17
+ readonly kind: 'skill-catalog';
18
+ readonly form: 'catalog';
19
+ /** Marks a replacement catalog rather than this session's first publication. */
20
+ readonly update?: true;
21
+ /** Exactly the entries this message published, in catalog order. */
22
+ readonly entries: readonly {
23
+ readonly name: string;
24
+ readonly description: string;
25
+ }[];
26
+ }
27
+ declare module '@x1a0f3n9/dsh-llm' {
28
+ interface MessageSourceMap {
29
+ 'skill-catalog': SkillCatalogSource;
30
+ }
31
+ }
32
+ /** Model-facing skill catalog configuration. */
33
+ export interface Config {
34
+ /** Maximum normalized description length rendered in the session catalog; minimum 3. */
35
+ catalogDescriptionMaxLength?: number;
36
+ }
37
+ /** Validate and default the model-facing skill catalog configuration. */
38
+ export declare const Config: z<Config>;
39
+ /**
40
+ * Register the model-facing skill loader and its visibility-matched
41
+ * durable session catalog. The catalog is emitted only when the calling agent
42
+ * resolves this plugin's exact tool registration; a restriction or scoped
43
+ * same-name shadow therefore removes both the schema and its call guidance.
44
+ */
45
+ export declare function apply(ctx: Context, config?: Config): void;
46
+ //# sourceMappingURL=index.d.ts.map
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@x1a0f3n9/dsh-tool-skill",
3
+ "description": "Model-facing skill loading tool for the DeepSeek Harness",
4
+ "version": "0.1.5-rc.2",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
11
+ "directory": "packages/skill/tool-skill"
12
+ },
13
+ "type": "module",
14
+ "main": "lib/index.js",
15
+ "types": "lib/types/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./lib/types/index.d.ts",
19
+ "default": "./lib/index.js"
20
+ },
21
+ "./src/*": "./src/*",
22
+ "./package.json": "./package.json"
23
+ },
24
+ "files": [
25
+ "lib/index.js",
26
+ "lib/types/**/*.d.ts"
27
+ ],
28
+ "license": "MIT",
29
+ "peerDependencies": {
30
+ "@x1a0f3n9/dsh-agent": "^0.1.5-rc.2",
31
+ "@x1a0f3n9/dsh-skill": "^0.1.5-rc.2",
32
+ "@deepseek-ai/cordis": "^4.0.2",
33
+ "@x1a0f3n9/dsh-llm": "^0.1.5-rc.2",
34
+ "@x1a0f3n9/dsh-tools": "^0.1.5-rc.2"
35
+ },
36
+ "dependencies": {
37
+ "@deepseek-ai/schemastery": "^3.18.2"
38
+ },
39
+ "devDependencies": {
40
+ "@x1a0f3n9/dsh-session": "^0.1.5-rc.2",
41
+ "@x1a0f3n9/dsh-llm": "^0.1.5-rc.2",
42
+ "@x1a0f3n9/dsh-agent-loop-testkit": "^0.1.5-rc.2",
43
+ "@x1a0f3n9/dsh-skill": "^0.1.5-rc.2",
44
+ "@x1a0f3n9/dsh-agent": "^0.1.5-rc.2",
45
+ "@x1a0f3n9/dsh-scope": "^0.1.5-rc.2",
46
+ "@x1a0f3n9/dsh-tools": "^0.1.5-rc.2",
47
+ "@x1a0f3n9/dsh-skill-filesystem": "^0.1.5-rc.2",
48
+ "@deepseek-ai/cordis": "^4.0.2"
49
+ }
50
+ }