feat: add optional dsh badge skill provider

This commit is contained in:
Turtle
2026-08-05 21:50:44 +08:00
parent c21d3cfed8
commit 928c99876e
33 changed files with 601 additions and 5 deletions

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-06-bundled-dsh-badge-skill.md
2026-08-06-bundled-dsh-badge-skill.md: afe0b21d64a414a9e78ef55459a42c0d3817e3fd
2026-08-06-bundled-dsh-badge-skill.zh.md: de1ec989570b07987b12f0a291c84643aa5531fd

View File

@@ -0,0 +1,25 @@
# Agent Note: Bundled dsh badge skill
Status: implemented
English | [中文](2026-08-06-bundled-dsh-badge-skill.zh.md)
## Problem
DeepSeek Harness has an official attribution badge skill, but keeping it only in a developer's personal skill directory makes it unavailable to other DSH installations and gives the shipped application no explicit opt-in point.
## Decision
`@deepseek-ai/dsh-skill-badge` is a native Cordis plugin that registers one immutable bundled provider on `ctx.skills`. The provider owns the `dsh-badge` summary, instruction body, and PNG resource base; `dsh-tool-skill` remains the sole owner of model-facing catalog and loader rendering.
The shipped CLI composition declares `skill-badge` as disabled. Enabling that existing row is the explicit opt-in; disabled installations advertise no badge skill and gain no model-visible content.
The provider uses the bundled rank after project, custom, and user filesystem sources, so a user-owned `dsh-badge` definition can override it through the ordinary registry precedence contract. Provider disposal removes the contribution through the registry-owned effect.
## Alternatives considered
A Codex marketplace plugin was rejected because it would install into a different runtime and would not participate in DSH's `ctx.skills` seam. Mounting `dsh-skill-local` over the packaged files was rejected because filesystem discovery, parsing, and watching add lifecycle machinery that an immutable single-skill provider does not need.
## Consequences
The badge instructions and source PNG are versioned with DSH and resolve through a packaged directory resource base. The provider has no configuration surface. Package tests pin provider lifecycle and the official PNG bytes, while a keyless assembled-application snapshot pins the enabled catalog and loaded skill body.

View File

@@ -0,0 +1,25 @@
# Agent Note: 内置 dsh 徽章 skill
Status: implemented
[English](2026-08-06-bundled-dsh-badge-skill.md) | 中文
## 问题
DeepSeek Harness 已有官方署名徽章 skill技能但如果它只保存在某位开发者的个人 skill 目录中,其他 DSH 安装实例便无法使用,交付的应用也没有显式的选择加入点。
## 决策
`@deepseek-ai/dsh-skill-badge` 是一个原生 Cordis 插件,会在 `ctx.skills` 上注册一个不可变的内置提供方。该提供方负责 `dsh-badge` 的摘要、指令正文和 PNG 资源基底;`dsh-tool-skill` 仍是面向模型的目录与 loader 渲染的唯一归属方。
交付的 CLI命令行界面组合将 `skill-badge` 声明为禁用。启用这个现有配置行就是显式选择加入;禁用它的安装实例不会公开任何徽章 skill也不会获得任何模型可见内容。
该提供方使用排在项目、自定义及用户文件系统来源之后的内置 rank因此用户自有的 `dsh-badge` 定义可通过注册表的常规优先级契约覆盖它。提供方释放时,注册表拥有的 effect 会移除该贡献。
## 曾考虑的替代方案
未采用 Codex marketplace 插件,因为它会安装到不同的运行时,无法参与 DSH 的 `ctx.skills` seam。未采用使用 `dsh-skill-local` 挂载随包文件的方案,因为文件系统发现、解析和监视会引入不必要的生命周期机制,而不可变的单一 skill 提供方并不需要这些机制。
## 后果
徽章指令和源 PNG 随 DSH 一同纳入版本管理,并通过以随包目录为基础的资源基底解析。该提供方没有配置面。包测试固定提供方生命周期和官方 PNG 的字节内容;无密钥的组装应用快照则固定启用后的目录和已加载的 skill 正文。

View File

