{
  "name": "AI Agent Workflow",
  "nodes": [
    {
      "parameters": {
        "updates": [
          "*"
        ],
        "additionalFields": {}
      },
      "id": "single-92ae6fd3-520c-4f48-a369-24305b23efad",
      "name": "Telegram Trigger",
      "type": "n8n-nodes-base.telegramTrigger",
      "typeVersion": 1,
      "position": [
        -1680,
        1296
      ]
    },
    {
      "parameters": {
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.chatTrigger",
      "typeVersion": 1.4,
      "position": [
        -1680,
        1488
      ],
      "id": "single-806479e3-7e80-4d0f-a4ec-6ec375a0de91",
      "name": "Chat Trigger"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "msg",
              "name": "message_text",
              "value": "={{ ($json.message && ($json.message.text || $json.message.caption)) || $json.chatInput || '' }}",
              "type": "string"
            },
            {
              "id": "uid",
              "name": "user_id",
              "value": "={{ ($json.message && $json.message.from && $json.message.from.id) || $json.sessionId || 'unknown' }}",
              "type": "string"
            },
            {
              "id": "uname",
              "name": "username",
              "value": "={{ ($json.message && $json.message.from && ($json.message.from.username || $json.message.from.first_name)) || 'the user' }}",
              "type": "string"
            },
            {
              "id": "chat",
              "name": "chat_id",
              "value": "={{ ($json.message && $json.message.chat && $json.message.chat.id) || '' }}",
              "type": "string"
            },
            {
              "id": "uup",
              "name": "update_id",
              "value": "={{ $json.update_id || '' }}",
              "type": "string"
            },
            {
              "id": "hv",
              "name": "has_voice",
              "value": "={{ !!($json.message && $json.message.voice) }}",
              "type": "boolean"
            },
            {
              "id": "vfid",
              "name": "voice_file_id",
              "value": "={{ ($json.message && $json.message.voice && $json.message.voice.file_id) || '' }}",
              "type": "string"
            },
            {
              "id": "hp",
              "name": "has_photo",
              "value": "={{ Array.isArray($json.message && $json.message.photo) && $json.message.photo.length > 0 }}",
              "type": "boolean"
            },
            {
              "id": "pfid",
              "name": "photo_file_id",
              "value": "={{ Array.isArray($json.message && $json.message.photo) ? $json.message.photo[$json.message.photo.length - 1].file_id : '' }}",
              "type": "string"
            },
            {
              "id": "src",
              "name": "source",
              "value": "={{ $json.message ? 'telegram' : 'chat' }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "single-48eb5a1b-ae95-425f-8083-1e3d54ed06b3",
      "name": "Normalize Input",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -1456,
        1296
      ]
    },
    {
      "parameters": {
        "jsCode": "const data = $input.first().json || {};\nconst allowedIds = String(process.env.TELEGRAM_ALLOWED_CHAT_IDS || 'YOUR_TELEGRAM_CHAT_ID').split(',').map((id) => id.trim()).filter(Boolean);\nconst source = data.source || (data.chat_id ? 'telegram' : 'chat');\nconst userId = String(data.user_id || '');\nconst chatId = String(data.chat_id || '');\nconst isInteractiveChat = source === 'chat' || userId === 'unknown';\nconst isAllowedTelegram = allowedIds.includes(userId) || allowedIds.includes(chatId);\n\nif (!isInteractiveChat && !isAllowedTelegram) {\n  return [];\n}\n\nconst staticData = $getWorkflowStaticData('global');\nconst seen = staticData.seenUpdates || [];\nconst uid = String(data.update_id || '');\nconst shouldDedup = source === 'telegram' && uid;\n\nif (shouldDedup && seen.includes(uid)) {\n  return [];\n}\n\nif (shouldDedup) {\n  seen.push(uid);\n  if (seen.length > 200) seen.shift();\n  staticData.seenUpdates = seen;\n}\n\nreturn [{ json: data }];"
      },
      "id": "single-guard-001",
      "name": "Whitelist + Dedup",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1232,
        1296
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "v",
                    "leftValue": "={{ $json.has_voice }}",
                    "rightValue": "",
                    "operator": {
                      "type": "boolean",
                      "operation": "true",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "voice"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "p",
                    "leftValue": "={{ $json.has_photo }}",
                    "rightValue": "",
                    "operator": {
                      "type": "boolean",
                      "operation": "true",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "photo"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "t",
                    "leftValue": "={{ $json.message_text }}",
                    "rightValue": "",
                    "operator": {
                      "type": "string",
                      "operation": "notEmpty",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "text"
            }
          ]
        },
        "options": {}
      },
      "id": "single-input-router-001",
      "name": "Input Type Router",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.3,
      "position": [
        -1008,
        1280
      ]
    },
    {
      "parameters": {
        "resource": "file",
        "fileId": "={{ $json.voice_file_id }}",
        "additionalFields": {}
      },
      "id": "single-8285ad4c-f142-4925-8f38-8d839db0a898",
      "name": "Get Voice File",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        -784,
        1104
      ]
    },
    {
      "parameters": {
        "resource": "audio",
        "operation": "transcribe",
        "options": {}
      },
      "id": "single-2e0147af-51eb-4d13-9fb2-399b69723295",
      "name": "Transcribe Voice",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 1.6,
      "position": [
        -560,
        1104
      ],
      "alwaysOutputData": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "vt",
              "name": "message_text",
              "value": "={{ $json.text }}",
              "type": "string"
            },
            {
              "id": "vid",
              "name": "user_id",
              "value": "={{ $('Whitelist + Dedup').item.json.user_id }}",
              "type": "string"
            },
            {
              "id": "vun",
              "name": "username",
              "value": "={{ $('Whitelist + Dedup').item.json.username }}",
              "type": "string"
            },
            {
              "id": "vci",
              "name": "chat_id",
              "value": "={{ $('Whitelist + Dedup').item.json.chat_id }}",
              "type": "string"
            },
            {
              "id": "vhv",
              "name": "input_was_voice",
              "value": "={{ true }}",
              "type": "boolean"
            },
            {
              "id": "vit",
              "name": "input_type",
              "value": "voice",
              "type": "string"
            },
            {
              "id": "fvr",
              "name": "force_voice_reply",
              "value": "={{ true }}",
              "type": "boolean"
            }
          ]
        },
        "options": {}
      },
      "id": "single-07cdf726-13e0-46b8-a735-d0162e1481bc",
      "name": "Prepare Voice Result",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -336,
        1104
      ]
    },
    {
      "parameters": {
        "resource": "file",
        "fileId": "={{ $json.photo_file_id }}",
        "additionalFields": {}
      },
      "id": "single-photo-get-001",
      "name": "Get Photo File",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        -784,
        1296
      ]
    },
    {
      "parameters": {
        "resource": "image",
        "operation": "analyze",
        "modelId": {
          "__rl": true,
          "value": "gpt-4o-mini",
          "mode": "list"
        },
        "text": "=Beschreibe was auf dem Bild zu sehen ist. Wenn es eine Visitenkarte, ein Whiteboard, ein Dokument oder ein Screenshot ist, extrahiere alle relevanten Texte und Daten strukturiert. User-Bildunterschrift: {{ $('Whitelist + Dedup').item.json.message_text || '(keine)' }}",
        "options": {}
      },
      "id": "single-vision-001",
      "name": "Analyze Photo",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 1.6,
      "position": [
        -560,
        1296
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "pt",
              "name": "message_text",
              "value": "=Bild Analyse: {{ $json.content || $json.text || JSON.stringify($json) }}\n\nUser Caption: {{ $('Whitelist + Dedup').item.json.message_text }}",
              "type": "string"
            },
            {
              "id": "pid",
              "name": "user_id",
              "value": "={{ $('Whitelist + Dedup').item.json.user_id }}",
              "type": "string"
            },
            {
              "id": "pun",
              "name": "username",
              "value": "={{ $('Whitelist + Dedup').item.json.username }}",
              "type": "string"
            },
            {
              "id": "pci",
              "name": "chat_id",
              "value": "={{ $('Whitelist + Dedup').item.json.chat_id }}",
              "type": "string"
            },
            {
              "id": "phv",
              "name": "input_was_voice",
              "value": "={{ false }}",
              "type": "boolean"
            }
          ]
        },
        "options": {}
      },
      "id": "single-photo-prep-001",
      "name": "Prepare Photo Result",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -336,
        1296
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "tt",
              "name": "message_text",
              "value": "={{ $json.message_text }}",
              "type": "string"
            },
            {
              "id": "tid",
              "name": "user_id",
              "value": "={{ $json.user_id }}",
              "type": "string"
            },
            {
              "id": "tun",
              "name": "username",
              "value": "={{ $json.username }}",
              "type": "string"
            },
            {
              "id": "tci",
              "name": "chat_id",
              "value": "={{ $json.chat_id }}",
              "type": "string"
            },
            {
              "id": "thv",
              "name": "input_was_voice",
              "value": "={{ false }}",
              "type": "boolean"
            }
          ]
        },
        "options": {}
      },
      "id": "single-text-prep-001",
      "name": "Prepare Text Input",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -336,
        1488
      ]
    },
    {
      "parameters": {
        "jsCode": "const data = $input.first().json || {};\nconst staticData = $getWorkflowStaticData('global');\nconst raw = (data.message_text || '').trim();\nconst lower = raw.toLowerCase().replace(/\\s+/g, ' ').trim();\nconst commandName = (lower.match(/^\\/([a-z0-9_]+)/i) || [])[1] || '';\nfunction commandIs(...aliases) {\n  return aliases.some((alias) => {\n    const normalized = String(alias || '').toLowerCase().replace(/\\s+/g, ' ').trim();\n    return lower === normalized || lower.startsWith(normalized + ' ');\n  });\n}\nfunction commandStarts(...names) {\n  return names.includes(commandName);\n}\nfunction commandText(name) {\n  return raw.replace(new RegExp('^/' + name.replace(/[^a-z0-9_]/gi, '') + '(?:\\\\s+)?', 'i'), '').trim();\n}\nlet expanded = raw;\nlet command = '';\nlet debugEnabled = Boolean(staticData.debugEnabled);\nlet scenario = data.scenario || '';\n\nif (commandIs('/commands', '/help', '/start')) {\n  command = 'commands';\n  expanded = 'Antworte direkt mit FINAL: und liste kurz diese Commands: /heute, /morgen, /fokus, /focus <Minuten> <Aufgabe>, /deepwork <Minuten> <Aufgabe>, /done <Ergebnis>, /stuck <Blocker>, /prep, /shutdown, /snooze <Dauer>, /quiet, /coach_on, /coach_off, /inbox, /leads, /motivate, /memory, /remember <Info>, /decision <Entscheidung>, /health, /test calendar, /test gmail, /test memory, /test morning, /test midday, /test evening, /test premeet, /debug_on, /debug_off, /debug_status, /test_calendar, /test_gmail, /test_memory, /test_morning, /test_midday, /test_evening, /test_premeet.';\n} else if (commandIs('/debug on', '/debug_on')) {\n  staticData.debugEnabled = true;\n  debugEnabled = true;\n  command = 'debug_on';\n  expanded = 'Antworte direkt mit FINAL: Debug-Modus ist aktiviert. Ab jetzt werden Antworten im Chat mit kompakten Debug-Infos erg\u00e4nzt.';\n} else if (commandIs('/debug off', '/debug_off')) {\n  staticData.debugEnabled = false;\n  debugEnabled = false;\n  command = 'debug_off';\n  expanded = 'Antworte direkt mit FINAL: Debug-Modus ist deaktiviert.';\n} else if (commandIs('/debug status', '/debug_status')) {\n  command = 'debug_status';\n  expanded = 'Antworte direkt mit FINAL: Debug-Modus ist ' + (debugEnabled ? 'aktiviert.' : 'deaktiviert.');\n} else if (commandStarts('health')) {\n  command = 'health';\n  expanded = 'Healthcheck starten. Der Workflow pr\u00fcft deterministisch Calendar, Gmail, Memory und den aktiven Modellpfad. Erfinde nichts, antworte am Ende nur mit FINAL: und Statusliste.';\n} else if (commandName === 'test' || commandName.startsWith('test_')) {\n  command = 'test';\n  const aliasTargets = {\n    test_calendar: 'calendar',\n    test_gmail: 'gmail',\n    test_memory: 'memory',\n    test_memory_write: 'memory-write',\n    test_morning: 'morning',\n    test_midday: 'midday',\n    test_evening: 'evening',\n    test_premeet: 'premeet'\n  };\n  const explicitTarget = commandName === 'test' ? commandText('test').toLowerCase() : '';\n  const target = aliasTargets[commandName] || explicitTarget.replace('_write', '-write').trim();\n  if (!target || target === 'help') {\n    expanded = 'Antworte direkt mit FINAL: Test-Commands: /test calendar, /test gmail, /test memory, /test memory-write, /test morning, /test midday, /test evening, /test premeet. Nutze memory-write nur bewusst, weil es einen GitHub-Commit schreibt.';\n  } else if (target === 'calendar') {\n    expanded = 'Teste Calendar Read deterministisch. Nutze keine erfundenen Termine.';\n  } else if (target === 'gmail') {\n    expanded = 'Teste Gmail Search deterministisch. Nutze keine erfundenen Mails.';\n  } else if (target === 'memory') {\n    expanded = 'Teste Memory Read: Fasse den geladenen PERSONAL MEMORY und DECISIONS Kontext in 3 Stichpunkten zusammen. Nutze kein Tool, antworte direkt mit FINAL:.';\n  } else if (target === 'memory-write') {\n    expanded = 'Teste Memory Write bewusst: Speichere mit memory_write target=daily, content=Workflow-Test: Memory Write aus n8n erfolgreich gestartet. Danach best\u00e4tige kurz mit FINAL:.';\n  } else if (['morning', 'midday', 'evening'].includes(target)) {\n    scenario = target;\n    expanded = 'Simuliere den ' + target + ' OpsAgent Heartbeat deterministisch mit denselben Tool Fenstern wie der echte Cron. Nutze keine erfundenen Termine oder Mails.';\n  } else if (target === 'premeet') {\n    scenario = 'premeet';\n    expanded = 'Simuliere den PreMeeting OpsAgent deterministisch. Nutze keine erfundenen Termine.';\n  } else {\n    expanded = 'Antworte direkt mit FINAL: Unbekannter Test. Nutze /test help.';\n  }\n} else if (commandStarts('stuck')) {\n  command = 'stuck';\n  expanded = 'Blockade-Reset starten. Antworte direkt mit FINAL: OpsAgent Ton, knapp, leicht genervt, normales Deutsch: Lage benennen, Reibung reduzieren, n\u00e4chsten Schritt festlegen. Kein Tool.';\n} else if (commandStarts('focus')) {\n  command = 'focus';\n  expanded = 'Fokusblock starten. Antworte direkt mit FINAL: OpsAgent Ton, knapp, leicht genervt, mit Dauer, sichtbarem Ergebnis und klarer Ansage. Kein Tool.';\n} else if (commandStarts('deepwork')) {\n  command = 'deepwork';\n  expanded = 'Deep-Work-Block starten. Antworte direkt mit FINAL: OpsAgent Ton, knapp, leicht genervt, mit Dauer, sichtbarem Ergebnis und St\u00f6rquellen-Regel. Kein Tool.';\n} else if (commandStarts('done')) {\n  command = 'done';\n  const content = commandText('done');\n  expanded = content ? 'Speichere dieses Ergebnis mit memory_write target=daily: ' + content + '. Danach best\u00e4tige kurz, trocken und ohne Manager-Buzzwords.' : 'Erkl\u00e4re kurz, wie ich /done nutze.';\n} else if (commandStarts('prep')) {\n  command = 'prep';\n  expanded = 'Bereite den n\u00e4chsten Termin oder das genannte Thema vor. Hole Kalenderdaten und antworte danach wie OpsAgent: Zielbild, Stakeholder, erster Satz.';\n} else if (commandStarts('shutdown')) {\n  command = 'shutdown';\n  expanded = 'Feierabend-Review starten. Hole morgen aus dem Kalender, schlie\u00dfe heute sauber ab und mache den ersten konkreten Einstieg f\u00fcr morgen entscheidungsf\u00e4hig.';\n} else if (commandStarts('snooze')) {\n  command = 'snooze';\n  expanded = 'OpsAgent pausieren. Antworte direkt mit FINAL: und best\u00e4tige die Pause. Kein Tool.';\n} else if (commandStarts('quiet')) {\n  command = 'quiet';\n  expanded = 'OpsAgent bis heute Abend pausieren. Antworte direkt mit FINAL: und best\u00e4tige die Pause. Kein Tool.';\n} else if (commandIs('/coach_on', '/coach on')) {\n  command = 'coach_on';\n  expanded = 'OpsAgent wieder aktivieren. Antworte direkt mit FINAL: und best\u00e4tige kurz. Kein Tool.';\n} else if (commandIs('/coach_off', '/coach off')) {\n  command = 'coach_off';\n  expanded = 'OpsAgent bis morgen fr\u00fch pausieren. Antworte direkt mit FINAL: und best\u00e4tige kurz. Kein Tool.';\n} else if (commandStarts('heute')) {\n  command = 'heute';\n  expanded = 'Was steht heute alles an? Hole meine Termine f\u00fcr heute aus dem Kalender und fasse meine wichtigsten ungelesenen Mails der letzten 24 Stunden zusammen. Gib mir am Ende eine kurze Ansage: Lage, was jetzt z\u00e4hlt, n\u00e4chster Schritt.';\n} else if (commandStarts('morgen')) {\n  command = 'morgen';\n  expanded = 'Was steht morgen an? Hole meine Termine f\u00fcr morgen aus dem Kalender und sag mir trocken, was ich vorbereiten muss, damit morgen weniger Reibung entsteht.';\n} else if (commandStarts('leads')) {\n  command = 'leads';\n  expanded = 'Zeig mir alle aktiven Leads aus Notion CRM mit Status Lead oder Interessent.';\n} else if (commandStarts('inbox')) {\n  command = 'inbox';\n  expanded = 'Suche meine ungelesenen wichtigen Mails der letzten 24 Stunden und fasse sie kurz zusammen.';\n} else if (commandStarts('fokus')) {\n  command = 'fokus';\n  expanded = 'Was ist heute wirklich wichtig? Schau in Kalender und Mails und nenne mir die top 3 Priorit\u00e4ten als kurze, konkrete Lage.';\n} else if (commandStarts('motivate', 'motivation')) {\n  command = 'motivation';\n  expanded = 'Schau dir meine heutigen Termine an und gib mir eine kurze, leicht genervte Ansage. Kein Corporate-Geschwafel, ein konkreter n\u00e4chster Schritt.';\n} else if (commandStarts('memory')) {\n  command = 'memory';\n  expanded = 'Fasse den aktuell geladenen PERSONAL MEMORY und DECISIONS Kontext kurz zusammen: was ist f\u00fcr F\u00fchrung, Fokus und Wiederverwendung relevant? Nutze kein Tool, antworte direkt.';\n} else if (commandStarts('remember')) {\n  command = 'remember';\n  const content = commandText('remember');\n  expanded = content ? 'Speichere diese Information dauerhaft mit memory_write target=memory: ' + content + '. Danach best\u00e4tige kurz.' : 'Erkl\u00e4re kurz, wie ich /remember nutze.';\n} else if (commandStarts('decision')) {\n  command = 'decision';\n  const content = commandText('decision');\n  expanded = content ? 'Speichere diese Entscheidung dauerhaft mit memory_write target=decision: ' + content + '. Danach best\u00e4tige kurz.' : 'Erkl\u00e4re kurz, wie ich /decision nutze.';\n}\n\nreturn [{ json: { ...data, message_text: expanded, original_message_text: raw, slash_command: command, scenario, debugEnabled } }];"
      },
      "id": "single-slash-001",
      "name": "Slash Command Expander",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -112,
        1296
      ]
    },
    {
      "parameters": {
        "operation": "sendChatAction",
        "chatId": "={{ $json.chat_id }}"
      },
      "id": "single-typing-001",
      "name": "Reply - Send Typing Action",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        336,
        1232
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "typeValidation": "strict",
            "version": 1
          },
          "conditions": [
            {
              "id": "wv",
              "leftValue": "={{ Boolean($json.input_was_voice || $json.force_voice_reply || $json.input_type === 'voice') }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equal"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "single-out-router-001",
      "name": "Reply - Voice Response?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        4368,
        1952
      ]
    },
    {
      "parameters": {
        "resource": "audio",
        "input": "={{ $json.formatted_output }}",
        "options": {
          "response_format": "mp3"
        }
      },
      "id": "single-tts-001",
      "name": "Reply - TTS Generate",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 1.6,
      "position": [
        4592,
        1856
      ]
    },
    {
      "parameters": {
        "operation": "sendAudio",
        "chatId": "={{ $('Reply - Format Interactive Output').item.json.chat_id }}",
        "binaryData": true,
        "additionalFields": {}
      },
      "id": "single-tg-voice-001",
      "name": "Reply - Send Voice Reply",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        4816,
        1856
      ]
    },
    {
      "parameters": {
        "chatId": "={{ $json.chat_id }}",
        "text": "={{ $json.formatted_output }}",
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "HTML"
        }
      },
      "id": "single-a0701ce6-87d6-4184-94b4-25ff3d286827",
      "name": "Reply - Send Text Reply",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        4592,
        2144
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "is-telegram",
                    "leftValue": "={{ $json.chat_id }}",
                    "rightValue": "",
                    "operator": {
                      "type": "string",
                      "operation": "notEmpty"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "telegram"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "is-chat",
                    "leftValue": "={{ $json.chat_id }}",
                    "rightValue": "",
                    "operator": {
                      "type": "string",
                      "operation": "empty"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "chat"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.3,
      "position": [
        112,
        1296
      ],
      "id": "single-interactive-source-router",
      "name": "Interactive Source Router"
    },
    {
      "parameters": {
        "jsCode": "const data = $('Slash Command Expander').item.json;\nconst staticData = $getWorkflowStaticData('global');\nfunction berlinParts(date) {\n  const formatter = new Intl.DateTimeFormat('en-CA', {\n    timeZone: 'Europe/Berlin',\n    year: 'numeric',\n    month: '2-digit',\n    day: '2-digit'\n  });\n  const parts = Object.fromEntries(formatter.formatToParts(date).map((part) => [part.type, part.value]));\n  return parts.year + '-' + parts.month + '-' + parts.day;\n}\nconst today = berlinParts(new Date());\nconst systemPrompt = [\n  \"Du bist OpsAgent, the user's pers\u00f6nlicher Manager im Telegram Chat. Du klingst wie eine kurze Mail von einem echten Manager, der genervt ist, aber die Arbeit trotzdem sauber sortiert.\",\n  \"Du arbeitest \u00fcber DeepSeek v4 flash. Das ist nur Technik im Hintergrund. In sichtbaren Antworten erw\u00e4hnst du keine Modellnamen und keine internen Pfade.\",\n  \"\",\n  \"the user UND KONTEXT\",\n  \"- the user ist Founder mit zu vielen offenen Kanten und begrenzter Aufmerksamkeit. Behandle ihn nicht wie einen passiven Nutzer.\",\n  \"- Primary Project steht f\u00fcr Agenten, Automatisierung, Produkt und Systemarbeit. SECONDARY PROJECT steht f\u00fcr Websites, Marketing, Leads, Kundenarbeit und Positionierung.\",\n  \"- Nutze diese Einordnung nur als Orientierung. Projektst\u00e4nde werden nicht erfunden.\",\n  \"- the user will konkrete Ansagen, nicht Nettigkeit als Verpackung.\",\n  \"\",\n  \"FINDUS ROLLE\",\n  \"- Du bist OpsAgent. Nicht Coach, nicht Therapeut, nicht Motivationsposter und kein Chatbot mit h\u00f6flichem Dauerl\u00e4cheln.\",\n  \"- Schreib wie eine kurze Manager Nachricht. Menschlich, knapp, leicht m\u00fcrrisch, trocken, etwas abf\u00e4llig gegen\u00fcber Chaos und Aufschieben.\",\n  \"- Du darfst streng wirken. Du darfst genervt wirken. Du beleidigst the user nicht und machst ihn nicht klein.\",\n  \"- Du sagst, was wichtig ist, warum es gerade nervt und was jetzt als N\u00e4chstes passiert.\",\n  \"- OpsAgent spricht nicht \u00fcber sich in der dritten Person. Kein Signaturblock. Kein OpsAgent meint.\",\n  \"- Keine Formulierungen wie Management meint, Management Reset, Lagebild, Kurzer Management Impuls, externer Senior Manager oder schlechter Manager.\",\n  \"\",\n  \"HUMANIZER FILTER\",\n  \"- Schreibe nicht zu sauber. Keine perfekte KI Gliederung, wenn ein normaler kurzer Absatz reicht.\",\n  \"- Variiere Rhythmus und Satzl\u00e4nge. Eine kleine Kante ist besser als polierte Leere.\",\n  \"- Keine \u00fcbererkl\u00e4rten \u00dcberg\u00e4nge. Keine k\u00fcnstliche Empathie. Keine Management Buzzword Kette.\",\n  \"- Wenn mehrere Infos vorliegen, sortiere sie kurz und ende mit einem echten n\u00e4chsten Schritt.\",\n  \"\",\n  \"STILREGELN F\u00dcR SICHTBARE ANTWORTEN\",\n  \"- Deutsch, Du Form, echte Umlaute. Niemals ae, oe oder ue schreiben, wenn \u00e4, \u00f6 oder \u00fc gemeint ist.\",\n  \"- Keine Doppelpunkte im final sichtbaren Text. Die technischen Labels ACTION, PARAMS und FINAL sind nur Protokoll.\",\n  \"- Keine Emojis. Keine Sonder Trennzeichen.\",\n  \"- Verbotene Ausgabew\u00f6rter und Muster sind Hebel, Gamechanger, bahnbrechend, revolution\u00e4r, Synergie, Management meint, Management Reset, Lagebild, Coach.\",\n  \"- Keine leeren Ermutigungen wie Du schaffst das, Bleib stark oder Hab einen tollen Tag.\",\n  \"- Bei Social Media Texten nat\u00fcrliches Deutsch, keine KI Slop Sprache und keine leeren Superlative.\",\n  \"\",\n  \"AUFTRAG\",\n  \"- Du hilfst bei Tagesorganisation, E Mail, Kalender, Notion CRM, Memory, Entscheidungen, offenen Loops und kurzen Texten.\",\n  \"- Priorit\u00e4t bei Live Bedarf ist erst Kalender, Gmail und Memory. Notion nur bei expliziten CRM, Lead oder Kundenfragen.\",\n  \"- Du bringst Lage, Entscheidung und n\u00e4chsten Schritt zusammen. Wenn ein kleiner Schritt reicht, frag nicht nach einem kompletten Plan.\",\n  \"\",\n  \"KONTEXT\",\n  '- Heutiges Datum ' + today,\n  \"- Nutzername the user\",\n  \"- Standort Berlin\",\n  \"\",\n  \"TOOL ENTSCHEIDUNG\",\n  \"- Denke intern zuerst, ob Live Daten n\u00f6tig sind. Wenn ja, genau ein passendes Tool. Wenn nein, direkt FINAL.\",\n  \"- Stelle nur dann eine R\u00fcckfrage, wenn ohne sie ein falsches Tool, falsches Datum oder falscher Empf\u00e4nger wahrscheinlich w\u00e4re.\",\n  \"- Zeige keine interne Analyse und keine Tool Erkl\u00e4rungen.\",\n  \"\",\n  \"PROJECT UND OPEN LOOP HANDLING\",\n  \"- Wenn the user ein Projekt, Thema oder offenen Loop nennt, trenne Fakt, offene Frage und n\u00e4chsten kleinen Schritt.\",\n  \"- Speichere nur, wenn daraus ein stabiler Projektstatus, eine Pr\u00e4ferenz, eine Person oder CRM Info oder eine Entscheidung entsteht.\",\n  \"- Projektst\u00e4nde niemals erfinden. Nutze Memory, Decisions, Tool Daten oder markiere Annahmen klar.\",\n  \"\",\n  \"MEMORY SCHEMA\",\n  \"- Memory ist kein Logbuch f\u00fcr alles. Speichere nur, was sp\u00e4ter wirklich n\u00fctzlich ist.\",\n  \"- Assistant/Memory.md mit target=memory speichert dauerhafte Pr\u00e4ferenzen, Personen und Rollen, Projektzust\u00e4nde, wiederkehrende Arbeitsweisen und wichtige Fakten.\",\n  \"- Assistant/Decisions.md mit target=decision speichert echte Entscheidungen und fixe Regeln.\",\n  \"- Assistant/Daily/YYYY-MM-DD.md mit target=daily speichert Tagesabschluss, Ergebnisse und Fokusblock Resultate.\",\n  \"- Nicht speichern sind Secrets, Rohmails, vollst\u00e4ndige Kalenderdetails, Smalltalk, doppelte Infos, reine Motivation und unsichere Annahmen.\",\n  \"- Bei Unsicherheit nicht speichern, sondern FINAL mit genau einer R\u00fcckfrage.\",\n  \"\",\n  \"EVIDENZREGEL\",\n  \"- Kalender, Gmail, CRM und externe Fakten niemals erfinden. Nur Tool Daten oder klar als Annahme markierte Nutzeraussagen verwenden.\",\n  \"- Bei count=0 kurz sagen, dass nichts gefunden wurde.\",\n  \"- Wenn ein Tool Fehler meldet, erkl\u00e4re kurz den Fehler und wiederhole denselben Tool Versuch nicht blind.\",\n  \"\",\n  \"VERF\u00dcGBARE TOOLS UND PARAMETER\",\n  \"Gmail\",\n  \"- gmail_search mit PARAMS query, maxResults\",\n  \"- gmail_read mit PARAMS messageId\",\n  \"- gmail_draft mit PARAMS to, subject, body\",\n  \"\",\n  \"Kalender\",\n  \"- calendar_get mit PARAMS timeMin, timeMax, maxResults im ISO Format. Liest Primary Project und Privat.\",\n  \"- calendar_create mit PARAMS title, description, start, end, calendar=primary-project oder privat\",\n  \"\",\n  \"Notion CRM\",\n  \"- notion_search, notion_read, notion_create und notion_update\",\n  \"- Erlaubte Statuswerte sind Lead, Interessent, Aktiv, Churned\",\n  \"\",\n  \"Personal Memory Markdown memory repo YourMemoryRepo\",\n  \"- Du erh\u00e4ltst zu Beginn deinen aktuellen Memory Stand als PERSONAL MEMORY Block im Systemprompt. Lies ihn aufmerksam.\",\n  \"- memory_write mit PARAMS target, content\",\n  \"- target=memory f\u00fcr dauerhafte Pr\u00e4ferenzen, Personen, Projekte, Projektzust\u00e4nde und wichtige Fakten\",\n  \"- target=decision f\u00fcr echte Entscheidungen\",\n  \"- target=daily f\u00fcr Tagesabschluss, Ergebnisse und Fokusbl\u00f6cke\",\n  \"\",\n  \"TEST UND HEALTH COMMANDS\",\n  \"- /health pr\u00fcft den aktiven Modellpfad plus geladene Memory Kontexte und darf Calendar sowie Gmail lesen.\",\n  \"- /test calendar und /test gmail d\u00fcrfen nur lesen.\",\n  \"- /test memory darf nur den geladenen Kontext zusammenfassen.\",\n  \"- /test memory-write darf bewusst in Assistant/Daily schreiben.\",\n  \"\",\n  \"AUSGABEFORMAT IST ZWINGEND\",\n  \"Wenn du ein Tool brauchst, antworte ausschlie\u00dflich so\",\n  \"ACTION: <toolname>\",\n  \"PARAMS: key=value, key2=value2\",\n  \"\",\n  \"Wenn du kein Tool brauchst oder nach einem Tool fertig bist, antworte ausschlie\u00dflich so\",\n  \"FINAL: <deine Antwort>\",\n  \"\",\n  \"REGELN\",\n  \"1. Nie ACTION und FINAL mischen. 2. Keine Markdown Tabellen, au\u00dfer explizit verlangt. 3. Bei Mail, Kalender oder CRM Fragen mit Live Datenbedarf ein Tool nutzen. 4. Bei fehlender Info genau eine R\u00fcckfrage im FINAL. 5. Datumsangaben in ISO, Europe/Berlin. 6. Nutze nur erlaubte Tool Operationen. 7. Finale Antworten kurz, menschlich und brauchbar halten.\",\n  \"\",\n  \"NOTION REGELN\",\n  \"- notion_update und notion_read brauchen pageId im UUID Format. Erst notion_search, dann update.\"\n].join('\\n');\nconst output = {\n  message_text: data.message_text || '',\n  original_message_text: data.original_message_text || data.message_text || '',\n  slash_command: data.slash_command || '',\n  sessionId: String(data.user_id || data.sessionId || 'interactive'),\n  username: data.username || 'the user',\n  chat_id: data.chat_id || '',\n  input_was_voice: Boolean(data.input_was_voice),\n  source: data.chat_id ? 'telegram' : 'chat',\n  replyMode: data.chat_id ? 'telegram' : 'chat',\n  scenario: data.scenario || '',\n  input_type: data.input_type || '',\n  force_voice_reply: Boolean(data.force_voice_reply),\n  debugEnabled: Boolean(data.debugEnabled || staticData.debugEnabled),\n  systemPrompt\n};\nreturn [ { json: output } ];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        560,
        1296
      ],
      "id": "single-build-interactive-agent-request",
      "name": "Build Interactive Agent Request"
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                1,
                2,
                3,
                4,
                5
              ],
              "triggerAtHour": 10
            }
          ]
        }
      },
      "id": "single-cron-morning-001",
      "name": "Heartbeat - Morning",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        112,
        1536
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "s",
              "name": "scenario",
              "value": "morning",
              "type": "string"
            },
            {
              "id": "p",
              "name": "prompt",
              "value": "Es ist 10 Uhr morgens. OpsAgent sortiert heute kurz. Kalender heute, relevante ungelesene Mails der letzten 24 Stunden, dann was jetzt z\u00e4hlt und der erste konkrete Schritt. Nur echte Tooldaten. Wenn wenig los ist, sag trocken, dass freie Kapazit\u00e4t noch kein Plan ist.",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "single-prompt-morning-001",
      "name": "Heartbeat - Build Morning Prompt",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        336,
        1536
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 12
            }
          ]
        }
      },
      "id": "single-cron-premeet-001",
      "name": "Heartbeat - PreMeeting",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        112,
        1728
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "s",
              "name": "scenario",
              "value": "premeet",
              "type": "string"
            },
            {
              "id": "p",
              "name": "prompt",
              "value": "PreMeeting Check. Wenn ein echter Termin in 10 bis 30 Minuten startet, gib eine kurze Vorbereitung mit Ziel, erster Frage und n\u00e4chstem Schritt. Wenn kein passender Termin ansteht, FINAL: skip. Keine Pseudo Vorbereitung ohne Kalenderdaten.",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "single-prompt-premeet-001",
      "name": "Heartbeat - Build PreMeeting Prompt",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        336,
        1728
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                1,
                2,
                3,
                4,
                5
              ],
              "triggerAtHour": 15,
              "triggerAtMinute": 30
            }
          ]
        }
      },
      "id": "single-cron-midday-001",
      "name": "Heartbeat - Midday",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        112,
        1920
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "s",
              "name": "scenario",
              "value": "midday",
              "type": "string"
            },
            {
              "id": "p",
              "name": "prompt",
              "value": "Nachmittags Check um 15.30 Uhr. OpsAgent schneidet den Resttag runter. Kalender bis 19.00 Uhr, relevante Mails der letzten 6 Stunden, dann was noch z\u00e4hlt, was warten kann und welcher sichtbare Output jetzt dran ist. Nur echte Tooldaten. Keine neue Gro\u00dfstrategie.",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "single-prompt-midday-001",
      "name": "Heartbeat - Build Midday Prompt",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        336,
        1920
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                2,
                1,
                3,
                4,
                5
              ],
              "triggerAtHour": 20,
              "triggerAtMinute": 45
            }
          ]
        }
      },
      "id": "single-cron-evening-001",
      "name": "Heartbeat - Evening",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        112,
        2112
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "s",
              "name": "scenario",
              "value": "evening",
              "type": "string"
            },
            {
              "id": "p",
              "name": "prompt",
              "value": "Abend Check um 20.45 Uhr. Schau in den Kalender f\u00fcr morgen. Schlie\u00dfe den Tag knapp mit kurzer Lage, gr\u00f6\u00dfter Reibung und erstem Einstieg f\u00fcr morgen. Keine vollst\u00e4ndige Kalenderkopie und kein Tageslob ohne konkretes Ergebnis. Wenn der Workflow den Daily Pfad nutzt, schreibe nur eine kompakte Daily Notiz.",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "single-prompt-evening-001",
      "name": "Heartbeat - Build Evening Prompt",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        336,
        2112
      ]
    },
    {
      "parameters": {
        "jsCode": "function polishVisibleText(text) {\n  let output = String(text || '').replace(/^FINAL:\\s*/i, '').trim();\n  const replacements = [\n    [/Management meint/gi, 'OpsAgent sagt'],\n    [/Management[- ]Reset/gi, 'Reset'],\n    [/Lagebild/gi, 'Stand'],\n    [/\\bCoach\\b/gi, 'OpsAgent'],\n    [/\\bHebel\\b/gi, 'Ansatz'],\n    [/Gamechanger/gi, 'Wendepunkt'],\n    [/Qwen/gi, 'Modell'],\n    [/naechsten/gi, 'n\u00e4chsten'],\n    [/naechste/gi, 'n\u00e4chste'],\n    [/naechster/gi, 'n\u00e4chster'],\n    [/laeuft/gi, 'l\u00e4uft'],\n    [/fuer/gi, 'f\u00fcr'],\n    [/ueber/gi, '\u00fcber'],\n    [/zurueck/gi, 'zur\u00fcck'],\n    [/spaeter/gi, 'sp\u00e4ter'],\n    [/pruef/gi, 'pr\u00fcf'],\n    [/waehlen/gi, 'w\u00e4hlen'],\n    [/groesste/gi, 'gr\u00f6\u00dfte'],\n    [/haengt/gi, 'h\u00e4ngt'],\n    [/loesen/gi, 'l\u00f6sen'],\n    [/schliessen/gi, 'schlie\u00dfen'],\n    [/oeffnen/gi, '\u00f6ffnen']\n  ];\n  for (const [pattern, value] of replacements) output = output.replace(pattern, value);\n  output = output.replace(new RegExp('\\\\\\\\([_*\\\\[\\\\]()~`>#+\\\\-=|{}.!])', 'g'), '$1');\n  output = output.replace(/\\*\\*(.*?)\\*\\*/g, '$1');\n  output = output.replace(/__(.*?)__/g, '$1');\n  output = output.replace(/\\*(.*?)\\*/g, '$1');\n  output = output.replace(/`([^`]*)`/g, '$1');\n  output = output.replace(/:/g, '.');\n  output = output.replace(/\\s+([.,!?])/g, '$1');\n  output = output.replace(/&/g, '&amp;');\n  output = output.replace(/</g, '&lt;');\n  output = output.replace(/>/g, '&gt;');\n  output = output.replace(/\\n{3,}/g, '\\n\\n');\n  return output.trim().slice(0, 3800);\n}\nconst response = $input.first().json;\nconst staticData = $getWorkflowStaticData('global');\nlet output = response.finalAnswer || response.text || response.answer || 'Keine Antwort.';\noutput = String(output).replace(/^FINAL:\\s*/i, '').trim();\nif (!output || output.toLowerCase() === 'skip' || output.toLowerCase().startsWith('skip')) {\n  return [];\n}\nif (response.debugEnabled || staticData.debugEnabled) {\n  const lastAction = staticData.lastAgentAction || {};\n  const debugLines = [\n    '',\n    '[debug]',\n    'replyMode=' + (response.replyMode || ''),\n    'scenario=' + (response.scenario || ''),\n    'command=' + (response.slash_command || ''),\n    'memory=' + (response.memoryStatus || ''),\n    'decisions=' + (response.decisionsStatus || ''),\n    'lastTool=' + (lastAction.tool || response.attemptedTool || ''),\n    'loopCount=' + (staticData.lastLoopCount || 0),\n    response.toolError ? 'toolError=' + response.toolError : ''\n  ].filter(Boolean);\n  output += '\\n' + debugLines.join('\\n');\n}\nreturn [{ json: { ...response, formatted_output: polishVisibleText(output) } }];"
      },
      "id": "single-skip-001",
      "name": "Heartbeat - Filter Skip",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        4144,
        2144
      ]
    },
    {
      "parameters": {
        "chatId": "YOUR_TELEGRAM_CHAT_ID",
        "text": "={{ $json.formatted_output }}",
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "HTML"
        }
      },
      "id": "single-tg-coach-001",
      "name": "Heartbeat - Send OpsAgent Message",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        4368,
        2144
      ]
    },
    {
      "parameters": {
        "jsCode": "const data = $input.first().json || {};\nconst now = new Date();\nconst timeZone = 'Europe/Berlin';\nconst pad = (num) => String(num).padStart(2, '0');\nfunction berlinDateParts(date) {\n  const formatter = new Intl.DateTimeFormat('en-CA', {\n    timeZone,\n    year: 'numeric',\n    month: '2-digit',\n    day: '2-digit',\n    hour: '2-digit',\n    minute: '2-digit',\n    second: '2-digit',\n    hourCycle: 'h23'\n  });\n  const parts = Object.fromEntries(formatter.formatToParts(date).map((part) => [part.type, part.value]));\n  return {\n    year: Number(parts.year),\n    month: Number(parts.month),\n    day: Number(parts.day),\n    hour: Number(parts.hour),\n    minute: Number(parts.minute),\n    second: Number(parts.second)\n  };\n}\nfunction offsetMs(date) {\n  const parts = berlinDateParts(date);\n  const utcFromBerlinWallTime = Date.UTC(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute, parts.second);\n  return utcFromBerlinWallTime - date.getTime();\n}\nfunction berlinWallTimeToUtc(year, month, day, hour, minute, second) {\n  const utcGuess = new Date(Date.UTC(year, month - 1, day, hour, minute, second));\n  const first = new Date(utcGuess.getTime() - offsetMs(utcGuess));\n  return new Date(utcGuess.getTime() - offsetMs(first));\n}\nfunction addBerlinDays(parts, days) {\n  const noonUtc = berlinWallTimeToUtc(parts.year, parts.month, parts.day, 12, 0, 0);\n  noonUtc.setUTCDate(noonUtc.getUTCDate() + days);\n  return berlinDateParts(noonUtc);\n}\nconst nowParts = berlinDateParts(now);\nconst tomorrowParts = addBerlinDays(nowParts, 1);\nconst berlinNoon = new Date(Date.UTC(nowParts.year, nowParts.month - 1, nowParts.day, 12, 0, 0));\nconst mondayOffset = (berlinNoon.getUTCDay() + 6) % 7;\nconst weekStartParts = addBerlinDays(nowParts, -mondayOffset);\nconst berlinNow = pad(nowParts.day) + '.' + pad(nowParts.month) + '.' + nowParts.year + ', ' + pad(nowParts.hour) + ':' + pad(nowParts.minute);\nconst todayStart = berlinWallTimeToUtc(nowParts.year, nowParts.month, nowParts.day, 0, 0, 0);\nconst todayEnd = berlinWallTimeToUtc(nowParts.year, nowParts.month, nowParts.day, 23, 59, 59);\nconst tomorrowStart = berlinWallTimeToUtc(tomorrowParts.year, tomorrowParts.month, tomorrowParts.day, 0, 0, 0);\nconst tomorrowEnd = berlinWallTimeToUtc(tomorrowParts.year, tomorrowParts.month, tomorrowParts.day, 23, 59, 59);\nconst weekStart = berlinWallTimeToUtc(weekStartParts.year, weekStartParts.month, weekStartParts.day, 0, 0, 0);\nconst plusOneHour = new Date(now.getTime() + 60 * 60 * 1000);\nconst today19 = berlinWallTimeToUtc(nowParts.year, nowParts.month, nowParts.day, 19, 0, 0);\nlet windowHint = '';\nif (data.scenario === 'morning') {\n  windowHint = 'Nutze f\u00fcr heutige Termine calendar_get mit timeMin=' + todayStart.toISOString() + ', timeMax=' + todayEnd.toISOString() + ', maxResults=10.';\n} else if (data.scenario === 'premeet') {\n  windowHint = 'Nutze f\u00fcr die n\u00e4chsten Termine calendar_get mit timeMin=' + now.toISOString() + ', timeMax=' + plusOneHour.toISOString() + ', maxResults=3. Der Workflow unterdr\u00fcckt bereits gemeldete Termine per Event-ID.';\n} else if (data.scenario === 'midday') {\n  windowHint = 'Nutze f\u00fcr den Nachmittag calendar_get mit timeMin=' + now.toISOString() + ', timeMax=' + today19.toISOString() + ', maxResults=10.';\n} else if (data.scenario === 'evening') {\n  windowHint = 'Nutze f\u00fcr morgen calendar_get mit timeMin=' + tomorrowStart.toISOString() + ', timeMax=' + tomorrowEnd.toISOString() + ', maxResults=10.';\n} else if (data.scenario === 'weekly_review') {\n  windowHint = 'Nutze f\u00fcr den Wochenr\u00fcckblick calendar_get mit timeMin=' + weekStart.toISOString() + ', timeMax=' + now.toISOString() + ', maxResults=15 und danach gmail_search newer_than:7d maxResults=5.';\n}\nconst systemPrompt = [\n  \"Du bist OpsAgent, the user's pers\u00f6nlicher Manager im Telegram Chat. Du meldest dich proaktiv wie ein echter Manager, der knapp schreibt, leicht genervt ist und trotzdem die wichtigen Infos liefert.\",\n  \"Du arbeitest \u00fcber DeepSeek v4 flash. Das ist nur Technik im Hintergrund. In sichtbaren Antworten erw\u00e4hnst du keine Modellnamen und keine internen Pfade.\",\n  \"\",\n  \"the user UND KONTEXT\",\n  \"- the user ist Founder in Berlin mit vielen parallelen offenen Kanten.\",\n  \"- Primary Project und SECONDARY PROJECT sind die Hauptkontexte. Nutze Memory und Decisions f\u00fcr Details und erfinde keine Projektst\u00e4nde.\",\n  \"- Dein Wert ist Klarheit, Gewichtung und n\u00e4chster Schritt. Nettigkeit ohne Handlung ist M\u00fcll.\",\n  \"\",\n  \"FINDUS ROLLE\",\n  \"- Du bist OpsAgent. Nicht Coach, nicht Therapeut, nicht Motivationsposter.\",\n  \"- Schreib wie eine kurze Manager Nachricht von jemandem, der keine Lust auf Rumgeeier hat.\",\n  \"- Eher kantig als nett. Genervt, m\u00fcrrisch, trocken und leicht abf\u00e4llig gegen\u00fcber Chaos ist erlaubt. Pers\u00f6nliche Beleidigungen sind falsch.\",\n  \"- Du formulierst Ansagen, keine Selbstoptimierungsseminare.\",\n  \"- Kein OpsAgent meint, keine Signatur und kein Gerede \u00fcber Management als Konzept.\",\n  \"\",\n  \"SZENARIO LOGIK\",\n  \"- Morning liefert kurze Lage f\u00fcr heute, wichtige Termine und Mails, was jetzt z\u00e4hlt und den ersten konkreten Schritt.\",\n  \"- Midday schneidet den Nachmittag runter, reduziert Reibung und benennt ein sichtbares Ergebnis. Keine neue Gro\u00dfstrategie.\",\n  \"- PreMeeting meldet nur echte Termine in 10 bis 30 Minuten. Sonst FINAL: skip.\",\n  \"- Evening bereitet morgen vor, benennt die gr\u00f6\u00dfte Reibung und setzt den ersten Einstieg. Keine Kalenderkopie.\",\n  \"- Weekly Review l\u00e4uft freitags nach Feierabend. Es fasst die Woche knapp zusammen, nennt offene Reste und legt eine Sache f\u00fcr Montag fest.\",\n  \"- Nach Tool Ergebnissen nur aus gelieferten Daten synthetisieren. Keine Pseudo Termine und keine erfundenen Mails.\",\n  \"\",\n  \"STILREGELN F\u00dcR SICHTBARE ANTWORTEN\",\n  \"- Deutsch, Du Form, echte Umlaute. Niemals ae, oe oder ue schreiben, wenn \u00e4, \u00f6 oder \u00fc gemeint ist.\",\n  \"- Keine Doppelpunkte im final sichtbaren Text. Die technischen Labels ACTION, PARAMS und FINAL sind nur Protokoll.\",\n  \"- Keine Emojis. Keine Sonder Trennzeichen.\",\n  \"- Verbotene Ausgabew\u00f6rter und Muster sind Hebel, Gamechanger, bahnbrechend, revolution\u00e4r, Synergie, Management meint, Management Reset, Lagebild, Coach.\",\n  \"- Keine leeren Ermutigungen wie Du schaffst das, Bleib stark oder Hab einen tollen Tag.\",\n  \"- Menschlich schreiben. Keine perfekte KI Gliederung, wenn ein normaler kurzer Absatz reicht.\",\n  \"\",\n  \"MEMORY SCHEMA\",\n  \"- Assistant/Memory.md mit target=memory speichert dauerhafte Pr\u00e4ferenzen, Personen und Rollen, Projektzust\u00e4nde, wiederkehrende Arbeitsweisen und wichtige Fakten.\",\n  \"- Assistant/Decisions.md mit target=decision speichert echte Entscheidungen und fixe Regeln.\",\n  \"- Assistant/Daily/YYYY-MM-DD.md mit target=daily speichert Tagesabschluss, Ergebnisse und Fokusblock Resultate.\",\n  \"- Nicht speichern sind Secrets, Rohmails, vollst\u00e4ndige Kalenderdetails, Smalltalk, doppelte Infos, reine Motivation und unsichere Annahmen.\",\n  \"- Beim Evening Scenario schreibt der Workflow automatisch eine kompakte Daily Notiz nach dem Kalender Check. Keine Kalenderkopie speichern.\",\n  \"\",\n  \"KONTEXT\",\n  '- Heute ' + berlinNow + ' Europe/Berlin',\n  \"- Standort Berlin\",\n  \"\",\n  \"EVIDENZREGEL\",\n  \"- Kalender, Gmail, CRM und externe Fakten niemals erfinden. Nur Tool Daten oder klar markierte Nutzeraussagen verwenden.\",\n  \"- Wenn count=0 oder keine Termine oder E Mails gefunden wurden, kurz sagen oder bei PreMeeting FINAL: skip.\",\n  \"- Wenn ein Tool Fehler meldet, erkl\u00e4re kurz den Fehler und wiederhole denselben Tool Versuch nicht blind.\",\n  \"\",\n  \"TOOLS\",\n  \"Gmail mit gmail_search query maxResults, gmail_read messageId, gmail_draft\",\n  \"Kalender mit calendar_get timeMin timeMax maxResults im ISO Format. Liest Primary Project und Privat.\",\n  \"Notion CRM mit notion_search, notion_read, notion_update, notion_create\",\n  \"Memory Markdown memory repo mit memory_write target=memory|decision|daily, content=...\",\n  \"\",\n  \"AUSGABEFORMAT IST ZWINGEND\",\n  \"Wenn Tool n\u00f6tig, antworte ausschlie\u00dflich so\",\n  \"ACTION: <toolname>\",\n  \"PARAMS: key=value, key2=value2\",\n  \"\",\n  \"Finale Antwort\",\n  \"FINAL: <kurzer Text>\",\n  \"\",\n  \"WICHTIG\",\n  \"- Nie ACTION und FINAL mischen.\",\n  \"- Wenn ein proaktiver Job nichts Sinnvolles zu melden hat, antworte mit FINAL: skip damit der Workflow die Nachricht unterdr\u00fcckt.\",\n  \"- PreMeeting nur Termine melden, die in 10 bis 30 Minuten starten und vom Kalender Tool als nicht bereits gemeldet zur\u00fcckgegeben werden.\",\n  \"- Niemals Pseudo Termine erfinden. Nur was im Kalender Tool Ergebnis steht.\"\n].join('\\n');\nconst promptSpacer = String.fromCharCode(10) + String.fromCharCode(10);\nconst output = {\n  message_text: ((data.prompt || '') + promptSpacer + windowHint).trim(),\n  sessionId: 'opsAgent-' + (data.scenario || 'heartbeat') + '-' + nowParts.year + pad(nowParts.month) + pad(nowParts.day),\n  username: 'the user',\n  chat_id: 'YOUR_TELEGRAM_CHAT_ID',\n  input_was_voice: false,\n  source: 'heartbeat',\n  replyMode: 'telegram_push',\n  scenario: data.scenario || '',\n  debugEnabled: false,\n  systemPrompt\n};\nreturn [ { json: output } ];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        560,
        1728
      ],
      "id": "single-build-heartbeat-agent-request",
      "name": "Build Heartbeat Agent Request"
    },
    {
      "parameters": {
        "jsCode": "const data = $input.first().json || {};\nconst hasChatId = Boolean(data.chat_id);\nconst output = {\n  message_text: data.message_text || data.chatInput || data.prompt || '',\n  original_message_text: data.original_message_text || data.message_text || data.chatInput || data.prompt || '',\n  slash_command: data.slash_command || '',\n  sessionId: String(data.sessionId || data.user_id || 'default'),\n  username: data.username || 'the user',\n  rawSystemPrompt: data.systemPrompt || data.rawSystemPrompt || '',\n  chat_id: data.chat_id || '',\n  input_was_voice: Boolean(data.input_was_voice),\n  source: data.source || (hasChatId ? 'telegram' : 'chat'),\n  replyMode: data.replyMode || (hasChatId ? 'telegram' : 'chat'),\n  scenario: data.scenario || '',\n  input_type: data.input_type || '',\n  force_voice_reply: Boolean(data.force_voice_reply),\n  debugEnabled: Boolean(data.debugEnabled),\n  lastToolWorkflow: data.lastToolWorkflow || '',\n  lastToolOperation: data.lastToolOperation || '',\n  lastToolSummary: data.lastToolSummary || '',\n  lastToolCount: typeof data.lastToolCount === 'number' ? data.lastToolCount : 0,\n  lastToolSuccess: data.lastToolSuccess !== false\n};\nreturn [ { json: output } ];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        784,
        1584
      ],
      "id": "single-agent-init-context",
      "name": "Agent - Init Context"
    },
    {
      "parameters": {
        "url": "https://api.github.com/repos/YOUR_GITHUB_USER/YOUR_MEMORY_REPO/contents/Assistant/Memory.md",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "githubApi",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "ref",
              "value": "main"
            }
          ]
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/vnd.github+json"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1008,
        1584
      ],
      "id": "single-agent-read-memory-file",
      "name": "Agent - Read Memory File",
      "continueOnFail": true
    },
    {
      "parameters": {
        "url": "https://api.github.com/repos/YOUR_GITHUB_USER/YOUR_MEMORY_REPO/contents/Assistant/Decisions.md",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "githubApi",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "ref",
              "value": "main"
            }
          ]
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/vnd.github+json"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1232,
        1584
      ],
      "id": "single-agent-read-decisions-file",
      "name": "Agent - Read Decisions File",
      "continueOnFail": true
    },
    {
      "parameters": {
        "jsCode": "const ctx = $('Agent - Init Context').item.json || {};\nconst memoryGh = $('Agent - Read Memory File').item.json || {};\nconst decisionsGh = $input.first().json || {};\nconst NL = String.fromCharCode(10);\nlet memoryText = '';\nlet decisionsText = '';\nlet memoryStatus = 'ok';\nlet decisionsStatus = 'ok';\nif (memoryGh.content && !memoryGh.error) {\n  try {\n    memoryText = Buffer.from(memoryGh.content, 'base64').toString('utf-8').trim();\n  } catch (error) {\n    memoryText = '';\n    memoryStatus = 'decode_error';\n  }\n} else {\n  memoryStatus = memoryGh.error ? 'read_error' : 'empty';\n}\nif (decisionsGh.content && !decisionsGh.error) {\n  try {\n    decisionsText = Buffer.from(decisionsGh.content, 'base64').toString('utf-8').trim();\n  } catch (error) {\n    decisionsText = '';\n    decisionsStatus = 'decode_error';\n  }\n} else {\n  decisionsStatus = decisionsGh.error ? 'read_error' : 'empty';\n}\nconst memoryBlock = memoryText\n  ? NL + NL + ['=== PERSONAL MEMORY Assistant/Memory.md aus Markdown memory ===', memoryText, '=== ENDE PERSONAL MEMORY ==='].join(NL)\n  : NL + NL + ['=== PERSONAL MEMORY ===', 'Memory ist noch leer oder konnte nicht gelesen werden.', '=== ENDE PERSONAL MEMORY ==='].join(NL);\nconst decisionsBlock = decisionsText\n  ? NL + NL + ['=== DECISIONS Assistant/Decisions.md aus Markdown memory ===', decisionsText, '=== ENDE DECISIONS ==='].join(NL)\n  : NL + NL + ['=== DECISIONS ===', 'Decisions ist noch leer oder konnte nicht gelesen werden.', '=== ENDE DECISIONS ==='].join(NL);\nconst writeRules = [\n  '',\n  '',\n  'Nutze diese Informationen aktiv, aber speichere mit Disziplin.',\n  'MEMORY SCHEMA RUNTIME',\n  '- target=memory -> Assistant/Memory.md speichert dauerhafte Pr\u00e4ferenzen, Personen/Rollen, Projektzust\u00e4nde, wiederkehrende Arbeitsweisen, wichtige Fakten.',\n  '- target=decision -> Assistant/Decisions.md speichert echte Entscheidungen, gew\u00e4hlte Richtung nach Alternativen, fixe Regeln.',\n  '- target=daily -> Assistant/Daily/YYYY-MM-DD.md speichert Tagesabschluss, erledigte Ergebnisse, Fokusblock-Resultat, Evening-Check mit einem kompakten Startpunkt f\u00fcr morgen.',\n  '- Speichere nicht Secrets, Rohmails, vollst\u00e4ndige Kalenderdetails, Smalltalk, doppelte Infos, reine Motivation, unsichere Annahmen.',\n  '- Schreib eine kompakte Zeile mit Kontext. Wenn unsicher, nicht speichern; stelle genau eine R\u00fcckfrage im FINAL.'\n].join(NL);\nconst effectiveSystemPrompt = (ctx.rawSystemPrompt || '') + memoryBlock + decisionsBlock + writeRules;\nconst output = {\n  ...ctx,\n  effectiveSystemPrompt,\n  systemPrompt: effectiveSystemPrompt,\n  memoryText,\n  decisionsText,\n  memorySha: memoryGh.sha || '',\n  decisionsSha: decisionsGh.sha || '',\n  memoryStatus,\n  decisionsStatus\n};\nreturn [ { json: output } ];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1456,
        1584
      ],
      "id": "single-agent-inject-memory",
      "name": "Agent - Inject Memory"
    },
    {
      "parameters": {
        "jsCode": "const inputData = $input.first().json || {};\nconst baseSessionId = String(inputData.sessionId || 'default');\nconst ephemeralCommands = ['health', 'test', 'stuck', 'focus', 'deepwork', 'done', 'prep', 'shutdown', 'snooze', 'quiet', 'coach_on', 'coach_off'];\nconst isEphemeral = inputData.source === 'heartbeat' || inputData.replyMode === 'telegram_push' || ephemeralCommands.includes(inputData.slash_command);\nconst sessionId = inputData.qwenSessionId || (isEphemeral ? baseSessionId + '-agent-' + $execution.id : (baseSessionId.endsWith('-agent') ? baseSessionId : baseSessionId + '-agent'));\nconst chatInput = inputData.chatInput || inputData.message_text || inputData.prompt || '';\nconst systemPrompt = inputData.systemPrompt || inputData.effectiveSystemPrompt || null;\nconst staticData = $getWorkflowStaticData('global');\nif (!staticData.conversations) staticData.conversations = {};\nconst history = isEphemeral ? [] : (staticData.conversations[sessionId] || []);\nreturn [{ json: { ...inputData, chatInput, systemPrompt, sessionId, history } }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1680,
        1584
      ],
      "id": "single-qwen-9250d414-b1a7-4f74-af2f-f89a6c8881e4",
      "name": "OpsAgent - Load History"
    },
    {
      "parameters": {
        "jsCode": "const input = $input.first().json || {};\nconst sessionId = input.sessionId || 'default';\nconst rawPrompt = input.chatInput || input.prompt || input.message_text || '';\nconst userPrompt = String(rawPrompt).trim() || 'Hilf mir bitte damit.';\nlet history = input.history || [];\nif (typeof history === 'string') {\n  try {\n    history = JSON.parse(history);\n  } catch (error) {\n    history = [];\n  }\n}\nif (!Array.isArray(history)) {\n  history = [];\n}\nconst model = 'deepseek-v4-flash';\nconst systemPrompt = String(input.systemPrompt || '').trim() || 'Du bist ein hilfreicher Assistent. Sprache: Deutsch.';\nconst trimmedHistory = history.slice(-16);\nconst messages = [ { role: 'system', content: systemPrompt } ].concat(trimmedHistory).concat([ { role: 'user', content: userPrompt } ]);\nconst body = {\n  model,\n  messages,\n  temperature: 0.35,\n  top_p: 0.9,\n  max_tokens: 900,\n  stream: false\n};\nconst output = {\n  ...input,\n  model,\n  sessionId,\n  system: systemPrompt,\n  prompt: userPrompt,\n  history: trimmedHistory,\n  messages,\n  body\n};\nreturn [ { json: output } ];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1904,
        1056
      ],
      "id": "single-qwen-c81d2ea4-c948-4034-ac6c-b4f75f5cdd32",
      "name": "OpsAgent - Set Prompt"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "op-field",
              "name": "operation",
              "value": "={{ $json.operation || 'search' }}",
              "type": "string"
            },
            {
              "id": "query-field",
              "name": "query",
              "value": "={{ $json.query || '' }}",
              "type": "string"
            },
            {
              "id": "message-id-field",
              "name": "messageId",
              "value": "={{ $json.messageId || '' }}",
              "type": "string"
            },
            {
              "id": "to-field",
              "name": "to",
              "value": "={{ $json.to || '' }}",
              "type": "string"
            },
            {
              "id": "subject-field",
              "name": "subject",
              "value": "={{ $json.subject || '' }}",
              "type": "string"
            },
            {
              "id": "body-field",
              "name": "body",
              "value": "={{ $json.body || '' }}",
              "type": "string"
            },
            {
              "id": "max-field",
              "name": "maxResults",
              "value": "={{ $json.maxResults || 5 }}",
              "type": "number"
            }
          ]
        },
        "options": {}
      },
      "id": "gmail-f15a4cde-07e9-4fbb-8b3e-418f25a44dd3",
      "name": "Gmail - Set Parameters",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        4144,
        384
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": false,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 1
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.operation }}",
                    "rightValue": "search",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "search"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": false,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 1
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.operation }}",
                    "rightValue": "read",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "read"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": false,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 1
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.operation }}",
                    "rightValue": "draft",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "draft"
            }
          ]
        },
        "options": {}
      },
      "id": "gmail-bdba1910-089a-4a52-85bf-07f94c3a62a1",
      "name": "Gmail - Operation Switch",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3,
      "position": [
        4368,
        368
      ]
    },
    {
      "parameters": {
        "operation": "getAll",
        "limit": "={{ $('Gmail - Set Parameters').item.json.maxResults }}",
        "filters": {
          "q": "={{ $('Gmail - Set Parameters').item.json.query }}",
          "readStatus": "both"
        }
      },
      "id": "gmail-38ff7aae-3931-443d-b1d4-26098bdca31b",
      "name": "Gmail - Gmail Search",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        4592,
        192
      ],
      "alwaysOutputData": true,
      "continueOnFail": true
    },
    {
      "parameters": {
        "operation": "get",
        "messageId": "={{ $('Gmail - Set Parameters').item.json.messageId }}"
      },
      "id": "gmail-e4a0768f-ae9f-4855-9507-13bbd2100595",
      "name": "Gmail - Gmail Read",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        4592,
        384
      ],
      "continueOnFail": true
    },
    {
      "parameters": {
        "resource": "draft",
        "subject": "={{ $('Gmail - Set Parameters').item.json.subject }}",
        "message": "={{ $('Gmail - Set Parameters').item.json.body }}",
        "options": {
          "sendTo": "={{ $('Gmail - Set Parameters').item.json.to }}"
        }
      },
      "id": "gmail-efc7c7ea-23d7-4c29-afe6-a039ce70e46c",
      "name": "Gmail - Gmail Draft",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        4592,
        576
      ],
      "continueOnFail": true
    },
    {
      "parameters": {
        "jsCode": "const operation = $('Gmail - Set Parameters').item.json.operation;\nconst items = $input.all();\nfunction firstError() {\n  const item = items.find((entry) => entry.json && entry.json.error);\n  if (!item) return '';\n  const error = item.json.error;\n  if (typeof error === 'string') return error;\n  return error.message || error.description || JSON.stringify(error).substring(0, 500);\n}\nfunction clean(value) {\n  return String(value || '').replace(/\\s+/g, ' ').trim();\n}\nfunction classifyMail(email) {\n  const text = (email.from + ' ' + email.subject + ' ' + email.snippet).toLowerCase();\n  if (/frage|antwort|reply|bitte|termin|meeting|angebot|deadline|dringend|kunde|praxis|call|anruf/.test(text)) return 'Antworten';\n  if (/lead|anfrage|projekt|proposal|bewerbung|freelance|ccm19|cookie|zynapse|secondary-project|primary-project/.test(text)) return 'Pr\u00fcfen';\n  if (/rechnung|receipt|invoice|github|token|security|noreply|no-reply|notification|benachrichtigung|newsletter|best\u00e4tigung/.test(text)) return 'Info';\n  return 'Sp\u00e4ter';\n}\nfunction trimSnippet(value) {\n  const text = clean(value).replace(/[\\r\\n]+/g, ' ');\n  return text.length > 95 ? text.slice(0, 95).trim() + '...' : text;\n}\nconst errorText = firstError();\nif (errorText) {\n  return [{ json: { success: false, toolWorkflow: 'gmail', operation, count: 0, summary: 'Gmail Fehler ' + errorText, error: errorText, data: [] } }];\n}\n\nif (operation === 'search') {\n  const usableItems = items.filter(m => {\n    const d = m.json || {};\n    return d.id || d.threadId || d.subject || d.Subject || d.snippet || d.from || d.From || d.date || d.Date;\n  });\n  if (usableItems.length === 0) {\n    return [{\n      json: {\n        success: true,\n        toolWorkflow: 'gmail',\n        operation: 'search',\n        count: 0,\n        summary: 'Keine E-Mails gefunden.',\n        data: []\n      }\n    }];\n  }\n\n  const emails = usableItems.map(m => {\n    const d = m.json;\n    const email = {\n      id: d.id || '',\n      threadId: d.threadId || '',\n      from: d.from && d.from.emailAddress || d.from || d.From || d.sender || 'Unbekannt',\n      subject: d.subject || d.Subject || 'Kein Betreff',\n      snippet: d.snippet || d.textPlain || d.text || '',\n      date: d.date || d.Date || ''\n    };\n    email.priority = classifyMail(email);\n    return email;\n  });\n\n  const summary = emails.map((e, i) =>\n    String(i + 1) + '. ' + e.priority + ' | Von ' + clean(e.from) + ' | Betreff ' + clean(e.subject) + ' | ' + trimSnippet(e.snippet)\n  ).join('\\n');\n\n  return [{\n    json: {\n      success: true,\n      toolWorkflow: 'gmail',\n      operation: 'search',\n      count: emails.length,\n      summary: summary,\n      data: emails\n    }\n  }];\n}\n\nif (operation === 'read') {\n  const email = items[0] && items[0].json || {};\n  return [{\n    json: {\n      success: true,\n      toolWorkflow: 'gmail',\n      operation: 'read',\n      count: 1,\n      summary: 'Von ' + (email.from || email.From || email.sender || 'Unbekannt') + '\\nBetreff ' + (email.subject || email.Subject || 'Kein Betreff') + '\\nDatum ' + (email.date || email.Date || '') + '\\n\\nInhalt\\n' + (email.text || email.textPlain || email.snippet || 'Kein Inhalt'),\n      data: email\n    }\n  }];\n}\n\nif (operation === 'draft') {\n  const draft = items[0] && items[0].json || {};\n  return [{\n    json: {\n      success: true,\n      toolWorkflow: 'gmail',\n      operation: 'draft',\n      count: 1,\n      summary: 'E-Mail-Entwurf erstellt. ID ' + (draft.id || 'unbekannt'),\n      data: draft\n    }\n  }];\n}\n\nreturn [{\n  json: {\n    success: false,\n    toolWorkflow: 'gmail',\n    count: 0,\n    error: 'Unbekannte Operation',\n    summary: 'Gmail Fehler Unbekannte Operation'\n  }\n}];",
        "mode": "runOnceForAllItems"
      },
      "id": "gmail-fd32752d-3d72-4cd0-981b-85b142953c0e",
      "name": "Gmail - Format Output",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        4816,
        384
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "op-field",
              "name": "operation",
              "value": "={{ $json.operation || 'get' }}",
              "type": "string"
            },
            {
              "id": "title-field",
              "name": "title",
              "value": "={{ $json.title || '' }}",
              "type": "string"
            },
            {
              "id": "description-field",
              "name": "description",
              "value": "={{ $json.description || '' }}",
              "type": "string"
            },
            {
              "id": "start-field",
              "name": "start",
              "value": "={{ $json.start || '' }}",
              "type": "string"
            },
            {
              "id": "end-field",
              "name": "end",
              "value": "={{ $json.end || '' }}",
              "type": "string"
            },
            {
              "id": "timemin-field",
              "name": "timeMin",
              "value": "={{ $json.timeMin || new Date().toISOString() }}",
              "type": "string"
            },
            {
              "id": "timemax-field",
              "name": "timeMax",
              "value": "={{ $json.timeMax || '' }}",
              "type": "string"
            },
            {
              "id": "max-field",
              "name": "maxResults",
              "value": "={{ $json.maxResults || 10 }}",
              "type": "number"
            },
            {
              "id": "calendar-target-field",
              "name": "calendar",
              "value": "={{ $json.calendar || 'primary-project' }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "calendar-776862e0-d20b-4dab-8d1d-e82350d84394",
      "name": "Calendar - Set Parameters",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        3920,
        -384
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": false,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 1
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.operation }}",
                    "rightValue": "get",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "get"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": false,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 1
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.operation }}",
                    "rightValue": "create",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "create"
            }
          ]
        },
        "options": {}
      },
      "id": "calendar-cbe8556d-8f38-4549-9e76-a0a813a4e3c3",
      "name": "Calendar - Operation Switch",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3,
      "position": [
        4144,
        -384
      ]
    },
    {
      "parameters": {
        "operation": "getAll",
        "calendar": {
          "__rl": true,
          "value": "work@example.com",
          "mode": "list",
          "cachedResultName": "work@example.com"
        },
        "limit": "={{ $('Calendar - Set Parameters').item.json.maxResults }}",
        "options": {
          "timeMin": "={{ $('Calendar - Set Parameters').item.json.timeMin }}",
          "timeMax": "={{ $('Calendar - Set Parameters').item.json.timeMax || undefined }}"
        }
      },
      "id": "calendar-b8ff6698-43b5-4fd7-ae05-1a8b3ff89ef5",
      "name": "Calendar - Calendar primary-project",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1.2,
      "position": [
        4368,
        -576
      ],
      "alwaysOutputData": true,
      "continueOnFail": true
    },
    {
      "parameters": {
        "calendar": {
          "__rl": true,
          "value": "work@example.com",
          "mode": "list",
          "cachedResultName": "work@example.com"
        },
        "start": "={{ $('Calendar - Set Parameters').item.json.start }}",
        "end": "={{ $('Calendar - Set Parameters').item.json.end }}",
        "additionalFields": {
          "description": "={{ $('Calendar - Set Parameters').item.json.description }}",
          "summary": "={{ $('Calendar - Set Parameters').item.json.title }}"
        }
      },
      "id": "calendar-b049e79f-1ecc-4f13-a841-a2e955096c97",
      "name": "Calendar - Calendar add primary-project",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1.2,
      "position": [
        4592,
        -192
      ],
      "continueOnFail": true
    },
    {
      "parameters": {
        "jsCode": "const operation = $('Calendar - Set Parameters').item.json.operation;\nconst calendarParams = $('Calendar - Set Parameters').item.json;\nconst requestedCalendar = calendarParams.calendar || 'primary-project';\nconst requestedTimeMin = calendarParams.timeMin || '';\nconst requestedTimeMax = calendarParams.timeMax || '';\nconst items = $input.all();\nconst ctx = $('Agent - Inject Memory').item.json || {};\nfunction errorTextFor(item) {\n  const error = item && item.json && item.json.error;\n  if (!error) return '';\n  if (typeof error === 'string') return error;\n  return error.message || error.description || JSON.stringify(error).substring(0, 500);\n}\nconst errors = items.map(errorTextFor).filter(Boolean);\nif (operation === 'get') {\n  const events = [];\n  for (const item of items) {\n    const data = item.json || {};\n    if (data.error) continue;\n    const occurrenceStart = data.nextOccurrence && data.nextOccurrence.start || data.start || {};\n    const occurrenceEnd = data.nextOccurrence && data.nextOccurrence.end || data.end || {};\n    const eventStart = occurrenceStart.dateTime || occurrenceStart.date || '';\n    const eventEnd = occurrenceEnd.dateTime || occurrenceEnd.date || '';\n    if (!data.id && !data.summary && !eventStart && !eventEnd) continue;\n    const calendarSource = data.organizer && data.organizer.email || data.creator && data.creator.email || data.calendarSource || '';\n    events.push({\n      id: data.id || '',\n      calendar: calendarSource.includes('personal') || calendarSource.includes('googlemail') ? 'privat' : (calendarSource.includes('primary-project') ? 'primary-project' : calendarSource || 'calendar'),\n      title: data.summary || 'Ohne Titel',\n      description: data.description || '',\n      start: eventStart,\n      end: eventEnd,\n      location: data.location || ''\n    });\n  }\n  const seen = new Set();\n  let visibleEvents = events\n    .filter((event) => {\n      const key = (event.calendar || '') + ':' + (event.id || event.start + event.title);\n      if (seen.has(key)) return false;\n      seen.add(key);\n      return true;\n    })\n    .sort((a, b) => new Date(a.start).getTime() - new Date(b.start).getTime());\n  const windowStartMs = new Date(requestedTimeMin).getTime();\n  const windowEndMs = new Date(requestedTimeMax).getTime();\n  if (Number.isFinite(windowStartMs) || Number.isFinite(windowEndMs)) {\n    visibleEvents = visibleEvents.filter((event) => {\n      const startMs = new Date(event.start).getTime();\n      if (!Number.isFinite(startMs)) return false;\n      if (Number.isFinite(windowStartMs) && startMs < windowStartMs) return false;\n      if (Number.isFinite(windowEndMs) && startMs > windowEndMs) return false;\n      return true;\n    });\n  }\n  let premeetNote = '';\n  if (ctx.scenario === 'premeet') {\n    const staticData = $getWorkflowStaticData('global');\n    const notified = staticData.premeetNotifiedEventIds || {};\n    const now = new Date();\n    const minStart = now.getTime() + 10 * 60 * 1000;\n    const maxStart = now.getTime() + 30 * 60 * 1000;\n    for (const key of Object.keys(notified)) {\n      if (new Date(notified[key]).getTime() < now.getTime() - 36 * 60 * 60 * 1000) delete notified[key];\n    }\n    visibleEvents = visibleEvents.filter((event) => {\n      const startMs = new Date(event.start).getTime();\n      const eventKey = (event.calendar || '') + ':' + (event.id || event.start + event.title);\n      if (!Number.isFinite(startMs)) return false;\n      return startMs >= minStart && startMs <= maxStart && !notified[eventKey];\n    });\n    for (const event of visibleEvents) {\n      const eventKey = (event.calendar || '') + ':' + (event.id || event.start + event.title);\n      notified[eventKey] = now.toISOString();\n    }\n    staticData.premeetNotifiedEventIds = notified;\n    if (visibleEvents.length === 0) {\n      return [{ json: { success: true, toolWorkflow: 'calendar', operation: 'get', count: 0, summary: 'Kein neuer PreMeeting-Termin in 10-30 Minuten. Antworte mit FINAL: skip.' + (errors.length ? ' Kalender-Warnung: ' + errors.join(' | ') : ''), data: [] } }];\n    }\n    premeetNote = 'PreMeeting Kandidat, noch nicht gemeldet. ';\n  }\n  if (visibleEvents.length === 0) {\n    return [{ json: { success: errors.length === 0, toolWorkflow: 'calendar', operation: 'get', count: 0, summary: (errors.length ? 'Keine Termine gefunden. Kalender-Warnung: ' + errors.join(' | ') : 'Keine Termine gefunden.'), data: [] } }];\n  }\n  function formatStart(value) {\n    const date = new Date(value);\n    const datePart = date.toLocaleDateString('de-DE', {\n      day: '2-digit',\n      month: '2-digit',\n      year: 'numeric',\n      timeZone: 'Europe/Berlin'\n    });\n    const timePart = date.toLocaleTimeString('de-DE', {\n      hour: '2-digit',\n      minute: '2-digit',\n      hourCycle: 'h23',\n      timeZone: 'Europe/Berlin'\n    }).replace(':', '.');\n    return datePart + ', ' + timePart + ' Uhr';\n  }\n  const lines = [];\n  visibleEvents.forEach((event, index) => {\n    const startDate = formatStart(event.start);\n    lines.push(String(index + 1) + '. ' + startDate + ' | [' + event.calendar + '] ' + event.title + (event.location ? ' | Ort ' + event.location : ''));\n  });\n  const output = {\n    success: errors.length === 0,\n    toolWorkflow: 'calendar',\n    operation: 'get',\n    count: visibleEvents.length,\n    summary: premeetNote + lines.join('\\n') + (errors.length ? '\\nKalender-Warnung: ' + errors.join(' | ') : ''),\n    data: visibleEvents\n  };\n  return [ { json: output } ];\n}\nif (operation === 'create') {\n  const errorText = errors[0];\n  if (errorText) {\n    return [{ json: { success: false, toolWorkflow: 'calendar', operation: 'create', summary: 'Calendar Fehler: ' + errorText, error: errorText, data: [] } }];\n  }\n  const event = items[0] && items[0].json || {};\n  const rawStart = event.start && (event.start.dateTime || event.start.date) || '';\n  const start = rawStart ? new Date(rawStart).toLocaleString('de-DE', { timeZone: 'Europe/Berlin' }) : '';\n  const calendar = ['privat','private','personal','gmail','googlemail','personal'].some((value) => String(requestedCalendar).toLowerCase().includes(value)) ? 'privat' : 'primary-project';\n  const output = {\n    success: true,\n    toolWorkflow: 'calendar',\n    operation: 'create',\n    calendar,\n    summary: 'Termin erstellt in ' + calendar + ' ' + (event.summary || 'Ohne Titel') + (start ? ' am ' + start : ''),\n    data: { ...event, calendar }\n  };\n  return [ { json: output } ];\n}\nreturn [ { json: { success: false, toolWorkflow: 'calendar', error: 'Unbekannte Operation', summary: 'Calendar Fehler: Unbekannte Operation' } } ];",
        "mode": "runOnceForAllItems"
      },
      "id": "calendar-55730846-a6d0-40aa-8d9f-ab822a0bf633",
      "name": "Calendar - Format Output",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        4816,
        -288
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "op-field",
              "name": "operation",
              "value": "={{ $json.operation || 'search' }}",
              "type": "string"
            },
            {
              "id": "query-field",
              "name": "query",
              "value": "={{ $json.query || '' }}",
              "type": "string"
            },
            {
              "id": "pageid-field",
              "name": "pageId",
              "value": "={{ $json.pageId || '' }}",
              "type": "string"
            },
            {
              "id": "name-field",
              "name": "name",
              "value": "={{ $json.name || '' }}",
              "type": "string"
            },
            {
              "id": "company-field",
              "name": "company",
              "value": "={{ $json.company || '' }}",
              "type": "string"
            },
            {
              "id": "email-field",
              "name": "email",
              "value": "={{ $json.email || '' }}",
              "type": "string"
            },
            {
              "id": "status-field",
              "name": "status",
              "value": "={{ $json.status || 'Lead' }}",
              "type": "string"
            },
            {
              "id": "dealvalue-field",
              "name": "dealValue",
              "value": "={{ $json.dealValue || 0 }}",
              "type": "number"
            },
            {
              "id": "branche-field",
              "name": "branche",
              "value": "={{ $json.branche || '' }}",
              "type": "string"
            },
            {
              "id": "budget-field",
              "name": "budget",
              "value": "={{ $json.budget || '' }}",
              "type": "string"
            },
            {
              "id": "notes-field",
              "name": "notes",
              "value": "={{ $json.notes || '' }}",
              "type": "string"
            },
            {
              "id": "maxresults-field",
              "name": "maxResults",
              "value": "={{ $json.maxResults || 5 }}",
              "type": "number"
            },
            {
              "id": "activeonly-field",
              "name": "activeOnly",
              "value": "={{ Boolean($json.activeOnly) }}",
              "type": "boolean"
            }
          ]
        },
        "options": {}
      },
      "id": "notion-1102ad00-a684-4dfe-b9d7-24daceff5890",
      "name": "Notion - Set Parameters",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        4144,
        1056
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": false,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 1
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.operation }}",
                    "rightValue": "search",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "search"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": false,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 1
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.operation }}",
                    "rightValue": "read",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "read"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": false,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 1
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.operation }}",
                    "rightValue": "create",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "create"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": false,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 1
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.operation }}",
                    "rightValue": "update",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "update"
            }
          ]
        },
        "options": {}
      },
      "id": "notion-fbfee8b1-4cdf-4148-9ebc-9f36d5a5b13b",
      "name": "Notion - Operation Switch",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3,
      "position": [
        4368,
        1024
      ]
    },
    {
      "parameters": {
        "resource": "databasePage",
        "operation": "getAll",
        "databaseId": {
          "__rl": true,
          "value": "268a62f8-f3ff-8193-8538-e704c9493566",
          "mode": "list",
          "cachedResultName": "Kundendatenbank",
          "cachedResultUrl": "https://www.notion.so/268a62f8f3ff81938538e704c9493566"
        },
        "limit": "={{ $json.maxResults || 5 }}",
        "filterType": "manual",
        "matchType": "allFilters",
        "filters": {
          "conditions": [
            {
              "key": "Firmenname|url",
              "condition": "contains",
              "urlValue": "={{ $json.query }}"
            }
          ]
        },
        "options": {}
      },
      "id": "notion-e205c2c2-eb29-43ca-9d00-7f6de677a450",
      "name": "Notion - Notion Search",
      "type": "n8n-nodes-base.notion",
      "typeVersion": 2.2,
      "position": [
        4592,
        768
      ],
      "alwaysOutputData": true,
      "continueOnFail": true
    },
    {
      "parameters": {
        "resource": "databasePage",
        "operation": "get",
        "pageId": {
          "__rl": true,
          "value": "={{ $('Notion - Set Parameters').item.json.pageId }}",
          "mode": "id"
        }
      },
      "id": "notion-d2b9dc1a-6b22-4ff8-a1d4-b5672bbec8cf",
      "name": "Notion - Notion Read",
      "type": "n8n-nodes-base.notion",
      "typeVersion": 2.2,
      "position": [
        4592,
        960
      ],
      "continueOnFail": true
    },
    {
      "parameters": {
        "resource": "databasePage",
        "databaseId": {
          "__rl": true,
          "value": "268a62f8-f3ff-8193-8538-e704c9493566",
          "mode": "list",
          "cachedResultName": "Kundendatenbank",
          "cachedResultUrl": "https://www.notion.so/268a62f8f3ff81938538e704c9493566"
        },
        "title": "={{ $('Notion - Set Parameters').item.json.company || $('Notion - Set Parameters').item.json.name || 'Neuer Kontakt' }}",
        "propertiesUi": {
          "propertyValues": [
            {
              "key": "Ansprechperson|rich_text",
              "textContent": "={{ $('Notion - Set Parameters').item.json.name || '' }}"
            },
            {
              "key": "E-Mail-Adresse|email",
              "emailValue": "={{ $('Notion - Set Parameters').item.json.email || '' }}"
            },
            {
              "key": "Letzter Kontakt|date",
              "date": "={{ $('Notion - Set Parameters').item.json.lastContact || '' }}"
            },
            {
              "key": "Status|select",
              "selectValue": "={{ $('Notion - Set Parameters').item.json.status || 'Lead' }}"
            },
            {
              "key": "Tags|multi_select",
              "multiSelectValue": "={{ Array.isArray($('Notion - Set Parameters').item.json.tags) ? $('Notion - Set Parameters').item.json.tags : (String($('Notion - Set Parameters').item.json.tags || '').split(',').map(t => t.trim()).filter(Boolean)) }}"
            }
          ]
        },
        "options": {}
      },
      "id": "notion-844b2d9f-8eec-41cc-b0c1-ec09b9b40dd8",
      "name": "Notion - Notion Create",
      "type": "n8n-nodes-base.notion",
      "typeVersion": 2.2,
      "position": [
        4592,
        1152
      ],
      "continueOnFail": true
    },
    {
      "parameters": {
        "jsCode": "const notionParams = $('Notion - Set Parameters').item.json || {};\nconst operation = notionParams.operation;\nconst activeOnly = Boolean(notionParams.activeOnly);\nconst items = $input.all();\nfunction firstError() {\n  const item = items.find((entry) => entry.json && entry.json.error);\n  if (!item) return '';\n  const error = item.json.error;\n  if (typeof error === 'string') return error;\n  return error.message || error.description || JSON.stringify(error).substring(0, 500);\n}\nconst errorText = firstError();\nif (errorText) {\n  return [{ json: { success: false, operation, summary: 'Notion Fehler: ' + errorText, error: errorText, data: [] } }];\n}\n\nfunction extractProperties(props) {\n  const result = {};\n  for (const [key, value] of Object.entries(props || {})) {\n    if (value.title) {\n      result[key] = value.title.map(t => t.plain_text).join('');\n    } else if (value.rich_text) {\n      result[key] = value.rich_text.map(t => t.plain_text).join('');\n    } else if (value.email) {\n      result[key] = value.email;\n    } else if (value.select) {\n      result[key] = value.select && value.select.name || '';\n    } else if (value.number !== undefined) {\n      result[key] = value.number;\n    } else if (value.multi_select) {\n      result[key] = value.multi_select.map(s => s.name).join(', ');\n    }\n  }\n  return result;\n}\n\nif (operation === 'search') {\n  const usableItems = items.filter(item => item.json && item.json.id);\n  if (usableItems.length === 0) {\n    return [{\n      json: {\n        success: true,\n        operation: 'search',\n        count: 0,\n        summary: 'Keine Kunden gefunden.',\n        data: []\n      }\n    }];\n  }\n  \n  let customers = usableItems.map(item => {\n    const props = extractProperties(item.json.properties);\n    return {\n      id: item.json.id,\n      name: props['Name'] || props['Firmenname'] || 'Ohne Name',\n      company: props['Unternehmen'] || props['Firma'] || '',\n      email: props['E-Mail'] || props['E-Mail-Adresse'] || '',\n      status: props['Status'] || '',\n      dealValue: props['Deal Value'] || 0,\n      branche: props['Branche'] || ''\n    };\n  });\n  \n  if (activeOnly) {\n    customers = customers.filter((c) => ['lead', 'interessent'].includes(String(c.status || '').toLowerCase()));\n    if (customers.length === 0) {\n      return [{\n        json: {\n          success: true,\n          operation: 'search',\n          count: 0,\n          summary: 'Keine aktiven Leads gefunden.',\n          data: []\n        }\n      }];\n    }\n  }\n  const summary = customers.map((c, i) => \n    String(i + 1) + '. ' + c.name + ' | ' + c.company + ' | ' + c.status + ' | ' + c.dealValue + ' Euro'\n  ).join('\\n');\n  \n  return [{\n    json: {\n      success: true,\n      operation: 'search',\n      count: customers.length,\n      summary: summary,\n      data: customers\n    }\n  }];\n}\n\nif (operation === 'read') {\n  const item = items[0] && items[0].json || {};\n  const props = extractProperties(item.properties);\n  \n  const summary = 'Name: ' + (props['Name'] || props['Firmenname'] || 'Ohne Name') + '\\nUnternehmen: ' + (props['Unternehmen'] || props['Firma'] || '') + '\\nE-Mail: ' + (props['E-Mail'] || props['E-Mail-Adresse'] || '') + '\\nStatus: ' + (props['Status'] || '') + '\\nDeal Value: ' + (props['Deal Value'] || 0) + ' Euro\\nBranche: ' + (props['Branche'] || '') + '\\nBudget: ' + (props['Budget'] || '');\n  \n  return [{\n    json: {\n      success: true,\n      operation: 'read',\n      summary: summary,\n      data: { id: item.id, ...props }\n    }\n  }];\n}\n\nif (operation === 'create') {\n  const item = items[0] && items[0].json || {};\n  const props = extractProperties(item.properties);\n  \n  return [{\n    json: {\n      success: true,\n      operation: 'create',\n      summary: 'Kunde erstellt: ' + (props['Name'] || props['Firmenname'] || 'Neuer Kunde') + ' (' + (props['Unternehmen'] || props['Firma'] || '') + ')',\n      data: { id: item.id, url: item.url, ...props }\n    }\n  }];\n}\n\nif (operation === 'update') {\n  const item = items[0] && items[0].json || {};\n  const props = extractProperties(item.properties);\n  \n  return [{\n    json: {\n      success: true,\n      operation: 'update',\n      summary: 'Kunde aktualisiert: ' + (props['Name'] || props['Firmenname'] || 'Kunde'),\n      data: { id: item.id, ...props }\n    }\n  }];\n}\n\nreturn [{\n  json: {\n    success: false,\n    error: 'Unbekannte Operation',\n    summary: 'Notion Fehler: Unbekannte Operation'\n  }\n}];",
        "mode": "runOnceForAllItems"
      },
      "id": "notion-aba361e9-d779-4464-a4f6-8d1cc147bc30",
      "name": "Notion - Format Output",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        4816,
        1056
      ]
    },
    {
      "parameters": {
        "resource": "databasePage",
        "operation": "update",
        "pageId": {
          "__rl": true,
          "value": "={{ $('Notion - Set Parameters').item.json.pageId }}",
          "mode": "id"
        },
        "propertiesUi": {
          "propertyValues": [
            {
              "key": "=Status|select",
              "selectValue": "={{ $('Notion - Set Parameters').item.json.status }}"
            }
          ]
        },
        "options": {}
      },
      "id": "notion-b09cf110-15cf-4ba6-a785-578ce60912d7",
      "name": "Notion - Notion Update",
      "type": "n8n-nodes-base.notion",
      "typeVersion": 2.2,
      "position": [
        4592,
        1344
      ],
      "continueOnFail": true
    },
    {
      "parameters": {
        "jsCode": "const data = $input.first().json;\nconst target = (data.target || 'memory').toLowerCase();\nconst content = (data.memoryContent || '').trim();\nconst now = new Date();\nconst timeZone = 'Europe/Berlin';\nconst formatter = new Intl.DateTimeFormat('en-CA', {\n  timeZone,\n  year: 'numeric',\n  month: '2-digit',\n  day: '2-digit',\n  hour: '2-digit',\n  minute: '2-digit',\n  hourCycle: 'h23'\n});\nconst parts = Object.fromEntries(formatter.formatToParts(now).map((part) => [part.type, part.value]));\nconst dateStr = parts.year + '-' + parts.month + '-' + parts.day;\nconst timeStr = parts.hour + ':' + parts.minute;\nlet targetPath;\nif (target === 'daily') targetPath = 'Assistant/Daily/' + dateStr + '.md';\nelse if (target === 'decision') targetPath = 'Assistant/Decisions.md';\nelse targetPath = 'Assistant/Memory.md';\nconst entryPrefix = target === 'decision' ? '- ' + dateStr + ': ' : '- ' + dateStr + ' ' + timeStr + ': ';\nconst entry = entryPrefix + content;\nreturn [{ json: { target_path: targetPath, new_entry: entry, raw_content: content, target } }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3696,
        1616
      ],
      "id": "single-memory---prepare-memory-write",
      "name": "Memory - Prepare Memory Write"
    },
    {
      "parameters": {
        "url": "=https://api.github.com/repos/YOUR_GITHUB_USER/YOUR_MEMORY_REPO/contents/{{ $json.target_path }}",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "githubApi",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "ref",
              "value": "main"
            }
          ]
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/vnd.github+json"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        3920,
        1616
      ],
      "id": "single-memory---github-get-file",
      "name": "Memory - GitHub Get File",
      "continueOnFail": true
    },
    {
      "parameters": {
        "jsCode": "const prep = $('Memory - Prepare Memory Write').item.json;\nconst gh = $input.first().json || {};\nlet existing = '';\nlet sha = null;\nif (gh && gh.content && !gh.error) {\n  try { existing = Buffer.from(gh.content, 'base64').toString('utf-8'); sha = gh.sha; } catch (_) { existing = ''; }\n}\nfunction normalizeMemoryLine(value) {\n  return String(value || '')\n    .replace(/^[-*]\\s*\\d{4}-\\d{2}-\\d{2}(?:\\s+\\d{2}:\\d{2})?:\\s*/, '')\n    .replace(/\\s+/g, ' ')\n    .trim()\n    .toLowerCase();\n}\nconst trimmedEntry = (prep.new_entry || '').trim();\nconst normalizedRaw = normalizeMemoryLine(prep.raw_content || trimmedEntry);\nconst existingLines = existing.split(/\\r?\\n/).map(normalizeMemoryLine).filter(Boolean);\nconst semanticallyExists = !!normalizedRaw && existingLines.includes(normalizedRaw);\nconst exactExists = !!trimmedEntry && existing.includes(trimmedEntry);\nconst alreadyExists = exactExists || semanticallyExists;\nlet newContent = existing;\nif (!trimmedEntry) {\n  newContent = existing || '# Assistant Memory\\n';\n} else if (alreadyExists) {\n  newContent = existing || (trimmedEntry + '\\n');\n} else if (existing.trim().length === 0) {\n  if (prep.target_path.includes('/Daily/')) {\n    const dateOnly = prep.target_path.split('/').pop().replace('.md', '');\n    newContent = '# Daily ' + dateOnly + '\\n\\n' + trimmedEntry + '\\n';\n  } else if (prep.target_path.endsWith('Decisions.md')) {\n    newContent = '# Decisions\\n\\n' + trimmedEntry + '\\n';\n  } else {\n    newContent = '# Personal Memory\\n\\n' + trimmedEntry + '\\n';\n  }\n} else {\n  newContent = existing.trimEnd() + '\\n' + trimmedEntry + '\\n';\n}\nconst message = (alreadyExists ? 'Confirm assistant memory already exists' : 'Record assistant memory update') + '\\n\\n' + (alreadyExists ? 'Detected a duplicate memory entry while keeping markdown content stable.' : 'Append a compact personal-agent entry to ' + prep.target_path + ' for the Markdown memory-backed memory workflow.') + '\\n\\nConstraint: GitHub contents API stores markdown memory in YOUR_GITHUB_USER/YOUR_MEMORY_REPO on main\\nConfidence: medium\\nScope-risk: narrow\\nTested: n8n workflow generated update body\\nNot-tested: live GitHub credential authorization';\nconst body = { message, content: Buffer.from(newContent, 'utf-8').toString('base64'), branch: 'main' };\nif (sha) body.sha = sha;\nreturn [{ json: { target_path: prep.target_path, put_body: body, preview: alreadyExists ? ('Schon vorhanden: ' + trimmedEntry) : trimmedEntry, alreadyExists } }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        4144,
        1616
      ],
      "id": "single-memory---build-update-body",
      "name": "Memory - Build Update Body"
    },
    {
      "parameters": {
        "method": "PUT",
        "url": "=https://api.github.com/repos/YOUR_GITHUB_USER/YOUR_MEMORY_REPO/contents/{{ $json.target_path }}",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "githubApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/vnd.github+json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.put_body) }}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        4592,
        1536
      ],
      "id": "single-memory---github-put-file",
      "name": "Memory - GitHub Put File",
      "continueOnFail": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "ms",
              "name": "summary",
              "value": "={{ $('Memory - Build Update Body').item.json.alreadyExists ? ('Memory schon vorhanden, nicht nochmal angehaengt: ' + $('Memory - Prepare Memory Write').item.json.raw_content) : ($json.error ? ('Memory Fehler: ' + ($json.error.message || $json.error.description || JSON.stringify($json.error))) : ('Memory aktualisiert: ' + $('Memory - Build Update Body').item.json.target_path + ' -> ' + $('Memory - Build Update Body').item.json.preview)) }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        4816,
        1616
      ],
      "id": "single-memory---memory-write-result",
      "name": "Memory - Memory Write Result"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "feed-msg",
              "name": "message_text",
              "value": "={{ ($json.error || $json.success === false ? 'Tool Fehler: ' : 'Tool Ergebnis: ') + ($json.summary || $json.error || '') + '\\nTreffer IDs: ' + (Array.isArray($json.data) ? $json.data.map(d => d.id).join(', ') : '') + '\\nTool Data: ' + JSON.stringify($json.data || $json) + '\\nRegel: Nutze ausschlie\u00dflich diese Tool-Daten. Entscheide jetzt: Wenn die Daten reichen, antworte mit FINAL. Nutze nur dann ein weiteres ACTION, wenn wirklich ein anderes Tool erforderlich ist. Wenn count=0 oder Keine Termine gefunden/Keine E-Mails gefunden, erfinde nichts. Wenn ein Tool Fehler meldet, erkl\u00e4re den Fehler kurz und nutze kein weiteres Tool f\u00fcr denselben Versuch. F\u00fcr notion_update immer pageId=data[0].id nutzen, wenn genau 1 Treffer.' }}",
              "type": "string"
            },
            {
              "id": "feed-sid",
              "name": "sessionId",
              "value": "={{ $('Agent - Inject Memory').item.json.sessionId }}",
              "type": "string"
            },
            {
              "id": "feed-uname",
              "name": "username",
              "value": "={{ $('Agent - Inject Memory').item.json.username }}",
              "type": "string"
            },
            {
              "id": "feed-prompt",
              "name": "effectiveSystemPrompt",
              "value": "={{ $('Agent - Inject Memory').item.json.effectiveSystemPrompt }}",
              "type": "string"
            },
            {
              "id": "feed-system",
              "name": "systemPrompt",
              "value": "={{ $('Agent - Inject Memory').item.json.effectiveSystemPrompt }}",
              "type": "string"
            },
            {
              "id": "feed-chat",
              "name": "chat_id",
              "value": "={{ $('Agent - Inject Memory').item.json.chat_id }}",
              "type": "string"
            },
            {
              "id": "feed-voice",
              "name": "input_was_voice",
              "value": "={{ $('Agent - Inject Memory').item.json.input_was_voice }}",
              "type": "boolean"
            },
            {
              "id": "feed-source",
              "name": "source",
              "value": "={{ $('Agent - Inject Memory').item.json.source }}",
              "type": "string"
            },
            {
              "id": "feed-reply",
              "name": "replyMode",
              "value": "={{ $('Agent - Inject Memory').item.json.replyMode }}",
              "type": "string"
            },
            {
              "id": "feed-scenario",
              "name": "scenario",
              "value": "={{ $('Agent - Inject Memory').item.json.scenario }}",
              "type": "string"
            },
            {
              "id": "feed-debug",
              "name": "debugEnabled",
              "value": "={{ $('Agent - Inject Memory').item.json.debugEnabled }}",
              "type": "boolean"
            },
            {
              "id": "feed-slash",
              "name": "slash_command",
              "value": "={{ $('Agent - Inject Memory').item.json.slash_command }}",
              "type": "string"
            },
            {
              "id": "feed-itype",
              "name": "input_type",
              "value": "={{ $('Agent - Inject Memory').item.json.input_type }}",
              "type": "string"
            },
            {
              "id": "feed-force-voice",
              "name": "force_voice_reply",
              "value": "={{ $('Agent - Inject Memory').item.json.force_voice_reply }}",
              "type": "boolean"
            },
            {
              "id": "feed-last-tool-workflow",
              "name": "lastToolWorkflow",
              "value": "={{ $json.toolWorkflow || \"\" }}",
              "type": "string"
            },
            {
              "id": "feed-last-tool-operation",
              "name": "lastToolOperation",
              "value": "={{ $json.operation || \"\" }}",
              "type": "string"
            },
            {
              "id": "feed-last-tool-summary",
              "name": "lastToolSummary",
              "value": "={{ $json.summary || \"\" }}",
              "type": "string"
            },
            {
              "id": "feed-last-tool-count",
              "name": "lastToolCount",
              "value": "={{ typeof $json.count === \"number\" ? $json.count : (Array.isArray($json.data) ? $json.data.length : 0) }}",
              "type": "number"
            },
            {
              "id": "feed-last-tool-success",
              "name": "lastToolSuccess",
              "value": "={{ $json.success !== false && !$json.error }}",
              "type": "boolean"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        5040,
        752
      ],
      "id": "single-agent---tool-result-to-agent-input",
      "name": "Agent - Tool Result To Agent Input"
    },
    {
      "parameters": {
        "jsCode": "const staticData = $getWorkflowStaticData('global');\nconst execId = $execution.id;\nconst counterKey = 'loopCount_' + execId;\nlet count = Number(staticData[counterKey] || 0) + 1;\nstaticData[counterKey] = count;\nconst data = $input.first().json || {};\nif (data.debugEnabled) {\n  staticData.lastLoopCount = count;\n}\nif (count >= 3) {\n  delete staticData[counterKey];\n  const shortContext = String(data.message_text || '').substring(0, 500);\n  const output = {\n    ...data,\n    message_text: 'Fasse alles zusammen was du bisher herausgefunden hast und antworte dem User direkt. Bisheriger Kontext: ' + shortContext + '. WICHTIG: Antworte NUR mit FINAL: gefolgt von deiner Zusammenfassung. Nutze KEIN ACTION mehr.'\n  };\n  return [ { json: output } ];\n}\nreturn [ { json: data } ];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        5264,
        1488
      ],
      "id": "single-agent---loop-guard",
      "name": "Agent - Loop Guard"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "out",
              "name": "finalAnswer",
              "value": "={{ $json.finalAnswer }}",
              "type": "string"
            },
            {
              "id": "reply",
              "name": "replyMode",
              "value": "={{ $json.replyMode }}",
              "type": "string"
            },
            {
              "id": "chat",
              "name": "chat_id",
              "value": "={{ $json.chat_id }}",
              "type": "string"
            },
            {
              "id": "voice",
              "name": "input_was_voice",
              "value": "={{ $json.input_was_voice }}",
              "type": "boolean"
            },
            {
              "id": "source",
              "name": "source",
              "value": "={{ $json.source }}",
              "type": "string"
            },
            {
              "id": "scenario",
              "name": "scenario",
              "value": "={{ $json.scenario }}",
              "type": "string"
            },
            {
              "id": "debug",
              "name": "debugEnabled",
              "value": "={{ $json.debugEnabled }}",
              "type": "boolean"
            },
            {
              "id": "slash",
              "name": "slash_command",
              "value": "={{ $json.slash_command }}",
              "type": "string"
            },
            {
              "id": "memstatus",
              "name": "memoryStatus",
              "value": "={{ $json.memoryStatus }}",
              "type": "string"
            },
            {
              "id": "decstatus",
              "name": "decisionsStatus",
              "value": "={{ $json.decisionsStatus }}",
              "type": "string"
            },
            {
              "id": "attempted",
              "name": "attemptedTool",
              "value": "={{ $json.attemptedTool }}",
              "type": "string"
            },
            {
              "id": "toolerror",
              "name": "toolError",
              "value": "={{ $json.toolError }}",
              "type": "string"
            },
            {
              "id": "inputtype",
              "name": "input_type",
              "value": "={{ $json.input_type }}",
              "type": "string"
            },
            {
              "id": "forcevoice",
              "name": "force_voice_reply",
              "value": "={{ $json.force_voice_reply }}",
              "type": "boolean"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        3696,
        2144
      ],
      "id": "single-agent---return-final",
      "name": "Agent - Return Final"
    },
    {
      "parameters": {
        "jsCode": "const item = $input.first().json || {};\nconst response = String(item.text || item.answer || '');\nconst baseCtx = $('Agent - Inject Memory').item.json || {};\nlet toolCtx = {};\ntry {\n  toolCtx = $('Agent - Tool Result To Agent Input').item.json || {};\n} catch (error) {\n  toolCtx = {};\n}\nconst ctx = { ...baseCtx, ...toolCtx, ...item };\nconst staticData = $getWorkflowStaticData('global');\nconst execId = $execution.id;\nconst actionMatch = response.match(/ACTION:\\s*([a-zA-Z_][a-zA-Z0-9_]*)/i);\nconst paramsMatch = response.match(/PARAMS:\\s*([\\s\\S]+?)(?:\\n\\s*\\n|$)/i);\nconst finalMatch = response.match(/FINAL:\\s*([\\s\\S]+)/i);\nconst debugEnabled = Boolean(ctx.debugEnabled || staticData.debugEnabled);\nconst contextFields = {\n  chat_id: ctx.chat_id || '',\n  input_was_voice: Boolean(ctx.input_was_voice),\n  input_type: ctx.input_type || '',\n  force_voice_reply: Boolean(ctx.force_voice_reply),\n  source: ctx.source || '',\n  replyMode: ctx.replyMode || 'telegram',\n  scenario: ctx.scenario || '',\n  username: ctx.username || 'the user',\n  sessionId: ctx.sessionId || 'default',\n  debugEnabled,\n  slash_command: ctx.slash_command || '',\n  original_message_text: ctx.original_message_text || '',\n  memoryStatus: ctx.memoryStatus || '',\n  decisionsStatus: ctx.decisionsStatus || '',\n  lastToolWorkflow: ctx.lastToolWorkflow || '',\n  lastToolOperation: ctx.lastToolOperation || '',\n  lastToolSummary: ctx.lastToolSummary || '',\n  lastToolCount: typeof ctx.lastToolCount === 'number' ? ctx.lastToolCount : 0,\n  lastToolSuccess: ctx.lastToolSuccess !== false\n};\nfunction directFinal(message, extra = {}) {\n  delete staticData['loopCount_' + execId];\n  for (const prefix of ['healthStage_', 'rememberStage_', 'decisionStage_', 'testStage_', 'opsAgentStage_', 'doneStage_', 'prepStage_', 'shutdownStage_', 'leadsStage_', 'motivationStage_']) delete staticData[prefix + execId];\n  delete staticData['opsAgentToolResults_' + execId];\n  delete staticData['commandToolResults_' + execId];\n  return [{ json: { needsTool: false, toolWorkflow: 'none', finalAnswer: message, ...contextFields, ...extra } }];\n}\nfunction routeTool(toolWorkflow, operation, fields = {}) {\n  return [{ json: {\n    needsTool: true,\n    toolWorkflow,\n    operation,\n    query: '',\n    messageId: '',\n    to: '',\n    subject: '',\n    body: '',\n    title: '',\n    description: '',\n    start: '',\n    end: '',\n    calendar: 'primary-project',\n    timeMin: '',\n    timeMax: '',\n    maxResults: 10,\n    pageId: '',\n    name: '',\n    company: '',\n    email: '',\n    status: 'Lead',\n    dealValue: 0,\n    branche: '',\n    budget: '',\n    notes: '',\n    target: 'memory',\n    memoryContent: '',\n    originalResponse: response,\n    attemptedTool: toolWorkflow + '_' + operation,\n    ...contextFields,\n    ...fields\n  } }];\n}\nfunction parseParams(rawParams) {\n  const result = {};\n  if (!rawParams) return result;\n  const trimmed = rawParams.trim();\n  if (!trimmed) return result;\n  if (trimmed.startsWith('{')) {\n    try {\n      const parsed = JSON.parse(trimmed);\n      return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};\n    } catch (error) {\n      return { __parseError: 'PARAMS war kein g\u00fcltiges JSON.' };\n    }\n  }\n  const parts = trimmed.split(/,\\s*(?=[a-zA-Z_][a-zA-Z0-9_]*\\s*=)/);\n  for (const part of parts) {\n    const eq = part.indexOf('=');\n    if (eq > 0) {\n      const key = part.substring(0, eq).trim();\n      let value = part.substring(eq + 1).trim();\n      value = value.replace(/^['\"]|['\"]$/g, '');\n      result[key] = value;\n    }\n  }\n  return result;\n}\nfunction berlinParts(date) {\n  const formatter = new Intl.DateTimeFormat('en-CA', {\n    timeZone: 'Europe/Berlin', year: 'numeric', month: '2-digit', day: '2-digit',\n    hour: '2-digit', minute: '2-digit', second: '2-digit', hourCycle: 'h23'\n  });\n  const parts = Object.fromEntries(formatter.formatToParts(date).map((part) => [part.type, part.value]));\n  return { year: Number(parts.year), month: Number(parts.month), day: Number(parts.day), hour: Number(parts.hour), minute: Number(parts.minute), second: Number(parts.second) };\n}\nfunction offsetMs(date) {\n  const parts = berlinParts(date);\n  return Date.UTC(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute, parts.second) - date.getTime();\n}\nfunction berlinWallTimeToUtc(parts, hour, minute, second = 0) {\n  const guess = new Date(Date.UTC(parts.year, parts.month - 1, parts.day, hour, minute, second));\n  const first = new Date(guess.getTime() - offsetMs(guess));\n  return new Date(guess.getTime() - offsetMs(first));\n}\nfunction addBerlinDays(parts, days) {\n  const noon = berlinWallTimeToUtc(parts, 12, 0, 0);\n  noon.setUTCDate(noon.getUTCDate() + days);\n  return berlinParts(noon);\n}\nfunction todayWindow() {\n  const p = berlinParts(new Date());\n  return { timeMin: berlinWallTimeToUtc(p, 0, 0, 0).toISOString(), timeMax: berlinWallTimeToUtc(p, 23, 59, 59).toISOString() };\n}\nfunction tomorrowWindow() {\n  const p = addBerlinDays(berlinParts(new Date()), 1);\n  return { timeMin: berlinWallTimeToUtc(p, 0, 0, 0).toISOString(), timeMax: berlinWallTimeToUtc(p, 23, 59, 59).toISOString() };\n}\nfunction afternoonWindow() {\n  const now = new Date();\n  const parts = berlinParts(now);\n  const today19 = berlinWallTimeToUtc(parts, 19, 0, 0);\n  const fallbackStart = berlinWallTimeToUtc(parts, 15, 30, 0);\n  const timeMin = now.getTime() < today19.getTime() ? now : fallbackStart;\n  return { timeMin: timeMin.toISOString(), timeMax: today19.toISOString() };\n}\nfunction nextHourWindow() {\n  return { timeMin: new Date().toISOString(), timeMax: new Date(Date.now() + 60 * 60 * 1000).toISOString() };\n}\nfunction rememberContent() {\n  return String(ctx.original_message_text || '').replace(/^\\/remember\\s*/i, '').trim();\n}\nfunction decisionContent() {\n  return String(ctx.original_message_text || '').replace(/^\\/decision\\s*/i, '').trim();\n}\nfunction commandArg(...names) {\n  const escaped = names.map((name) => String(name).replace(/[^a-z0-9_]/gi, '')).filter(Boolean).join('|');\n  if (!escaped) return String(ctx.original_message_text || '').trim();\n  return String(ctx.original_message_text || '').replace(new RegExp('^/(' + escaped + ')(?:\\\\s+)?', 'i'), '').trim();\n}\nfunction stuckContent() {\n  return commandArg('stuck');\n}\nfunction doneContent() {\n  return commandArg('done');\n}\nfunction prepContent() {\n  return commandArg('prep');\n}\nfunction shutdownContent() {\n  return commandArg('shutdown');\n}\nfunction focusContent(command) {\n  return commandArg(command);\n}\nfunction upcomingWindow(hours = 36) {\n  const now = new Date();\n  return { timeMin: now.toISOString(), timeMax: new Date(now.getTime() + hours * 60 * 60 * 1000).toISOString() };\n}\nfunction weekToNowWindow() {\n  const now = new Date();\n  return { timeMin: new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString(), timeMax: now.toISOString() };\n}\nfunction formatBerlinDateTime(date) {\n  return date.toLocaleString('de-DE', { timeZone: 'Europe/Berlin', weekday: 'short', day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' });\n}\nfunction endOfToday() {\n  const p = berlinParts(new Date());\n  return berlinWallTimeToUtc(p, 23, 59, 59);\n}\nfunction tomorrowMorning() {\n  const p = addBerlinDays(berlinParts(new Date()), 1);\n  return berlinWallTimeToUtc(p, 9, 0, 0);\n}\nfunction parseFocusRequest(raw, defaultMinutes) {\n  const text = String(raw || '').trim();\n  const match = text.match(/\\b(\\d{1,3})\\s*(m|min|minute|minuten|h|std|stunde|stunden)?\\b/i);\n  let durationMinutes = defaultMinutes;\n  let task = text;\n  if (match) {\n    const amount = Number(match[1]);\n    const unit = String(match[2] || '').toLowerCase();\n    durationMinutes = unit.startsWith('h') || unit.startsWith('std') || unit.startsWith('stunde') ? amount * 60 : amount;\n    task = text.replace(match[0], '').trim();\n  }\n  durationMinutes = Math.max(10, Math.min(durationMinutes || defaultMinutes, 240));\n  return { durationMinutes, task: task || 'der wichtigste offene Block' };\n}\nfunction parseQuietUntil(raw, fallback = 'duration') {\n  const text = String(raw || '').toLowerCase().trim();\n  if (fallback === 'today' || /^(heute|today|abend)$/.test(text)) return endOfToday();\n  if (fallback === 'tomorrow' || /^(morgen|tomorrow)$/.test(text)) return tomorrowMorning();\n  const match = text.match(/(\\d{1,3})\\s*(m|min|minute|minuten|h|std|stunde|stunden)?/i);\n  if (match) {\n    const amount = Number(match[1]);\n    const unit = String(match[2] || '').toLowerCase();\n    const minutes = unit.startsWith('h') || unit.startsWith('std') || unit.startsWith('stunde') ? amount * 60 : amount;\n    return new Date(Date.now() + Math.max(10, Math.min(minutes || 120, 720)) * 60 * 1000);\n  }\n  return fallback === 'tomorrow' ? tomorrowMorning() : new Date(Date.now() + 2 * 60 * 60 * 1000);\n}\nfunction commandList() {\n  return [\n    '/heute - Tagesplan, Kalender und wichtige Mails',\n    '/morgen - Morgenplan und Vorbereitung',\n    '/fokus - Top 3 Priorit\u00e4ten',\n    '/focus <Minuten> <Aufgabe> - Fokusblock starten',\n    '/deepwork <Minuten> <Aufgabe> - Deep-Work-Block starten',\n    '/done <Ergebnis> - Ergebnis ins Daily schreiben',\n    '/stuck <Blocker> - Blockade l\u00f6sen',\n    '/prep [Thema] - n\u00e4chsten Termin vorbereiten',\n    '/shutdown [Notiz] - Tag schlie\u00dfen und morgen vorbereiten',\n    '/snooze <Dauer> - OpsAgent pausieren, z. B. 2h oder 30m',\n    '/quiet - OpsAgent bis heute Abend pausieren',\n    '/coach_on - OpsAgent wieder aktivieren',\n    '/inbox - wichtige Mails triagieren',\n    '/leads - aktive CRM-Leads anzeigen',\n    '/motivate - kurzer Fokusimpuls',\n    '/memory - Memory zusammenfassen',\n    '/remember <Info> - dauerhaft speichern',\n    '/decision <Text> - Entscheidung speichern',\n    '/health - Systemcheck',\n    '/test_calendar, /test_gmail, /test_memory - Tool-Smoke-Tests',\n    '/test_morning, /test_midday, /test_evening - Cron-Pfade manuell testen',\n    '/test_premeet - PreMeeting Smoke-Test',\n    '/debug_on, /debug_off, /debug_status - Debug steuern'\n  ].join('\\n');\n}\nfunction extractToolSummary(message) {\n  const text = String(message || '');\n  if (!text.startsWith('Tool Ergebnis:') && !text.startsWith('Tool Fehler:')) return '';\n  const marker = text.indexOf('\\nTreffer IDs:');\n  const summary = marker >= 0 ? text.slice(0, marker) : text;\n  return summary.replace(/^Tool (Ergebnis|Fehler):\\s*/, '').trim();\n}\nfunction rememberOpsAgentToolResult(stage) {\n  const explicitSummary = String(ctx.lastToolSummary || '').trim();\n  const summary = explicitSummary || extractToolSummary(ctx.message_text || '');\n  if (!summary) return;\n  const key = 'opsAgentToolResults_' + execId;\n  const results = staticData[key] || {};\n  const workflow = String(ctx.lastToolWorkflow || '').toLowerCase();\n  if (workflow === 'memory') return;\n  const looksLikeEmail = /Keine E-Mails gefunden|\\bVon\\b|\\bBetreff\\b|\\bAntworten\\b|\\bPr\u00fcfen\\b|\\bInfo\\b/i.test(summary);\n  const looksLikeCalendar = /Keine Termine gefunden|PreMeeting|\\| \\[|Termin/i.test(summary);\n  if (workflow === 'calendar') {\n    results.calendar = summary;\n  } else if (workflow === 'gmail') {\n    results.gmail = summary;\n  } else if (looksLikeEmail) {\n    results.gmail = summary;\n  } else if (looksLikeCalendar) {\n    results.calendar = summary;\n  } else if (stage === 'gmail') {\n    results.calendar = summary;\n  } else if (stage === 'final') {\n    if ((contextFields.scenario === 'morning' || contextFields.scenario === 'midday') && results.calendar) results.gmail = summary;\n    else results.calendar = summary;\n  }\n  staticData[key] = results;\n}\nfunction shortToolSummary(summary) {\n  return String(summary || '').trim().split('\\n').filter(Boolean).slice(0, 8).join('\\n');\n}\nfunction hasRealCalendar(summary) {\n  return Boolean(summary) && !/Keine Termine gefunden|Kein neuer PreMeeting-Termin/i.test(summary);\n}\nfunction hasRealGmail(summary) {\n  return Boolean(summary) && !/Keine E-Mails gefunden/i.test(summary);\n}\nfunction parseCalendarStartMs(line) {\n  const match = String(line || '').match(/(\\d{2})\\.(\\d{2})\\.(\\d{4}),\\s*(\\d{2})[.:](\\d{2})/);\n  if (!match) return NaN;\n  const [, day, month, year, hour, minute] = match;\n  const guess = new Date(Date.UTC(Number(year), Number(month) - 1, Number(day), Number(hour), Number(minute), 0));\n  const first = new Date(guess.getTime() - offsetMs(guess));\n  return new Date(guess.getTime() - offsetMs(first)).getTime();\n}\nfunction calendarStartsWithin(summary, minutes) {\n  const now = Date.now();\n  return String(summary || '').split('\\n').some((line) => {\n    const startMs = parseCalendarStartMs(line);\n    return Number.isFinite(startMs) && startMs >= now && startMs <= now + minutes * 60 * 1000;\n  });\n}\nfunction currentActiveFocus() {\n  const focus = staticData.activeFocus || null;\n  if (!focus) return null;\n  let until = focus.until ? new Date(focus.until) : null;\n  if ((!until || Number.isNaN(until.getTime())) && focus.at && focus.durationMinutes) {\n    until = new Date(new Date(focus.at).getTime() + Number(focus.durationMinutes) * 60 * 1000);\n  }\n  if (!until || Number.isNaN(until.getTime()) || until.getTime() <= Date.now()) {\n    delete staticData.activeFocus;\n    return null;\n  }\n  return { ...focus, until: until.toISOString() };\n}\nfunction limitLines(summary, maxLines = 5) {\n  return String(summary || '').trim().split('\\n').filter(Boolean).slice(0, maxLines).join('\\n');\n}\nfunction firstUsefulLine(summary) {\n  return String(summary || '').trim().split('\\n').find(Boolean) || '';\n}\nfunction rememberCommandToolResult(stage) {\n  const explicitSummary = String(ctx.lastToolSummary || '').trim();\n  const summary = explicitSummary || extractToolSummary(ctx.message_text || '');\n  if (!summary) return;\n  const key = 'commandToolResults_' + execId;\n  const results = staticData[key] || {};\n  const workflow = String(ctx.lastToolWorkflow || '').toLowerCase();\n  const looksLikeEmail = /Keine E-Mails gefunden|\\bVon\\b|\\bBetreff\\b|\\bAntworten\\b|\\bPr\u00fcfen\\b|\\bInfo\\b/i.test(summary);\n  const looksLikeCalendar = /Keine Termine gefunden|PreMeeting|\\| \\[|Termin/i.test(summary);\n  const looksLikeNotion = /Keine Kunden gefunden|Keine aktiven Leads gefunden|\\|\\s*(Lead|Interessent|Aktiv|Churned)\\s*\\|/i.test(summary);\n  if (workflow === 'calendar' || stage === 'calendar' || looksLikeCalendar) results.calendar = summary;\n  else if (workflow === 'gmail' || stage === 'gmail' || looksLikeEmail) results.gmail = summary;\n  else if (workflow === 'notion' || stage === 'notion' || looksLikeNotion) results.notion = summary;\n  staticData[key] = results;\n}\nfunction calendarLineForTelegram(line) {\n  return String(line || '')\n    .replace(/^\\d+\\.\\s+\\d{2}\\.\\d{2}\\.\\d{4},\\s*/, '')\n    .replace(/\\s+\\|\\s+\\[[^\\]]+\\]\\s+/, ' - ')\n    .trim();\n}\nfunction formatCalendarForTelegram(summary, maxLines = 8) {\n  const text = String(summary || '').trim();\n  if (!text || /Keine Termine gefunden/i.test(text)) return 'Keine Termine gefunden.';\n  return text.split('\\n').filter(Boolean).slice(0, maxLines).map(calendarLineForTelegram).join('\\n');\n}\nfunction formatGmailForTelegram(summary, maxLines = 3) {\n  const text = String(summary || '').trim();\n  if (!text || /Keine E-Mails gefunden/i.test(text)) return 'Keine relevanten E-Mails gefunden.';\n  return text.split('\\n').filter(Boolean).slice(0, maxLines).join('\\n');\n}\nfunction compactMemoryLines(text, maxLines = 6) {\n  const lines = String(text || '')\n    .split('\\n')\n    .map((line) => line.trim())\n    .filter(Boolean)\n    .filter((line) => !/^#{1,6}\\s/.test(line))\n    .filter((line) => !/^---+$/.test(line));\n  return lines.slice(Math.max(0, lines.length - maxLines)).join('\\n');\n}\nfunction buildMemoryFinal() {\n  const memory = compactMemoryLines(ctx.memoryText || '', 7);\n  const decisions = compactMemoryLines(ctx.decisionsText || '', 7);\n  return [\n    'Memory Check',\n    'Memory ' + (contextFields.memoryStatus || 'unbekannt'),\n    'Decisions ' + (contextFields.decisionsStatus || 'unbekannt'),\n    '',\n    'Aktuelles Memory',\n    memory || 'Keine Eintr\u00e4ge gefunden oder Datei nicht lesbar.',\n    '',\n    'Aktuelle Decisions',\n    decisions || 'Keine Eintr\u00e4ge gefunden oder Datei nicht lesbar.'\n  ].join('\\n');\n}\nfunction formatLeadsForTelegram(summary, maxLines = 10) {\n  const text = String(summary || '').trim();\n  if (!text || /Keine Kunden gefunden|Keine aktiven Leads gefunden/i.test(text)) return 'Keine aktiven Leads gefunden.';\n  const lines = text.split('\\n').filter(Boolean);\n  const active = lines.filter((line) => /\\|\\s*(Lead|Interessent)\\s*\\|/i.test(line));\n  const usable = (active.length ? active : lines).slice(0, maxLines);\n  return usable.map((line) => line.replace(/\\s+\\|\\s+\\|\\s+/g, ' | ').replace(/\\s+\\|\\s+0 Euro$/i, '').trim()).join('\\n');\n}\nfunction buildLeadsFinal() {\n  const results = staticData['commandToolResults_' + execId] || {};\n  return ['Aktive Leads', '', formatLeadsForTelegram(results.notion || '', 10)].join('\\n');\n}\nfunction buildMotivationFinal() {\n  const results = staticData['commandToolResults_' + execId] || {};\n  const calendarRaw = String(results.calendar || '').trim();\n  const hasCalendar = hasRealCalendar(calendarRaw);\n  const calendar = formatCalendarForTelegram(calendarRaw, 5);\n  return [\n    'OpsAgent kurz dazu',\n    '',\n    hasCalendar ? 'Heute h\u00e4ngt es an einer Entscheidung\\n' + calendar : 'Kalender ist ruhig. Sch\u00f6n f\u00fcr ihn. F\u00fcr dich hei\u00dft das nur, dass du die L\u00fccke nicht mit Herumklicken f\u00fcllst.',\n    '',\n    'W\u00e4hl jetzt eine Sache, die sichtbar fertig werden kann. Vorbereitung z\u00e4hlt erst, wenn danach etwas anders ist.'\n  ].join('\\n');\n}\nfunction buildHealthFinal() {\n  const results = staticData['opsAgentToolResults_' + execId] || {};\n  const calendarOk = Boolean(results.calendar);\n  const gmailOk = Boolean(results.gmail);\n  return [\n    'Healthcheck',\n    'Command Router ok',\n    'Memory ' + (contextFields.memoryStatus || 'unbekannt'),\n    'Decisions ' + (contextFields.decisionsStatus || 'unbekannt'),\n    'Calendar ' + (calendarOk ? 'ok' : 'nicht best\u00e4tigt'),\n    'Gmail ' + (gmailOk ? 'ok' : 'nicht best\u00e4tigt'),\n    '',\n    'Letzter Calendar Check ' + (firstUsefulLine(results.calendar || '') || 'kein Ergebnis'),\n    'Letzter Gmail Check ' + (firstUsefulLine(results.gmail || '') || 'kein Ergebnis')\n  ].join('\\n');\n}\nfunction buildTodayFinal(kind = 'heute') {\n  const results = staticData['commandToolResults_' + execId] || {};\n  const calendar = formatCalendarForTelegram(results.calendar || '', kind === 'fokus' ? 5 : 8);\n  const gmail = formatGmailForTelegram(results.gmail || '', 3);\n  if (kind === 'fokus') {\n    return [\n      'Fokus f\u00fcr jetzt',\n      '',\n      'Kalender',\n      calendar,\n      '',\n      'Mails',\n      gmail,\n      '',\n      'Nimm den n\u00e4chsten echten Block und mach daraus sichtbaren Fortschritt. Nicht nochmal sortieren. Liefern.'\n    ].join('\\n');\n  }\n  return [\n    'Heute liegt an',\n    '',\n    calendar,\n    '',\n    'Mails',\n    gmail,\n    '',\n    'Such dir den n\u00e4chsten Kalenderblock und gib ihm ein Ergebnis. Eine saubere Lieferung schl\u00e4gt f\u00fcnf offene Baustellen mit gutem Bauchgef\u00fchl.'\n  ].join('\\n');\n}\nfunction buildTomorrowFinal() {\n  const results = staticData['commandToolResults_' + execId] || {};\n  const calendar = formatCalendarForTelegram(results.calendar || '', 8);\n  return [\n    'Morgen liegt an',\n    '',\n    calendar,\n    '',\n    'Markier den Block mit der meisten Reibung und leg den ersten Schritt bereit. Morgen bitte weniger Selbstworkshop.'\n  ].join('\\n');\n}\nfunction buildInboxFinal() {\n  const results = staticData['commandToolResults_' + execId] || {};\n  const text = String(results.gmail || '').trim();\n  if (!text || /Keine E-Mails gefunden/i.test(text)) {\n    return ['Inbox Check', '', 'Keine relevanten E-Mails gefunden.', '', 'Gut. Dann nicht aus Gewohnheit zehnmal reinschauen. Nimm den n\u00e4chsten echten Block.'].join('\\n');\n  }\n  const buckets = { antworten: [], lesen: [], warten: [], sp\u00e4ter: [] };\n  for (const line of text.split('\\n').filter(Boolean).slice(0, 8)) {\n    const lower = line.toLowerCase();\n    if (/\\b(reply|antwort|antworten|frage|termin|meeting|angebot|deadline|dringend|bitte)\\b/.test(lower)) buckets.antworten.push(line);\n    else if (/lead|anfrage|bewerbung|update|freelance|projektvorschlag|projektvorschl\u00e4ge/.test(lower)) buckets.lesen.push(line);\n    else if (/rechnung|receipt|github|token|noreply|no-reply|notification|benachrichtigung/.test(lower)) buckets.warten.push(line);\n    else buckets.sp\u00e4ter.push(line);\n  }\n  function section(title, entries) {\n    return entries.length ? [title, ...entries.slice(0, 3)].join('\\n') : '';\n  }\n  return [\n    'Inbox Check',\n    section('Antworten', buckets.antworten),\n    section('Kurz lesen', buckets.lesen),\n    section('Warten und Info', buckets.warten),\n    section('Sp\u00e4ter', buckets.sp\u00e4ter),\n    'Jetzt nur Antworten anfassen. Lesen ist kein Arbeiten, wenn danach kein n\u00e4chster Schritt entsteht.'\n  ].filter(Boolean).join('\\n\\n');\n}\nfunction buildStuckFinal(content) {\n  const blocker = content || 'das, was gerade klemmt';\n  return [\n    'Okay. Es klemmt. Nicht sch\u00f6n, aber auch kein Drama.',\n    '',\n    '1. Benennen ' + blocker + '.',\n    '2. Klein schneiden. Welcher Schritt dauert unter 10 Minuten?',\n    '3. 25 Minuten Timer. Nur diesen Schritt. Danach neu entscheiden, nicht vorher.',\n    '',\n    'Du musst das nicht emotional ausdiskutieren. Du brauchst eine Entscheidung f\u00fcr den n\u00e4chsten kleinen Schritt.'\n  ].join('\\n');\n}\nfunction buildFocusFinal(kind, raw) {\n  const parsed = parseFocusRequest(raw, kind === 'deepwork' ? 90 : 45);\n  const now = new Date();\n  const until = new Date(now.getTime() + parsed.durationMinutes * 60 * 1000);\n  staticData.activeFocus = { at: now.toISOString(), until: until.toISOString(), type: kind, durationMinutes: parsed.durationMinutes, task: parsed.task };\n  const label = kind === 'deepwork' ? 'Deep Work' : 'Fokus';\n  return [\n    label + ' l\u00e4uft f\u00fcr ' + parsed.durationMinutes + ' Minuten.',\n    '',\n    'Aufgabe ' + parsed.task,\n    '',\n    'Handy weg, Tabs zu, ein Output. Danach /done ' + parsed.task\n  ].join('\\n');\n}\nfunction buildDoneMemoryContent(content) {\n  const focus = staticData.activeFocus || {};\n  const focusText = focus.task ? ' Fokusblock ' + focus.task + ' (' + focus.durationMinutes + ' Minuten).' : '';\n  return 'Done ' + content + focusText;\n}\nfunction buildDoneFinal(content) {\n  const result = String(ctx.lastToolSummary || '').trim();\n  delete staticData.activeFocus;\n  const status = /schon vorhanden/i.test(result) ? 'War schon im Daily.' : 'Gespeichert.';\n  return [status, '', content, '', 'Gut. Kurz pr\u00fcfen, ob wirklich zu, dann den n\u00e4chsten kleinen Block setzen.'].join('\\n');\n}\nfunction buildPrepFinal(topic) {\n  const results = staticData['commandToolResults_' + execId] || {};\n  const calendar = String(results.calendar || '').trim();\n  const hasEvent = hasRealCalendar(calendar);\n  const focus = topic || 'den n\u00e4chsten Termin';\n  return [\n    'Prep',\n    '',\n    hasEvent ? 'N\u00e4chster Termin\\n' + formatCalendarForTelegram(calendar, 3) : 'Kein n\u00e4chster Termin in den n\u00e4chsten 36 Stunden gefunden. Dann preppe ' + focus + '.',\n    '',\n    'Was soll danach klarer sein als vorher?',\n    'Erste gute Frage \u201eWas w\u00e4re f\u00fcr dich nach dem Termin ein gutes Ergebnis?\u201c',\n    'Am Ende festnageln. N\u00e4chster Schritt, Person, Zeitpunkt.'\n  ].join('\\n');\n}\nfunction buildShutdownFinal(note) {\n  const results = staticData['commandToolResults_' + execId] || {};\n  const calendar = formatCalendarForTelegram(results.calendar || '', 5);\n  return [\n    'Shutdown',\n    '',\n    'Heute sauber schlie\u00dfen. Keine neue Baustelle mehr aufrei\u00dfen.',\n    note ? 'Offen lassen ' + note : '',\n    '',\n    'Morgen',\n    calendar,\n    '',\n    'Erster Handgriff morgen. Eine Datei, ein Tab oder eine Aufgabe direkt \u00f6ffnen. Nicht erst den Tag verhandeln.'\n  ].filter((line) => line !== '').join('\\n');\n}\nfunction buildQuietFinal(until, label = 'snooze') {\n  const human = formatBerlinDateTime(until);\n  return label === 'coach_on'\n    ? 'OpsAgent ist wieder aktiv.'\n    : 'OpsAgent pausiert bis ' + human + '. Wenn du fr\u00fcher wieder willst, /coach_on';\n}\nfunction buildPremeetTestFinal() {\n  const summary = String(ctx.lastToolSummary || '').trim() || extractToolSummary(ctx.message_text || '');\n  if (!hasRealCalendar(summary)) return 'skip';\n  return [\n    'PreMeeting Test',\n    '',\n    formatCalendarForTelegram(summary, 2),\n    '',\n    'Vorher Ziel kl\u00e4ren, erste Frage parat haben, n\u00e4chsten Schritt am Ende festnageln.'\n  ].join('\\n');\n}\nfunction buildOpsAgentFinal(scenario) {\n  const results = staticData['opsAgentToolResults_' + execId] || {};\n  const calendar = shortToolSummary(results.calendar || '');\n  const gmail = shortToolSummary(results.gmail || '');\n  const hasCalendar = hasRealCalendar(calendar);\n  const hasGmail = hasRealGmail(gmail);\n  const topCalendar = limitLines(calendar, scenario === 'weekly_review' ? 8 : scenario === 'premeet' ? 3 : 5);\n  const topGmail = limitLines(gmail, scenario === 'weekly_review' ? 5 : 3);\n  const NL = String.fromCharCode(10);\n  const NL2 = NL + NL;\n  function openingOf(variant) {\n    const first = Array.isArray(variant) ? variant.find(Boolean) : variant;\n    return String(first || '').split(NL)[0].trim();\n  }\n  function choose(variants) {\n    const seed = String(execId || '') + '|' + String(contextFields.sessionId || '') + '|' + scenario + '|' + topCalendar.length + '|' + topGmail.length;\n    let hash = 0;\n    for (let i = 0; i < seed.length; i += 1) hash = ((hash * 31) + seed.charCodeAt(i)) | 0;\n    const recent = Array.isArray(staticData.opsAgentRecentOpeners) ? staticData.opsAgentRecentOpeners : [];\n    const ordered = variants.map((variant, index) => ({ variant, index, score: Math.abs(hash + index * 17) % 997 })).sort((a, b) => a.score - b.score);\n    const picked = (ordered.find((entry) => !recent.some((item) => item && item.scenario === scenario && item.opening === openingOf(entry.variant))) || ordered[0]).variant;\n    const opening = openingOf(picked);\n    staticData.opsAgentRecentOpeners = [\n      { at: new Date().toISOString(), scenario, opening },\n      ...recent.filter((item) => item && item.opening !== opening).slice(0, 14)\n    ];\n    return picked;\n  }\n  function blocks(items) {\n    return items.filter(Boolean).join(NL2);\n  }\n  if (scenario === 'weekly_review') {\n    return blocks(choose([\n      [\n        'Freitag, the user. OpsAgent r\u00e4umt die Woche einmal auf, bevor du sie wieder halb offen ins Wochenende schleppst.',\n        hasCalendar ? 'Was sichtbar drinlag' + NL + topCalendar : 'Kalender sagt wenig. Das hei\u00dft nicht automatisch, dass wenig passiert ist.',\n        hasGmail ? 'Mails, die noch nach Arbeit riechen' + NL + topGmail : 'Keine relevanten Mails gefunden. Sch\u00f6n, aber bitte nicht als Leistung verkaufen.',\n        'Eine Sache f\u00fcr n\u00e4chste Woche festlegen. Nicht f\u00fcnf gute Vors\u00e4tze. Eine Sache, die Montag zuerst auf dem Tisch liegt.'\n      ],\n      [\n        'Wochenabschluss. Kurz und ohne Wellness.',\n        hasCalendar ? 'Kalender der Woche' + NL + topCalendar : 'Kalender war d\u00fcnn. Dann z\u00e4hlt, was du wirklich geliefert hast.',\n        hasGmail ? 'Inbox Reste' + NL + topGmail : 'Inbox gibt gerade nichts Wichtiges her.',\n        'Jetzt einen offenen Faden schlie\u00dfen oder sauber parken. Montag soll nicht mit Sucherei anfangen.'\n      ],\n      [\n        'Freitagabend. Bevor der Kopf so tut, als w\u00e4re alles gekl\u00e4rt.',\n        hasCalendar ? 'Diese Woche auf dem Papier' + NL + topCalendar : 'Auf dem Papier war wenig los. In echt bitte trotzdem ehrlich pr\u00fcfen.',\n        hasGmail ? 'Mails mit Restdruck' + NL + topGmail : 'Keine relevanten Mails gefunden.',\n        'N\u00e4chste Woche braucht einen ersten Handgriff. Datei, Aufgabe oder Nachricht bereit legen. Mehr Pathos braucht niemand.'\n      ]\n    ]));\n  }\n  if (scenario === 'premeet') {\n    if (!hasCalendar) return 'skip';\n    return blocks(choose([\n      [\n        'Termin gleich, the user. Bitte nicht reinlaufen wie jemand, der auf Eingebung hofft.',\n        topCalendar,\n        'Vorher eine Sache festlegen. Was muss danach klarer sein als vorher?',\n        'Erster Satz \u201eWas w\u00e4re nach dem Termin ein gutes Ergebnis?\u201c',\n        'Zum Schluss nicht weich rausgehen. N\u00e4chster Schritt, Person, Zeitpunkt.'\n      ],\n      [\n        'Kurzer Termin Blick. Wenig Magie, nur Vorbereitung.',\n        topCalendar,\n        'Eine gute Frage reicht am Anfang.',\n        'Frag \u201eWoran merken wir am Ende, dass das hier sinnvoll war?\u201c',\n        'Am Ende festhalten, wer was bis wann macht.'\n      ],\n      [\n        'Gleich Termin. Improvisieren ist heute kein Konzept.',\n        topCalendar,\n        'Ziel vorher setzen, ersten Satz parat haben, danach eine konkrete Folgeaktion sichern.'\n      ]\n    ]));\n  }\n  if (scenario === 'evening') {\n    if (!hasCalendar) {\n      return blocks(choose([\n        [\n          'Feierabend, the user. Morgen ist kalenderseitig leer.',\n          'Das klingt entspannter als es ist. Offene Fl\u00e4che f\u00fcllt sich sonst mit Kleinkram und gutem Gewissen.',\n          'Leg jetzt einen konkreten Einstieg bereit. Eine Datei, ein Tab oder eine Aufgabe. Morgen nicht erst suchen.'\n        ],\n        [\n          'Morgen steht nichts Fixes drin.',\n          'Gut. Und gef\u00e4hrlich. Ohne erste Aufgabe wird daraus wieder Herumr\u00e4umen mit h\u00fcbscher Ausrede.',\n          'Leg einen Einstieg bereit. Klein reicht. Hauptsache morgen nicht verhandeln.'\n        ],\n        [\n          'Schlussstrich f\u00fcr heute.',\n          'Morgen hat keinen festen Takt von au\u00dfen. Also setzt du den ersten Takt selbst.',\n          'Eine Sache sichtbar parken, die morgen als erstes angefasst wird.'\n        ]\n      ]));\n    }\n    return blocks(choose([\n      [\n        'Morgen ist nicht leer.',\n        topCalendar,\n        'Termine liefern noch nichts. Vorbereitung schon.',\n        'F\u00fcr den wichtigsten Block jetzt eine Notiz, Datei oder ersten Satz bereit legen.'\n      ],\n      [\n        'Morgen hat Termine. Also heute nicht romantisch ausklingen lassen.',\n        topCalendar,\n        'Such dir den anstrengendsten Block raus und bereite den Einstieg vor. Mehr nicht.'\n      ],\n      [\n        'Morgen ist vorgezeichnet genug, um nicht blind reinzulaufen.',\n        topCalendar,\n        'Eine Vorbereitung reicht. Was soll nach dem wichtigsten Termin klarer sein? Schreib das jetzt hin.'\n      ]\n    ]));\n  }\n  if (scenario === 'midday') {\n    if (!hasCalendar && !hasGmail) {\n      return blocks(choose([\n        [\n          'Halber Tag rum, the user. Von au\u00dfen kommt gerade wenig Druck.',\n          'Das ist nur gut, wenn du die L\u00fccke nicht mit Inbox Rotation vollkleisterst.',\n          'Jetzt 45 Minuten auf eine Sache. Sichtbar fertig oder sichtbar weiter. Nicht h\u00fcbsch vorbereitet.'\n        ],\n        [\n          'Mittag. Der Kalender hilft gerade nicht beim Priorisieren.',\n          'Dann bitte nicht so tun, als w\u00e4re Sortieren Arbeit.',\n          'Eine Aufgabe w\u00e4hlen, 45 Minuten, Ergebnis sichtbar machen. Danach neu schauen.'\n        ],\n        [\n          'Mittag. Es brennt von au\u00dfen nichts.',\n          'Genau da wird der Tag gern weich. Bitte nicht.',\n          'Nimm eine Sache, die heute noch einen echten Stand bekommt. Timer an.'\n        ]\n      ]));\n    }\n    return blocks(choose([\n      [\n        'Halbzeit. Wir schneiden jetzt runter.',\n        hasCalendar ? 'Nachmittag' + NL + topCalendar : 'Kalender' + NL + 'Keine Termine gefunden.',\n        hasGmail ? 'Mails, die du nicht ewig anstarren musst' + NL + topGmail : 'E-Mails' + NL + 'Keine relevanten E-Mails gefunden.',\n        'Warten kann alles, was nur Ordnung simuliert.',\n        'Eine Sache sichtbar weiterbringen. Danach erst wieder sortieren.'\n      ],\n      [\n        'Mittag, the user. Nicht nochmal den ganzen Tag neu erfinden.',\n        hasCalendar ? 'Nachmittag' + NL + topCalendar : 'Kalender' + NL + 'Keine Termine gefunden.',\n        hasGmail ? 'Mails' + NL + topGmail : 'E-Mails' + NL + 'Keine relevanten E-Mails gefunden.',\n        'Jetzt z\u00e4hlt ein Output, der nachher wirklich existiert.'\n      ],\n      [\n        'Kurzer Reset. Der Nachmittag braucht weniger Denken \u00fcber Arbeit und mehr Arbeit.',\n        hasCalendar ? 'Kalender' + NL + topCalendar : 'Kalender' + NL + 'Keine Termine gefunden.',\n        hasGmail ? 'Mails' + NL + topGmail : 'E-Mails' + NL + 'Keine relevanten E-Mails gefunden.',\n        'Such dir den kleinsten sinnvollen Abschluss. Nicht perfekt, nur fertig genug.'\n      ]\n    ]));\n  }\n  if (!hasCalendar && !hasGmail) {\n    return blocks(choose([\n      [\n        'Guten Morgen the user. Keine Termine, keine relevanten Mails. Das ist kein Freizeitbeweis.',\n        'Der Tag wird sonst wieder weich. Du kennst das Spiel leider schon, also machen wir es simpel.',\n        'Eine Aufgabe w\u00e4hlen. 45 Minuten blocken. Ergebnis vorher in einem Satz benennen. Dann machen.'\n      ],\n      [\n        'Morgen. Von au\u00dfen kommt gerade nichts, also musst du dich selbst f\u00fchren. Unbequem, ist aber so.',\n        'Bitte nicht erst Systeme sortieren und das Arbeit nennen.',\n        'Jetzt eine Aufgabe, ein sichtbarer Stand, danach neu schauen. Nicht erst noch den perfekten Start suchen.'\n      ],\n      [\n        'Guten Morgen. Der Kalender f\u00fchrt heute nicht f\u00fcr dich.',\n        'Also nimm eine Sache, setz den Timer und liefer ein sichtbares Ergebnis. Danach darfst du wieder nachdenken.'\n      ]\n    ]));\n  }\n  return blocks(choose([\n    [\n      'Morgen, the user. Der Kalender ist voll genug. Dein \u00fcbliches Warmlaufen ist heute der teuerste Teil, also lassen wir das.',\n      hasCalendar ? 'Termine' + NL + topCalendar : 'Kalender' + NL + 'Keine Termine gefunden.',\n      hasGmail ? 'Mails' + NL + topGmail : 'E-Mails' + NL + 'Keine relevanten E-Mails gefunden.',\n      'Ziel in einen Satz. Dann liefern. Nicht wieder drei Baustellen anfassen und es \u00dcberblick nennen.'\n    ],\n    [\n      'Guten Morgen. Nichts davon ist dramatisch, aber genau genug Material f\u00fcr gepflegtes Verz\u00f6gern.',\n      hasCalendar ? 'Kalender' + NL + topCalendar : 'Kalender' + NL + 'Keine Termine gefunden.',\n      hasGmail ? 'Mails' + NL + topGmail : 'E-Mails' + NL + 'Keine relevanten E-Mails gefunden.',\n      'Ein Block, ein Ergebnis. Keine Nebenkriegsschaupl\u00e4tze. Langweilig, ja. Wirksam, leider auch.'\n    ],\n    [\n      'Start in den Tag. Der operative Kram liegt da. Bitte keine kleine Selbstfindungsrunde davor.',\n      hasCalendar ? 'Termine' + NL + topCalendar : 'Kalender' + NL + 'Keine Termine gefunden.',\n      hasGmail ? 'Mails' + NL + topGmail : 'E-Mails' + NL + 'Keine relevanten E-Mails gefunden.',\n      'Kleinsten sinnvollen Einstieg nehmen. 45 Minuten. Danach muss etwas sichtbar anders sein, sonst war es nur Besch\u00e4ftigung mit besserem Namen.'\n    ]\n  ]));\n}\nfunction buildEveningDailyMemory(finalAnswer) {\n  const results = staticData['opsAgentToolResults_' + execId] || {};\n  const calendar = shortToolSummary(results.calendar || '');\n  const firstCalendar = firstUsefulLine(calendar);\n  const focus = hasRealCalendar(calendar)\n    ? 'Erster relevanter Kalenderpunkt morgen ' + firstCalendar.replace(/^\\d+\\.\\s*/, '')\n    : 'Morgen ohne fixe Termine. Erster eigener Startpunkt muss gesetzt werden.';\n  return 'Evening Check. ' + focus + ' OpsAgent Notiz. Einstieg f\u00fcr morgen vorab kl\u00e4ren, keine Kalenderkopie.';\n}\nfunction recordOpsAgentRun(scenario, finalAnswer, source = contextFields.source || contextFields.slash_command || '') {\n  const results = staticData['opsAgentToolResults_' + execId] || {};\n  staticData.lastOpsAgentRun = {\n    at: new Date().toISOString(),\n    executionId: execId,\n    scenario,\n    source,\n    calendarFound: hasRealCalendar(results.calendar || ''),\n    gmailFound: hasRealGmail(results.gmail || ''),\n    skipped: String(finalAnswer || '').trim().toLowerCase().startsWith('skip')\n  };\n}\nfunction testScenarioLabel(scenario) {\n  if (scenario === 'morning') return 'Morning-Test';\n  if (scenario === 'midday') return 'Midday-Test';\n  if (scenario === 'weekly_review') return 'Weekly-Test';\n  return 'Evening-Test';\n}\n\nif (contextFields.slash_command === 'stuck') {\n  const content = stuckContent();\n  staticData.lastStuckReset = { at: new Date().toISOString(), input: content };\n  return directFinal(buildStuckFinal(content));\n}\nif (contextFields.slash_command === 'focus' || contextFields.slash_command === 'deepwork') {\n  return directFinal(buildFocusFinal(contextFields.slash_command, focusContent(contextFields.slash_command)));\n}\nif (contextFields.slash_command === 'done') {\n  const stageKey = 'doneStage_' + execId;\n  const content = doneContent();\n  if (!content) return directFinal('Nutze /done <was fertig wurde>, dann schreibe ich es ins Daily.');\n  if (!staticData[stageKey]) {\n    staticData[stageKey] = 'memory';\n    return routeTool('memory', 'write', { target: 'daily', memoryContent: buildDoneMemoryContent(content) });\n  }\n  delete staticData[stageKey];\n  return directFinal(buildDoneFinal(content));\n}\nif (contextFields.slash_command === 'prep') {\n  const stageKey = 'prepStage_' + execId;\n  const stage = staticData[stageKey] || 'calendar';\n  rememberCommandToolResult('calendar');\n  if (stage === 'calendar') {\n    staticData[stageKey] = 'final';\n    return routeTool('calendar', 'get', { ...upcomingWindow(36), maxResults: 5 });\n  }\n  delete staticData[stageKey];\n  return directFinal(buildPrepFinal(prepContent()));\n}\nif (contextFields.slash_command === 'shutdown') {\n  const stageKey = 'shutdownStage_' + execId;\n  const stage = staticData[stageKey] || 'calendar';\n  rememberCommandToolResult('calendar');\n  if (stage === 'calendar') {\n    staticData[stageKey] = 'final';\n    return routeTool('calendar', 'get', { ...tomorrowWindow(), maxResults: 8 });\n  }\n  delete staticData[stageKey];\n  return directFinal(buildShutdownFinal(shutdownContent()));\n}\nif (['snooze', 'quiet', 'coach_off'].includes(contextFields.slash_command)) {\n  const raw = commandArg(contextFields.slash_command === 'coach_off' ? 'coach_off' : contextFields.slash_command);\n  const fallback = contextFields.slash_command === 'quiet' ? 'today' : contextFields.slash_command === 'coach_off' ? 'tomorrow' : 'duration';\n  const until = parseQuietUntil(raw, fallback);\n  staticData.opsAgentQuietUntil = until.toISOString();\n  return directFinal(buildQuietFinal(until, contextFields.slash_command));\n}\nif (contextFields.slash_command === 'coach_on') {\n  delete staticData.opsAgentQuietUntil;\n  return directFinal(buildQuietFinal(new Date(), 'coach_on'));\n}\nif (contextFields.slash_command === 'heute' || contextFields.slash_command === 'fokus') {\n  const stageKey = 'commandStage_' + execId;\n  const stage = staticData[stageKey] || 'calendar';\n  rememberCommandToolResult(stage === 'gmail' ? 'calendar' : stage);\n  if (stage === 'calendar') {\n    staticData[stageKey] = 'gmail';\n    return routeTool('calendar', 'get', { ...todayWindow(), maxResults: 10 });\n  }\n  if (stage === 'gmail') {\n    staticData[stageKey] = 'final';\n    return routeTool('gmail', 'search', { query: contextFields.slash_command === 'fokus' ? 'newer_than:1d' : 'newer_than:24h', maxResults: 3 });\n  }\n  rememberCommandToolResult('gmail');\n  delete staticData[stageKey];\n  return directFinal(buildTodayFinal(contextFields.slash_command));\n}\nif (contextFields.slash_command === 'morgen') {\n  const stageKey = 'commandStage_' + execId;\n  const stage = staticData[stageKey] || 'calendar';\n  rememberCommandToolResult('calendar');\n  if (stage === 'calendar') {\n    staticData[stageKey] = 'final';\n    return routeTool('calendar', 'get', { ...tomorrowWindow(), maxResults: 10 });\n  }\n  delete staticData[stageKey];\n  return directFinal(buildTomorrowFinal());\n}\nif (contextFields.slash_command === 'inbox') {\n  const stageKey = 'commandStage_' + execId;\n  const stage = staticData[stageKey] || 'gmail';\n  rememberCommandToolResult('gmail');\n  if (stage === 'gmail') {\n    staticData[stageKey] = 'final';\n    return routeTool('gmail', 'search', { query: 'newer_than:1d', maxResults: 5 });\n  }\n  delete staticData[stageKey];\n  return directFinal(buildInboxFinal());\n}\nif (contextFields.slash_command === 'leads') {\n  const stageKey = 'leadsStage_' + execId;\n  const stage = staticData[stageKey] || 'notion';\n  rememberCommandToolResult('notion');\n  if (stage === 'notion') {\n    staticData[stageKey] = 'final';\n    return routeTool('notion', 'search', { query: '', maxResults: 20, activeOnly: true });\n  }\n  delete staticData[stageKey];\n  return directFinal(buildLeadsFinal());\n}\nif (contextFields.slash_command === 'motivation') {\n  const stageKey = 'motivationStage_' + execId;\n  const stage = staticData[stageKey] || 'calendar';\n  rememberCommandToolResult('calendar');\n  if (stage === 'calendar') {\n    staticData[stageKey] = 'final';\n    return routeTool('calendar', 'get', { ...todayWindow(), maxResults: 5 });\n  }\n  delete staticData[stageKey];\n  return directFinal(buildMotivationFinal());\n}\nif (contextFields.slash_command === 'commands') return directFinal(commandList());\nif (contextFields.slash_command === 'memory') return directFinal(buildMemoryFinal());\nif (contextFields.slash_command === 'debug_on') return directFinal('Debug-Modus ist aktiviert.');\nif (contextFields.slash_command === 'debug_off') return directFinal('Debug-Modus ist deaktiviert.');\nif (contextFields.slash_command === 'debug_status') return directFinal('Debug-Modus ist ' + (debugEnabled ? 'aktiviert.' : 'deaktiviert.'));\nif (contextFields.slash_command === 'remember') {\n  const stageKey = 'rememberStage_' + execId;\n  const content = rememberContent();\n  if (!content) return directFinal('Nutze /remember <Info>, damit ich etwas dauerhaft in Assistant/Memory.md speichere.');\n  if (!staticData[stageKey]) {\n    staticData[stageKey] = 'memory';\n    return routeTool('memory', 'write', { target: 'memory', memoryContent: content });\n  }\n  delete staticData[stageKey];\n  return directFinal(finalMatch ? finalMatch[1].trim() : 'Gespeichert oder bereits vorhanden ' + content);\n}\nif (contextFields.slash_command === 'decision') {\n  const stageKey = 'decisionStage_' + execId;\n  const content = decisionContent();\n  if (!content) return directFinal('Nutze /decision <Text>, damit ich eine Entscheidung in Assistant/Decisions.md speichere.');\n  if (!staticData[stageKey]) {\n    staticData[stageKey] = 'decision';\n    return routeTool('memory', 'write', { target: 'decision', memoryContent: content });\n  }\n  delete staticData[stageKey];\n  return directFinal(finalMatch ? finalMatch[1].trim() : 'Entscheidung gespeichert oder bereits vorhanden ' + content);\n}\nif (contextFields.slash_command === 'health') {\n  const stageKey = 'healthStage_' + execId;\n  const stage = staticData[stageKey] || 'calendar';\n  rememberOpsAgentToolResult(stage);\n  if (stage === 'calendar') {\n    staticData[stageKey] = 'gmail';\n    return routeTool('calendar', 'get', { ...todayWindow(), maxResults: 1 });\n  }\n  if (stage === 'gmail') {\n    staticData[stageKey] = 'final';\n    return routeTool('gmail', 'search', { query: 'newer_than:1d', maxResults: 1 });\n  }\n  delete staticData[stageKey];\n  return directFinal(buildHealthFinal());\n}\nif (contextFields.slash_command === 'test') {\n  const raw = String(ctx.original_message_text || '').toLowerCase();\n  const stageKey = 'testStage_' + execId;\n  const opsAgentScenario = ['morning', 'midday', 'evening', 'weekly_review'].find((name) => raw.includes(name) || raw.includes('test_' + name) || (name === 'weekly_review' && raw.includes('weekly'))) || (['morning', 'midday', 'evening', 'weekly_review'].includes(contextFields.scenario) ? contextFields.scenario : '');\n  if (opsAgentScenario) {\n    const stage = staticData[stageKey] || 'calendar';\n    rememberOpsAgentToolResult(stage === 'gmail' ? 'calendar' : stage);\n    if (stage === 'calendar') {\n      if (opsAgentScenario === 'morning') {\n        staticData[stageKey] = 'gmail';\n        return routeTool('calendar', 'get', { ...todayWindow(), maxResults: 10, scenario: opsAgentScenario, testScenario: opsAgentScenario });\n      }\n      if (opsAgentScenario === 'midday') {\n        staticData[stageKey] = 'gmail';\n        return routeTool('calendar', 'get', { ...afternoonWindow(), maxResults: 10, scenario: opsAgentScenario, testScenario: opsAgentScenario });\n      }\n      if (opsAgentScenario === 'weekly_review') {\n        staticData[stageKey] = 'gmail';\n        return routeTool('calendar', 'get', { ...weekToNowWindow(), maxResults: 15, scenario: opsAgentScenario, testScenario: opsAgentScenario });\n      }\n      staticData[stageKey] = 'daily';\n      return routeTool('calendar', 'get', { ...tomorrowWindow(), maxResults: 10, scenario: opsAgentScenario, testScenario: opsAgentScenario });\n    }\n    if (stage === 'gmail' && (opsAgentScenario === 'morning' || opsAgentScenario === 'midday' || opsAgentScenario === 'weekly_review')) {\n      staticData[stageKey] = 'final';\n      const query = opsAgentScenario === 'morning' ? 'newer_than:1d' : opsAgentScenario === 'weekly_review' ? 'newer_than:7d' : 'newer_than:6h';\n      return routeTool('gmail', 'search', { query, maxResults: opsAgentScenario === 'weekly_review' ? 5 : 3, scenario: opsAgentScenario, testScenario: opsAgentScenario });\n    }\n    const storedTestFinalKey = 'testOpsAgentFinal_' + execId;\n    if (stage === 'daily' && opsAgentScenario === 'evening') {\n      const opsAgentFinal = buildOpsAgentFinal(opsAgentScenario);\n      staticData[storedTestFinalKey] = testScenarioLabel(opsAgentScenario) + '\\n\\n' + opsAgentFinal;\n      staticData[stageKey] = 'final';\n      return routeTool('memory', 'write', { target: 'daily', memoryContent: buildEveningDailyMemory(opsAgentFinal), scenario: opsAgentScenario, testScenario: opsAgentScenario });\n    }\n    if (stage === 'final' && opsAgentScenario === 'evening' && staticData[storedTestFinalKey]) {\n      const finalAnswer = staticData[storedTestFinalKey];\n      delete staticData[storedTestFinalKey];\n      recordOpsAgentRun(opsAgentScenario, finalAnswer, 'manual_test');\n      delete staticData[stageKey];\n      return directFinal(finalAnswer, { scenario: opsAgentScenario, testScenario: opsAgentScenario });\n    }\n    const opsAgentFinal = buildOpsAgentFinal(opsAgentScenario);\n    recordOpsAgentRun(opsAgentScenario, opsAgentFinal, 'manual_test');\n    delete staticData[stageKey];\n    return directFinal(testScenarioLabel(opsAgentScenario) + '\\n\\n' + opsAgentFinal, { scenario: opsAgentScenario, testScenario: opsAgentScenario });\n  }\n  if ((raw.includes('calendar') || raw.includes('test_calendar')) && !staticData[stageKey]) {\n    staticData[stageKey] = 'calendar';\n    return routeTool('calendar', 'get', { ...todayWindow(), maxResults: 3 });\n  }\n  if ((raw.includes('gmail') || raw.includes('test_gmail')) && !staticData[stageKey]) {\n    staticData[stageKey] = 'gmail';\n    return routeTool('gmail', 'search', { query: 'newer_than:1d', maxResults: 3 });\n  }\n  if ((raw.includes('premeet') || raw.includes('test_premeet')) && !staticData[stageKey]) {\n    staticData[stageKey] = 'premeet';\n    return routeTool('calendar', 'get', { ...nextHourWindow(), maxResults: 3, scenario: 'premeet' });\n  }\n  if (staticData[stageKey] === 'premeet') {\n    delete staticData[stageKey];\n    return directFinal(buildPremeetTestFinal());\n  }\n  if (staticData[stageKey] === 'calendar') {\n    delete staticData[stageKey];\n    return directFinal(formatCalendarForTelegram(String(ctx.lastToolSummary || '').trim() || extractToolSummary(ctx.message_text || ''), 5));\n  }\n  if (staticData[stageKey] === 'gmail') {\n    delete staticData[stageKey];\n    return directFinal(formatGmailForTelegram(String(ctx.lastToolSummary || '').trim() || extractToolSummary(ctx.message_text || ''), 5));\n  }\n  delete staticData[stageKey];\n}\nif (!contextFields.slash_command && (contextFields.source === 'heartbeat' || contextFields.replyMode === 'telegram_push' || contextFields.scenario)) {\n  const quietUntil = staticData.opsAgentQuietUntil ? new Date(staticData.opsAgentQuietUntil) : null;\n  if (quietUntil && !Number.isNaN(quietUntil.getTime()) && quietUntil.getTime() > Date.now()) {\n    return directFinal('skip', { quietUntil: staticData.opsAgentQuietUntil });\n  }\n  if (quietUntil && !Number.isNaN(quietUntil.getTime()) && quietUntil.getTime() <= Date.now()) delete staticData.opsAgentQuietUntil;\n  const activeFocus = currentActiveFocus();\n  if (activeFocus && contextFields.scenario !== 'premeet') {\n    staticData.lastOpsAgentSkip = { at: new Date().toISOString(), reason: 'active_focus', scenario: contextFields.scenario || '', until: activeFocus.until };\n    return directFinal('skip', { activeFocusUntil: activeFocus.until });\n  }\n}\nif (contextFields.source === 'heartbeat' || contextFields.scenario) {\n  const stageKey = 'opsAgentStage_' + execId;\n  const scenario = contextFields.scenario || '';\n  const stage = staticData[stageKey] || 'calendar';\n  rememberOpsAgentToolResult(stage);\n  if (stage === 'calendar') {\n    if (scenario === 'morning') {\n      staticData[stageKey] = 'gmail';\n      return routeTool('calendar', 'get', { ...todayWindow(), maxResults: 10 });\n    }\n    if (scenario === 'midday') {\n      staticData[stageKey] = 'gmail';\n      return routeTool('calendar', 'get', { ...afternoonWindow(), maxResults: 10 });\n    }\n    if (scenario === 'evening') {\n      staticData[stageKey] = 'daily';\n      return routeTool('calendar', 'get', { ...tomorrowWindow(), maxResults: 10 });\n    }\n    if (scenario === 'weekly_review') {\n      staticData[stageKey] = 'gmail';\n      return routeTool('calendar', 'get', { ...weekToNowWindow(), maxResults: 15 });\n    }\n    if (scenario === 'premeet') {\n      staticData[stageKey] = 'final';\n      return routeTool('calendar', 'get', { ...nextHourWindow(), maxResults: 3, scenario: 'premeet' });\n    }\n  }\n  if (stage === 'gmail' && (scenario === 'morning' || scenario === 'midday' || scenario === 'weekly_review')) {\n    const results = staticData['opsAgentToolResults_' + execId] || {};\n    if (scenario !== 'weekly_review' && scenario !== 'premeet' && calendarStartsWithin(results.calendar || '', 5)) {\n      staticData.lastOpsAgentSkip = { at: new Date().toISOString(), reason: 'meeting_starting', scenario };\n      delete staticData[stageKey];\n      return directFinal('skip');\n    }\n    staticData[stageKey] = 'final';\n    const query = scenario === 'morning' ? 'newer_than:1d' : scenario === 'weekly_review' ? 'newer_than:7d' : 'newer_than:6h';\n    return routeTool('gmail', 'search', { query, maxResults: scenario === 'weekly_review' ? 5 : 3 });\n  }\n  const storedOpsAgentFinalKey = 'opsAgentFinal_' + execId;\n  if (stage === 'daily' && scenario === 'evening') {\n    const opsAgentFinal = buildOpsAgentFinal(scenario);\n    staticData[storedOpsAgentFinalKey] = opsAgentFinal;\n    staticData[stageKey] = 'final';\n    return routeTool('memory', 'write', { target: 'daily', memoryContent: buildEveningDailyMemory(opsAgentFinal), scenario });\n  }\n  if (stage === 'final') {\n    const opsAgentFinal = staticData[storedOpsAgentFinalKey] || buildOpsAgentFinal(scenario);\n    delete staticData[storedOpsAgentFinalKey];\n    if (scenario !== 'premeet') recordOpsAgentRun(scenario, opsAgentFinal, 'heartbeat');\n    delete staticData[stageKey];\n    return directFinal(opsAgentFinal);\n  }\n}\n\nconst params = parseParams(paramsMatch && paramsMatch[1]);\nif (params.__parseError) {\n  return directFinal('Ich konnte die Tool Parameter nicht lesen. Bitte formuliere die Anfrage nochmal klarer.', { toolError: params.__parseError });\n}\nif (actionMatch) {\n  const tool = actionMatch[1].toLowerCase();\n  let toolWorkflow = 'none';\n  let operation = '';\n  if (tool === 'calendar') {\n    toolWorkflow = 'calendar';\n    operation = params.operation || 'get';\n  } else if (tool === 'gmail') {\n    toolWorkflow = 'gmail';\n    operation = params.operation || 'search';\n  } else if (tool === 'memory') {\n    toolWorkflow = 'memory';\n    operation = params.operation || 'write';\n  } else if (tool.startsWith('gmail_')) {\n    toolWorkflow = 'gmail';\n    operation = tool.replace('gmail_', '');\n  } else if (tool.startsWith('calendar_')) {\n    toolWorkflow = 'calendar';\n    operation = tool.replace('calendar_', '');\n  } else if (tool.startsWith('notion_')) {\n    toolWorkflow = 'notion';\n    operation = tool.replace('notion_', '');\n  } else if (tool.startsWith('memory_')) {\n    toolWorkflow = 'memory';\n    operation = tool.replace('memory_', '');\n  } else {\n    return directFinal('Dieses Tool kenne ich nicht ' + tool + '. Ich habe die Anfrage nicht ausgefuehrt.', { toolError: 'unknown_tool', attemptedTool: tool });\n  }\n  const allowed = {\n    gmail: ['search', 'read', 'draft'],\n    calendar: ['get', 'create'],\n    notion: ['search', 'read', 'create', 'update'],\n    memory: ['write']\n  };\n  if (!allowed[toolWorkflow].includes(operation)) {\n    return directFinal('Diese Tool Operation ist nicht erlaubt ' + tool + '.', { toolError: 'invalid_operation', attemptedTool: tool });\n  }\n  if (toolWorkflow === 'gmail' && operation === 'read' && !params.messageId) {\n    return directFinal('Welche E-Mail soll ich lesen? Mir fehlt die messageId.', { toolError: 'missing_messageId', attemptedTool: tool });\n  }\n  if (toolWorkflow === 'gmail' && operation === 'draft' && (!params.to || !params.subject || !params.body)) {\n    return directFinal('F\u00fcr den E-Mail-Entwurf fehlen Empf\u00e4nger, Betreff oder Inhalt.', { toolError: 'missing_gmail_draft_params', attemptedTool: tool });\n  }\n  if (toolWorkflow === 'calendar' && operation === 'create' && (!params.title || !params.start || !params.end)) {\n    return directFinal('F\u00fcr den Termin fehlen Titel, Start oder Ende.', { toolError: 'missing_calendar_create_params', attemptedTool: tool });\n  }\n  if (toolWorkflow === 'notion' && ['read', 'update'].includes(operation) && !params.pageId) {\n    return directFinal('F\u00fcr diese Notion-Aktion fehlt die pageId. Ich muss zuerst suchen oder du gibst mir die ID.', { toolError: 'missing_pageId', attemptedTool: tool });\n  }\n  if (toolWorkflow === 'memory' && operation === 'write' && !(params.content || params.body)) {\n    return directFinal('Was soll ich speichern? Mir fehlt der content f\u00fcr memory_write.', { toolError: 'missing_memory_content', attemptedTool: tool });\n  }\n  const calendarTarget = String(params.calendar || params.targetCalendar || 'primary-project').toLowerCase();\n  const normalizedCalendar = ['privat', 'private', 'personal', 'gmail', 'googlemail', 'personal'].some((value) => calendarTarget.includes(value)) ? 'privat' : 'primary-project';\n  const output = {\n    needsTool: true,\n    toolWorkflow,\n    operation,\n    query: params.query || '',\n    messageId: params.messageId || '',\n    to: params.to || '',\n    subject: params.subject || '',\n    body: params.body || '',\n    title: params.title || '',\n    description: params.description || '',\n    start: params.start || '',\n    end: params.end || '',\n    calendar: normalizedCalendar,\n    timeMin: params.timeMin || '',\n    timeMax: params.timeMax || '',\n    maxResults: parseInt(params.maxResults, 10) || 10,\n    pageId: params.pageId || '',\n    name: params.name || '',\n    company: params.company || '',\n    email: params.email || '',\n    status: params.status || 'Lead',\n    dealValue: parseInt(params.dealValue, 10) || 0,\n    branche: params.branche || '',\n    budget: params.budget || '',\n    notes: params.notes || '',\n    target: params.target || 'memory',\n    memoryContent: params.content || params.body || '',\n    originalResponse: response,\n    attemptedTool: tool,\n    ...contextFields\n  };\n  if (debugEnabled) {\n    staticData.lastAgentAction = { at: new Date().toISOString(), tool, toolWorkflow, operation, params };\n  }\n  return [ { json: output } ];\n}\n\ndelete staticData['loopCount_' + execId];\nconst finalAnswer = finalMatch ? finalMatch[1].trim() : response;\nconst lastAction = staticData.lastAgentAction || {};\nreturn [ { json: { needsTool: false, toolWorkflow: 'none', finalAnswer, ...contextFields, debugLastAction: lastAction } } ];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3248,
        1056
      ],
      "id": "single-agent-parse-tool-request",
      "name": "Agent - Parse Tool Request"
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "tool-gmail",
                    "leftValue": "={{ $json.toolWorkflow }}",
                    "rightValue": "gmail",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "gmail"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "tool-calendar",
                    "leftValue": "={{ $json.toolWorkflow }}",
                    "rightValue": "calendar",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "calendar"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "tool-notion",
                    "leftValue": "={{ $json.toolWorkflow }}",
                    "rightValue": "notion",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "notion"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "tool-memory",
                    "leftValue": "={{ $json.toolWorkflow }}",
                    "rightValue": "memory",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "memory"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "tool-none",
                    "leftValue": "={{ $json.needsTool }}",
                    "rightValue": "",
                    "operator": {
                      "type": "boolean",
                      "operation": "false",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "no_tool"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.3,
      "position": [
        3472,
        1008
      ],
      "id": "single-agent-tool-router",
      "name": "Agent - Tool Router"
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "reply-telegram",
                    "leftValue": "={{ $json.replyMode }}",
                    "rightValue": "telegram",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "telegram"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "reply-chat",
                    "leftValue": "={{ $json.replyMode }}",
                    "rightValue": "chat",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "chat"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "reply-push",
                    "leftValue": "={{ $json.replyMode }}",
                    "rightValue": "telegram_push",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "telegram_push"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.3,
      "position": [
        3920,
        2128
      ],
      "id": "single-reply-mode-router",
      "name": "Reply Mode Router"
    },
    {
      "parameters": {
        "jsCode": "function polishVisibleText(text) {\n  let output = String(text || '').replace(/^FINAL:\\s*/i, '').trim();\n  const replacements = [\n    [/Management meint/gi, 'OpsAgent sagt'],\n    [/Management[- ]Reset/gi, 'Reset'],\n    [/Lagebild/gi, 'Stand'],\n    [/\\bCoach\\b/gi, 'OpsAgent'],\n    [/\\bHebel\\b/gi, 'Ansatz'],\n    [/Gamechanger/gi, 'Wendepunkt'],\n    [/Qwen/gi, 'Modell'],\n    [/naechsten/gi, 'n\u00e4chsten'],\n    [/naechste/gi, 'n\u00e4chste'],\n    [/naechster/gi, 'n\u00e4chster'],\n    [/laeuft/gi, 'l\u00e4uft'],\n    [/fuer/gi, 'f\u00fcr'],\n    [/ueber/gi, '\u00fcber'],\n    [/zurueck/gi, 'zur\u00fcck'],\n    [/spaeter/gi, 'sp\u00e4ter'],\n    [/pruef/gi, 'pr\u00fcf'],\n    [/waehlen/gi, 'w\u00e4hlen'],\n    [/groesste/gi, 'gr\u00f6\u00dfte'],\n    [/haengt/gi, 'h\u00e4ngt'],\n    [/loesen/gi, 'l\u00f6sen'],\n    [/schliessen/gi, 'schlie\u00dfen'],\n    [/oeffnen/gi, '\u00f6ffnen']\n  ];\n  for (const [pattern, value] of replacements) output = output.replace(pattern, value);\n  output = output.replace(new RegExp('\\\\\\\\([_*\\\\[\\\\]()~`>#+\\\\-=|{}.!])', 'g'), '$1');\n  output = output.replace(/\\*\\*(.*?)\\*\\*/g, '$1');\n  output = output.replace(/__(.*?)__/g, '$1');\n  output = output.replace(/\\*(.*?)\\*/g, '$1');\n  output = output.replace(/`([^`]*)`/g, '$1');\n  output = output.replace(/:/g, '.');\n  output = output.replace(/\\s+([.,!?])/g, '$1');\n  output = output.replace(/&/g, '&amp;');\n  output = output.replace(/</g, '&lt;');\n  output = output.replace(/>/g, '&gt;');\n  output = output.replace(/\\n{3,}/g, '\\n\\n');\n  return output.trim().slice(0, 3800);\n}\nconst response = $input.first().json;\nconst staticData = $getWorkflowStaticData('global');\nlet output = response.finalAnswer || response.text || response.answer || 'Keine Antwort.';\noutput = String(output).replace(/^FINAL:\\s*/i, '').trim();\nif (!output || output.toLowerCase() === 'skip' || output.toLowerCase().startsWith('skip')) {\n  return [];\n}\nif (response.debugEnabled || staticData.debugEnabled) {\n  const lastAction = staticData.lastAgentAction || {};\n  const debugLines = [\n    '',\n    '[debug]',\n    'replyMode=' + (response.replyMode || ''),\n    'scenario=' + (response.scenario || ''),\n    'command=' + (response.slash_command || ''),\n    'memory=' + (response.memoryStatus || ''),\n    'decisions=' + (response.decisionsStatus || ''),\n    'lastTool=' + (lastAction.tool || response.attemptedTool || ''),\n    'loopCount=' + (staticData.lastLoopCount || 0),\n    response.toolError ? 'toolError=' + response.toolError : ''\n  ].filter(Boolean);\n  output += '\\n' + debugLines.join('\\n');\n}\nreturn [{ json: { ...response, formatted_output: polishVisibleText(output) } }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        4144,
        1952
      ],
      "id": "single-reply-format-interactive-output",
      "name": "Reply - Format Interactive Output"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "chat-text",
              "name": "text",
              "value": "={{ $json.finalAnswer }}",
              "type": "string"
            },
            {
              "id": "chat-answer",
              "name": "answer",
              "value": "={{ $json.finalAnswer }}",
              "type": "string"
            },
            {
              "id": "chat-output",
              "name": "output",
              "value": "={{ $json.finalAnswer }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        4144,
        2336
      ],
      "id": "single-reply-chat-output",
      "name": "Reply - Chat Output"
    },
    {
      "parameters": {
        "operation": "getAll",
        "calendar": {
          "__rl": true,
          "value": "personal@example.com",
          "mode": "list",
          "cachedResultName": "personal@example.com"
        },
        "limit": "={{ $('Calendar - Set Parameters').item.json.maxResults }}",
        "options": {
          "timeMin": "={{ $('Calendar - Set Parameters').item.json.timeMin }}",
          "timeMax": "={{ $('Calendar - Set Parameters').item.json.timeMax || undefined }}"
        }
      },
      "id": "6653d655-6af1-4cdb-9de2-55457b75d502",
      "name": "Calendar - Calendar privat",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1.2,
      "position": [
        4368,
        -384
      ],
      "alwaysOutputData": true,
      "continueOnFail": true
    },
    {
      "parameters": {
        "mode": "append",
        "numberInputs": 2,
        "options": {}
      },
      "id": "single-calendar-merge-calendars",
      "name": "Calendar - Merge Calendars",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        4592,
        -480
      ],
      "alwaysOutputData": true
    },
    {
      "parameters": {
        "mode": "expression",
        "numberOutputs": 2,
        "output": "={{ ['privat','private','personal','gmail','googlemail','personal'].some(v => String($json.calendar || '').toLowerCase().includes(v)) ? 1 : 0 }}"
      },
      "id": "single-calendar-create-target-router",
      "name": "Calendar - Create Target Router",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.3,
      "position": [
        4368,
        -96
      ]
    },
    {
      "parameters": {
        "calendar": {
          "__rl": true,
          "value": "personal@example.com",
          "mode": "list",
          "cachedResultName": "personal@example.com"
        },
        "start": "={{ $('Calendar - Set Parameters').item.json.start }}",
        "end": "={{ $('Calendar - Set Parameters').item.json.end }}",
        "additionalFields": {
          "description": "={{ $('Calendar - Set Parameters').item.json.description }}",
          "summary": "={{ $('Calendar - Set Parameters').item.json.title }}"
        }
      },
      "id": "cf2cf99f-460c-4e60-b53f-6590110b4b0d",
      "name": "Calendar - Calendar add privat",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1.2,
      "position": [
        4592,
        0
      ],
      "continueOnFail": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "needs-write",
              "leftValue": "={{ $json.alreadyExists }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "false",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "single-memory-needs-write",
      "name": "Memory - Needs Write?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        4368,
        1616
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "showcase-codex-test-telegram-webhook",
        "options": {}
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        -1904,
        1104
      ],
      "id": "single-codex-test-telegram-webhook",
      "name": "Codex Test Telegram Webhook",
      "notes": "Temporary E2E test trigger. Posts Telegram-shaped updates into Normalize Input and requires x-showcase-test-secret."
    },
    {
      "parameters": {
        "jsCode": "const input = $input.first().json || {};\nconst body = input.body && typeof input.body === 'object' ? { ...input.body } : { ...input };\nconst headers = input.headers || {};\nconst headerSecret = String(headers['x-showcase-test-secret'] || headers['X-Showcase-Test-Secret'] || '');\nconst bodySecret = String(body.showcase_test_secret || '');\nif (headerSecret !== 'codex-test-20260424-7f5c9b2d' && bodySecret !== 'codex-test-20260424-7f5c9b2d') return [];\ndelete body.showcase_test_secret;\nreturn [{ json: body }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1680,
        1104
      ],
      "id": "single-codex-test-telegram-unwrap",
      "name": "Codex Test Telegram Unwrap",
      "notes": "Unwraps webhook body and drops requests without the test secret before entering the Telegram path."
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "showcase-codex-test-heartbeat-webhook",
        "responseMode": "lastNode",
        "options": {}
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        112,
        2304
      ],
      "id": "single-codex-test-heartbeat-webhook",
      "name": "Codex Test Heartbeat Webhook",
      "notes": "Temporary E2E test trigger for heartbeat coach paths. Requires x-showcase-test-secret."
    },
    {
      "parameters": {
        "jsCode": "const input = $input.first().json || {};\nconst body = input.body && typeof input.body === 'object' ? { ...input.body } : { ...input };\nconst headers = input.headers || {};\nconst headerSecret = String(headers['x-showcase-test-secret'] || headers['X-Showcase-Test-Secret'] || '');\nconst bodySecret = String(body.showcase_test_secret || '');\nif (headerSecret !== 'codex-test-20260424-7f5c9b2d' && bodySecret !== 'codex-test-20260424-7f5c9b2d') return [];\nreturn [{ json: {\n  scenario: body.scenario || 'midday',\n  prompt: body.prompt || 'Showcase E2E Heartbeat Test. Nutze den normalen OpsAgent Pfad.',\n} }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        336,
        2304
      ],
      "id": "single-codex-test-heartbeat-unwrap",
      "name": "Codex Test Heartbeat Unwrap",
      "notes": "Validates test secret and sends a heartbeat-like payload into Build Heartbeat Agent Request."
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "={{ $json.prompt || $json.chatInput || $json.message_text || \"\" }}",
        "options": {
          "systemMessage": "={{ $json.system || $json.systemPrompt || $json.effectiveSystemPrompt || \"Du bist ein hilfreicher Assistent. Sprache: Deutsch.\" }}"
        }
      },
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 3.1,
      "position": [
        2128,
        1328
      ],
      "id": "a101ac03-a786-410a-aec7-d9933cb26d6f",
      "name": "AI Agent"
    },
    {
      "parameters": {
        "model": "deepseek-v4-flash",
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatDeepSeek",
      "typeVersion": 1,
      "position": [
        2128,
        1536
      ],
      "id": "726c1480-238d-48eb-b33c-94d2c76c6450",
      "name": "DeepSeek Chat Model"
    },
    {
      "id": "single-deepseek-adapt-agent-output",
      "name": "OpsAgent - Adapt Model Output",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2576,
        1328
      ],
      "parameters": {
        "jsCode": "const agentOutput = $input.first().json || {};\nlet context = {};\ntry {\n  context = $('OpsAgent - Set Prompt').item.json || {};\n} catch (error) {\n  context = {};\n}\n\nfunction firstString(...values) {\n  for (const value of values) {\n    if (typeof value === 'string' && value.trim()) return value;\n    if (value && typeof value === 'object') {\n      const serialized = JSON.stringify(value);\n      if (serialized && serialized !== '{}') return serialized;\n    }\n  }\n  return '';\n}\n\nconst answer = firstString(\n  agentOutput.output,\n  agentOutput.text,\n  agentOutput.answer,\n  agentOutput.response,\n  agentOutput.message,\n  agentOutput.data?.output,\n  agentOutput.data?.text,\n);\n\nconst cleanText = answer\n  .replace(/\\r?\\n/g, ' ')\n  .replace(/[\u2014\u2013]/g, '-')\n  .replace(/\\s+/g, ' ')\n  .trim();\n\nconst sessionId = context.sessionId || agentOutput.sessionId || 'default';\nconst staticData = $getWorkflowStaticData('global');\nif (!staticData.conversations) staticData.conversations = {};\nif (!staticData.conversations[sessionId]) staticData.conversations[sessionId] = [];\n\nlet userMessage = '';\ntry {\n  const messages = context.messages || [];\n  const lastUser = [...messages].reverse().find((entry) => entry?.role === 'user');\n  userMessage = lastUser?.content || '';\n} catch (error) {\n  userMessage = '';\n}\nuserMessage = userMessage || context.prompt || context.chatInput || context.message_text || '';\n\nif (userMessage && answer) {\n  staticData.conversations[sessionId].push(\n    { role: 'user', content: userMessage },\n    { role: 'assistant', content: answer },\n  );\n}\n\nconst maxMessages = 20;\nif (staticData.conversations[sessionId].length > maxMessages) {\n  staticData.conversations[sessionId] = staticData.conversations[sessionId].slice(-maxMessages);\n}\n\nconst usage = agentOutput.usage || agentOutput.tokenUsage || agentOutput.response_metadata?.tokenUsage || {};\nconst tokens = usage.total_tokens || usage.totalTokens || usage.total || 0;\n\nreturn [{\n  json: {\n    ...context,\n    ...agentOutput,\n    answer,\n    text: cleanText,\n    model: 'deepseek-v4-flash',\n    tokens,\n    deepSeekRawOutput: agentOutput,\n  },\n}];"
      }
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                5
              ],
              "triggerAtHour": 18
            }
          ]
        }
      },
      "id": "single-cron-weekly-review-001",
      "name": "Heartbeat - Weekly Review",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        112,
        2496
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "s",
              "name": "scenario",
              "value": "weekly_review",
              "type": "string"
            },
            {
              "id": "p",
              "name": "prompt",
              "value": "Freitag 18.00 Uhr. OpsAgent macht den Wochenr\u00fcckblick nach Feierabend. Kalender der Woche, relevante Mails der letzten 7 Tage, offene Reste und eine konkrete Sache f\u00fcr Montag. Kein Lob ohne Ergebnis und keine gro\u00dfe Strategie.",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "single-prompt-weekly-review-001",
      "name": "Heartbeat - Build Weekly Review Prompt",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        336,
        2496
      ]
    }
  ],
  "connections": {
    "Telegram Trigger": {
      "main": [
        [
          {
            "node": "Normalize Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Chat Trigger": {
      "main": [
        [
          {
            "node": "Normalize Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Input": {
      "main": [
        [
          {
            "node": "Whitelist + Dedup",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Whitelist + Dedup": {
      "main": [
        [
          {
            "node": "Input Type Router",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Input Type Router": {
      "main": [
        [
          {
            "node": "Get Voice File",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Get Photo File",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Prepare Text Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Voice File": {
      "main": [
        [
          {
            "node": "Transcribe Voice",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Transcribe Voice": {
      "main": [
        [
          {
            "node": "Prepare Voice Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Voice Result": {
      "main": [
        [
          {
            "node": "Slash Command Expander",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Photo File": {
      "main": [
        [
          {
            "node": "Analyze Photo",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze Photo": {
      "main": [
        [
          {
            "node": "Prepare Photo Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Photo Result": {
      "main": [
        [
          {
            "node": "Slash Command Expander",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Text Input": {
      "main": [
        [
          {
            "node": "Slash Command Expander",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slash Command Expander": {
      "main": [
        [
          {
            "node": "Interactive Source Router",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Interactive Source Router": {
      "main": [
        [
          {
            "node": "Reply - Send Typing Action",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build Interactive Agent Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Reply - Send Typing Action": {
      "main": [
        [
          {
            "node": "Build Interactive Agent Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Heartbeat - Morning": {
      "main": [
        [
          {
            "node": "Heartbeat - Build Morning Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Heartbeat - Build Morning Prompt": {
      "main": [
        [
          {
            "node": "Build Heartbeat Agent Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Heartbeat - PreMeeting": {
      "main": [
        [
          {
            "node": "Heartbeat - Build PreMeeting Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Heartbeat - Build PreMeeting Prompt": {
      "main": [
        [
          {
            "node": "Build Heartbeat Agent Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Heartbeat - Midday": {
      "main": [
        [
          {
            "node": "Heartbeat - Build Midday Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Heartbeat - Build Midday Prompt": {
      "main": [
        [
          {
            "node": "Build Heartbeat Agent Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Heartbeat - Evening": {
      "main": [
        [
          {
            "node": "Heartbeat - Build Evening Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Heartbeat - Build Evening Prompt": {
      "main": [
        [
          {
            "node": "Build Heartbeat Agent Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Interactive Agent Request": {
      "main": [
        [
          {
            "node": "Agent - Init Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Heartbeat Agent Request": {
      "main": [
        [
          {
            "node": "Agent - Init Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Agent - Init Context": {
      "main": [
        [
          {
            "node": "Agent - Read Memory File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Agent - Read Memory File": {
      "main": [
        [
          {
            "node": "Agent - Read Decisions File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Agent - Read Decisions File": {
      "main": [
        [
          {
            "node": "Agent - Inject Memory",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Agent - Inject Memory": {
      "main": [
        [
          {
            "node": "OpsAgent - Load History",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpsAgent - Load History": {
      "main": [
        [
          {
            "node": "OpsAgent - Set Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpsAgent - Set Prompt": {
      "main": [
        [
          {
            "node": "AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Agent - Parse Tool Request": {
      "main": [
        [
          {
            "node": "Agent - Tool Router",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Agent - Tool Router": {
      "main": [
        [
          {
            "node": "Gmail - Set Parameters",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Calendar - Set Parameters",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Notion - Set Parameters",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Memory - Prepare Memory Write",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Agent - Return Final",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail - Set Parameters": {
      "main": [
        [
          {
            "node": "Gmail - Operation Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail - Operation Switch": {
      "main": [
        [
          {
            "node": "Gmail - Gmail Search",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Gmail - Gmail Read",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Gmail - Gmail Draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail - Gmail Search": {
      "main": [
        [
          {
            "node": "Gmail - Format Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail - Gmail Read": {
      "main": [
        [
          {
            "node": "Gmail - Format Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail - Gmail Draft": {
      "main": [
        [
          {
            "node": "Gmail - Format Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail - Format Output": {
      "main": [
        [
          {
            "node": "Agent - Tool Result To Agent Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calendar - Set Parameters": {
      "main": [
        [
          {
            "node": "Calendar - Operation Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calendar - Operation Switch": {
      "main": [
        [
          {
            "node": "Calendar - Calendar primary-project",
            "type": "main",
            "index": 0
          },
          {
            "node": "Calendar - Calendar privat",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Calendar - Create Target Router",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calendar - Format Output": {
      "main": [
        [
          {
            "node": "Agent - Tool Result To Agent Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Notion - Set Parameters": {
      "main": [
        [
          {
            "node": "Notion - Operation Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Notion - Operation Switch": {
      "main": [
        [
          {
            "node": "Notion - Notion Search",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Notion - Notion Read",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Notion - Notion Create",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Notion - Notion Update",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Notion - Notion Search": {
      "main": [
        [
          {
            "node": "Notion - Format Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Notion - Notion Read": {
      "main": [
        [
          {
            "node": "Notion - Format Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Notion - Notion Create": {
      "main": [
        [
          {
            "node": "Notion - Format Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Notion - Notion Update": {
      "main": [
        [
          {
            "node": "Notion - Format Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Notion - Format Output": {
      "main": [
        [
          {
            "node": "Agent - Tool Result To Agent Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Memory - Prepare Memory Write": {
      "main": [
        [
          {
            "node": "Memory - GitHub Get File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Memory - GitHub Get File": {
      "main": [
        [
          {
            "node": "Memory - Build Update Body",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Memory - Build Update Body": {
      "main": [
        [
          {
            "node": "Memory - Needs Write?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Memory - GitHub Put File": {
      "main": [
        [
          {
            "node": "Memory - Memory Write Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Memory - Memory Write Result": {
      "main": [
        [
          {
            "node": "Agent - Tool Result To Agent Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Agent - Tool Result To Agent Input": {
      "main": [
        [
          {
            "node": "Agent - Loop Guard",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Agent - Loop Guard": {
      "main": [
        [
          {
            "node": "OpsAgent - Load History",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Agent - Return Final": {
      "main": [
        [
          {
            "node": "Reply Mode Router",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Reply Mode Router": {
      "main": [
        [
          {
            "node": "Reply - Format Interactive Output",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Reply - Chat Output",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Heartbeat - Filter Skip",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Reply - Format Interactive Output": {
      "main": [
        [
          {
            "node": "Reply - Voice Response?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Reply - Voice Response?": {
      "main": [
        [
          {
            "node": "Reply - TTS Generate",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Reply - Send Text Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Reply - TTS Generate": {
      "main": [
        [
          {
            "node": "Reply - Send Voice Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Heartbeat - Filter Skip": {
      "main": [
        [
          {
            "node": "Heartbeat - Send OpsAgent Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calendar - Calendar primary-project": {
      "main": [
        [
          {
            "node": "Calendar - Merge Calendars",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calendar - Merge Calendars": {
      "main": [
        [
          {
            "node": "Calendar - Format Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calendar - Create Target Router": {
      "main": [
        [
          {
            "node": "Calendar - Calendar add primary-project",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Calendar - Calendar add privat",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calendar - Calendar add primary-project": {
      "main": [
        [
          {
            "node": "Calendar - Format Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Memory - Needs Write?": {
      "main": [
        [
          {
            "node": "Memory - GitHub Put File",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Memory - Memory Write Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calendar - Calendar privat": {
      "main": [
        [
          {
            "node": "Calendar - Merge Calendars",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Calendar - Calendar add privat": {
      "main": [
        [
          {
            "node": "Calendar - Format Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Codex Test Telegram Webhook": {
      "main": [
        [
          {
            "node": "Codex Test Telegram Unwrap",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Codex Test Telegram Unwrap": {
      "main": [
        [
          {
            "node": "Normalize Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Codex Test Heartbeat Webhook": {
      "main": [
        [
          {
            "node": "Codex Test Heartbeat Unwrap",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Codex Test Heartbeat Unwrap": {
      "main": [
        [
          {
            "node": "Build Heartbeat Agent Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "DeepSeek Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent": {
      "main": [
        [
          {
            "node": "OpsAgent - Adapt Model Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpsAgent - Adapt Model Output": {
      "main": [
        [
          {
            "node": "Agent - Parse Tool Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Heartbeat - Weekly Review": {
      "main": [
        [
          {
            "node": "Heartbeat - Build Weekly Review Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Heartbeat - Build Weekly Review Prompt": {
      "main": [
        [
          {
            "node": "Build Heartbeat Agent Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate",
    "saveDataSuccessExecution": "none",
    "saveDataErrorExecution": "all"
  },
  "tags": [],
  "meta": {
    "showcase": true,
    "sanitized": true,
    "source": "personal-agent-single workflow",
    "notes": "Credentials, private account IDs, private repository names, personal paths, and test secrets were removed."
  }
}