green-dalii/dsh-plugin-dev-skill

Un pack de compétences qui permet à n'importe quel agent de développer des plugins DeepSeek Harness (DSH) correctement, efficacement et conformément aux conventions officielles.

Qu'est-ce que dsh-plugin-dev-skill ?

Ce skill est un guide opérationnel complet pour le développement de plugins DeepSeek Harness (DSH). Il fournit un modèle mental basé sur le framework Cordis, des règles strictes (comme l'utilisation obligatoire du contexte `ctx` pour toutes les inscriptions et la déclaration des dépendances via `inject`), ainsi que des templates de code prêts à l'emploi pour créer des outils, des adaptateurs LLM, des services et bien plus. Il couvre l'ensemble du cycle de vie : de la création d'un plugin minimal à sa configuration via Schemastery, en passant par la gestion des événements, le développement d'adaptateurs LLM, les stratégies de déploiement et le packaging. Destiné aux développeurs utilisant les agents DSH, ce skill garantit que vos plugins sont robustes, réutilisables et conformes aux conventions, évitant les erreurs courantes comme les nettoyages manuels ou les dépendances non déclarées. Il supporte les environnements de développement avec code source ou CLI installé.

Compatible avec✓Claude Code✓Codex CLI~Cursor
npx skills add green-dalii/dsh-plugin-dev-skill

Installed? Explore more Productivité et collaboration skills: steipete/gemini, steipete/gh-issues, steipete/skill-creator · View all 6 →

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Compétence de Développement de Plugin DeepSeek Harness

Permet à n'importe quel agent de développer correctement, efficacement et conformément aux normes des plugins DeepSeek Harness (DSH). Ce skill est le manuel d'exploitation du développement de plugins DSH : il fournit d'abord le modèle mental et les règles absolues, puis les templates de code prêts à copier et le processus étape par étape, et enfin la liste de vérification. Pour des informations de fond détaillées, consultez les documents synthétiques dans le répertoire References/.


0. Quand utiliser ce skill

Ce skill doit être chargé lorsque la tâche implique l'un des éléments suivants :

  • Écrire, modifier ou déboguer un plugin (outil, adaptateur LLM, fournisseur de service, hook, interface utilisateur, pont de protocole, etc.) pour DeepSeek Harness.
  • Écrire ou modifier les fichiers de configuration cordis.yml / cordis.patch.yml / dsh.profile / dsh.bundle.
  • Utiliser dsh plugin ... pour installer, supprimer ou empaqueter des plugins.
  • Comprendre les services DSH comme ctx.tools, ctx.llm, ctx.agents, ctx.sessions ou les événements comme tools/*, agent/*, session/event.
  • Étudier le fonctionnement du modèle de plugin DSH (framework Cordis).

Lisez d'abord l'intégralité de ce SKILL.md, puis passez à l'action. Avant de commencer, lisez les fichiers cordis.yml / profile / le code source existant dans le projet pour confirmer les conventions en vigueur.


1. Modèle mental : DSH est un système plugin construit sur Cordis

En une phrase : DeepSeek Harness est un SDK Agent Harness où chaque capacité – outil, adaptateur LLM, accès aux fichiers, boucle d'agent elle-même – est un plugin (composant) monté sur un contexte partagé (context).

Trois concepts clés (tirés du document « A Programming Paradigm for Spatiotemporal Composability », voir References/10-spatiotemporal.md) :

ConceptSignificationDans le code
Effet réversible (revertible effect)Chaque modification de l'environnement partagé par un composant porte une opération inverse ; l'exécution la suit et la restaure dans l'ordre LIFO lorsque le composant est déchargé.ctx.effect(() => () => cleanup) ; ctx.on(), ctx.tools.register(), etc. sont des effets, annulés automatiquement au déchargement.
Co-effet réactif (reactive coeffect)Le composant déclare les services dont il dépend ; lorsque les services apparaissent/disparaissent/changent d'implémentation, le composant s'active/se désactive/se recharge automatiquement.export const inject = ['tools'] ; lorsque la dépendance disparaît, le plugin est automatiquement disposé, et se recharge automatiquement lorsqu'elle revient.
Fiber (instance de composant)Chaque instance de plugin chargée est une fibre avec une machine à états de cycle de vie.PENDING → LOADING → ACTIVE → UNLOADING → DISPOSED (si apply échoue, FAILED).

Les règles absolues qui en découlent :

  1. Tout enregistrement doit se faire via ctx (ctx.on / ctx.tools.register / ctx.effect / ctx.plugin / ctx.provide). Tout enregistrement passant par ctx est automatiquement nettoyé lors du déchargement, ne jamais utiliser manuellement removeListener / clearInterval.
  2. Toute ressource non gérée par ctx (connexion réseau, minuteur personnalisé, descripteur de fichier, watcher) doit être encapsulée dans ctx.effect(() => { ...; return () => cleanup }).
  3. Les dépendances doivent être déclarées (inject). N'utilisez pas ctx.get() pour sonder les dépendances nécessaires, ne présumez pas de l'ordre de chargement – l'ordre est déterminé par les dépendances, pas par l'ordre des lignes dans cordis.yml.
  4. La configuration doit être configurable : tout paramètre pouvant avoir une valeur différente selon les déploiements doit être un champ Config. Aucune valeur en dur (critère : peut-on modifier cette valeur dans cordis.yml sans modifier le code ?).
  5. Les erreurs de configuration doivent être explicites : utilisez un schéma Schemastery pour valider au chargement ; une configuration invalide doit faire échouer le chargement avec un message clair, ne jamais fonctionner avec des erreurs.
  6. Les callbacks doivent être rejouables/chargés à chaud : les callbacks d'affichage (output.render, presentCall, presentResult) doivent être des fonctions pures ; le plugin peut être déchargé/rechargé à tout moment par HMR, ne pas dépendre d'états globaux persistants entre les rechargements.

2. Environnement et chaîne d'outils

  • Les plugins sont des modules TypeScript (JS également accepté), exécutés dans un environnement Node.js ESM.
  • Paquets de dépendances essentiels (tous dans le scope @deepseek-ai) :
    • @deepseek-ai/cordis — Framework de plugins (contexte, fibre, événements, registre).
    • @deepseek-ai/schemastery — Validation de schéma de configuration (implémentation Standard Schema).
    • @deepseek-ai/dsh-tools — Service de registre defineTool et d'outils.
    • @deepseek-ai/dsh-llm — LlmAdapter, StreamChunk, GenerateOptions, CallId.
    • Paquets de couches de capacité : dsh-shell, dsh-session, dsh-agent, dsh-system-prompt, dsh-fs, dsh-jobs, dsh-credentials, etc. (liste complète dans References/08-capability-layering.md).
  • Deux points d'entrée de développement (au choix) :
    • Extraction des sources (recommandé, pour didacticiel) : clonez deepseek-ai/deepseek-harness, pnpm install, lancez l'interface Web avec pnpm dsh web --patch <votre_cordis.yml>, exécutez des exemples de didacticiel Cordis purs avec node --import tsx ../../vendor/cordis/bin.js.
    • CLI installé : dsh --profile <nom> ... pour lancer un profil, dsh plugin --profile <nom> <args pnpm> pour gérer les plugins.

3. Forme minimale d'un plugin (apprenez d'abord cela)

Un plugin est un module qui exporte une fonction apply :

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

export const name = 'my-plugin'   // Optionnel, uniquement pour l'affichage de diagnostic

export function apply(ctx: Context) {
  // Enregistrez les capacités ici
}

Monté via cordis.yml ou un patch :

- name: './src/my-plugin.ts'        # Chemin relatif (source/développement) ou nom de paquet npm (après installation)
  id: my-plugin                     # Identifiant stable, le chargeur utilise l'id pour la concordance de configuration

Trois formes de plugins

  1. Forme fonction (par défaut recommandée) :
    export const name = 'greet-tool'
    export const inject = ['tools']
    export function apply(ctx: Context) { /* ... */ }
    
  2. Forme objet : export default { name, inject, apply(ctx) {} }
  3. Forme classe (lorsqu'il faut fournir un service) : export default class MyService extends Service { constructor(ctx) { super(ctx, 'myService') } }

Utilisez toujours la forme fonction tant que vous n'avez pas besoin de fournir un service.


4. Développer un outil (la tâche la plus fréquente)

Les outils sont enregistrés dans ctx.tools, définis avec defineTool. Template (à copier directement) :

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

export const name = 'my-tool'
export const inject = ['tools']

export function apply(ctx: Context) {
  ctx.tools.register(defineTool({
    name: 'my_tool',                                  // Nom de l'outil vu par le modèle (convention snake_case)
    description: 'One sentence about what it does.',  // Description fonctionnelle vue par le modèle, spécifique, indiquant quand l'utiliser
    parameters: {
      path: { type: 'string', required: true, description: 'What the param means' },
      limit: { type: 'number' },                      // Non marqué required signifie optionnel
    },
    output: {
      schema: { type: 'string' },                     // Déclare la valeur JSON normalisée retournée par execute
      render: (_args, value) => [{ type: 'text', text: value }],  // Convertit en contenu visible par le modèle
    },
    async execute(args, exec) {
      // args a été validé par le schéma et le type est inféré
      // exec.signal est le signal d'annulation, les tâches longues doivent y répondre
      return `result for ${args.path}`
    },
  }))
}

Contrat complet de defineTool (à respecter impérativement)

  • parameters : Mapping de schéma des paramètres, la racine est un objet implicite ouvert ; chaque propriété peut avoir required: true. Prend en charge string/number/integer/boolean/null/array/object/oneOf ; les nœuds objet explicites doivent déclarer additionalProperties: true | false.
  • output.schema : Déclare la valeur JSON normalisée retournée par execute (objet/tableau/scalaire/null accepté). C'est l'API pour l'appelant programmatique (await tools.xxx() en Mode Code), conçue pour retourner directement un handle et des champs ; l'explication orientée humain est dans output.render.
  • execute(args, exec) :
    • args a été validé à l'exécution, mais les contraintes non exprimables dans le DSL de schéma (chaîne non vide, nombre positif, inter-champs) doivent être vérifiées manuellement avec throw new Error(...).
    • Retourne la valeur normalisée déclarée par output.schema. Ne retournez pas de blocs de contenu, ne forcez pas l'appelant à analyser un identifiant à partir de langage naturel.
    • Respectez exec.signal : annulez le travail en cours lorsque le signal est déclenché.
    • Défaillance d'infrastructure (exception/valeur de retour invalide) → le résultat est marqué comme isError. Succès mais résultat insatisfaisant (ex. code de sortie non nul) → retournez toujours la valeur normalisée, expliquez dans le render.
  • output.render(args, value) : Fonction pure, convertit la valeur normalisée en ContentBlock[] (texte visible par le modèle). Ne faites jamais d'E/S, de lecture d'état de session ici.
  • Optionnel output.presentationMeta(args, value) : Dérive des métadonnées d'interface utilisateur rejouables à partir de la valeur normalisée (fonction pure).
  • Optionnel presentCall(args) / presentResult(args, result) : Déclare l'intention de rendu de carte de l'outil dans l'interface utilisateur (card: 'generic' | 'terminal' | 'diff' | 'search' | 'web' | 'read'). Doivent être des fonctions pures (exécutées dans le flux en direct et la relecture de session), ne faites pas d'E/S.
  • Ne mettez pas de métadonnées d'exécution comme timeoutMs, isConcurrencySafe, finalizeContent dans le schéma visible par le modèle – elles ne fuient pas vers le modèle.
  • Paramètres immuables : traitez args en lecture seule ; ne modifiez pas le schéma ni ne remplacez les callbacks après l'enregistrement.

Tâches longues (tâches en arrière-plan)

Pour prendre en charge run_in_background :

const jobs = ctx.get('jobs')
if (!jobs) throw new Error('background jobs unavailable: load @deepseek-ai/dsh-jobs')
return {
  kind: 'background',
  jobId: jobs.start({
    kind: 'my-work',
    label: args.command,
    ...exec.agent ? { owner: exec.agent } : {},
    run: () => { /* retourne { cancel, done, readOutput? } */ },
  }),
}

Points clés : après avoir publié le jobId, utilisez le propre signal d'annulation de la tâche (géré par job_kill / dispose du propriétaire), n'utilisez plus exec.signal ; le schéma de sortie réussi doit pouvoir transporter ce type de { kind: 'background', jobId } comme handle normalisé. Référez-vous à References/03-tools.md et à l'implémentation @deepseek-ai/dsh-tool-bash.

Stratégies d'exécution des outils (points d'extension, à utiliser selon les besoins)