@@ -72,6 +72,8 @@ flowchart LR
cfg --> plugin_dsh_base_skill
plugin_dsh_base_skill_local["skill-local<br/>@deepseek-ai/dsh-skill-local"]
cfg --> plugin_dsh_base_skill_local
plugin_dsh_base_skill_badge["skill-badge<br/>@deepseek-ai/dsh-skill-badge"]
cfg --> plugin_dsh_base_skill_badge
plugin_dsh_base_tool_skill["tool-skill<br/>@deepseek-ai/dsh-tool-skill"]
cfg --> plugin_dsh_base_tool_skill
plugin_dsh_base_commands["commands<br/>@deepseek-ai/dsh-commands"]
@@ -182,6 +184,7 @@ flowchart LR
| `workspace-context` | `@deepseek-ai/dsh-workspace-context` |
| `skill` | `@deepseek-ai/dsh-skill` |
| `skill-local` | `@deepseek-ai/dsh-skill-local` |
| `skill-badge` | `@deepseek-ai/dsh-skill-badge` |
| `tool-skill` | `@deepseek-ai/dsh-tool-skill` |
| `commands` | `@deepseek-ai/dsh-commands` |
| `goal` | `@deepseek-ai/dsh-goal` |

View File

@@ -204,6 +204,10 @@
- id: skill-local
name: '@deepseek-ai/dsh-skill-local'
- id: skill-badge
name: '@deepseek-ai/dsh-skill-badge'
disabled: true
- id: tool-skill
name: '@deepseek-ai/dsh-tool-skill'

View File

@@ -92,6 +92,7 @@
"@deepseek-ai/dsh-session-title-first-message-llm": "workspace:^",
"@deepseek-ai/dsh-settings-local": "workspace:^",
"@deepseek-ai/dsh-skill": "workspace:^",
"@deepseek-ai/dsh-skill-badge": "workspace:^",
"@deepseek-ai/dsh-skill-local": "workspace:^",
"@deepseek-ai/dsh-spill-local": "workspace:^",
"@deepseek-ai/dsh-spill-policy": "workspace:^",

View File

