Saltar al contenido

Crear paneles

Acciones de paneles

Deja que el agente de una tarea que la persona conecta lea y cambie tu panel: cómo declarar acciones, los esquemas que revisa Deska, cómo responder llamadas, las imágenes, cómo ofrecer contenido con Enviar a la tarea y las reglas que mantienen todo en la computadora.

Cómo funciona

Un panel puede ofrecer acciones a un agente de programación: leer las notas de un tablero, agregar una imagen, cambiar una tarjeta. La persona decide qué tarea puede usarlas conectando tu panel a esa tarea, y el agente las llama mientras trabaja.

  • Tu panel declara sus acciones en panel.json, cada una con un nombre, una descripción, un tipo (read o write) y un esquema de lo que entra y, en una lectura, de lo que sale.
  • La persona conecta el panel a una tarea, desde la franja de Deska sobre el panel (Conectar a una tarea) o desde el encabezado de la tarea (Conectar un panel). La primera vez, Deska pregunta con su propia hoja, que lista cada acción. Solo se conectan tareas supervisadas locales: nunca Dex, nunca un agente en una terminal, nunca una tarea en otra computadora.
  • El agente de la tarea ve dos herramientas de Deska, una para listar los paneles conectados a ella con sus acciones y otra para llamar una acción. Deska lleva una llamada solo a un panel conectado a esa tarea, y la revisa contra tu esquema antes de que tu página la vea.
  • Tu página recibe la llamada, hace el trabajo y responde. Deska revisa también la respuesta.

Tu panel nunca llama una herramienta y nunca ve la conversación del agente. Solo recibe las llamadas a sus propias acciones. La persona puede apagar las acciones de paneles para todas las tareas en Ajustes, Agentes, Acciones de paneles.

Las acciones necesitan "sdk": 4. Una versión que las declara siempre se revisa antes de publicarse, y Deska lee las descripciones en la revisión: llegan al agente, así que escríbelas para él, con claridad, y nunca como instrucciones más allá de lo que hace la acción.

Un panel que trabaja con agentes no habla con nadie más

Lo que un agente le da a tu panel se queda en la computadora. Por eso una versión que declara agent no puede declarar además ninguno de estos, y las revisiones rechazan cada uno:

  • network con algún host, así la página no llega a ningún sitio web.
  • embeds o signedInEmbeds, así no muestra ningún otro sitio.
  • El permiso account, así no puede iniciar la sesión de nadie.
  • El permiso open-url, porque un enlace que arma podría llevar lo que el agente le dio.

Conserva todo lo demás: deska.storage, deska.files, guardar un archivo donde la persona elige, copiar con clipboard-write y notify.

La regla vale entre versiones, porque el almacenamiento y los archivos sobreviven a una actualización. Una vez que cualquier versión del id de tu panel declaró agent, ninguna versión posterior puede declarar ninguno de esos cinco: deska.dev rechaza ese envío, y Deska se niega a instalarla, actualizarla o recargarla en una computadora que alguna vez tuvo una versión que trabajaba con agentes. Una versión posterior puede quitar agent y seguir sin red. Para llegar a la red, envía el panel con un id nuevo.

Declarar acciones

panel.jsonjson
{
  "permissions": ["agent"],
  "sdk": 4,
  "agent": {
    "actions": [
      {
        "name": "add_image",
        "description": "Adds an image to the board, with an optional caption.",
        "kind": "write",
        "input": {
          "type": "object",
          "properties": {
            "image": { "type": "string", "maxLength": 1000, "format": "deska-image" },
            "caption": { "type": "string", "maxLength": 280 }
          },
          "required": ["image"]
        }
      },
      {
        "name": "read_board",
        "description": "Reads the notes and images on the board.",
        "kind": "read",
        "input": { "type": "object" },
        "output": {
          "type": "object",
          "properties": {
            "notes": { "type": "array", "maxItems": 100, "items": { "type": "string", "maxLength": 2000 } },
            "images": { "type": "array", "maxItems": 10, "items": { "type": "string", "maxLength": 36, "format": "deska-file" } }
          }
        }
      }
    ]
  }
}
CampoRegla
nameUna letra minúscula y luego hasta 39 letras minúsculas, dígitos y guiones bajos ([a-z][a-z0-9_]{0,39}), único en el paquete.
descriptionDe 1 a 300 caracteres de texto simple, que ven el agente y la persona. Sin saltos de línea ni caracteres de ancho cero o bidi.
kindread (su respuesta llega al agente) o write (cambia el panel y solo responde si funcionó).
inputUn esquema de objeto para lo que envía el agente.
outputUn esquema de objeto para lo que responde una lectura. Obligatorio en una lectura, rechazado en una escritura.

