openhands cli aproach

This commit is contained in:
Git Admin 2025-11-30 22:23:24 +00:00
parent 3db5b22c59
commit c7e2c7ab0b
8 changed files with 854 additions and 5 deletions

128
NEXT_STEPS.md Normal file
View File

@ -0,0 +1,128 @@
# OpenHands Integration - Next Steps
## Status Summary
✅ **Completed:**
- Tested both API and CLI approaches
- Identified technical blockers (network namespace + TTY issues)
- Created 3 n8n workflow configurations
- Built Python wrapper scripts
- Comprehensive documentation
⏳ **Ready for Testing:**
- `/home/bam/openhands-pty.py` - Pseudo-TTY wrapper (needs verification)
- `/home/bam/claude/mvp-factory/openhands-n8n-workflow.json` - Complete workflow
## Immediate Actions Required
### 1. Test the pty-based wrapper (CRITICAL)
```bash
# Run this manually to verify it works:
python3 /home/bam/openhands-pty.py "Create a file named final-test.txt with content: Success!"
# Check if file was created:
ls -la /home/bam/final-test.txt
cat /home/bam/final-test.txt
```
### 2. If wrapper works, proceed with n8n integration
```bash
# The workflow file to use:
/home/bam/claude/mvp-factory/openhands-n8n-workflow.json
# Import via n8n UI:
# 1. Open https://n8n.oky.sh
# 2. Credentials → Add SSH credentials (ai-dev-localhost)
# 3. Import from file → Select openhands-n8n-workflow.json
# 4. Activate workflow
```
### 3. Test webhook endpoint
```bash
# Test manually:
curl -X POST https://n8n.oky.sh/webhook/openhands-task \
-H "Content-Type: application/json" \
-d '{
"repository": {"full_name": "test/repo"},
"commits": [{"message": "Test build task"}]
}'
```
## Files Reference
**Python Wrappers:**
- `/home/bam/run-openhands.py` - Original wrapper (may have TTY issues)
- `/home/bam/openhands-pty.py` - **BEST CANDIDATE** (uses pseudo-TTY)
**n8n Workflows:**
- `/home/bam/claude/mvp-factory/openhands-n8n-workflow.json` - **USE THIS** (complete workflow)
- `/home/bam/claude/mvp-factory/openhands-cli-tmux-workflow.json` - Alternative with tmux
**Documentation:**
- `/home/bam/claude/mvp-factory/OPENHANDS_INTEGRATION_STATUS.md` - Full status report
- `/home/bam/claude/mvp-factory/NEXT_STEPS.md` - This file
## If pty wrapper doesn't work:
**Option A: Debug and improve**
- Check logs from pty wrapper run
- Adjust timing in openhands-pty.py
- Try pexpect library if available
**Option B: Use tmux approach**
- Update n8n workflow to use tmux commands
- More reliable for long-running tasks
**Option C: API approach with fixed runtime**
- Debug network namespace issue
- Requires Docker networking expertise
- May take significant time
## Decision Matrix
| Approach | Reliability | Complexity | Time to Implement |
|----------|------------|------------|------------------|
| pty wrapper | Medium | Low | Immediate |
| tmux in n8n | High | Medium | 1-2 hours |
| Fixed API | High | High | Unknown |
| Manual CLI | N/A | High | Not viable |
## Gitea Webhook Configuration
Once n8n workflow works:
```
URL: https://n8n.oky.sh/webhook/openhands-task
Method: POST
Content-Type: application/json
Secret: [generate random string]
Events: Push events
```
Test by pushing to any repository:
```bash
echo "Test" > test.txt
git add . && git commit -m "Test webhook" && git push
```
## Success Criteria
- [ ] Python wrapper creates file successfully
- [ ] n8n workflow imports without errors
- [ ] Webhook triggers workflow
- [ ] OpenHands executes task
- [ ] File is created and verified
- [ ] End-to-end test passes
## Contact/Support
See `/home/bam/claude/mvp-factory/OPENHANDS_INTEGRATION_STATUS.md` for:
- Detailed error analysis
- Technical root causes
- Alternative solutions
- Complete testing procedures
---
**Last Updated:** 2025-11-30 22:07
**Ready for:** User testing and validation

