Files
ajarbot/n8n_workflows/garvis_webhook_v2.json
Jordan Ramos 916f86725d feat: RSO observation system, child safety, Discord adapter, Telegram watchdog, email attachments
Core agent improvements:
- RSO (Relevance Scoring & Observation) system: interaction_logger, memory_scorer, signal_detector
- Memory access logging (memory_access_log table) for relevance scoring; high-signal turn detection
- Rich conversation storage for notable turns; compact_conversation truncates long user messages
- Task-type classifier (query/action/analysis/creative) for observation tagging
- Nested sub-agent visibility: deep delegations now register against the main agent's manager

Child safety (Gabriel profile):
- child_safety.py: filtering, audit logging, prompt constants for restricted sessions
- .kiro/specs/child-safety-profile: requirements, design, tasks specs
- GABRIEL_BOT_PROPOSAL.md: initial proposal doc
- Reduced context window (10 msgs) and tutor-mode identity for restricted users

Telegram adapter:
- Polling watchdog: auto-restarts updater if polling drops unexpectedly
- get_me() with exponential-backoff retry on NetworkError at startup
- Correct stop() ordering: signal watchdog before cancelling tasks

Email / Gmail:
- send_email: supports file attachments (attachments list param)
- get_email: surfaces attachment metadata in response

Scheduled tasks / weather:
- Remove OpenWeatherMap API calls from morning-weather task; use wttr.in exclusively
- New scheduled tasks and scheduler state persistence

Discord:
- adapters/discord/__init__.py scaffold
- discord-plugin: MCP plugin for Claude Code Discord integration (server.ts, skills, config)

Infrastructure:
- n8n workflow exports (garvis_webhook, content_pipeline variants)
- memory_workspace: context, homelab-repo-updates, weekly observation summaries, error logs
- UCS C240 migration plan doc
- requirements.txt: new deps
- .claude/settings.json, fix_hooks.py: hook/permission tuning
2026-04-23 07:54:01 -06:00

247 lines
11 KiB
JSON