Como máximo 16 acciones. El permiso agent y el objeto agent se necesitan mutuamente, y ninguno acepta otra clave.

Los esquemas

Una parte pequeña de JSON Schema, suficiente para describir los datos de un panel y fácil de revisar. Cada nodo es uno de estos:

typePalabras clave
objectproperties, hasta 32, cada una con un nombre que empieza con una letra y sigue con hasta 39 letras, dígitos y guiones bajos ([A-Za-z][A-Za-z0-9_]{0,39}); required, nombres de properties; additionalProperties, sin poner o false. Un objeto nunca acepta propiedades que no lista.
stringmaxLength, obligatorio, de 1 a 4,000; enum, de 1 a 50 valores distintos, cada uno de 1 a 64 letras, dígitos, espacios, puntos, guiones bajos o guiones ([A-Za-z0-9 ._-]) y dentro de maxLength; format: deska-image en una entrada o deska-file en una salida (consulta Imágenes), nunca junto con enum.
number, integerminimum y maximum, los dos inclusivos.
booleanSin palabras clave.
arrayitems, un esquema; maxItems, obligatorio, de 1 a 100.
  • La raíz de input y de output es un objeto. Los esquemas se anidan como máximo 4 niveles: la raíz es el primero, y cada propiedad o elemento de un arreglo suma uno.
  • Los nombres de las propiedades y los valores de enum son palabras cortas, nunca frases: el agente lee cada uno, y la persona no lee ninguno, porque la hoja de Deska muestra el nombre y la descripción de cada acción, no sus esquemas. Uno fuera de esos caracteres rechaza el paquete, con un problema que lo cita.
  • Cualquier nodo puede llevar una description de 1 a 200 caracteres, sin caracteres invisibles. Es para quien lee tu panel.json, y nunca llega al agente (consulta Lo que ve el agente): pon lo que el agente debe saber en la descripción de la propia acción.
  • Cualquier otra cosa ($ref, oneOf, minLength, pattern, una lista de tipos) rechaza el paquete, con un problema que nombra la palabra clave y dónde está.

Deska revisa cada llamada contra input antes de que tu página la reciba: una propiedad desconocida, una obligatoria que falta, un texto más largo, más elementos, un valor fuera de enum o una fracción para un entero nunca te llegan, y al agente se le dice qué estaba mal. Los caracteres invisibles se quitan del texto que envía.

Lo que ve el agente

Cuando el agente pregunta qué paneles están conectados a su tarea, Deska lista cada panel con su id y el nombre que ve la persona, y cada acción solo con lo que la persona vio en la hoja de conexión y lo que necesita una llamada:

  • Su name, su kind y su description, con los caracteres invisibles convertidos en espacios, hasta 300 caracteres.
  • Sus esquemas input y output, sin ninguna description de esquema. Ninguna persona las leyó, así que nunca llegan al agente.

Una acción cuyos nombres de propiedades o valores de enum quedan fuera de las reglas de arriba (una versión instalada antes de que valieran) queda fuera, y una llamada a ella responde unknown_action. La lista entera tiene un tope de 8 KB: las acciones se conservan en el orden de panel.json mientras caben, y las demás se cuentan como dejadas fuera. Pon primero tus acciones más útiles.

Responder una llamada

app.jsjs
const EMPTY = { notes: [], images: [], captions: {}, revision: 0 }
const readBoard = async () => (await deska.storage.get('board')) ?? EMPTY

async function run(action, input) {
  const board = await readBoard()
  if (action === 'read_board') {
    return { notes: board.notes.slice(-100), images: board.images.slice(-10) }
  }
  // add_image: input.image es un Blob que Deska leyó de la carpeta de la tarea
  const kept = await deska.files.put(input.image)
  const next = {
    ...board,
    images: [...board.images, kept.id],
    captions: { ...board.captions, [kept.id]: input.caption ?? '' },
    revision: board.revision + 1,
  }
  await deska.storage.set('board', next)
  render(next)
  return { ok: true, revision: String(next.revision) }
}