Point d'extensionModeUtilisation
tools/pre-executewaterfallPermettre/refuser/demander une stratégie (contrôle d'accès) ; retourne {kind:'allow'} / {kind:'deny',reason} / {kind:'ask'}
ctx.tools.guard()Garde monotoneRefus final, les écouteurs suivants ne peuvent pas l'annuler (retourner reason signifie refus)
tools/executewaterfallEnvelopper la distribution : timeout/retry/metrics ; peut remplacer exec.signal mais ne peut pas le supprimer
tools/post-executewaterfallRemplacer le contenu/la valeur, empêcher le résultat, ajouter un contexte de modèle
tools/resultemitObservation en lecture seule du résultat final immuable (journal, audit)

N'intégrez pas la stratégie dans l'outil ; mettez la stratégie de déploiement dans un plugin hook (un simple plugin Cordis qui écoute ces événements, comme l'exemple de contrôle d'accès dans References/11-cookbook.md).


5. Configuration du plugin (Schemastery)

Le plugin exporte Config (interface TS du même nom + schéma Schemastery), Cordis valide et remplit les valeurs par défaut avant apply :

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

export interface Config {
  greeting: string
  maxRetries: number
  verbose?: boolean
}

export const Config: Schema<Config> = Schema.object({
  greeting: Schema.string().default('Hello'),
  maxRetries: Schema.number().default(3),
  verbose: Schema.boolean().default(false),
})

export function apply(ctx: Context, config: Config) {
  // config est toujours complet et validé
}

Transmettez la configuration dans cordis.yml :

- name: './src/my-plugin.ts'
  config:
    greeting: 'Hi there'
    maxRetries: 5

Points clés :

  • Config doit être un objet Standard Schema (construit avec Schemastery), ne peut pas exporter un objet ordinaire.
  • Constructeurs courants : Schema.string().required() / .default(x), Schema.number(), Schema.boolean(), Schema.array(String), Schema.union(['a','b']), Schema.object({...}) imbriqué.
  • Prend en charge l'expression !!js pour l'évaluation au chargement (ex. greeting: !!js process.env.GREETING ?? 'Hello') ; !!js est valide uniquement pour les champs config et disabled.
  • Les changements de configuration déclenchent HMR : ancienne instance déchargée (enregistrement automatiquement nettoyé) → nouvelle instance chargée. Ne mettez pas en cache la configuration en dehors du plugin.

6. Services et dépendances (développement côté serveur)

Utiliser un service (consommateur)

export const inject = ['tools']        // Dépendance requise : ne pas charger si non satisfaite
export function apply(ctx: Context) {
  ctx.tools.register(/* ... */)        // Garanti prêt lors de apply
}

Dépendance optionnelle : n'écrivez pas inject, utilisez ctx.get('service') pour sonder (retourne undefined si aucun fournisseur).

Fournir un service (fournisseur)

Utilisez une sous-classe de Service (plugin de forme classe) :

import { Service, type Context } from '@deepseek-ai/cordis'

declare module '@deepseek-ai/cordis' {
  interface Context {
    metrics: MetricsService     // Déclaration de fusion : donne un type à ctx.metrics
  }
}

export default class MetricsService extends Service {
  static inject = ['llm']       // Un service peut aussi dépendre d'autres services
  constructor(ctx: Context) {
    super(ctx, 'metrics')       // 'metrics' est le nom du service
  }
  record(event: string, value: number) { /* ... */ }
}

export const name = 'metrics'
export function apply(ctx: Context) {
  ctx.plugin(MetricsService)
}

API de bas niveau (peu courant mais à connaître) : ctx.provide(name, value) enregistre une implémentation de service (effet, se désenregistre automatiquement au déchargement), ctx.set/get lit/écrit le stockage, ctx.accessor définit des propriétés calculées, ctx.mixin attache les membres du service à ctx.

Sémantique de comportement des services (comprendre plutôt que mémoriser)

  • inject n'est pas une vérification de démarrage unique : si un service requis disparaît pendant l'exécution → le plugin dépendant est automatiquement disposé ; si le service réapparaît → rechargement automatique. Cela garantit que le consommateur ne détient jamais de référence à un service déchargé.
  • Les noms de service sont un espace de noms global plat : tools, llm, etc. sont déjà pris ; les noms de service personnalisés doivent avoir un préfixe identifiable (ex. myCap, metrics).
  • Isolation des services : dans cordis.yml, utilisez group + isolate pour que différents groupes de plugins voient différentes instances d'un même nom de service (par exemple, deux groupes avec des fournisseurs shell configurés avec des timeouts différents).

7. Système d'événements (communication entre plugins)

Modes de distribution (partie du nom de l'événement, à choisir correctement)

ModeAppelSémantique
emitctx.emit(name, ...args)Diffusion synchrone, n'attend pas, ne collecte pas les valeurs de retour
parallelawait ctx.parallel(...)Tous les écouteurs s'exécutent en concurrence et sont attendus
serialawait ctx.serial(...)Exécution séquentielle, le premier retour non null/false/undefined gagne et arrête
bailctx.bail(...)Version synchrone de serial
waterfallawait ctx.waterfall(name, ...args, next)Intergiciel enveloppant ; les écouteurs reçoivent next, enveloppent la valeur de retour en aval

Règle absolue du waterfall : un écouteur qui ne fait qu'observer/annoter doit appeler next() ; ne pas appeler next() et retourner directement = court-circuit intentionnel (refus/interception). Oublier d'appeler next() engloutit silencieusement le comportement par défaut en aval.

Événements typés

declare module '@deepseek-ai/cordis' {
  interface Events {
    'my-plugin/ready': (payload: { id: string }) => void
    'my-plugin/check': (input: string) => boolean | undefined
  }
}

Convention de nommage des événements namespace/action (ex. agent/step, tools/result, session/event). Les écouteurs enregistrés avec ctx.on() sont automatiquement nettoyés lors du déchargement du plugin.

Événements Harness courants (souvent utilisés en développement)

  • tools/pre-execute / tools/execute / tools/post-execute / tools/result — Pipeline d'outils (voir le tableau ci-dessus).
  • agent/* (agent/request, agent/pre-step, agent/session-start, agent/turn-stopping, etc.) — Cycle de vie de l'agent et construction de requête.
  • session/event — Flux d'événements de session persistants (turn/*, step/*, tool/call, tool/result, assistant/chunk sont tous des event.type ici, pas des événements Cordis du même nom).
  • approval/request — Waterfall d'approbation.
  • system-prompt/assemble — Assemblage complet du prompt système (retour faisant autorité, les écouteurs ont la responsabilité de préserver les contributions existantes).

Les signatures complètes/modes de déclenchement se trouvent dans les blocs cordis-surface générés par chaque sous-système (voir References/08-capability-layering.md pour les indications).


8. Adaptateur LLM (intégrer un nouveau fournisseur de modèle)

Un adaptateur = une classe qui hérite de LlmAdapter et implémente stream(), convertissant les requêtes indépendantes du fournisseur Harness en appels API spécifiques et les retransformant en fragments StreamChunk.

import type { Context } from '@deepseek-ai/cordis'
import Schema from '@deepseek-ai/schemastery'
import { LlmAdapter, type GenerateOptions, type StreamChunk } from '@deepseek-ai/dsh-llm'

class MyAdapter extends LlmAdapter {
  async *stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
    // 1. options.messages → format du fournisseur
    // 2. Appel d'API en flux (doit passer options.signal)
    // 3. Réponse → séquence StreamChunk
  }
}

export const name = 'my-llm-adapter'
export const inject = ['llm']
export const Config = Schema.object({
  apiKey: Schema.string().required(),
  providers: Schema.array(String).required(),
})

export function apply(ctx: Context, config: Config) {
  ctx.llm.registerAdapter(config.providers, new MyAdapter(config.apiKey))
}

Protocole StreamChunk (à suivre rigoureusement)

async function* chunks(): AsyncIterable<StreamChunk> {
  yield { type: 'block-start', index: 0, blockType: 'text' }
  yield { type: 'text-delta', index: 0, text: 'Hello' }
  yield { type: 'block-end', index: 0, block: { type: 'text', text: 'Hello world' } }
  // Bloc d'appel d'outil :
  yield { type: 'block-start', index: 1, blockType: 'tool-call' }
  yield { type: 'tool-call-delta', index: 1, id: CallId('c1'), name: 'bash', argumentsDelta: '{"command":"ls"}' }
  yield { type: 'block-end', index: 1, block: { type: 'tool-call', id: CallId('c1'), name: 'bash', arguments: '{"command":"ls"}' } }
  yield { type: 'usage', usage: { inputTokens: 100, outputTokens: 50 } }   // Doit être avant finish
  yield { type: 'finish', reason: { kind: 'stop' } }                        // Doit être le dernier
}

Règles : chaque block-start doit avoir un block-end correspondant ; index s'incrémente à partir de 0 ; les arguments de tool-call sont toujours une chaîne JSON brute ; finish est le dernier bloc ; usage avant finish.

Erreurs et métadonnées

  • Défaut de transport/protocole : throw new LlmError(msg, 'STABLE_CODE') (avec un code stable), ne comptez pas sur la conversion automatique d'une Error normale.
  • Le fournisseur ne prend pas en charge un champ de GenerateOptions : throw new LlmError(..., 'UNSUPPORTED'), ne l'ignorez jamais silencieusement.
  • Ajoutez attributionHeaders() à chaque requête HTTP et transmettez options.signal.
  • Optionnel : surchargez resolveModel() (retourne l'identité du fournisseur/modèle + métadonnées context/reasoning optionnelles) et listModels() (publie les options de modèle).

Tutoriel complet dans References/05-llm-adapter.md ; implémentations de référence : packages/llm/llm-deepseek (compatible OpenAI SSE) et packages/llm/llm-pi-ai.


9. Hiérarchisation des capacités (trois rôles, motif de conception de capacité remplaçable)

Lorsqu'une capacité nécessite un fournisseur remplaçable (ex. exécution Bash), divisez-la en trois rôles, placés dans différents paquets :

  1. Definition de Service (ex. dsh-shell) : Définit l'interface du service Cordis + les types Request/Result. abstract class MyCapService extends Service { super(ctx,'myCap'); abstract execute(req): Promise<res> }
  2. Fournisseur de Service (ex. dsh-bash-local) : Implémente l'interface. class MyCapLocal extends MyCapService { ... } ; apply(ctx){ ctx.plugin(MyCapLocal) }
  3. Consommateur (ex. dsh-tool-bash) : Expose la capacité comme un outil pour le modèle. inject: ['tools','myCap']

Direction de la dépendance : le Fournisseur dépend de la Definition ; le Consommateur dépend de la Definition ; le Fournisseur et le Consommateur ne dépendent pas l'un de l'autre. Pour changer de fournisseur, modifiez une seule ligne dans cordis.yml, la Definition et le Consommateur restent inchangés.

Ne faites pas de division préventive : ne divisez en paquets que si le rôle doit évoluer indépendamment. Un simple plugin d'outil dans un seul paquet suffit.


10. Empaquetage et installation (livrer le plugin aux utilisateurs)

Deux concepts

  • Paquet combiné (bundle) : Un paquet npm avec une couche de configuration, le manifeste déclare dsh.bundle. C'est ce que vous écrivez et distribuez.
  • Profil (profile) : Un ensemble démarrable situé dans $DSH_HOME/profiles/<nom>, le manifeste déclare dsh.profile (contenant une liste ordonnée de bundles). C'est ce que l'utilisateur démarre.

Empaqueter un bundle

hello-plugin/
├── package.json       # Déclare dsh.bundle
├── cordis.patch.yml   # Contenu de la couche
└── index.js           # Module plugin
// package.json
{
  "name": "dsh-hello-plugin",
  "version": "0.1.0",
  "type": "module",
  "main": "index.js",
  "files": ["index.js", "cordis.patch.yml"],
  "dsh": { "bundle": { "patch": "cordis.patch.yml" } }
}
# cordis.patch.yml
plugins:
  - name: dsh-hello-plugin
    config:
      greeting: !!js process.env.HELLO_GREETING ?? 'Hello'

Empaqueter un profil (recommandé : utiliser dsh init)

Ne créez pas manuellement les fichiers dans $DSH_HOME/ ; utilisez dsh init qui génère un profil Dockerisé standard. Pour comprendre la structure, sachez que dsh init crée :

$DSH_HOME/profiles/<name>/
├── dsh.profile          # manifeste YAML, déclare bundles + fournisseur LLM + bases
├── cordis.patch.yml     # personnalisation ultime de la couche
└── custom-adapters/     # répertoire d'adaptateurs locaux
# dsh.profile
dsh:
  profile:
    bundles: ['@deepseek-ai/dsh-tool-bash', 'local-plugin']   # Liste des bundles
    dialog: { root: { slotTags: ['main', 'agent'] } }         # Contexte de session
    llm: { model: 'deepseek-chat', provider: 'deepseek' }     # Modèle par défaut
  providers:
    deepseek: { apiKey: !!js process.env.DEEPSEEK_API_KEY }

Installer et mettre à jour un plugin

  • Développement local : dsh plugin --profile <nom> add ../../chemin/vers/mon-plugin ou ajoutez directement le bundle dans dsh.profile.
  • Depuis npm : dsh plugin --profile <nom> add mon-plugin + configuration dans cordis.patch.yml.
  • Retirer : dsh plugin --profile <nom> remove mon-plugin.
  • Mettre à jour : dsh plugin --profile <nom> update (met à jour toutes les dépendances du profil).

Règle : tout ce qui est déclaré dans bundle est versionné et figé dans un fichier verrou (lock file). Utilisez dsh plugin update pour mettre à jour.


11. Débogage et vérification

Charger un plugin avec l'interface Web

pnpm dsh web --patch chemin/vers/votre/cordis.patch.yml

Vérifiez la console Harness (paneau de gauche) : si votre plugin apparaît dans la section "Services" avec le statut ACTIVE, tout va bien. Sinon, l'erreur de chargement est affichée dans le journal.

Charger un plugin en mode console

pnpm dsh run --profile <nom>

Test unitaire d'un plugin

import { App } from '@deepseek-ai/cordis'
import pluginConfig from '@deepseek-ai/deepseek-harness/__tests__/harness-app'

const app = new App(pluginConfig)
app.plugin(MyPlugin, { greeting: 'Test' })
await app.start()

Vérifications

  • Toutes les ressources sont enregistrées via ctx (pas de setInterval nu, pas d'écouteur global).
  • inject déclare toutes les dépendances nécessaires ; les dépendances optionnelles sont sondées avec ctx.get().
  • La configuration est validée par un schéma Schemastery ; les valeurs invalides font échouer le chargement.
  • Les callbacks d'affichage (render, presentCall, presentResult) sont des fonctions pures.
  • Les adaptateurs LLM respectent le protocole StreamChunk.
  • Le plugin est empaqueté en un bundle ; son dsh.bundle pointe vers le bon cordis.patch.yml.

Références

Tous les documents se trouvent dans le répertoire References/ :

  • 01-architecture.md — Architecture de DeepSeek Harness
  • 02-cordis.md — Framework Cordis (contexte, service, effet, événement, configuration)
  • 03-tools.md — Contrat complet des outils (defineTool, commande, exécution en arrière-plan, pipeline)
  • 04-ui.md — Interface utilisateur et rendu des cartes
  • 05-llm-adapter.md — Contrat complet des adaptateurs LLM (stream, métadonnées, erreurs)
  • 06-plugin-route.md — Routage des plugins et stratégie de chargement
  • 07-account.md — Connexion des comptes et stockage des credentials
  • 08-capability-layering.md — Hiérarchisation des capacités (definition, fournisseur, consommateur) et liste des paquets
  • 09-config-schema.md — Bonnes pratiques et références des schémas de configuration
  • 10-spatiotemporal.md — Fondements théoriques du modèle de composant Cordis
  • 11-cookbook.md — Exemples de recettes de développement (contrôle d'accès, hook de session, etc.)

Skills associés