diff --git a/memory/2026-03-12.md b/memory/2026-03-12.md index 675bbe6..0288ff8 100644 --- a/memory/2026-03-12.md +++ b/memory/2026-03-12.md @@ -9,4 +9,4 @@ - `openclaw-ping` webhook path tested end-to-end - Operating note: prefer narrow webhook-first integration rather than broad n8n admin/API access. - Will clarified the primary host LAN IP to use/document is `192.168.153.113`. -- Drafted local skill `skills/n8n-webhook` for authenticated webhook-first n8n integration, including `scripts/call-webhook.sh`, payload notes, and a successful package/validation run to `/tmp/n8n-skill-dist/n8n-webhook.skill`. +- Drafted local skill `skills/n8n-webhook` for authenticated webhook-first n8n integration, including `scripts/call-webhook.sh`, `scripts/call-action.sh`, payload notes, and a successful package/validation run to `/tmp/n8n-skill-dist/n8n-webhook.skill`. diff --git a/skills/n8n-webhook/SKILL.md b/skills/n8n-webhook/SKILL.md index 3012f07..ba4195b 100644 --- a/skills/n8n-webhook/SKILL.md +++ b/skills/n8n-webhook/SKILL.md @@ -1,6 +1,6 @@ --- name: n8n-webhook -description: Trigger authenticated local n8n webhooks on the LAN for OpenClaw-to-n8n integration. Use when calling safe, narrow workflows on the dedicated local n8n-agent instance, such as ping/test endpoints, action-bus style workflows, notifications, logging, or other preapproved webhook entrypoints. Do not use for broad n8n admin/API management, workflow mutation, credential management, or unrestricted orchestration. +description: Trigger authenticated local n8n webhooks on the LAN for OpenClaw-to-n8n integration. Use when calling safe, narrow workflows on the dedicated local n8n-agent instance, such as ping/test endpoints, an action-bus style router workflow, notifications, logging, or other preapproved webhook entrypoints. Do not use for broad n8n admin/API management, workflow mutation, credential management, or unrestricted orchestration. --- # N8n Webhook @@ -15,11 +15,12 @@ Keep the integration narrow: let OpenClaw decide what to do, and let n8n execute ## Policy 1. Prefer named webhook entrypoints over generic admin APIs. -2. Send JSON and expect JSON back. -3. Use header auth by default (`x-openclaw-secret`). -4. Use `/webhook-test/` only while building/editing a workflow. -5. Surface non-2xx responses clearly instead of pretending success. -6. If a new workflow is needed, define its request/response contract before wiring callers. +2. Prefer one small router webhook (`openclaw-action`) when several agent-safe actions are needed. +3. Send JSON and expect JSON back. +4. Use header auth by default (`x-openclaw-secret`). +5. Use `/webhook-test/` only while building/editing a workflow. +6. Surface non-2xx responses clearly instead of pretending success. +7. If a new workflow is needed, define its request/response contract before wiring callers. ## Quick usage @@ -29,40 +30,56 @@ Set the shared secret once for the shell session: export N8N_WEBHOOK_SECRET='replace-me' ``` -Call a production webhook: +Call a production webhook directly: ```bash scripts/call-webhook.sh openclaw-ping --data '{"message":"hello from OpenClaw"}' ``` +Call the preferred action-bus route: + +```bash +scripts/call-action.sh append_log --args '{"text":"backup complete"}' +``` + Call a test webhook while editing a flow: ```bash -scripts/call-webhook.sh openclaw-ping --test --data '{"message":"hello from OpenClaw"}' -``` - -Pretty-print JSON response: - -```bash -scripts/call-webhook.sh openclaw-ping --pretty --data '{"message":"hello"}' +scripts/call-action.sh notify --args '{"message":"hello from OpenClaw"}' --test --pretty ``` ## Workflow -### Call an existing safe webhook +### Call an existing safe webhook directly -1. Confirm the target webhook path is already intended for agent use. -2. Use `scripts/call-webhook.sh` with JSON input. -3. Treat any non-2xx response as a failure that needs investigation. +Use `scripts/call-webhook.sh` when the path is already defined and there is no benefit to the action-bus wrapper. -Current known endpoint: +Current known direct endpoint: - `openclaw-ping` — basic end-to-end connectivity check +### Call the action bus + +Use `scripts/call-action.sh` when the n8n side exposes a router webhook such as `openclaw-action`. + +Payload shape: + +```json +{ + "action": "append_log", + "args": { + "text": "backup complete" + }, + "request_id": "optional-uuid" +} +``` + +This keeps the external surface small while letting n8n route internally. + ### Add a new webhook-backed capability 1. Write down the webhook path, required auth, request JSON, and response JSON. -2. If the shape is more than trivial, read `references/payloads.md` first. +2. If the path should become part of the shared action bus, document the `action` name and `args` shape in `references/payloads.md`. 3. Keep the first version small and explicit. 4. Only add the new endpoint to regular use after a successful `/webhook-test/` run. @@ -71,8 +88,10 @@ Current known endpoint: - `N8N_BASE_URL` — override base URL (default `http://192.168.153.113:18808`) - `N8N_WEBHOOK_SECRET` — required shared secret for authenticated calls - `N8N_SECRET_HEADER` — header name (default `x-openclaw-secret`) +- `N8N_ACTION_PATH` — router path for `call-action.sh` (default `openclaw-action`) ## Resources -- `scripts/call-webhook.sh` — authenticated POST helper for local n8n webhooks -- `references/payloads.md` — suggested request/response contracts and naming conventions +- `scripts/call-webhook.sh` — authenticated POST helper for direct local n8n webhooks +- `scripts/call-action.sh` — wrapper for action-bus style calls against `openclaw-action` +- `references/payloads.md` — request/response contracts and naming conventions diff --git a/skills/n8n-webhook/references/payloads.md b/skills/n8n-webhook/references/payloads.md index 8b3a4a5..8cd47ca 100644 --- a/skills/n8n-webhook/references/payloads.md +++ b/skills/n8n-webhook/references/payloads.md @@ -25,52 +25,88 @@ Recommended success response: } ``` -## Recommended contract for new endpoints +## Preferred router endpoint -Prefer a stable JSON shape like: +### `openclaw-action` + +Purpose: +- keep the external n8n surface small +- route several agent-safe operations behind one authenticated webhook + +Recommended request shape: + +```json +{ + "action": "append_log", + "args": { + "text": "backup complete" + }, + "request_id": "optional-uuid" +} +``` + +Recommended success response: ```json { "ok": true, "request_id": "optional-uuid", "result": { - "...": "workflow-specific payload" + "status": "accepted" } } ``` -On failure, return a non-2xx status plus: +Recommended failure response: ```json { "ok": false, "error": { - "code": "short-machine-code", - "message": "human-readable summary" + "code": "unknown_action", + "message": "action is not supported" } } ``` -## Naming guidance +## Suggested initial actions -- Use lowercase kebab-case webhook paths. -- Keep names explicit: `openclaw-ping`, `openclaw-action`, `append-log`, `send-notify`. -- Avoid generic names like `run`, `task`, or `webhook1`. +### `append_log` -## Suggested action-bus pattern - -If multiple agent-safe actions are needed, prefer one router webhook such as `openclaw-action`. - -Request shape: +Request: ```json { "action": "append_log", "args": { - "text": "backup finished" - }, - "request_id": "optional-uuid" + "text": "backup complete" + } } ``` -That keeps the external surface small while letting n8n route internally. +Purpose: +- append a short line to a known log or tracking sink + +### `notify` + +Request: + +```json +{ + "action": "notify", + "args": { + "message": "workflow finished", + "title": "optional title" + } +} +``` + +Purpose: +- send a small notification through a known downstream channel + +## Naming guidance + +- Use lowercase kebab-case for webhook paths. +- Use lowercase snake_case or kebab-case consistently for action names; prefer snake_case for JSON actions if using switch/router logic. +- Keep names explicit: `openclaw-ping`, `openclaw-action`, `append_log`, `notify`. +- Avoid generic names like `run`, `task`, or `webhook1`. diff --git a/skills/n8n-webhook/scripts/call-action.sh b/skills/n8n-webhook/scripts/call-action.sh new file mode 100755 index 0000000..e63d66f --- /dev/null +++ b/skills/n8n-webhook/scripts/call-action.sh @@ -0,0 +1,128 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: + scripts/call-action.sh [--args '{"k":"v"}'] [--args-file args.json] [--request-id ] [--path openclaw-action] [--test] [--pretty] [--dry-run] + +Environment: + N8N_ACTION_PATH Default router webhook path (default: openclaw-action) + N8N_WEBHOOK_SECRET Shared secret for header auth + N8N_BASE_URL Base n8n URL (default: http://192.168.153.113:18808) + N8N_SECRET_HEADER Header name (default: x-openclaw-secret) + +Examples: + scripts/call-action.sh append_log --args '{"text":"backup complete"}' + scripts/call-action.sh notify --args-file notify.json --test --pretty +EOF +} + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +CALL_WEBHOOK="$SCRIPT_DIR/call-webhook.sh" +ACTION_PATH="${N8N_ACTION_PATH:-openclaw-action}" +ACTION="" +ARGS='{}' +ARGS_FILE="" +REQUEST_ID="" +MODE_FLAG="" +PRETTY=0 +DRY_RUN=0 +EXTRA_ARGS=() + +while [[ $# -gt 0 ]]; do + case "$1" in + --help|-h) + usage + exit 0 + ;; + --args) + ARGS="${2:?missing value for --args}" + shift 2 + ;; + --args-file) + ARGS_FILE="${2:?missing value for --args-file}" + shift 2 + ;; + --request-id) + REQUEST_ID="${2:?missing value for --request-id}" + shift 2 + ;; + --path) + ACTION_PATH="${2:?missing value for --path}" + shift 2 + ;; + --test) + MODE_FLAG="--test" + shift + ;; + --prod) + MODE_FLAG="--prod" + shift + ;; + --pretty) + PRETTY=1 + shift + ;; + --dry-run) + DRY_RUN=1 + shift + ;; + --*) + echo "Unknown option: $1" >&2 + usage >&2 + exit 2 + ;; + *) + if [[ -z "$ACTION" ]]; then + ACTION="$1" + else + EXTRA_ARGS+=("$1") + fi + shift + ;; + esac +done + +if [[ -z "$ACTION" ]]; then + usage >&2 + exit 2 +fi + +if [[ ${#EXTRA_ARGS[@]} -gt 0 ]]; then + echo "Unexpected extra arguments: ${EXTRA_ARGS[*]}" >&2 + exit 2 +fi + +if [[ -n "$ARGS_FILE" ]]; then + ARGS="$(cat "$ARGS_FILE")" +fi + +PAYLOAD="$({ + python3 - <<'PY' "$ACTION" "$ARGS" "$REQUEST_ID" +import json, sys + +action = sys.argv[1] +args = json.loads(sys.argv[2]) +request_id = sys.argv[3] + +if not isinstance(args, dict): + raise SystemExit('Action args must decode to a JSON object.') + +payload = { + 'action': action, + 'args': args, +} +if request_id: + payload['request_id'] = request_id + +print(json.dumps(payload, separators=(',', ':'))) +PY +} )" + +CMD=("$CALL_WEBHOOK" "$ACTION_PATH" --data "$PAYLOAD") +[[ -n "$MODE_FLAG" ]] && CMD+=("$MODE_FLAG") +[[ "$PRETTY" -eq 1 ]] && CMD+=(--pretty) +[[ "$DRY_RUN" -eq 1 ]] && CMD+=(--dry-run) + +exec "${CMD[@]}"