@@ -0,0 +1,173 @@
import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'
import { LOADER_SMOKE_TEST_TIMEOUT_MS, runLoaderSmoke } from '@deepseek-ai/dsh-loader-smoke'
const binScript = fileURLToPath(new URL('./fixtures/dsh-badge/snapshot.ts', import.meta.url))
const configPath = fileURLToPath(new URL('./fixtures/dsh-badge/cordis.yml', import.meta.url))
const defaultConfigPath = fileURLToPath(new URL('./fixtures/dsh-badge/default.cordis.yml', import.meta.url))
const tsconfigPath = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url))
const badgeAssetsPath = fileURLToPath(new URL('../../../packages/skill/skill-badge/assets/', import.meta.url))
describe('dsh badge assembled snapshot', () => {
it('advertises and loads the opt-in bundled skill through the shipped app', async () => {
const disabled = await runLoaderSmoke({
label: 'disabled dsh badge skill snapshot',
tempDirPrefix: 'headless-snapshot-dsh-badge-disabled-',
binScript,
libBinScript: binScript,
configPath: defaultConfigPath,
tsconfigPath,
})
const enabled = await runLoaderSmoke({
label: 'dsh badge skill snapshot',
tempDirPrefix: 'headless-snapshot-dsh-badge-',
binScript,
libBinScript: binScript,
configPath,
tsconfigPath,
})
const disabledSnapshot = JSON.parse(disabled.stdout) as unknown
const enabledSnapshot = JSON.parse(
enabled.stdout.replaceAll(badgeAssetsPath, '{{badgeAssetsPath}}'),
) as unknown
expect(disabled.stderr).toBe('')
expect(enabled.stderr).toBe('')
expect(disabledSnapshot).toMatchInlineSnapshot(`
{
"result": {
"content": [
{
"text": "Error: skill "dsh-badge" is unknown or no longer available",
"type": "text",
},
],
"error": {
"message": "skill "dsh-badge" is unknown or no longer available",
},
"isError": true,
},
}
`)
expect(enabledSnapshot).toMatchInlineSnapshot(`
{
"catalog": [
{
"text": "<system-reminder>
A skill is a reusable set of task-specific instructions. The following skills are available in this session:
<available_skills>
- \`dsh-badge\`: Add the official “powered by dsh” badge to documents, pull requests, merge requests, and other content produced with DeepSeek Harness. Use whenever creating a pull request or merge request. Also use when the user asks for a dsh badge, powered-by-dsh attribution, or a reusable dsh badge asset or snippet.
</available_skills>
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.
</system-reminder>",
"type": "text",
},
],
"result": {
"content": [
{
"text": "<skill_content name="dsh-badge">
<skill_resources>
Base directory for this skill: {{badgeAssetsPath}}
Resolve relative paths mentioned by this skill against the base directory before using them. Load referenced resources only as needed.
</skill_resources>
<skill_instructions>
# dsh Badge
Add the official “powered by dsh” badge without recreating or restyling it.
## Assets
- Local PNG: [\`dsh-badge.png\`](dsh-badge.png), 726×120 source image; render at 121×20
- Shields.io image URL: \`https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white\`
- Project URL: \`https://github.com/deepseek-harness/deepseek-harness\`
## Markdown
Use this linked badge in Markdown:
\`\`\`markdown
[![](https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white)](https://github.com/deepseek-harness/deepseek-harness)
\`\`\`
If attribution should not be linked, use:
\`\`\`markdown
![](https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white)
\`\`\`
## Usage rules
- For GitHub or GitLab Markdown, use the Shields.io URL and link it to the project URL unless the user asks for an unlinked image.
- For Feishu and other systems that import remote images unreliably, upload \`dsh-badge.png\` from this skill directory instead of generating another badge.
- Preserve the badge's 121×20 dimensions and aspect ratio.
- Place the badge at the end of the attributed document or section unless the user specifies another position.
- Do not substitute another color, logo, label, or project URL.
</skill_instructions>
</skill_content>",
"type": "text",
},
],
"isError": false,
"value": {
"content": "# dsh Badge
Add the official “powered by dsh” badge without recreating or restyling it.
## Assets
- Local PNG: [\`dsh-badge.png\`](dsh-badge.png), 726×120 source image; render at 121×20
- Shields.io image URL: \`https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white\`
- Project URL: \`https://github.com/deepseek-harness/deepseek-harness\`
## Markdown
Use this linked badge in Markdown:
\`\`\`markdown
[![](https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white)](https://github.com/deepseek-harness/deepseek-harness)
\`\`\`
If attribution should not be linked, use:
\`\`\`markdown
![](https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white)
\`\`\`
## Usage rules
- For GitHub or GitLab Markdown, use the Shields.io URL and link it to the project URL unless the user asks for an unlinked image.
- For Feishu and other systems that import remote images unreliably, upload \`dsh-badge.png\` from this skill directory instead of generating another badge.
- Preserve the badge's 121×20 dimensions and aspect ratio.
- Place the badge at the end of the attributed document or section unless the user specifies another position.
- Do not substitute another color, logo, label, or project URL.
",
"name": "dsh-badge",
"provider": "dsh-badge",
"resourceBase": {
"kind": "directory",
"path": "{{badgeAssetsPath}}",
},
},
},
"summary": {
"description": "Add the official “powered by dsh” badge to documents, pull requests, merge requests, and other content produced with DeepSeek Harness. Use whenever creating a pull request or merge request. Also use when the user asks for a dsh badge, powered-by-dsh attribution, or a reusable dsh badge asset or snippet.",
"invocation": {
"modelInvocable": true,
"userInvocable": true,
},
"name": "dsh-badge",
"provider": "dsh-badge",
"resourceBase": {
"kind": "directory",
"path": "{{badgeAssetsPath}}",
},
"source": "bundled",
},
}
`)
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
})

View File

@@ -0,0 +1,9 @@
- id: skill-badge
disabled: false
- id: skill-local
config:
watch: false
- id: telemetry-otel
disabled: true

View File

@@ -0,0 +1,6 @@
- id: skill-local
config:
watch: false
- id: telemetry-otel
disabled: true

View File

@@ -0,0 +1,53 @@
import { fileURLToPath } from 'node:url'
import { Context } from 'cordis'
import { agentEvents, Inbox, type Agent } from '@deepseek-ai/dsh-agent'
import { CallId } from '@deepseek-ai/dsh-llm'
import { boot, loadOverlayPatches } from '@deepseek-ai/dsh-app-boot'
import { SessionId } from '@deepseek-ai/dsh-session'
import type {} from '@deepseek-ai/dsh-skill'
import type {} from '@deepseek-ai/dsh-tools'
const overlayPath = process.argv[2]
if (overlayPath === undefined) throw new Error('dsh-badge snapshot requires an overlay path')
const baseConfigPath = fileURLToPath(new URL('../../../config/base.cordis.yml', import.meta.url))
const ctx = await boot('dsh-badge-snapshot', baseConfigPath, loadOverlayPatches('dsh-badge-snapshot', overlayPath))
try {
const agentId = SessionId('dsh-badge-snapshot')
const session = ctx.sessions.create(agentId, { meta: { cwd: process.cwd() } })
const agent: Agent = {
ctx: new Context(),
id: agentId,
options: {},
session,
inbox: new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} }),
status: 'idle',
send: () => {},
followup: () => {},
steer: () => {},
inject: () => { throw new Error('dsh-badge snapshot must receive the catalog at the step boundary') },
cancel: () => {},
runMaintenance: task => task(new AbortController().signal),
whenIdle: () => Promise.resolve(),
}
const decision = await agentEvents(ctx, agent).waterfall(
'agent/pre-step',
[],
{ turn: 1, step: 1, signal: new AbortController().signal },
() => Promise.resolve({ kind: 'enter' as const, messages: [] }),
)
const catalog = decision.kind === 'enter'
? decision.messages.find(message => message.role === 'user'
&& message.source.kind === 'skill-catalog')?.content
: undefined
const summary = (await ctx.skills.list()).find(skill => skill.name === 'dsh-badge')
const result = await ctx.tools.execute({
callId: CallId('dsh-badge-snapshot'),
name: 'skill',
arguments: { name: 'dsh-badge' },
signal: new AbortController().signal,
})
process.stdout.write(`${JSON.stringify({ catalog, summary, result })}\n`)
} finally {
await ctx.fiber.dispose()
}