// Escucha antes de ready: una tarea ya conectada puede llamar apenas la página carga
deska.on('agent.action', async ({ callId, action, input }) => {
  const result = await run(action, input).catch(() => ({ ok: false, error: 'failed' }))
  await deska.agent.respond(callId, result).catch((err) => console.warn(action, err.code))
})

deska.on('agent.connected', ({ tasks }) => {
  connectedNote.hidden = tasks === 0
})

render(await deska.ready().then(readBoard))

Tu página recibe agent.action con { callId, action, input }: el nombre de la acción y una entrada que ya pasó su esquema. Escucha antes de llamar a deska.ready(). Una llamada que llega antes de que haya un listener espera al primero (el SDK guarda las últimas 16), pero sus 30 segundos ya están corriendo.

deska.agent.respond(callId, result)

ArgumentoTipoNotas
callIdstringEl id con el que llegó la llamada.
resultobjectUna escritura: { ok, revision?, error? }. Una lectura: lo que permite su esquema output.
Permiso
agent
Devuelve
true cuando Deska tomó la respuesta.
Límites
  • 30 segundos por llamada.
  • Solo la copia del panel a la que fue la llamada puede responderla, una vez.
ErrorCuándo
invalid_argsNo hay una llamada así esperando a esta página, o la respuesta no pasa. La llamada sigue esperando una respuesta buena.
timeoutSe acabaron los 30 segundos; al agente se le dijo que el panel no respondió.
permission_deniedpanel.json no pide agent, o la persona lo apagó.

init.agent.tasks dice a cuántas tareas está conectado el panel cuando su página empieza (0 en su página de Extras), y agent.connected llega después de init y en cada cambio, así una página que se recarga lo sabe de las dos formas. Una conexión pertenece al panel del escritorio, no a su página de Extras.

Respuestas de escritura

Una escritura solo responde si funcionó: { ok, revision?, error? }. Nada más de lo que dice tu página llega al agente, y cualquier otra clave rechaza la respuesta. Así, una escritura nunca puede llevar el texto del panel a la conversación.

  • ok: true o false.
  • revision: opcional, de 1 a 64 letras, dígitos, ., _ y -. Úsalo para que el agente distinga un estado del panel del siguiente.
  • error: solo con ok: false, uno de invalid, conflict, not_found y failed.

El agente nunca ve tu revision tal cual, porque es tu texto. En su lugar lee un contador que Deska lleva para cada tarea y panel: "revision":"r1", luego r2, y el mismo contador otra vez para la misma revisión (guarda los últimos 256). Cuando la entrada de una llamada posterior lleva exactamente un contador que Deska le dio a esa tarea para tu panel, tu página recibe tu propia revisión en su lugar. Así que usa las revisiones como siempre, por ejemplo para responder conflict cuando un cambio nombra una revisión que ya no es la actual.

Haz escrituras que la persona pueda deshacer, como deshace sus propios cambios: el agente trabaja sobre su panel, no sobre una copia.

Respuestas de lectura

Una lectura responde lo que permite su esquema output. Deska la revisa, quita cada carácter que se dibuja como nada (los saltos de línea y los tabuladores se quedan), vuelve a codificar sus imágenes y se la da al agente encerrada como contenido de tu panel que el agente no debe tomar como instrucciones:

text
<panel_content package="nook" panel="Nook" trust="untrusted">
{"notes":["Make the header blue"],"images":["image:1"]}
</panel_content>

Una respuesta que no pasa su esquema se rechaza con invalid_args, y la llamada sigue esperando una buena hasta que se acaban sus 30 segundos. Deska además limita el JSON a 65,536 caracteres, tal como se envía y una vez escapado para encerrarlo: si lo supera, respond igual responde true, y al agente se le dice que la respuesta era demasiado grande. Como lo que responde una lectura puede llevar texto que escribió cualquiera, el turno primero se pausa para pedir aprobaciones. Consulta La pausa para pedir aprobación.

