diff --git a/openhands-gitea-webhook-workflow.json b/openhands-gitea-webhook-workflow.json new file mode 100644 index 0000000..e560152 --- /dev/null +++ b/openhands-gitea-webhook-workflow.json @@ -0,0 +1,350 @@ +{ + "name": "Gitea → OpenHands CI/CD", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "gitea-push", + "options": {} + }, + "id": "webhook-trigger", + "name": "Gitea Webhook", + "type": "n8n-nodes-base.webhook", + "typeVersion": 1.1, + "position": [240, 300], + "webhookId": "gitea-push" + }, + { + "parameters": { + "jsCode": "// Extract repository and commit information from Gitea webhook\nconst payload = $input.item.json;\n\n// Extract key information\nconst repoName = payload.repository?.name || 'unknown';\nconst repoFullName = payload.repository?.full_name || 'unknown';\nconst repoCloneUrl = payload.repository?.clone_url || '';\nconst branch = payload.ref?.replace('refs/heads/', '') || 'main';\nconst commitSha = payload.after || '';\nconst commitMessage = payload.commits?.[0]?.message || 'No message';\nconst pusher = payload.pusher?.username || 'unknown';\n\n// Create task message for OpenHands\nconst task = `Build and test project ${repoFullName} on branch ${branch}. ` +\n `Latest commit: \"${commitMessage}\". ` +\n `Run the following commands: npm install && npm test && npm build. ` +\n `Report any errors found.`;\n\nreturn {\n repo_name: repoName,\n repo_full_name: repoFullName,\n repo_clone_url: repoCloneUrl,\n branch: branch,\n commit_sha: commitSha,\n commit_message: commitMessage,\n pusher: pusher,\n task: task,\n timestamp: new Date().toISOString()\n};" + }, + "id": "extract-repo-info", + "name": "Extract Repo Info", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [460, 300] + }, + { + "parameters": { + "url": "http://172.18.0.1:3000/api/conversations", + "method": "POST", + "sendBody": true, + "bodyParameters": { + "parameters": [ + { + "name": "initial_user_msg", + "value": "={{ $json.task }}" + }, + { + "name": "repository", + "value": "={{ $json.repo_clone_url }}" + }, + { + "name": "selected_branch", + "value": "={{ $json.branch }}" + } + ] + }, + "options": { + "response": { + "response": { + "responseFormat": "json" + } + } + } + }, + "id": "create-conversation", + "name": "Create OpenHands Session", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [680, 300] + }, + { + "parameters": { + "amount": 10, + "unit": "seconds" + }, + "id": "wait-initial", + "name": "Wait 10s", + "type": "n8n-nodes-base.wait", + "typeVersion": 1.1, + "position": [900, 300], + "webhookId": "wait-initial" + }, + { + "parameters": { + "url": "=http://172.18.0.1:3000/api/conversations/{{ $json.conversation_id }}", + "method": "GET", + "options": { + "response": { + "response": { + "responseFormat": "json" + } + } + } + }, + "id": "get-status", + "name": "Check Build Status", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [1120, 300] + }, + { + "parameters": { + "conditions": { + "options": { + "combineOperation": "any" + }, + "conditions": [ + { + "id": "status-running", + "leftValue": "={{ $json.status }}", + "rightValue": "RUNNING", + "operator": { + "type": "string", + "operation": "equals" + } + }, + { + "id": "status-stopped", + "leftValue": "={{ $json.status }}", + "rightValue": "STOPPED", + "operator": { + "type": "string", + "operation": "equals" + } + }, + { + "id": "status-awaiting", + "leftValue": "={{ $json.status }}", + "rightValue": "AWAITING_USER_INPUT", + "operator": { + "type": "string", + "operation": "equals" + } + } + ] + }, + "options": {} + }, + "id": "check-ready", + "name": "Is Build Ready?", + "type": "n8n-nodes-base.if", + "typeVersion": 2, + "position": [1340, 300] + }, + { + "parameters": { + "amount": 15, + "unit": "seconds" + }, + "id": "wait-retry", + "name": "Wait 15s Retry", + "type": "n8n-nodes-base.wait", + "typeVersion": 1.1, + "position": [1560, 180], + "webhookId": "wait-retry" + }, + { + "parameters": { + "jsCode": "// Track retry count\nconst retries = $workflow.staticData.retries || 0;\nconst maxRetries = 20; // 5 minutes max (20 * 15s)\n\nif (retries >= maxRetries) {\n throw new Error('Build timeout: Max retries exceeded (5 minutes)');\n}\n\n$workflow.staticData.retries = retries + 1;\n\n// Pass through conversation_id\nconst convId = $input.item.json.conversation_id || $('Create OpenHands Session').item.json.conversation_id;\n\nreturn {\n conversation_id: convId,\n retry_count: retries + 1,\n max_retries: maxRetries\n};" + }, + "id": "retry-counter", + "name": "Retry Counter", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [1780, 180] + }, + { + "parameters": { + "url": "=http://172.18.0.1:3000/api/conversations/{{ $('Create OpenHands Session').item.json.conversation_id }}/events", + "method": "GET", + "options": { + "response": { + "response": { + "responseFormat": "json" + } + } + } + }, + "id": "get-events", + "name": "Get Build Events", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [1560, 420] + }, + { + "parameters": { + "jsCode": "// Analyze events to determine build success/failure\nconst events = $input.item.json.events || [];\nconst repoInfo = $('Extract Repo Info').item.json;\n\n// Look for error indicators\nconst hasErrors = events.some(e => \n e.message?.toLowerCase().includes('error') ||\n e.message?.toLowerCase().includes('failed') ||\n e.observation?.toLowerCase().includes('error')\n);\n\n// Look for success indicators\nconst hasSuccess = events.some(e =>\n e.message?.toLowerCase().includes('success') ||\n e.message?.toLowerCase().includes('passed') ||\n e.message?.toLowerCase().includes('completed')\n);\n\n// Get last event\nconst lastEvent = events[events.length - 1] || {};\n\nreturn {\n repo: repoInfo.repo_full_name,\n branch: repoInfo.branch,\n commit: repoInfo.commit_sha,\n build_status: hasErrors ? 'FAILED' : (hasSuccess ? 'SUCCESS' : 'UNKNOWN'),\n total_events: events.length,\n last_event_message: lastEvent.message || 'No message',\n has_errors: hasErrors,\n has_success: hasSuccess,\n events: events\n};" + }, + "id": "analyze-results", + "name": "Analyze Build Results", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [1780, 420] + }, + { + "parameters": { + "mode": "raw", + "jsonOutput": "={{ {\n \"status\": $json.build_status,\n \"repo\": $json.repo,\n \"branch\": $json.branch,\n \"commit\": $json.commit.substring(0, 8),\n \"message\": $json.build_status === 'SUCCESS' ? '✅ Build passed' : ($json.build_status === 'FAILED' ? '❌ Build failed' : '⚠️ Build status unknown'),\n \"total_events\": $json.total_events,\n \"timestamp\": new Date().toISOString()\n} }}", + "options": {} + }, + "id": "format-response", + "name": "Format Response", + "type": "n8n-nodes-base.set", + "typeVersion": 3.3, + "position": [2000, 420] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ $json }}", + "options": {} + }, + "id": "webhook-response", + "name": "Webhook Response", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1.1, + "position": [2220, 420] + } + ], + "connections": { + "Gitea Webhook": { + "main": [ + [ + { + "node": "Extract Repo Info", + "type": "main", + "index": 0 + } + ] + ] + }, + "Extract Repo Info": { + "main": [ + [ + { + "node": "Create OpenHands Session", + "type": "main", + "index": 0 + } + ] + ] + }, + "Create OpenHands Session": { + "main": [ + [ + { + "node": "Wait 10s", + "type": "main", + "index": 0 + } + ] + ] + }, + "Wait 10s": { + "main": [ + [ + { + "node": "Check Build Status", + "type": "main", + "index": 0 + } + ] + ] + }, + "Check Build Status": { + "main": [ + [ + { + "node": "Is Build Ready?", + "type": "main", + "index": 0 + } + ] + ] + }, + "Is Build Ready?": { + "main": [ + [ + { + "node": "Get Build Events", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Wait 15s Retry", + "type": "main", + "index": 0 + } + ] + ] + }, + "Wait 15s Retry": { + "main": [ + [ + { + "node": "Retry Counter", + "type": "main", + "index": 0 + } + ] + ] + }, + "Retry Counter": { + "main": [ + [ + { + "node": "Check Build Status", + "type": "main", + "index": 0 + } + ] + ] + }, + "Get Build Events": { + "main": [ + [ + { + "node": "Analyze Build Results", + "type": "main", + "index": 0 + } + ] + ] + }, + "Analyze Build Results": { + "main": [ + [ + { + "node": "Format Response", + "type": "main", + "index": 0 + } + ] + ] + }, + "Format Response": { + "main": [ + [ + { + "node": "Webhook Response", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": {}, + "settings": { + "executionOrder": "v1" + }, + "staticData": null, + "tags": [], + "triggerCount": 0, + "updatedAt": "2025-11-29T19:40:00.000Z", + "versionId": "1" +} diff --git a/openhands-workflow.json b/openhands-workflow.json new file mode 100644 index 0000000..5dea70a --- /dev/null +++ b/openhands-workflow.json @@ -0,0 +1,249 @@ +{ + "name": "OpenHands API Test Workflow", + "nodes": [ + { + "parameters": {}, + "id": "manual-trigger", + "name": "Manual Trigger", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [240, 300] + }, + { + "parameters": { + "url": "http://172.18.0.1:3000/api/conversations", + "method": "POST", + "sendBody": true, + "bodyParameters": { + "parameters": [ + { + "name": "initial_user_msg", + "value": "Create a file named hello.txt with content: Hello from n8n automated workflow!" + } + ] + }, + "options": { + "response": { + "response": { + "responseFormat": "json" + } + } + } + }, + "id": "create-conversation", + "name": "Create Conversation", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [460, 300] + }, + { + "parameters": { + "amount": 5, + "unit": "seconds" + }, + "id": "wait-5s", + "name": "Wait 5s", + "type": "n8n-nodes-base.wait", + "typeVersion": 1.1, + "position": [680, 300], + "webhookId": "wait-5s" + }, + { + "parameters": { + "url": "=http://172.18.0.1:3000/api/conversations/{{ $json.conversation_id }}", + "method": "GET", + "options": { + "response": { + "response": { + "responseFormat": "json" + } + } + } + }, + "id": "get-status", + "name": "Get Conversation Status", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [900, 300] + }, + { + "parameters": { + "conditions": { + "options": { + "combineOperation": "any" + }, + "conditions": [ + { + "id": "status-running", + "leftValue": "={{ $json.status }}", + "rightValue": "RUNNING", + "operator": { + "type": "string", + "operation": "equals" + } + }, + { + "id": "status-awaiting", + "leftValue": "={{ $json.status }}", + "rightValue": "AWAITING_USER_INPUT", + "operator": { + "type": "string", + "operation": "equals" + } + }, + { + "id": "status-stopped", + "leftValue": "={{ $json.status }}", + "rightValue": "STOPPED", + "operator": { + "type": "string", + "operation": "equals" + } + } + ] + }, + "options": {} + }, + "id": "check-status", + "name": "Check If Ready", + "type": "n8n-nodes-base.if", + "typeVersion": 2, + "position": [1120, 300] + }, + { + "parameters": { + "amount": 10, + "unit": "seconds" + }, + "id": "wait-more", + "name": "Wait 10s More", + "type": "n8n-nodes-base.wait", + "typeVersion": 1.1, + "position": [1340, 180], + "webhookId": "wait-more" + }, + { + "parameters": { + "url": "=http://172.18.0.1:3000/api/conversations/{{ $('Create Conversation').item.json.conversation_id }}/events?limit=20", + "method": "GET", + "options": { + "response": { + "response": { + "responseFormat": "json" + } + } + } + }, + "id": "get-events", + "name": "Get Events", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [1340, 420] + }, + { + "parameters": { + "jsCode": "// Extract conversation ID and status from previous node\nconst convId = $input.item.json.conversation_id || $('Create Conversation').item.json.conversation_id;\nconst status = $input.item.json.status;\nconst runtimeStatus = $input.item.json.runtime_status;\n\nreturn {\n conversation_id: convId,\n status: status,\n runtime_status: runtimeStatus,\n message: `Status: ${status}, Runtime: ${runtimeStatus}`\n};" + }, + "id": "format-status", + "name": "Format Status", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [1560, 180] + } + ], + "connections": { + "Manual Trigger": { + "main": [ + [ + { + "node": "Create Conversation", + "type": "main", + "index": 0 + } + ] + ] + }, + "Create Conversation": { + "main": [ + [ + { + "node": "Wait 5s", + "type": "main", + "index": 0 + } + ] + ] + }, + "Wait 5s": { + "main": [ + [ + { + "node": "Get Conversation Status", + "type": "main", + "index": 0 + } + ] + ] + }, + "Get Conversation Status": { + "main": [ + [ + { + "node": "Check If Ready", + "type": "main", + "index": 0 + } + ] + ] + }, + "Check If Ready": { + "main": [ + [ + { + "node": "Get Events", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Wait 10s More", + "type": "main", + "index": 0 + } + ] + ] + }, + "Wait 10s More": { + "main": [ + [ + { + "node": "Format Status", + "type": "main", + "index": 0 + } + ] + ] + }, + "Format Status": { + "main": [ + [ + { + "node": "Get Conversation Status", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": {}, + "settings": { + "executionOrder": "v1" + }, + "staticData": null, + "tags": [], + "triggerCount": 0, + "updatedAt": "2025-11-29T19:30:00.000Z", + "versionId": "1" +}