View File

@ -0,0 +1,422 @@
# OpenHands Integration Status Report
**Date:** 2025-11-30
**Phase:** OpenHands CLI Integration Testing
**Status:** Technical Blocker Identified
---
## Executive Summary
We attempted to integrate OpenHands with n8n workflows using both API and CLI approaches. While both methods show partial functionality, there are technical blockers preventing full automation:
1. **API Approach:** Conversations can be created, but runtime containers fail to start due to network namespace isolation
2. **CLI Approach:** Works interactively but requires TTY for auto-confirmation, which is problematic in non-TTY environments (like n8n SSH nodes)
---
## What We Tested
### 1. OpenHands CLI Direct Execution
**Command:** `/home/bam/.local/bin/openhands -t "Create a file"`
**Result:**
- ✅ CLI starts successfully
- ✅ Agent initializes with MiniMax-M2 model
- ✅ Task is processed and action created
- ❌ **BLOCKER:** Waits for interactive confirmation ("Choose an option:")
- ❌ File not created in non-TTY environment
**Output Log:**
```
Predicted Security Risk: LOW
Agent Action created, waiting for confirmation
Choose an option:
Yes, proceed
Reject
Always proceed (don't ask again)
No input received; pausing agent.
```
### 2. OpenHands API Approach
**Test:** Create conversation via HTTP API
**Command:**
```bash
curl -X POST http://localhost:3000/api/conversations \
-H "Content-Type: application/json" \
-d '{"initial_user_msg": "Create a file"}'
```
**Result:**
- ✅ Conversation created successfully (ID: a23d491e55ae4e0995b563a54705d59c)
- ❌ **BLOCKER:** Runtime stuck at "STATUS$STARTING_RUNTIME"
- ❌ Network namespace isolation prevents runtime container from connecting
**Status Response:**
```json
{
"status": "STARTING",
"runtime_status": "STATUS$STARTING_RUNTIME",
"conversation_id": "a23d491e55ae4e0995b563a54705d59c"
}
```
### 3. CLI Automation Attempts
#### Attempt 1: Python Wrapper (subprocess.PIPE)
**File:** `/home/bam/run-openhands.py`
**Result:**
- Created wrapper with stdin pipe
- Still receives TTY warning: "Warning: Input is not a terminal (fd=0)"
- Auto-confirmation logic attempted but unreliable
#### Attempt 2: Bash Script with Timeout
**File:** `/home/bam/test-openhands-cli.sh`
**Result:**
- Uses piped input: `echo -e "task\ny" | openhands -t ""`
- ❌ Input not properly received
- Task starts but never completes
#### Attempt 3: Python with pty Module
**File:** `/home/bam/openhands-pty.py`
**Result:**
- Created pseudo-TTY for proper terminal emulation
- Uses select() for async I/O
- ⏳ Still running during test (potential solution)
---
## n8n Workflows Created
We created three n8n workflow configurations:
### 1. Simple CLI Workflow
**File:** `/home/bam/claude/mvp-factory/openhands-cli-simple.json`
- Single SSH node
- Direct CLI execution
- Expected to fail due to TTY issues
### 2. tmux-based Workflow
**File:** `/home/bam/claude/mvp-factory/openhands-cli-tmux-workflow.json`
- Uses tmux for session management
- Attempts to run CLI in persistent session
- May work but untested
### 3. Complete Workflow with Webhook
**File:** `/home/bam/claude/mvp-factory/openhands-n8n-workflow.json`
- Webhook trigger for Gitea integration
- SSH execution node
- Verification node
- Response node
- **BEST CANDIDATE** for testing
---
## Root Cause Analysis
### Network Namespace Isolation Issue
**Problem:** OpenHands server runs on host network, but runtime containers are in bridge network. The `--add-host host.docker.internal:host-gateway` flag resolves DNS but not port connectivity.
**Evidence:**
- API responds on http://localhost:3000
- Conversation creation succeeds
- Runtime startup fails silently
- Status remains "STATUS$STARTING_RUNTIME"
### TTY/Interactive Input Issue
**Problem:** OpenHands CLI requires interactive confirmation for security, but n8n SSH nodes don't provide TTY.
**Evidence:**
- "Warning: Input is not a terminal (fd=0)"
- "Choose an option:" prompt appears
- Piped input not properly handled
- Auto-confirmation doesn't trigger
---
## Potential Solutions
### Solution 1: Improve Python Wrapper
**Approach:** Enhance `/home/bam/run-openhands.py` or `/home/bam/openhands-pty.py`
**Implementation:**
- Use pexpect library (if available) for robust interaction
- Implement proper response timing
- Add retry logic
- Test thoroughly
**Pros:**
- Works within existing setup
- No infrastructure changes
- Reusable for other automation
**Cons:**
- Requires Python library installation
- May still have timing issues
### Solution 2: Use tmux in n8n
**Approach:** Use tmux for persistent sessions in SSH node
**Implementation:**
```bash
tmux new-session -d -s task
tmux send-keys -t task 'openhands -t "task"' C-m
sleep 30
tmux capture-pane -t task -p
```
**Pros:**
- Provides proper TTY
- Industry-standard approach
- Works with existing SSH
**Cons:**
- Adds session management complexity
- Requires tmux on target system
- May timeout on long tasks
### Solution 3: Use OpenHands Built-in Automation
**Approach:** Check if OpenHands has non-interactive or batch mode
**Commands to check:**
```bash
openhands --help
openhands -h
cat ~/.openhands/settings.json
```
**Pros:**
- Official solution
- Most reliable
- Supported by upstream
**Cons:**
- May not exist in current version (1.3.0)
### Solution 4: API with Fixed Runtime
**Approach:** Fix network namespace issue for API approach
**Implementation:**
- Use host networking for runtime (dangerous)
- Custom Docker network configuration
- Different runtime architecture
**Pros:**
- Clean API interface
- Better error handling
- Proper status monitoring
**Cons:**
- Requires significant infrastructure changes
- Security implications
- May break other functionality
---
## Current Workflow Architecture
```
Gitea Push → n8n Webhook → SSH Node → OpenHands CLI → File Creation
Verification Node
Response to Gitea (optional)
```
**Webhooks:**
- Gitea → https://n8n.oky.sh/webhook/openhands-task
- n8n will execute OpenHands task
- Verify file creation
- Return status to Gitea (future enhancement)
---
## Immediate Next Steps
### Option 1: Continue with CLI Approach (Recommended)
1. Test the `openhands-pty.py` wrapper more thoroughly
2. If it works, update n8n workflow to use it
3. Test with real Gitea webhook
4. Add error handling and timeouts
### Option 2: Debug API Runtime Issue
1. Check Docker logs for runtime startup errors
2. Test network connectivity between containers
3. Try different Docker networking modes
4. If fixed, use API approach (cleaner)
### Option 3: Hybrid Approach
1. Use API to create conversation
2. Use CLI to execute task
3. Monitor status via API
4. Best of both worlds?
---
## Files Created/Modified
```
/home/bam/
├── run-openhands.py # Python wrapper (original)
├── openhands-pty.py # Python wrapper with pty
├── test-openhands-cli.sh # Bash test script
├── start-openhands-fixed.sh # Docker startup script
└── claude/mvp-factory/
├── openhands-cli-simple.json # Simple workflow
├── openhands-cli-tmux-workflow.json # tmux-based workflow
├── openhands-n8n-workflow.json # Complete workflow
└── OPENHANDS_INTEGRATION_STATUS.md # This file
```
---
## Testing Required
### Pre-Production Testing
1. **Wrapper Script Testing**
- [ ] Test `/home/bam/openhands-pty.py` with simple task
- [ ] Verify file creation works
- [ ] Test timeout handling
- [ ] Test error handling
2. **n8n Workflow Testing**
- [ ] Import `/home/bam/claude/mvp-factory/openhands-n8n-workflow.json`
- [ ] Configure SSH credentials
- [ ] Test webhook manually
- [ ] Check execution logs
3. **End-to-End Testing**
- [ ] Configure Gitea webhook
- [ ] Push to test repository
- [ ] Verify workflow triggers
- [ ] Check OpenHands execution
- [ ] Verify file creation
4. **Integration Testing**
- [ ] Test with real project (not just file creation)
- [ ] Test npm install & build
- [ ] Test error scenarios
- [ ] Test timeout handling
---
## Risks and Considerations
### Security Risks
- OpenHands has sudo access in container
- SSH credentials stored in n8n
- Webhook endpoints exposed publicly
- Runtime isolation issues
### Reliability Risks
- TTY issues may prevent automation
- Network namespace problems
- Timeout handling inadequate
- No retry logic currently
### Operational Risks
- Long-running tasks in SSH node
- Session management complexity
- Log storage and rotation
- Resource consumption
---
## Success Criteria
To consider this integration complete:
1. ✅ OpenHands can be triggered via n8n
2. ✅ Task executes successfully (not just starts)
3. ✅ File/folder creation verified
4. ✅ Gitea webhook triggers workflow
5. ✅ End-to-end flow works (Push → Build → Verify)
6. ✅ Error handling and timeouts implemented
7. ✅ Documentation updated
---
## Recommendations
### Immediate (Today)
1. **Test `openhands-pty.py` thoroughly**
- If it works: Use in n8n workflow
- If not: Debug and improve
2. **Use `/home/bam/claude/mvp-factory/openhands-n8n-workflow.json`**
- Most complete workflow
- Ready for testing
- Good documentation
### Short-term (This Week)
1. **Fix remaining TTY/automation issues**
2. **Add proper error handling**
3. **Test with real build tasks**
4. **Configure Gitea webhook properly**
### Long-term
1. **Investigate API runtime fix**
2. **Add retry logic and timeouts**
3. **Implement proper status reporting**
4. **Add logging and monitoring**
---
## Conclusion
The OpenHands integration is **partially functional** but has **technical blockers** preventing full automation. Both API and CLI approaches work partially:
- **CLI:** Starts but doesn't complete due to TTY issues
- **API:** Creates conversations but runtime fails to start
**Next action:** Test the `openhands-pty.py` wrapper and if successful, integrate into n8n workflow for full automation testing.
---
## Appendix: Command Reference
### Test OpenHands CLI Directly
```bash
/home/bam/.local/bin/openhands -t "Create a test file"
# Press 'y' when prompted, then 'exit'
```
### Test Python Wrapper
```bash
python3 /home/bam/run-openhands.py "Create a file"
# Or
python3 /home/bam/openhands-pty.py "Create a file"
```
### Test API Directly
```bash
curl -X POST http://localhost:3000/api/conversations \
-H "Content-Type: application/json" \
-d '{"initial_user_msg": "Create a file"}'
```
### Check n8n Workflows
```bash
curl -u admin:password https://n8n.oky.sh/api/v1/workflows
```
### Monitor OpenHands Server
```bash
docker logs -f openhands-app
# Or if running via systemd
sudo journalctl -u openhands.service -f
```
---
**End of Report**