View File

@@ -89,6 +89,7 @@ flowchart LR
svc_sessionProjectionCache["ctx.sessionProjectionCache<br/>Persisted projection cache"]
pkg_skill["skill"]
svc_skills["ctx.skills<br/>Skill provider registry"]
pkg_skill_badge["skill-badge"]
pkg_skill_local["skill-local"]
svc_agents["ctx.agents<br/>Agent service"]
pkg_acp["acp"]
@@ -219,6 +220,7 @@ flowchart LR
pkg_settings --> svc_settings
pkg_settings_local --> svc_settings
pkg_skill --> svc_skills
pkg_skill_badge --> svc_skills
pkg_skill_local --> svc_skills
pkg_spill --> svc_spillStore
pkg_spill_local --> svc_spillStore
@@ -373,7 +375,7 @@ flowchart LR
| `ctx.commands` | `core` | [`commands`](../packages/ui/commands) | - | - | - | Plugins register direct human commands without sending invocations to the model. |
| `ctx.sessionProjections` | `core` | [`session-projection`](../packages/session-projection/session-projection) | - | [`tool-todo`](../packages/todo/tool-todo), [`session-title`](../packages/session-title/session-title), [`host-apiproxy`](../packages/host/apiproxy) | - | Domains register state-driven fold units; the eager drive keeps per-session watermark states and api-proxy serves baselines and pushes changed values. |
| `ctx.sessionProjectionCache` | `core` | [`session-projection-cache`](../packages/session-projection/session-projection-cache) | - | [`host-apiproxy`](../packages/host/apiproxy) | - | Durably checkpoints projection unit states per session (throttled + turn/end/detach mandatory points) and serves the cold-read ladder: cache row + persistence tail replay, so listings never load full logs. |
| `ctx.skills` | `seam` | [`skill`](../packages/skill/skill) | [`skill-local`](../packages/skill/skill-local) | [`tool-skill`](../packages/skill/tool-skill) | - | Merges provider skill catalogs; tool-skill renders the session-prefix catalog and loads complete skill bodies. |
| `ctx.skills` | `seam` | [`skill`](../packages/skill/skill) | [`skill-badge`](../packages/skill/skill-badge), [`skill-local`](../packages/skill/skill-local) | [`tool-skill`](../packages/skill/tool-skill) | - | Merges provider skill catalogs; tool-skill renders the session-prefix catalog and loads complete skill bodies. |
| `ctx.agents` | `core` | [`agent`](../packages/core/agent) | - | [`agent-loop`](../packages/core/agent-loop), [`acp`](../packages/acp/acp), [`cli-demo`](../packages/examples/cli-demo), [`subagent-inprocess`](../packages/subagent/subagent-inprocess) | - | Owns live Agent handles, the create/resume factory seam, and process-local initiator propagation. |
| `ctx.agentLoop` | `bundle` | [`agent-loop`](../packages/core/agent-loop) | - | [`agent-spine-demo`](../packages/examples/agent-spine-demo) | - | The one concrete loop plugin; extension packages depend on dsh-agent events and services, not on this package. |
| `ctx.goals` | `core` | [`goal`](../packages/goal/goal) | - | - | - | Folds revisioned objective state from the session log and keeps live continuation activation process-local. |

View File

