← back to Letsbegin
app/api/live/check/route.ts
179 lines
import { NextRequest, NextResponse } from 'next/server'
import { exec } from 'child_process'
import { promisify } from 'util'
const execAsync = promisify(exec)
export async function POST(request: NextRequest) {
try {
const { pid, action } = await request.json()
if (!pid) {
return NextResponse.json({ error: 'PID required' }, { status: 400 })
}
// Check if process exists
let processExists = false
let processInfo: any = null
try {
const { stdout } = await execAsync(`ps -p ${pid} -o pid,state,pcpu,pmem,etime,comm --no-headers 2>/dev/null`)
if (stdout.trim()) {
const parts = stdout.trim().split(/\s+/)
processExists = true
processInfo = {
pid: parseInt(parts[0]),
state: parts[1],
cpu: parseFloat(parts[2]),
memory: parseFloat(parts[3]),
elapsed: parts[4],
command: parts.slice(5).join(' ')
}
}
} catch {
processExists = false
}
if (!processExists) {
return NextResponse.json({
status: 'dead',
message: `Process ${pid} is not running`,
processInfo: null
})
}
// Get detailed process info
let detailedInfo: any = {}
try {
// Get working directory
const { stdout: cwdOut } = await execAsync(`readlink -f /proc/${pid}/cwd 2>/dev/null || echo "unknown"`)
detailedInfo.cwd = cwdOut.trim()
// Get command line
const { stdout: cmdOut } = await execAsync(`cat /proc/${pid}/cmdline 2>/dev/null | tr '\\0' ' ' || echo "unknown"`)
detailedInfo.cmdline = cmdOut.trim()
// Get file descriptors count
const { stdout: fdOut } = await execAsync(`ls /proc/${pid}/fd 2>/dev/null | wc -l || echo "0"`)
detailedInfo.openFiles = parseInt(fdOut.trim())
// Get threads count
const { stdout: threadsOut } = await execAsync(`ls /proc/${pid}/task 2>/dev/null | wc -l || echo "0"`)
detailedInfo.threads = parseInt(threadsOut.trim())
// Get memory details
const { stdout: memOut } = await execAsync(`cat /proc/${pid}/status 2>/dev/null | grep -E "^(VmRSS|VmSize|VmPeak):" || echo ""`)
const memLines = memOut.trim().split('\n')
memLines.forEach(line => {
const [key, value] = line.split(':').map(s => s.trim())
if (key && value) {
detailedInfo[key] = value
}
})
// Check for stuck indicators
const stuckIndicators = []
// Check CPU usage over time
if (processInfo.cpu < 0.1 && processInfo.state === 'S') {
// Get recent activity
const { stdout: statOut } = await execAsync(`cat /proc/${pid}/stat 2>/dev/null || echo ""`)
const statParts = statOut.split(' ')
if (statParts.length > 13) {
const utime = parseInt(statParts[13])
const stime = parseInt(statParts[14])
detailedInfo.cpuTicks = { user: utime, system: stime }
}
}
// Check if it's a ralph process and get its status
if (detailedInfo.cmdline.includes('ralph') || detailedInfo.cwd.includes('ralph')) {
try {
const { stdout: ralphOut } = await execAsync(`curl -s http://localhost:7300/api/ralph/status 2>/dev/null`)
const ralphStatus = JSON.parse(ralphOut)
detailedInfo.ralphStatus = {
running: ralphStatus.running,
currentStory: ralphStatus.currentStory,
progress: `${Object.values(ralphStatus.storyResults || {}).filter((s: any) => s === 'pass').length}/${Object.keys(ralphStatus.storyResults || {}).length}`,
lastLog: ralphStatus.logs?.slice(-3) || []
}
// Check if ralph is stuck (same story for too long with no progress)
if (ralphStatus.running && ralphStatus.elapsed > 3600) {
stuckIndicators.push('Ralph has been running for over an hour on the same story')
}
} catch {}
}
detailedInfo.stuckIndicators = stuckIndicators
} catch (err) {
console.error('Error getting detailed info:', err)
}
// Determine if process appears stuck
const isStuck =
processInfo.state === 'D' || // Uninterruptible sleep (usually I/O)
processInfo.state === 'T' || // Stopped
processInfo.state === 'Z' || // Zombie
(detailedInfo.stuckIndicators && detailedInfo.stuckIndicators.length > 0)
// Handle restart action
if (action === 'restart') {
try {
// Kill the process
await execAsync(`kill -9 ${pid}`)
// If it's ralph, try to restart it
if (detailedInfo.cmdline?.includes('ralph') || detailedInfo.cwd?.includes('ralph')) {
// Get the project path from ralph status or cwd
const projectPath = detailedInfo.cwd || '/root/Projects/angels-flowers'
return NextResponse.json({
status: 'restarted',
message: `Process ${pid} was killed. Ralph processes need to be restarted manually via the UI.`,
processInfo,
detailedInfo
})
}
return NextResponse.json({
status: 'killed',
message: `Process ${pid} was terminated`,
processInfo,
detailedInfo
})
} catch (err: any) {
return NextResponse.json({
status: 'error',
message: `Failed to kill process: ${err.message}`,
processInfo,
detailedInfo
})
}
}
return NextResponse.json({
status: isStuck ? 'stuck' : 'running',
message: isStuck
? `Process ${pid} appears to be stuck (state: ${processInfo.state})`
: `Process ${pid} is running normally`,
processInfo,
detailedInfo,
stateDescription: ({
'R': 'Running',
'S': 'Sleeping (waiting for event)',
'D': 'Uninterruptible sleep (usually I/O)',
'T': 'Stopped',
'Z': 'Zombie',
'X': 'Dead'
} as Record<string, string>)[processInfo.state] || processInfo.state
})
} catch (error: any) {
console.error('Check process error:', error)
return NextResponse.json({ error: error.message }, { status: 500 })
}
}