Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 63 additions & 5 deletions src/content/docs-es/notifications.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ Puedes tener hasta 10 canales. Cada uno envía el mensaje en el formato que espe

| Tipo | Lo que recibes |
|---|---|
| **Slack** | Un mensaje con el título como enlace al portal, una línea con el evento, el estado del run y el código de error, y el resumen del run cuando lo tiene. |
| **Microsoft Teams** | Una adaptive card con el título, el evento, el estado y el código de error, el resumen y un botón **Open in NaN**. |
| **Discord** | Un embed con el título como enlace, el evento, el estado y el código de error, y el resumen. Nunca menciona a nadie. |
| **Slack** | Un mensaje con el título como enlace al portal, el resumen del run cuando lo tiene, un botón al portal (**View run**, **Open automation** o **View triggers**) y una línea con el evento, el estado del run y el código de error. Una aprobación añade sus detalles y un botón verde **Review and approve**: mira [Mensajes de aprobación](#mensajes-de-aprobación). |
| **Microsoft Teams** | Una adaptive card con el título, el evento, el estado y el código de error, el resumen y un botón al portal: **Review and approve** en una aprobación, **View run**, **Open automation** o **View triggers** en los demás (los resúmenes y los mensajes de prueba mantienen **Open in NaN**). Una aprobación añade sus detalles: mira [Mensajes de aprobación](#mensajes-de-aprobación). |
| **Discord** | Un embed con el título como enlace, el evento, el estado y el código de error, el resumen y un enlace en negrita al portal (los webhooks de Discord no pueden mostrar botones). Una aprobación añade sus detalles: mira [Mensajes de aprobación](#mensajes-de-aprobación). Nunca menciona a nadie. |
| **HTTPS** | Un `POST` JSON firmado a tu endpoint, con el [esquema documentado](#contenido-del-mensaje). |

### Consigue la URL de un webhook de Slack
Expand Down Expand Up @@ -80,6 +80,19 @@ Un run cancelado nunca notifica, ni tampoco un run que lanzas tú (fuera de una

El mismo evento se envía una sola vez por canal, aunque el canal esté suscrito a él para todas las automatizaciones y para esa en concreto.

### Mensajes de aprobación

Un mensaje `approval.pending` te dice qué está esperando, para que decidas si abrirlo ya:

- la automatización y su workspace;
- cuándo caduca la aprobación, como hora UTC y cuánto falta, por ejemplo `2026-10-15 10:00 UTC (in 24 h)`;
- los tipos de acción que la automatización manda a aprobación;
- hasta 5 acciones que esperan aprobación, cada una con su tipo y su destino: `github_pr_comment on acme/api#342`, `git_push to branch fix/login`, `github_add_labels on acme/api#12: 2 labels`. Si hay más, la lista termina con `and 3 more`.

Su botón **Review and approve** (un enlace en negrita en Discord) abre el run en el portal con el foco en las acciones propuestas: el enlace termina en `?approval=1` y sigue funcionando si antes tienes que iniciar sesión. Allí revisas las acciones completas, con el texto de los comentarios y el diff, y pulsas **Approve and apply** o **Reject**. Nunca se aprueba desde Slack, Teams o Discord: el mensaje solo te lleva al portal.

Si la aprobación ya se decidió o caducó antes de enviar el mensaje, el mensaje dice que ya no está pendiente y el botón pasa a ser **View run**. La página del run muestra lo mismo: lo que pasó, sin Approve ni Reject.

## Gestiona los canales en el portal

Tus canales están en los ajustes de [cloud.nan.builders](https://cloud.nan.builders/): abre **Settings** y luego **Notifications**, justo debajo de **Secrets**. Los ajustes de cada automatización también tienen una tarjeta **Notifications** que lista los canales que la cubren, con **Manage notifications** para venir aquí.
Expand Down Expand Up @@ -239,9 +252,13 @@ Un canal **HTTPS** recibe peticiones `POST` con `Content-Type: application/json`
"phase": "propose",
"summary": "Reviewed PR #42: two issues found in pkg/foo."
},
"workspace": { "id": "…", "name": "backend-ws" },
"trigger": null,
"reason": null,
"approval": null,
"url": "https://cloud.nan.builders/workspaces/…/runs/…",
"actions_url": "https://cloud.nan.builders/workspaces/…/runs/…",
"approve_url": null,
"text": "Run failed: PR review (agent_failed)"
}
```
Expand All @@ -255,9 +272,49 @@ Un canal **HTTPS** recibe peticiones `POST` con `Content-Type: application/json`
| `run.summary` | El resumen del run tal como lo muestra el portal, con los secretos ya enmascarados y sin caracteres de control, como mucho 280 caracteres. Solo en `run.finished` y `run.failed`. |
| `trigger` | `{ "id", "kind" }` en `trigger.failing`. |
| `reason` | `automation.disabled`: `member`, `workspace_deleted` o `secret_deleted`. `trigger.failing`: `bad_signature`. |
| `workspace` | En los eventos de run (`approval.pending`, `run.failed`, `run.finished`): `{ "id", "name" }` del workspace del run (`name` es `null` si el workspace ya no existe). Si no, `null`. |
| `url` | Dónde mirar en el portal: el run en los eventos de run, la automatización en los demás. |
| `actions_url` | Adónde lleva el botón de los mensajes de Slack, Teams y Discord: el run con el foco en sus acciones propuestas (`?approval=1`) en `approval.pending`, el run en `run.failed` y `run.finished`, la automatización en `automation.disabled` y sus triggers en `trigger.failing`. |
| `approve_url` | En `approval.pending`, mientras la aprobación sigue esperando: el mismo enlace que `actions_url`. Si no, `null`. Se aprueba en el portal, nunca con una petición a este enlace. |
| `approval` | En `approval.pending`: `state`, `expires_at`, `approval_kinds`, `actions` y `actions_total`. Si no, `null`. |
| `approval.state` | `pending` mientras espera tu decisión. Si el mensaje sale cuando la aprobación ya se decidió o caducó: `approved`, `rejected`, `cancelled` o `expired`, y `approve_url` es `null`. |
| `approval.expires_at` | Cuándo caduca la aprobación (UTC), o `null` si no caduca. |
| `approval.approval_kinds` | Los tipos de acción que la automatización manda a aprobación. |
| `approval.actions` | Hasta 5 acciones que esperan aprobación: `kind`, `target` (`owner/repo#number`, la rama de un `git_push`, el repositorio de un `github_create_pr`) y un `text` de una línea. Nunca el texto de un comentario, un título, un mensaje de commit, una etiqueta ni el diff. |
| `approval.actions_total` | Cuántas acciones esperan aprobación en total. Puede ser mayor que la longitud de `actions`, que se queda en 5. |
| `text` | Un título de una línea, en inglés, por ejemplo `Approval needed: PR review`, `Run timed out: PR review` o `Automation disabled: PR review (its workspace was deleted)`. |

El cuerpo de `approval.pending` añade el workspace, la aprobación y los dos enlaces:

```json
{
"version": 1,
"id": "…",
"event": "approval.pending",
"created_at": "2026-10-14T10:00:00Z",
"automation": { "id": "…", "name": "PR review" },
"workspace": { "id": "…", "name": "backend-ws" },
"run": { "id": "…", "state": "awaiting_approval", "error_code": null, "phase": "propose", "summary": null },
"trigger": null,
"reason": null,
"approval": {
"state": "pending",
"expires_at": "2026-10-15T10:00:00Z",
"approval_kinds": ["git_push", "github_pr_comment", "github_add_labels"],
"actions": [
{ "kind": "github_pr_comment", "target": "helmcode/nan-cloud-api#342", "text": "github_pr_comment on helmcode/nan-cloud-api#342" },
{ "kind": "git_push", "target": "nan/fix-tests", "text": "git_push to branch nan/fix-tests" },
{ "kind": "github_add_labels", "target": "helmcode/nan-cloud-api#12", "text": "github_add_labels on helmcode/nan-cloud-api#12: 2 labels" }
],
"actions_total": 3
},
"url": "https://cloud.nan.builders/workspaces/…/runs/…",
"actions_url": "https://cloud.nan.builders/workspaces/…/runs/…?approval=1",
"approve_url": "https://cloud.nan.builders/workspaces/…/runs/…?approval=1",
"text": "Approval needed: PR review"
}
```

Un **resumen** (`digest`) agrupa varias notificaciones:

```json
Expand Down Expand Up @@ -316,9 +373,10 @@ Los mensajes llevan nombres, estados, códigos de error, ids y enlaces. **Nunca*

- el valor de ningún secreto, ni la URL de un canal;
- el cuerpo ni las cabeceras de la petición de webhook que lanzó un run;
- la salida del agente, más allá del resumen corto del run (como mucho 280 caracteres, con los secretos enmascarados).
- la salida del agente, más allá del resumen corto del run (como mucho 280 caracteres, con los secretos enmascarados);
- el texto, el título ni el mensaje de commit de las acciones propuestas, sus etiquetas ni el diff: una aprobación solo lista tipos y destinos.

El texto que escribes tú (el nombre de una automatización, un resumen) se escapa para cada proveedor: no puede mencionar a nadie, avisar a un rol ni formar un enlace.
El texto que escribes tú (el nombre de una automatización o de un workspace, un resumen, una rama) se escapa para cada proveedor: no puede mencionar a nadie, avisar a un rol ni formar un enlace.

### Qué URLs se aceptan

Expand Down
68 changes: 63 additions & 5 deletions src/content/docs/notifications.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ You can have up to 10 channels. Each one sends a message in the format its provi

| Kind | What you get |
|---|---|
| **Slack** | A message with the title as a link to the portal, a line with the event, the run state and the error code, and the run's summary when it has one. |
| **Microsoft Teams** | An adaptive card with the title, the event, state and error code, the summary and an **Open in NaN** button. |
| **Discord** | An embed with the title as a link, the event, state and error code, and the summary. It never mentions anyone. |
| **Slack** | A message with the title as a link to the portal, the run's summary when it has one, a button to the portal (**View run**, **Open automation** or **View triggers**) and a line with the event, the run state and the error code. An approval adds its details and a green **Review and approve** button: see [Approval messages](#approval-messages). |
| **Microsoft Teams** | An adaptive card with the title, the event, state and error code, the summary and a button to the portal: **Review and approve** on an approval, **View run**, **Open automation** or **View triggers** on the others (digests and test messages keep **Open in NaN**). An approval adds its details: see [Approval messages](#approval-messages). |
| **Discord** | An embed with the title as a link, the event, state and error code, the summary and a bold link to the portal (Discord webhooks cannot show buttons). An approval adds its details: see [Approval messages](#approval-messages). It never mentions anyone. |
| **HTTPS** | A signed JSON `POST` to your endpoint, with the [documented schema](#payload). |

### Get a Slack webhook URL
Expand Down Expand Up @@ -80,6 +80,19 @@ A cancelled run never notifies, and neither does a run you start yourself (outsi

The same event is sent once per channel, even when the channel is subscribed to it for all automations and for that one.

### Approval messages

An `approval.pending` message tells you what is waiting, so you can decide whether to open it now:

- the automation and its workspace;
- when the approval expires, as a UTC time and how long is left, for example `2026-10-15 10:00 UTC (in 24 h)`;
- the action kinds the automation sends for approval;
- up to 5 actions waiting for approval, each as its kind and target: `github_pr_comment on acme/api#342`, `git_push to branch fix/login`, `github_add_labels on acme/api#12: 2 labels`. When there are more, the list ends with `and 3 more`.

Its **Review and approve** button (a bold link in Discord) opens the run in the portal with the proposed actions in view and focused: the link ends in `?approval=1`, and it still works when you have to sign in first. There you review the full actions, with comment bodies and the diff, and press **Approve and apply** or **Reject**. You never approve from Slack, Teams or Discord: the message only takes you to the portal.

If the approval was decided or expired before the message went out, the message says it is no longer pending and the button becomes **View run**. The run page shows the same: what happened, without Approve or Reject.

## Manage channels in the portal

Your channels are in your settings at [cloud.nan.builders](https://cloud.nan.builders/): open **Settings**, then **Notifications**, right below **Secrets**. Each automation's settings also have a **Notifications** card that lists the channels covering it, with **Manage notifications** to come here.
Expand Down Expand Up @@ -239,9 +252,13 @@ An **HTTPS** channel receives `POST` requests with `Content-Type: application/js
"phase": "propose",
"summary": "Reviewed PR #42: two issues found in pkg/foo."
},
"workspace": { "id": "…", "name": "backend-ws" },
"trigger": null,
"reason": null,
"approval": null,
"url": "https://cloud.nan.builders/workspaces/…/runs/…",
"actions_url": "https://cloud.nan.builders/workspaces/…/runs/…",
"approve_url": null,
"text": "Run failed: PR review (agent_failed)"
}
```
Expand All @@ -255,9 +272,49 @@ An **HTTPS** channel receives `POST` requests with `Content-Type: application/js
| `run.summary` | The run's summary as the portal shows it, with secrets already masked and control characters removed, at most 280 characters. Only on `run.finished` and `run.failed`. |
| `trigger` | `{ "id", "kind" }` on `trigger.failing`. |
| `reason` | `automation.disabled`: `member`, `workspace_deleted` or `secret_deleted`. `trigger.failing`: `bad_signature`. |
| `workspace` | On run events (`approval.pending`, `run.failed`, `run.finished`): `{ "id", "name" }` of the run's workspace (`name` is `null` when the workspace no longer exists). `null` otherwise. |
| `url` | Where to look in the portal: the run for run events, the automation for the others. |
| `actions_url` | Where the button of the Slack, Teams and Discord messages goes: the run with its proposed actions focused (`?approval=1`) on `approval.pending`, the run on `run.failed` and `run.finished`, the automation on `automation.disabled`, and its triggers on `trigger.failing`. |
| `approve_url` | On `approval.pending`, while the approval is still waiting: the same link as `actions_url`. `null` otherwise. You approve in the portal, never with a request to this link. |
| `approval` | On `approval.pending`: `state`, `expires_at`, `approval_kinds`, `actions` and `actions_total`. `null` otherwise. |
| `approval.state` | `pending` while it waits for you. When the message goes out after the approval was decided or expired: `approved`, `rejected`, `cancelled` or `expired`, and `approve_url` is `null`. |
| `approval.expires_at` | When the approval expires (UTC), or `null` if it has no expiry. |
| `approval.approval_kinds` | The action kinds the automation sends for approval. |
| `approval.actions` | Up to 5 actions waiting for approval: `kind`, `target` (`owner/repo#number`, the branch of a `git_push`, the repository of a `github_create_pr`) and a one-line `text`. Never a comment body, a title, a commit message, a label or the diff. |
| `approval.actions_total` | How many actions wait for approval in total. It can be larger than the length of `actions`, which stops at 5. |
| `text` | A one-line title, for example `Approval needed: PR review`, `Run timed out: PR review` or `Automation disabled: PR review (its workspace was deleted)`. |

An `approval.pending` body adds the workspace, the approval and both links:

```json
{
"version": 1,
"id": "…",
"event": "approval.pending",
"created_at": "2026-10-14T10:00:00Z",
"automation": { "id": "…", "name": "PR review" },
"workspace": { "id": "…", "name": "backend-ws" },
"run": { "id": "…", "state": "awaiting_approval", "error_code": null, "phase": "propose", "summary": null },
"trigger": null,
"reason": null,
"approval": {
"state": "pending",
"expires_at": "2026-10-15T10:00:00Z",
"approval_kinds": ["git_push", "github_pr_comment", "github_add_labels"],
"actions": [
{ "kind": "github_pr_comment", "target": "helmcode/nan-cloud-api#342", "text": "github_pr_comment on helmcode/nan-cloud-api#342" },
{ "kind": "git_push", "target": "nan/fix-tests", "text": "git_push to branch nan/fix-tests" },
{ "kind": "github_add_labels", "target": "helmcode/nan-cloud-api#12", "text": "github_add_labels on helmcode/nan-cloud-api#12: 2 labels" }
],
"actions_total": 3
},
"url": "https://cloud.nan.builders/workspaces/…/runs/…",
"actions_url": "https://cloud.nan.builders/workspaces/…/runs/…?approval=1",
"approve_url": "https://cloud.nan.builders/workspaces/…/runs/…?approval=1",
"text": "Approval needed: PR review"
}
```

A **digest** groups several notifications:

```json
Expand Down Expand Up @@ -316,9 +373,10 @@ Messages carry names, states, error codes, ids and links. They **never** carry:

- the value of any secret, or a channel URL;
- the body or headers of the webhook request that started a run;
- the agent's output, beyond the run's short summary (at most 280 characters, with secrets masked).
- the agent's output, beyond the run's short summary (at most 280 characters, with secrets masked);
- the bodies, titles and commit messages of proposed actions, their labels or the diff: an approval lists only kinds and targets.

Member text (an automation's name, a summary) is escaped for each provider: it cannot mention anyone, ping a role or form a link.
Member text (an automation's or workspace's name, a summary, a branch) is escaped for each provider: it cannot mention anyone, ping a role or form a link.

### Which URLs are accepted

Expand Down
52 changes: 52 additions & 0 deletions src/tests/fixtures/notifications/discord_approval_pending.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"allowed_mentions": {
"parse": []
},
"content": null,
"embeds": [
{
"color": 16098851,
"description": "**Proposed actions waiting for approval (3)**\n• github\\_pr\\_comment on helmcode/nan\\-cloud\\-api\\#342\n• git\\_push to branch nan/fix\\-tests\n• github\\_add\\_labels on helmcode/nan\\-cloud\\-api\\#12\\: 2 labels",
"fields": [
{
"inline": true,
"name": "Automation",
"value": "PR review"
},
{
"inline": true,
"name": "Workspace",
"value": "backend\\-ws"
},
{
"inline": true,
"name": "Expires",
"value": "2026\\-10\\-15 10\\:00 UTC \\(in 24 h\\)"
},
{
"inline": false,
"name": "Needs approval",
"value": "git\\_push, github\\_pr\\_comment, github\\_add\\_labels"
},
{
"inline": true,
"name": "Event",
"value": "approval.pending"
},
{
"inline": true,
"name": "State",
"value": "awaiting_approval"
},
{
"inline": false,
"name": "Next step",
"value": "**[Review and approve](https://cloud.nan.builders/workspaces/33333333-3333-4333-8333-333333333333/runs/22222222-2222-4222-8222-222222222222?approval=1)**"
}
],
"timestamp": "2026-10-14T10:00:00Z",
"title": "Approval needed: PR review",
"url": "https://cloud.nan.builders/workspaces/33333333-3333-4333-8333-333333333333/runs/22222222-2222-4222-8222-222222222222?approval=1"
}
]
}
5 changes: 5 additions & 0 deletions src/tests/fixtures/notifications/discord_run_failed.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
"inline": true,
"name": "Error",
"value": "agent_failed"
},
{
"inline": false,
"name": "Next step",
"value": "**[View run](https://cloud.nan.builders/workspaces/33333333-3333-4333-8333-333333333333/runs/22222222-2222-4222-8222-222222222222)**"
}
],
"timestamp": "2026-10-14T10:00:00Z",
Expand Down
Loading
Loading