Un plugin añade operaciones y servicios al runtime de Comal. La casa mantiene actor, permisos, contratos y recibos: el plugin no puede autorizarse a sí mismo. ## Descubrir y cargar ```sh node bin/comal.mjs --demo --plugin-config examples/comal-plugins.json -p /plugins ``` Descubrir el JSON no importa el módulo. Para cargar plugins habilitados al iniciar, usa `--trust-plugins`. Los entrypoints son archivos locales `.mjs`/`.js`; las rutas se resuelven respecto del archivo de configuración. ```json { "plugins": [ { "id": "hello", "module": "./hello.mjs", "enabled": true, "config": {} } ] } ``` ## Una operación pequeña ```js export default { id: 'hello', version: '1.0.0', requires: [], provides: [], activate(ctx) { ctx.operation({ name: 'Hello', description: 'Devuelve un saludo', input_schema: { type: 'object', additionalProperties: false }, run() { return { message: 'Hola desde Comal' }; } }); } }; ``` La operación se publica como `plugin__hello__Hello`. Cargar el módulo no concede permiso para llamarla; la invocación pasa por el juez. ## Ciclo de vida ```text /plugins /plugins enable hello /plugins reload hello /plugins disable hello ``` Los comandos pertenecen al usuario y los cambios mutables esperan en cola. Enable/disable/reload afectan esta sesión; para persistir selección entre procesos, edita `enabled` en la configuración. La preparación de una nueva generación ocurre antes de publicarla. Un fallo de preparación conserva la anterior; la retirada cancela cooperativamente y espera operaciones iniciadas antes del cleanup. No hay rollback de efectos externos de activate. ## Contratos de extensión `ctx.service` consume servicios declarados en requires; `ctx.provide` publica los de provides. `ctx.guard` puede vetar operaciones propias, pero nunca superar una denegación. Los observers reciben copias inmutables; no fabrican recibos. `ctx.defer` registra limpieza. Los módulos Node son código confiado del proceso, no un sandbox. Para implementaciones en otros lenguajes, expón capacidades mediante MCP o procesos autorizados. La API de plugins de Comal es propia; no promete cargar directamente paquetes de DeepSeek/Cordis. ## Casos que ya existen `web` publica WebSearch/WebFetch para investigar fuentes reales. `delegation` publica Agent/AgentBatch para separar exploraciones; no se registra en demo. El ejemplo local `hello` publica Hello, un comando humano Inspect y un servicio hello.greeter. Su saludo se ha ejecutado mediante CLI sin llamada al modelo. Un plugin también puede proporcionar servicios a otro mediante requires/provides. Casos como diagnósticos de un monorepo, políticas de proyecto o adaptadores de compilación son extensiones posibles: no vienen implementadas por el ejemplo Hello. F6 permite inspeccionar operaciones publicadas; `/plugins run ` completa el ID y después el nombre de operación. Preparar texto no importa código ni ejecuta el handler. ## Chrome Extension Lab con Codex Para abrir Comal con el plugin chrome-lab y tu login Codex ya autenticado, usa desde el repositorio: ```sh npm run lab:chrome -- --auth-provider codex --name residente ``` Para reanudar una sesión con ese plugin: ```sh npm run lab:chrome -- --auth-provider codex --name residente --resume UUID ``` El launcher selecciona `examples/comal-chrome-lab.json`, carga el plugin y autoriza sus 15 operaciones de laboratorio. Las denegaciones explícitas prevalecen y los permisos de ediciones locales/Bash siguen separados. Conserva también tus argumentos de perfil/proveedores si los utilizas. Comprueba `/plugins`: debe mostrar `chrome-lab` en estado `active`. `/plugins run chrome-lab Status {}` verifica la conexión sin llamar al modelo. La conexión SSH se abre cuando se invoca la primera operación, no al listar el plugin. Si sólo aparecen `web` y `delegation`, la configuración local del laboratorio no se cargó. `node bin/comal.mjs` utiliza el archivo `plugins.json` del directorio de estado, que puede no existir; ejecutar o desarrollar el plugin no lo registra automáticamente allí. El launcher `lab:chrome` carga la configuración explícita en cada inicio. `lab:chrome-cycle` y `lab:chrome-cycle-live` ejecutan experimentos y terminan. Para trabajar en la TUI con el plugin utiliza `lab:chrome`. ## Tarea Chrome coordinada por la casa ```sh npm run lab:chrome -- --chrome-task mi-tarea.json --name residente ``` El JSON declara `project`, `directory`, `popup`, `selector` y `equals`. Por ejemplo: `{"project":"mi-extension","directory":"extension","popup":"popup.html","selector":"#status","equals":"Lista lista"}`. `directory` debe estar dentro del workspace y contener la extensión MV3. Opcionalmente `tests` declara una secuencia de `fill`, `click`, `reload` y `assert` (con selector y `equals` o `count`). Esas interacciones escriben datos de prueba en la extensión; usa un proyecto propio de laboratorio. La casa conserva el encargo, deriva las versiones y el target y verifica el cierre mediante sus operaciones normales. El modelo recibe Read/Edit/Write y acciones ChromePublish/ChromeVerify/ChromeFinish/ChromeRecover; con pruebas de interacción también ChromeTest. La configuración no concede permisos adicionales. La TUI muestra `chrome:PROYECTO` junto a los indicadores del modelo. Reanuda con `npm run lab:chrome -- --resume UUID`; el JSON original puede faltar porque la declaración está en el stream. Otro criterio requiere otra sesión. Un despliegue incierto no se repite automáticamente. `delivery.status=verified` acredita sólo el criterio y pruebas declarados observados; la gobernanza y los recibos permanecen locales, sin firma externa. ### Recuperar una publicación interrumpida Después de reanudar el mismo UUID, ejecuta `/work recover`. La casa reconecta y compara el intento guardado, los hashes de los archivos remotos y la extensión cargada. No llama al modelo ni repite Deploy. Si devuelve `recovered`, `/work resume` permite continuar: ChromePublish abre el popup pendiente sin publicar otra vez. ChromeRecover también está disponible al residente. Un recibo ausente, otro intento, una publicación en curso o un snapshot distinto mantienen el bloqueo. Las aperturas inciertas requieren un popup propio único ya observado; los clicks/fills inciertos y los intentos legacy sin identidad no se reconcilian automáticamente. Deployment es la observación de lectura del plugin que sustenta este control.