View File

@ -0,0 +1,22 @@
version: '3.8'
# Simple proxy to allow n8n (bridge network) to reach OpenHands (host network)
# This is a workaround for the iptables restriction
services:
openhands-proxy:
image: alpine/socat:latest
container_name: openhands-proxy
restart: unless-stopped
# Listen on port 3000 inside services-stack network
# Forward to host's port 3000 (OpenHands)
command: tcp-listen:3000,fork,reuseaddr tcp-connect:host.docker.internal:3000
networks:
- services-network
extra_hosts:
- "host.docker.internal:host-gateway"
networks:
services-network:
name: services-stack_services-network
external: true

37
openhands-cli-simple.json Normal file
View File

@ -0,0 +1,37 @@
{
"meta": {
"instanceId": "openhands-cli-test"
},
"nodes": [
{
"parameters": {
"command": "echo 'Hello World' && /home/bam/.local/bin/openhands -t 'Create a file named test-cli.txt with content: Testing CLI workflow'",
"sessionId": "test-session"
},
"id": "ssh-execute",
"name": "Execute OpenHands CLI",
"type": "n8n-nodes-base.ssh",
"typeVersion": 2,
"position": [460, 300],
"credentials": {
"sshPassword": {
"id": "ai-dev-localhost",
"name": "ai-dev-localhost"
}
}
}
],
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"staticData": null,
"tags": [],
"triggerCount": 0,
"updatedAt": "2025-11-30T21:50:00.000Z",
"versionId": "1",
"active": false,
"meta": {
"templateCredsSetupCompleted": false
}
}