Imágenes

  • Entrada. Un texto con "format": "deska-image" en una entrada es una ruta que da el agente, a una imagen en la carpeta de trabajo de la tarea. Deska la lee por su cuenta (un PNG, JPEG o WebP de hasta 10 MB, nunca fuera de esa carpeta ni a través de un enlace que salga de ella), y tu página recibe un Blob donde estaba la ruta. Guárdala con deska.files.put.
  • Salida. Un texto con "format": "deska-file" en una salida es el id de una imagen que tu panel guarda en deska.files. Deska la lee, la vuelve a escribir como un PNG de 2,048 píxeles por lado como máximo, sin los metadatos del original, y el agente la recibe como imagen después del texto, donde el id dice image:1, image:2 y así. Un id que el panel no guarda queda como image:unavailable.
  • Como máximo 10 imágenes en una llamada o en una respuesta.

Enviar a la tarea

Hay contenido que solo debe llegar a una tarea cuando la persona lo decide: comentarios escritos en el tablero, las imágenes que eligió. Tu página lo ofrece, y el botón Enviar a la tarea de Deska se enciende en su franja sobre el panel. Tu página nunca puede pulsarlo.

app.jsjs
// Di qué enviaría Enviar a la tarea cada vez que cambia la selección.
// Sin texto y sin imágenes, la oferta se retira.
async function offerSelection() {
  await deska.agent.offer({ text: commentBox.value.trim(), files: selectedImageIds.slice(0, 6) })
}

deska.agent.offer({ text, files })

Una oferta nueva reemplaza la anterior. Cuando la persona pulsa Enviar a la tarea, Deska muestra exactamente qué va, el texto como texto y cada imagen, y adónde: la tarea conectada, como su siguiente mensaje (en cola mientras trabaja), o un borrador de Nueva tarea que la persona inicia cuando no hay ninguna tarea conectada. Lo enviado va encerrado como contenido de tu panel, y el turno que empieza se pausa para pedir aprobaciones. Deska nunca envía ni inicia nada por su cuenta. Ofrecer funciona antes de que haya una tarea conectada.

ArgumentoTipoNotas
textstringOpcional, hasta 8,000 caracteres.
filesstring[]Opcional, hasta 6 ids distintos de imágenes PNG, JPEG o WebP que el panel guarda en deska.files. Deska envía cada una como un PNG nuevo de hasta 2,048 píxeles por lado, sin sus metadatos.
Permiso
agent
Devuelve
true mientras Enviar a la tarea está encendido, false cuando se retiró o desde una página de Extras, que no tiene franja.
ErrorCuándo
invalid_argsUn texto de más de 8,000 caracteres, más de 6 ids, un id repetido o un id que no es una imagen PNG, JPEG o WebP que guarda el panel (un GIF se rechaza).
invalid_argsUn texto con un carácter que se dibuja como nada (salvo saltos de línea y tabuladores). La hoja muestra exactamente qué va, y la persona no podría verlo.
permission_deniedLa persona apagó agent en la página del panel.

La pausa para pedir aprobación

El texto dentro de un panel puede intentar dirigir a un agente. Por eso, cuando la respuesta de una lectura, o contenido que la persona envía con Enviar a la tarea, entra en un turno, Deska pone esa tarea en Supervisado durante el resto de ese turno, en el modo que sea, y devuelve el modo cuando el turno termina. Supervisado es el modo en que cada agente pregunta: sobre qué le pregunta a la persona lo decide ese agente, no Deska. Una línea en la tarea dice por qué: <panel> envió contenido a este turno, así que esta tarea queda en Supervisado hasta que el turno termine. La respuesta de una escritura no lleva texto del panel y no pausa nada.

Mientras dura la pausa, Deska además:

  • Rechaza cada llamada a sus herramientas de vista previa del navegador, y le dice al agente que espere a la persona.
  • Ignora una respuesta Permitir siempre en esta sesión dada antes de la pausa, donde Deska la guarda, así que la misma solicitud vuelve a preguntar. Una nueva cuenta como permitir una vez.
  • No lanza ningún subagente de Claude Code.