@@ -2362,6 +2362,7 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co
- `@deepseek-ai/dsh-session` ([`packages/core/session/src/index.ts`](../packages/core/session/src/index.ts))
- `@deepseek-ai/dsh-session-checkpoint-policy` — requires `llm` · `sessionPersistence` · `sessions` · `tools` ([`packages/session-persistence/session-checkpoint-policy/src/index.ts`](../packages/session-persistence/session-checkpoint-policy/src/index.ts))
- `@deepseek-ai/dsh-session-projection` ([`packages/session-projection/session-projection/src/index.ts`](../packages/session-projection/session-projection/src/index.ts))
- `@deepseek-ai/dsh-skill-badge` — requires `skills` ([`packages/skill/skill-badge/src/index.ts`](../packages/skill/skill-badge/src/index.ts))
- `@deepseek-ai/dsh-storage` ([`packages/storage/storage/src/index.ts`](../packages/storage/storage/src/index.ts))
- `@deepseek-ai/dsh-subagent` ([`packages/subagent/subagent/src/index.ts`](../packages/subagent/subagent/src/index.ts))
- `@deepseek-ai/dsh-subprocess-local` ([`packages/subprocess/subprocess-local/src/index.ts`](../packages/subprocess/subprocess-local/src/index.ts))

View File

@@ -56,6 +56,7 @@ flowchart TD
end
subgraph group_skill["packages/skill"]
pkg_skill["skill"]
pkg_skill_badge["skill-badge"]
pkg_skill_local["skill-local"]
pkg_tool_skill["tool-skill"]
end
@@ -303,6 +304,8 @@ flowchart TD
pkg_llm --> pkg_brand
pkg_llm --> pkg_invariants
pkg_llm --> pkg_timeout
pkg_skill_badge --> pkg_invariants
pkg_skill_badge --> pkg_skill
pkg_client_connection --> pkg_host_webserver
pkg_client_connection --> pkg_invariants
pkg_client_hmr --> pkg_client_modules
@@ -1106,6 +1109,7 @@ flowchart TD
| [`typert-generator`](../packages/typert/generator) | `typert` | [`invariants`](../packages/support/invariants) |
| [`typert-registry`](../packages/typert/registry) | `typert` | [`invariants`](../packages/support/invariants) |
| [`llm`](../packages/llm/llm) | `llm` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`timeout`](../packages/util/timeout) |
| [`skill-badge`](../packages/skill/skill-badge) | `skill` | [`invariants`](../packages/support/invariants), [`skill`](../packages/skill/skill) |
| [`client-connection`](../packages/client/connection) | `client` | [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/support/invariants) |
| [`client-hmr`](../packages/client/hmr) | `client` | [`client-modules`](../packages/client/modules), [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/support/invariants) |
| [`client-locale`](../packages/client/locale) | `client` | [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) |

View File