{
"name": "Garvis Webhook - Bot Actions",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "garvis",
"responseMode": "responseNode",
"options": {}
},
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [260, 300],
"webhookId": "garvis-webhook-001"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "auth1",
"leftValue": "={{ $json.headers['x-garvis-secret'] }}",
"rightValue": "={{ $env.GARVIS_WEBHOOK_SECRET }}",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
}
},
"name": "IF Auth Valid?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [480, 300]
},
{
"parameters": {
"options": {},
"respondWith": "json",
"responseBody": "={\"error\": \"Unauthorized\", \"status\": 401}",
"responseCode": 401
},
"name": "Respond 401",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [700, 500]
},
{
"parameters": {
"rules": {
"values": [
{"conditions": {"conditions": [{"leftValue": "={{ $json.body.action }}", "rightValue": "run_pipeline", "operator": {"type": "string", "operation": "equals"}}]}, "renameOutput": true, "outputKey": "Run Pipeline"},
{"conditions": {"conditions": [{"leftValue": "={{ $json.body.action }}", "rightValue": "check_nas", "operator": {"type": "string", "operation": "equals"}}]}, "renameOutput": true, "outputKey": "Check NAS"},
{"conditions": {"conditions": [{"leftValue": "={{ $json.body.action }}", "rightValue": "check_services", "operator": {"type": "string", "operation": "equals"}}]}, "renameOutput": true, "outputKey": "Check Services"},
{"conditions": {"conditions": [{"leftValue": "={{ $json.body.action }}", "rightValue": "send_message", "operator": {"type": "string", "operation": "equals"}}]}, "renameOutput": true, "outputKey": "Send Message"},
{"conditions": {"conditions": [{"leftValue": "={{ $json.body.action }}", "rightValue": "get_status", "operator": {"type": "string", "operation": "equals"}}]}, "renameOutput": true, "outputKey": "Get Status"},
{"conditions": {"conditions": [{"leftValue": "={{ $json.body.action }}", "rightValue": "get_analytics", "operator": {"type": "string", "operation": "equals"}}]}, "renameOutput": true, "outputKey": "Get Analytics"}
]
},
"options": {}
},
"name": "Switch Action",
"type": "n8n-nodes-base.switch",
"typeVersion": 3,
"position": [700, 300]
},
{
"parameters": {
"mode": "manual",
"duplicateItem": false,
"assignments": {
"assignments": [
{"id": "rp1", "name": "result", "value": "Pipeline triggered manually. Processing DropZone...", "type": "string"},
{"id": "rp2", "name": "action", "value": "run_pipeline", "type": "string"}
]
}
},
"name": "Handle Run Pipeline",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [960, 60]
},
{
"parameters": {
"method": "POST",
"url": "http://192.168.2.40:5000/webapi/entry.cgi",
"sendBody": true,
"bodyParameters": {
"parameters": [
{"name": "api", "value": "SYNO.FileStation.List"},
{"name": "version", "value": "2"},
{"name": "method", "value": "list"},
{"name": "folder_path", "value": "/BlendedFamilyKitchen/DropZone"}
]
}
},
"name": "Handle Check NAS",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [960, 200]
},
{
"parameters": {
"command": "echo '{\"docker\": \"'$(systemctl is-active docker)'\", \"n8n\": \"running\", \"whisper\": \"'$(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:9000/health 2>/dev/null || echo 'unreachable')'\", \"ffmpeg\": \"'$(ffmpeg -version 2>/dev/null | head -1 || echo 'not installed')'\"}'"
},
"name": "Handle Check Services",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [960, 340]
},
{
"parameters": {
"method": "POST",
"url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/sendMessage",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"chat_id\": \"{{ $json.body.chat_id || 'TODO_DEFAULT_CHAT_ID' }}\",\n \"text\": \"{{ $json.body.message }}\",\n \"parse_mode\": \"Markdown\"\n}"
},
"name": "Handle Send Message",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [960, 480]
},
{
"parameters": {
"mode": "manual",
"duplicateItem": false,
"assignments": {
"assignments": [
{"id": "gs1", "name": "status", "value": "operational", "type": "string"},
{"id": "gs2", "name": "workflows", "value": "content_pipeline: inactive, garvis_webhook: active", "type": "string"},
{"id": "gs3", "name": "uptime", "value": "={{ new Date().toISOString() }}", "type": "string"}
]
}
},
"name": "Handle Get Status",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [960, 620]
},
{
"parameters": {
"options": {},
"respondWith": "json",
"responseBody": "={\"success\": true, \"action\": \"{{ $json.body?.action || 'unknown' }}\", \"result\": {{ JSON.stringify($json) }}}",
"responseCode": 200
},
"name": "Respond 200 Success",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [1200, 300]
},
{
"parameters": {
"options": {},
"respondWith": "json",
"responseBody": "={\"error\": \"Unknown action\", \"received\": \"{{ $json.body?.action }}\", \"available\": [\"run_pipeline\", \"check_nas\", \"check_services\", \"send_message\", \"get_status\", \"get_analytics\"]}",
"responseCode": 400
},
"name": "Respond 400 Unknown",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [960, 800]
},
{
"parameters": {
"content": "## 🔐 Garvis Webhook — Auth & Setup\n\n**Endpoint:** POST http://192.168.2.113:5678/webhook/garvis\n\n**Authentication:**\n- Header: x-garvis-secret\n- Validate against env var GARVIS_WEBHOOK_SECRET\n- Returns 401 if invalid\n\n**Request Format:**\n```json\n{\n \"action\": \"run_pipeline|check_nas|check_services|send_message|get_status\",\n \"message\": \"optional message text\",\n \"chat_id\": \"optional telegram chat id\"\n}\n```\n\n**Test with curl:**\n```\ncurl -X POST http://192.168.2.113:5678/webhook/garvis \\\n -H 'Content-Type: application/json' \\\n -H 'x-garvis-secret: YOUR_SECRET' \\\n -d '{\"action\": \"get_status\"}'\n```\n\n**TODO:**\n1. Set GARVIS_WEBHOOK_SECRET in n8n environment variables\n2. Set TELEGRAM_BOT_TOKEN in n8n environment variables\n3. Activate workflow when ready\n4. Test each action endpoint\n5. Add webhook URL to Garvis bot config",
"height": 620,
"width": 520
},
"name": "Sticky - Webhook Setup",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [200, -320]
},
{
"parameters": {
"method": "POST",
"url": "http://192.168.2.40:5000/webapi/entry.cgi",
"sendBody": true,
"bodyParameters": {
"parameters": [
{"name": "api", "value": "SYNO.FileStation.List"},
{"name": "version", "value": "2"},
{"name": "method", "value": "list"},
{"name": "folder_path", "value": "/BlendedFamilyKitchen/Analytics"},
{"name": "sort_by", "value": "crtime"},
{"name": "sort_direction", "value": "desc"},
{"name": "limit", "value": "={{ $json.body.limit || 10 }}"}
]
}
},
"name": "Handle Get Analytics",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [960, 760]
},
{
"parameters": {
"content": "## 🔀 Action Router — Available Actions\n\n**run_pipeline:** Manually triggers the Content Pipeline workflow. Use when Cloe drops a video and doesn't want to wait for the 30-min poll cycle.\n\n**check_nas:** Queries Synology FileStation API to list files in DropZone. Returns file count and names. Useful for: \"Garvis, anything in the drop zone?\"\n\n**check_services:** Runs local service checks — Docker, Whisper, FFmpeg availability. Returns health status JSON. Useful for: \"Garvis, is the pipeline infrastructure healthy?\"\n\n**send_message:** Forwards a message to a Telegram chat via the bot. Requires chat_id and message in request body. Useful for: cross-service notifications.\n\n**get_status:** Returns current n8n workflow states, uptime, and basic system info. Useful for: \"Garvis, n8n status?\"\n\n**TODO:**\n1. Wire run_pipeline to actually trigger Content Pipeline (use n8n Execute Workflow node)\n2. Add NAS auth to check_nas handler\n3. Expand check_services with more endpoints\n4. Add rate limiting / cooldown logic\n5. Add logging to each action handler",
"height": 580,
"width": 520
},
"name": "Sticky - Action Router",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [880, -320]
}
],
"connections": {
"Webhook": {"main": [[{"node": "IF Auth Valid?", "type": "main", "index": 0}]]},
"IF Auth Valid?": {
"main": [
[{"node": "Switch Action", "type": "main", "index": 0}],
[{"node": "Respond 401", "type": "main", "index": 0}]
]
},
"Switch Action": {
"main": [
[{"node": "Handle Run Pipeline", "type": "main", "index": 0}],
[{"node": "Handle Check NAS", "type": "main", "index": 0}],
[{"node": "Handle Check Services", "type": "main", "index": 0}],
[{"node": "Handle Send Message", "type": "main", "index": 0}],
[{"node": "Handle Get Status", "type": "main", "index": 0}],
[{"node": "Handle Get Analytics", "type": "main", "index": 0}],
[{"node": "Respond 400 Unknown", "type": "main", "index": 0}]
]
},
"Handle Run Pipeline": {"main": [[{"node": "Respond 200 Success", "type": "main", "index": 0}]]},
"Handle Check NAS": {"main": [[{"node": "Respond 200 Success", "type": "main", "index": 0}]]},
"Handle Check Services": {"main": [[{"node": "Respond 200 Success", "type": "main", "index": 0}]]},
"Handle Send Message": {"main": [[{"node": "Respond 200 Success", "type": "main", "index": 0}]]},
"Handle Get Status": {"main": [[{"node": "Respond 200 Success", "type": "main", "index": 0}]]},
"Handle Get Analytics": {"main": [[{"node": "Respond 200 Success", "type": "main", "index": 0}]]}
},
"settings": {
"executionOrder": "v1"
}
}