533 lines
16 KiB
Markdown
533 lines
16 KiB
Markdown
# openclaw-action workflow
|
|
|
|
This skill ships an importable workflow at:
|
|
|
|
- `assets/openclaw-action.workflow.json`
|
|
|
|
It implements a real local OpenClaw → n8n router.
|
|
|
|
## What it does
|
|
|
|
- accepts `POST /webhook/openclaw-action`
|
|
- normalizes incoming JSON into an action contract
|
|
- supports these actions in the shipped asset:
|
|
- `append_log`
|
|
- `get_logs`
|
|
- `notify`
|
|
- `send_notification_draft`
|
|
- `send_email_draft`
|
|
- `list_email_drafts`
|
|
- `delete_email_draft`
|
|
- `send_gmail_draft` (alias: `send_approved_email`)
|
|
- `create_calendar_event`
|
|
- `list_upcoming_events`
|
|
- `update_calendar_event`
|
|
- `delete_calendar_event`
|
|
- `approval_queue_add`
|
|
- `approval_queue_list`
|
|
- `approval_queue_resolve`
|
|
- `approval_history_attach_execution`
|
|
- `fetch_and_normalize_url`
|
|
- `inbound_event_filter`
|
|
- returns normalized JSON responses
|
|
- returns `400` for unknown actions
|
|
- returns `400` when required args are missing
|
|
|
|
## Current side effects
|
|
|
|
### `append_log`
|
|
|
|
- appends records into workflow static data under key:
|
|
- `actionLog`
|
|
- keeps the most recent `200` entries
|
|
- persists in n8n's database when the workflow execution succeeds
|
|
|
|
Example stored record:
|
|
|
|
```json
|
|
{"ts":"2026-03-12T07:00:00Z","source":"openclaw-action","request_id":"abc","text":"backup complete"}
|
|
```
|
|
|
|
### `send_notification_draft`
|
|
|
|
- queues an approval-gated notification proposal into workflow static data under key:
|
|
- `approvalQueue`
|
|
- when resolved with `decision=approve`, it executes the existing `notify` path and sends through Telegram + Discord
|
|
- uses only the already-configured notification credentials in the live n8n instance
|
|
|
|
### Gmail + Calendar approval-gated actions
|
|
|
|
Actions:
|
|
- `send_email_draft`
|
|
- `list_email_drafts`
|
|
- `delete_email_draft`
|
|
- `send_gmail_draft` (alias: `send_approved_email`)
|
|
- `create_calendar_event`
|
|
- `list_upcoming_events`
|
|
- `update_calendar_event`
|
|
- `delete_calendar_event`
|
|
|
|
Behavior:
|
|
- queue proposals into workflow static data under key:
|
|
- `approvalQueue`
|
|
- keep the most recent `200` pending entries
|
|
- return explicit approval policy metadata per action (`approval.policy`, `approval.required`, `approval.mutation_level`)
|
|
- do **not** execute Gmail/Calendar side effects directly in the shipped starter workflow
|
|
- are intended for host-side execution via the included `gog` bridge after explicit approval resolution
|
|
|
|
Approval policy defaults by action family:
|
|
- notification family
|
|
- `send_notification_draft`
|
|
- `approval.family = "notification"`
|
|
- `approval.required = true`
|
|
- `approval.mutation_level = "high"`
|
|
- approved items execute inline in n8n via the existing `notify` path
|
|
- Gmail family
|
|
- read-only: `list_email_drafts`
|
|
- `approval.family = "gmail"`
|
|
- `approval.required = true`
|
|
- `approval.mutation_level = "low"`
|
|
- still queued so operators must explicitly acknowledge host-side Gmail reads
|
|
- mutating: `send_email_draft`, `delete_email_draft`, `send_gmail_draft` / `send_approved_email`
|
|
- `approval.family = "gmail"`
|
|
- `approval.required = true`
|
|
- `approval.mutation_level = "high"`
|
|
- Calendar family
|
|
- read-only: `list_upcoming_events`
|
|
- `approval.family = "calendar"`
|
|
- `approval.required = true`
|
|
- `approval.mutation_level = "low"`
|
|
- mutating: `create_calendar_event`, `update_calendar_event`, `delete_calendar_event`
|
|
- `approval.family = "calendar"`
|
|
- `approval.required = true`
|
|
- `approval.mutation_level = "high"`
|
|
- manual/generic approvals
|
|
- `approval_queue_add`
|
|
- no automatic side effect is implied; the operator decides what the queued item means
|
|
|
|
Queue/history entries now also carry compact operator-facing fields for low-noise review:
|
|
- `approvalQueue[].payload_preview`
|
|
- `approvalQueue[].operator.summary_line`
|
|
- `approvalHistory[].operator.execution_state`
|
|
- `approvalHistory[].operator.result_refs`
|
|
|
|
### `approval_queue_resolve`
|
|
|
|
- removes one item from `approvalQueue`
|
|
- appends the resolved entry into:
|
|
- `approvalHistory`
|
|
- supports optional notification on approval/rejection
|
|
- executes notification drafts inline when the approved item kind is `notification`
|
|
- returns both the full resolved item and a compact operator view at:
|
|
- `result.item_compact`
|
|
|
|
### `approval_history_attach_execution`
|
|
|
|
- patches an existing resolved history item in `approvalHistory`
|
|
- designed for host-side executors that run outside n8n itself
|
|
- used by the included `scripts/resolve-approval-with-gog.py` bridge to attach Gmail/Calendar execution results
|
|
- the updated history entry now includes low-noise operator metadata such as:
|
|
- `operator.summary_line`
|
|
- `operator.execution_state`
|
|
- `operator.result_refs`
|
|
- `execution.summary`
|
|
- returns both the full item and `result.item_compact`
|
|
|
|
### `fetch_and_normalize_url`
|
|
|
|
- fetches a remote `http` or `https` URL from inside n8n
|
|
- normalizes HTML/text/JSON into a single response shape
|
|
- returns title/excerpt/body text suitable for downstream summarization or logging
|
|
- uses n8n's runtime HTTP helper inside the Code node rather than relying on global `fetch`
|
|
- supports optional arg `skip_ssl_certificate_validation: true` for runtimes with incomplete CA trust
|
|
|
|
### `inbound_event_filter`
|
|
|
|
- classifies inbound events as `urgent`, `important`, `watch`, or `deduped`
|
|
- stores recent events in:
|
|
- `inboundEvents`
|
|
- stores recent dedupe keys in:
|
|
- `eventDedup`
|
|
- can fan out a notification for urgent/important non-duplicate events
|
|
|
|
### `notify`
|
|
|
|
- sends a Telegram message using credential:
|
|
- `Telegram Bot (OpenClaw)`
|
|
- sends a Discord message using credential:
|
|
- `Discord Bot Auth`
|
|
- current targets mirror the already-working reminder workflow
|
|
|
|
## Why workflow static data first
|
|
|
|
Why this first:
|
|
- built-in, no extra credentials
|
|
- persists without guessing writable filesystem paths
|
|
- good fit for queues, recent breadcrumbs, and small operational state
|
|
- lets us implement safe approval-gated patterns immediately
|
|
|
|
When to add provider-backed steps later:
|
|
- email draft creation in Gmail/Outlook
|
|
- calendar writes in Google Calendar
|
|
- Airtable/Sheets append pipelines
|
|
- long-retention logs or external archival
|
|
|
|
## Intentional security choice
|
|
|
|
The exported workflow leaves the Webhook node auth unset in the JSON file.
|
|
|
|
Why:
|
|
- n8n credentials are instance-local
|
|
- secrets should not be embedded in a shareable skill asset
|
|
|
|
After import, set this manually in n8n:
|
|
|
|
- Webhook node → **Authentication** → `Header Auth`
|
|
- bind a credential with:
|
|
- header name: `x-openclaw-secret`
|
|
- header value: your generated shared secret
|
|
|
|
## Import steps
|
|
|
|
1. In n8n, create or open a workflow.
|
|
2. Import `assets/openclaw-action.workflow.json`.
|
|
3. Open the **Webhook** node.
|
|
4. Set **Authentication** to `Header Auth`.
|
|
5. Bind your local credential.
|
|
6. Save.
|
|
7. Use **Listen for test event** and call the test URL first.
|
|
8. Once successful, activate the workflow for production URL use.
|
|
|
|
## Expected URLs
|
|
|
|
- test: `http://192.168.153.113:18808/webhook-test/openclaw-action`
|
|
- prod: `http://192.168.153.113:18808/webhook/openclaw-action`
|
|
|
|
## Test payloads included
|
|
|
|
- `assets/test-append-log.json`
|
|
- `assets/test-notify.json`
|
|
- `assets/test-send-notification-draft.json`
|
|
- `assets/test-send-email-draft.json`
|
|
- `assets/test-list-email-drafts.json`
|
|
- `assets/test-delete-email-draft.json`
|
|
- `assets/test-send-gmail-draft.json`
|
|
- `assets/test-send-approved-email.json`
|
|
- `assets/test-create-calendar-event.json`
|
|
- `assets/test-list-upcoming-events.json`
|
|
- `assets/test-update-calendar-event.json`
|
|
- `assets/test-delete-calendar-event.json`
|
|
- `assets/test-verify-email-draft-cycle.json`
|
|
- `assets/test-verify-calendar-event-cycle.json`
|
|
- `assets/test-fetch-and-normalize-url.json`
|
|
- `assets/test-approval-queue-list.json`
|
|
- `assets/test-inbound-event-filter.json`
|
|
|
|
## Example tests
|
|
|
|
```bash
|
|
export N8N_WEBHOOK_SECRET='YOUR_SECRET_HERE'
|
|
scripts/call-action.sh append_log --args '{"text":"backup complete"}' --pretty
|
|
scripts/call-action.sh get_logs --args '{"limit":5}' --pretty
|
|
scripts/call-action.sh notify --args '{"title":"Workflow finished","message":"n8n router test"}' --pretty
|
|
scripts/call-action.sh send_notification_draft --args-file assets/test-send-notification-draft.json --pretty
|
|
scripts/call-action.sh send_email_draft --args-file assets/test-send-email-draft.json --pretty
|
|
scripts/call-action.sh list_email_drafts --args-file assets/test-list-email-drafts.json --pretty
|
|
scripts/call-action.sh delete_email_draft --args-file assets/test-delete-email-draft.json --pretty
|
|
scripts/call-action.sh send_gmail_draft --args-file assets/test-send-gmail-draft.json --pretty
|
|
scripts/call-action.sh send_approved_email --args-file assets/test-send-approved-email.json --pretty
|
|
scripts/call-action.sh create_calendar_event --args-file assets/test-create-calendar-event.json --pretty
|
|
scripts/call-action.sh list_upcoming_events --args-file assets/test-list-upcoming-events.json --pretty
|
|
scripts/call-action.sh update_calendar_event --args-file assets/test-update-calendar-event.json --pretty
|
|
scripts/call-action.sh delete_calendar_event --args-file assets/test-delete-calendar-event.json --pretty
|
|
scripts/call-action.sh --args-file assets/test-verify-email-draft-cycle.json --pretty
|
|
scripts/call-action.sh --args-file assets/test-verify-calendar-event-cycle.json --pretty
|
|
scripts/call-action.sh fetch_and_normalize_url --args '{"url":"http://192.168.153.113:18808/healthz"}' --pretty
|
|
scripts/call-action.sh fetch_and_normalize_url --args '{"url":"https://example.com","skip_ssl_certificate_validation":true}' --pretty
|
|
scripts/call-action.sh approval_queue_list --args '{"limit":10,"include_history":true}' --pretty
|
|
scripts/call-action.sh inbound_event_filter --args-file assets/test-inbound-event-filter.json --pretty
|
|
python3 scripts/resolve-approval-with-gog.py --id <approval-id> --decision approve --dry-run
|
|
```
|
|
|
|
## Operator command reference
|
|
|
|
Common approval flows:
|
|
|
|
```bash
|
|
# 1) inspect the queue with both full and compact views
|
|
scripts/call-action.sh approval_queue_list --args '{"limit":10,"include_history":true}' --pretty
|
|
|
|
# 2) reject an item without host-side execution
|
|
scripts/call-action.sh approval_queue_resolve \
|
|
--args '{"id":"approval-abc123","decision":"reject","note":"not safe to run"}' \
|
|
--pretty
|
|
|
|
# 3) approve through the host bridge, but keep it side-effect free
|
|
python3 scripts/resolve-approval-with-gog.py --id approval-abc123 --decision approve --dry-run
|
|
|
|
# 4) approve for real through the host bridge
|
|
python3 scripts/resolve-approval-with-gog.py --id approval-abc123 --decision approve
|
|
|
|
# 5) re-check recent history in compact form
|
|
scripts/call-action.sh approval_queue_list --args '{"limit":5,"include_history":true}' --pretty
|
|
```
|
|
|
|
What to look for in low-noise history output:
|
|
- `result.pending_compact[]` and `result.history_compact[]`
|
|
- `summary_line` for a one-line operator digest
|
|
- `execution_state` for `pending`, `awaiting_host_execution`, `dry_run`, `executed`, or `failed`
|
|
- `result_refs` for durable IDs such as `draft_id`, `message_id`, or `event_id`
|
|
|
|
## Canned recurring verification flows
|
|
|
|
### Gmail draft queue → approve → verify → cleanup
|
|
|
|
1. Queue the canned payload:
|
|
|
|
```bash
|
|
scripts/call-action.sh --args-file assets/test-verify-email-draft-cycle.json --pretty
|
|
```
|
|
|
|
2. Find the new approval id from `pending_compact`.
|
|
3. Approve with the host bridge:
|
|
|
|
```bash
|
|
python3 scripts/resolve-approval-with-gog.py --id <approval-id> --decision approve
|
|
```
|
|
|
|
4. Re-run `approval_queue_list` and confirm the matching history item shows:
|
|
- `execution_state = "executed"`
|
|
- `result_refs.draft_id` populated
|
|
5. Cleanup by queueing `assets/test-delete-email-draft.json` with the returned draft id and approving that item.
|
|
|
|
### Calendar event queue → approve → verify → cleanup
|
|
|
|
1. Queue the canned payload:
|
|
|
|
```bash
|
|
scripts/call-action.sh --args-file assets/test-verify-calendar-event-cycle.json --pretty
|
|
```
|
|
|
|
2. Approve with the host bridge:
|
|
|
|
```bash
|
|
python3 scripts/resolve-approval-with-gog.py --id <approval-id> --decision approve
|
|
```
|
|
|
|
3. Re-run `approval_queue_list` and confirm the matching history item shows:
|
|
- `execution_state = "executed"`
|
|
- `result_refs.event_id` populated
|
|
4. Cleanup by queueing `assets/test-delete-calendar-event.json` with the returned `event_id` and approving that item.
|
|
|
|
## Expected success examples
|
|
|
|
### send_notification_draft
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"request_id": "test-notify-draft-001",
|
|
"result": {
|
|
"action": "send_notification_draft",
|
|
"status": "queued_for_approval",
|
|
"pending_id": "approval-abc123",
|
|
"approval_status": "pending"
|
|
}
|
|
}
|
|
```
|
|
|
|
### send_email_draft
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"request_id": "test-email-draft-001",
|
|
"result": {
|
|
"action": "send_email_draft",
|
|
"status": "queued_for_approval",
|
|
"pending_id": "approval-abc123",
|
|
"approval_status": "pending"
|
|
}
|
|
}
|
|
```
|
|
|
|
### list_email_drafts
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"request_id": "test-list-email-drafts-001",
|
|
"result": {
|
|
"action": "list_email_drafts",
|
|
"status": "queued_for_approval",
|
|
"pending_id": "approval-ghi789",
|
|
"approval_status": "pending",
|
|
"approval": {
|
|
"policy": "approval_queue_resolve",
|
|
"required": true,
|
|
"mutation_level": "low"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### delete_email_draft
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"request_id": "test-delete-email-draft-001",
|
|
"result": {
|
|
"action": "delete_email_draft",
|
|
"status": "queued_for_approval",
|
|
"pending_id": "approval-jkl012",
|
|
"approval_status": "pending"
|
|
}
|
|
}
|
|
```
|
|
|
|
### send_gmail_draft / send_approved_email
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"request_id": "test-send-gmail-draft-001",
|
|
"result": {
|
|
"action": "send_gmail_draft",
|
|
"requested_action": "send_gmail_draft",
|
|
"status": "queued_for_approval",
|
|
"pending_id": "approval-mno345",
|
|
"approval_status": "pending"
|
|
}
|
|
}
|
|
```
|
|
|
|
### create_calendar_event
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"request_id": "test-calendar-event-001",
|
|
"result": {
|
|
"action": "create_calendar_event",
|
|
"status": "queued_for_approval",
|
|
"pending_id": "approval-def456",
|
|
"approval_status": "pending"
|
|
}
|
|
}
|
|
```
|
|
|
|
### list_upcoming_events
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"request_id": "test-list-calendar-events-001",
|
|
"result": {
|
|
"action": "list_upcoming_events",
|
|
"status": "queued_for_approval",
|
|
"pending_id": "approval-pqr678",
|
|
"approval_status": "pending",
|
|
"approval": {
|
|
"policy": "approval_queue_resolve",
|
|
"required": true,
|
|
"mutation_level": "low"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### update_calendar_event
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"request_id": "test-update-calendar-event-001",
|
|
"result": {
|
|
"action": "update_calendar_event",
|
|
"status": "queued_for_approval",
|
|
"pending_id": "approval-stu901",
|
|
"approval_status": "pending"
|
|
}
|
|
}
|
|
```
|
|
|
|
### delete_calendar_event
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"request_id": "test-delete-calendar-event-001",
|
|
"result": {
|
|
"action": "delete_calendar_event",
|
|
"status": "queued_for_approval",
|
|
"pending_id": "approval-vwx234",
|
|
"approval_status": "pending"
|
|
}
|
|
}
|
|
```
|
|
|
|
### fetch_and_normalize_url
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"request_id": "test-fetch-001",
|
|
"result": {
|
|
"action": "fetch_and_normalize_url",
|
|
"status": "ok",
|
|
"url": "http://192.168.153.113:18808/healthz",
|
|
"title": "",
|
|
"content_type": "application/json; charset=utf-8"
|
|
}
|
|
}
|
|
```
|
|
|
|
### inbound_event_filter
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"request_id": "test-inbound-001",
|
|
"result": {
|
|
"action": "inbound_event_filter",
|
|
"status": "stored",
|
|
"classification": "urgent",
|
|
"duplicate": false,
|
|
"notified": true
|
|
}
|
|
}
|
|
```
|
|
|
|
## Host bridge notes
|
|
|
|
The included host bridge `scripts/resolve-approval-with-gog.py` is for the case where Gmail/Calendar auth exists on the OpenClaw host via `gog`, not inside n8n itself.
|
|
|
|
Behavior:
|
|
- resolves an approval item through `openclaw-action`
|
|
- executes supported kinds on the host:
|
|
- `email_draft` → `gog gmail drafts create`
|
|
- `email_list_drafts` → `gog gmail drafts list`
|
|
- `email_draft_delete` → `gog gmail drafts delete`
|
|
- `email_draft_send` → `gog gmail drafts send`
|
|
- `calendar_event` → `gog calendar create`
|
|
- `calendar_list_events` → `gog calendar events`
|
|
- `calendar_event_update` → `gog calendar update`
|
|
- `calendar_event_delete` → `gog calendar delete`
|
|
- writes execution metadata back via `approval_history_attach_execution`
|
|
|
|
Important automation note:
|
|
- real unattended execution needs `GOG_KEYRING_PASSWORD` available to the executor
|
|
- the included bridge auto-loads `/home/openclaw/.openclaw/credentials/gog.env` when present
|
|
- keep that file mode `600` if you use it for `GOG_ACCOUNT` / `GOG_KEYRING_PASSWORD`
|
|
- without the password, non-TTY `gog` calls will fail when the file keyring tries to prompt
|
|
- `--dry-run` works without touching Google state and is useful for plumbing verification
|
|
|
|
## Validation
|
|
|
|
Run the local validator before import/package changes:
|
|
|
|
```bash
|
|
python3 scripts/validate-workflow.py assets/openclaw-action.workflow.json
|
|
```
|