Cuando el turno termina, Deska devuelve el modo y agrega una fila: El turno terminó, así que esta tarea vuelve a <modo>. El contenido que envió <panel> sigue en esta conversación, y los turnos siguientes pueden leerlo. Su Seguir preguntando pone la tarea en Supervisado hasta que la persona lo cambie. Solo un cambio de modo de la propia persona termina antes una pausa. Uno de Dex nunca: Dex nunca lee lo que envió un panel, y mientras dura una pausa necesita que la persona confirme para aflojar el modo de la tarea o aprobar algo en ella.

El mensaje de un panel espera su propio turno, llegue donde llegue en la cola. Deska nunca lo suma a un turno en curso, A continuación no le ofrece Guiar ahora ni Editar, y una vez enviado no se puede editar.

Algunos agentes pasan a Supervisado en medio de un turno, y otros solo desde el siguiente. En esos, una lectura se aplaza: la llamada responde enseguida con una frase de Deska y nada de tu panel, y cuando termina el turno Deska envía la respuesta del panel a la tarea como su siguiente mensaje, marcada como del panel, en un turno que pregunta. Espera una lectura por tarea y panel: una más nueva del mismo panel la reemplaza, y detener o interrumpir la tarea la descarta. Listar los paneles, las escrituras y Enviar a la tarea funcionan en todos los agentes.

AgenteLecturasEscrituras y Enviar a la tarea
Claude CodeEn el turno (aplazadas en modo plan)Funcionan
CodexAplazadas al siguiente mensajeFuncionan
CursorAplazadas al siguiente mensajeFuncionan
OpenCode 1 y 2En el turnoFuncionan
Antigravity (agy y su runtime ACP)En el turnoFuncionan
ConsejoDale a cada lectura importante una forma de enviarse a mano: ofrece el mismo contenido con deska.agent.offer, para que alguien con cualquier agente pueda pulsar Enviar a la tarea.

Lo que la pausa no detiene

La pausa limita lo que el contenido de un panel puede hacer en un turno. No lo detiene todo:

  • Una regla que un agente guardó de una respuesta Permitir siempre en esta sesión dada antes de la pausa sigue valiendo dentro de ese agente. Deska no puede verla ni quitarla.
  • Un subagente de Claude Code que ya corre cuando empieza la pausa conserva su propio modo.
  • Lo que cada agente deja sin preguntar en el modo que pregunta lo decide ese agente. Codex y Cursor aplican ese modo desde el siguiente turno, por eso sus lecturas se aplazan.
  • El runtime ACP de Antigravity rechaza una lectura fuera de la carpeta de la tarea en lugar de preguntar por ella.
  • Un nombre de host que tu página escribe en una sugerencia de recursos todavía puede llegar al resolvedor DNS, llevando como mucho el nombre.
  • El texto dibujado dentro de una imagen todavía puede llevar instrucciones.

Pruebas

  • Carga la carpeta en Crear un panel, abre una tarea supervisada del mismo proyecto en esta computadora y pulsa Conectar a una tarea en la franja del panel. Deska hace la misma pregunta que les hará a quienes lo instalen.
  • Pídele al agente que liste los paneles conectados a su tarea, y luego que llame cada acción.
  • La página del panel en la Biblioteca cuenta, en Lo que hizo en esta computadora, cada acción que llamó una tarea y cada llamada que Deska rechazó, con el motivo.
  • Cuando Deska rechaza una llamada, el agente recibe una frase fija de Deska con un código (como not_connected, invalid_input o panel_timeout), nunca el texto de tu página.

Consulta también Paneles conectados a una tarea, lo que ve la persona.

Límites

LímiteValor
Acciones16 por paquete
Nombre de una acción40 caracteres
Descripción300 caracteres en una acción, 200 en un nodo de esquema
Profundidad de un esquema4 niveles
Propiedades32 por objeto, cada nombre de hasta 40 letras, dígitos y guiones bajos
Texto4,000 caracteres por texto
enumDe 1 a 50 valores, cada uno de 1 a 64 letras, dígitos, espacios, puntos, guiones bajos o guiones
Arreglo100 elementos
Lista8 KB para todos los paneles conectados a la tarea juntos
Respuesta30 segundos; una lectura responde como máximo 65,536 caracteres de JSON
Imágenes10 por llamada o respuesta; entrada: 10 MB; salida: recodificada a 2,048 píxeles por lado
Oferta8,000 caracteres y 6 imágenes
Contadores de revisiónLos últimos 256 por tarea y panel