Skip to content

CH 18 · Tu Primer Plugin: hello-plugin

Recuento de palabras~2,990 palabrasTiempo~15 minRequisitosCH 08 (Árbol de Plugins), CH 17 (Instalación de Plugins)NivelReproducible

Objetivo del capítulo

En CH 17 aprendiste a instalar plugins hechos por otros. Este capítulo comienza escribiendo el tuyo propio — el objetivo es el mínimo: escribe un hello-plugin y haz que sea cargado por dsh. No escribes herramientas, no tocas la interfaz, solo verificas una cosa: el plugin que escribes puede ser descubierto, cargado y ejecutado por dsh. Una vez conectado este paso, las cosas interesantes de CH 19 a CH 23 (herramientas, hooks, UI, publicación) crecen encima de él.

Primero, una mentalidad: los plugins no son misteriosos. CH 08 dijo "todo es un plugin", la inversa es: si quieres que dsh tenga una capacidad más, escribe un pequeño módulo que exporte una función apply. Ese es todo el esqueleto de un plugin.

Práctica: escribe un hello-plugin

En el workspace que quieras, crea el directorio (primero cd a ese directorio, luego ejecuta):

powershell
New-Item -ItemType Directory -Path "hello-plugin\src" -Force

Luego crea hello-plugin\src\hello-plugin.js, escribe:

js
export const name = 'hello-plugin'

export function apply(ctx) {
  console.log('[hello-plugin] plugin loaded!')
}

Solo estas dos líneas de lógica central: cuando se carga el plugin, imprime [hello-plugin] plugin loaded!. Si puede imprimir, eso prueba "tu código fue ejecutado por dsh" — ese es el primer hito.

Aquí usamos JS en lugar del ejemplo oficial TS: el dsh instalado globalmente no tiene un runtime tsx integrado, y cargar .ts directamente dará error; usar .js no necesita build, cero dependencias, se ejecuta en cinco minutos. Cuando escribamos plugins más complejos, traeremos TypeScript y la cadena de build (CH 20 lo amplía).

Cárgalo en dsh

Solo con tener el archivo, dsh no sabe que debe cargarlo. Necesitamos un "overlay" para decirle a dsh: carga adicionalmente este plugin. Crea hello-plugin\cordis.yml (la ruta en name de abajo es un ejemplo, sustitúyela por tu propio directorio, consulta las notas después):

yaml
- insert:
    - id: hello
      name: 'file:///E:/software-workspace/DeepSeek%20harness%20demo/hello-plugin/src/hello-plugin.js'

Tres notas:

  • name debe ser una URL completa que comience con file://, no puedes escribir E:\... o E:/.... En Windows, el cargador de módulos de dsh solo acepta la forma file:///E:/...; escribir la ruta de la letra de unidad directamente reportará Only URLs with a scheme in: file, data, and node are supported.
  • Los espacios en la ruta deben codificarse como %20. Por ejemplo, si tu directorio es mi espacio de trabajo, escríbelo como mi%20espacio%20de%20trabajo.
  • Esta es una ruta absoluta. El archivo patch solo aporta configuración, la raíz de resolución de módulos sigue siendo el directorio del profile, así que los plugins locales deben escribirse como rutas completas.

Ejecuta headless una vez para verificación rápida

No toques la Web UI, usa headless para verificar rápidamente que el plugin está realmente cargado:

powershell
cd tu-directorio-de-workspace
dsh --profile headless --patch "./hello-plugin/cordis.yml" "Just reply: hi"

En la salida verás estas dos líneas:

text
[hello-plugin] plugin loaded!
hi

La primera línea la imprime tu plugin al arrancar, la segunda es la respuesta del modelo tras completar la tarea. Ver [hello-plugin] plugin loaded!, tu primer plugin está en marcha.

Luego cárgalo en la Web UI

Headless puede verificar, pero el plugin está pensado para usarse en la Web UI. Primero detén el dsh web en ejecución (si no, el puerto está ocupado), luego arranca con el patch:

powershell
dsh web --patch "./hello-plugin/cordis.yml"

Abre http://127.0.0.1:3080, la terminal donde se inició dsh también imprimirá [hello-plugin] plugin loaded!. hello-plugin no tiene efecto en la UI por ahora, su único "output" es esa línea de log — pero esto prueba que ha entrado en el árbol de plugins web, junto a los miembros del árbol del que habló CH 08.

Mira la salida de la terminal: la primera línea es el log de carga del plugin impreso al arrancar dsh, las dos líneas siguientes son la información de listo de la Web UI.

Limpieza automática al descargar

Todo lo que registres con ctx (event listeners, herramientas, timers) será limpiado automáticamente por el framework cuando el plugin se descargue; no necesitas removeListener ni clearInterval manualmente. Si tienes recursos que necesitan liberación manual (p. ej. una conexión de red), usa ctx.effect() para indicarle al framework cómo limpiar:

js
export function apply(ctx) {
  ctx.effect(() => {
    const timer = setInterval(() => {
      console.log('heartbeat')
    }, 5000)

    // La función devuelta se ejecuta cuando el plugin se descarga
    return () => clearInterval(timer)
  })
}

La función de limpieza devuelta por effect se llamará en el momento en que el plugin se descargue — esta es la forma estándar en que dsh te ayuda a gestionar el ciclo de vida de los recursos.

Declara dependencias: inject

Si tu plugin necesita otras capacidades (p. ej. tools, llm), declara inject, y el framework se asegurará de que las dependencias estén listas antes de cargar tu plugin:

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

export function apply(ctx) {
  // Aquí ctx.tools está garantizado que estará disponible
  ctx.tools.register(/* ... */)
}

inject es el punto de entrada para las "dependencias de servicio" en Cordis. Nos familiarizaremos con él por ahora, y lo usaremos oficialmente en CH 20 cuando escribamos plugins de herramientas.

Tres formas de plugin

La función apply es la forma más común, pero los plugins admiten tres formas de escritura (CH 19 detallará cada una, aquí va la visión general):

FormaAspectoCuándo usarla
Funciónexport function apply(ctx) {}Opción por defecto, lo que usa este tutorial
Objetoexport default { name, apply(ctx) {} }Cuando quieres llevar algo de metadatos estáticos contigo
Claseexport default class extends Service {}Cuando quieres proveer servicios a otros plugins (CH 19 lo amplía)

Por ahora, solo recuerda una línea: la forma función resuelve el 90% de las necesidades, deja la forma servicio para cuando "tu plugin necesite que otros plugins dependan de él".

Deja que dsh lo haga: un prompt lo resuelve

Los pasos de arriba los hiciste manualmente, todo aprendido. Pero dsh en sí es un Agent — puede hacer el trabajo de escribir plugins, y "lo escribe y verifica por sí mismo".

Envía este prompt directamente en el cuadro de entrada de la Web UI:

text
In my current workspace, help me write a minimum hello-plugin plugin that dsh can load. First go read the official dsh plugin development docs, figure out how plugins should be written and loaded, then implement per the official spec, verify it's actually loaded, and finally tell me the result and where the files are.

No necesitas decirle ningún detalle técnico — irá a leer los docs oficiales de desarrollo de plugins por sí mismo, decidirá cómo escribir, cómo cargar, cómo verificar. Solo observa cómo trabaja, luego abre el archivo para comprobar qué escribió. Esta es exactamente la extensión de "todo es un plugin": el trabajo de escribir plugins también puede hacerlo un Agent ensamblado a partir de plugins.

Yo lo ejecuté una vez realmente, la esquina superior derecha muestra los materiales de referencia que enumeró y los archivos producidos (package.json, index.js, cordis.patch.yml). Tras terminar de escribir, el panel de archivos del lado derecho muestra directamente el nuevo directorio hello-plugin en el workspace — justo donde brilla el plugin de barra lateral instalado en CH 17, no hace falta cambiar al gestor de archivos para comprobar qué escribió.

Errores comunes

ProblemaQué está pasandoCómo manejarlo
Reporta Only URLs with a scheme in: file...En Windows la ruta está escrita como forma de letra de unidadCambia name a una URL completa como file:///E:/...
Reporta file/module not foundLos espacios en la ruta no están codificadosEscribe los espacios como %20
¿Sin respuesta tras el patch y reinicio?La ruta del plugin o el yml están mal escritosComprueba la ortografía de id y name, usa dsh --profile web --patch ./hello-plugin/cordis.yml --dump-config para ver si el plugin está en el árbol de configuración
¿Cargar .ts directamente da error?El dsh global no tiene un runtime tsx integradoPrimero usa .js para ejecutarlo sin build, cuando se necesite TS construye primero a .js y luego carga
¿Puerto ocupado, no se puede iniciar?El dsh web anterior sigue ejecutándoseDetén primero el proceso antiguo, luego inicia

Lo que aprendiste en este capítulo

Apruebas si puedes completar los puntos de abajo:

  • [ ] Enuncias la forma mínima de un plugin: un módulo que exporta una función apply(ctx)
  • [ ] Creas un hello-plugin y lo declaras con una URL file:// en cordis.yml
  • [ ] Usas dsh --profile headless --patch ... para verificar rápidamente que el plugin está cargado
  • [ ] Inicias web con --patch para traer el plugin al árbol de plugins de la Web UI
  • [ ] Sabes que ctx.effect() hace limpieza de recursos, inject declara dependencias de servicio, los plugins tienen tres formas: función / objeto / clase

Open Source · MIT · Community Driven