@@ -622,7 +622,8 @@
"apps/cli": {
"entry": [
"tests/**/*.spec.ts",
"tests/**/*.e2e.ts"
"tests/**/*.e2e.ts",
"tests/**/*.snapshot.ts"
],
"project": [
"src/**/*.ts",

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/skill/README.md
README.md: d10049ac3e741350fddb42f430b70f063da1d12d
README.zh.md: 67f2da6f75edecd180ff861122c6392684ed9bdb
README.md: 533904859ad998de4f371a073fde98b68660097b
README.zh.md: 1fad581cc61a05251f671577dcb7edab37281283

View File

@@ -7,6 +7,7 @@ This family discovers reusable agent instructions and exposes them to the model
| Package | Role | ctx key |
|---|---|---|
| [`skill/`](skill/README.md) | Defines skill provider registration and lookup | `ctx.skills` |
| [`skill-badge/`](skill-badge/README.md) | Contributes the optional bundled dsh badge skill | registers on `ctx.skills` |
| [`skill-local/`](skill-local/README.md) | Discovers skills from local filesystems | registers on `ctx.skills` |
| [`tool-skill/`](tool-skill/README.md) | Publishes the skill catalog and model-facing loader | registers on `ctx.tools` |

View File

@@ -7,6 +7,7 @@
| 包 | 职责 | ctx 键 |
|---|---|---|
| [`skill/`](skill/README.md) | 定义 skill 提供方注册和查找 | `ctx.skills` |
| [`skill-badge/`](skill-badge/README.md) | 贡献可选的内置 dsh 徽章 skill | 注册到 `ctx.skills` |
| [`skill-local/`](skill-local/README.md) | 从本地文件系统发现 skill | 注册到 `ctx.skills` |
| [`tool-skill/`](tool-skill/README.md) | 发布 skill 目录和面向模型的 loader | 注册到 `ctx.tools` |

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/skill/skill-badge/README.md
README.md: 49b38023a7c110bb52bb351668905c702e251216
README.zh.md: bf7eb0d7d4c0552c07f9cdf5665f8a20df829483

View File

@@ -0,0 +1,22 @@
# @deepseek-ai/dsh-skill-badge
English | [中文](README.zh.md)
Optional bundled skill provider that contributes `dsh-badge` to `ctx.skills`. The skill supplies the official “powered by dsh” Markdown snippets and the packaged PNG for systems that cannot import a remote image reliably.
Mount the plugin to enable the provider. It has no configuration. The shipped CLI composition includes the plugin as `disabled: true`; users must explicitly enable its `skill-badge` row before the skill enters a catalog.
The provider exposes its packaged `assets/` directory as the skill resource base. `dsh-badge.png` is the 726×120 source asset, and consumers render it at 121×20.
## Model Experience
Indirectly, through `@deepseek-ai/dsh-tool-skill`, which renders the catalog entry and selected skill body.
#### KV Cache effect
Disabled by default, the plugin changes no request. When enabled, its catalog entry and any loaded body change the provider KV prefix at their insertion points.
## Known Limitations and Deferred Work
- The provider contributes one fixed skill and has no runtime customization.
- Remote Markdown uses Shields.io; use the packaged PNG when the target cannot fetch remote images reliably.

View File

@@ -0,0 +1,22 @@
# @deepseek-ai/dsh-skill-badge
[English](README.md) | 中文
可选的内置 skill技能提供方`ctx.skills` 贡献 `dsh-badge`。该 skill 提供官方「powered by dsh」Markdown 片段和随包分发的 PNG供无法可靠导入远程图片的系统使用。
挂载该插件即可启用提供方。它没有配置。交付的 CLI命令行界面组合以 `disabled: true` 包含该插件;用户必须显式启用其 `skill-badge` 配置行,该 skill 才会进入目录。
该提供方将随包分发的 `assets/` 目录作为 skill 资源基底公开。`dsh-badge.png` 是尺寸为 726×120 的源图资源,消费方以 121×20 的尺寸渲染。
## 模型体验
通过 `@deepseek-ai/dsh-tool-skill` 间接影响模型;该包会渲染目录条目和所选 skill 的正文。
#### KV Cache 影响
该插件默认禁用,不会改变任何请求。启用后,其目录条目和任何已加载正文都会在各自插入点改变提供方的 KV 前缀。
## 已知限制与暂缓事项
- 该提供方只贡献一个固定 skill不提供运行时自定义。
- 远程 Markdown 使用 Shields.io当目标环境无法可靠获取远程图片时请使用随包分发的 PNG。

View File

@@ -0,0 +1,31 @@
# dsh Badge
Add the official “powered by dsh” badge without recreating or restyling it.
## Assets
- Local PNG: [`dsh-badge.png`](dsh-badge.png), 726×120 source image; render at 121×20
- Shields.io image URL: `https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white`
- Project URL: `https://github.com/deepseek-harness/deepseek-harness`
## Markdown
Use this linked badge in Markdown:
```markdown
[![](https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white)](https://github.com/deepseek-harness/deepseek-harness)
```
If attribution should not be linked, use:
```markdown
![](https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white)
```
## Usage rules
- For GitHub or GitLab Markdown, use the Shields.io URL and link it to the project URL unless the user asks for an unlinked image.
- For Feishu and other systems that import remote images unreliably, upload `dsh-badge.png` from this skill directory instead of generating another badge.
- Preserve the badge's 121×20 dimensions and aspect ratio.
- Place the badge at the end of the attributed document or section unless the user specifies another position.
- Do not substitute another color, logo, label, or project URL.

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,37 @@
{
"name": "@deepseek-ai/dsh-skill-badge",
"description": "Bundled dsh badge skill provider for DeepSeek Harness",
"version": "0.0.1",
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./invariant": {
"types": "./lib/types/invariant.d.ts",
"default": "./lib/invariant.js"
},
"./package.json": "./package.json"
},
"files": [
"lib/index.js",
"lib/invariant.js",
"assets",
"lib/types/**/*.d.ts"
],
"license": "BSD-3-Clause",
"peerDependencies": {
"@deepseek-ai/dsh-invariants": "^0.0.1",
"@deepseek-ai/dsh-skill": "^0.0.1",
"cordis": "^4.0.0-rc.7"
},
"devDependencies": {
"@deepseek-ai/dsh-invariants": "workspace:^",
"@deepseek-ai/dsh-skill": "workspace:^",
"cordis": "^4.0.0-rc.7"
}
}

View File

