feat(n8n-webhook): expand action bus starter workflow
This commit is contained in:
@@ -40,3 +40,14 @@
|
|||||||
- `notify` → success
|
- `notify` → success
|
||||||
- Refreshed packaged skill artifact again at `/tmp/n8n-skill-dist/n8n-webhook.skill`.
|
- Refreshed packaged skill artifact again at `/tmp/n8n-skill-dist/n8n-webhook.skill`.
|
||||||
- Will clarified a standing operating preference: treat local n8n as an assistant tool to use proactively when appropriate, not as something needing separate approval each time.
|
- Will clarified a standing operating preference: treat local n8n as an assistant tool to use proactively when appropriate, not as something needing separate approval each time.
|
||||||
|
- Extended the shipped `skills/n8n-webhook` router asset beyond the original live trio (`append_log`, `get_logs`, `notify`) to add:
|
||||||
|
- `send_email_draft`
|
||||||
|
- `create_calendar_event`
|
||||||
|
- `approval_queue_add`
|
||||||
|
- `approval_queue_list`
|
||||||
|
- `approval_queue_resolve`
|
||||||
|
- `fetch_and_normalize_url`
|
||||||
|
- `inbound_event_filter`
|
||||||
|
- Design choice for the new actions: keep the starter workflow immediately usable without new provider credentials by using n8n workflow static data for approval queue/history/event state, while leaving room to wire provider-backed email/calendar executors later.
|
||||||
|
- Updated local docs, validator, and sample payloads for the expanded action bus and re-ran local structural validation successfully.
|
||||||
|
- Live n8n re-import/update was not completed in this pass because the current session did not have a verified safe path into the already-running instance (no confirmed admin/browser path and no confirmed current webhook secret for live test calls).
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,7 +1,10 @@
|
|||||||
{
|
{
|
||||||
"action": "append_log",
|
"action": "append_log",
|
||||||
|
"request_id": "test-append-log-001",
|
||||||
"args": {
|
"args": {
|
||||||
"text": "backup complete"
|
"text": "backup complete",
|
||||||
},
|
"meta": {
|
||||||
"request_id": "test-append-log-001"
|
"source": "backup-job"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"action": "approval_queue_list",
|
||||||
|
"request_id": "test-approval-list-001",
|
||||||
|
"args": {
|
||||||
|
"limit": 10,
|
||||||
|
"include_history": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"action": "create_calendar_event",
|
||||||
|
"request_id": "test-calendar-event-001",
|
||||||
|
"args": {
|
||||||
|
"calendar": "primary",
|
||||||
|
"title": "Call with vendor",
|
||||||
|
"start": "2026-03-13T18:00:00Z",
|
||||||
|
"end": "2026-03-13T18:30:00Z",
|
||||||
|
"description": "Drafted from OpenClaw action bus."
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"action": "fetch_and_normalize_url",
|
||||||
|
"request_id": "test-fetch-001",
|
||||||
|
"args": {
|
||||||
|
"url": "https://example.com",
|
||||||
|
"max_chars": 2000
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"action": "inbound_event_filter",
|
||||||
|
"request_id": "test-inbound-001",
|
||||||
|
"args": {
|
||||||
|
"source": "homelab",
|
||||||
|
"type": "alert",
|
||||||
|
"severity": "critical",
|
||||||
|
"summary": "Build failed on swarm cluster",
|
||||||
|
"notify": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"action": "notify",
|
"action": "notify",
|
||||||
|
"request_id": "test-notify-001",
|
||||||
"args": {
|
"args": {
|
||||||
"title": "Workflow finished",
|
"title": "Workflow finished",
|
||||||
"message": "n8n router test"
|
"message": "n8n router test"
|
||||||
},
|
}
|
||||||
"request_id": "test-notify-001"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"action": "send_email_draft",
|
||||||
|
"request_id": "test-email-draft-001",
|
||||||
|
"args": {
|
||||||
|
"to": [
|
||||||
|
"will@example.com"
|
||||||
|
],
|
||||||
|
"subject": "Draft daily brief",
|
||||||
|
"body_text": "Here is a draft daily brief for review."
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,10 +10,17 @@ It implements a real local OpenClaw → n8n router.
|
|||||||
|
|
||||||
- accepts `POST /webhook/openclaw-action`
|
- accepts `POST /webhook/openclaw-action`
|
||||||
- normalizes incoming JSON into an action contract
|
- normalizes incoming JSON into an action contract
|
||||||
- supports three live actions:
|
- supports these actions in the shipped asset:
|
||||||
- `append_log`
|
- `append_log`
|
||||||
- `get_logs`
|
- `get_logs`
|
||||||
- `notify`
|
- `notify`
|
||||||
|
- `send_email_draft`
|
||||||
|
- `create_calendar_event`
|
||||||
|
- `approval_queue_add`
|
||||||
|
- `approval_queue_list`
|
||||||
|
- `approval_queue_resolve`
|
||||||
|
- `fetch_and_normalize_url`
|
||||||
|
- `inbound_event_filter`
|
||||||
- returns normalized JSON responses
|
- returns normalized JSON responses
|
||||||
- returns `400` for unknown actions
|
- returns `400` for unknown actions
|
||||||
- returns `400` when required args are missing
|
- returns `400` when required args are missing
|
||||||
@@ -33,13 +40,35 @@ Example stored record:
|
|||||||
{"ts":"2026-03-12T07:00:00Z","source":"openclaw-action","request_id":"abc","text":"backup complete"}
|
{"ts":"2026-03-12T07:00:00Z","source":"openclaw-action","request_id":"abc","text":"backup complete"}
|
||||||
```
|
```
|
||||||
|
|
||||||
### `get_logs`
|
### `send_email_draft` and `create_calendar_event`
|
||||||
|
|
||||||
- reads from workflow static data key:
|
- queue approval-gated proposals into workflow static data under key:
|
||||||
- `actionLog`
|
- `approvalQueue`
|
||||||
- returns newest-first
|
- keep the most recent `200` pending entries
|
||||||
- default `limit` is `20`
|
- do **not** send email or create provider-side calendar events in the shipped starter workflow
|
||||||
- clamps `limit` to `1..50`
|
- are designed to become safe provider-backed executors later once instance-local creds are bound in n8n
|
||||||
|
|
||||||
|
### `approval_queue_resolve`
|
||||||
|
|
||||||
|
- removes one item from `approvalQueue`
|
||||||
|
- appends the resolved entry into:
|
||||||
|
- `approvalHistory`
|
||||||
|
- supports optional notification on approval/rejection
|
||||||
|
|
||||||
|
### `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
|
||||||
|
|
||||||
|
### `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`
|
### `notify`
|
||||||
|
|
||||||
@@ -49,18 +78,19 @@ Example stored record:
|
|||||||
- `Discord Bot Auth`
|
- `Discord Bot Auth`
|
||||||
- current targets mirror the already-working reminder workflow
|
- current targets mirror the already-working reminder workflow
|
||||||
|
|
||||||
## Why workflow static data for logs
|
## Why workflow static data first
|
||||||
|
|
||||||
Why this first:
|
Why this first:
|
||||||
- built-in, no extra credentials
|
- built-in, no extra credentials
|
||||||
- persists without guessing writable filesystem paths
|
- persists without guessing writable filesystem paths
|
||||||
- better fit than MinIO for small, recent operational breadcrumbs
|
- good fit for queues, recent breadcrumbs, and small operational state
|
||||||
|
- lets us implement safe approval-gated patterns immediately
|
||||||
|
|
||||||
When to use MinIO later:
|
When to add provider-backed steps later:
|
||||||
- long retention
|
- email draft creation in Gmail/Outlook
|
||||||
- rotated archives
|
- calendar writes in Google Calendar
|
||||||
- large/batched exports
|
- Airtable/Sheets append pipelines
|
||||||
- sharing logs outside n8n
|
- long-retention logs or external archival
|
||||||
|
|
||||||
## Intentional security choice
|
## Intentional security choice
|
||||||
|
|
||||||
@@ -97,6 +127,11 @@ After import, set this manually in n8n:
|
|||||||
|
|
||||||
- `assets/test-append-log.json`
|
- `assets/test-append-log.json`
|
||||||
- `assets/test-notify.json`
|
- `assets/test-notify.json`
|
||||||
|
- `assets/test-send-email-draft.json`
|
||||||
|
- `assets/test-create-calendar-event.json`
|
||||||
|
- `assets/test-fetch-and-normalize-url.json`
|
||||||
|
- `assets/test-approval-queue-list.json`
|
||||||
|
- `assets/test-inbound-event-filter.json`
|
||||||
|
|
||||||
## Example tests
|
## Example tests
|
||||||
|
|
||||||
@@ -105,69 +140,73 @@ export N8N_WEBHOOK_SECRET='YOUR_SECRET_HERE'
|
|||||||
scripts/call-action.sh append_log --args '{"text":"backup complete"}' --pretty
|
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 get_logs --args '{"limit":5}' --pretty
|
||||||
scripts/call-action.sh notify --args '{"title":"Workflow finished","message":"n8n router test"}' --pretty
|
scripts/call-action.sh notify --args '{"title":"Workflow finished","message":"n8n router test"}' --pretty
|
||||||
|
scripts/call-action.sh send_email_draft --args-file assets/test-send-email-draft.json --pretty
|
||||||
|
scripts/call-action.sh create_calendar_event --args-file assets/test-create-calendar-event.json --pretty
|
||||||
|
scripts/call-action.sh fetch_and_normalize_url --args '{"url":"https://example.com"}' --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
|
||||||
```
|
```
|
||||||
|
|
||||||
## Expected success examples
|
## Expected success examples
|
||||||
|
|
||||||
### append_log
|
### send_email_draft
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"ok": true,
|
"ok": true,
|
||||||
"request_id": "test-append-log-001",
|
"request_id": "test-email-draft-001",
|
||||||
"result": {
|
"result": {
|
||||||
"action": "append_log",
|
"action": "send_email_draft",
|
||||||
"status": "logged",
|
"status": "queued_for_approval",
|
||||||
"preview": {
|
"pending_id": "approval-abc123",
|
||||||
"text": "backup complete"
|
"approval_status": "pending"
|
||||||
},
|
|
||||||
"sink": {
|
|
||||||
"type": "workflow-static-data",
|
|
||||||
"key": "actionLog",
|
|
||||||
"retained_entries": 200
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### get_logs
|
### create_calendar_event
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"ok": true,
|
"ok": true,
|
||||||
"request_id": "",
|
"request_id": "test-calendar-event-001",
|
||||||
"result": {
|
"result": {
|
||||||
"action": "get_logs",
|
"action": "create_calendar_event",
|
||||||
|
"status": "queued_for_approval",
|
||||||
|
"pending_id": "approval-def456",
|
||||||
|
"approval_status": "pending"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### fetch_and_normalize_url
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"request_id": "test-fetch-001",
|
||||||
|
"result": {
|
||||||
|
"action": "fetch_and_normalize_url",
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
"count": 1,
|
"url": "https://example.com/",
|
||||||
"total_retained": 1,
|
"title": "Example Domain",
|
||||||
"retained_entries": 200,
|
"content_type": "text/html; charset=UTF-8"
|
||||||
"entries": [
|
|
||||||
{
|
|
||||||
"ts": "2026-03-12T08:42:37.615Z",
|
|
||||||
"source": "openclaw-action",
|
|
||||||
"request_id": "live-log-003",
|
|
||||||
"text": "n8n append_log static-data verification"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### notify
|
### inbound_event_filter
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"ok": true,
|
"ok": true,
|
||||||
"request_id": "test-notify-001",
|
"request_id": "test-inbound-001",
|
||||||
"result": {
|
"result": {
|
||||||
"action": "notify",
|
"action": "inbound_event_filter",
|
||||||
"status": "sent",
|
"status": "stored",
|
||||||
"preview": {
|
"classification": "urgent",
|
||||||
"title": "Workflow finished",
|
"duplicate": false,
|
||||||
"message": "n8n router test"
|
"notified": true
|
||||||
},
|
|
||||||
"targets": ["telegram", "discord"]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ Recommended request shape:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Live actions
|
## Live actions in the shipped workflow asset
|
||||||
|
|
||||||
### `append_log`
|
### `append_log`
|
||||||
|
|
||||||
@@ -96,23 +96,6 @@ Behavior:
|
|||||||
- max limit: `50`
|
- max limit: `50`
|
||||||
- entries are returned newest-first
|
- entries are returned newest-first
|
||||||
|
|
||||||
Success shape:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"ok": true,
|
|
||||||
"request_id": "optional-uuid",
|
|
||||||
"result": {
|
|
||||||
"action": "get_logs",
|
|
||||||
"status": "ok",
|
|
||||||
"count": 2,
|
|
||||||
"total_retained": 7,
|
|
||||||
"retained_entries": 200,
|
|
||||||
"entries": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### `notify`
|
### `notify`
|
||||||
|
|
||||||
Request:
|
Request:
|
||||||
@@ -130,21 +113,171 @@ Request:
|
|||||||
Purpose:
|
Purpose:
|
||||||
- send the message through the currently configured Telegram + Discord notification targets
|
- send the message through the currently configured Telegram + Discord notification targets
|
||||||
|
|
||||||
Success shape:
|
### `send_email_draft`
|
||||||
|
|
||||||
|
Request:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"ok": true,
|
"action": "send_email_draft",
|
||||||
"request_id": "optional-uuid",
|
"args": {
|
||||||
"result": {
|
"to": ["will@example.com"],
|
||||||
"action": "notify",
|
"subject": "Draft daily brief",
|
||||||
"status": "sent",
|
"body_text": "Here is a draft daily brief for review."
|
||||||
"targets": ["telegram", "discord"]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Failure shape
|
Purpose:
|
||||||
|
- queue an email draft proposal for approval
|
||||||
|
- does **not** send mail directly in the shipped starter workflow
|
||||||
|
|
||||||
|
Sink:
|
||||||
|
- type: `workflow-static-data`
|
||||||
|
- key: `approvalQueue`
|
||||||
|
- retained entries: `200`
|
||||||
|
|
||||||
|
### `create_calendar_event`
|
||||||
|
|
||||||
|
Request:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action": "create_calendar_event",
|
||||||
|
"args": {
|
||||||
|
"calendar": "primary",
|
||||||
|
"title": "Call with vendor",
|
||||||
|
"start": "2026-03-13T18:00:00Z",
|
||||||
|
"end": "2026-03-13T18:30:00Z",
|
||||||
|
"description": "Drafted from OpenClaw action bus."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Purpose:
|
||||||
|
- queue a calendar event proposal for approval
|
||||||
|
- does **not** write to a calendar provider directly in the shipped starter workflow
|
||||||
|
|
||||||
|
Sink:
|
||||||
|
- type: `workflow-static-data`
|
||||||
|
- key: `approvalQueue`
|
||||||
|
- retained entries: `200`
|
||||||
|
|
||||||
|
### `approval_queue_add`
|
||||||
|
|
||||||
|
Request:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action": "approval_queue_add",
|
||||||
|
"args": {
|
||||||
|
"kind": "manual",
|
||||||
|
"summary": "Review outbound customer reply",
|
||||||
|
"payload": {
|
||||||
|
"channel": "email"
|
||||||
|
},
|
||||||
|
"tags": ["approval", "customer"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Purpose:
|
||||||
|
- add a generic pending approval item to the queue
|
||||||
|
|
||||||
|
### `approval_queue_list`
|
||||||
|
|
||||||
|
Request:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action": "approval_queue_list",
|
||||||
|
"args": {
|
||||||
|
"limit": 10,
|
||||||
|
"include_history": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Purpose:
|
||||||
|
- inspect pending approval items
|
||||||
|
- optionally include recent resolved history
|
||||||
|
|
||||||
|
### `approval_queue_resolve`
|
||||||
|
|
||||||
|
Request:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action": "approval_queue_resolve",
|
||||||
|
"args": {
|
||||||
|
"id": "approval-abc123",
|
||||||
|
"decision": "approve",
|
||||||
|
"note": "Looks good",
|
||||||
|
"notify_on_resolve": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Purpose:
|
||||||
|
- approve or reject a pending item
|
||||||
|
- moves resolved entries into `approvalHistory`
|
||||||
|
|
||||||
|
### `fetch_and_normalize_url`
|
||||||
|
|
||||||
|
Request:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action": "fetch_and_normalize_url",
|
||||||
|
"args": {
|
||||||
|
"url": "https://example.com/article",
|
||||||
|
"max_chars": 8000,
|
||||||
|
"timeout_ms": 10000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Purpose:
|
||||||
|
- fetch a URL inside n8n
|
||||||
|
- normalize content into a predictable summary-ready shape
|
||||||
|
|
||||||
|
Success shape includes:
|
||||||
|
- `url`
|
||||||
|
- `title`
|
||||||
|
- `content_type`
|
||||||
|
- `http_status`
|
||||||
|
- `excerpt`
|
||||||
|
- `body_text`
|
||||||
|
- `text_length`
|
||||||
|
- `truncated`
|
||||||
|
|
||||||
|
### `inbound_event_filter`
|
||||||
|
|
||||||
|
Request:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action": "inbound_event_filter",
|
||||||
|
"args": {
|
||||||
|
"source": "homelab",
|
||||||
|
"type": "alert",
|
||||||
|
"severity": "critical",
|
||||||
|
"summary": "Build failed on swarm cluster",
|
||||||
|
"notify": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Purpose:
|
||||||
|
- dedupe and classify inbound events
|
||||||
|
- store recent events in workflow static data
|
||||||
|
- optionally notify on urgent/important events
|
||||||
|
|
||||||
|
Sinks:
|
||||||
|
- `inboundEvents`
|
||||||
|
- `eventDedup`
|
||||||
|
|
||||||
|
## Common failure shape
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -160,6 +293,6 @@ Success shape:
|
|||||||
## Naming guidance
|
## Naming guidance
|
||||||
|
|
||||||
- Use lowercase kebab-case for webhook paths.
|
- 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.
|
- Use lowercase snake_case for JSON action names.
|
||||||
- Keep names explicit: `openclaw-ping`, `openclaw-action`, `append_log`, `get_logs`, `notify`.
|
- Keep names explicit: `openclaw-ping`, `openclaw-action`, `append_log`, `approval_queue_resolve`.
|
||||||
- Avoid generic names like `run`, `task`, or `webhook1`.
|
- Avoid generic names like `run`, `task`, or `webhook1`.
|
||||||
|
|||||||
@@ -4,7 +4,13 @@ set -euo pipefail
|
|||||||
usage() {
|
usage() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
Usage:
|
Usage:
|
||||||
scripts/call-action.sh <action> [--args '{"k":"v"}'] [--args-file args.json] [--request-id <id|auto>] [--path openclaw-action] [--test] [--pretty] [--dry-run]
|
scripts/call-action.sh [action] [--args '{"k":"v"}'] [--args-file args.json] [--request-id <id|auto>] [--path openclaw-action] [--test] [--pretty] [--dry-run]
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- `action` is optional when --args/--args-file contains a full payload with top-level `action` + `args`.
|
||||||
|
- `--args` / `--args-file` may contain either:
|
||||||
|
1) a plain args object, or
|
||||||
|
2) a full payload object: {"action":"...","args":{...},"request_id":"..."}
|
||||||
|
|
||||||
Environment:
|
Environment:
|
||||||
N8N_ACTION_PATH Default router webhook path (default: openclaw-action)
|
N8N_ACTION_PATH Default router webhook path (default: openclaw-action)
|
||||||
@@ -14,6 +20,7 @@ Environment:
|
|||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
scripts/call-action.sh append_log --args '{"text":"backup complete"}' --request-id auto
|
scripts/call-action.sh append_log --args '{"text":"backup complete"}' --request-id auto
|
||||||
|
scripts/call-action.sh --args-file assets/test-send-email-draft.json --pretty
|
||||||
scripts/call-action.sh notify --args-file notify.json --test --pretty
|
scripts/call-action.sh notify --args-file notify.json --test --pretty
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
@@ -84,11 +91,6 @@ while [[ $# -gt 0 ]]; do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ -z "$ACTION" ]]; then
|
|
||||||
usage >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ${#EXTRA_ARGS[@]} -gt 0 ]]; then
|
if [[ ${#EXTRA_ARGS[@]} -gt 0 ]]; then
|
||||||
echo "Unexpected extra arguments: ${EXTRA_ARGS[*]}" >&2
|
echo "Unexpected extra arguments: ${EXTRA_ARGS[*]}" >&2
|
||||||
exit 2
|
exit 2
|
||||||
@@ -110,13 +112,32 @@ PAYLOAD="$({
|
|||||||
python3 - <<'PY' "$ACTION" "$ARGS" "$REQUEST_ID"
|
python3 - <<'PY' "$ACTION" "$ARGS" "$REQUEST_ID"
|
||||||
import json, sys
|
import json, sys
|
||||||
|
|
||||||
action = sys.argv[1]
|
cli_action = sys.argv[1]
|
||||||
args = json.loads(sys.argv[2])
|
raw = json.loads(sys.argv[2])
|
||||||
request_id = sys.argv[3]
|
cli_request_id = sys.argv[3]
|
||||||
|
|
||||||
if not isinstance(args, dict):
|
if not isinstance(raw, dict):
|
||||||
raise SystemExit('Action args must decode to a JSON object.')
|
raise SystemExit('Action args must decode to a JSON object.')
|
||||||
|
|
||||||
|
if 'action' in raw and 'args' in raw:
|
||||||
|
file_action = raw.get('action', '')
|
||||||
|
file_args = raw.get('args', {})
|
||||||
|
file_request_id = raw.get('request_id', '')
|
||||||
|
if not isinstance(file_args, dict):
|
||||||
|
raise SystemExit('Full payload args must decode to a JSON object.')
|
||||||
|
if cli_action and file_action and cli_action != file_action:
|
||||||
|
raise SystemExit(f'CLI action {cli_action!r} does not match payload action {file_action!r}.')
|
||||||
|
action = cli_action or file_action
|
||||||
|
args = file_args
|
||||||
|
request_id = cli_request_id or file_request_id
|
||||||
|
else:
|
||||||
|
action = cli_action
|
||||||
|
args = raw
|
||||||
|
request_id = cli_request_id
|
||||||
|
|
||||||
|
if not action:
|
||||||
|
raise SystemExit('Action is required unless provided inside --args/--args-file full payload.')
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
'action': action,
|
'action': action,
|
||||||
'args': args,
|
'args': args,
|
||||||
|
|||||||
@@ -21,6 +21,38 @@ EXPECTED_TYPES = {
|
|||||||
'Respond to Webhook': 'n8n-nodes-base.respondToWebhook',
|
'Respond to Webhook': 'n8n-nodes-base.respondToWebhook',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SAMPLE_FILES = [
|
||||||
|
'test-append-log.json',
|
||||||
|
'test-notify.json',
|
||||||
|
'test-send-email-draft.json',
|
||||||
|
'test-create-calendar-event.json',
|
||||||
|
'test-fetch-and-normalize-url.json',
|
||||||
|
'test-approval-queue-list.json',
|
||||||
|
'test-inbound-event-filter.json',
|
||||||
|
]
|
||||||
|
|
||||||
|
ROUTER_SNIPPETS = [
|
||||||
|
'append_log',
|
||||||
|
'get_logs',
|
||||||
|
'notify',
|
||||||
|
'send_email_draft',
|
||||||
|
'create_calendar_event',
|
||||||
|
'approval_queue_add',
|
||||||
|
'approval_queue_list',
|
||||||
|
'approval_queue_resolve',
|
||||||
|
'fetch_and_normalize_url',
|
||||||
|
'inbound_event_filter',
|
||||||
|
'unknown_action',
|
||||||
|
'invalid_request',
|
||||||
|
'$getWorkflowStaticData',
|
||||||
|
'approvalQueue',
|
||||||
|
'approvalHistory',
|
||||||
|
'inboundEvents',
|
||||||
|
'eventDedup',
|
||||||
|
'notify_text',
|
||||||
|
'fetch(',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def fail(msg: str):
|
def fail(msg: str):
|
||||||
print(f'ERROR: {msg}', file=sys.stderr)
|
print(f'ERROR: {msg}', file=sys.stderr)
|
||||||
@@ -72,7 +104,7 @@ def main():
|
|||||||
|
|
||||||
router = by_name['route-action'].get('parameters', {})
|
router = by_name['route-action'].get('parameters', {})
|
||||||
js_code = router.get('jsCode', '')
|
js_code = router.get('jsCode', '')
|
||||||
for snippet in ('append_log', 'get_logs', 'notify', 'unknown_action', 'invalid_request', '$getWorkflowStaticData', 'actionLog', 'retained_entries', 'notify_text', 'entries.length', 'Math.min(50'):
|
for snippet in ROUTER_SNIPPETS:
|
||||||
if snippet not in js_code:
|
if snippet not in js_code:
|
||||||
fail(f'route-action jsCode missing expected snippet: {snippet!r}')
|
fail(f'route-action jsCode missing expected snippet: {snippet!r}')
|
||||||
|
|
||||||
@@ -94,7 +126,8 @@ def main():
|
|||||||
if responder.get('respondWith') != 'json':
|
if responder.get('respondWith') != 'json':
|
||||||
fail('Respond to Webhook must respondWith json')
|
fail('Respond to Webhook must respondWith json')
|
||||||
|
|
||||||
for sample in (path.parent / 'test-append-log.json', path.parent / 'test-notify.json'):
|
for sample_name in SAMPLE_FILES:
|
||||||
|
sample = path.parent / sample_name
|
||||||
sample_data = load_json(sample)
|
sample_data = load_json(sample)
|
||||||
if not isinstance(sample_data, dict) or 'action' not in sample_data or 'args' not in sample_data:
|
if not isinstance(sample_data, dict) or 'action' not in sample_data or 'args' not in sample_data:
|
||||||
fail(f'sample payload missing action/args: {sample}')
|
fail(f'sample payload missing action/args: {sample}')
|
||||||
@@ -102,8 +135,8 @@ def main():
|
|||||||
print('OK: workflow asset structure looks consistent')
|
print('OK: workflow asset structure looks consistent')
|
||||||
print(f'- workflow: {path}')
|
print(f'- workflow: {path}')
|
||||||
print(f'- nodes: {len(nodes)}')
|
print(f'- nodes: {len(nodes)}')
|
||||||
print('- routes: append_log + get_logs via workflow static data, notify via Telegram + Discord, fallback -> JSON error')
|
print('- routes: notify via Telegram + Discord; queue/log/fetch/filter handled in route-action code')
|
||||||
print('- samples: test-append-log.json, test-notify.json')
|
print('- samples: ' + ', '.join(SAMPLE_FILES))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
Reference in New Issue
Block a user