[
    {
        "id": "73394bf33c3e3369",
        "type": "tab",
        "label": "플로우 1",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "aiwk_profile_test_tab",
        "type": "tab",
        "label": "AIWK Profile 서버DB 테스트",
        "disabled": false,
        "info": "AIWK 공용 설정 token/session 공유 테스트용 flow입니다. 먼저 aiwk-config에 AIWK1 token을 한 번만 입력합니다.",
        "env": []
    },
    {
        "id": "18af01daa1a16f24",
        "type": "aiwk-config",
        "name": "aiwk",
        "wssUrl": "wss://n.yjm.kr/aiwk/ws",
        "profileApiUrl": "https://n.yjm.kr/y/aiwk_profile_api.php",
        "group": "home",
        "project": "IG_CRM",
        "room": "RE5ay2jRmY",
        "clientId": "N1",
        "endpointBase": "node-red",
        "systemHostname": "SEC",
        "deviceLabel": "SEC",
        "deviceName": "SEC",
        "deviceInstanceId": "dev_SEC_3c73d96a90c6",
        "profileInstanceId": "pf_nodered_dev_SEC_3c73d96a90c6",
        "tabKind": "node-red",
        "tabId": "nodrd",
        "remoteIp": "211.222.247.74",
        "autoConnect": true,
        "reconnectMs": 3000
    },
    {
        "id": "855a8dd4776456c9",
        "type": "aiwk-in",
        "z": "73394bf33c3e3369",
        "name": "N1 aiwk_ping 수신",
        "server": "18af01daa1a16f24",
        "inputEndpoint": "node-red",
        "filterAction": "aiwk_ping",
        "allowAnyEndpoint": true,
        "x": 110,
        "y": 80,
        "wires": [
            [
                "4fdc1c42a0444270",
                "c1eeb82586899fa4"
            ]
        ]
    },
    {
        "id": "4fdc1c42a0444270",
        "type": "function",
        "z": "73394bf33c3e3369",
        "name": "aiwk_ping → aiwk_pong 라우팅로그 응답 v148",
        "func": "const p = msg.aiwk || {};\nconst fromUrl = msg.aiwk_from_url || (msg.aiwk_route_debug && msg.aiwk_route_debug.from) || p.from_url || p.from || \"\";\nconst toUrl = msg.aiwk_to_url || (msg.aiwk_route_debug && msg.aiwk_route_debug.to) || p.to_url || p.to || \"\";\n\nfunction parseAiwkUrl(u){\n    u = String(u || \"\").trim();\n    if (u.toLowerCase().indexOf(\"aiwk://\") === 0) u = u.slice(7);\n    const at = u.lastIndexOf(\"@\");\n    if (at >= 0) u = u.slice(at + 1);\n    const hi = u.indexOf(\"#\");\n    const endpoint = hi >= 0 ? u.slice(hi + 1) : \"\";\n    if (hi >= 0) u = u.slice(0, hi);\n    const parts = u.split(\".\");\n    const tail = parts.slice(2).join(\".\");\n    const ci = tail.indexOf(\":\");\n    return { client_id: ci >= 0 ? tail.slice(0, ci) : tail, route_key: ci >= 0 ? tail.slice(ci + 1) : \"\", endpoint };\n}\nconst replyTarget = parseAiwkUrl(fromUrl);\nmsg.payload = {\n    type: \"result\",\n    action: \"aiwk_pong\",\n    ok: 1,\n    received_action: p.action || \"aiwk_ping\",\n    client_ts: p.client_ts || Date.now(),\n    reply_to: p.message_id || p.msg_id || p.id || \"\",\n    reply_to_message_id: p.message_id || p.msg_id || p.id || \"\",\n    aiwk_from_url: toUrl,\n    aiwk_to_url: fromUrl,\n    aiwk_request_from_url: fromUrl,\n    aiwk_request_to_url: toUrl,\n    aiwk_route_debug: {\n        request_from: fromUrl,\n        request_to: toUrl,\n        reply_from: toUrl,\n        reply_to: fromUrl,\n        raw_from: p.from || \"\",\n        raw_to: p.to || \"\",\n        client_id: p.client_id || \"\",\n        from_client: p.from_client || \"\",\n        to_client: p.to_client || \"\",\n        route_key: p.route_key || p.url_key || \"\",\n        endpoint: p.endpoint || p.from_endpoint || \"\",\n        tab_id: p.tab_id || p.from_tab_id || \"\",\n        profile_instance_id: p.profile_instance_id || p.from_profile_instance_id || \"\",\n        room_id: p.room_id || \"\"\n    }\n};\nmsg.aiwk_to = fromUrl || p.from || p.from_client || p.client_id || msg.aiwk_to;\nmsg.aiwk_to_client = replyTarget.client_id || p.from_client || p.client_id || msg.aiwk_to_client;\nmsg.aiwk_to_route_key = replyTarget.route_key || p.from_route_key || p.from_url5 || p.from_tab_id || p.tab_id || msg.aiwk_to_route_key;\nmsg.aiwk_to_url_key = msg.aiwk_to_route_key;\nmsg.aiwk_to_endpoint = replyTarget.endpoint || p.from_endpoint || p.endpoint || msg.aiwk_to_endpoint;\nmsg.aiwk_reply_to_message_id = msg.payload.reply_to_message_id;\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 190,
        "y": 140,
        "wires": [
            [
                "55e4ab1fcc055985"
            ]
        ]
    },
    {
        "id": "55e4ab1fcc055985",
        "type": "aiwk-out",
        "z": "73394bf33c3e3369",
        "name": "aiwk_pong 회신",
        "server": "18af01daa1a16f24",
        "fromEndpoint": "node-red",
        "to": "",
        "action": "aiwk_pong",
        "needAck": true,
        "needResult": true,
        "timeoutMs": 10000,
        "x": 400,
        "y": 220,
        "wires": [
            [
                "1637ac906fd3c0e8",
                "e2997f9b9bf39c83"
            ]
        ]
    },
    {
        "id": "1637ac906fd3c0e8",
        "type": "function",
        "z": "73394bf33c3e3369",
        "name": "PONG 송신 상태",
        "func": "node.status({ fill: msg.aiwk_sent ? \"green\" : \"red\", shape: \"ring\", text: JSON.stringify({sent:msg.aiwk_sent,from:msg.aiwk_from_url,to:msg.aiwk_to_url,reply_to:msg.aiwk_reply_to_message_id}) });\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 440,
        "y": 80,
        "wires": [
            []
        ]
    },
    {
        "id": "aiwk_chat_in_v058",
        "type": "aiwk-in",
        "z": "73394bf33c3e3369",
        "name": "N1 aiwk_chat 수신",
        "server": "18af01daa1a16f24",
        "inputEndpoint": "node-red",
        "filterAction": "aiwk_chat",
        "allowAnyEndpoint": true,
        "x": 110,
        "y": 360,
        "wires": [
            [
                "aiwk_chat_reply_v058",
                "3ecd91d0ff352b0a"
            ]
        ]
    },
    {
        "id": "aiwk_chat_reply_v058",
        "type": "function",
        "z": "73394bf33c3e3369",
        "name": "aiwk_chat → aiwk_chat_ack 라우팅로그 응답 v148",
        "func": "const p = msg.aiwk || {};\nconst text = (p.payload && p.payload.text) || (msg.payload && msg.payload.text) || \"\";\nconst fromUrl = msg.aiwk_from_url || (msg.aiwk_route_debug && msg.aiwk_route_debug.from) || p.from_url || p.from || \"\";\nconst toUrl = msg.aiwk_to_url || (msg.aiwk_route_debug && msg.aiwk_route_debug.to) || p.to_url || p.to || \"\";\n\nfunction parseAiwkUrl(u){\n    u = String(u || \"\").trim();\n    if (u.toLowerCase().indexOf(\"aiwk://\") === 0) u = u.slice(7);\n    const at = u.lastIndexOf(\"@\");\n    if (at >= 0) u = u.slice(at + 1);\n    const hi = u.indexOf(\"#\");\n    const endpoint = hi >= 0 ? u.slice(hi + 1) : \"\";\n    if (hi >= 0) u = u.slice(0, hi);\n    const parts = u.split(\".\");\n    const tail = parts.slice(2).join(\".\");\n    const ci = tail.indexOf(\":\");\n    return { client_id: ci >= 0 ? tail.slice(0, ci) : tail, route_key: ci >= 0 ? tail.slice(ci + 1) : \"\", endpoint };\n}\nconst replyTarget = parseAiwkUrl(fromUrl);\nmsg.payload = {\n    type: \"result\",\n    action: \"aiwk_chat_ack\",\n    ok: 1,\n    text: \"Node-RED 수신: \" + text,\n    received_action: p.action || \"aiwk_chat\",\n    reply_to: p.message_id || p.msg_id || p.id || \"\",\n    reply_to_message_id: p.message_id || p.msg_id || p.id || \"\",\n    aiwk_from_url: toUrl,\n    aiwk_to_url: fromUrl,\n    aiwk_request_from_url: fromUrl,\n    aiwk_request_to_url: toUrl,\n    aiwk_route_debug: {\n        request_from: fromUrl,\n        request_to: toUrl,\n        reply_from: toUrl,\n        reply_to: fromUrl,\n        raw_from: p.from || \"\",\n        raw_to: p.to || \"\",\n        client_id: p.client_id || \"\",\n        from_client: p.from_client || \"\",\n        to_client: p.to_client || \"\",\n        route_key: p.route_key || p.url_key || \"\",\n        endpoint: p.endpoint || p.from_endpoint || \"\",\n        tab_id: p.tab_id || p.from_tab_id || \"\",\n        profile_instance_id: p.profile_instance_id || p.from_profile_instance_id || \"\",\n        room_id: p.room_id || \"\"\n    }\n};\nmsg.aiwk_to = fromUrl || p.from || p.from_client || p.client_id || msg.aiwk_to;\nmsg.aiwk_to_client = replyTarget.client_id || p.from_client || p.client_id || msg.aiwk_to_client;\nmsg.aiwk_to_route_key = replyTarget.route_key || p.from_route_key || p.from_url5 || p.from_tab_id || p.tab_id || msg.aiwk_to_route_key;\nmsg.aiwk_to_url_key = msg.aiwk_to_route_key;\nmsg.aiwk_to_endpoint = replyTarget.endpoint || p.from_endpoint || p.endpoint || msg.aiwk_to_endpoint;\nmsg.aiwk_reply_to_message_id = msg.payload.reply_to_message_id;\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 470,
        "y": 360,
        "wires": [
            [
                "aiwk_chat_out_v058"
            ]
        ]
    },
    {
        "id": "aiwk_chat_out_v058",
        "type": "aiwk-out",
        "z": "73394bf33c3e3369",
        "name": "aiwk_chat_ack 회신",
        "server": "18af01daa1a16f24",
        "fromEndpoint": "node-red",
        "to": "",
        "action": "aiwk_chat_ack",
        "needAck": true,
        "needResult": true,
        "timeoutMs": 10000,
        "x": 550,
        "y": 440,
        "wires": [
            [
                "4ae6463d7686b3ee",
                "80acc97eadc6f42d"
            ]
        ]
    },
    {
        "id": "3ecd91d0ff352b0a",
        "type": "function",
        "z": "73394bf33c3e3369",
        "name": "function 1",
        "func": "// msg 객체를 문자열로 변환하여 상태창에 표시\nnode.status({ fill: \"green\", shape: \"ring\", text: JSON.stringify(msg) });\n\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 380,
        "y": 320,
        "wires": [
            []
        ]
    },
    {
        "id": "c1eeb82586899fa4",
        "type": "function",
        "z": "73394bf33c3e3369",
        "name": "function 4",
        "func": "// msg 객체를 문자열로 변환하여 상태창에 표시\nnode.status({ fill: \"green\", shape: \"ring\", text: JSON.stringify(msg) });\n\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 280,
        "y": 20,
        "wires": [
            []
        ]
    },
    {
        "id": "e2997f9b9bf39c83",
        "type": "debug",
        "z": "73394bf33c3e3369",
        "name": "debug 1 payload + aiwk:// route",
        "active": true,
        "tosidebar": true,
        "console": true,
        "tostatus": true,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "payload",
        "statusType": "auto",
        "x": 650,
        "y": 160,
        "wires": []
    },
    {
        "id": "4ae6463d7686b3ee",
        "type": "debug",
        "z": "73394bf33c3e3369",
        "name": "debug 2 chat payload + aiwk:// route",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 780,
        "y": 480,
        "wires": []
    },
    {
        "id": "80acc97eadc6f42d",
        "type": "function",
        "z": "73394bf33c3e3369",
        "name": "PONG 송신 상태",
        "func": "node.status({ fill: msg.aiwk_sent ? \"green\" : \"red\", shape: \"ring\", text: JSON.stringify({sent:msg.aiwk_sent,from:msg.aiwk_from_url,to:msg.aiwk_to_url,reply_to:msg.aiwk_reply_to_message_id}) });\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 800,
        "y": 420,
        "wires": [
            []
        ]
    },
    {
        "id": "aiwk_profile_inject_url",
        "type": "inject",
        "z": "aiwk_profile_test_tab",
        "name": "URL resolve 테스트",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "https://n.yjm.kr/y/_docs/aiwk/aiwk_std_test_v002_mission_lab/",
        "payloadType": "str",
        "x": 150,
        "y": 100,
        "wires": [
            [
                "aiwk_profile_resolve_node"
            ]
        ]
    },
    {
        "id": "aiwk_profile_auth_inject",
        "type": "inject",
        "z": "aiwk_profile_test_tab",
        "name": "auth/session 테스트",
        "props": [
            {
                "p": "aiwk_profile_mode",
                "v": "auth",
                "vt": "str"
            },
            {
                "p": "payload"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 150,
        "y": 160,
        "wires": [
            [
                "aiwk_profile_resolve_node"
            ]
        ]
    },
    {
        "id": "aiwk_profile_resolve_node",
        "type": "aiwk-profile",
        "z": "aiwk_profile_test_tab",
        "name": "공용 token/session profile",
        "server": "18af01daa1a16f24",
        "url": "https://n.yjm.kr/y/_docs/aiwk/aiwk_std_test_v002_mission_lab/",
        "x": 410,
        "y": 120,
        "wires": [
            [
                "aiwk_profile_debug",
                "aiwk_profile_std_debug",
                "f73d47763c561d15"
            ]
        ]
    },
    {
        "id": "aiwk_profile_debug",
        "type": "aiwk-debug",
        "z": "aiwk_profile_test_tab",
        "name": "AIWK Profile 결과",
        "complete": "msg",
        "passThrough": true,
        "x": 690,
        "y": 80,
        "wires": [
            [
                "2c2cf1699723f6e9"
            ]
        ]
    },
    {
        "id": "aiwk_profile_std_debug",
        "type": "debug",
        "z": "aiwk_profile_test_tab",
        "name": "profile payload",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": true,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "payload",
        "statusType": "auto",
        "x": 740,
        "y": 140,
        "wires": []
    },
    {
        "id": "aiwk_profile_comment",
        "type": "comment",
        "z": "aiwk_profile_test_tab",
        "name": "토큰은 aiwk-config 한 곳에만 입력 / 모든 aiwk-* 노드 공유",
        "info": "1. aiwk-config 설정창을 열어 AIWK1 token을 한 번만 입력합니다.\n2. auth/session 테스트를 누르면 session_key가 발급됩니다.\n3. URL resolve 테스트를 누르면 서버 DB profile을 조회하고 userdir_A1/cache/domain_profiles/에 저장합니다.",
        "x": 350,
        "y": 40,
        "wires": []
    },
    {
        "id": "2c2cf1699723f6e9",
        "type": "function",
        "z": "aiwk_profile_test_tab",
        "name": "function 2",
        "func": "// msg 객체를 문자열로 변환하여 상태창에 표시\nnode.status({ fill: \"green\", shape: \"ring\", text: JSON.stringify(msg) });\n\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 140,
        "y": 300,
        "wires": [
            []
        ]
    },
    {
        "id": "f73d47763c561d15",
        "type": "function",
        "z": "aiwk_profile_test_tab",
        "name": "function 3",
        "func": "// msg 객체를 문자열로 변환하여 상태창에 표시\nnode.status({ fill: \"green\", shape: \"ring\", text: JSON.stringify(msg) });\n\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 140,
        "y": 240,
        "wires": [
            []
        ]
    }
]