schedule updates

This commit is contained in:
2026-01-06 15:18:16 +00:00
parent 166ee1fc73
commit 17cbee3ab9
4 changed files with 78 additions and 47 deletions

View File

@@ -0,0 +1,15 @@
{
"id": "UPyS6lDZxRuS9ZMh",
"name": "Perplexity account",
"type": "perplexityApi",
"data": {
"apiKey": ""
},
"ownedBy": {
"type": "personal",
"projectId": "FeLO36wNUAcn61Wj",
"projectName": "Ben W <admin@ben.io>",
"personalEmail": "admin@ben.io"
},
"isGlobal": false
}

View File

@@ -50,10 +50,6 @@
"workflowId": "Z_YHsJaf_pyFQR6e7VuLo", "workflowId": "Z_YHsJaf_pyFQR6e7VuLo",
"tagId": "zrmVqhwdDmkuhhaQ" "tagId": "zrmVqhwdDmkuhhaQ"
}, },
{
"workflowId": "J3uKCCbSuQ1fdJkC",
"tagId": "FydpKYmttDwoZVAA"
},
{ {
"workflowId": "H6TZCHyiYOr1X6Xf", "workflowId": "H6TZCHyiYOr1X6Xf",
"tagId": "FydpKYmttDwoZVAA" "tagId": "FydpKYmttDwoZVAA"
@@ -61,6 +57,10 @@
{ {
"workflowId": "c3N3bYrOAy0rNGGq", "workflowId": "c3N3bYrOAy0rNGGq",
"tagId": "zrmVqhwdDmkuhhaQ" "tagId": "zrmVqhwdDmkuhhaQ"
},
{
"workflowId": "J3uKCCbSuQ1fdJkC",
"tagId": "FydpKYmttDwoZVAA"
} }
] ]
} }

View File

