Back to skills
extension
Category: Development & EngineeringNo API key required

dsh-plugin-dev

高精度 DeepSeek Harness (dsh) 插件开发技能。 包含完整的 API 契约、类型签名、配置 Schema 写法及事件处理范式。 AI 应严格遵循此 Skills 中的代码模板与约束,禁止臆造 API。

personAuthor: awol2005exhubModelScope

DeepSeek Harness Plugin Development Skill

You are an expert plugin developer for the DeepSeek Harness (dsh) ecosystem. When asked to create, modify, or debug a dsh plugin, you MUST follow the specifications below precisely. Never invent APIs, types, or patterns not defined here.

Core Principles

  • Everything in dsh is a plugin: adapters, tools, loggers, and the agent loop itself.
  • Plugins interact exclusively through Context (ctx). Never import or call other plugins directly.
  • All registrations MUST be reversible. Use ctx.effect(), ctx.on(), or ctx.plugin() so cleanup happens automatically on HMR/unload.
  • Load order is determined by inject dependencies, not file order.
  • Function plugins MUST use named exports. Never use export default.

Plugin Structure Contract

Every plugin module must export exactly these identifiers:

| Export | Required | Type | Purpose | |--------|----------|------|---------| | name | Yes | string | Unique plugin identifier, kebab-case | | apply | Yes | (ctx: Context, config?: Config) => void \| Promise<void> | Plugin entry point | | inject | No | readonly string[] | Required service dependencies | | Config | No | z.ZodType<Config> | Declarative config schema using @deepseek-ai/schemastery |

Correct Plugin Skeleton

import type { Context } from '@deepseek-ai/cordis'
import z from '@deepseek-ai/schemastery'

export const name = 'my-plugin'
export const inject = ['tools'] as const

export interface Config {
  apiKey: string
  timeout?: number
}
export const Config: z<Config> = z.object({
  apiKey: z.string().required(),
  timeout: z.number().default(30000),
})

export function apply(ctx: Context, config: Config) {
  // Plugin logic here
}

Tool Development Contract

Import defineTool from @deepseek-ai/dsh-tools. The execute function receives pre-validated args and an execution context.

Key Rules

  1. Args are already validated. Do not re-validate inside execute.
  2. Return only what output.schema defines. Returning a raw string or mismatched object will fail validation.
  3. Respect cancellation. Check exec.signal?.aborted at the start and between async steps.
  4. Errors = isError. Throw Error for failures; do not return error objects.
  5. Never console.log. Use ctx.logger.info/warn/error.

Tool Template

import { defineTool } from '@deepseek-ai/dsh-tools'

ctx.tools.register(defineTool({
  name: 'search-docs',
  description: 'Search internal documentation by keyword',
  args: z.object({ query: z.string().required() }),
  output: {
    schema: z.object({ results: z.array(z.string()) }),
    render: (v) => `Found ${v.results.length} results`,
  },
  async execute(args, exec) {
    if (exec.signal?.aborted) throw new Error('Cancelled')
    const results = await searchInternal(args.query)
    return { results }
  },
}))

Event Hook Patterns

Hooks have three distinct signatures. Using the wrong one breaks the pipeline.

Waterfall Hooks (MUST call next())

Used for interception, permission checks, and request modification. You must either return a short-circuit value OR call return next().

// Permission guard example
ctx.on('tools/pre-execute', async (exec, next) => {
  if (!hasPermission(exec.tool.name)) {
    return { kind: 'deny', reason: 'Insufficient permissions' }
  }
  return next() // ← CRITICAL: forgetting this hangs the pipeline
})

Common waterfall events: tools/pre-execute, agent/request, tools/execute.

Serial Hooks (No next())

Used for side effects that don't modify flow.

ctx.on('agent/turn-stopping', async ({ reason }) => {
  ctx.logger.info(`Turn stopping: ${reason}`)
})

Emit Hooks (Read-only observation)

Used for logging, metrics, and UI updates. Synchronous or async, no next().

ctx.on('tools/result', ({ tool, result, duration }) => {
  metrics.record(tool, duration)
})

Anti-Patterns (Auto-Correct These)

When generating or reviewing code, actively detect and fix these issues:

| ❌ Wrong | ✅ Correct | Why | |----------|-----------|-----| | export default function apply | export function apply | Default export loses inject metadata | | Bare setInterval(...) | Wrap in ctx.effect(() => { const t = setInterval(...); return () => clearInterval(t) }) | Leaks on HMR/unload | | Return string from execute | Return object matching output.schema | Schema validation will reject primitives | | Waterfall hook without next() | Always return next() or explicit short-circuit | Pipeline hangs indefinitely | | Optional service in inject | Use ctx.get('name') at runtime | Missing optional dep blocks plugin load | | Deep merge assumption in patch | Patch replaces entire config by id | Partial patches silently drop fields |

Configuration & Patching

  • Config schemas use @deepseek-ai/schemastery (Zod-compatible).
  • Patches replace the entire config object for a given plugin id. They do NOT deep merge.
  • !!js expressions are ONLY allowed in plugin.config values and disabled fields.
  • Environment variables: !!js "process.env.MY_VAR"

Debugging Workflow (Independent Project)

When developing outside the main harness repo:

  1. Link plugin: dsh plugin --profile headless add .
  2. Verify config tree: dsh --profile headless --dump-config
  3. Test with task: dsh --profile headless "test my plugin"
  4. Apply temporary override: dsh --profile headless --patch ./overlay.yml "task"
  5. Type check: tsc --noEmit

Always confirm the plugin appears in --dump-config output before debugging runtime behavior.