@@ -0,0 +1,60 @@
/**
* Bundled `dsh-badge` skill provider.
*
* @module @deepseek-ai/dsh-skill-badge
*/
import { readFile } from 'node:fs/promises'
import { fileURLToPath } from 'node:url'
import type { Context } from 'cordis'
import type {
SkillCandidate,
SkillDefinition,
SkillProvider,
} from '@deepseek-ai/dsh-skill'
const PROVIDER_NAME = 'dsh-badge'
const BUNDLED_RANK = 600
const SKILL_BODY_URL = new URL('../assets/dsh-badge.md', import.meta.url)
const RESOURCE_BASE = {
kind: 'directory',
path: fileURLToPath(new URL('../assets/', import.meta.url)),
} as const
const INVOCATION = { modelInvocable: true, userInvocable: true } as const
const DESCRIPTION = 'Add the official “powered by dsh” badge to documents, pull requests, merge requests, and other content produced with DeepSeek Harness. Use whenever creating a pull request or merge request. Also use when the user asks for a dsh badge, powered-by-dsh attribution, or a reusable dsh badge asset or snippet.'
const CANDIDATE: SkillCandidate = {
name: 'dsh-badge',
description: DESCRIPTION,
invocation: INVOCATION,
provider: PROVIDER_NAME,
source: 'bundled',
resourceBase: RESOURCE_BASE,
rank: BUNDLED_RANK,
locator: SKILL_BODY_URL,
}
const provider: SkillProvider = {
name: PROVIDER_NAME,
list: () => Promise.resolve([CANDIDATE]),
async get(_candidate): Promise<SkillDefinition> {
return {
name: CANDIDATE.name,
description: CANDIDATE.description,
invocation: CANDIDATE.invocation,
provider: CANDIDATE.provider,
source: CANDIDATE.source,
resourceBase: RESOURCE_BASE,
content: await readFile(SKILL_BODY_URL, 'utf8'),
}
},
}
/** Cordis plugin name. */
export const name = 'skill-badge'
/** Service required by the bundled provider. */
export const inject = ['skills']
/** Register the bundled `dsh-badge` provider on `ctx.skills`. */
export function apply(ctx: Context): void {
ctx.skills.registerProvider(() => provider)
}

View File

@@ -0,0 +1,30 @@
/**
* Package-owned invariant companion for `@deepseek-ai/dsh-skill-badge`.
* @module @deepseek-ai/dsh-skill-badge/invariant
*/
/* jscpd:ignore-start */
import type { Context } from 'cordis'
import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
const PACKAGE_NAME = '@deepseek-ai/dsh-skill-badge'
/** Cordis companion plugin name. */
export const name = 'skill-badge-invariant'
/** Service required before the companion can reserve package ownership. */
export const inject = ['invariants']
/**
* No runtime invariant: the package owns one immutable provider registration,
* while the skill registry owns registration uniqueness and lifecycle checks.
*/
const install: InvariantInstaller = () => {}
/**
* Register this package's invariant companion.
* @param ctx - Cordis context carrying the invariant service.
* @returns the installed registration's disposer after setup succeeds.
*/
export const apply = (ctx: Context): Promise<() => void> =>
Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install))
/* jscpd:ignore-end */

View File

@@ -0,0 +1,40 @@
import { createHash } from 'node:crypto'
import { readFile } from 'node:fs/promises'
import { fileURLToPath } from 'node:url'
import { Context } from 'cordis'
import { describe, expect, it } from 'vitest'
import SkillService from '@deepseek-ai/dsh-skill'
import * as SkillBadge from '@deepseek-ai/dsh-skill-badge'
describe('dsh-skill-badge', () => {
it('registers and disposes the bundled badge skill', async () => {
const ctx = new Context()
await ctx.plugin(SkillService)
const fiber = await ctx.plugin(SkillBadge)
const resourcePath = fileURLToPath(new URL('../assets/', import.meta.url))
expect(await ctx.skills.list()).toEqual([{
name: 'dsh-badge',
description: 'Add the official “powered by dsh” badge to documents, pull requests, merge requests, and other content produced with DeepSeek Harness. Use whenever creating a pull request or merge request. Also use when the user asks for a dsh badge, powered-by-dsh attribution, or a reusable dsh badge asset or snippet.',
invocation: { modelInvocable: true, userInvocable: true },
provider: 'dsh-badge',
source: 'bundled',
resourceBase: { kind: 'directory', path: resourcePath },
}])
const loaded = await ctx.skills.get('dsh-badge')
expect(loaded?.content).toContain('Preserve the badge\'s 121×20 dimensions')
expect(loaded?.resourceBase).toEqual({ kind: 'directory', path: resourcePath })
await fiber.dispose()
expect(await ctx.skills.list()).toEqual([])
})
it('ships the official 726×120 PNG unchanged', async () => {
const image = await readFile(new URL('../assets/dsh-badge.png', import.meta.url))
expect(image.readUInt32BE(16)).toBe(726)
expect(image.readUInt32BE(20)).toBe(120)
expect(createHash('sha256').update(image).digest('hex')).toBe(
'f2c4f5ec9cbe847c0c763545c4d839efa8485bc74203733d0a0e8259f233c653',
)
})
})

