mirror of
https://github.com/hanzoai/agent.git
synced 2026-09-11 21:08:51 +00:00
Consolidate all Hanzo agent infrastructure into one repo: - control-plane/: Go control plane server (was agents-new) - sdk/: Go, Python, TypeScript SDKs - deployments/: Docker, Kubernetes, Helm configs - Dockerfile.sdk: Python SDK image (ghcr.io/hanzoai/agent:latest) - CI builds both SDK + control-plane images No more agentfield — everything is hanzoai/agent.
149 lines
4.3 KiB
Go
149 lines
4.3 KiB
Go
package process
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"syscall"
|
|
|
|
"github.com/hanzoai/agents/control-plane/internal/core/interfaces"
|
|
)
|
|
|
|
// DefaultProcessManager provides a default implementation for managing system processes.
|
|
// It keeps track of running processes and provides methods to start, stop, and monitor them.
|
|
type DefaultProcessManager struct {
|
|
runningProcesses map[int]*exec.Cmd
|
|
}
|
|
|
|
// NewProcessManager creates a new instance of DefaultProcessManager.
|
|
// It initializes the map for tracking running processes.
|
|
func NewProcessManager() interfaces.ProcessManager {
|
|
return &DefaultProcessManager{
|
|
runningProcesses: make(map[int]*exec.Cmd),
|
|
}
|
|
}
|
|
|
|
// Start initiates a new process based on the provided configuration.
|
|
// It returns the PID of the started process or an error if the process could not be started.
|
|
func (pm *DefaultProcessManager) Start(config interfaces.ProcessConfig) (pid int, err error) {
|
|
// Create the command with arguments
|
|
cmd := exec.Command(config.Command, config.Args...)
|
|
|
|
// Set working directory if specified
|
|
if config.WorkDir != "" {
|
|
cmd.Dir = config.WorkDir
|
|
}
|
|
|
|
// Set environment variables
|
|
if len(config.Env) > 0 {
|
|
// Start with current environment and add/override with provided variables
|
|
cmd.Env = append(os.Environ(), config.Env...)
|
|
}
|
|
|
|
// Handle log file redirection if specified
|
|
if config.LogFile != "" {
|
|
// Ensure log directory exists
|
|
logDir := filepath.Dir(config.LogFile)
|
|
if err := os.MkdirAll(logDir, 0755); err != nil {
|
|
return 0, fmt.Errorf("failed to create log directory %s: %w", logDir, err)
|
|
}
|
|
|
|
// Create or open log file
|
|
logFile, err := os.OpenFile(config.LogFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed to open log file %s: %w", config.LogFile, err)
|
|
}
|
|
|
|
// Redirect stdout and stderr to log file
|
|
cmd.Stdout = logFile
|
|
cmd.Stderr = logFile
|
|
|
|
// Note: We don't close the file here as the process needs it
|
|
// The file will be closed when the process exits
|
|
}
|
|
|
|
// Start the process
|
|
if err := cmd.Start(); err != nil {
|
|
return 0, fmt.Errorf("failed to start process '%s': %w", config.Command, err)
|
|
}
|
|
|
|
// Get the PID
|
|
pid = cmd.Process.Pid
|
|
|
|
// Track the running process
|
|
pm.runningProcesses[pid] = cmd
|
|
|
|
return pid, nil
|
|
}
|
|
|
|
// Stop terminates a process identified by its PID.
|
|
// It attempts graceful termination first, then forceful termination if necessary.
|
|
func (pm *DefaultProcessManager) Stop(pid int) error {
|
|
cmd, exists := pm.runningProcesses[pid]
|
|
if !exists {
|
|
return fmt.Errorf("process with PID %d not found in managed processes", pid)
|
|
}
|
|
|
|
// Check if process is still running
|
|
if cmd.Process == nil {
|
|
// Process already terminated, clean up
|
|
delete(pm.runningProcesses, pid)
|
|
return nil
|
|
}
|
|
|
|
// Try graceful termination first (SIGTERM)
|
|
if err := cmd.Process.Signal(syscall.SIGTERM); err != nil {
|
|
if !errors.Is(err, os.ErrProcessDone) {
|
|
// If SIGTERM fails, try forceful termination (SIGKILL)
|
|
if killErr := cmd.Process.Kill(); killErr != nil && !errors.Is(killErr, os.ErrProcessDone) {
|
|
return fmt.Errorf("failed to terminate process %d: SIGTERM failed (%v), SIGKILL failed (%v)", pid, err, killErr)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Wait for the process to actually terminate
|
|
// Ignore errors as process might have already exited
|
|
_, _ = cmd.Process.Wait()
|
|
|
|
// Clean up tracking
|
|
delete(pm.runningProcesses, pid)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Status retrieves the current status and information of a process identified by its PID.
|
|
func (pm *DefaultProcessManager) Status(pid int) (interfaces.ProcessInfo, error) {
|
|
cmd, exists := pm.runningProcesses[pid]
|
|
if !exists {
|
|
return interfaces.ProcessInfo{}, fmt.Errorf("process with PID %d not found in managed processes", pid)
|
|
}
|
|
|
|
// Build the command string for display
|
|
commandStr := cmd.Path
|
|
if len(cmd.Args) > 1 {
|
|
commandStr = fmt.Sprintf("%s %v", cmd.Path, cmd.Args[1:])
|
|
}
|
|
|
|
// Determine status
|
|
status := "stopped"
|
|
if cmd.Process != nil {
|
|
// Check if process is still running by sending signal 0
|
|
if err := cmd.Process.Signal(syscall.Signal(0)); err == nil {
|
|
status = "running"
|
|
} else {
|
|
// Clean up if process is no longer running
|
|
delete(pm.runningProcesses, pid)
|
|
}
|
|
} else {
|
|
delete(pm.runningProcesses, pid)
|
|
}
|
|
|
|
return interfaces.ProcessInfo{
|
|
PID: pid,
|
|
Status: status,
|
|
Command: commandStr,
|
|
}, nil
|
|
}
|