662 lines
26 KiB
JSON
662 lines
26 KiB
JSON
{
|
|
"createdAt": "2025-11-15T03:06:25.006Z",
|
|
"updatedAt": "2025-11-17T00:55:34.000Z",
|
|
"id": "yoXYSA197mEd6zfK",
|
|
"name": "test-ai-memory",
|
|
"active": true,
|
|
"isArchived": false,
|
|
"nodes": [
|
|
{
|
|
"parameters": {
|
|
"assignments": {
|
|
"assignments": [
|
|
{
|
|
"id": "9d0acfc5-26eb-43da-84a4-14f2bed657c8",
|
|
"name": "user_id",
|
|
"value": "={{\n (\n {\n 'bill_a_': 'bill',\n 'bill_alt': 'bill',\n 'paddy_x': 'paddy'\n }[$json.body?.user?.name]\n )\n || $json.user_id\n || $json.userId\n || $json.body?.user?.name\n || $json.user?.id\n || 'bill'\n}}\n",
|
|
"type": "string"
|
|
},
|
|
{
|
|
"id": "7ca5458c-b3d5-4a65-b7f2-4e47a9eef4df",
|
|
"name": "message",
|
|
"value": "={{\n $json.body.content\n || $json.text\n || $json.input\n || $json.prompt\n || $json.query\n || $json.chatInput\n || \"No message found\"\n}}\n",
|
|
"type": "string"
|
|
},
|
|
{
|
|
"id": "b42f61e3-4906-4630-ab45-2731780e86bd",
|
|
"name": "sessionID",
|
|
"value": "={{ $now }}",
|
|
"type": "string"
|
|
}
|
|
]
|
|
},
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.set",
|
|
"typeVersion": 3.4,
|
|
"position": [
|
|
32,
|
|
-64
|
|
],
|
|
"id": "3cd210ac-9aec-401f-b4d8-2ac0de844967",
|
|
"name": "Set Input"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"operation": "executeQuery",
|
|
"query": "SELECT\n role,\n content,\n created_at\nFROM user_episodes\nWHERE user_id = $1\nORDER BY created_at DESC\nLIMIT 20;\n",
|
|
"options": {
|
|
"queryReplacement": "={{ $('Set Input').item.json.user_id }}"
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.postgres",
|
|
"typeVersion": 2.6,
|
|
"position": [
|
|
480,
|
|
-64
|
|
],
|
|
"id": "d5c07210-d099-429c-8658-be6b6c9538fd",
|
|
"name": "Load Episodes",
|
|
"alwaysOutputData": true,
|
|
"credentials": {
|
|
"postgres": {
|
|
"id": "0kFTQeK8ZmBaEUtX",
|
|
"name": "Postgres account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// Original input (user_id, message) from Set Input\nconst inputItems = $items(\"Set Input\");\nconst input = inputItems.length ? inputItems[0].json : $json;\n\n// Profile rows from Postgres\nconst profileItems = $items(\"Load Profile Memory\").map(i => i.json);\n\n// Episode rows from Postgres\nconst episodeItems = $items(\"Load Episodes\").map(i => i.json);\n\n// Build profile lines\nconst profileLines = profileItems.map(p =>\n `- ${p.key}: ${JSON.stringify(p.value)} (importance=${p.importance})`\n);\n\n// Sort episodes oldest → newest\nepisodeItems.sort(\n (a, b) => new Date(a.created_at || 0) - new Date(b.created_at || 0)\n);\n\n// Build episode lines with safe defaults\nconst episodeLines = episodeItems.map(e => {\n const role = (e.role || \"user\").toString().toUpperCase();\n const when = e.created_at || \"\";\n const content = e.content || \"\";\n return `${role} [${when}]: ${content}`;\n});\n\nconst memoryContext = `\nThe following is persistent memory about this user:\n\nProfile:\n${profileLines.length ? profileLines.join(\"\\n\") : \"- (none yet)\"}\n\nRecent interactions:\n${episodeLines.length ? episodeLines.join(\"\\n\") : \"- (none recorded)\"}\n`;\n\nreturn [\n {\n json: {\n ...input,\n memoryContext,\n },\n },\n];\n"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
704,
|
|
-64
|
|
],
|
|
"id": "de10d931-5ed0-4728-938c-0b40d0c50020",
|
|
"name": "Build Memory Context"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"promptType": "define",
|
|
"text": "={{ $json[\"message\"] }}\n",
|
|
"options": {
|
|
"systemMessage": "=You are an assistant for a specific user in a home-lab / home-automation environment.\n\nHere is what you already know about this user:\n\n {{ $json.memoryContext }}\n\nUse this information to personalize responses and maintain continuity.\n\nIf some stored information appears outdated or contradicted by the current message, prefer the latest information.\n"
|
|
}
|
|
},
|
|
"type": "@n8n/n8n-nodes-langchain.agent",
|
|
"typeVersion": 2.2,
|
|
"position": [
|
|
928,
|
|
-64
|
|
],
|
|
"id": "d3d4f432-b6d2-4fbd-a0c9-13ce23ce6d40",
|
|
"name": "AI With Memory"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"operation": "executeQuery",
|
|
"query": "SELECT key, value, importance, updated_at\nFROM user_profile_memories\nWHERE user_id = '{{$json[\"user_id\"]}}'\nORDER BY importance DESC, updated_at DESC;\n",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.postgres",
|
|
"typeVersion": 2.6,
|
|
"position": [
|
|
256,
|
|
-64
|
|
],
|
|
"id": "cf53308e-da01-45fb-b262-ef96fe032473",
|
|
"name": "Load Profile Memory",
|
|
"alwaysOutputData": true,
|
|
"retryOnFail": false,
|
|
"credentials": {
|
|
"postgres": {
|
|
"id": "0kFTQeK8ZmBaEUtX",
|
|
"name": "Postgres account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"model": {
|
|
"__rl": true,
|
|
"mode": "list",
|
|
"value": "gpt-4.1-mini"
|
|
},
|
|
"options": {}
|
|
},
|
|
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
|
|
"typeVersion": 1.2,
|
|
"position": [
|
|
1328,
|
|
528
|
|
],
|
|
"id": "56d7d2b5-5f3b-4139-9ae5-31a3534709c7",
|
|
"name": "OpenAI Chat Model",
|
|
"credentials": {
|
|
"openAiApi": {
|
|
"id": "0aLJYVCIXPIQZb1L",
|
|
"name": "OpenAi account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"assignments": {
|
|
"assignments": [
|
|
{
|
|
"id": "798e8e3a-a19e-4b07-8996-69198436ee42",
|
|
"name": "user_id",
|
|
"value": "={{ $('Build Memory Context').item.json.user_id }}",
|
|
"type": "string"
|
|
},
|
|
{
|
|
"id": "9f8f1a53-bfe3-4b5c-9e7b-955319aa6f8f",
|
|
"name": "message",
|
|
"value": "={{ $json.output }}",
|
|
"type": "string"
|
|
},
|
|
{
|
|
"id": "9653c980-eba9-4b0b-8f8d-accb21b2a355",
|
|
"name": "assistant_reply",
|
|
"value": "={{ $json.output }}",
|
|
"type": "string"
|
|
}
|
|
]
|
|
},
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.set",
|
|
"typeVersion": 3.4,
|
|
"position": [
|
|
1312,
|
|
192
|
|
],
|
|
"id": "e213696a-a96f-4521-8cab-e04e97d01dc4",
|
|
"name": "Prepare For Memory Extraction"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// Get user_id / message from Prepare For Memory Extraction\nconst baseItems = $items(\"Prepare For Memory Extraction\");\nconst base = baseItems.length ? baseItems[0].json : {};\n\n\n// Get Extract Memory output\nconst extractItems = $items(\"Extract Memory\");\n\nif (!extractItems.length) {\n return [{\n json: {\n user_id: base.user_id ?? null,\n profile_upserts: [],\n episodes: [],\n debug: { reason: \"no Extract Memory items\" },\n },\n }];\n}\n\n// The Extract Memory node returns a JSON string in `output`\nconst extract = extractItems[0].json;\nlet raw = extract.output;\n\nif (typeof raw !== \"string\" || !raw.trim()) {\n // Nothing usable\n return [{\n json: {\n user_id: base.user_id ?? null,\n profile_upserts: [],\n episodes: [],\n debug: {\n reason: \"no usable string in extract.output\",\n extract,\n },\n },\n }];\n}\n\nlet parsed;\ntry {\n parsed = JSON.parse(raw);\n} catch (e) {\n return [{\n json: {\n user_id: base.user_id ?? null,\n profile_upserts: [],\n episodes: [],\n debug: {\n reason: \"JSON.parse failed\",\n error: e.message,\n raw,\n },\n },\n }];\n}\n\n// If the model returned `[ { ... } ]`, unwrap first element\nif (Array.isArray(parsed)) {\n parsed = parsed[0] || {};\n}\n\n// Pull arrays out (with sane defaults)\nconst profile_upserts = parsed.profile_upserts || [];\nconst episodes = parsed.episodes || [];\n\nreturn [{\n json: {\n user_id: base.user_id ?? null,\n profile_upserts,\n episodes,\n },\n}];\n"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
1872,
|
|
192
|
|
],
|
|
"id": "e44777c0-56d7-44fd-bc87-5b0259d09b9c",
|
|
"name": "Parse Memory JSON"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"promptType": "define",
|
|
"text": "=Conversation:\nUSER: {{ $json[\"message\"] }}\nASSISTANT: {{ $json[\"assistant_reply\"] }}\n",
|
|
"options": {
|
|
"systemMessage": "You are a memory extraction module.\n\nFrom the following conversation between user and assistant, decide what should be stored as persistent memory about the user.\n\nOnly include facts that:\n- are about the user (preferences, personal details, long-term projects, corrections, etc.)\n- will be useful in future conversations.\n\nDo NOT restate generic info or one-off particulars that will not be useful later.\n\nOutput STRICT JSON with this shape:\n\n{\n \"profile_upserts\": [\n { \"key\": \"string\", \"value\": { ... }, \"importance\": 1-5 }\n ],\n \"episodes\": [\n { \"role\": \"user\" | \"assistant\", \"content\": \"string\" }\n ]\n}\n\nIf there is nothing to store, use empty arrays:\n{\n \"profile_upserts\": [],\n \"episodes\": []\n}\n"
|
|
}
|
|
},
|
|
"type": "@n8n/n8n-nodes-langchain.agent",
|
|
"typeVersion": 2.2,
|
|
"position": [
|
|
1536,
|
|
-16
|
|
],
|
|
"id": "2dc60f06-a015-48c2-a5d8-a9f146f7da6b",
|
|
"name": "Extract Memory"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const { user_id, profile_upserts } = $json;\n\nconst out = (profile_upserts || []).map(m => ({\n json: {\n user_id,\n key: m.key,\n value_serialized: JSON.stringify(m.value ?? {}),\n importance: m.importance ?? 1,\n },\n}));\n\n// If no profile memories, return an empty list (node will just do nothing next)\nreturn out;\n"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
2112,
|
|
112
|
|
],
|
|
"id": "f5829dbd-1475-4602-ac39-a5ec4124d868",
|
|
"name": "Fan Out Profile",
|
|
"alwaysOutputData": false
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const { user_id, episodes } = $json;\n\nconst out = (episodes || []).map(e => ({\n json: {\n user_id,\n role: e.role || \"user\",\n content: e.content || \"\",\n },\n}));\n\nreturn out;\n"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
2112,
|
|
304
|
|
],
|
|
"id": "8e604b1b-654e-4233-ba30-988b5291f8e5",
|
|
"name": "Fan Out Episodes",
|
|
"alwaysOutputData": false
|
|
},
|
|
{
|
|
"parameters": {
|
|
"operation": "executeQuery",
|
|
"query": "INSERT INTO user_profile_memories (user_id, key, value, importance, updated_at)\nVALUES ($1, $2, $3::jsonb, $4, now())\nON CONFLICT (user_id, key)\nDO UPDATE SET\n value = EXCLUDED.value,\n importance = EXCLUDED.importance,\n updated_at = now();\n",
|
|
"options": {
|
|
"queryReplacement": "={{ $json[\"user_id\"] }}, {{ $json[\"key\"] }}, {{ $json[\"value_serialized\"] }}, {{ $json[\"importance\"] }}"
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.postgres",
|
|
"typeVersion": 2.6,
|
|
"position": [
|
|
2336,
|
|
112
|
|
],
|
|
"id": "45112ca7-515d-4687-a486-42e9540442bd",
|
|
"name": "Upsert Profile Memory",
|
|
"alwaysOutputData": false,
|
|
"credentials": {
|
|
"postgres": {
|
|
"id": "0kFTQeK8ZmBaEUtX",
|
|
"name": "Postgres account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"operation": "executeQuery",
|
|
"query": "INSERT INTO user_episodes (user_id, role, content)\nVALUES ($1, $2, $3);\n",
|
|
"options": {
|
|
"queryReplacement": "={{ $json[\"user_id\"] }}, {{ $json[\"role\"] }}, {{ $json[\"content\"] }}"
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.postgres",
|
|
"typeVersion": 2.6,
|
|
"position": [
|
|
2336,
|
|
304
|
|
],
|
|
"id": "44159ab6-7b57-4e57-8550-e3e282f7fd33",
|
|
"name": "Insert Episodes",
|
|
"executeOnce": false,
|
|
"credentials": {
|
|
"postgres": {
|
|
"id": "0kFTQeK8ZmBaEUtX",
|
|
"name": "Postgres account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"httpMethod": "POST",
|
|
"path": "discord/Tahoma",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.webhook",
|
|
"typeVersion": 2.1,
|
|
"position": [
|
|
-224,
|
|
-128
|
|
],
|
|
"id": "3a28dd25-23a1-45da-8cbb-5e37cb77bce2",
|
|
"name": "discordWebhook",
|
|
"webhookId": "f9a4841a-3e54-4432-9f4f-b380cb44c6a8"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "http://10.20.22.1:8282/send",
|
|
"sendHeaders": true,
|
|
"headerParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"name": "X-Tahoma-Token",
|
|
"value": "superlongrandomsharedsecret"
|
|
}
|
|
]
|
|
},
|
|
"sendBody": true,
|
|
"bodyParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "user_id",
|
|
"value": "={{ $('discordWebhook').first().json.body.user.id }}"
|
|
},
|
|
{
|
|
"name": "reply_to_message_id",
|
|
"value": "={{ $('discordWebhook').first().json.body.message_id }}"
|
|
},
|
|
{
|
|
"name": "content",
|
|
"value": "={{ $('AI With Memory').first().json.output }}"
|
|
}
|
|
]
|
|
},
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [
|
|
1536,
|
|
-192
|
|
],
|
|
"id": "e532ff47-1989-4aa4-bbab-35d23124a4f8",
|
|
"name": "DM Message"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "http://10.20.22.1:8282/send",
|
|
"sendHeaders": true,
|
|
"headerParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"name": "X-Tahoma-Token",
|
|
"value": "superlongrandomsharedsecret"
|
|
}
|
|
]
|
|
},
|
|
"sendBody": true,
|
|
"bodyParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "channel_id",
|
|
"value": "={{ $('discordWebhook').first().json.body.channel.id }}"
|
|
},
|
|
{
|
|
"name": "reply_to_message_id",
|
|
"value": "={{ $('discordWebhook').first().json.body.message_id }}"
|
|
},
|
|
{
|
|
"name": "content",
|
|
"value": "={{ $('AI With Memory').item.json.output }}"
|
|
}
|
|
]
|
|
},
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [
|
|
1536,
|
|
-384
|
|
],
|
|
"id": "bb46440b-f3c7-4a11-8b10-55b019e5e726",
|
|
"name": "Channel Message"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"rules": {
|
|
"values": [
|
|
{
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict",
|
|
"version": 2
|
|
},
|
|
"conditions": [
|
|
{
|
|
"leftValue": "={{ $('discordWebhook').first().json.body.source }}",
|
|
"rightValue": "mention",
|
|
"operator": {
|
|
"type": "string",
|
|
"operation": "equals"
|
|
},
|
|
"id": "4e57debd-1943-49a9-b794-65bec790bb4f"
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
},
|
|
"renameOutput": true,
|
|
"outputKey": "Channel"
|
|
},
|
|
{
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict",
|
|
"version": 2
|
|
},
|
|
"conditions": [
|
|
{
|
|
"id": "e0ca0788-c704-4799-8525-adff31e68a86",
|
|
"leftValue": "={{ $('discordWebhook').first().json.body.source }}",
|
|
"rightValue": "dm",
|
|
"operator": {
|
|
"type": "string",
|
|
"operation": "equals",
|
|
"name": "filter.operator.equals"
|
|
}
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
},
|
|
"renameOutput": true,
|
|
"outputKey": "User"
|
|
}
|
|
]
|
|
},
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.switch",
|
|
"typeVersion": 3.3,
|
|
"position": [
|
|
1312,
|
|
-288
|
|
],
|
|
"id": "d22ac00e-9205-4260-87ea-c1e715e6e0dd",
|
|
"name": "Response Channel",
|
|
"executeOnce": false
|
|
}
|
|
],
|
|
"connections": {
|
|
"Set Input": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Load Profile Memory",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Load Episodes": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Build Memory Context",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Build Memory Context": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "AI With Memory",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Load Profile Memory": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Load Episodes",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"OpenAI Chat Model": {
|
|
"ai_languageModel": [
|
|
[
|
|
{
|
|
"node": "AI With Memory",
|
|
"type": "ai_languageModel",
|
|
"index": 0
|
|
},
|
|
{
|
|
"node": "Extract Memory",
|
|
"type": "ai_languageModel",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"AI With Memory": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Prepare For Memory Extraction",
|
|
"type": "main",
|
|
"index": 0
|
|
},
|
|
{
|
|
"node": "Response Channel",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Prepare For Memory Extraction": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Extract Memory",
|
|
"type": "main",
|
|
"index": 0
|
|
},
|
|
{
|
|
"node": "Parse Memory JSON",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Extract Memory": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Parse Memory JSON",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Parse Memory JSON": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Fan Out Episodes",
|
|
"type": "main",
|
|
"index": 0
|
|
},
|
|
{
|
|
"node": "Fan Out Profile",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Fan Out Episodes": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Insert Episodes",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Fan Out Profile": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Upsert Profile Memory",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Insert Episodes": {
|
|
"main": [
|
|
[]
|
|
]
|
|
},
|
|
"discordWebhook": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Set Input",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Upsert Profile Memory": {
|
|
"main": [
|
|
[]
|
|
]
|
|
},
|
|
"Response Channel": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Channel Message",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "DM Message",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
}
|
|
},
|
|
"settings": {
|
|
"executionOrder": "v1"
|
|
},
|
|
"staticData": null,
|
|
"meta": {
|
|
"templateCredsSetupCompleted": true
|
|
},
|
|
"pinData": {},
|
|
"versionId": "c5484de0-f952-417a-bf0c-1f6acb4afe27",
|
|
"triggerCount": 1,
|
|
"shared": [
|
|
{
|
|
"createdAt": "2025-11-15T03:06:25.010Z",
|
|
"updatedAt": "2025-11-15T03:06:25.010Z",
|
|
"role": "workflow:owner",
|
|
"workflowId": "yoXYSA197mEd6zfK",
|
|
"projectId": "oI3LZpkceKxAFXfg"
|
|
}
|
|
],
|
|
"tags": []
|
|
} |