@@ -3,90 +3,106 @@
"name": "Project Orchestrator", "name": "Project Orchestrator",
"nodes": [ "nodes": [
{ {
"id": "2dd84c9e-0fc0-4b34-911e-4a2cea1aae86",
"name": "Code in JavaScript",
"parameters": { "parameters": {
"jsCode": "// Get raw inputs\nconst inputItem = items[0].json;\nconst rawOutput = inputItem.stdout;\nconst rawError = inputItem.stderr || \"\";\n\n// If stdout is empty but stderr has content, the agent likely failed to start\nif (!rawOutput.trim() && rawError.trim()) {\n throw new Error(`OpenCode Agent failed to produce output. Error logs: ${rawError}`);\n}\n\ntry {\n // OpenCode outputs newline-delimited JSON (NDJSON)\n const lines = rawOutput.split('\\n').filter(line => line.trim() !== '');\n if (lines.length === 0) {\n throw new Error(\"No output events received from agent.\");\n }\n \n const events = lines.map(line => JSON.parse(line));\n\n // Extract text responses (agent's messages to user)\n const textEvents = events.filter(e => e.type === 'text');\n const response = textEvents.map(e => e.part?.text || '').join('\\n').trim();\n\n // Extract tool usage summary\n const toolEvents = events.filter(e => e.type === 'tool_use');\n const tools = toolEvents.map(e => ({\n tool: e.part?.tool,\n status: e.part?.state?.status,\n input: e.part?.state?.input\n }));\n\n // Extract step finish events for token/cost stats\n const stepFinishes = events.filter(e => e.type === 'step_finish');\n \n // Validation: If no steps finished and no text was produced, it's a failure\n if (stepFinishes.length === 0 && textEvents.length === 0) {\n throw new Error(\"Agent session ended prematurely without output.\");\n }\n\n const totalTokens = stepFinishes.reduce((acc, e) => {\n const t = e.part?.tokens || {};\n return {\n input: (acc.input || 0) + (t.input || 0),\n output: (acc.output || 0) + (t.output || 0),\n reasoning: (acc.reasoning || 0) + (t.reasoning || 0)\n };\n }, {});\n const totalCost = stepFinishes.reduce((acc, e) => acc + (e.part?.cost || 0), 0);\n\n // Get session info from first event\n const sessionID = events[0]?.sessionID || null;\n\n // Clean up logs\n const cleanLogs = rawError.split('\\n').filter(line => line.trim() !== '');\n\n return [{\n json: {\n response: response.replace(/\\n+/g, ' ').trim(),\n sessionID,\n stats: {\n exit_code: inputItem.code,\n signal: inputItem.signal,\n total_cost: totalCost,\n tokens: totalTokens,\n steps: stepFinishes.length,\n tool_calls: toolEvents.length\n },\n tools,\n cli_logs: cleanLogs\n }\n }];\n\n} catch (error) {\n // Fail the node if parsing or validation fails\n throw new Error(\"Failed to validate OpenCode output: \" + error.message);\n}\n" "jsCode": "// n8n.js - Parser for OpenCode NDJSON output\n// Used by n8n workflow to process orchestrator agent results\n\n// Get raw inputs\nconst inputItem = items[0].json;\nlet rawOutput = inputItem.stdout || \"\";\nconst rawError = inputItem.stderr || \"\";\n\n// Strip ANSI escape codes from output (safety measure for permission prompts)\nconst stripAnsi = (str) => str.replace(/\\x1B(?:[@-Z\\\\-_]|\\[[0-?]*[ -/]*[@-~])/g, '');\nrawOutput = stripAnsi(rawOutput);\n\n// If stdout is empty but stderr has content, the agent likely failed to start\nif (!rawOutput.trim() && rawError.trim()) {\n throw new Error(`OpenCode Agent failed to produce output. Error logs: ${rawError}`);\n}\n\ntry {\n // OpenCode outputs newline-delimited JSON (NDJSON)\n // Filter out any non-JSON lines (e.g., residual ANSI or prompt text)\n const lines = rawOutput.split('\\n').filter(line => {\n const trimmed = line.trim();\n return trimmed !== '' && trimmed.startsWith('{');\n });\n \n if (lines.length === 0) {\n throw new Error(\"No output events received from agent.\");\n }\n \n const events = lines.map(line => JSON.parse(line));\n\n // Extract text responses (agent's messages to user)\n const textEvents = events.filter(e => e.type === 'text');\n const response = textEvents.map(e => e.part?.text || '').join('\\n').trim();\n\n // Extract tool usage summary\n const toolEvents = events.filter(e => e.type === 'tool_use');\n const tools = toolEvents.map(e => ({\n tool: e.part?.tool,\n status: e.part?.state?.status,\n input: e.part?.state?.input\n }));\n\n // Extract step finish events for token/cost stats\n const stepFinishes = events.filter(e => e.type === 'step_finish');\n \n // === VALIDATION CHECKS ===\n \n // Check 1: Agent must have produced some output\n if (stepFinishes.length === 0 && textEvents.length === 0) {\n throw new Error(\"Agent session ended prematurely without output.\");\n }\n \n // Check 2: Agent must have executed at least one tool (not just text output)\n if (toolEvents.length === 0) {\n throw new Error(\"Agent did not execute any tools - prompt may need adjustment.\");\n }\n \n // Check 3: Agent should complete multiple steps for a full run\n if (stepFinishes.length < 2) {\n throw new Error(`Agent completed only ${stepFinishes.length} step(s) - incomplete execution.`);\n }\n \n // Check 4: Exit code should be 0 (normal completion)\n if (inputItem.code !== 0 && inputItem.code !== null) {\n throw new Error(`Agent exited with non-zero code: ${inputItem.code}`);\n }\n \n // Check 5: Agent should not have been killed\n if (inputItem.signal && inputItem.signal !== 'null') {\n throw new Error(`Agent was terminated by signal: ${inputItem.signal}`);\n }\n\n // Aggregate token usage\n const totalTokens = stepFinishes.reduce((acc, e) => {\n const t = e.part?.tokens || {};\n return {\n input: (acc.input || 0) + (t.input || 0),\n output: (acc.output || 0) + (t.output || 0),\n reasoning: (acc.reasoning || 0) + (t.reasoning || 0)\n };\n }, {});\n const totalCost = stepFinishes.reduce((acc, e) => acc + (e.part?.cost || 0), 0);\n\n // Get session info from first event\n const sessionID = events[0]?.sessionID || null;\n\n // Clean up stderr logs\n const cleanLogs = rawError.split('\\n').filter(line => line.trim() !== '');\n \n // Extract orchestrator summary if present (between === markers)\n const summaryMatch = response.match(/=== ORCHESTRATOR COMPLETE ===([\\s\\S]*?)===/);\n const orchestratorSummary = summaryMatch ? summaryMatch[0].trim() : null;\n\n return [{\n json: {\n response: response.replace(/\\n+/g, ' ').trim(),\n orchestrator_summary: orchestratorSummary,\n sessionID,\n stats: {\n exit_code: inputItem.code,\n signal: inputItem.signal,\n total_cost: totalCost,\n tokens: totalTokens,\n steps: stepFinishes.length,\n tool_calls: toolEvents.length\n },\n tools,\n cli_logs: cleanLogs,\n validation: {\n passed: true,\n checks: {\n has_output: true,\n has_tool_calls: toolEvents.length > 0,\n multi_step: stepFinishes.length >= 2,\n clean_exit: inputItem.code === 0 || inputItem.code === null,\n no_signal: !inputItem.signal || inputItem.signal === 'null'\n }\n }\n }\n }];\n\n} catch (error) {\n // Fail the node if parsing or validation fails\n throw new Error(\"Failed to validate OpenCode output: \" + error.message);\n}",
"language": "javaScript",
"mode": "runOnceForAllItems",
"notice": ""
}, },
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [ "position": [
-96, -96,
48 48
], ],
"name": "Code in JavaScript", "type": "n8n-nodes-base.code",
"id": "2dd84c9e-0fc0-4b34-911e-4a2cea1aae86" "typeVersion": 2
}, },
{ {
"credentials": {
"sshPrivateKey": {
"id": "S2dcVMjrpg0I0kdV",
"name": "vscode-dev.local.ben.io"
}
},
"id": "f7a8fc32-699d-4e5b-afad-eb269855dbab",
"name": "execute run_agent.sh",
"parameters": { "parameters": {
"authentication": "privateKey", "authentication": "privateKey",
"command": "/home/b3nw/projects/core/project-orchestrator/bin/run-agent.sh", "command": "/home/b3nw/projects/core/project-orchestrator/bin/run-agent.sh",
"cwd": "/home/b3nw/projects/" "cwd": "/home/b3nw/projects/",
"operation": "execute",
"resource": "command"
}, },
"type": "n8n-nodes-base.ssh",
"position": [ "position": [
-304, -304,
48 48
], ],
"typeVersion": 1, "type": "n8n-nodes-base.ssh",
"id": "f7a8fc32-699d-4e5b-afad-eb269855dbab", "typeVersion": 1
"name": "execute run_agent.sh", },
{
"credentials": { "credentials": {
"sshPrivateKey": { "sshPrivateKey": {
"id": "S2dcVMjrpg0I0kdV", "id": "S2dcVMjrpg0I0kdV",
"name": "vscode-dev.local.ben.io" "name": "vscode-dev.local.ben.io"
} }
}
}, },
{ "id": "67e5e087-a6db-4013-87b3-a7fbb02421d1",
"name": "execute cleanup_sessions.sh",
"parameters": { "parameters": {
"authentication": "privateKey", "authentication": "privateKey",
"command": "/home/b3nw/projects/core/project-orchestrator/bin/session-cleanup.sh", "command": "/home/b3nw/projects/core/project-orchestrator/bin/session-cleanup.sh",
"cwd": "/home/b3nw/projects/core/project-orchestrator" "cwd": "/home/b3nw/projects/core/project-orchestrator",
"operation": "execute",
"resource": "command"
}, },
"type": "n8n-nodes-base.ssh",
"position": [ "position": [
-304, -304,
-128 -160
], ],
"typeVersion": 1, "type": "n8n-nodes-base.ssh",
"id": "67e5e087-a6db-4013-87b3-a7fbb02421d1", "typeVersion": 1
"name": "execute cleanup_sessions.sh",
"credentials": {
"sshPrivateKey": {
"id": "S2dcVMjrpg0I0kdV",
"name": "vscode-dev.local.ben.io"
}
}
}, },
{ {
"id": "fc10c6c7-dd2c-4458-9d75-624f8a22124c",
"name": "Weekly Trigger",
"parameters": { "parameters": {
"notice": "",
"rule": { "rule": {
"interval": [ "interval": [
{ {
"daysInterval": 7, "daysInterval": 7,
"field": "days",
"triggerAtHour": 0,
"triggerAtMinute": 30 "triggerAtMinute": 30
} }
] ]
} }
}, },
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.3,
"position": [ "position": [
-528, -528,
-128 -160
], ],
"id": "fc10c6c7-dd2c-4458-9d75-624f8a22124c", "type": "n8n-nodes-base.scheduleTrigger",
"name": "Weekly Trigger" "typeVersion": 1.3
}, },
{ {
"id": "343d37fc-f49d-4a30-969d-11abfed0594f",
"name": "Nightly Trigger",
"parameters": { "parameters": {
"notice": "",
"rule": { "rule": {
"interval": [ "interval": [
{} {
"daysInterval": 1,
"field": "days",
"triggerAtHour": 0,
"triggerAtMinute": 0
}
] ]
} }
}, },
"name": "Nightly Trigger",
"id": "343d37fc-f49d-4a30-969d-11abfed0594f",
"position": [ "position": [
-512, -512,
48 48
@@ -96,13 +112,13 @@
} }
], ],
"connections": { "connections": {
"execute run_agent.sh": { "Nightly Trigger": {
"main": [ "main": [
[ [
{ {
"type": "main",
"index": 0, "index": 0,
"node": "Code in JavaScript" "node": "execute run_agent.sh",
"type": "main"
} }
] ]
] ]
@@ -111,19 +127,19 @@
"main": [ "main": [
[ [
{ {
"index": 0,
"node": "execute cleanup_sessions.sh", "node": "execute cleanup_sessions.sh",
"type": "main", "type": "main"
"index": 0
} }
] ]
] ]
}, },
"Nightly Trigger": { "execute run_agent.sh": {
"main": [ "main": [
[ [
{ {
"node": "execute run_agent.sh",
"index": 0, "index": 0,
"node": "Code in JavaScript",
"type": "main" "type": "main"
} }
] ]
@@ -136,7 +152,7 @@
"availableInMCP": false "availableInMCP": false
}, },
"triggerCount": 2, "triggerCount": 2,
"versionId": "ae676149-56f0-4096-89d3-cdb6ab12fe44", "versionId": "5e28caa9-ed48-4605-a32d-08e4972ab792",
"owner": { "owner": {
"type": "personal", "type": "personal",
"projectId": "FeLO36wNUAcn61Wj", "projectId": "FeLO36wNUAcn61Wj",

View File

@@ -7,7 +7,7 @@
"rule": { "rule": {
"interval": [ "interval": [
{ {
"field": "hours" "triggerAtMinute": 3
} }
] ]
} }
@@ -261,7 +261,7 @@
"availableInMCP": false "availableInMCP": false
}, },
"triggerCount": 1, "triggerCount": 1,
"versionId": "efe8c6e8-c102-4086-ace6-89dc1ac2a381", "versionId": "6b85905f-aa69-42b8-a224-520c4f32ae29",
"owner": { "owner": {
"type": "personal", "type": "personal",
"projectId": "FeLO36wNUAcn61Wj", "projectId": "FeLO36wNUAcn61Wj",