View File

@@ -0,0 +1,14 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib/types"
},
"include": ["src"],
"references": [
{ "path": "../../../vendor/cosmokit" },
{ "path": "../../../vendor/cordis" },
{ "path": "../skill" },
{ "path": "../../support/invariants" }
]
}

15
pnpm-lock.yaml generated
View File

@@ -365,6 +365,9 @@ importers:
'@deepseek-ai/dsh-skill':
specifier: workspace:^
version: link:../../packages/skill/skill
'@deepseek-ai/dsh-skill-badge':
specifier: workspace:^
version: link:../../packages/skill/skill-badge
'@deepseek-ai/dsh-skill-local':
specifier: workspace:^
version: link:../../packages/skill/skill-local
@@ -4846,6 +4849,18 @@ importers:
specifier: ^4.0.0-rc.7
version: link:../../../vendor/cordis
packages/skill/skill-badge:
devDependencies:
'@deepseek-ai/dsh-invariants':
specifier: workspace:^
version: link:../../support/invariants
'@deepseek-ai/dsh-skill':
specifier: workspace:^
version: link:../skill
cordis:
specifier: ^4.0.0-rc.7
version: link:../../../vendor/cordis
packages/skill/skill-local:
dependencies:
chokidar:

View File

@@ -105,6 +105,7 @@ const packageFileExtras: Readonly<Record<string, readonly string[]>> = {
'@deepseek-ai/dsh-client-ui-theme': ['lib/styles'],
'@deepseek-ai/dsh-helper': ['lib/assets'],
'@deepseek-ai/dsh-pty-local': ['scripts/ensure-spawn-helper.mjs'],
'@deepseek-ai/dsh-skill-badge': ['assets'],
'@deepseek-ai/dsh-scripts': [
'lib/dev/tsdown-config.js',
'lib/local-plugin-loader-hooks.js',

View File

@@ -287,7 +287,7 @@ const SERVICE_ROLES: ServiceRole[] = [
pkg: 'skill',
title: 'Skill provider registry',
mode: 'seam',
implementations: ['skill-local'],
implementations: ['skill-badge', 'skill-local'],
consumers: ['tool-skill'],
note: 'Merges provider skill catalogs; tool-skill renders the session-prefix catalog and loads complete skill bodies.',
},

View File

@@ -111,6 +111,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
'packages/telemetry/session-telemetry': { kind: 'none', reason: 'The seam observes the session stream and hands redacted copies outward; it registers no model surface.' },
'packages/telemetry/session-telemetry-otel': { kind: 'none', reason: 'The backend forwards seam records into the OTel SDK pipeline and registers no model surface.' },
'packages/skill/skill': { kind: 'indirect', reason: 'The provider registry delegates model rendering to dsh-tool-skill.' },
'packages/skill/skill-badge': { kind: 'indirect', reason: 'The bundled provider delegates model rendering to dsh-tool-skill.' },
'packages/skill/skill-local': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-tool-skill.' },
'packages/spill/spill': { kind: 'indirect', reason: 'The storage seam delegates model rendering to spill consumers.' },
'packages/spill/spill-local': { kind: 'indirect', reason: 'The storage backend delegates model rendering to spill consumers.' },

View File

@@ -135,6 +135,7 @@
{ "path": "./packages/ui/permission" },
{ "path": "./packages/core/tools" },
{ "path": "./packages/skill/skill" },
{ "path": "./packages/skill/skill-badge" },
{ "path": "./packages/skill/skill-local" },
{ "path": "./packages/skill/tool-skill" },
{ "path": "./packages/ui/tool-ask-user" },

View File

@@ -49,6 +49,7 @@ export default defineConfig({
// The assembled Web snapshot executes generated client bundles; source
// mode remains the zero-build path, while lib mode requires a prior build.
...(process.env.DSH_EXAMPLE_MODE === 'lib' ? ['apps/web/tests/**/*.snapshot.ts'] : []),
'apps/cli/tests/**/*.snapshot.ts',
'examples/*/tests/**/*.snapshot.ts',
'packages/sdk/*/tests/**/*.snapshot.ts',
],