{
  "name": "Bridge_Finance_BOT",
  "nodes": [
    {
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {}
      },
      "id": "24555f71-2031-45be-b82a-d7e19e362ecd",
      "name": "Telegram: user DM",
      "type": "n8n-nodes-base.telegramTrigger",
      "typeVersion": 1.1,
      "position": [
        240,
        200
      ],
      "disabled": true
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 0 */4 * * *"
            }
          ]
        }
      },
      "id": "5df399d4-ec17-4419-a353-6543a1885b20",
      "name": "Schedule: daily",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        240,
        400
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 0 9 * * 1"
            }
          ]
        }
      },
      "id": "5717a861-6bdd-4522-a4b6-ca03be986308",
      "name": "Schedule: weekly",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        240,
        600
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "bridge-finance-invoke",
        "responseMode": "lastNode",
        "options": {}
      },
      "id": "358bbf01-ceb8-4180-bfb7-0c92cc16e2ee",
      "name": "Webhook: invoke",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        240,
        800
      ]
    },
    {
      "parameters": {
        "jsCode": "const src = $input.all()[0];\n// \u2500\u2500 Loop prevention guard \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst _incomingBody = (src.json && src.json.body) ? src.json.body : {};\nconst _attemptCount = (_incomingBody.attempt_count !== undefined)\n    ? parseInt(_incomingBody.attempt_count, 10) || 0 : 0;\nif (_attemptCount >= 1) {\n    return [{ json: { loop_detected: true, original_task: _incomingBody,\n        bot_name: 'finance',\n        task: 'loop_escalation', user_text: null, chat_id: null, from_agent: 'loop_guard',\n        now_iso: new Date().toISOString(), daily_spec: [], weekly_spec: [] } }];\n}\n// \u2500\u2500 End loop prevention \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst messageId = (src.json && src.json.message && src.json.message.message_id)\n    ? String(src.json.message.message_id) : null;\n\nlet task = 'scheduled_tick';\nlet user_text = null;\nlet chat_id = null;\nlet from_agent = 'system';\n\nif (src.json && src.json.message && src.json.message.text) {\n    task = 'user_dm';\n    user_text = src.json.message.text;\n    chat_id = src.json.message.chat && src.json.message.chat.id;\n    from_agent = 'telegram';\n} else if (src.json && src.json.body && src.json.body.from_agent) {\n    task = 'agent_invoke';\n    user_text = src.json.body.user_text || src.json.body.objective || null;\n    chat_id = src.json.body.chat_id || null;\n    from_agent = src.json.body.from_agent;\n}\n\nconst now = new Date();\nconst now_iso = now.toISOString();\n\nreturn [{ json: {\n    bot_name: 'finance',\n    task,\n    user_text,\n    chat_id,\n    from_agent,\n    now_iso,\n    message_id: messageId,\n    daily_spec: [],\n    weekly_spec: [],\n} }];"
      },
      "id": "7b3694f3-10e3-41fc-b5de-ee9b42fbe2be",
      "name": "Build task payload",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        460,
        400
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT (value = 'true') AS enabled FROM bridge.system_limits WHERE key = 'ceo_bot_enabled';",
        "options": {}
      },
      "id": "f182e9bf-63f7-4a1c-8f2e-7aa7cef6a34b",
      "name": "Read enabled flag",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        680,
        400
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "enabled_check",
              "leftValue": "={{ $json.enabled }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "9cf6ca25-39a2-4fe9-949e-02bb445cdf61",
      "name": "If enabled",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        900,
        400
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT COALESCE(jsonb_agg(row_to_json(m) ORDER BY m.priority_rank, m.created_at), '[]'::jsonb)        AS open_inbox FROM (   SELECT memo_id, from_agent, memo_type, priority, subject, body_json,          created_at, related_lead_id,          CASE priority WHEN 'urgent' THEN 0 WHEN 'high' THEN 1                        WHEN 'normal' THEN 2 ELSE 3 END AS priority_rank   FROM bridge.agent_memos   WHERE to_agent IN ('finance', 'all')     AND status = 'open'   LIMIT 20 ) m;",
        "options": {}
      },
      "id": "fdef54ec-6dd1-46a9-8f4f-9255cd5873f0",
      "name": "Fetch open inbox",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        1120,
        400
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT jsonb_build_object(\n    'now_utc', NOW(),\n    'kpi_last_24h', (\n        SELECT jsonb_build_object(\n            'leads_created',     (SELECT COUNT(*)::int FROM bridge.leads WHERE created_at >= NOW() - INTERVAL '24 hours'),\n            'demos_built',       (SELECT COUNT(*)::int FROM bridge.website_projects WHERE created_at >= NOW() - INTERVAL '24 hours'),\n            'outreach_sent',     (SELECT COUNT(*)::int FROM bridge.outreach_messages WHERE direction='outbound' AND sent_at >= NOW() - INTERVAL '24 hours'),\n            'interested_replies',(SELECT COUNT(*)::int FROM bridge.leads WHERE marketing_status='Interested' AND updated_at >= NOW() - INTERVAL '24 hours'),\n            'invoices_paid',     (SELECT COUNT(*)::int FROM bridge.leads WHERE finance_status='Paid' AND updated_at >= NOW() - INTERVAL '24 hours'),\n            'expenses_usd',      (SELECT ROUND(COALESCE(SUM(amount_usd),0)::numeric, 4) FROM bridge.expenses WHERE occurred_at >= NOW() - INTERVAL '24 hours')\n        )\n    ),\n    'kpi_last_7d', (\n        SELECT jsonb_build_object(\n            'leads_created',     (SELECT COUNT(*)::int FROM bridge.leads WHERE created_at >= NOW() - INTERVAL '7 days'),\n            'demos_built',       (SELECT COUNT(*)::int FROM bridge.website_projects WHERE created_at >= NOW() - INTERVAL '7 days'),\n            'outreach_sent',     (SELECT COUNT(*)::int FROM bridge.outreach_messages WHERE direction='outbound' AND sent_at >= NOW() - INTERVAL '7 days'),\n            'interested_replies',(SELECT COUNT(*)::int FROM bridge.leads WHERE marketing_status='Interested' AND updated_at >= NOW() - INTERVAL '7 days'),\n            'invoices_paid',     (SELECT COUNT(*)::int FROM bridge.leads WHERE finance_status='Paid' AND updated_at >= NOW() - INTERVAL '7 days'),\n            'expenses_usd',      (SELECT ROUND(COALESCE(SUM(amount_usd),0)::numeric, 4) FROM bridge.expenses WHERE occurred_at >= NOW() - INTERVAL '7 days')\n        )\n    ),\n    'urgent_memos_across_ecosystem', (\n        SELECT COALESCE(jsonb_agg(row_to_json(m)), '[]'::jsonb)\n        FROM (\n            SELECT memo_id, from_agent, to_agent, subject, priority, created_at, body_json\n            FROM bridge.agent_memos\n            WHERE status='open' AND priority IN ('urgent','high')\n            ORDER BY priority, created_at\n            LIMIT 15\n        ) m\n    ),\n    'researcher_proposals_open', (\n        SELECT COALESCE(jsonb_agg(row_to_json(p)), '[]'::jsonb)\n        FROM (\n            SELECT memo_id, subject, body_json, created_at\n            FROM bridge.agent_memos\n            WHERE from_agent='researcher' AND memo_type='proposal' AND status='open'\n            ORDER BY created_at\n            LIMIT 10\n        ) p\n    ),\n    'campaign_priorities', (\n        SELECT COALESCE(jsonb_agg(row_to_json(c)), '[]'::jsonb)\n        FROM (\n            SELECT campaign_target_id, niche, city, priority, active_flag,\n                   daily_lead_target, daily_website_limit\n            FROM bridge.campaign_targets\n            ORDER BY priority DESC, niche, city\n            LIMIT 20\n        ) c\n    )\n) AS context;",
        "options": {}
      },
      "id": "0e8526df-2c3a-4463-8e97-69af7529e3f7",
      "name": "Fetch bot context",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        1120,
        600
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const _buildTask = $('Build task payload').first().json;\nif (_buildTask.loop_detected) {\n    return [{ json: {\n        message: JSON.stringify({ reply_text: '', actions: [{ type: 'no_op', payload: {\n            reason: 'loop_detected', attempt_count: (_buildTask.original_task && _buildTask.original_task.attempt_count) || 1\n        }}]}),\n        session_id: 'bridge-loop-guard-' + Date.now(),\n        task_kind: 'loop_escalation', user_chat_id: null,\n        bot_name: _buildTask.bot_name || 'unknown', _pre_formed: true\n    }}];\n}\n\nconst task = $('Build task payload').first().json;\n// \u2500\u2500 Memory auto-block check (non-blocking) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet _autoBlockActive = false;\nconst _querySubject = task.user_text || task.subject || '';\nif (_querySubject && _querySubject.length > 5) {\n    try {\n        const _memCheck = await $http.post(\n            `${$env.SUPER_AGENT_URL || 'https://super-agent-production.up.railway.app'}/webhook/memory-query`,\n            { project_name: _querySubject.slice(0, 100), api_key: $env.N8N_API_KEY || '' }\n        );\n        if (_memCheck && _memCheck.data && _memCheck.data.auto_block === true) {\n            _autoBlockActive = true;\n            return [{ json: {\n                message: JSON.stringify({\n                    reply_text: `\ud83d\udeab AUTO-BLOCK: Project has been rejected ${_memCheck.data.rejection_count} time(s). Route to CRO then CEO for override.`,\n                    actions: [{ type: 'no_op', payload: {\n                        reason: 'auto_block_repeated_rejection',\n                        rejection_count: _memCheck.data.rejection_count,\n                        similar_projects: _memCheck.data.similar_projects\n                    }}]\n                }),\n                session_id: `bridge-auto-block-${Date.now()}`,\n                task_kind: 'auto_block', user_chat_id: task.chat_id,\n                bot_name: task.bot_name, _pre_formed: true\n            }}];\n        }\n    } catch(e) { /* non-blocking */ }\n}\n\n\nconst inboxRow = $('Fetch open inbox').first().json || {};\nconst inbox = Array.isArray(inboxRow.open_inbox) ? inboxRow.open_inbox : [];\nconst ctxRow = $('Fetch bot context').first().json || {};\nconst ctx = ctxRow.context ? [ctxRow.context] : [ctxRow];\n\nconst globalProtocol = `\n\nGLOBAL OPERATING PROTOCOL (non-negotiable):\n1. ALWAYS respond in valid JSON only. No prose, no markdown outside JSON strings.\n2. EXACT response structure: {\"reply_text\": \"...\", \"actions\": [...]}\n3. reply_text: human-readable Telegram message. Brief, role-appropriate.\n4. actions: array of {type, payload} objects. Empty array if no actions.\n5. NEVER include PLACEHOLDER, PASTE_, TBD, INSERT_HERE values in any field.\n6. NEVER mention internal tools (n8n, shell, SQL, workflow IDs) in reply_text.\n7. If required data is missing \u2192 return no_op with status \"need_input\" + list exact missing fields.\n8. If blocked \u2192 escalate to Chief of Staff. NEVER retry autonomously.\n9. MAX_RETRIES = 1. If attempt_count >= 1 in incoming body_json \u2192 return no_op immediately.\n`;\n\nconst system = \"You are Bridge_Finance_BOT, the financial intelligence layer of Bridge Digital Solutions. You are the company's real-time CFO \\u2014 monitoring costs, revenue, token usage, and agent activity to give CEO and COS full financial visibility without manual reporting.\\n\\nROLE\\nTrack all costs, revenue streams, and token consumption. Produce structured financial reports. Flag budget anomalies immediately. Calculate ROI per campaign. Ensure the company never runs blind on spending or cash position.\\n\\nDAILY REPORT (07:00 UTC) \\u2014 send via reply_text:\\n\\ud83d\\udcb0 DAILY BRIDGE FINANCE REPORT\\nDate: {date}\\nAI Costs (24h): ${ai_cost}\\nRevenue (24h): ${revenue}\\nNet Margin: {margin}%\\nAgent calls: {agent_calls}\\nActive campaigns: {campaigns}\\n\\u26a0\\ufe0f Alerts: {alerts}\\n\\nWEEKLY P&L SUMMARY (Monday 08:00 UTC) \\u2014 send to CEO via memo:\\nInclude 7-day totals, top 3 cost drivers, best ROI campaign, and budget recommendation.\\n\\nBUDGET ALERT THRESHOLD: Flag to CEO immediately if daily AI cost exceeds $5 USD.\\n\\nDB QUERIES (read-only):\\n  SELECT * FROM bridge.expenses WHERE created_at >= NOW() - INTERVAL '24h'\\n  SELECT * FROM bridge.billing_records WHERE created_at >= NOW() - INTERVAL '7d'\\n  SELECT bot_name, COUNT(*) as calls FROM bridge.workflow_events     WHERE created_at >= NOW() - INTERVAL '24h' GROUP BY bot_name\\n\\nENABLED FLAG: Check bridge.system_limits WHERE key = 'finance_bot_enabled' before acting. If value != 'true', emit no_op with reason='finance_bot_disabled'.\\n\\nESCALATE WHEN: daily AI cost > $5, revenue drops >20% vs prior day, any billing anomaly, any agent running >3x normal call volume.\\n\\nOUTPUT STYLE: Emoji-rich for human-facing Telegram messages. Structured JSON for agent memos. Never guess figures \\u2014 only report what you can read from DB queries.\";\n\nconst contextBlock = JSON.stringify({\n    now: task.now_iso,\n    bot: task.bot_name,\n    task_kind: task.task,\n    open_inbox: inbox,\n    bot_context: ctx,\n    daily_cadence: task.daily_spec,\n    weekly_cadence: task.weekly_spec,\n}, null, 2);\n\nconst taskBlock = task.task === 'user_dm'\n    ? `The user sent you this message on Telegram:\\n${task.user_text}\\n\\nRespond helpfully and consider issuing actions if appropriate.`\n    : task.task === 'scheduled_tick'\n      ? `This is a scheduled cadence run. Produce the appropriate financial report or check for anomalies.`\n      : `An inter-agent invocation arrived from '${task.from_agent}': ${task.user_text || 'no message body'}. Decide how to respond.`;\n\nconst outputGuard = `\\n\\n[OUTPUT FORMAT]\\nReturn ONLY a JSON object (no prose, no markdown fences):\\n{\\n  \"reply_text\": \"<Telegram message or empty string>\",\\n  \"actions\": [\\n    {\"type\": \"memo\",     \"payload\": {\"to_agent\": \"ceo|chief_of_staff|all\", \"memo_type\": \"finance_report|alert|status\", \"priority\": \"urgent|high|normal|low\", \"subject\": \"...\", \"body_json\": {...}}},\\n    {\"type\": \"query\",    \"payload\": {\"sql\": \"<safe single SELECT>\"}},\\n    {\"type\": \"no_op\",    \"payload\": {\"reason\": \"...\"}}\\n  ]\\n}\\nKeep actions <= 5.`;\n\nconst fullMessage = `${system}${globalProtocol}\\n\\n[CONTEXT]\\n${contextBlock}\\n\\n[TASK]\\n${taskBlock}${outputGuard}`;\n\nreturn [{ json: {\n    message: fullMessage,\n    session_id: `bridge-finance-${task.now_iso.slice(0,16).replace(/[:T-]/g,'')}`,\n    task_kind: task.task,\n    user_chat_id: task.chat_id,\n    bot_name: task.bot_name,\n} }];"
      },
      "id": "2aeed23d-a2f9-475f-9f39-f3e93cbeeef6",
      "name": "Assemble prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1340,
        400
      ]
    },
    {
      "id": "2041d81d-bc49-4d69-9971-9b919d943ed3",
      "name": "super-agent /webhook/bot-engine",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1560,
        400
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ $env.SUPER_AGENT_URL || 'https://super-agent-production.up.railway.app' }}/webhook/bot-engine",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={ \"bot_name\": {{ JSON.stringify($json.bot_name || \"unknown\") }}, \"task_block\": {{ JSON.stringify($json.message || \"\") }}, \"session_id\": {{ JSON.stringify($json.session_id || \"default\") }}, \"task_kind\": {{ JSON.stringify($json.task || \"agent_invoke\") }}, \"api_key\": {{ JSON.stringify($env.N8N_API_KEY || \"\") }} }",
        "options": {
          "timeout": 180000,
          "response": {
            "response": {
              "neverError": true
            }
          },
          "retryOnFail": true,
          "maxTries": 3,
          "waitBetweenTries": 4000
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Passthrough \u2014 bot-engine already parsed, validated, and risk-annotated the response.\nconst _upstream = $('Assemble prompt').first().json;\nif (_upstream && _upstream._pre_formed) {\n    let _pfActions;\n    try { _pfActions = JSON.parse(_upstream.message).actions; } catch(e) { _pfActions = []; }\n    return [{ json: {\n        reply_text: '',\n        actions: [{ type: 'no_op', risk: 'low',\n            payload: (_pfActions[0] && _pfActions[0].payload) || { reason: 'loop_guard' } }],\n        bot_name: _upstream.bot_name, user_chat_id: _upstream.user_chat_id,\n        task_kind: 'loop_escalation', model_used: 'loop_guard', parse_error: null\n    }}];\n}\nconst upstream = $('Assemble prompt').first().json;\n\n// \u2500\u2500 Telegram personality formatting \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction _formatTgMsg(raw, taskKind) {\n  if (!raw || raw.length < 3) return raw;\n\n  // Parse ISO timestamp for the footer\n  const _ts = new Date().toLocaleTimeString('en-GB', {hour:'2-digit',minute:'2-digit',timeZone:'UTC'}) + ' UTC';\n\n  // Build structured header\n  const _kind = (taskKind || 'report').replace(/_/g,' ');\n  let header = '\ud83d\udcca *Finance & Accounting*\\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\\n';\n\n  // Transform the body: bold lines that look like labels, add section icons\n  let body = raw\n    .replace(/\\*\\*/g, '*')           // normalise bold markers\n    .replace(/^(#+)\\s+(.+)$/gm, (_, h, t) => '*' + t + '*')   // ## heading \u2192 bold\n    .replace(/^[-\u2022]\\s+/gm, '  \ud83d\udcb5 ')                        // bullet \u2192 section icon\n    .replace(/^(\\w[^:\\n]{2,40}):\\s*(.+)$/gm, '*$1:* $2')    // Key: val \u2192 bold key\n    .replace(/ERROR|FAILED|CRITICAL|BLOCKED/g, '\ud83d\udcb8 $&')     // flag errors\n    .replace(/SUCCESS|COMPLETE|DONE|APPROVED/gi, '\u2705 $&')     // flag successes\n    .replace(/ACTION:|action:/g, '\ud83c\udfe6 *ACTION:*')          // highlight actions\n    ;\n\n  // Escape Markdown special chars NOT inside existing bold spans\n  // (Telegram Markdown v1: escape [ ] only)\n  body = body.replace(/([\\[\\]])/g, '\\\\$1');\n\n  const footer = '\\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\\n_\ud83d\udcb3 Finance \u00b7 Bridge OS_ \u00b7 _' + _ts + '_';\n\n  return header + body + footer;\n}\n\n// Apply formatting to reply_text if there is one\nif ($json && $json.reply_text && $json.reply_text.length > 3) {\n  const _taskKind = $json.task_kind || upstream && upstream.task_kind || '';\n  $json.reply_text = _formatTgMsg($json.reply_text, _taskKind);\n}\n\nreturn [{ json: {\n    reply_text:  $json.reply_text  || '',\n    actions:     Array.isArray($json.actions) ? $json.actions : [],\n    model_used:  $json.model_used  || 'unknown',\n    parse_error: $json.parse_error || null,\n    bot_name:    upstream.bot_name,\n    user_chat_id: upstream.user_chat_id,\n    task_kind:   upstream.task_kind,\n} }];"
      },
      "id": "f6c75db5-6557-4f8f-9d70-660baced655e",
      "name": "Parse response + risk tag",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1780,
        400
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "has_reply",
              "leftValue": "={{ ($json.reply_text || '').trim().length }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "gt"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "2059cffa-1a10-40b8-b174-c289ba9706e4",
      "name": "If has reply",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2000,
        200
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://api.telegram.org/bot{{$env.BRIDGE_FINANCE_BOT_TOKEN || $env.Bridge_CEO_BOT}}/sendMessage",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json; charset=utf-8"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={ \"chat_id\": {{ $('Parse response + risk tag').first().json.user_chat_id || $env.BRIDGE_ADMIN_TELEGRAM_CHAT_ID }}, \"text\": \"{{ $('Parse response + risk tag').first().json.reply_text || '(no reply)' }}\" , \"parse_mode\": \"Markdown\" }",
        "options": {
          "timeout": 15000,
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      },
      "id": "f0678089-3d1a-4b57-8f0b-3f35ecd4d241",
      "name": "Reply on Telegram",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2880,
        400
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://super-agent-production.up.railway.app/memory/ingest",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Memory-Secret",
              "value": "={{$env.MEMORY_INGEST_SECRET}}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={ \"memories\": [ { \"content\": {{ JSON.stringify(($('Parse response + risk tag').first().json.reply_text || '(no reply)') + ' [actions=' + (($('Parse response + risk tag').first().json.actions || []).map(a => a.type).join(',')) + ']') }}, \"memory_type\": \"decision\", \"importance\": 3, \"source\": \"bridge_ceo_bot\"}]}",
        "options": {
          "timeout": 15000,
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      },
      "id": "444e11b0-7bd8-4653-a2b4-1ca5e4f5f532",
      "name": "Memory ingest",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        3100,
        400
      ]
    },
    {
      "parameters": {
        "jsCode": "const p = $json;\nconst out = [];\nfor (const a of (p.actions || [])) {\n    out.push({ json: {\n        action_type: a.type,\n        action_risk: a.risk,\n        action_payload: a.payload,\n        bot_name: p.bot_name,\n        user_chat_id: p.user_chat_id,\n        task_kind: p.task_kind,\n        model_used: p.model_used,\n        reply_text: p.reply_text,\n    }});\n}\n// Always emit at least one item so the reply path runs even when no actions.\nif (out.length === 0) {\n    out.push({ json: {\n        action_type: 'no_op',\n        action_risk: 'low',\n        action_payload: { reason: 'no actions from LLM' },\n        bot_name: p.bot_name,\n        user_chat_id: p.user_chat_id,\n        task_kind: p.task_kind,\n        model_used: p.model_used,\n        reply_text: p.reply_text,\n    }});\n}\nreturn out;"
      },
      "id": "9e0259e7-803f-4427-b2c8-c1d9e2ec30e9",
      "name": "Split actions",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2000,
        400
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.action_risk }}",
                    "rightValue": "low",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "outputKey": "low"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.action_risk }}",
                    "rightValue": "medium",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "outputKey": "medium"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.action_risk }}",
                    "rightValue": "high",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "outputKey": "high"
            }
          ]
        },
        "options": {
          "allMatchingOutputs": false,
          "fallbackOutput": 2
        }
      },
      "id": "f339f28c-89bb-45ef-87bf-c0aeacf051ee",
      "name": "Switch by risk",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        2220,
        400
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "WITH input AS (\n  SELECT $1::jsonb AS p\n),\nmemo_insert AS (\n  INSERT INTO bridge.agent_memos\n    (from_agent, to_agent, memo_type, priority, subject, body_json, related_lead_id)\n  SELECT\n    $2,\n    COALESCE(p->>'to_agent','all'),\n    COALESCE(p->>'memo_type','finance_report'),\n    COALESCE(p->>'priority','normal'),\n    COALESCE(p->>'subject','(no subject)'),\n    COALESCE(p->'body_json','{}') || '{\"attempt_count\": 0}'::jsonb,\n    (p->>'related_lead_id')::uuid\n  FROM input\n  WHERE COALESCE(p->>'memo_type','finance_report') NOT IN ('query','no_op')\n  RETURNING memo_id, to_agent, memo_type\n)\nSELECT\n  COALESCE(json_agg(row_to_json(memo_insert)), '[]'::json) AS inserted_memos\nFROM memo_insert;",
        "options": {
          "queryReplacement": "={{ JSON.stringify($json.action_payload) }},{{ $json.bot_name }},{{ $json.action_type }}"
        }
      },
      "id": "d59ea2b9-055c-49ec-9041-f0303131174d",
      "name": "Execute low-risk action",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        2440,
        300
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://api.telegram.org/bot{{$env.BRIDGE_FINANCE_BOT_TOKEN || $env.Bridge_CEO_BOT}}/sendMessage",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json; charset=utf-8"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={ \"chat_id\": {{$env.BRIDGE_ADMIN_TELEGRAM_CHAT_ID}}, \"text\": {{ JSON.stringify((function(){ const inbox = ($('Fetch open inbox').first().json && $('Fetch open inbox').first().json.open_inbox) || []; const reviews = inbox.filter(m => m.from_agent==='bizdev' && m.memo_type==='revenue_review'); const latest = reviews.length ? reviews[reviews.length-1] : null; const r = latest && latest.body_json || null; const fromAgent = $json.bot_name || 'agent'; const action = $json.action_type || 'unknown'; const payload = $json.action_payload || {}; const subject = payload.subject || 'No subject'; const lines = []; lines.push('NEW PROJECT APPROVAL REQUEST'); lines.push(''); lines.push('From: ' + fromAgent + ' | Action: ' + action); lines.push('Subject: ' + subject); lines.push(''); if (r) { lines.push('--- REVENUE VALIDATION (Business Dev) ---'); if (r.customer_segments) lines.push('Customers: ' + (Array.isArray(r.customer_segments) ? r.customer_segments.join(', ') : r.customer_segments)); if (r.distribution_channels) lines.push('Channels: ' + (Array.isArray(r.distribution_channels) ? r.distribution_channels.join(', ') : r.distribution_channels)); if (r.revenue_model) lines.push('Revenue Model: ' + r.revenue_model); if (r.partnership_opportunities) lines.push('Partnerships: ' + (Array.isArray(r.partnership_opportunities) ? r.partnership_opportunities.join(', ') : r.partnership_opportunities)); if (r.go_to_market) lines.push('Go-To-Market: ' + r.go_to_market); if (r.time_to_first_revenue_weeks) lines.push('Time to Revenue: ' + r.time_to_first_revenue_weeks + ' weeks'); if (r.revenue_risks) lines.push('Risks: ' + (Array.isArray(r.revenue_risks) ? r.revenue_risks.join('; ') : r.revenue_risks)); if (r.confidence) lines.push('Confidence: ' + r.confidence); if (r.verdict) lines.push('BizDev Verdict: ' + r.verdict); } else { lines.push('!! NO RESOLVED REVENUE_REVIEW FROM BIZDEV !!'); lines.push('This action has been routed to bizdev for revenue validation.'); lines.push('No human approval will be requested until validation completes.'); } lines.push(''); lines.push('Payload: ' + JSON.stringify(payload).slice(0,400)); lines.push(''); lines.push('Reply APPROVE ' + fromAgent + ' ' + action + ' to proceed, or ignore to reject.'); return lines.join('\\n'); })()) }} , \"parse_mode\": \"Markdown\" }",
        "options": {
          "timeout": 15000,
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      },
      "id": "740c65a1-db24-4053-a610-9b32202ab5e7",
      "name": "Medium-risk: approval DM",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2440,
        500
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO bridge.agent_memos (from_agent, to_agent, memo_type, priority, subject, body_json) VALUES ($1, 'chief_of_staff', 'approval_request', 'high',         'Pending medium-risk action awaiting user approval', $2::jsonb) RETURNING memo_id;",
        "options": {
          "queryReplacement": "={{ $json.bot_name }},{{ JSON.stringify({action_type: $json.action_type, action_payload: $json.action_payload}) }}"
        }
      },
      "id": "f3219b21-818a-48b4-8984-59e2bbdd0cb0",
      "name": "Medium-risk: log pending",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        2660,
        500
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO bridge.agent_memos (from_agent, to_agent, memo_type, priority, subject, body_json) VALUES ('ceo', 'ceo', 'escalation', 'urgent',         'HIGH-RISK action requires manual execution', $1::jsonb) RETURNING memo_id;",
        "options": {
          "queryReplacement": "={{ JSON.stringify({action_type: $json.action_type, action_payload: $json.action_payload}) }}"
        }
      },
      "id": "1f8ac194-cf6e-4cf1-96e4-fa31d4cbd21b",
      "name": "High-risk: escalate to CEO",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        2440,
        700
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://api.telegram.org/bot{{$env.BRIDGE_FINANCE_BOT_TOKEN || $env.Bridge_CEO_BOT}}/sendMessage",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json; charset=utf-8"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={ \"chat_id\": {{$env.BRIDGE_ADMIN_TELEGRAM_CHAT_ID}}, \"text\": \"\\ud83d\\udea8  *HIGH-RISK action blocked (escalated to CEO)*\\n\\n  From:    {{$json.bot_name}}\\n  Action:  `{{$json.action_type}}`\\n  Payload: {{ JSON.stringify($json.action_payload).slice(0,600) }}\\n\\nThis action was NOT executed. Review the agent_memos table for the escalation.\\n\\u2014 Bridge Agents\", \"parse_mode\": \"Markdown\" }",
        "options": {
          "timeout": 15000,
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      },
      "id": "d1c1c7cc-5766-4108-9cbb-3f63a73593e3",
      "name": "High-risk: urgent DM",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2660,
        700
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={\"ok\": true, \"bot\": {{JSON.stringify($(\"Parse response + risk tag\").first().json.bot_name)}}}",
        "options": {}
      },
      "id": "9c7fc69b-768c-4757-870f-a77b765f4a45",
      "name": "Respond OK",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        3320,
        400
      ]
    }
  ],
  "connections": {
    "Telegram: user DM": {
      "main": [
        [
          {
            "node": "Build task payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule: daily": {
      "main": [
        [
          {
            "node": "Build task payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule: weekly": {
      "main": [
        [
          {
            "node": "Build task payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook: invoke": {
      "main": [
        [
          {
            "node": "Build task payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build task payload": {
      "main": [
        [
          {
            "node": "Read enabled flag",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read enabled flag": {
      "main": [
        [
          {
            "node": "If enabled",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If enabled": {
      "main": [
        [
          {
            "node": "Fetch open inbox",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Fetch open inbox": {
      "main": [
        [
          {
            "node": "Fetch bot context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch bot context": {
      "main": [
        [
          {
            "node": "Assemble prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Assemble prompt": {
      "main": [
        [
          {
            "node": "super-agent /webhook/bot-engine",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse response + risk tag": {
      "main": [
        [
          {
            "node": "If has reply",
            "type": "main",
            "index": 0
          },
          {
            "node": "Memory ingest",
            "type": "main",
            "index": 0
          },
          {
            "node": "Split actions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If has reply": {
      "main": [
        [
          {
            "node": "Reply on Telegram",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Reply on Telegram": {
      "main": [
        [
          {
            "node": "Respond OK",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Memory ingest": {
      "main": [
        [
          {
            "node": "Respond OK",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split actions": {
      "main": [
        [
          {
            "node": "Switch by risk",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Switch by risk": {
      "main": [
        [
          {
            "node": "Execute low-risk action",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Medium-risk: approval DM",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "High-risk: escalate to CEO",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Execute low-risk action": {
      "main": [
        [
          {
            "node": "Respond OK",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Medium-risk: approval DM": {
      "main": [
        [
          {
            "node": "Medium-risk: log pending",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Medium-risk: log pending": {
      "main": [
        [
          {
            "node": "Respond OK",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "High-risk: escalate to CEO": {
      "main": [
        [
          {
            "node": "High-risk: urgent DM",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "High-risk: urgent DM": {
      "main": [
        [
          {
            "node": "Respond OK",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "super-agent /webhook/bot-engine": {
      "main": [
        [
          {
            "node": "Parse response + risk tag",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "id": "df7cb176-5069-481d-9c98-717db050f93a"
}