View File

@ -0,0 +1,54 @@
{
"meta": {
"instanceId": "openhands-cli-tmux-test"
},
"nodes": [
{
"parameters": {
"command": "cd /home/bam && tmux new-session -d -s openhands-task && tmux send-keys -t openhands-task 'python3 /home/bam/openhands-pty.py \"$TASK\"' C-m && sleep 60 && tmux capture-pane -t openhands-task -p",
"sessionId": "test-session"
},
"id": "ssh-execute",
"name": "Execute OpenHands CLI via tmux",
"type": "n8n-nodes-base.ssh",
"typeVersion": 2,
"position": [460, 300],
"credentials": {
"sshPassword": {
"id": "ai-dev-localhost",
"name": "ai-dev-localhost"
}
}
},
{
"parameters": {
"command": "ls -la /home/bam/*.txt 2>/dev/null | tail -10",
"sessionId": "test-session"
},
"id": "ssh-verify",
"name": "Verify Files Created",
"type": "n8n-nodes-base.ssh",
"typeVersion": 2,
"position": [680, 300],
"credentials": {
"sshPassword": {
"id": "ai-dev-localhost",
"name": "ai-dev-localhost"
}
}
}
],
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"staticData": null,
"tags": [],
"triggerCount": 0,
"updatedAt": "2025-11-30T22:00:00.000Z",
"versionId": "1",
"active": false,
"meta": {
"templateCredsSetupCompleted": false
}
}

