Choose the shortest honest path.
Prove one module and its lifecycle before packaging.
Prove schema, execution, canonical value, and render.
Prove install discovery, layer order, and removal.
Start from official coordinates.
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
pnpm install
pnpm run build
Record git remote get-url origin and git rev-parse HEAD. Similar repository and package names do not prove provenance.
The smallest plugin is one function.
import type { Context } from '@deepseek-ai/cordis'
export const name = 'hello-plugin'
export function apply(ctx: Context) {
console.log('[hello-plugin] loaded')
}
All contributions begin inside the Cordis lifecycle.
An absolute path makes local resolution explicit.
The existing surface must still boot.
Runtime loading and TypeScript discovery are separate.
POSIX paths load directly. A Windows drive path must become a canonical file: URL.
Extend ../tsconfig.base.json to inherit official workspace aliases, then include only src.
pnpm exec tsc -p scratch-plugin/tsconfig.jsonKeep the scratch project outside the official Host aggregate. Never add include or files to the shared base config just to silence local diagnostics.
On Windows, convert the path—do not rewrite it by eye.
$pluginPath = Join-Path $PWD 'scratch-plugin\src\my-plugin.ts'
node -e "const {pathToFileURL}=require('node:url'); console.log(pathToFileURL(process.argv[1]).href)" $pluginPathF:\repo\plugin.tsValid path, invalid raw ESM scheme.
file:///F:/repo/plugin.tsCanonical escaping survives spaces and Unicode.
The terminal signal proves this boundary passed.
Current alpha source can also anchor ./src/my-plugin.ts to the overlay file. Use the generated file URL for rc.2 or uncertain mixed prerelease installations.
Make the Agent call it.
export const inject = ['tools']
export function apply(ctx: Context) {
ctx.tools.register(defineTool({
name: 'greet',
description: 'Greet someone by name.',
parameters: {
name: { type: 'string', required: true }
},
output: {
schema: { type: 'string' },
render: (_args, value) => [{ type: 'text', text: value }]
},
async execute(args) {
return `Hello, ${args.name}!`
}
}))
}
injectDeclare the hard service dependency.
parametersReject malformed model arguments.
executeReturn the canonical business value.
output.renderConvert the value to model-visible content.
A bundle is not a profile.
An npm package whose dsh.bundle.patch points to a configuration layer.
An ordered composition under $DSH_HOME/profiles/<name>, maintained by the CLI.
The four-command acceptance loop.
- Install
dsh plugin --profile plugin-lab add ./hello-plugin - Inspect
dsh --profile plugin-lab --dump-config - Boot
dsh --profile plugin-lab - Remove
dsh plugin --profile plugin-lab remove dsh-hello-plugin
pnpm 10 requires an explicit allowance before a Git dependency may run its prepare build. Pin the commit and audit the script; it executes on the Host, not in the Agent sandbox.
Route the first failure.
Check the absolute development path or packaged entry point.
Inspect the installed manifest's dsh.bundle.patch.
Add a true hard dependency to inject, or use guarded optional access.
Verify the active composition, registry injection, and unique tool name.
Primary evidence.
- Windows first-plugin report #4814
- Official first-plugin tutorial
- alpha.1 overlay path anchoring
- Windows file-URL regression test
- Official TypeScript project layout
Keep the complete lab.
The canonical guide includes configuration schemas, bundle files, layer precedence, distribution choices, an acceptance checklist, and the full failure router.
Read the complete guide on GitHub