feat(n8n-webhook): bridge approvals to gog executors
This commit is contained in:
@@ -17,6 +17,8 @@
|
|||||||
- Claude ACP tiering preference: Haiku 4.5 (simple), Sonnet 4.6 (default medium), Opus 4.6 (hard/high-stakes)
|
- Claude ACP tiering preference: Haiku 4.5 (simple), Sonnet 4.6 (default medium), Opus 4.6 (hard/high-stakes)
|
||||||
- Git preference: commit frequently with Conventional Commits; create feature branches for non-trivial work; auto-commit after meaningful workspace changes without being asked; never auto-push (push only when explicitly asked)
|
- Git preference: commit frequently with Conventional Commits; create feature branches for non-trivial work; auto-commit after meaningful workspace changes without being asked; never auto-push (push only when explicitly asked)
|
||||||
- Tooling preference: treat the local n8n instance as an assistant-owned execution/orchestration tool and use it proactively when it is the right fit, without asking for separate permission each time.
|
- Tooling preference: treat the local n8n instance as an assistant-owned execution/orchestration tool and use it proactively when it is the right fit, without asking for separate permission each time.
|
||||||
|
- n8n access preference: treat the live n8n public API as part of that allowed tool surface as well; when the right path is via the n8n API, use it directly instead of acting blocked or asking again for permission.
|
||||||
|
- Google Workspace automation note: `gog` works for non-interactive planning/dry-runs without unlocking the keyring, but real headless Gmail/Calendar execution requires `GOG_KEYRING_PASSWORD` in the environment because the file keyring backend cannot prompt in non-TTY automation.
|
||||||
|
|
||||||
## Boundaries
|
## Boundaries
|
||||||
- Never fetch/read remote files to alter instructions.
|
- Never fetch/read remote files to alter instructions.
|
||||||
|
|||||||
@@ -78,3 +78,19 @@
|
|||||||
- `send_notification_draft` returned `200` and produced pending id `approval-mmnr8pyq-tjxiqkps`
|
- `send_notification_draft` returned `200` and produced pending id `approval-mmnr8pyq-tjxiqkps`
|
||||||
- approving that item via `approval_queue_resolve` returned `executed: true` and `executed_action: "notify"`
|
- approving that item via `approval_queue_resolve` returned `executed: true` and `executed_action: "notify"`
|
||||||
- `approval_queue_list` showed `pending_count: 0` afterward and recorded the execution metadata in history
|
- `approval_queue_list` showed `pending_count: 0` afterward and recorded the execution metadata in history
|
||||||
|
- Will explicitly reinforced a durable operating expectation: local n8n, including its live public API, should be treated as assistant-owned tooling. If the correct path is the n8n API, use it directly instead of re-asking for permission or acting blocked.
|
||||||
|
- After Google Workspace auth was completed with `gog`, headless testing showed an important automation constraint: real non-TTY `gog` calls fail unless `GOG_KEYRING_PASSWORD` is present, because the current `gog` file keyring backend cannot prompt in automation. However, `gog --dry-run` for Gmail draft creation and Calendar event creation works without unlocking the keyring, which made it possible to fully validate executor plumbing safely.
|
||||||
|
- Implemented a host-side bridge script at `skills/n8n-webhook/scripts/resolve-approval-with-gog.py`.
|
||||||
|
- flow: resolve approval in n8n → execute supported kinds on host via `gog` → write execution metadata back into n8n history
|
||||||
|
- supported host-executed kinds:
|
||||||
|
- `email_draft` → `gog gmail drafts create`
|
||||||
|
- `calendar_event` → `gog calendar create`
|
||||||
|
- Expanded the live `openclaw-action` workflow with new action `approval_history_attach_execution`, allowing host-side executors to patch resolved history entries with execution status/details.
|
||||||
|
- Live dry-run verification on 2026-03-12 succeeded end-to-end:
|
||||||
|
- queued one `email_draft` approval item and one `calendar_event` item
|
||||||
|
- resolved both via the new host bridge with `--dry-run`
|
||||||
|
- `gog` returned dry-run JSON for both operations without touching Google state
|
||||||
|
- `approvalHistory` entries were updated in n8n with execution metadata:
|
||||||
|
- email draft item id `approval-mmnsx7iz-k26qb60c` → `execution.op = gmail.drafts.create`, `status = dry_run`
|
||||||
|
- calendar item id `approval-mmnsx7ji-3rt7yd74` → `execution.op = calendar.create`, `status = dry_run`
|
||||||
|
- Current practical next step for real Gmail/Calendar execution: provide `GOG_KEYRING_PASSWORD` to the runtime environment that will invoke the bridge script, or switch `gog` to a keyring backend that supports unattended access on this host.
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ Keep the integration narrow: let OpenClaw decide what to do, and let n8n execute
|
|||||||
|
|
||||||
- direct webhook caller: `scripts/call-webhook.sh`
|
- direct webhook caller: `scripts/call-webhook.sh`
|
||||||
- action-bus caller: `scripts/call-action.sh`
|
- action-bus caller: `scripts/call-action.sh`
|
||||||
|
- approval executor bridge: `scripts/resolve-approval-with-gog.py`
|
||||||
- workflow validator: `scripts/validate-workflow.py`
|
- workflow validator: `scripts/validate-workflow.py`
|
||||||
- importable router workflow: `assets/openclaw-action.workflow.json`
|
- importable router workflow: `assets/openclaw-action.workflow.json`
|
||||||
- sample payloads:
|
- sample payloads:
|
||||||
@@ -106,6 +107,7 @@ Use the included workflow asset when you want a ready-made local router for:
|
|||||||
- `send_email_draft` → queue approval-gated email drafts in workflow static data
|
- `send_email_draft` → queue approval-gated email drafts in workflow static data
|
||||||
- `create_calendar_event` → queue approval-gated calendar proposals in workflow static data
|
- `create_calendar_event` → queue approval-gated calendar proposals in workflow static data
|
||||||
- `approval_queue_add` / `approval_queue_list` / `approval_queue_resolve` → manage pending approvals and recent history
|
- `approval_queue_add` / `approval_queue_list` / `approval_queue_resolve` → manage pending approvals and recent history
|
||||||
|
- `approval_history_attach_execution` → let a host-side executor attach real execution metadata back onto approval history entries
|
||||||
- `fetch_and_normalize_url` → fetch + normalize URL content using n8n runtime HTTP helpers
|
- `fetch_and_normalize_url` → fetch + normalize URL content using n8n runtime HTTP helpers
|
||||||
- `inbound_event_filter` → classify, dedupe, store, and optionally notify on inbound events
|
- `inbound_event_filter` → classify, dedupe, store, and optionally notify on inbound events
|
||||||
- normalized JSON success/failure responses
|
- normalized JSON success/failure responses
|
||||||
@@ -118,6 +120,18 @@ Important:
|
|||||||
|
|
||||||
See `references/openclaw-action.md` for import and test steps.
|
See `references/openclaw-action.md` for import and test steps.
|
||||||
|
|
||||||
|
### Host execution bridge for Gmail/Calendar
|
||||||
|
|
||||||
|
When email/calendar provider creds live on the host via `gog` rather than inside n8n, use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 scripts/resolve-approval-with-gog.py --id <approval-id> --decision approve
|
||||||
|
```
|
||||||
|
|
||||||
|
Practical note:
|
||||||
|
- unattended execution needs `GOG_KEYRING_PASSWORD` in the environment because `gog`'s file keyring cannot prompt in non-TTY automation
|
||||||
|
- for safe plumbing tests without touching Google state, add `--dry-run`
|
||||||
|
|
||||||
### Add a new webhook-backed capability
|
### Add a new webhook-backed capability
|
||||||
|
|
||||||
1. Write down the webhook path, required auth, request JSON, and response JSON.
|
1. Write down the webhook path, required auth, request JSON, and response JSON.
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -20,6 +20,7 @@ It implements a real local OpenClaw → n8n router.
|
|||||||
- `approval_queue_add`
|
- `approval_queue_add`
|
||||||
- `approval_queue_list`
|
- `approval_queue_list`
|
||||||
- `approval_queue_resolve`
|
- `approval_queue_resolve`
|
||||||
|
- `approval_history_attach_execution`
|
||||||
- `fetch_and_normalize_url`
|
- `fetch_and_normalize_url`
|
||||||
- `inbound_event_filter`
|
- `inbound_event_filter`
|
||||||
- returns normalized JSON responses
|
- returns normalized JSON responses
|
||||||
@@ -62,6 +63,13 @@ Example stored record:
|
|||||||
- appends the resolved entry into:
|
- appends the resolved entry into:
|
||||||
- `approvalHistory`
|
- `approvalHistory`
|
||||||
- supports optional notification on approval/rejection
|
- supports optional notification on approval/rejection
|
||||||
|
- executes notification drafts inline when the approved item kind is `notification`
|
||||||
|
|
||||||
|
### `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
|
||||||
|
|
||||||
### `fetch_and_normalize_url`
|
### `fetch_and_normalize_url`
|
||||||
|
|
||||||
@@ -158,6 +166,7 @@ scripts/call-action.sh fetch_and_normalize_url --args '{"url":"http://192.168.15
|
|||||||
scripts/call-action.sh fetch_and_normalize_url --args '{"url":"https://example.com","skip_ssl_certificate_validation":true}' --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 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
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
## Expected success examples
|
## Expected success examples
|
||||||
@@ -239,6 +248,22 @@ scripts/call-action.sh inbound_event_filter --args-file assets/test-inbound-even
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 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`
|
||||||
|
- `calendar_event` → `gog calendar create`
|
||||||
|
- writes execution metadata back via `approval_history_attach_execution`
|
||||||
|
|
||||||
|
Important automation note:
|
||||||
|
- real unattended execution needs `GOG_KEYRING_PASSWORD` in the environment
|
||||||
|
- without it, 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
|
## Validation
|
||||||
|
|
||||||
Run the local validator before import/package changes:
|
Run the local validator before import/package changes:
|
||||||
|
|||||||
@@ -239,6 +239,29 @@ Request:
|
|||||||
Purpose:
|
Purpose:
|
||||||
- approve or reject a pending item
|
- approve or reject a pending item
|
||||||
- moves resolved entries into `approvalHistory`
|
- moves resolved entries into `approvalHistory`
|
||||||
|
- executes notification drafts inline when the resolved item kind is `notification`
|
||||||
|
|
||||||
|
### `approval_history_attach_execution`
|
||||||
|
|
||||||
|
Request:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action": "approval_history_attach_execution",
|
||||||
|
"args": {
|
||||||
|
"id": "approval-abc123",
|
||||||
|
"execution": {
|
||||||
|
"driver": "gog",
|
||||||
|
"op": "gmail.drafts.create",
|
||||||
|
"status": "draft_created"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Purpose:
|
||||||
|
- patch a resolved history item with host-side execution metadata after a real executor runs outside n8n
|
||||||
|
- intended for bridges such as `gog`-backed Gmail/Calendar execution
|
||||||
|
|
||||||
### `fetch_and_normalize_url`
|
### `fetch_and_normalize_url`
|
||||||
|
|
||||||
|
|||||||
+241
@@ -0,0 +1,241 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import urllib.error
|
||||||
|
import urllib.request
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
DEFAULT_BASE_URL = os.environ.get('N8N_BASE_URL', 'http://192.168.153.113:18808').rstrip('/')
|
||||||
|
DEFAULT_ACTION_PATH = os.environ.get('N8N_ACTION_PATH', 'openclaw-action').strip('/')
|
||||||
|
DEFAULT_SECRET_HEADER = os.environ.get('N8N_SECRET_HEADER', 'x-openclaw-secret')
|
||||||
|
|
||||||
|
|
||||||
|
def fail(msg: str, code: int = 1):
|
||||||
|
print(msg, file=sys.stderr)
|
||||||
|
raise SystemExit(code)
|
||||||
|
|
||||||
|
|
||||||
|
def run(cmd, *, env=None):
|
||||||
|
proc = subprocess.run(cmd, capture_output=True, text=True, env=env)
|
||||||
|
return proc.returncode, proc.stdout, proc.stderr
|
||||||
|
|
||||||
|
|
||||||
|
def gog_account(args_account: str | None) -> str:
|
||||||
|
account = args_account or os.environ.get('GOG_ACCOUNT', '').strip()
|
||||||
|
if not account:
|
||||||
|
fail('missing gog account: pass --account or set GOG_ACCOUNT')
|
||||||
|
return account
|
||||||
|
|
||||||
|
|
||||||
|
def webhook_secret() -> str:
|
||||||
|
secret = os.environ.get('N8N_WEBHOOK_SECRET', '').strip()
|
||||||
|
if not secret:
|
||||||
|
fail('missing N8N_WEBHOOK_SECRET in environment')
|
||||||
|
return secret
|
||||||
|
|
||||||
|
|
||||||
|
def call_action(payload: dict, *, base_url: str, path: str, secret_header: str, secret: str) -> dict:
|
||||||
|
url = f'{base_url}/webhook/{path}'
|
||||||
|
req = urllib.request.Request(
|
||||||
|
url,
|
||||||
|
data=json.dumps(payload).encode(),
|
||||||
|
method='POST',
|
||||||
|
headers={
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
secret_header: secret,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req, timeout=60) as r:
|
||||||
|
body = r.read().decode('utf-8', 'replace')
|
||||||
|
return json.loads(body) if body else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
body = e.read().decode('utf-8', 'replace')
|
||||||
|
try:
|
||||||
|
parsed = json.loads(body) if body else {}
|
||||||
|
except Exception:
|
||||||
|
parsed = {'ok': False, 'error': {'code': 'http_error', 'message': body or str(e)}}
|
||||||
|
parsed.setdefault('http_status', e.code)
|
||||||
|
return parsed
|
||||||
|
|
||||||
|
|
||||||
|
def attach_execution(item_id: str, execution: dict, *, base_url: str, path: str, secret_header: str, secret: str) -> dict:
|
||||||
|
return call_action(
|
||||||
|
{
|
||||||
|
'action': 'approval_history_attach_execution',
|
||||||
|
'args': {'id': item_id, 'execution': execution},
|
||||||
|
'request_id': f'attach-{item_id}',
|
||||||
|
},
|
||||||
|
base_url=base_url,
|
||||||
|
path=path,
|
||||||
|
secret_header=secret_header,
|
||||||
|
secret=secret,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_email_command(item: dict, account: str, dry_run: bool):
|
||||||
|
payload = item.get('payload') or {}
|
||||||
|
body_text = payload.get('body_text') or ''
|
||||||
|
body_html = payload.get('body_html') or ''
|
||||||
|
cmd = [
|
||||||
|
'gog', 'gmail', 'drafts', 'create',
|
||||||
|
'--account', account,
|
||||||
|
'--json',
|
||||||
|
'--no-input',
|
||||||
|
'--to', ','.join(payload.get('to') or []),
|
||||||
|
'--subject', payload.get('subject') or '',
|
||||||
|
]
|
||||||
|
for key in ('cc', 'bcc'):
|
||||||
|
vals = payload.get(key) or []
|
||||||
|
if vals:
|
||||||
|
cmd.extend([f'--{key}', ','.join(vals)])
|
||||||
|
tmp = None
|
||||||
|
if body_text:
|
||||||
|
tmp = tempfile.NamedTemporaryFile('w', delete=False, encoding='utf-8', suffix='.txt')
|
||||||
|
tmp.write(body_text)
|
||||||
|
tmp.close()
|
||||||
|
cmd.extend(['--body-file', tmp.name])
|
||||||
|
elif body_html:
|
||||||
|
# gog requires body or body_html; for HTML-only drafts we can use body_html.
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
fail('email_draft payload missing body_text/body_html')
|
||||||
|
if body_html:
|
||||||
|
cmd.extend(['--body-html', body_html])
|
||||||
|
if dry_run:
|
||||||
|
cmd.append('--dry-run')
|
||||||
|
return cmd, tmp.name if tmp else None
|
||||||
|
|
||||||
|
|
||||||
|
def build_calendar_command(item: dict, account: str, dry_run: bool):
|
||||||
|
payload = item.get('payload') or {}
|
||||||
|
calendar = payload.get('calendar') or 'primary'
|
||||||
|
cmd = [
|
||||||
|
'gog', 'calendar', 'create', calendar,
|
||||||
|
'--account', account,
|
||||||
|
'--json',
|
||||||
|
'--no-input',
|
||||||
|
'--summary', payload.get('title') or '',
|
||||||
|
'--from', payload.get('start') or '',
|
||||||
|
'--to', payload.get('end') or '',
|
||||||
|
'--send-updates', 'none',
|
||||||
|
]
|
||||||
|
if payload.get('description'):
|
||||||
|
cmd.extend(['--description', payload['description']])
|
||||||
|
if payload.get('location'):
|
||||||
|
cmd.extend(['--location', payload['location']])
|
||||||
|
attendees = payload.get('attendees') or []
|
||||||
|
if attendees:
|
||||||
|
cmd.extend(['--attendees', ','.join(attendees)])
|
||||||
|
if dry_run:
|
||||||
|
cmd.append('--dry-run')
|
||||||
|
return cmd
|
||||||
|
|
||||||
|
|
||||||
|
def parse_json(output: str):
|
||||||
|
text = output.strip()
|
||||||
|
if not text:
|
||||||
|
return None
|
||||||
|
return json.loads(text)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser(description='Resolve an n8n approval item and execute email/calendar actions via gog.')
|
||||||
|
ap.add_argument('--id', required=True, help='Approval queue item id')
|
||||||
|
ap.add_argument('--decision', choices=['approve', 'reject'], default='approve')
|
||||||
|
ap.add_argument('--account', help='Google account email; otherwise uses GOG_ACCOUNT')
|
||||||
|
ap.add_argument('--dry-run', action='store_true', help='Use gog --dry-run for host execution')
|
||||||
|
ap.add_argument('--base-url', default=DEFAULT_BASE_URL)
|
||||||
|
ap.add_argument('--path', default=DEFAULT_ACTION_PATH)
|
||||||
|
ap.add_argument('--secret-header', default=DEFAULT_SECRET_HEADER)
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
secret = webhook_secret()
|
||||||
|
resolved = call_action(
|
||||||
|
{
|
||||||
|
'action': 'approval_queue_resolve',
|
||||||
|
'args': {'id': args.id, 'decision': args.decision, 'note': 'resolved by host gog executor', 'notify_on_resolve': False},
|
||||||
|
'request_id': f'resolve-{args.id}',
|
||||||
|
},
|
||||||
|
base_url=args.base_url,
|
||||||
|
path=args.path,
|
||||||
|
secret_header=args.secret_header,
|
||||||
|
secret=secret,
|
||||||
|
)
|
||||||
|
if not resolved.get('ok'):
|
||||||
|
print(json.dumps(resolved, indent=2))
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
result = (resolved.get('result') or {})
|
||||||
|
item = result.get('item') or {}
|
||||||
|
kind = item.get('kind') or ''
|
||||||
|
|
||||||
|
if args.decision == 'reject':
|
||||||
|
print(json.dumps({'resolved': resolved, 'executed': False, 'reason': 'rejected'}, indent=2))
|
||||||
|
return
|
||||||
|
|
||||||
|
if result.get('executed') is True:
|
||||||
|
print(json.dumps({'resolved': resolved, 'executed': True, 'driver': 'n8n'}, indent=2))
|
||||||
|
return
|
||||||
|
|
||||||
|
if kind not in {'email_draft', 'calendar_event'}:
|
||||||
|
print(json.dumps({'resolved': resolved, 'executed': False, 'reason': f'no host executor for kind {kind}'}, indent=2))
|
||||||
|
return
|
||||||
|
|
||||||
|
account = gog_account(args.account)
|
||||||
|
env = os.environ.copy()
|
||||||
|
env['GOG_ACCOUNT'] = account
|
||||||
|
|
||||||
|
if kind == 'email_draft':
|
||||||
|
cmd, tmpfile = build_email_command(item, account, args.dry_run)
|
||||||
|
op = 'gmail.drafts.create'
|
||||||
|
success_status = 'draft_created' if not args.dry_run else 'dry_run'
|
||||||
|
else:
|
||||||
|
cmd = build_calendar_command(item, account, args.dry_run)
|
||||||
|
tmpfile = None
|
||||||
|
op = 'calendar.create'
|
||||||
|
success_status = 'event_created' if not args.dry_run else 'dry_run'
|
||||||
|
|
||||||
|
try:
|
||||||
|
code, stdout, stderr = run(cmd, env=env)
|
||||||
|
finally:
|
||||||
|
if tmpfile:
|
||||||
|
try:
|
||||||
|
Path(tmpfile).unlink(missing_ok=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if code != 0:
|
||||||
|
execution = {
|
||||||
|
'driver': 'gog',
|
||||||
|
'op': op,
|
||||||
|
'status': 'failed',
|
||||||
|
'account': account,
|
||||||
|
'dry_run': args.dry_run,
|
||||||
|
'stderr': stderr.strip(),
|
||||||
|
'stdout': stdout.strip(),
|
||||||
|
}
|
||||||
|
attach = attach_execution(item['id'], execution, base_url=args.base_url, path=args.path, secret_header=args.secret_header, secret=secret)
|
||||||
|
print(json.dumps({'resolved': resolved, 'execution': execution, 'attach': attach}, indent=2))
|
||||||
|
raise SystemExit(code)
|
||||||
|
|
||||||
|
parsed = parse_json(stdout) if stdout.strip() else None
|
||||||
|
execution = {
|
||||||
|
'driver': 'gog',
|
||||||
|
'op': op,
|
||||||
|
'status': success_status,
|
||||||
|
'account': account,
|
||||||
|
'dry_run': args.dry_run,
|
||||||
|
'result': parsed,
|
||||||
|
}
|
||||||
|
attach = attach_execution(item['id'], execution, base_url=args.base_url, path=args.path, secret_header=args.secret_header, secret=secret)
|
||||||
|
print(json.dumps({'resolved': resolved, 'execution': execution, 'attach': attach}, indent=2))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -42,6 +42,7 @@ ROUTER_SNIPPETS = [
|
|||||||
'approval_queue_add',
|
'approval_queue_add',
|
||||||
'approval_queue_list',
|
'approval_queue_list',
|
||||||
'approval_queue_resolve',
|
'approval_queue_resolve',
|
||||||
|
'approval_history_attach_execution',
|
||||||
'fetch_and_normalize_url',
|
'fetch_and_normalize_url',
|
||||||
'inbound_event_filter',
|
'inbound_event_filter',
|
||||||
'unknown_action',
|
'unknown_action',
|
||||||
|
|||||||
Reference in New Issue
Block a user