View File

@ -0,0 +1,63 @@
{
"meta": {
"instanceId": "openhands-cli-test"
},
"nodes": [
{
"parameters": {},
"id": "webhook-trigger",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [460, 300],
"webhookId": "gitea-webhook"
},
{
"parameters": {
"command": "=cd /home/bam && echo \"{{ $json.repository.full_name }} {{ $json.commits[0].message }}\" > /tmp/task.txt && /home/bam/.local/bin/openhands -t \"$(cat /tmp/task.txt)\"",
"sessionId": "bam-openhands"
},
"id": "ssh-execute",
"name": "2. SSH Execute OpenHands",
"type": "n8n-nodes-base.ssh",
"typeVersion": 2,
"position": [680, 300],
"credentials": {
"sshPassword": {
"id": "ai-dev-localhost",
"name": "ai-dev-localhost"
}
}
},
{
"parameters": {
"command": "=ls -la /home/bam/*.txt 2>/dev/null | tail -10",
"sessionId": "bam-openhands"
},
"id": "ssh-verify",
"name": "3. SSH Verify Results",
"type": "n8n-nodes-base.ssh",
"typeVersion": 2,
"position": [900, 300],
"credentials": {
"sshPassword": {
"id": "ai-dev-localhost",
"name": "ai-dev-localhost"
}
}
}
],
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"staticData": null,
"tags": [],
"triggerCount": 0,
"updatedAt": "2025-11-30T21:50:00.000Z",
"versionId": "1",
"active": false,
"meta": {
"templateCredsSetupCompleted": false
}
}

119
openhands-n8n-workflow.json Normal file
View File

@ -0,0 +1,119 @@
{
"meta": {
"instanceId": "openhands-n8n-workflow"
},
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "openhands-task",
"responseMode": "responseNode"
},
"id": "webhook",
"name": "Webhook Trigger",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [240, 300],
"webhookId": "openhands-webhook"
},
{
"parameters": {
"command": "cd /home/bam && echo \"{{ $json.repository.full_name }}: {{ $json.commits[0].message }}\" > /tmp/openhands-task.txt && python3 /home/bam/run-openhands.py \"$(cat /tmp/openhands-task.txt)\"",
"sessionId": "openhands-session"
},
"id": "ssh-execute",
"name": "Execute OpenHands",
"type": "n8n-nodes-base.ssh",
"typeVersion": 2,
"position": [460, 300],
"credentials": {
"sshPassword": {
"id": "ai-dev-localhost",
"name": "ai-dev-localhost"
}
}
},
{
"parameters": {
"command": "ls -la /home/bam/*.txt 2>/dev/null | tail -15",
"sessionId": "openhands-session"
},
"id": "ssh-verify",
"name": "Verify Results",
"type": "n8n-nodes-base.ssh",
"typeVersion": 2,
"position": [680, 300],
"credentials": {
"sshPassword": {
"id": "ai-dev-localhost",
"name": "ai-dev-localhost"
}
}
},
{
"parameters": {
"respondWith": "json",
"responseBody": {
"status": "success",
"message": "OpenHands task executed",
"timestamp": "{{ $now }}",
"results": "Check logs for details"
},
"options": {}
},
"id": "webhook-response",
"name": "Webhook Response",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": [900, 300]
}
],
"connections": {
"Webhook Trigger": {
"main": [
[
{
"node": "Execute OpenHands",
"type": "main",
"index": 0
}
]
]
},
"Execute OpenHands": {
"main": [
[
{
"node": "Verify Results",
"type": "main",
"index": 0
}
]
]
},
"Verify Results": {
"main": [
[
{
"node": "Webhook Response",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"staticData": null,
"tags": [],
"triggerCount": 1,
"updatedAt": "2025-11-30T22:10:00.000Z",
"versionId": "1",
"active": false,
"meta": {
"templateCredsSetupCompleted": false
}
}

View File

@ -21,7 +21,7 @@
}, },
{ {
"parameters": { "parameters": {
"url": "http://10.10.10.11:3000/api/conversations", "url": "http://host.docker.internal:3000/api/conversations",
"method": "POST", "method": "POST",
"sendBody": true, "sendBody": true,
"bodyParameters": { "bodyParameters": {
@ -48,7 +48,7 @@
}, },
{ {
"parameters": { "parameters": {
"url": "=http://10.10.10.11:3000/api/conversations/{{ $json.conversation_id }}/start", "url": "=http://host.docker.internal:3000/api/conversations/{{ $json.conversation_id }}/start",
"method": "POST", "method": "POST",
"sendBody": true, "sendBody": true,
"specifyBody": "json", "specifyBody": "json",
@ -81,7 +81,7 @@
}, },
{ {
"parameters": { "parameters": {
"url": "=http://10.10.10.11:3000/api/conversations/{{ $('1. Create Conversation').item.json.conversation_id }}", "url": "=http://host.docker.internal:3000/api/conversations/{{ $('1. Create Conversation').item.json.conversation_id }}",
"method": "GET", "method": "GET",
"options": { "options": {
"response": { "response": {
@ -165,7 +165,7 @@
}, },
{ {
"parameters": { "parameters": {
"url": "=http://10.10.10.11:3000/api/conversations/{{ $('1. Create Conversation').item.json.conversation_id }}/events", "url": "=http://host.docker.internal:3000/api/conversations/{{ $('1. Create Conversation').item.json.conversation_id }}/events",
"method": "GET", "method": "GET",
"options": { "options": {
"response": { "response": {
@ -361,5 +361,9 @@
"tags": [], "tags": [],
"triggerCount": 0, "triggerCount": 0,
"updatedAt": "2025-11-30T08:30:00.000Z", "updatedAt": "2025-11-30T08:30:00.000Z",
"versionId": "1" "versionId": "1",
"active": false,
"meta": {
"templateCredsSetupCompleted": false
}
} }